diff options
author | Laurent Balland <laurent.balland@mailo.fr> | 2023-02-04 09:29:20 +0100 |
---|---|---|
committer | Eike Rathke <erack@redhat.com> | 2023-08-25 17:49:17 +0200 |
commit | 74d9da037cac01c5abd768a99b2f948553fbf144 (patch) | |
tree | d91cf6581202c313f9a9c74fcc9f4ef31eb6928e /xmloff/source/style | |
parent | c7b59c9484ae6ff88cd8d7017aeb83b02e212c9c (diff) |
tdf#152724 Extend ODF for blank width "_x"
Number format code "_x" is currently saved as a text string containing a
number of spaces corresponding to the width of character "x". It may be
confusing for user if its format code is modified.
This change introduces a new XML tag XML_BLANK_WIDTH_CHAR to replace the
previous text string if ODF version is extended
<number:text> and <number:embedded-text>:
the attribute is composed of a string containing the used character and its
position in the text string (if position is 0, it is omitted).
Several blank code characters are separated by '_'.
Replacement blanks in the text string are preserved to enable compatibility.
Example: format code
"foo"_M_I_N"!"???,???.000_.000"!"_)
is saved as:
<number:number-style style:name="N173">
<number:text loext:blank-width-char="M3_I6_N7">foo !</number:text>
<number:number number:decimal-places="6" number:min-decimal-places="6" number:min-integer-digits="6" loext:max-blank-integer-digits="6" number:grouping="true">
<number:embedded-text number:position="-4" loext:blank-width-char="."> </number:embedded-text>
</number:number>
<number:text loext:blank-width-char=")1">! </number:text>
</number:number-style>
Add QA test
Change-Id: I785e1a14ecccc900e9fd5af88dd7b743fefcc48c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146582
Tested-by: Jenkins
Reviewed-by: Eike Rathke <erack@redhat.com>
Diffstat (limited to 'xmloff/source/style')
-rw-r--r-- | xmloff/source/style/xmlnumfe.cxx | 107 | ||||
-rw-r--r-- | xmloff/source/style/xmlnumfi.cxx | 103 |
2 files changed, 177 insertions, 33 deletions
diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx index fb767dc3a10d..67675cf22ab3 100644 --- a/xmloff/source/style/xmlnumfe.cxx +++ b/xmloff/source/style/xmlnumfe.cxx @@ -66,9 +66,10 @@ struct SvXMLEmbeddedTextEntry sal_uInt16 nSourcePos; // position in NumberFormat (to skip later) sal_Int32 nFormatPos; // resulting position in embedded-text element OUString aText; + bool isBlankWidth; // "_x" - SvXMLEmbeddedTextEntry( sal_uInt16 nSP, sal_Int32 nFP, OUString aT ) : - nSourcePos(nSP), nFormatPos(nFP), aText(std::move(aT)) {} + SvXMLEmbeddedTextEntry( sal_uInt16 nSP, sal_Int32 nFP, OUString aT, bool bBW = false ) : + nSourcePos(nSP), nFormatPos(nFP), aText(std::move(aT)), isBlankWidth( bBW ) {} }; } @@ -323,6 +324,16 @@ void SvXMLNumFmtExport::FinishTextElement_Impl(bool bUseExtensionNS) { if ( m_bHasText ) { + if ( !m_sBlankWidthString.isEmpty() ) + { + // Export only for 1.3 with extensions and later. + SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); + if (eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 )) + { + m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_WIDTH_CHAR, + m_sBlankWidthString.makeStringAndClear() ); + } + } sal_uInt16 nNS = bUseExtensionNS ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER; SvXMLElementExport aElem( m_rExport, nNS, XML_TEXT, true, false ); @@ -502,6 +513,36 @@ void SvXMLNumFmtExport::WriteRepeatedElement_Impl( sal_Unicode nChar ) } } +namespace { +void lcl_WriteBlankWidthString( std::u16string_view rBlankWidthChar, OUStringBuffer& rBlankWidthString, OUStringBuffer& rTextContent ) +{ + // export "_x" + if ( rBlankWidthString.isEmpty() ) + { + rBlankWidthString.append( rBlankWidthChar ); + if ( !rTextContent.isEmpty() ) + { + // add position in rTextContent + rBlankWidthString.append( rTextContent.getLength() ); + } + } + else + { + // add "_" as separator if there are several blank width char + rBlankWidthString.append( "_" ); + rBlankWidthString.append( rBlankWidthChar ); + rBlankWidthString.append( rTextContent.getLength() ); + } + // for previous versions, turn "_x" into the number of spaces used for x in InsertBlanks in the NumberFormat + if ( !rBlankWidthChar.empty() ) + { + OUString aBlanks; + SvNumberformat::InsertBlanks( aBlanks, 0, rBlankWidthChar[0] ); + rTextContent.append( aBlanks ); + } +} +} + void SvXMLNumFmtExport::WriteSecondsElement_Impl( bool bLong, sal_uInt16 nDecimals ) { FinishTextElement_Impl(); @@ -553,28 +594,45 @@ void SvXMLNumFmtExport::WriteIntegerElement_Impl( void SvXMLNumFmtExport::WriteEmbeddedEntries_Impl( const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries ) { auto nEntryCount = rEmbeddedEntries.size(); + SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); for (decltype(nEntryCount) nEntry=0; nEntry < nEntryCount; ++nEntry) { - const SvXMLEmbeddedTextEntry *const pObj = &rEmbeddedEntries[nEntry]; + const SvXMLEmbeddedTextEntry* pObj = &rEmbeddedEntries[nEntry]; // position attribute // position == 0 is between first integer digit and decimal separator // position < 0 is inside decimal part m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_POSITION, OUString::number( pObj->nFormatPos ) ); - SvXMLElementExport aChildElem( m_rExport, XML_NAMESPACE_NUMBER, XML_EMBEDDED_TEXT, - true, false ); // text as element content - OUStringBuffer aContent( pObj->aText ); - while ( nEntry+1 < nEntryCount && rEmbeddedEntries[nEntry+1].nFormatPos == pObj->nFormatPos ) + OUStringBuffer aContent; + OUStringBuffer aBlankWidthString; + do { - // The array can contain several elements for the same position in the number - // (for example, literal text and space from underscores). They must be merged - // into a single embedded-text element. - aContent.append(rEmbeddedEntries[nEntry+1].aText); + pObj = &rEmbeddedEntries[nEntry]; + if ( pObj->isBlankWidth ) + { + // (#i20396# the spaces may also be in embedded-text elements) + lcl_WriteBlankWidthString( pObj->aText, aBlankWidthString, aContent ); + } + else + { + // The array can contain several elements for the same position in the number. + // Literal texts are merged into a single embedded-text element. + aContent.append( pObj->aText ); + } ++nEntry; } + while ( nEntry < nEntryCount + && rEmbeddedEntries[nEntry].nFormatPos == pObj->nFormatPos ); + --nEntry; + + // Export only for 1.3 with extensions and later. + if ( !aBlankWidthString.isEmpty() && eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) ) + m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_WIDTH_CHAR, aBlankWidthString.makeStringAndClear() ); + SvXMLElementExport aChildElem( m_rExport, XML_NAMESPACE_NUMBER, XML_EMBEDDED_TEXT, + true, false ); m_rExport.Characters( aContent.makeStringAndClear() ); } } @@ -1196,13 +1254,13 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt aAttr.Style ); } + SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if ( !aAttr.Spellout.isEmpty() ) { const bool bWriteSpellout = aAttr.Format.isEmpty(); assert(bWriteSpellout); // mutually exclusive // Export only for 1.2 and later with extensions - SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); // Also ensure that duplicated transliteration-language and // transliteration-country attributes never escape into the wild with // releases. @@ -1389,7 +1447,6 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt } // collect strings for embedded-text (must be known before number element is written) - SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); bool bAllowEmbedded = ( nFmtType == SvNumFormatType::ALL || nFmtType == SvNumFormatType::NUMBER || nFmtType == SvNumFormatType::CURRENCY || // Export only for 1.x with extensions @@ -1429,18 +1486,25 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt // text (literal or underscore) within the integer (>=0) or decimal (<0) part of a number:number element OUString aEmbeddedStr; + bool bSaveBlankWidthSymbol = false; if ( nElemType == NF_SYMBOLTYPE_STRING || nElemType == NF_SYMBOLTYPE_PERCENT ) { aEmbeddedStr = *pElemStr; } else if (pElemStr->getLength() >= 2) { - SvNumberformat::InsertBlanks( aEmbeddedStr, 0, (*pElemStr)[1] ); + if ( eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) ) + { + aEmbeddedStr = pElemStr->copy( 1, 1 ); + bSaveBlankWidthSymbol = true; + } + else // turn "_x" into the number of spaces used for x in InsertBlanks in the NumberFormat + SvNumberformat::InsertBlanks( aEmbeddedStr, 0, (*pElemStr)[1] ); } sal_Int32 nEmbedPos = nIntegerSymbols - nDigitsPassed; aEmbeddedEntries.push_back( - SvXMLEmbeddedTextEntry(nPos, nEmbedPos, aEmbeddedStr)); + SvXMLEmbeddedTextEntry( nPos, nEmbedPos, aEmbeddedStr, bSaveBlankWidthSymbol )); } break; } @@ -1505,13 +1569,12 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt case NF_SYMBOLTYPE_BLANK: if ( pElemStr && !lcl_IsInEmbedded( aEmbeddedEntries, nPos ) ) { - // turn "_x" into the number of spaces used for x in InsertBlanks in the NumberFormat - // (#i20396# the spaces may also be in embedded-text elements) - - OUString aBlanks; - if (pElemStr->getLength() >= 2) - SvNumberformat::InsertBlanks( aBlanks, 0, (*pElemStr)[1] ); - AddToTextElement_Impl( aBlanks ); + if ( pElemStr->getLength() == 2 ) + { + OUString aBlankWidthChar = pElemStr->copy( 1 ); + lcl_WriteBlankWidthString( aBlankWidthChar, m_sBlankWidthString, m_sTextContent ); + m_bHasText = true; + } } break; case NF_KEY_GENERAL : diff --git a/xmloff/source/style/xmlnumfi.cxx b/xmloff/source/style/xmlnumfi.cxx index e43e732a4ba8..f9f9bce5f675 100644 --- a/xmloff/source/style/xmlnumfi.cxx +++ b/xmloff/source/style/xmlnumfi.cxx @@ -123,6 +123,7 @@ class SvXMLNumFmtElementContext : public SvXMLImportContext bool bLong; bool bTextual; OUString sCalendar; + OUString sBlankWidthString; public: SvXMLNumFmtElementContext( SvXMLImport& rImport, sal_Int32 nElement, @@ -134,7 +135,7 @@ public: virtual void SAL_CALL characters( const OUString& rChars ) override; virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; - void AddEmbeddedElement( sal_Int32 nFormatPos, const OUString& rContent ); + void AddEmbeddedElement( sal_Int32 nFormatPos, std::u16string_view rContent, std::u16string_view rBlankWidthString ); }; class SvXMLNumFmtEmbeddedTextContext : public SvXMLImportContext @@ -142,6 +143,7 @@ class SvXMLNumFmtEmbeddedTextContext : public SvXMLImportContext SvXMLNumFmtElementContext& rParent; OUStringBuffer aContent; sal_Int32 nTextPosition; + OUString aBlankWidthString; public: SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport, sal_Int32 nElement, @@ -461,6 +463,11 @@ SvXMLNumFmtEmbeddedTextContext::SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rIm if (::sax::Converter::convertNumber( nAttrVal, aIter.toView() )) nTextPosition = nAttrVal; } + else if ( aIter.getToken() == XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR) + || aIter.getToken() == XML_ELEMENT(NUMBER, XML_BLANK_WIDTH_CHAR) ) + { + aBlankWidthString = aIter.toString(); + } else XMLOFF_WARN_UNKNOWN("xmloff", aIter); } @@ -473,7 +480,7 @@ void SvXMLNumFmtEmbeddedTextContext::characters( const OUString& rChars ) void SvXMLNumFmtEmbeddedTextContext::endFastElement(sal_Int32 ) { - rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear() ); + rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear(), aBlankWidthString ); } static bool lcl_ValidChar( sal_Unicode cChar, const SvXMLNumFormatContext& rParent ) @@ -778,6 +785,10 @@ SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport, case XML_ELEMENT(NUMBER, XML_CALENDAR): sCalendar = aIter.toString(); break; + case XML_ELEMENT(NUMBER, XML_BLANK_WIDTH_CHAR): + case XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR): + sBlankWidthString = aIter.toString(); + break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } @@ -856,15 +867,83 @@ void SvXMLNumFmtElementContext::characters( const OUString& rChars ) aContent.append( rChars ); } -void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, const OUString& rContent ) +namespace { +void lcl_InsertBlankWidthChars( std::u16string_view rBlankWidthString, OUStringBuffer& rContent ) { - if (rContent.isEmpty()) - return; + sal_Int32 nShiftPosition = 1; // rContent starts with a quote + const size_t nLenBlank = rBlankWidthString.size(); + for ( size_t i = 0 ; i < nLenBlank ; i++ ) + { + sal_Unicode nChar = rBlankWidthString[ i ]; + OUString aBlanks; + SvNumberformat::InsertBlanks( aBlanks, 0, nChar ); + sal_Int32 nPositionContent = 0; + if ( ++i < nLenBlank ) + { + sal_Int32 nNext = rBlankWidthString.find( '_', i ); + if ( static_cast<sal_Int32>( i ) < nNext ) + { + nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i, nNext - i ) ); + i = nNext; + } + else + nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i ) ); + } + nPositionContent += nShiftPosition; + if ( nPositionContent >= 0 ) + { + rContent.remove( nPositionContent, aBlanks.getLength() ); + if ( nPositionContent >= 1 && rContent[ nPositionContent-1 ] == '\"' ) + { + nPositionContent--; + rContent.insert( nPositionContent, nChar ); + rContent.insert( nPositionContent, '_' ); + } + else + { + rContent.insert( nPositionContent, '\"' ); + rContent.insert( nPositionContent, nChar ); + rContent.insert( nPositionContent, "\"_" ); + nShiftPosition += 2; + } + // rContent length was modified: remove blanks, add "_x" + nShiftPosition += 2 - aBlanks.getLength(); + } + } + // remove empty string at the end of rContent + if ( std::u16string_view( rContent ).substr( rContent.getLength() - 2 ) == u"\"\"" ) + { + sal_Int32 nLen = rContent.getLength(); + if ( nLen >= 3 && rContent[ nLen-3 ] != '\\' ) + rContent.truncate( nLen - 2 ); + } +} +} - auto iterPair = aNumInfo.m_EmbeddedElements.emplace(nFormatPos, rContent); +void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, std::u16string_view rContentEmbedded, std::u16string_view rBlankWidthString ) +{ + if ( rContentEmbedded.empty() ) + return; + OUStringBuffer aContentEmbedded( rContentEmbedded ); + // #107805# always quote embedded strings - even space would otherwise + // be recognized as thousands separator in French. + aContentEmbedded.insert( 0, '"' ); + aContentEmbedded.append( '"' ); + if ( !rBlankWidthString.empty() ) + lcl_InsertBlankWidthChars( rBlankWidthString, aContentEmbedded ); + + auto iterPair = aNumInfo.m_EmbeddedElements.emplace( nFormatPos, aContentEmbedded.toString() ); if (!iterPair.second) + { // there's already an element at this position - append text to existing element - iterPair.first->second += rContent; + if ( iterPair.first->second.endsWith( "\"" ) && aContentEmbedded[ 0 ] == '"' ) + { // remove double quote + iterPair.first->second = OUString::Concat( iterPair.first->second.subView( 0, iterPair.first->second.getLength() - 1 ) ) + + aContentEmbedded.subView( 1, aContentEmbedded.getLength() - 1 ); + } + else + iterPair.first->second += aContentEmbedded; + } } void SvXMLNumFmtElementContext::endFastElement(sal_Int32 ) @@ -889,6 +968,11 @@ void SvXMLNumFmtElementContext::endFastElement(sal_Int32 ) if ( !aContent.isEmpty() ) { lcl_EnquoteIfNecessary( aContent, rParent ); + if ( !sBlankWidthString.isEmpty() ) + { + lcl_InsertBlankWidthChars( sBlankWidthString, aContent ); + sBlankWidthString = ""; + } rParent.AddToCode( aContent ); aContent.setLength(0); } @@ -1818,10 +1902,7 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo ) sal_Int32 nInsertPos = nZeroPos - nFormatPos; if ( nInsertPos >= 0 ) { - // #107805# always quote embedded strings - even space would otherwise - // be recognized as thousands separator in French. - - aNumStr.insert(nInsertPos, OUString::Concat("\"") + it.second + "\""); + aNumStr.insert( nInsertPos, it.second ); } } } |