sw/inc/cmdid.h                      |    2 
 sw/qa/uibase/shells/shells.cxx      |   94 ++++++++++++++++++++++++
 sw/sdi/_textsh.sdi                  |    6 +
 sw/sdi/swriter.sdi                  |   16 +++-
 sw/source/uibase/shells/textsh1.cxx |  136 ++++++++++++++++++++++++++++++++++++
 5 files changed, 253 insertions(+), 1 deletion(-)

New commits:
commit 073d95d48d4fa98ffd22ead8c5ac0b21d986cea3
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Dec 14 16:06:37 2022 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Dec 16 08:09:12 2022 +0000

    sw: add a new .uno:UpdateBookmarks UNO command
    
    Current the .uno:InsertBookmark command allows inserting a bookmark with
    a provided content into the document, but an existing bookmark can't be
    updated similarly. This is a problem in case Zotero citations are to be
    modeled with bookmarks.
    
    Another trouble is that bookmarks don't have dummy characters, so we
    need to be careful to replace the content in a way that maintains these
    bookmarks, a naive delete + insert will collapse them.
    
    Fix the problem by introducing a new .uno:UpdateBookmarks command,
    somewhat similar to what commit 7765b442e13048f857fd7ee49ced1731caee297e
    (sw: add a new .uno:TextFormFields UNO command, 2022-11-28) did.
    
    As usual, the provided new text is meant to be HTML, which allows
    multi-paragraph, formatted content.
    
    (cherry picked from commit 724180ec495a696c79332653cb6fb52ecfbccc29)
    
    Conflicts:
            sw/inc/cmdid.h
            sw/qa/uibase/shells/shells.cxx
            sw/sdi/_textsh.sdi
            sw/sdi/swriter.sdi
    
    Change-Id: Id5bb3fd2fd9e147d98709b5a7e0c322e73c6d283
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144210
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h
index fc22a240fdac..43f4b12f55e3 100644
--- a/sw/inc/cmdid.h
+++ b/sw/inc/cmdid.h
@@ -194,6 +194,7 @@
 
 // Region: Insert
 #define FN_INSERT_BOOKMARK      (FN_INSERT + 2 )  /* Bookmark */
+// FN_INSERT + 3 is FN_INSERT_BREAK
 
 #define FN_INSERT_BREAK_DLG     (FN_INSERT + 4 )  /* Break */
 #define FN_INSERT_COLUMN_BREAK  (FN_INSERT + 5 )  /* Column break */
@@ -308,6 +309,7 @@
 #define FN_PASTE_NESTED_TABLE       (FN_INSERT2 + 30)  /* instead of the 
cell-by-cell copy between source and target tables */
 #define FN_TABLE_PASTE_ROW_BEFORE   (FN_INSERT2 + 31)  /* paste table as new 
table rows */
 #define FN_TABLE_PASTE_COL_BEFORE   (FN_INSERT2 + 32)  /* paste table as new 
table columns */
+#define FN_UPDATE_BOOKMARKS (FN_INSERT2 + 34)
 
 // Region: Format
 #define FN_AUTOFORMAT_APPLY     (FN_FORMAT + 1 ) /* apply autoformat options */
diff --git a/sw/qa/uibase/shells/shells.cxx b/sw/qa/uibase/shells/shells.cxx
index 18687d612011..9b2ecd1d1612 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -26,6 +26,8 @@
 #include <comphelper/propertyvalue.hxx>
 #include <xmloff/odffields.hxx>
 #include <comphelper/string.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/sequence.hxx>
 
 #include <IDocumentContentOperations.hxx>
 #include <cmdid.h>
