sw/qa/uibase/shells/shells.cxx     |   55 ++++++++++++++++++
 sw/sdi/swriter.sdi                 |    2 
 sw/source/uibase/shells/basesh.cxx |  108 +++++++++++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+), 1 deletion(-)

New commits:
commit babba472391d26aed68d7ac31c7a918c08e65256
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Jan 4 08:56:04 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Wed Jan 4 08:41:53 2023 +0000

    sw, UpdateFields: add new TypeName, NamePrefix and Fields parameters
    
    Currently the .uno:InsertField command allows inserting a refmark with a
    provided name & content, but existing refmarks can't be updated
    similarly. This is a problem in case Zotero citations are to be modeled
    with refmarks.
    
    Another trouble is that refmarks don't have dummy characters and have to
    stay inside a single paragraph, so we need to be careful to replace the
    content in a way that keeps the refmark alive, a naive delete + insert
    will delete the refmark as well.
    
    Fix the problem by extending the existing .uno:UpdateFields command with
    3 new optional parameters, somewhat similar to what commit
    724180ec495a696c79332653cb6fb52ecfbccc29 (sw: add a new
    .uno:UpdateBookmarks UNO command, 2022-12-14) did.
    
    As usual, the provided new text is meant to be HTML, which allows
    formatted content.
    
    Change-Id: Ib0951aa1a39e1b47bcf8b47bc9d65c89e0853e96
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145033
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 2f89720cf570..0348965b234c 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -532,6 +532,61 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testInsertFieldmarkReadonly)
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), nActual);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateRefmarks)
+{
+    // Given a document with a refmark:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("TypeName", 
uno::Any(OUString("SetRef"))),
+        comphelper::makePropertyValue(
+            "Name", uno::Any(OUString("ZOTERO_ITEM CSL_CITATION {} old 
refmark"))),
+        comphelper::makePropertyValue("Content", uno::Any(OUString("old 
content"))),
+    };
+    dispatchCommand(mxComponent, ".uno:InsertField", aArgs);
+
+    // When updating that refmark:
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "TypeName": {
+        "type": "string",
+        "value": "SetRef"
+    },
+    "NamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_ITEM CSL_CITATION"
+    },
+    "Fields": {
+        "type": "[][]com.sun.star.beans.PropertyValue",
+        "value": [
+            {
+                "Name": {
+                    "type": "string",
+                    "value": "ZOTERO_ITEM CSL_CITATION {} new refmark"
+                },
+                "Content": {
+                    "type": "string",
+                    "value": "new content"
+                }
+            }
+        ]
+    }
+}
+)json");
+    aArgs = comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:UpdateFields", aArgs);
+
+    // Then make sure that the document text features the new content:
+    SwTextNode* pTextNode = 
pWrtShell->GetCursor()->GetPointNode().GetTextNode();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: new content
+    // - Actual  : old content
+    // i.e. the doc content was not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("new content"), pTextNode->GetText());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index e14b52cba879..ebd54c064402 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -6514,7 +6514,7 @@ SfxVoidItem UpdateCurIndex FN_UPDATE_CUR_TOX
 ]
 
 SfxVoidItem UpdateFields FN_UPDATE_FIELDS
-()
+(SfxStringItem TypeName FN_PARAM_1, SfxStringItem NamePrefix FN_PARAM_2, 
SfxUnoAnyItem Fields FN_PARAM_3)
 [
     AutoUpdate = FALSE,
     FastCall = TRUE,
diff --git a/sw/source/uibase/shells/basesh.cxx 
b/sw/source/uibase/shells/basesh.cxx
index 5e13fece9132..81eaec0f7c41 100644
--- a/sw/source/uibase/shells/basesh.cxx
+++ b/sw/source/uibase/shells/basesh.cxx
@@ -90,6 +90,7 @@
 #include <svx/galleryitem.hxx>
 #include <sfx2/devtools/DevelopmentToolChildWindow.hxx>
 #include <com/sun/star/gallery/GalleryItemType.hpp>
+#include <com/sun/star/beans/PropertyValues.hpp>
 #include <memory>
 
 #include <svx/unobrushitemhelper.hxx>
@@ -98,12 +99,16 @@
 #include <osl/diagnose.h>
 
 #include <svx/svxdlg.hxx>
+#include <comphelper/sequenceashashmap.hxx>
 
 #include <shellres.hxx>
 #include <UndoTable.hxx>
 
 #include <ndtxt.hxx>
 #include <UndoManager.hxx>
+#include <fmtrfmrk.hxx>
+#include <txtrfmrk.hxx>
+#include <translatehelper.hxx>
 
 FlyMode SwBaseShell::s_eFrameMode = FLY_DRAG_END;
 
@@ -765,6 +770,102 @@ void SwBaseShell::StateUndo(SfxItemSet &rSet)
     }
 }
 
