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

New commits:
commit 87add1240a011b05f7501b902ae4edfa8fbe7367
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Jan 4 08:56:04 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Jan 6 14:55:50 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.
    
    (cherry picked from commit babba472391d26aed68d7ac31c7a918c08e65256)
    
    Also contains:
    
    sw update of refmarks: fix handling of ignored refmarks
    
    As pointed out at
    
<https://gerrit.libreoffice.org/c/core/+/145036/1#message-165c0282ace92160592c896c6867095d3558ee96>,
    it's not correct that UpdateFieldConents() uses a single index into both
    the provided fields/refmarks array and into the document's all refmarks
    array.
    
    We need a new index that counts the input fields/refmarks we got and
    which is not incremented for ignored refmarks.
    
    Extend the testcase to have 2 paragraphs in the document: the first
    paragraph has a refmark that is to be ignored and the second para has a
    refmark that is interesting to us. This makes the test fail.
    
    Then fix up UpdateFieldConents() to properly count how we iterate
    through all refmarks and the provided refmarks in parallel.
    
    (cherry picked from commit 471804e251b4e15b37a10920bd4b88b40f97b227)
    
    Conflicts:
            sw/qa/uibase/shells/shells.cxx
    
    Also contains:
    
    sw: UpdateFieldContents: fix typos
    
    - The function name itself had a typo, spotted at
      
<https://gerrit.libreoffice.org/c/core/+/145036/2#message-d8cb4de7de483866e0c86c8919cdf47f84b6037e>.
    
    - Also, if the # of provided fields and # of fields we find in the
      document don't match, we can give up, so no need to continue, the same
      condition would fail again, anyway.
    
    (cherry picked from commit f83c1353b94fc7dec79d05ac45c11f40f497261d)
    
    Change-Id: Ib0951aa1a39e1b47bcf8b47bc9d65c89e0853e96
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145036
    Tested-by: Miklos Vajna <vmik...@collabora.com>
    Reviewed-by: Justin Luth <jl...@mail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 88324d12327e..ced0157284ae 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -469,6 +469,68 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testInsertFieldmarkReadonly)
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), nActual);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateRefmarks)
+{
+    // Given a document with two refmarks, one is not interesting the other is 
a citation:
+    SwDoc* pDoc = createSwDoc();
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("TypeName", 
uno::Any(OUString("SetRef"))),
+        comphelper::makePropertyValue("Name", uno::Any(OUString("some other 
old refmark"))),
+        comphelper::makePropertyValue("Content", uno::Any(OUString("some other 
old content"))),
+    };
+    dispatchCommand(mxComponent, ".uno:InsertField", aArgs);
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->SttEndDoc(/*bStt=*/false);
+    pWrtShell->SplitNode();
+    pWrtShell->SttEndDoc(/*bStt=*/false);
+    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:
+    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()->GetNode().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 11ecc7c465e1..2c644638443e 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -6482,7 +6482,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 a5ea0e6b1012..0e2f6769167c 100644
--- a/sw/source/uibase/shells/basesh.cxx
+++ b/sw/source/uibase/shells/basesh.cxx
@@ -89,6 +89,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>
@@ -97,12 +98,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::eFrameMode = FLY_DRAG_END;
 
@@ -763,6 +768,103 @@ 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 UpdateFieldContents(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();
+    sal_uInt16 nFieldIndex = 0;
+    for (sal_uInt16 nRefMark = 0; nRefMark < pDoc->GetRefMarks(); ++nRefMark)
+    {
+        auto pRefMark = 
const_cast<SwFormatRefMark*>(pDoc->GetRefMark(nRefMark));
+        if (!pRefMark->GetRefName().startsWith(rNamePrefix))
+        {
+            continue;
+        }
+
+        if (nFieldIndex >= aFields.getLength())
+        {
+            break;
+        }
+        comphelper::SequenceAsHashMap aMap(aFields[nFieldIndex++]);
+        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(const_cast<SwTextNode&>(rTextNode), 
*pTextRefMark->End()));
+        IDocumentContentOperations& rIDCO = 
pDoc->getIDocumentContentOperations();
+        pTextRefMark->SetDontExpand(false);
+        bool bSuccess = rIDCO.InsertString(aMarkers, "XY");
+        if (bSuccess)
+        {
+            SwPaM aPasteEnd(SwPosition(const_cast<SwTextNode&>(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()->nContent.GetIndex());
+            // 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()->nContent += 1;
+            SwPaM aStartMarker(SwPosition(const_cast<SwTextNode&>(rTextNode), 
nOldStart), SwPosition(const_cast<SwTextNode&>(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)
@@ -785,6 +887,13 @@ void SwBaseShell::Execute(SfxRequest &rReq)
             break;
         case FN_UPDATE_FIELDS:
             {
+                if (UpdateFieldContents(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