@@ -375,6 +377,68 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testInsertBookmark)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testUpdateBookmarks)
+{
+    // Given a document with 2 bookmarks, first covering "B" and second 
covering "D":
+    SwDoc* pDoc = createSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->Insert("ABCDE");
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 1, 
/*bBasicCall=*/false);
+    pWrtShell->SetBookmark(vcl::KeyCode(), "ZOTERO_BREF_GiQ7DAWQYWLy");
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
+    pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 1, 
/*bBasicCall=*/false);
+    pWrtShell->SetBookmark(vcl::KeyCode(), "ZOTERO_BREF_PRxDGUb4SWXF");
+
+    // When updating the content of bookmarks:
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    std::vector<beans::PropertyValue> aArgsVec = 
comphelper::JsonToPropertyValues(R"json(
+{
+    "BookmarkNamePrefix": {
+        "type": "string",
+        "value": "ZOTERO_BREF_"
+    },
+    "Bookmarks": {
+        "type": "[][]com.sun.star.beans.PropertyValue",
+        "value": [
+            {
+                "Bookmark": {
+                    "type": "string",
+                    "value": "ZOTERO_BREF_GiQ7DAWQYWLy"
+                },
+                "BookmarkText": {
+                    "type": "string",
+                    "value": "new result 1"
+                }
+            },
+            {
+                "Bookmark": {
+                    "type": "string",
+                    "value": "ZOTERO_BREF_PRxDGUb4SWXF"
+                },
+                "BookmarkText": {
+                    "type": "string",
+                    "value": "new result 2"
+                }
+            }
+        ]
+    }
+}
+)json");
+    uno::Sequence<beans::PropertyValue> aArgs = 
comphelper::containerToSequence(aArgsVec);
+    dispatchCommand(mxComponent, ".uno:UpdateBookmarks", aArgs);
+
+    // Then make sure that the only paragraph is updated correctly:
+    SwCursor* pCursor = pWrtShell->GetCursor();
+    OUString aActual = 
pCursor->GetPoint()->nNode.GetNode().GetTextNode()->GetText();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: Anew result 1Cnew result 2E
+    // - Actual  : ABCDE
+    // i.e. the content was not updated.
+    CPPUNIT_ASSERT_EQUAL(OUString("Anew result 1Cnew result 2E"), aActual);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi
index 655efeeaf4d6..b03f90903868 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -140,6 +140,12 @@ interface BaseText
         StateMethod = GetState ;
         DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
     ]
+    FN_UPDATE_BOOKMARKS
+    [
+        ExecMethod = Execute ;
+        StateMethod = GetState ;
+        DisableFlags="SfxDisableFlags::SwOnProtectedCursor";
+    ]
     FN_SET_REMINDER
     [
         ExecMethod = Execute ;
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index a5f3239f7fd6..f001b70ebb90 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -2554,6 +2554,20 @@ SfxVoidItem InsertBookmark FN_INSERT_BOOKMARK
     GroupId = SfxGroupId::Insert;
 ]
 
+SfxVoidItem UpdateBookmarks FN_UPDATE_BOOKMARKS
+(SfxStringItem BookmarkNamePrefix FN_PARAM_1, SfxUnoAnyItem Bookmarks 
FN_PARAM_2)
+[
+    AutoUpdate = FALSE,
+    FastCall = FALSE,
+    ReadOnlyDoc = FALSE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    GroupId = SfxGroupId::Insert;
+]
+
 SfxVoidItem SetReminder FN_SET_REMINDER
 
 [
diff --git a/sw/source/uibase/shells/textsh1.cxx 
b/sw/source/uibase/shells/textsh1.cxx
index 151cdff40a63..4641aede858f 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -102,11 +102,13 @@
 #include <xmloff/odffields.hxx>
 #include <bookmark.hxx>
 #include <linguistic/misc.hxx>
+#include <comphelper/sequenceashashmap.hxx>
 #include <authfld.hxx>
 #include <translatelangselect.hxx>
 #include <svtools/deeplcfg.hxx>
 #include <translatehelper.hxx>
 #include <IDocumentContentOperations.hxx>
+#include <IDocumentUndoRedo.hxx>
 
 using namespace ::com::sun::star;
 using namespace com::sun::star::beans;
@@ -379,6 +381,92 @@ OUString GetLocalURL(const SwWrtShell& rSh)
     return rLocalURL;
 }
 
