/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::text; using namespace ::com::sun::star::lang; static std::pair MakeRefNumStr(SwRootFrame const* pLayout, const SwTextNode& rTextNodeOfField, const SwTextNode& rTextNodeOfReferencedItem, sal_uInt16 nSubType, sal_uInt32 nRefNumFormat, sal_uInt16 nFlags); static void lcl_GetLayTree( const SwFrame* pFrame, std::vector& rArr ) { while( pFrame ) { if( pFrame->IsBodyFrame() ) // unspectacular pFrame = pFrame->GetUpper(); else { rArr.push_back( pFrame ); // this is the last page if( pFrame->IsPageFrame() ) break; if( pFrame->IsFlyFrame() ) pFrame = static_cast(pFrame)->GetAnchorFrame(); else pFrame = pFrame->GetUpper(); } } } bool IsFrameBehind( const SwTextNode& rMyNd, sal_Int32 nMySttPos, const SwTextNode& rBehindNd, sal_Int32 nSttPos ) { const SwTextFrame * pMyFrame = static_cast(rMyNd.getLayoutFrame( rMyNd.GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)); const SwTextFrame * pFrame = static_cast(rBehindNd.getLayoutFrame( rBehindNd.GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)); if( !pFrame || !pMyFrame) return false; TextFrameIndex const nMySttPosIndex(pMyFrame->MapModelToView(&rMyNd, nMySttPos)); TextFrameIndex const nSttPosIndex(pFrame->MapModelToView(&rBehindNd, nSttPos)); while (pFrame && !pFrame->IsInside(nSttPosIndex)) pFrame = pFrame->GetFollow(); while (pMyFrame && !pMyFrame->IsInside(nMySttPosIndex)) pMyFrame = pMyFrame->GetFollow(); if( !pFrame || !pMyFrame || pFrame == pMyFrame ) return false; std::vector aRefArr, aArr; ::lcl_GetLayTree( pFrame, aRefArr ); ::lcl_GetLayTree( pMyFrame, aArr ); size_t nRefCnt = aRefArr.size() - 1, nCnt = aArr.size() - 1; bool bVert = false; bool bR2L = false; // Loop as long as a frame does not equal? while( nRefCnt && nCnt && aRefArr[ nRefCnt ] == aArr[ nCnt ] ) { const SwFrame* pTmpFrame = aArr[ nCnt ]; bVert = pTmpFrame->IsVertical(); bR2L = pTmpFrame->IsRightToLeft(); --nCnt; --nRefCnt; } // If a counter overflows? if( aRefArr[ nRefCnt ] == aArr[ nCnt ] ) { if( nCnt ) --nCnt; else --nRefCnt; } const SwFrame* pRefFrame = aRefArr[ nRefCnt ]; const SwFrame* pFieldFrame = aArr[ nCnt ]; // different frames, check their Y-/X-position bool bRefIsLower = false; if( ( SwFrameType::Column | SwFrameType::Cell ) & pFieldFrame->GetType() || ( SwFrameType::Column | SwFrameType::Cell ) & pRefFrame->GetType() ) { if( pFieldFrame->GetType() == pRefFrame->GetType() ) { // here, the X-pos is more important if( bVert ) { if( bR2L ) bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() ); else bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() ); } else if( bR2L ) bRefIsLower = pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() || ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); else bRefIsLower = pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() || ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); pRefFrame = nullptr; } else if( ( SwFrameType::Column | SwFrameType::Cell ) & pFieldFrame->GetType() ) pFieldFrame = aArr[ nCnt - 1 ]; else pRefFrame = aRefArr[ nRefCnt - 1 ]; } if( pRefFrame ) // misuse as flag { if( bVert ) { if( bR2L ) bRefIsLower = pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() || ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); else bRefIsLower = pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() || ( pRefFrame->getFrameArea().Left() == pFieldFrame->getFrameArea().Left() && pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() ); } else if( bR2L ) bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && pRefFrame->getFrameArea().Left() > pFieldFrame->getFrameArea().Left() ); else bRefIsLower = pRefFrame->getFrameArea().Top() < pFieldFrame->getFrameArea().Top() || ( pRefFrame->getFrameArea().Top() == pFieldFrame->getFrameArea().Top() && pRefFrame->getFrameArea().Left() < pFieldFrame->getFrameArea().Left() ); } return bRefIsLower; } // tdf#115319 create alternative reference formats, if the user asked for it // (ReferenceFieldLanguage attribute of the reference field is not empty), and // language of the text and ReferenceFieldLanguage are the same. // Right now only HUNGARIAN seems to need this (as in the related issue, // the reversed caption order in autocaption, solved by #i61007#) static void lcl_formatReferenceLanguage( OUString& rRefText, bool bClosingParenthesis, LanguageType eLang, std::u16string_view rReferenceLanguage) { if (eLang != LANGUAGE_HUNGARIAN || (rReferenceLanguage != u"hu" && rReferenceLanguage != u"Hu")) return; // Add Hungarian definitive article (a/az) before references, // similar to \aref, \apageref etc. of LaTeX Babel package. // // for example: // // "az 1. oldalon" ("on page 1"), but // "a 2. oldalon" ("on page 2") // "a fentebbi", "az alábbi" (above/below) // "a Lorem", "az Ipsum" // // Support following numberings of EU publications: // // 1., 1a., a), (1), (1a), iii., III., IA. // // (http://publications.europa.eu/code/hu/hu-120700.htm, // http://publications.europa.eu/code/hu/hu-4100600.htm) CharClass aCharClass(( LanguageTag(eLang) )); sal_Int32 nLen = rRefText.getLength(); sal_Int32 i; // substring of rRefText starting with letter or number OUString sNumbering; // is article "az"? bool bArticleAz = false; // is numbering a number? bool bNum = false; // search first member of the numbering (numbers or letters) for (i=0; i 1 && (nLen + 1 >= sNumbering.getLength() || sNumbering[nLen] == '.')) { sal_Unicode last = sNumbering[nLen - 1]; OUString sNumberingTrim; if ((last >= 'A' && last < 'I') || (last >= 'a' && last < 'i')) sNumberingTrim = sNumbering.copy(0, nLen - 1); else sNumberingTrim = sNumbering.copy(0, nLen); bRomanNumber = sNumberingTrim.replaceAll("i", "").replaceAll("v", "").replaceAll("x", "").replaceAll("l", "").replaceAll("c", "").isEmpty() || sNumberingTrim.replaceAll("I", "").replaceAll("V", "").replaceAll("X", "").replaceAll("L", "").replaceAll("C", "").isEmpty(); } if ( // Roman number and a letter optionally ( bRomanNumber && ( (sNumbering[0] == 'i' && sNumbering[1] != 'i' && sNumbering[1] != 'v' && sNumbering[1] != 'x') || (sNumbering[0] == 'I' && sNumbering[1] != 'I' && sNumbering[1] != 'V' && sNumbering[1] != 'X') || (sNumbering[0] == 'v' && sNumbering[1] != 'i') || (sNumbering[0] == 'V' && sNumbering[1] != 'I') || (sNumbering[0] == 'l' && sNumbering[1] != 'x') || (sNumbering[0] == 'L' && sNumbering[1] != 'X')) ) || // a word starting with vowel (not Roman number) ( !bRomanNumber && sVowels.indexOf(sNumbering[0]) != -1)) { bArticleAz = true; } } // not a title text starting already with a definitive article if ( sNumbering.startsWith("A ") || sNumbering.startsWith("Az ") || sNumbering.startsWith("a ") || sNumbering.startsWith("az ") ) return; // lowercase, if rReferenceLanguage == "hu", not "Hu" OUString sArticle; if ( rReferenceLanguage == u"hu" ) sArticle = "a"; else sArticle = "A"; if (bArticleAz) sArticle += "z"; rRefText = sArticle + " " + rRefText; } /// get references SwGetRefField::SwGetRefField( SwGetRefFieldType* pFieldType, OUString aSetRef, OUString aSetReferenceLanguage, sal_uInt16 nSubTyp, sal_uInt16 nSequenceNo, sal_uInt16 nFlags, sal_uLong nFormat ) : SwField(pFieldType, nFormat), m_sSetRefName(std::move(aSetRef)), m_sSetReferenceLanguage(std::move(aSetReferenceLanguage)), m_nSubType(nSubTyp), m_nSeqNo(nSequenceNo), m_nFlags(nFlags) { } SwGetRefField::~SwGetRefField() { } OUString SwGetRefField::GetDescription() const { return SwResId(STR_REFERENCE); } sal_uInt16 SwGetRefField::GetSubType() const { return m_nSubType; } void SwGetRefField::SetSubType( sal_uInt16 n ) { m_nSubType = n; } // #i81002# bool SwGetRefField::IsRefToHeadingCrossRefBookmark() const { return GetSubType() == REF_BOOKMARK && ::sw::mark::CrossRefHeadingBookmark::IsLegalName(m_sSetRefName); } bool SwGetRefField::IsRefToNumItemCrossRefBookmark() const { return GetSubType() == REF_BOOKMARK && ::sw::mark::CrossRefNumItemBookmark::IsLegalName(m_sSetRefName); } const SwTextNode* SwGetRefField::GetReferencedTextNode(SwTextNode* pTextNode, SwFrame* pFrame) const { SwGetRefFieldType *pTyp = dynamic_cast(GetTyp()); if (!pTyp) return nullptr; sal_Int32 nDummy = -1; return SwGetRefFieldType::FindAnchor( &pTyp->GetDoc(), m_sSetRefName, m_nSubType, m_nSeqNo, m_nFlags, &nDummy, nullptr, nullptr, pTextNode, pFrame ); } // strikethrough for tooltips using Unicode combining character static OUString lcl_formatStringByCombiningCharacter(std::u16string_view sText, const sal_Unicode cChar) { OUStringBuffer sRet(sText.size() * 2); for (size_t i = 0; i < sText.size(); ++i) { sRet.append(OUStringChar(sText[i]) + OUStringChar(cChar)); } return sRet.makeStringAndClear(); } // #i85090# OUString SwGetRefField::GetExpandedTextOfReferencedTextNode( SwRootFrame const& rLayout, SwTextNode* pTextNode, SwFrame* pFrame) const { const SwTextNode* pReferencedTextNode( GetReferencedTextNode(pTextNode, pFrame) ); if ( !pReferencedTextNode ) return OUString(); // show the referenced text without the deletions, but if the whole text was // deleted, show the original text for the sake of the comfortable reviewing, // but with Unicode strikethrough in the tooltip OUString sRet = sw::GetExpandTextMerged(&rLayout, *pReferencedTextNode, true, false, ExpandMode::HideDeletions); if ( sRet.isEmpty() ) { static const sal_Unicode cStrikethrough = u'\x0336'; sRet = sw::GetExpandTextMerged(&rLayout, *pReferencedTextNode, true, false, ExpandMode(0)); sRet = lcl_formatStringByCombiningCharacter( sRet, cStrikethrough ); } return sRet; } void SwGetRefField::SetExpand( const OUString& rStr ) { m_sText = rStr; m_sTextRLHidden = rStr; } OUString SwGetRefField::ExpandImpl(SwRootFrame const*const pLayout) const { return pLayout && pLayout->IsHideRedlines() ? m_sTextRLHidden : m_sText; } OUString SwGetRefField::GetFieldName() const { const OUString aName = GetTyp()->GetName(); if ( !aName.isEmpty() || !m_sSetRefName.isEmpty() ) { return aName + " " + m_sSetRefName; } return ExpandImpl(nullptr); } static void FilterText(OUString & rText, LanguageType const eLang, std::u16string_view rSetReferenceLanguage) { // remove all special characters (replace them with blanks) if (rText.isEmpty()) return; rText = rText.replaceAll(u"\u00ad", ""); OUStringBuffer aBuf(rText); const sal_Int32 l = aBuf.getLength(); for (sal_Int32 i = 0; i < l; ++i) { if (aBuf[i] < ' ') { aBuf[i] = ' '; } else if (aBuf[i] == 0x2011) { aBuf[i] = '-'; } } rText = aBuf.makeStringAndClear(); if (!rSetReferenceLanguage.empty()) { lcl_formatReferenceLanguage(rText, false, eLang, rSetReferenceLanguage); } } // #i81002# - parameter added void SwGetRefField::UpdateField( const SwTextField* pFieldTextAttr, SwFrame* pFrame ) { SwDoc& rDoc = static_cast(GetTyp())->GetDoc(); for (SwRootFrame const* const pLay : rDoc.GetAllLayouts()) { if (pLay->IsHideRedlines()) { UpdateField(pFieldTextAttr, pFrame, pLay, m_sTextRLHidden); } else { UpdateField(pFieldTextAttr, pFrame, pLay, m_sText); } } } void SwGetRefField::UpdateField(const SwTextField* pFieldTextAttr, SwFrame* pFrameContainingField, const SwRootFrame* const pLayout, OUString& rText) { SwDoc& rDoc = static_cast(GetTyp())->GetDoc(); rText.clear(); // finding the reference target (the number) sal_Int32 nNumStart = -1; sal_Int32 nNumEnd = -1; SwTextNode* pTextNd = SwGetRefFieldType::FindAnchor( &rDoc, m_sSetRefName, m_nSubType, m_nSeqNo, m_nFlags, &nNumStart, &nNumEnd, pLayout, pFieldTextAttr ? pFieldTextAttr->GetpTextNode() : nullptr, pFrameContainingField ); // not found? if ( !pTextNd ) { rText = SwViewShell::GetShellRes()->aGetRefField_RefItemNotFound; return; } // where is the category name (e.g. "Illustration")? const OUString aText = pTextNd->GetText(); const sal_Int32 nCatStart = aText.indexOf(m_sSetRefName); const bool bHasCat = nCatStart>=0; const sal_Int32 nCatEnd = bHasCat ? nCatStart + m_sSetRefName.getLength() : -1; // length of the referenced text const sal_Int32 nLen = aText.getLength(); // which format? switch( GetFormat() ) { case REF_CONTENT: case REF_ONLYNUMBER: case REF_ONLYCAPTION: case REF_ONLYSEQNO: { // needed part of Text sal_Int32 nStart; sal_Int32 nEnd; switch( m_nSubType ) { case REF_SEQUENCEFLD: switch( GetFormat() ) { // "Category and Number" case REF_ONLYNUMBER: if (bHasCat) { nStart = std::min(nNumStart, nCatStart); nEnd = std::max(nNumEnd, nCatEnd); } else { nStart = nNumStart; nEnd = nNumEnd; } break; // "Caption Text" case REF_ONLYCAPTION: { // next alphanumeric character after category+number if (const SwTextAttr* const pTextAttr = pTextNd->GetTextAttrForCharAt(nNumStart, RES_TXTATR_FIELD) ) { // start searching from nFrom const sal_Int32 nFrom = bHasCat ? std::max(nNumStart + 1, nCatEnd) : nNumStart + 1; nStart = SwGetExpField::GetReferenceTextPos( pTextAttr->GetFormatField(), rDoc, nFrom ); } else { nStart = bHasCat ? std::max(nNumEnd, nCatEnd) : nNumEnd; } nEnd = nLen; break; } // "Numbering" case REF_ONLYSEQNO: nStart = nNumStart; nEnd = std::min(nStart + 1, nLen); break; // "Reference" (whole Text) case REF_CONTENT: nStart = 0; nEnd = nLen; break; default: O3TL_UNREACHABLE; } break; case REF_BOOKMARK: nStart = nNumStart; // text is spread across multiple nodes - get whole text or only until end of node? nEnd = nNumEnd<0 ? nLen : nNumEnd; break; case REF_OUTLINE: nStart = nNumStart; nEnd = nNumEnd; break; case REF_FOOTNOTE: case REF_ENDNOTE: // get number or numString for( size_t i = 0; i < rDoc.GetFootnoteIdxs().size(); ++i ) { SwTextFootnote* const pFootnoteIdx = rDoc.GetFootnoteIdxs()[i]; if( m_nSeqNo == pFootnoteIdx->GetSeqRefNo() ) { rText = pFootnoteIdx->GetFootnote().GetViewNumStr(rDoc, pLayout); if (!m_sSetReferenceLanguage.isEmpty()) { lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); } break; } } return; case REF_STYLE: nStart = 0; nEnd = nLen; break; case REF_SETREFATTR: nStart = nNumStart; nEnd = nNumEnd; break; default: O3TL_UNREACHABLE; } if( nStart != nEnd ) // a section? { if (pLayout->IsHideRedlines()) { if (m_nSubType == REF_OUTLINE || (m_nSubType == REF_SEQUENCEFLD && REF_CONTENT == GetFormat())) { rText = sw::GetExpandTextMerged(pLayout, *pTextNd, false, false, ExpandMode(0)); } else { rText = pTextNd->GetExpandText(pLayout, nStart, nEnd - nStart, false, false, false, ExpandMode::HideDeletions); } } else { rText = pTextNd->GetExpandText(pLayout, nStart, nEnd - nStart, false, false, false, ExpandMode::HideDeletions); // show the referenced text without the deletions, but if the whole text was // deleted, show the original text for the sake of the comfortable reviewing // (with strikethrough in tooltip, see GetExpandedTextOfReferencedTextNode()) if (rText.isEmpty()) rText = pTextNd->GetExpandText(pLayout, nStart, nEnd - nStart, false, false, false, ExpandMode(0)); } FilterText(rText, GetLanguage(), m_sSetReferenceLanguage); } } break; case REF_PAGE: case REF_PAGE_PGDESC: { SwTextFrame const* pFrame = static_cast(pTextNd->getLayoutFrame(pLayout, nullptr, nullptr)); SwTextFrame const*const pSave = pFrame; if (pFrame) { TextFrameIndex const nNumStartIndex(pFrame->MapModelToView(pTextNd, nNumStart)); while (pFrame && !pFrame->IsInside(nNumStartIndex)) pFrame = pFrame->GetFollow(); } if( pFrame || nullptr != ( pFrame = pSave )) { sal_uInt16 nPageNo = pFrame->GetVirtPageNum(); const SwPageFrame *pPage; if( REF_PAGE_PGDESC == GetFormat() && nullptr != ( pPage = pFrame->FindPageFrame() ) && pPage->GetPageDesc() ) { rText = pPage->GetPageDesc()->GetNumType().GetNumStr(nPageNo); } else { rText = OUString::number(nPageNo); } if (!m_sSetReferenceLanguage.isEmpty()) lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); } } break; case REF_CHAPTER: { // a bit tricky: search any frame SwFrame const* const pFrame = pTextNd->getLayoutFrame(pLayout); if (pFrame) { SwChapterFieldType aFieldTyp; SwChapterField aField(&aFieldTyp, 0); aField.SetLevel(MAXLEVEL - 1); aField.ChangeExpansion(*pFrame, pTextNd, true); rText = aField.GetNumber(pLayout); if (!m_sSetReferenceLanguage.isEmpty()) lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); } } break; case REF_UPDOWN: { // #i81002# // simplified: use parameter if( !pFieldTextAttr || !pFieldTextAttr->GetpTextNode() ) break; LocaleDataWrapper aLocaleData(( LanguageTag( GetLanguage() ) )); // first a "short" test - in case both are in the same node if( pFieldTextAttr->GetpTextNode() == pTextNd ) { rText = nNumStart < pFieldTextAttr->GetStart() ? aLocaleData.getAboveWord() : aLocaleData.getBelowWord(); break; } rText = ::IsFrameBehind( *pFieldTextAttr->GetpTextNode(), pFieldTextAttr->GetStart(), *pTextNd, nNumStart ) ? aLocaleData.getAboveWord() : aLocaleData.getBelowWord(); if (!m_sSetReferenceLanguage.isEmpty()) lcl_formatReferenceLanguage(rText, false, GetLanguage(), m_sSetReferenceLanguage); } break; // #i81002# case REF_NUMBER: case REF_NUMBER_NO_CONTEXT: case REF_NUMBER_FULL_CONTEXT: { if ( pFieldTextAttr && pFieldTextAttr->GetpTextNode() ) { auto result = MakeRefNumStr(pLayout, pFieldTextAttr->GetTextNode(), *pTextNd, m_nSubType, GetFormat(), GetFlags()); rText = result.first; // for differentiation of Roman numbers and letters in Hungarian article handling bool bClosingParenthesis = result.second; if (!m_sSetReferenceLanguage.isEmpty()) { lcl_formatReferenceLanguage(rText, bClosingParenthesis, GetLanguage(), m_sSetReferenceLanguage); } } } break; default: OSL_FAIL(" - unknown format type"); } } // #i81002# static std::pair MakeRefNumStr( SwRootFrame const*const pLayout, const SwTextNode& i_rTextNodeOfField, const SwTextNode& i_rTextNodeOfReferencedItem, const sal_uInt16 nSubType, const sal_uInt32 nRefNumFormat, const sal_uInt16 nFlags) { bool bHideNonNumerical = (nSubType == REF_STYLE) && ((nFlags & REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL) == REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL); SwTextNode const& rTextNodeOfField(pLayout ? *sw::GetParaPropsNode(*pLayout, i_rTextNodeOfField) : i_rTextNodeOfField); SwTextNode const& rTextNodeOfReferencedItem(pLayout ? *sw::GetParaPropsNode(*pLayout, i_rTextNodeOfReferencedItem) : i_rTextNodeOfReferencedItem); if ( rTextNodeOfReferencedItem.HasNumber(pLayout) && rTextNodeOfReferencedItem.IsCountedInList() ) { OSL_ENSURE( rTextNodeOfReferencedItem.GetNum(pLayout), " - referenced paragraph has number, but no instance!" ); // Determine, up to which level the superior list labels have to be // included - default is to include all superior list labels. int nRestrictInclToThisLevel( 0 ); // Determine for format REF_NUMBER the level, up to which the superior // list labels have to be restricted, if the text node of the reference // field and the text node of the referenced item are in the same // document context. if ( nRefNumFormat == REF_NUMBER && rTextNodeOfField.FindFlyStartNode() == rTextNodeOfReferencedItem.FindFlyStartNode() && rTextNodeOfField.FindFootnoteStartNode() == rTextNodeOfReferencedItem.FindFootnoteStartNode() && rTextNodeOfField.FindHeaderStartNode() == rTextNodeOfReferencedItem.FindHeaderStartNode() && rTextNodeOfField.FindFooterStartNode() == rTextNodeOfReferencedItem.FindFooterStartNode() ) { const SwNodeNum* pNodeNumForTextNodeOfField( nullptr ); if ( rTextNodeOfField.HasNumber(pLayout) && rTextNodeOfField.GetNumRule() == rTextNodeOfReferencedItem.GetNumRule() ) { pNodeNumForTextNodeOfField = rTextNodeOfField.GetNum(pLayout); } else { pNodeNumForTextNodeOfField = rTextNodeOfReferencedItem.GetNum(pLayout)->GetPrecedingNodeNumOf(rTextNodeOfField); } if ( pNodeNumForTextNodeOfField ) { const SwNumberTree::tNumberVector rFieldNumVec = pNodeNumForTextNodeOfField->GetNumberVector(); const SwNumberTree::tNumberVector rRefItemNumVec = rTextNodeOfReferencedItem.GetNum()->GetNumberVector(); std::size_t nLevel( 0 ); while ( nLevel < rFieldNumVec.size() && nLevel < rRefItemNumVec.size() ) { if ( rRefItemNumVec[nLevel] == rFieldNumVec[nLevel] ) { nRestrictInclToThisLevel = nLevel + 1; } else { break; } ++nLevel; } } } // Determine, if superior list labels have to be included const bool bInclSuperiorNumLabels( ( nRestrictInclToThisLevel < rTextNodeOfReferencedItem.GetActualListLevel() && ( nRefNumFormat == REF_NUMBER || nRefNumFormat == REF_NUMBER_FULL_CONTEXT ) ) ); OSL_ENSURE( rTextNodeOfReferencedItem.GetNumRule(), " - referenced numbered paragraph has no numbering rule set!" ); return std::make_pair( rTextNodeOfReferencedItem.GetNumRule()->MakeRefNumString( *(rTextNodeOfReferencedItem.GetNum(pLayout)), bInclSuperiorNumLabels, nRestrictInclToThisLevel, bHideNonNumerical ), rTextNodeOfReferencedItem.GetNumRule()->MakeNumString( *(rTextNodeOfReferencedItem.GetNum(pLayout)), true).endsWith(")") ); } return std::make_pair(OUString(), false); } std::unique_ptr SwGetRefField::Copy() const { std::unique_ptr pField( new SwGetRefField( static_cast(GetTyp()), m_sSetRefName, m_sSetReferenceLanguage, m_nSubType, m_nSeqNo, m_nFlags, GetFormat() ) ); pField->m_sText = m_sText; pField->m_sTextRLHidden = m_sTextRLHidden; return std::unique_ptr(pField.release()); } /// get reference name OUString SwGetRefField::GetPar1() const { return m_sSetRefName; } /// set reference name void SwGetRefField::SetPar1( const OUString& rName ) { m_sSetRefName = rName; } OUString SwGetRefField::GetPar2() const { return ExpandImpl(nullptr); } bool SwGetRefField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const { switch( nWhichId ) { case FIELD_PROP_USHORT1: { sal_Int16 nPart = 0; switch(GetFormat()) { case REF_PAGE : nPart = ReferenceFieldPart::PAGE ; break; case REF_CHAPTER : nPart = ReferenceFieldPart::CHAPTER ; break; case REF_CONTENT : nPart = ReferenceFieldPart::TEXT ; break; case REF_UPDOWN : nPart = ReferenceFieldPart::UP_DOWN ; break; case REF_PAGE_PGDESC: nPart = ReferenceFieldPart::PAGE_DESC ; break; case REF_ONLYNUMBER : nPart = ReferenceFieldPart::CATEGORY_AND_NUMBER ; break; case REF_ONLYCAPTION: nPart = ReferenceFieldPart::ONLY_CAPTION ; break; case REF_ONLYSEQNO : nPart = ReferenceFieldPart::ONLY_SEQUENCE_NUMBER; break; // #i81002# case REF_NUMBER: nPart = ReferenceFieldPart::NUMBER; break; case REF_NUMBER_NO_CONTEXT: nPart = ReferenceFieldPart::NUMBER_NO_CONTEXT; break; case REF_NUMBER_FULL_CONTEXT: nPart = ReferenceFieldPart::NUMBER_FULL_CONTEXT; break; } rAny <<= nPart; } break; case FIELD_PROP_USHORT2: { sal_Int16 nSource = 0; switch(m_nSubType) { case REF_SETREFATTR : nSource = ReferenceFieldSource::REFERENCE_MARK; break; case REF_SEQUENCEFLD: nSource = ReferenceFieldSource::SEQUENCE_FIELD; break; case REF_BOOKMARK : nSource = ReferenceFieldSource::BOOKMARK; break; case REF_OUTLINE : OSL_FAIL("not implemented"); break; case REF_FOOTNOTE : nSource = ReferenceFieldSource::FOOTNOTE; break; case REF_ENDNOTE : nSource = ReferenceFieldSource::ENDNOTE; break; case REF_STYLE : nSource = ReferenceFieldSource::STYLE; break; } rAny <<= nSource; } break; case FIELD_PROP_USHORT3: rAny <<= m_nFlags; break; case FIELD_PROP_PAR1: { OUString sTmp(GetPar1()); if(REF_SEQUENCEFLD == m_nSubType) { sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( sTmp, SwGetPoolIdFromName::TxtColl ); switch( nPoolId ) { case RES_POOLCOLL_LABEL_ABB: case RES_POOLCOLL_LABEL_TABLE: case RES_POOLCOLL_LABEL_FRAME: case RES_POOLCOLL_LABEL_DRAWING: case RES_POOLCOLL_LABEL_FIGURE: SwStyleNameMapper::FillProgName(nPoolId, sTmp) ; break; } } rAny <<= sTmp; } break; case FIELD_PROP_PAR3: rAny <<= ExpandImpl(nullptr); break; case FIELD_PROP_PAR4: rAny <<= m_sSetReferenceLanguage; break; case FIELD_PROP_SHORT1: rAny <<= static_cast(m_nSeqNo); break; default: assert(false); } return true; } bool SwGetRefField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) { switch( nWhichId ) { case FIELD_PROP_USHORT1: { sal_Int16 nPart = 0; rAny >>= nPart; switch(nPart) { case ReferenceFieldPart::PAGE: nPart = REF_PAGE; break; case ReferenceFieldPart::CHAPTER: nPart = REF_CHAPTER; break; case ReferenceFieldPart::TEXT: nPart = REF_CONTENT; break; case ReferenceFieldPart::UP_DOWN: nPart = REF_UPDOWN; break; case ReferenceFieldPart::PAGE_DESC: nPart = REF_PAGE_PGDESC; break; case ReferenceFieldPart::CATEGORY_AND_NUMBER: nPart = REF_ONLYNUMBER; break; case ReferenceFieldPart::ONLY_CAPTION: nPart = REF_ONLYCAPTION; break; case ReferenceFieldPart::ONLY_SEQUENCE_NUMBER : nPart = REF_ONLYSEQNO; break; // #i81002# case ReferenceFieldPart::NUMBER: nPart = REF_NUMBER; break; case ReferenceFieldPart::NUMBER_NO_CONTEXT: nPart = REF_NUMBER_NO_CONTEXT; break; case ReferenceFieldPart::NUMBER_FULL_CONTEXT: nPart = REF_NUMBER_FULL_CONTEXT; break; default: return false; } SetFormat(nPart); } break; case FIELD_PROP_USHORT2: { sal_Int16 nSource = 0; rAny >>= nSource; switch(nSource) { case ReferenceFieldSource::REFERENCE_MARK : m_nSubType = REF_SETREFATTR ; break; case ReferenceFieldSource::SEQUENCE_FIELD : { if(REF_SEQUENCEFLD == m_nSubType) break; m_nSubType = REF_SEQUENCEFLD; ConvertProgrammaticToUIName(); } break; case ReferenceFieldSource::BOOKMARK : m_nSubType = REF_BOOKMARK ; break; case ReferenceFieldSource::FOOTNOTE : m_nSubType = REF_FOOTNOTE ; break; case ReferenceFieldSource::ENDNOTE : m_nSubType = REF_ENDNOTE ; break; case ReferenceFieldSource::STYLE : m_nSubType = REF_STYLE ; break; } } break; case FIELD_PROP_PAR1: { OUString sTmpStr; rAny >>= sTmpStr; SetPar1(sTmpStr); ConvertProgrammaticToUIName(); } break; case FIELD_PROP_PAR3: { OUString sTmpStr; rAny >>= sTmpStr; SetExpand( sTmpStr ); } break; case FIELD_PROP_PAR4: rAny >>= m_sSetReferenceLanguage; break; case FIELD_PROP_USHORT3: { sal_uInt16 nSetFlags = 0; rAny >>= nSetFlags; m_nFlags = nSetFlags; } break; case FIELD_PROP_SHORT1: { sal_Int16 nSetSeq = 0; rAny >>= nSetSeq; if(nSetSeq >= 0) m_nSeqNo = nSetSeq; } break; default: assert(false); } return true; } void SwGetRefField::ConvertProgrammaticToUIName() { if(!(GetTyp() && REF_SEQUENCEFLD == m_nSubType)) return; SwDoc& rDoc = static_cast(GetTyp())->GetDoc(); const OUString rPar1 = GetPar1(); // don't convert when the name points to an existing field type if (rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, rPar1, false)) return; sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromProgName( rPar1, SwGetPoolIdFromName::TxtColl ); TranslateId pResId; switch( nPoolId ) { case RES_POOLCOLL_LABEL_ABB: pResId = STR_POOLCOLL_LABEL_ABB; break; case RES_POOLCOLL_LABEL_TABLE: pResId = STR_POOLCOLL_LABEL_TABLE; break; case RES_POOLCOLL_LABEL_FRAME: pResId = STR_POOLCOLL_LABEL_FRAME; break; case RES_POOLCOLL_LABEL_DRAWING: pResId = STR_POOLCOLL_LABEL_DRAWING; break; case RES_POOLCOLL_LABEL_FIGURE: pResId = STR_POOLCOLL_LABEL_FIGURE; break; } if (pResId) SetPar1(SwResId(pResId)); } SwGetRefFieldType::SwGetRefFieldType( SwDoc& rDc ) : SwFieldType( SwFieldIds::GetRef ), m_rDoc( rDc ) {} std::unique_ptr SwGetRefFieldType::Copy() const { return std::make_unique( m_rDoc ); } void SwGetRefFieldType::UpdateGetReferences() { std::vector vFields; GatherFields(vFields, false); for(auto pFormatField: vFields) { // update only the GetRef fields //JP 3.4.2001: Task 71231 - we need the correct language SwGetRefField* pGRef = static_cast(pFormatField->GetField()); const SwTextField* pTField; if(!pGRef->GetLanguage() && nullptr != (pTField = pFormatField->GetTextField()) && pTField->GetpTextNode()) { pGRef->SetLanguage(pTField->GetpTextNode()->GetLang(pTField->GetStart())); } // #i81002# pGRef->UpdateField(pFormatField->GetTextField(), nullptr); } CallSwClientNotify(sw::LegacyModifyHint(nullptr, nullptr)); } void SwGetRefFieldType::UpdateStyleReferences() { std::vector vFields; GatherFields(vFields, false); bool bModified = false; for(auto pFormatField: vFields) { // update only the GetRef fields which are also STYLEREF fields SwGetRefField* pGRef = static_cast(pFormatField->GetField()); if (pGRef->GetSubType() != REF_STYLE) continue; const SwTextField* pTField; if(!pGRef->GetLanguage() && nullptr != (pTField = pFormatField->GetTextField()) && pTField->GetpTextNode()) { pGRef->SetLanguage(pTField->GetpTextNode()->GetLang(pTField->GetStart())); } pGRef->UpdateField(pFormatField->GetTextField(), nullptr); bModified = true; } if (bModified) CallSwClientNotify(sw::LegacyModifyHint(nullptr, nullptr)); } void SwGetRefFieldType::SwClientNotify(const SwModify&, const SfxHint& rHint) { if (rHint.GetId() != SfxHintId::SwLegacyModify) return; auto pLegacy = static_cast(&rHint); if(!pLegacy->m_pNew && !pLegacy->m_pOld) // update to all GetReference fields // hopefully, this codepath is soon dead code, and // UpdateGetReferences gets only called directly UpdateGetReferences(); else // forward to text fields, they "expand" the text CallSwClientNotify(rHint); } namespace sw { bool IsMarkHintHidden(SwRootFrame const& rLayout, SwTextNode const& rNode, SwTextAttrEnd const& rHint) { if (!rLayout.HasMergedParas()) { return false; } SwTextFrame const*const pFrame(static_cast( rNode.getLayoutFrame(&rLayout))); if (!pFrame) { return true; } sal_Int32 const*const pEnd(rHint.GetEnd()); if (pEnd) { return pFrame->MapModelToView(&rNode, rHint.GetStart()) == pFrame->MapModelToView(&rNode, *pEnd); } else { assert(rHint.HasDummyChar()); return pFrame->MapModelToView(&rNode, rHint.GetStart()) == pFrame->MapModelToView(&rNode, rHint.GetStart() + 1); } } } // namespace sw namespace { enum StyleRefElementType { Default, Reference, /* e.g. footnotes, endnotes */ Marginal, /* headers, footers */ }; /// Picks the first text node with a matching style from a double ended queue, starting at the front /// This allows us to use the deque either as a stack or as a queue depending on whether we want to search up or down SwTextNode* SearchForStyleAnchor(SwTextNode* pSelf, const std::deque& pToSearch, std::u16string_view rStyleName, bool bCaseSensitive = true) { std::deque pSearching(pToSearch); while (!pSearching.empty()) { SwNode* pCurrent = pSearching.front(); pSearching.pop_front(); if (*pCurrent == *pSelf) continue; SwTextNode* pTextNode = pCurrent->GetTextNode(); if (!pTextNode) continue; if (bCaseSensitive) { if (pTextNode->GetFormatColl()->GetName() == rStyleName) return pTextNode; } else { if (pTextNode->GetFormatColl()->GetName().equalsIgnoreAsciiCase(rStyleName)) return pTextNode; } } return nullptr; } } SwTextNode* SwGetRefFieldType::FindAnchor(SwDoc* pDoc, const OUString& rRefMark, sal_uInt16 nSubType, sal_uInt16 nSeqNo, sal_uInt16 nFlags, sal_Int32* pStt, sal_Int32* pEnd, SwRootFrame const* const pLayout, SwTextNode* pSelf, SwFrame* pContentFrame) { OSL_ENSURE( pStt, "Why did no one check the StartPos?" ); IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess()); SwTextNode* pTextNd = nullptr; switch( nSubType ) { case REF_SETREFATTR: { const SwFormatRefMark *pRef = pDoc->GetRefMark( rRefMark ); SwTextRefMark const*const pRefMark(pRef ? pRef->GetTextRefMark() : nullptr); if (pRefMark && (!pLayout || !sw::IsMarkHintHidden(*pLayout, pRefMark->GetTextNode(), *pRefMark))) { pTextNd = const_cast(&pRef->GetTextRefMark()->GetTextNode()); *pStt = pRef->GetTextRefMark()->GetStart(); if( pEnd ) *pEnd = pRef->GetTextRefMark()->GetAnyEnd(); } } break; case REF_SEQUENCEFLD: { SwFieldType* pFieldType = pDoc->getIDocumentFieldsAccess().GetFieldType( SwFieldIds::SetExp, rRefMark, false ); if( pFieldType && pFieldType->HasWriterListeners() && nsSwGetSetExpType::GSE_SEQ & static_cast(pFieldType)->GetType() ) { std::vector vFields; pFieldType->GatherFields(vFields, false); for(auto pFormatField: vFields) { SwTextField *const pTextField(pFormatField->GetTextField()); if (pTextField && nSeqNo == static_cast(pFormatField->GetField())->GetSeqNumber() && (!pLayout || !pLayout->IsHideRedlines() || !sw::IsFieldDeletedInModel(rIDRA, *pTextField))) { pTextNd = pTextField->GetpTextNode(); *pStt = pTextField->GetStart(); if( pEnd ) *pEnd = (*pStt) + 1; break; } } } } break; case REF_BOOKMARK: { IDocumentMarkAccess::const_iterator_t ppMark = pDoc->getIDocumentMarkAccess()->findMark(rRefMark); if (ppMark != pDoc->getIDocumentMarkAccess()->getAllMarksEnd() && (!pLayout || !pLayout->IsHideRedlines() || !sw::IsMarkHidden(*pLayout, **ppMark))) { const ::sw::mark::IMark* pBkmk = *ppMark; const SwPosition* pPos = &pBkmk->GetMarkStart(); pTextNd = pPos->GetNode().GetTextNode(); *pStt = pPos->GetContentIndex(); if(pEnd) { if(!pBkmk->IsExpanded()) { *pEnd = *pStt; // #i81002# if(dynamic_cast< ::sw::mark::CrossRefBookmark const *>(pBkmk)) { OSL_ENSURE( pTextNd, " - node marked by cross-reference bookmark isn't a text node --> crash" ); *pEnd = pTextNd->Len(); } } else if(pBkmk->GetOtherMarkPos().GetNode() == pBkmk->GetMarkPos().GetNode()) *pEnd = pBkmk->GetMarkEnd().GetContentIndex(); else *pEnd = -1; } } } break; case REF_OUTLINE: break; case REF_FOOTNOTE: case REF_ENDNOTE: { for( auto pFootnoteIdx : pDoc->GetFootnoteIdxs() ) if( nSeqNo == pFootnoteIdx->GetSeqRefNo() ) { if (pLayout && pLayout->IsHideRedlines() && sw::IsFootnoteDeleted(rIDRA, *pFootnoteIdx)) { return nullptr; } // otherwise: the position at the start of the footnote // will be mapped to something visible at least... const SwNodeIndex* pIdx = pFootnoteIdx->GetStartNode(); if( pIdx ) { SwNodeIndex aIdx( *pIdx, 1 ); pTextNd = aIdx.GetNode().GetTextNode(); if( nullptr == pTextNd ) pTextNd = static_cast(pDoc->GetNodes().GoNext( &aIdx )); } *pStt = 0; if( pEnd ) *pEnd = 0; break; } } break; case REF_STYLE: if (!pSelf) break; const SwNodes& nodes = pDoc->GetNodes(); StyleRefElementType elementType = StyleRefElementType::Default; const SwTextNode* pReference = nullptr; { /* Check if we're a footnote/endnote */ for (SwTextFootnote* pFootnoteIdx : pDoc->GetFootnoteIdxs()) { if (pLayout && pLayout->IsHideRedlines() && sw::IsFootnoteDeleted(rIDRA, *pFootnoteIdx)) { continue; } const SwNodeIndex* pIdx = pFootnoteIdx->GetStartNode(); if (pIdx) { SwNodeIndex aIdx(*pIdx, 1); SwTextNode* pFootnoteNode = aIdx.GetNode().GetTextNode(); if (nullptr == pFootnoteNode) pFootnoteNode = static_cast(pDoc->GetNodes().GoNext(&aIdx)); if (*pSelf == *pFootnoteNode) { elementType = StyleRefElementType::Reference; pReference = &pFootnoteIdx->GetTextNode(); } } } } if (pDoc->IsInHeaderFooter(*pSelf)) { elementType = StyleRefElementType::Marginal; } if (pReference == nullptr) { pReference = pSelf; } switch (elementType) { case Marginal: { // For marginals, styleref tries to act on the current page first // 1. Get the page we're on, search it from top to bottom bool bFlagFromBottom = (nFlags & REFFLDFLAG_STYLE_FROM_BOTTOM) == REFFLDFLAG_STYLE_FROM_BOTTOM; Point aPt; std::pair const tmp(aPt, false); if (!pContentFrame) SAL_WARN("xmloff.text", ": Missing content frame for marginal styleref"); const SwPageFrame* pPageFrame = nullptr; if (pContentFrame) pPageFrame = pContentFrame->FindPageFrame(); const SwNode* pPageStart(nullptr); const SwNode* pPageEnd(nullptr); if (pPageFrame) { const SwContentFrame* pPageStartFrame = pPageFrame->FindFirstBodyContent(); const SwContentFrame* pPageEndFrame = pPageFrame->FindLastBodyContent(); if (pPageStartFrame) { if (pPageStartFrame->IsTextFrame()) { pPageStart = static_cast(pPageStartFrame) ->GetTextNodeFirst(); } else { pPageStart = static_cast(pPageStartFrame)->GetNode(); } } if (pPageEndFrame) { if (pPageEndFrame->IsTextFrame()) { pPageEnd = static_cast(pPageEndFrame) ->GetTextNodeFirst(); } else { pPageEnd = static_cast(pPageEndFrame)->GetNode(); } } } if (!pPageStart || !pPageEnd) { pPageStart = pReference; pPageEnd = pReference; } std::deque pSearchSecond; std::deque pInPage; /* or pSearchFirst */ std::deque pSearchThird; bool beforeStart = true; bool beforeEnd = true; for (SwNodeOffset n(0); n < nodes.Count(); n++) { if (beforeStart && *pPageStart == *nodes[n]) { beforeStart = false; } if (beforeStart) { pSearchSecond.push_front(nodes[n]); } else if (beforeEnd) { if (bFlagFromBottom) pInPage.push_front(nodes[n]); else pInPage.push_back(nodes[n]); if (*pPageEnd == *nodes[n]) { beforeEnd = false; } } else pSearchThird.push_back(nodes[n]); } pTextNd = SearchForStyleAnchor(pSelf, pInPage, rRefMark); if (pTextNd) { break; } // 2. Search up from the top of the page pTextNd = SearchForStyleAnchor(pSelf, pSearchSecond, rRefMark); if (pTextNd) { break; } // 3. Search down from the bottom of the page pTextNd = SearchForStyleAnchor(pSelf, pSearchThird, rRefMark); if (pTextNd) { break; } // Word has case insensitive styles. LO has case sensitive styles. If we didn't find // it yet, maybe we could with a case insensitive search. Let's do that pTextNd = SearchForStyleAnchor(pSelf, pInPage, rRefMark, false /* bCaseSensitive */); if (pTextNd) { break; } pTextNd = SearchForStyleAnchor(pSelf, pSearchSecond, rRefMark, false /* bCaseSensitive */); if (pTextNd) { break; } pTextNd = SearchForStyleAnchor(pSelf, pSearchThird, rRefMark, false /* bCaseSensitive */); break; } case Reference: case Default: { // Normally, styleref does searches around the field position // For references, styleref acts from the position of the reference not the field // Happily, the previous code saves either one into pReference, so the following is generic for both std::deque pSearchFirst; std::deque pSearchSecond; bool beforeElement = true; for (SwNodeOffset n(0); n < nodes.Count(); n++) { if (beforeElement) { pSearchFirst.push_front(nodes[n]); if (*pReference == *nodes[n]) { beforeElement = false; } } pSearchSecond.push_back(nodes[n]); } // 1. Search up until we hit the top of the document pTextNd = SearchForStyleAnchor(pSelf, pSearchFirst, rRefMark); if (pTextNd) { break; } // 2. Search down until we hit the bottom of the document pTextNd = SearchForStyleAnchor(pSelf, pSearchSecond, rRefMark); if (pTextNd) { break; } // Again, we need to remember that Word styles are not case sensitive pTextNd = SearchForStyleAnchor(pSelf, pSearchFirst, rRefMark, false /* bCaseSensitive */); if (pTextNd) { break; } pTextNd = SearchForStyleAnchor(pSelf, pSearchSecond, rRefMark, false /* bCaseSensitive */); break; } default: OSL_FAIL(" - unknown getref element type"); } if (pTextNd) { *pStt = 0; if (pEnd) *pEnd = pTextNd->GetText().getLength(); } break; } return pTextNd; } namespace { struct RefIdsMap { private: OUString aName; std::set aIds; std::set aDstIds; std::map sequencedIds; /// ID numbers sorted by sequence number. bool bInit; void Init(SwDoc& rDoc, SwDoc& rDestDoc, bool bField ); static void GetNoteIdsFromDoc( SwDoc& rDoc, std::set &rIds ); void GetFieldIdsFromDoc( SwDoc& rDoc, std::set &rIds ); void AddId( sal_uInt16 id, sal_uInt16 seqNum ); static sal_uInt16 GetFirstUnusedId( std::set &rIds ); public: explicit RefIdsMap( OUString _aName ) : aName(std::move( _aName )), bInit( false ) {} void Check( SwDoc& rDoc, SwDoc& rDestDoc, SwGetRefField& rField, bool bField ); const OUString& GetName() const { return aName; } }; } /// Get a sorted list of the field IDs from a document. /// @param[in] rDoc The document to search. /// @param[in,out] rIds The list of IDs found in the document. void RefIdsMap::GetFieldIdsFromDoc( SwDoc& rDoc, std::set &rIds) { SwFieldType *const pType = rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, aName, false); if (!pType) return; std::vector vFields; pType->GatherFields(vFields); for(const auto pF: vFields) rIds.insert(static_cast(pF->GetField())->GetSeqNumber()); } /// Get a sorted list of the footnote/endnote IDs from a document. /// @param[in] rDoc The document to search. /// @param[in,out] rIds The list of IDs found in the document. void RefIdsMap::GetNoteIdsFromDoc( SwDoc& rDoc, std::set &rIds) { for( auto n = rDoc.GetFootnoteIdxs().size(); n; ) rIds.insert( rDoc.GetFootnoteIdxs()[ --n ]->GetSeqRefNo() ); } /// Initialise the aIds and aDestIds collections from the source documents. /// @param[in] rDoc The source document. /// @param[in] rDestDoc The destination document. /// @param[in] bField True if we're interested in all fields, false for footnotes. void RefIdsMap::Init( SwDoc& rDoc, SwDoc& rDestDoc, bool bField ) { if( bInit ) return; if( bField ) { GetFieldIdsFromDoc( rDestDoc, aIds ); GetFieldIdsFromDoc( rDoc, aDstIds ); // Map all the new src fields to the next available unused id for (const auto& rId : aDstIds) AddId( GetFirstUnusedId(aIds), rId ); // Change the Sequence number of all SetExp fields in the source document SwFieldType* pType = rDoc.getIDocumentFieldsAccess().GetFieldType( SwFieldIds::SetExp, aName, false ); if(pType) { std::vector vFields; pType->GatherFields(vFields, false); for(auto pF: vFields) { if(!pF->GetTextField()) continue; SwSetExpField *const pSetExp(static_cast(pF->GetField())); sal_uInt16 const n = pSetExp->GetSeqNumber(); pSetExp->SetSeqNumber(sequencedIds[n]); } } } else { GetNoteIdsFromDoc( rDestDoc, aIds ); GetNoteIdsFromDoc( rDoc, aDstIds ); for (const auto& rId : aDstIds) AddId( GetFirstUnusedId(aIds), rId ); // Change the footnotes/endnotes in the source doc to the new ID for ( const auto pFootnoteIdx : rDoc.GetFootnoteIdxs() ) { sal_uInt16 const n = pFootnoteIdx->GetSeqRefNo(); pFootnoteIdx->SetSeqNo(sequencedIds[n]); } } bInit = true; } /// Get the lowest number unused in the passed set. /// @param[in] rIds The set of used ID numbers. /// @returns The lowest number unused by the passed set sal_uInt16 RefIdsMap::GetFirstUnusedId( std::set &rIds ) { sal_uInt16 num(0); for( const auto& rId : rIds ) { if( num != rId ) { return num; } ++num; } return num; } /// Add a new ID and sequence number to the "occupied" collection. /// @param[in] id The ID number. /// @param[in] seqNum The sequence number. void RefIdsMap::AddId( sal_uInt16 id, sal_uInt16 seqNum ) { aIds.insert( id ); sequencedIds[ seqNum ] = id; } void RefIdsMap::Check( SwDoc& rDoc, SwDoc& rDestDoc, SwGetRefField& rField, bool bField ) { Init( rDoc, rDestDoc, bField); sal_uInt16 const nSeqNo = rField.GetSeqNo(); // check if it needs to be remapped // if sequencedIds doesn't contain the number, it means there is no // SetExp field / footnote in the source document: do not modify // the number, which works well for copy from/paste to same document // (and if it is not the same document, there's no "correct" result anyway) if (sequencedIds.count(nSeqNo)) { rField.SetSeqNo( sequencedIds[nSeqNo] ); } } /// 1. if _both_ SetExp + GetExp / Footnote + GetExp field are copied, /// ensure that both get a new unused matching number /// 2. if only SetExp / Footnote is copied, it gets a new unused number /// 3. if only GetExp field is copied, for the case of copy from / paste to /// same document it's desirable to keep the same number; /// for other cases of copy/paste or master documents it's not obvious /// what is most desirable since it's going to be wrong anyway void SwGetRefFieldType::MergeWithOtherDoc( SwDoc& rDestDoc ) { if (&rDestDoc == &m_rDoc) return; if (rDestDoc.IsClipBoard()) { // when copying _to_ clipboard, expectation is that no fields exist // so no re-mapping is required to avoid collisions assert(!rDestDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::GetRef)->HasWriterListeners()); return; // don't modify the fields in the source doc } // then there are RefFields in the DescDox - so all RefFields in the SourceDoc // need to be converted to have unique IDs for both documents RefIdsMap aFntMap { OUString() }; std::vector> aFieldMap; std::vector vFields; GatherFields(vFields); for(auto pField: vFields) { SwGetRefField& rRefField = *static_cast(pField->GetField()); switch( rRefField.GetSubType() ) { case REF_SEQUENCEFLD: { RefIdsMap* pMap = nullptr; for( auto n = aFieldMap.size(); n; ) { if (aFieldMap[ --n ]->GetName() == rRefField.GetSetRefName()) { pMap = aFieldMap[ n ].get(); break; } } if( !pMap ) { pMap = new RefIdsMap( rRefField.GetSetRefName() ); aFieldMap.push_back(std::unique_ptr(pMap)); } pMap->Check(m_rDoc, rDestDoc, rRefField, true); } break; case REF_FOOTNOTE: case REF_ENDNOTE: aFntMap.Check(m_rDoc, rDestDoc, rRefField, false); break; } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */