summaryrefslogtreecommitdiff
path: root/xmloff/source/style
diff options
context:
space:
mode:
authorLaurent Balland <laurent.balland@mailo.fr>2023-02-04 09:29:20 +0100
committerEike Rathke <erack@redhat.com>2023-08-25 17:49:17 +0200
commit74d9da037cac01c5abd768a99b2f948553fbf144 (patch)
treed91cf6581202c313f9a9c74fcc9f4ef31eb6928e /xmloff/source/style
parentc7b59c9484ae6ff88cd8d7017aeb83b02e212c9c (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.cxx107
-rw-r--r--xmloff/source/style/xmlnumfi.cxx103
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 );
}
}
}