From dba0f5d370342a1bc5fd39e3e484196a1fdb574b Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Tue, 15 Jul 2025 20:13:58 -0700 Subject: [PATCH] tdf#147943: Change numbering format of subdocument numbering Sets default numbering format of subdocuments to be 012_ABC rather than previous ABC12, which allows for formatting. 1. Introduces a class `PaddedSequentialTokens` to "pad" the number with leading zeroes 2. Introduces a function `concatFunc` in parameters to customize format of numbered subdocuments Change-Id: I95aaf552189a5e1b9bdf7a178cbc3a83c8ab8e16 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187939 Tested-by: Jenkins Reviewed-by: Heiko Tietze Tested-by: Heiko Tietze --- include/unotools/tempfile.hxx | 215 ++++++++++++++++++++++++- sw/inc/doc.hxx | 61 +++++-- sw/source/core/doc/docglbl.cxx | 42 +---- sw/source/uibase/app/docsh2.cxx | 16 +- unotools/source/ucbhelper/tempfile.cxx | 87 ++-------- 5 files changed, 288 insertions(+), 133 deletions(-) diff --git a/include/unotools/tempfile.hxx b/include/unotools/tempfile.hxx index 84812cb9eedd..13474a27ce55 100644 --- a/include/unotools/tempfile.hxx +++ b/include/unotools/tempfile.hxx @@ -29,6 +29,198 @@ #include #include #include +#include +#include + +/** + Base class providing an interface to generate tokens sequentially, storing state + between calls. + + @see SequentialTokens + @see PaddedSequentialTokens + @see UniqueTokens +*/ +class Tokens { +public: + /** + Generates the next token in a sequence and modfies an OUString token + with the generated value. + + @param[out] token Pointer to the OUString in which you plan to store the generated token + @return Boolean representing if token generation was successful + */ + virtual bool next(OUString * token) = 0; + +protected: + virtual ~Tokens() {} // avoid warnings +}; + +/** + Specialization of `Tokens` class that generates unpadded, sequential numbers. + + @see Tokens + @see PaddedSequentialTokens +*/ +class SequentialTokens: public Tokens { +public: + /** + Constructor that initializes the first number to zero and sets boolean + determining whether the first item (zero) will be returned + + @param[in] showZero Determines if the first token (0) will be generated + */ + explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {} + + /** + Generates the token representing the integer coming after the previous token + generated, as an OUString + + @param[out] token Pointer to OUString in which the generated token is stored + @return Boolean representing whether token generation was successful + */ + bool next(OUString * token) override { + assert(token != nullptr); + if (m_value == SAL_MAX_UINT32) { + return false; + } + *token = m_show ? OUString::number(m_value) : OUString(); + ++m_value; + m_show = true; + return true; + } + +private: + /// Represents the integer value stored in the current token + sal_uInt32 m_value; + /// Represents if the first token will be generated, or if it will just be an empty string + bool m_show; +}; + +/** + Modification of `SequentialTokens` class and specialization of `Tokens` class that generates + sequential numbers that are padded to a specified number of characters with leading zeroes. + + @see Tokens + @see SequentialTokens + @tparam padding Specifies the minimum number of characters in a returned token + @tparam initial Specifies the integer value of the first token +*/ +template +class PaddedSequentialTokens: public Tokens { +public: + /** + Constructor initializing the number of characters to which the token must be padded, + the integer value of the first token, and whether the first token will be shown or just + returned as an empty OUString. + + @param[in] showInitial Determines if the first token will be generated or just empty + */ + explicit PaddedSequentialTokens(bool showInitial): + m_padding(padding), m_value(initial), m_show(showInitial) {} + + /** + Generates the token coming after the one returned by the last call, as an OUString + + @param[out] token Pointer to OUString in which the generated token is stored + @return Boolean representing whether token generation was successful + */ + bool next(OUString * token) override + { + // Ensure that the pointer to OUString is valid + assert(token != nullptr); + + // Fail if it is impossible to generate more tokens + if (m_value == SAL_MAX_UINT32) + { + return false; + } + + // Generate token value if it needs to be displayed; otherwise, just return empty string + if (m_show) + { + OUString newTokenValue = OUString::number(m_value); + sal_Int32 nPaddingCharacters = m_padding - newTokenValue.getLength(); + + while (nPaddingCharacters > 0) + { + newTokenValue = "0" + newTokenValue; + nPaddingCharacters--; + } + + *token = newTokenValue; + } + else + { + *token = OUString(); + } + + // Increment internal counter for next call + ++m_value; + + // Ensure the second (and future) tokens will be generated + m_show = true; + + return true; + } + +private: + /// Minimum number of characters to which the token must be padded + sal_uInt32 m_padding; + /// Integer value stored in current token + sal_uInt32 m_value; + /// Represents whether the first token will be shown or not + bool m_show; +}; + +/** + Specialization of `Tokens` that generates a guaranteed unique token every call + + @see Tokens +*/ +class UniqueTokens: public Tokens { +public: + /** + Initializes the count of tokens generated to zero + */ + UniqueTokens(): m_count(0) {} + + /** + Generate a new unique token as an OUString + + @param[out] token Pointer to OUString in which teh generated token is stored + @return Boolean representing whether token generation was successful + */ + bool next(OUString * token) override { + assert(token != nullptr); + // Because of the shared globalValue, no single instance of UniqueTokens + // is guaranteed to exhaustively test all 36^6 possible values, but stop + // after that many attempts anyway: + sal_uInt32 radix = 36; + sal_uInt32 max = radix * radix * radix * radix * radix * radix; + // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295 + if (m_count == max) { + return false; + } + sal_uInt32 v; + { + osl::MutexGuard g(osl::Mutex::getGlobalMutex()); + globalValue + = ((globalValue == SAL_MAX_UINT32 + ? tools::Time::GetSystemTicks() : globalValue + 1) + % max); + v = globalValue; + } + *token = OUString::number(v, radix); + ++m_count; + return true; + } + +private: + /// Static integer representing shared value among all instances of UniqueTokens + static sal_uInt32 globalValue; + /// Number of tokens generated by current instance + sal_uInt32 m_count; +}; class SvFileStream; class SvStream; @@ -80,15 +272,22 @@ UNOTOOLS_DLLPUBLIC OUString CreateTempName(); UNOTOOLS_DLLPUBLIC OUString CreateTempURL( const OUString* pParent=nullptr, bool bDirectory=false ); /** -Same as above; additionally the name starts with some given characters followed by a counter ( example: -rLeadingChars="abc" means "abc0","abc1" and so on, depending on existing files in the folder ). -The extension string may be f.e. ".txt" or "", if no extension string is given, ".tmp" is used - @param _bStartWithZero If set to false names will be generated like "abc","abc0","abc1" - @param bCreateParentDirs If rLeadingChars contains a slash, this will create the required - parent directories. + Same as above; additionally the name starts with some given characters followed by a counter ( example: + rLeadingChars="abc" means "abc0","abc1" and so on, depending on existing files in the folder ). +The extension string may be f.e. ".txt" or "", if no extension string is given, ".tmp" is use + @param _bStartWithZero If set to false names will be generated like "abc","abc0","abc1" + @param pExtension String representing filename extension + @param bCreateParentDirs If rLeadingChars contains a slash, this will create the required + parent directories. + @param concatFunc Optional parameter to change the way the counter is added onto the character. + Default is to just add the number immediately after the name, e.g. "abc0" "abc1" etc. */ -UNOTOOLS_DLLPUBLIC OUString CreateTempURL( std::u16string_view rLeadingChars, bool _bStartWithZero=true, std::u16string_view pExtension={}, - const OUString* pParent=nullptr, bool bCreateParentDirs=false ); +UNOTOOLS_DLLPUBLIC OUString CreateTempURL( + std::u16string_view rLeadingChars, bool _bStartWithZero = true, + std::u16string_view pExtension = {}, const OUString* pParent = nullptr, + bool bCreateParentDirs = false, + std::function concatFunc + = [](OUString aName, OUString token) -> OUString { return aName + token; }); /** The TempNameBaseDirectory is a subfolder in the folder that is passed as a "physical" file name in the diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx index 763b54bca272..31a7b81299f1 100644 --- a/sw/inc/doc.hxx +++ b/sw/inc/doc.hxx @@ -190,6 +190,14 @@ namespace sfx2 { class IXmlIdRegistry; } +namespace { + enum SwSplitDocType + { + SPLITDOC_TO_GLOBALDOC, + SPLITDOC_TO_HTML + }; +} + void SetAllScriptItem( SfxItemSet& rSet, const SfxPoolItem& rItem ); using SwRubyList = std::vector>; @@ -380,10 +388,18 @@ private: bool UnProtectTableCells( SwTable& rTable ); - /** Create sub-documents according to the given collection. - If no collection is given, take chapter style of the 1st level. */ - bool SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, - const SwTextFormatColl* pSplitColl, int nOutlineLevel = 0 ); + /** + Create sub-documents according to the given collection. + If no collection is given, take chapter style of the 1st level. + + @param eDocType Enumeration value representing doctype to be generated + @param rPath String representing parent directories of file to be generated + @param concatFunc Function representing how token is concatenated to the filename + */ + bool SplitDoc(sal_uInt16 eDocType, const OUString& rPath, bool bOutline, + const SwTextFormatColl* pSplitColl, int nOutlineLevel = 0, + std::function concatFunc = + [](OUString aName, OUString token) -> OUString { return aName + token; }); // Update charts of given table. void UpdateCharts_( const SwTable& rTable, SwViewShell const & rVSh ) const; @@ -1525,12 +1541,37 @@ public: // Create sub-documents according to given collection. // If no collection is given, use chapter styles for 1st level. - bool GenerateGlobalDoc( const OUString& rPath, - const SwTextFormatColl* pSplitColl ); - bool GenerateGlobalDoc( const OUString& rPath, int nOutlineLevel ); - bool GenerateHTMLDoc( const OUString& rPath, - const SwTextFormatColl* pSplitColl ); - bool GenerateHTMLDoc( const OUString& rPath, int nOutlineLevel ); + bool GenerateGlobalDoc( + const OUString& rPath, const SwTextFormatColl* pSplitColl, + std::function concatFunc + = [](OUString aName, OUString token) -> OUString { return aName + token; }) + { + return SplitDoc(SPLITDOC_TO_GLOBALDOC, rPath, false, pSplitColl, 0, concatFunc); + } + + bool GenerateGlobalDoc( + const OUString& rPath, int nOutlineLevel, + std::function concatFunc + = [](OUString aName, OUString token) -> OUString { return aName + token; }) + { + return SplitDoc(SPLITDOC_TO_GLOBALDOC, rPath, true, nullptr, nOutlineLevel, concatFunc); + } + + bool GenerateHTMLDoc( + const OUString& rPath, const SwTextFormatColl* pSplitColl, + std::function concatFunc + = [](OUString aName, OUString token) -> OUString { return aName + token; }) + { + return SplitDoc(SPLITDOC_TO_HTML, rPath, false, pSplitColl, 0, concatFunc); + } + + bool GenerateHTMLDoc( + const OUString& rPath, int nOutlineLevel, + std::function concatFunc + = [](OUString aName, OUString token) -> OUString { return aName + token; }) + { + return SplitDoc(SPLITDOC_TO_HTML, rPath, true, nullptr, nOutlineLevel, concatFunc); + } // Compare two documents. tools::Long CompareDoc( const SwDoc& rDoc ); diff --git a/sw/source/core/doc/docglbl.cxx b/sw/source/core/doc/docglbl.cxx index bd0460e59551..c6827653b8d2 100644 --- a/sw/source/core/doc/docglbl.cxx +++ b/sw/source/core/doc/docglbl.cxx @@ -56,38 +56,6 @@ using namespace ::com::sun::star; -namespace { - -enum SwSplitDocType -{ - SPLITDOC_TO_GLOBALDOC, - SPLITDOC_TO_HTML -}; - -} - -bool SwDoc::GenerateGlobalDoc( const OUString& rPath, - const SwTextFormatColl* pSplitColl ) -{ - return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, false, pSplitColl ); -} - -bool SwDoc::GenerateGlobalDoc( const OUString& rPath, int nOutlineLevel ) -{ - return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, true, nullptr, nOutlineLevel ); -} - -bool SwDoc::GenerateHTMLDoc( const OUString& rPath, int nOutlineLevel ) -{ - return SplitDoc( SPLITDOC_TO_HTML, rPath, true, nullptr, nOutlineLevel ); -} - -bool SwDoc::GenerateHTMLDoc( const OUString& rPath, - const SwTextFormatColl* pSplitColl ) -{ - return SplitDoc( SPLITDOC_TO_HTML, rPath, false, pSplitColl ); -} - // two helpers for outline mode static SwNode* GetStartNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) { @@ -159,7 +127,7 @@ static SwNode* GetEndNode( const SwOutlineNodes* pOutlNds, const SwTextFormatCol return nullptr; } -bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, const SwTextFormatColl* pSplitColl, int nOutlineLevel ) +bool SwDoc::SplitDoc(sal_uInt16 eDocType, const OUString& rPath, bool bOutline, const SwTextFormatColl* pSplitColl, int nOutlineLevel, std::function concatFunc) { // Iterate over all the template's Nodes, creating an own // document for every single one and replace linked sections (GlobalDoc) for links (HTML). @@ -316,9 +284,9 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, SwNodeIndex aIdx( pDoc->GetNodes().GetEndOfExtras(), 2 ); if( aIdx.GetIndex() + 1 != pDoc->GetNodes().GetEndOfContent().GetIndex() ) - pDoc->GetNodes().Delete( aIdx ); + pDoc->GetNodes().Delete(aIdx); - sFileName = utl::CreateTempURL(sLeading, true, sExt, &sPath); + sFileName = utl::CreateTempURL(sLeading, true, sExt, &sPath, false, concatFunc); SfxMedium* pTmpMed = new SfxMedium( sFileName, StreamMode::STD_READWRITE ); pTmpMed->SetFilter( pFilter ); @@ -357,6 +325,7 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, SwNodeIndex aSIdx( aTmp.GetMark()->GetNode() ); SwNodeIndex aEIdx( aTmp.GetPoint()->GetNode() ); + // Ensure that *pStartNd and aEndIdx.GetNode() (the nodes around the mark and point) refer to valid nodes // Try to move past the end if( !aTmp.Move( fnMoveForward, GoInNode ) ) { @@ -382,7 +351,7 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && aSIdx <= *pAnchorNode && - *pAnchorNode < aEIdx.GetNode() ) + *pAnchorNode <= aEIdx.GetNode() ) { getIDocumentLayoutAccess().DelLayoutFormat( pFly ); } @@ -390,6 +359,7 @@ bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, ++n; } + aTmp.DeleteMark(); GetNodes().Delete( aSIdx, nNodeDiff ); } diff --git a/sw/source/uibase/app/docsh2.cxx b/sw/source/uibase/app/docsh2.cxx index 53f3cffdf9f1..90bd585fbf2b 100644 --- a/sw/source/uibase/app/docsh2.cxx +++ b/sw/source/uibase/app/docsh2.cxx @@ -1108,15 +1108,21 @@ void SwDocShell::Execute(SfxRequest& rReq) if( !aFileName.isEmpty() ) { + std::function concatFunc + = [](OUString aName, OUString token) -> OUString + { return token + u"_" + aName; }; + if( PrepareClose( false ) ) { - SwWait aWait( *this, true ); + SwWait aWait(*this, true); if ( bCreateByOutlineLevel ) { bDone = bCreateHtml - ? m_xDoc->GenerateHTMLDoc( aFileName, nTemplateOutlineLevel ) - : m_xDoc->GenerateGlobalDoc( aFileName, nTemplateOutlineLevel ); + ? m_xDoc->GenerateHTMLDoc( + aFileName, nTemplateOutlineLevel, concatFunc) + : m_xDoc->GenerateGlobalDoc( + aFileName, nTemplateOutlineLevel, concatFunc); } else { @@ -1124,8 +1130,8 @@ void SwDocShell::Execute(SfxRequest& rReq) if ( !aTemplateName.isEmpty() ) pSplitColl = m_xDoc->FindTextFormatCollByName(UIName(aTemplateName)); bDone = bCreateHtml - ? m_xDoc->GenerateHTMLDoc( aFileName, pSplitColl ) - : m_xDoc->GenerateGlobalDoc( aFileName, pSplitColl ); + ? m_xDoc->GenerateHTMLDoc(aFileName, pSplitColl, concatFunc) + : m_xDoc->GenerateGlobalDoc(aFileName, pSplitColl, concatFunc); } if( bDone ) { diff --git a/unotools/source/ucbhelper/tempfile.cxx b/unotools/source/ucbhelper/tempfile.cxx index 739572ef75e2..444a730aba11 100644 --- a/unotools/source/ucbhelper/tempfile.cxx +++ b/unotools/source/ucbhelper/tempfile.cxx @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -44,6 +43,8 @@ #include #endif +sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32; + namespace { OUString gTempNameBase_Impl; @@ -125,70 +126,6 @@ OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs return ensureTrailingSlash(aName); } -class Tokens { -public: - virtual bool next(OUString *) = 0; - -protected: - virtual ~Tokens() {} // avoid warnings -}; - -class SequentialTokens: public Tokens { -public: - explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {} - - bool next(OUString * token) override { - assert(token != nullptr); - if (m_value == SAL_MAX_UINT32) { - return false; - } - *token = m_show ? OUString::number(m_value) : OUString(); - ++m_value; - m_show = true; - return true; - } - -private: - sal_uInt32 m_value; - bool m_show; -}; - -class UniqueTokens: public Tokens { -public: - UniqueTokens(): m_count(0) {} - - bool next(OUString * token) override { - assert(token != nullptr); - // Because of the shared globalValue, no single instance of UniqueTokens - // is guaranteed to exhaustively test all 36^6 possible values, but stop - // after that many attempts anyway: - sal_uInt32 radix = 36; - sal_uInt32 max = radix * radix * radix * radix * radix * radix; - // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295 - if (m_count == max) { - return false; - } - sal_uInt32 v; - { - osl::MutexGuard g(osl::Mutex::getGlobalMutex()); - globalValue - = ((globalValue == SAL_MAX_UINT32 - ? tools::Time::GetSystemTicks() : globalValue + 1) - % max); - v = globalValue; - } - *token = OUString::number(v, radix); - ++m_count; - return true; - } - -private: - static sal_uInt32 globalValue; - - sal_uInt32 m_count; -}; - -sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32; class TempDirCreatedObserver : public osl::DirectoryCreationObserver { @@ -203,7 +140,9 @@ sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32; OUString lcl_createName( std::u16string_view rLeadingChars, Tokens & tokens, std::u16string_view pExtension, const OUString* pParent, bool bDirectory, bool bKeep, bool bLock, - bool bCreateParentDirs ) + bool bCreateParentDirs, + std::function concatFunc = + [](OUString aName, OUString token) -> OUString { return aName + token; }) { OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs ); if ( bCreateParentDirs ) @@ -218,12 +157,11 @@ OUString lcl_createName( if (!okOrExists(osl::Directory::createPath(aDirName, &observer))) return OUString(); } - aName += rLeadingChars; OUString token; while (tokens.next(&token)) { - OUString aTmp( aName + token ); + OUString aTmp(aName + concatFunc(OUString(rLeadingChars), token)); if ( !pExtension.empty() ) aTmp += pExtension; else @@ -384,13 +322,14 @@ OUString CreateTempURL( const OUString* pParent, bool bDirectory ) return CreateTempName_Impl( pParent, true, bDirectory ); } -OUString CreateTempURL( std::u16string_view rLeadingChars, bool _bStartWithZero, - std::u16string_view pExtension, const OUString* pParent, - bool bCreateParentDirs ) +OUString CreateTempURL(std::u16string_view rLeadingChars, bool _bStartWithZero, + std::u16string_view pExtension, const OUString* pParent, + bool bCreateParentDirs, + std::function concatFunc) { - SequentialTokens t(_bStartWithZero); - return lcl_createName( rLeadingChars, t, pExtension, pParent, false, - true, true, bCreateParentDirs ); + PaddedSequentialTokens<3,1> t(_bStartWithZero); + return lcl_createName(rLeadingChars, t, pExtension, pParent, false, + true, true, bCreateParentDirs, concatFunc); } TempFileNamed::TempFileNamed( const OUString* pParent, bool bDirectory )