+void UpdateBookmarks(SfxRequest& rReq, SwWrtShell& rWrtSh)
+{
+    if 
(rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))
+    {
+        return;
+    }
+
+    OUString aBookmarkNamePrefix;
+    const SfxStringItem* pBookmarkNamePrefix = 
rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+    if (pBookmarkNamePrefix)
+    {
+        aBookmarkNamePrefix = pBookmarkNamePrefix->GetValue();
+    }
+
+    uno::Sequence<beans::PropertyValues> aBookmarks;
+    const SfxUnoAnyItem* pBookmarks = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_2);
+    if (pBookmarks)
+    {
+        pBookmarks->GetValue() >>= aBookmarks;
+    }
+
+    rWrtSh.GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSBOOKMARK, 
nullptr);
+    rWrtSh.StartAction();
+
+    IDocumentMarkAccess& rIDMA = *rWrtSh.GetDoc()->getIDocumentMarkAccess();
+    sal_Int32 nBookmarkIndex = 0;
+    bool bSortMarks = false;
+    for (auto it = rIDMA.getBookmarksBegin(); it != rIDMA.getBookmarksEnd(); 
++it)
+    {
+        auto pMark = dynamic_cast<sw::mark::Bookmark*>(*it);
+        assert(pMark);
+        if (!pMark->GetName().startsWith(aBookmarkNamePrefix))
+        {
+            continue;
+        }
+
+        if (aBookmarks.getLength() <= nBookmarkIndex)
+        {
+            continue;
+        }
+
+        comphelper::SequenceAsHashMap aMap(aBookmarks[nBookmarkIndex++]);
+        if (aMap["Bookmark"].get<OUString>() != pMark->GetName())
+        {
+            continue;
+        }
+
+        OUString aBookmarkText = aMap["BookmarkText"].get<OUString>();
+
+        // Insert markers to remember where the paste positions are.
+        SwPaM aMarkers(pMark->GetMarkEnd());
+        IDocumentContentOperations& rIDCO = 
rWrtSh.GetDoc()->getIDocumentContentOperations();
+        bool bSuccess = rIDCO.InsertString(aMarkers, "XY");
+        if (bSuccess)
+        {
+            SwPaM aPasteEnd(pMark->GetMarkEnd());
+            aPasteEnd.Move(fnMoveForward, GoInContent);
+
+            // Paste HTML content.
+            SwPaM* pCursorPos = rWrtSh.GetCursor();
+            *pCursorPos = aPasteEnd;
+            SwTranslateHelper::PasteHTMLToPaM(rWrtSh, pCursorPos, 
aBookmarkText.toUtf8(), true);
+
+            // Update the bookmark to point to the new content.
+            SwPaM aPasteStart(pMark->GetMarkEnd());
+            aPasteStart.Move(fnMoveForward, GoInContent);
+            SwPaM aStartMarker(pMark->GetMarkStart(), *aPasteStart.GetPoint());
+            SwPaM aEndMarker(*aPasteEnd.GetPoint(), *aPasteEnd.GetPoint());
+            aEndMarker.GetMark()->nContent += 1;
+            pMark->SetMarkPos(*aPasteStart.GetPoint());
+            pMark->SetOtherMarkPos(*aPasteEnd.GetPoint());
+            bSortMarks = true;
+
+            // Remove markers. the start marker includes the old content as 
well.
+            rIDCO.DeleteAndJoin(aStartMarker);
+            rIDCO.DeleteAndJoin(aEndMarker);
+        }
+    }
+    if (bSortMarks)
+    {
+        rIDMA.assureSortedMarkContainers();
+    }
+
+    rWrtSh.EndAction();
+    rWrtSh.GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSBOOKMARK, 
nullptr);
+}
 }
 
 void SwTextShell::Execute(SfxRequest &rReq)
@@ -752,6 +840,11 @@ void SwTextShell::Execute(SfxRequest &rReq)
 
             break;
         }
+        case FN_UPDATE_BOOKMARKS:
+        {
+            UpdateBookmarks(rReq, rWrtSh);
+            break;
+        }
         case FN_DELETE_BOOKMARK:
         {
             if (pItem && 
!rWrtSh.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))
commit 07e6d948ae2a6118bde4f2dce83b5f1e3684fa87
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Dec 6 14:41:45 2022 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Dec 16 08:09:01 2022 +0000

    sw, .uno:InsertBookmark: add a new BookmarkText parameter and accept HTML 
there
    
    There was already an UNO command to insert a new bookmark with the
    provided name, in a non-interactive way.
    
    What was missing is to allow specifying the bookmark text, which is to
    some extent not part of the bookmark, but e.g. the bookmark dialog
    allows editing that still.
    
    Add a new BookmarkText parameter to .uno:InsertBookmark, in case it's
    specified then we interpret this as HTML and we create the bookmark on
    the imported content, not simply at the current cursor position.
    
    This is similar to commit 1c2ef850db29beb369dcc89a58fc73416ecd9c5c (sw,
    .uno:TextFormField command: accept HTML in the FieldResult parameter,
    2022-11-16), but that was for the field mode, while this is for the
    bookmark mode of Zotero.
    
    (cherry picked from commit fa82e151d80d15eeb6dfae434f1dbb3b68907188)
    
    Conflicts:
            sw/source/uibase/shells/textsh1.cxx
    
    Change-Id: I4928d173e197796d40fdb53f81e84b6bfd77cdc5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144209
    Tested-by: Miklos Vajna <vmik...@collabora.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 e287ab6d23e6..18687d612011 100644
--- a/sw/qa/uibase/shells/shells.cxx
+++ b/sw/qa/uibase/shells/shells.cxx
@@ -345,6 +345,36 @@ CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, 
testUpdateFieldmarks)
     CPPUNIT_ASSERT_EQUAL(OUString("new result 1new result 2"), aActual);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUibaseShellsTest, testInsertBookmark)
