diff options
author | Michael Stahl <michael.stahl@allotropia.de> | 2021-01-28 19:59:35 +0100 |
---|---|---|
committer | Michael Stahl <michael.stahl@allotropia.de> | 2021-02-02 18:16:43 +0100 |
commit | 7685c0746cf0db6f51c6a7a488f4a960f8eab3c9 (patch) | |
tree | 9e68984782579ae58f685354b7f816a60f4807e9 /sw | |
parent | a2e533767bed88f293d2129aca9dfd9c4c60226b (diff) |
tdf#121842 sw: add hyperlinks to toxmarks in ToC/User-Defined Index
The toxmark is identified by the type of index, the name of the index
type (only for user-defined; there is only one ToC type), the text
(either text:string-value or text content of the toxmark),
and a counter to distinguish marks with the same text.
Both text and type name can contain arbitrary characters so use U+0019
control character as separator.
Links look like: #1%19text%19Utypename|toxmark
Change-Id: I5aeec727e2cd3a02d676cf3ea4c302bf7c77d319
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110091
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
Diffstat (limited to 'sw')
-rw-r--r-- | sw/inc/ToxTextGenerator.hxx | 5 | ||||
-rw-r--r-- | sw/inc/swtypes.hxx | 2 | ||||
-rw-r--r-- | sw/qa/extras/uiwriter/data3/udindex3.odt | bin | 0 -> 11068 bytes | |||
-rw-r--r-- | sw/qa/extras/uiwriter/uiwriter3.cxx | 125 | ||||
-rw-r--r-- | sw/source/core/doc/doctxm.cxx | 10 | ||||
-rw-r--r-- | sw/source/core/inc/txmsrt.hxx | 7 | ||||
-rw-r--r-- | sw/source/core/tox/ToxTextGenerator.cxx | 16 | ||||
-rw-r--r-- | sw/source/core/tox/txmsrt.cxx | 44 | ||||
-rw-r--r-- | sw/source/uibase/uiview/view2.cxx | 81 |
9 files changed, 269 insertions, 21 deletions
diff --git a/sw/inc/ToxTextGenerator.hxx b/sw/inc/ToxTextGenerator.hxx index 675835573266..d3a7e9177db0 100644 --- a/sw/inc/ToxTextGenerator.hxx +++ b/sw/inc/ToxTextGenerator.hxx @@ -27,6 +27,7 @@ #include <memory> #include <vector> +#include <unordered_map> class SfxItemSet; class SwAttrPool; @@ -67,7 +68,9 @@ public: * process @p numberOfEntriesToProcess entries. */ void - GenerateText(SwDoc *doc, const std::vector<std::unique_ptr<SwTOXSortTabBase>>& entries, + GenerateText(SwDoc *doc, + std::unordered_map<OUString, int> & rMarkURLs, + const std::vector<std::unique_ptr<SwTOXSortTabBase>>& entries, sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess, SwRootFrame const* pLayout); diff --git a/sw/inc/swtypes.hxx b/sw/inc/swtypes.hxx index 24e9c248b167..c45ce1a5a216 100644 --- a/sw/inc/swtypes.hxx +++ b/sw/inc/swtypes.hxx @@ -129,6 +129,8 @@ SW_DLLPUBLIC Size GetGraphicSizeTwip( const Graphic&, vcl::RenderContext* pOutDe const sal_Unicode cMarkSeparator = '|'; // Sequences names for jumps are <name of sequence>!<no> const char cSequenceMarkSeparator = '!'; +/// separator for toxmarks: #<no>%19<text>%19<type><typename>|toxmark +sal_Unicode const toxMarkSeparator = '\u0019'; #define DB_DELIM u'\x00ff' // Database <-> table separator. diff --git a/sw/qa/extras/uiwriter/data3/udindex3.odt b/sw/qa/extras/uiwriter/data3/udindex3.odt Binary files differnew file mode 100644 index 000000000000..e6c7736b91e4 --- /dev/null +++ b/sw/qa/extras/uiwriter/data3/udindex3.odt diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx b/sw/qa/extras/uiwriter/uiwriter3.cxx index f6b67313889f..87f2cc338038 100644 --- a/sw/qa/extras/uiwriter/uiwriter3.cxx +++ b/sw/qa/extras/uiwriter/uiwriter3.cxx @@ -28,6 +28,8 @@ #include <tools/json_writer.hxx> #include <unotools/streamwrap.hxx> +#include <fmtinfmt.hxx> +#include <view.hxx> #include <wrtsh.hxx> #include <unotxdoc.hxx> #include <docsh.hxx> @@ -656,6 +658,129 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf124722) CPPUNIT_ASSERT_EQUAL(22, getPages()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testToxmarkLinks) +{ + load(DATA_DIRECTORY, "udindex3.odt"); + + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + CPPUNIT_ASSERT(pTextDoc); + SwWrtShell& rWrtShell(*pTextDoc->GetDocShell()->GetWrtShell()); + SwView& rView(*pTextDoc->GetDocShell()->GetView()); + + // update indexes + for (auto i = rWrtShell.GetTOXCount(); 0 < i;) + { + --i; + rWrtShell.UpdateTableOf(*rWrtShell.GetTOX(i)); + } + + // click on the links... + { + OUString const tmp("Table of Contents"); + rWrtShell.GotoNextTOXBase(&tmp); + } + + { // ToC toxmark + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#1%19the%20tocmark%19C%7Ctoxmark"), url); + rView.JumpToSwMark(url.copy(1)); // SfxApplication::OpenDocExec_Impl eats the "#" + CPPUNIT_ASSERT_EQUAL(OUString(OUStringChar(CH_TXTATR_INWORD) + "tocmark"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + + { // ToC heading + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#__RefHeading___Toc105_706348105"), url); + rView.JumpToSwMark(url.copy(1)); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + + { + OUString const tmp("User-Defined1"); + rWrtShell.GotoNextTOXBase(&tmp); + } + + { // UD1 toxmark 1 + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#1%19the%20udmark%19UUser-Defined%7Ctoxmark"), url); + rView.JumpToSwMark(url.copy(1)); + CPPUNIT_ASSERT_EQUAL(OUString(OUStringChar(CH_TXTATR_INWORD) + "udmark the first"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + + { // UD1 toxmark 2 (with same text) + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#2%19the%20udmark%19UUser-Defined%7Ctoxmark"), url); + rView.JumpToSwMark(url.copy(1)); + CPPUNIT_ASSERT_EQUAL(OUString(OUStringChar(CH_TXTATR_INWORD) + "udmark the 2nd"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + + { // UD heading + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#__RefHeading___Toc105_706348105"), url); + rView.JumpToSwMark(url.copy(1)); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + + { + OUString const tmp("NewUD!|1"); + rWrtShell.GotoNextTOXBase(&tmp); + } + + { // UD2 toxmark, with same text as those in other UD + rWrtShell.Down(false); + SfxItemSet aSet(rWrtShell.GetAttrPool(), + svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{}); + rWrtShell.GetCurAttr(aSet); + CPPUNIT_ASSERT(aSet.HasItem(RES_TXTATR_INETFMT)); + rWrtShell.Push(); + OUString const url(aSet.GetItem<SwFormatINetFormat>(RES_TXTATR_INETFMT)->GetValue()); + CPPUNIT_ASSERT_EQUAL(OUString("#1%19the%20udmark%19UNewUD!%7C%7Ctoxmark"), url); + rView.JumpToSwMark(url.copy(1)); + CPPUNIT_ASSERT_EQUAL(OUString("the udmark"), + rWrtShell.GetCursor()->GetNode().GetTextNode()->GetText()); + rWrtShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf125261) { load(DATA_DIRECTORY, "tdf125261.odt"); diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx index d367ce54a482..d8e7d30e80e1 100644 --- a/sw/source/core/doc/doctxm.cxx +++ b/sw/source/core/doc/doctxm.cxx @@ -229,11 +229,8 @@ const SwTOXMark& SwDoc::GotoTOXMark( const SwTOXMark& rCurTOXMark, SwTOXSearch eDir, bool bInReadOnly ) { const SwTextTOXMark* pMark = rCurTOXMark.GetTextTOXMark(); - OSL_ENSURE(pMark, "pMark==0 invalid TextTOXMark"); - const SwTextNode *pTOXSrc = pMark->GetpTextNd(); - - CompareNodeContent aAbsIdx( pTOXSrc->GetIndex(), pMark->GetStart() ); + CompareNodeContent aAbsIdx(pMark ? pMark->GetpTextNd()->GetIndex() : 0, pMark ? pMark->GetStart() : 0); CompareNodeContent aPrevPos( 0, 0 ); CompareNodeContent aNextPos( ULONG_MAX, SAL_MAX_INT32 ); CompareNodeContent aMax( 0, 0 ); @@ -256,7 +253,7 @@ const SwTOXMark& SwDoc::GotoTOXMark( const SwTOXMark& rCurTOXMark, if (!pMark) continue; - pTOXSrc = pMark->GetpTextNd(); + SwTextNode const*const pTOXSrc = pMark->GetpTextNd(); if (!pTOXSrc) continue; @@ -982,6 +979,7 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, // Sort the List of all TOC Marks and TOC Sections std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr ); + std::unordered_map<OUString, int> markURLs; SwNodeIndex aInsPos( *pFirstEmptyNd, 1 ); for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) { @@ -1034,7 +1032,7 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_INDENT : sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_PAGE); sw::ToxTextGenerator ttgn(GetTOXForm(), tabStopTokenHandler); - ttgn.GenerateText(GetFormat()->GetDoc(), m_aSortArr, nCnt, nRange, pLayout); + ttgn.GenerateText(GetFormat()->GetDoc(), markURLs, m_aSortArr, nCnt, nRange, pLayout); nCnt += nRange - 1; } diff --git a/sw/source/core/inc/txmsrt.hxx b/sw/source/core/inc/txmsrt.hxx index dd8cc50412f0..c4c91b734a00 100644 --- a/sw/source/core/inc/txmsrt.hxx +++ b/sw/source/core/inc/txmsrt.hxx @@ -148,7 +148,7 @@ struct SwTOXSortTabBase virtual bool equivalent( const SwTOXSortTabBase& ); virtual bool sort_lt( const SwTOXSortTabBase& ); - virtual OUString GetURL() const; + virtual std::pair<OUString, bool> GetURL(SwRootFrame const*const pLayout) const; virtual bool IsFullPara() const; @@ -251,7 +251,7 @@ struct SwTOXPara final : public SwTOXSortTabBase sal_uInt16 nAuthField, SwRootFrame const* pLayout) const override; virtual sal_uInt16 GetLevel() const override; - virtual OUString GetURL() const override; + virtual std::pair<OUString, bool> GetURL(SwRootFrame const*const pLayout) const override; virtual bool IsFullPara() const override; private: virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; @@ -271,7 +271,8 @@ struct SwTOXTable final : public SwTOXSortTabBase virtual sal_uInt16 GetLevel() const override; - virtual OUString GetURL() const override; + virtual std::pair<OUString, bool> GetURL(SwRootFrame const*const pLayout) const override; + private: virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const override; diff --git a/sw/source/core/tox/ToxTextGenerator.cxx b/sw/source/core/tox/ToxTextGenerator.cxx index ba76ecdaaa62..d836ec60f238 100644 --- a/sw/source/core/tox/ToxTextGenerator.cxx +++ b/sw/source/core/tox/ToxTextGenerator.cxx @@ -164,7 +164,9 @@ ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, c // Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control, // which page description is used, no appropriate one is found. void -ToxTextGenerator::GenerateText(SwDoc* pDoc, const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries, +ToxTextGenerator::GenerateText(SwDoc* pDoc, + std::unordered_map<OUString, int> & rMarkURLs, + const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries, sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess, SwRootFrame const*const pLayout) { @@ -239,7 +241,17 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, const std::vector<std::unique_ptr<Sw break; case TOKEN_LINK_END: - mLinkProcessor->CloseLink(rText.getLength(), rBase.GetURL()); + { + auto [url, isMark] = rBase.GetURL(pLayout); + if (isMark) + { + auto [iter, _] = rMarkURLs.emplace(url, 0); + (void) _; // sigh... ignore it more explicitly + ++iter->second; + url = "#" + OUString::number(iter->second) + url; + } + mLinkProcessor->CloseLink(rText.getLength(), url); + } break; case TOKEN_AUTHORITY: diff --git a/sw/source/core/tox/txmsrt.cxx b/sw/source/core/tox/txmsrt.cxx index 7aefd76721a9..f626f09afc40 100644 --- a/sw/source/core/tox/txmsrt.cxx +++ b/sw/source/core/tox/txmsrt.cxx @@ -19,6 +19,7 @@ #include <unotools/charclass.hxx> #include <osl/diagnose.h> +#include <rtl/uri.hxx> #include <txtfld.hxx> #include <doc.hxx> #include <IDocumentLayoutAccess.hxx> @@ -179,9 +180,34 @@ SwTOXSortTabBase::SwTOXSortTabBase( TOXSortType nTyp, const SwContentNode* pNd, } } -OUString SwTOXSortTabBase::GetURL() const +std::pair<OUString, bool> SwTOXSortTabBase::GetURL(SwRootFrame const*const pLayout) const { - return OUString(); + OUString typeName; + SwTOXType const& rType(*pTextMark->GetTOXMark().GetTOXType()); + switch (rType.GetType()) + { + case TOX_INDEX: + typeName = "A"; + break; + case TOX_CONTENT: + typeName = "C"; + break; + case TOX_USER: + typeName = "U" + rType.GetTypeName(); + break; + default: + assert(false); // other tox can't have toxmarks as source + break; + } + OUString const decodedUrl( // counter will be added by caller! + OUStringChar(toxMarkSeparator) + pTextMark->GetTOXMark().GetText(pLayout) + + OUStringChar(toxMarkSeparator) + typeName + + OUStringChar(cMarkSeparator) + "toxmark" ); + + OUString const uri(rtl::Uri::encode(decodedUrl, rtl_UriCharClassUricNoSlash, + rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8)); + + return std::make_pair(uri, true); } bool SwTOXSortTabBase::IsFullPara() const @@ -645,7 +671,7 @@ sal_uInt16 SwTOXPara::GetLevel() const return nRet; } -OUString SwTOXPara::GetURL() const +std::pair<OUString, bool> SwTOXPara::GetURL(SwRootFrame const*const) const { OUString aText; const SwContentNode* pNd = aTOXSources[0].pNd; @@ -696,7 +722,7 @@ OUString SwTOXPara::GetURL() const break; default: break; } - return aText; + return std::make_pair(aText, false); } bool SwTOXPara::IsFullPara() const @@ -741,21 +767,21 @@ sal_uInt16 SwTOXTable::GetLevel() const return nLevel; } -OUString SwTOXTable::GetURL() const +std::pair<OUString, bool> SwTOXTable::GetURL(SwRootFrame const*const) const { const SwNode* pNd = aTOXSources[0].pNd; if (!pNd) - return OUString(); + return std::make_pair(OUString(), false); pNd = pNd->FindTableNode(); if (!pNd) - return OUString(); + return std::make_pair(OUString(), false); const OUString sName = static_cast<const SwTableNode*>(pNd)->GetTable().GetFrameFormat()->GetName(); if ( sName.isEmpty() ) - return OUString(); + return std::make_pair(OUString(), false); - return "#" + sName + OUStringChar(cMarkSeparator) + "table"; + return std::make_pair("#" + sName + OUStringChar(cMarkSeparator) + "table", false); } SwTOXAuthority::SwTOXAuthority( const SwContentNode& rNd, diff --git a/sw/source/uibase/uiview/view2.cxx b/sw/source/uibase/uiview/view2.cxx index 8b7e4667aae3..e76fc2f30e41 100644 --- a/sw/source/uibase/uiview/view2.cxx +++ b/sw/source/uibase/uiview/view2.cxx @@ -2022,6 +2022,83 @@ void SwView::EditLinkDlg() pDlg->Execute(); } +static auto JumpToTOXMark(SwWrtShell & rSh, OUString const& rName) -> bool +{ + sal_Int32 const first(rName.indexOf(toxMarkSeparator)); + if (first == -1) + { + SAL_WARN("sw.ui", "JumpToTOXMark: missing separator"); + return false; + } + sal_Int32 const counter(rName.copy(0, first).toInt32()); + if (counter <= 0) + { + SAL_WARN("sw.ui", "JumpToTOXMark: invalid counter"); + return false; + } + sal_Int32 const second(rName.indexOf(toxMarkSeparator, first + 1)); + if (second == -1) + { + SAL_WARN("sw.ui", "JumpToTOXMark: missing separator"); + return false; + } + OUString const entry(rName.copy(first + 1, second - (first + 1))); + if (rName.getLength() < second + 2) + { + SAL_WARN("sw.ui", "JumpToTOXMark: invalid tox"); + return false; + } + sal_uInt16 const indexType(rName[second + 1]); + OUString const indexName(rName.copy(second + 2)); + SwTOXType const* pType(nullptr); + switch (indexType) + { + case 'A': + pType = rSh.GetTOXType(TOX_INDEX, 0); + assert(pType); + break; + case 'C': + pType = rSh.GetTOXType(TOX_CONTENT, 0); + assert(pType); + break; + case 'U': + for (auto i = rSh.GetTOXTypeCount(TOX_USER); 0 < i; ) + { + --i; + auto const pTmp(rSh.GetTOXType(TOX_USER, i)); + if (pTmp->GetTypeName() == indexName) + { + pType = pTmp; + break; + } + } + break; + } + if (!pType) + { + SAL_WARN("sw.ui", "JumpToTOXMark: tox doesn't exist"); + return false; + } + // type and alt text are the search keys + SwTOXMark tmp(pType); + tmp.SetAlternativeText(entry); + SwTOXMark const* pMark(&tmp); + // hack: check first if one exists + if (&tmp != &rSh.GetDoc()->GotoTOXMark(tmp, TOX_SAME_NXT, rSh.IsReadOnlyAvailable())) + { + for (sal_Int32 i = 0; i < counter; ++i) + { + pMark = &rSh.GotoTOXMark(*pMark, TOX_SAME_NXT); + } + return true; + } + else + { + SAL_WARN("sw.ui", "JumpToTOXMark: tox mark doesn't exist"); + return false; + } +} + bool SwView::JumpToSwMark( const OUString& rMark ) { bool bRet = false; @@ -2092,6 +2169,10 @@ bool SwView::JumpToSwMark( const OUString& rMark ) bRet = m_pWrtShell->GotoRefMark(sName, REF_SEQUENCEFLD, nSeqNo); } } + else if (sCmp == "toxmark") + { + bRet = JumpToTOXMark(*m_pWrtShell, sName); + } else if( sCmp == "text" ) { // normal text search |