+namespace
+{
+/// Searches for the specified field type and field name prefix and update the 
matching fields to
+/// have the provided new name and content.
+bool UpdateFieldConents(SfxRequest& rReq, SwWrtShell& rWrtSh)
+{
+    const SfxStringItem* pTypeName = rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+    if (!pTypeName || pTypeName->GetValue() != "SetRef")
+    {
+        // This is implemented so far only for reference marks.
+        return false;
+    }
+
+    const SfxStringItem* pNamePrefix = rReq.GetArg<SfxStringItem>(FN_PARAM_2);
+    if (!pNamePrefix)
+    {
+        return false;
+    }
+    const OUString& rNamePrefix = pNamePrefix->GetValue();
+
+    const SfxUnoAnyItem* pFields = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_3);
+    if (!pFields)
+    {
+        return false;
+    }
+    uno::Sequence<beans::PropertyValues> aFields;
+    pFields->GetValue() >>= aFields;
+
+    SwDoc* pDoc = rWrtSh.GetDoc();
+    pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, nullptr);
+    rWrtSh.StartAction();
+    for (sal_uInt16 nRefMark = 0; nRefMark < pDoc->GetRefMarks(); ++nRefMark)
+    {
+        auto pRefMark = 
const_cast<SwFormatRefMark*>(pDoc->GetRefMark(nRefMark));
+        if (!pRefMark->GetRefName().startsWith(rNamePrefix))
+        {
+            continue;
+        }
+
+        if (aFields.getLength() <= nRefMark)
+        {
+            continue;
+        }
+        comphelper::SequenceAsHashMap aMap(aFields[nRefMark]);
+        auto aName = aMap["Name"].get<OUString>();
+        pRefMark->GetRefName() = aName;
+
+        OUString aContent = aMap["Content"].get<OUString>();
+        auto pTextRefMark = 
const_cast<SwTextRefMark*>(pRefMark->GetTextRefMark());
+        if (!pTextRefMark->End())
+        {
+            continue;
+        }
+
+        // Insert markers to remember where the paste positions are.
+        const SwTextNode& rTextNode = pTextRefMark->GetTextNode();
+        SwPaM aMarkers(SwPosition(rTextNode, *pTextRefMark->End()));
+        IDocumentContentOperations& rIDCO = 
pDoc->getIDocumentContentOperations();
+        pTextRefMark->SetDontExpand(false);
+        bool bSuccess = rIDCO.InsertString(aMarkers, "XY");
+        if (bSuccess)
+        {
+            SwPaM aPasteEnd(SwPosition(rTextNode, *pTextRefMark->End()));
+            aPasteEnd.Move(fnMoveBackward, GoInContent);
+
+            // Paste HTML content.
+            SwPaM* pCursorPos = rWrtSh.GetCursor();
+            *pCursorPos = aPasteEnd;
+            SwTranslateHelper::PasteHTMLToPaM(rWrtSh, pCursorPos, 
aContent.toUtf8(), true);
+
+            // Update the refmark to point to the new content.
+            sal_Int32 nOldStart = pTextRefMark->GetStart();
+            sal_Int32 nNewStart = *pTextRefMark->End();
+            // First grow it to include text till the end of the paste 
position.
+            pTextRefMark->SetEnd(aPasteEnd.GetPoint()->GetContentIndex());
+            // Then shrink it to only start at the paste start: we know that 
the refmark was
+            // truncated to the paste start, as the refmark has to stay inside 
a single text node
+            pTextRefMark->SetStart(nNewStart);
+            rTextNode.GetSwpHints().SortIfNeedBe();
+            SwPaM aEndMarker(*aPasteEnd.GetPoint());
+            aEndMarker.SetMark();
+            aEndMarker.GetMark()->AdjustContent(1);
+            SwPaM aStartMarker(SwPosition(rTextNode, nOldStart), 
SwPosition(rTextNode, nNewStart));
+
+            // Remove markers. The start marker includes the old content as 
well.
+            rIDCO.DeleteAndJoin(aStartMarker);
+            rIDCO.DeleteAndJoin(aEndMarker);
+        }
+    }
+
+    rWrtSh.EndAction();
+    pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, nullptr);
+    return true;
+}
+}
+
 // Evaluate respectively dispatching the slot Id
 
 void SwBaseShell::Execute(SfxRequest &rReq)
@@ -787,6 +888,13 @@ void SwBaseShell::Execute(SfxRequest &rReq)
             break;
         case FN_UPDATE_FIELDS:
             {
+                if (UpdateFieldConents(rReq, rSh))
+                {
+                    // Parameters indicated that the name / content of fields 
has to be updated to
+                    // the provided values, don't do an actual fields update.
+                    break;
+                }
+
                 rSh.UpdateDocStat();
                 rSh.EndAllTableBoxEdit();
                 rSh.SwViewShell::UpdateFields(true);

Reply via email to