+{
+    // Given an empty document:
+    SwDoc* pDoc = createSwDoc();
+
+    // When inserting a bookmark with text:
+    OUString aExpectedBookmarkName("ZOTERO_BREF_GiQ7DAWQYWLy");
+    uno::Sequence<css::beans::PropertyValue> aArgs = {
+        comphelper::makePropertyValue("Bookmark", 
uno::Any(aExpectedBookmarkName)),
+        comphelper::makePropertyValue("BookmarkText", 
uno::Any(OUString("<p>aaa</p><p>bbb</p>"))),
+    };
+    dispatchCommand(mxComponent, ".uno:InsertBookmark", aArgs);
+
+    // Then make sure that we create a bookmark that covers that text:
+    IDocumentMarkAccess& rIDMA = *pDoc->getIDocumentMarkAccess();
+    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), rIDMA.getBookmarksCount());
+    for (auto it = rIDMA.getBookmarksBegin(); it != rIDMA.getBookmarksEnd(); 
++it)
+    {
+        sw::mark::IMark* pMark = *it;
+        CPPUNIT_ASSERT_EQUAL(aExpectedBookmarkName, pMark->GetName());
+        SwPaM aPam(pMark->GetMarkStart(), pMark->GetMarkEnd());
+        OUString aActualResult = aPam.GetText();
+        // Without the accompanying fix in place, this test would have failed 
with:
+        // - Expected: aaa\nbbb
+        // - Actual  :
+        // i.e. no text was inserted, the bookmark was collapsed.
+        CPPUNIT_ASSERT_EQUAL(OUString("aaa\nbbb"), aActualResult);
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi
index d09534ae3686..a5f3239f7fd6 100644
--- a/sw/sdi/swriter.sdi
+++ b/sw/sdi/swriter.sdi
@@ -2537,7 +2537,7 @@ SfxVoidItem InsertAuthoritiesEntry 
FN_INSERT_AUTH_ENTRY_DLG
 ]
 
 SfxVoidItem InsertBookmark FN_INSERT_BOOKMARK
-(SfxStringItem Bookmark FN_INSERT_BOOKMARK)
+(SfxStringItem Bookmark FN_INSERT_BOOKMARK, SfxStringItem BookmarkText 
FN_PARAM_1)
 [
     AutoUpdate = FALSE,
     FastCall = FALSE,
diff --git a/sw/source/uibase/shells/textsh1.cxx 
b/sw/source/uibase/shells/textsh1.cxx
index 53813ff905e6..151cdff40a63 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -106,6 +106,7 @@
 #include <translatelangselect.hxx>
 #include <svtools/deeplcfg.hxx>
 #include <translatehelper.hxx>
+#include <IDocumentContentOperations.hxx>
 
 using namespace ::com::sun::star;
 using namespace com::sun::star::beans;
@@ -693,10 +694,52 @@ void SwTextShell::Execute(SfxRequest &rReq)
         }
         case FN_INSERT_BOOKMARK:
         {
+            const SfxStringItem* pBookmarkText = 
rReq.GetArg<SfxStringItem>(FN_PARAM_1);
+            SwPaM* pCursorPos = rWrtSh.GetCursor();
             if ( pItem )
             {
+                rWrtSh.StartAction();
                 OUString sName = static_cast<const 
SfxStringItem*>(pItem)->GetValue();
+
+                if (pBookmarkText)
+                {
+                    OUString aBookmarkText = pBookmarkText->GetValue();
+                    // Split node to remember where the start position is.
+                    bool bSuccess = 
rWrtSh.GetDoc()->getIDocumentContentOperations().SplitNode(
+                        *pCursorPos->GetPoint(), /*bChkTableStart=*/false);
+                    if (bSuccess)
+                    {
+                        SwPaM aBookmarkPam(*pCursorPos->GetPoint());
+                        aBookmarkPam.Move(fnMoveBackward, GoInContent);
+
+                        // Paste HTML content.
+                        SwTranslateHelper::PasteHTMLToPaM(
+                            rWrtSh, pCursorPos, aBookmarkText.toUtf8(), 
/*bSetSelection=*/true);
+                        if (pCursorPos->GetPoint()->nContent == 0)
+                        {
+                            // The paste created a last empty text node, 
remove it.
+                            SwPaM aPam(*pCursorPos->GetPoint());
+                            aPam.SetMark();
+                            aPam.Move(fnMoveBackward, GoInContent);
+                            
rWrtSh.GetDoc()->getIDocumentContentOperations().DeleteAndJoin(aPam);
+                        }
+
+                        // Undo the above SplitNode().
+                        aBookmarkPam.SetMark();
+                        aBookmarkPam.Move(fnMoveForward, GoInContent);
+                        
rWrtSh.GetDoc()->getIDocumentContentOperations().DeleteAndJoin(
+                            aBookmarkPam);
+                        *aBookmarkPam.GetMark() = *pCursorPos->GetPoint();
+                        *pCursorPos = aBookmarkPam;
+                    }
+                }
+
                 rWrtSh.SetBookmark( vcl::KeyCode(), sName );
+                if (pBookmarkText)
+                {
+                    pCursorPos->DeleteMark();
+                }
+                rWrtSh.EndAction();
             }
             else
             {

Reply via email to