xref: /core/xmloff/source/style/xmlnumfi.cxx (revision 46b7fd59)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <svl/zforlist.hxx>
21 #include <svl/numformat.hxx>
22 #include <svl/zformat.hxx>
23 #include <svl/numuno.hxx>
24 #include <i18nlangtag/languagetag.hxx>
25 #include <tools/color.hxx>
26 #include <osl/diagnose.h>
27 #include <rtl/math.hxx>
28 #include <rtl/ustrbuf.hxx>
29 #include <sal/log.hxx>
30 
31 #include <sax/tools/converter.hxx>
32 
33 #include <utility>
34 #include <xmloff/xmlement.hxx>
35 #include <xmloff/xmlnumfi.hxx>
36 #include <xmloff/xmltkmap.hxx>
37 #include <xmloff/xmlnamespace.hxx>
38 #include <xmloff/xmlictxt.hxx>
39 #include <xmloff/xmlimp.hxx>
40 #include <xmloff/xmluconv.hxx>
41 #include <xmloff/namespacemap.hxx>
42 #include <xmloff/families.hxx>
43 #include <xmloff/xmltoken.hxx>
44 #include <xmloff/languagetagodf.hxx>
45 
46 #include <memory>
47 #include <string_view>
48 #include <vector>
49 
50 using namespace ::com::sun::star;
51 using namespace ::xmloff::token;
52 
53 namespace {
54 
55 struct SvXMLNumFmtEntry
56 {
57     OUString   aName;
58     sal_uInt32  nKey;
59     bool        bRemoveAfterUse;
60 
61     SvXMLNumFmtEntry( OUString aN, sal_uInt32 nK, bool bR ) :
62         aName(std::move(aN)), nKey(nK), bRemoveAfterUse(bR) {}
63 };
64 
65 }
66 
67 class SvXMLNumImpData
68 {
69     SvNumberFormatter*  pFormatter;
70     std::unique_ptr<LocaleDataWrapper>  pLocaleData;
71     std::vector<SvXMLNumFmtEntry> m_NameEntries;
72 
73     uno::Reference< uno::XComponentContext > m_xContext;
74 
75 public:
76     SvXMLNumImpData(
77         SvNumberFormatter* pFmt,
78         const uno::Reference<uno::XComponentContext>& rxContext );
79 
80     SvNumberFormatter*      GetNumberFormatter() const  { return pFormatter; }
81     const LocaleDataWrapper&    GetLocaleData( LanguageType nLang );
82     sal_uInt32              GetKeyForName( std::u16string_view rName );
83     void                    AddKey( sal_uInt32 nKey, const OUString& rName, bool bRemoveAfterUse );
84     void                    SetUsed( sal_uInt32 nKey );
85     void                    RemoveVolatileFormats();
86 };
87 
88 struct SvXMLNumberInfo
89 {
90     sal_Int32   nDecimals           = -1;
91     sal_Int32   nInteger            = -1;       /// Total min number of digits in integer part ('0' + '?')
92     sal_Int32   nBlankInteger       = -1;       /// Number of '?' in integer part
93     sal_Int32   nExpDigits          = -1;
94     sal_Int32   nExpInterval        = -1;
95     sal_Int32   nMinNumerDigits     = -1;
96     sal_Int32   nMinDenomDigits     = -1;
97     sal_Int32   nMaxNumerDigits     = -1;
98     sal_Int32   nMaxDenomDigits     = -1;
99     sal_Int32   nFracDenominator    = -1;
100     sal_Int32   nMinDecimalDigits   = -1;
101     sal_Int32   nZerosNumerDigits   = -1;
102     sal_Int32   nZerosDenomDigits   = -1;
103     bool        bGrouping           = false;
104     bool        bDecReplace         = false;
105     bool        bExpSign            = true;
106     bool        bDecAlign           = false;
107     double      fDisplayFactor      = 1.0;
108     OUString    aIntegerFractionDelimiter;
109     std::map<sal_Int32, OUString> m_EmbeddedElements;
110 };
111 
112 namespace {
113 
114 enum class SvXMLStyleTokens;
115 
116 class SvXMLNumFmtElementContext : public SvXMLImportContext
117 {
118     SvXMLNumFormatContext&  rParent;
119     SvXMLStyleTokens        nType;
120     OUStringBuffer          aContent;
121     SvXMLNumberInfo         aNumInfo;
122     LanguageType            nElementLang;
123     bool                    bLong;
124     bool                    bTextual;
125     OUString                sCalendar;
126 
127 public:
128                 SvXMLNumFmtElementContext( SvXMLImport& rImport, sal_Int32 nElement,
129                                     SvXMLNumFormatContext& rParentContext, SvXMLStyleTokens nNewType,
130                                     const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList );
131 
132     virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
133         sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& AttrList ) override;
134     virtual void SAL_CALL characters( const OUString& rChars ) override;
135     virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
136 
137     void    AddEmbeddedElement( sal_Int32 nFormatPos, const OUString& rContent );
138 };
139 
140 class SvXMLNumFmtEmbeddedTextContext : public SvXMLImportContext
141 {
142     SvXMLNumFmtElementContext&  rParent;
143     OUStringBuffer         aContent;
144     sal_Int32                   nTextPosition;
145 
146 public:
147                 SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport, sal_Int32 nElement,
148                                     SvXMLNumFmtElementContext& rParentContext,
149                                     const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList );
150 
151     virtual void SAL_CALL characters( const OUString& rChars ) override;
152     virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
153 };
154 
155 class SvXMLNumFmtMapContext : public SvXMLImportContext
156 {
157     SvXMLNumFormatContext&  rParent;
158     OUString           sCondition;
159     OUString           sName;
160 
161 public:
162                 SvXMLNumFmtMapContext( SvXMLImport& rImport, sal_Int32 nElement,
163                                     SvXMLNumFormatContext& rParentContext,
164                                     const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList );
165 
166     virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
167 };
168 
169 class SvXMLNumFmtPropContext : public SvXMLImportContext
170 {
171     SvXMLNumFormatContext&  rParent;
172     Color                   m_nColor;
173     bool                    bColSet;
174 
175 public:
176                 SvXMLNumFmtPropContext( SvXMLImport& rImport, sal_Int32 nElement,
177                                     SvXMLNumFormatContext& rParentContext,
178                                     const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList );
179 
180     virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
181 };
182 
183 enum class SvXMLStyleTokens
184 {
185     Text,
186     FillCharacter,
187     Number,
188     ScientificNumber,
189     Fraction,
190     CurrencySymbol,
191     Day,
192     Month,
193     Year,
194     Era,
195     DayOfWeek,
196     WeekOfYear,
197     Quarter,
198     Hours,
199     AmPm,
200     Minutes,
201     Seconds,
202     Boolean,
203     TextContent
204 };
205 
206 }
207 
208 //  standard colors
209 
210 
211 #define XML_NUMF_COLORCOUNT     10
212 
213 const Color aNumFmtStdColors[XML_NUMF_COLORCOUNT] =
214 {
215     COL_BLACK,
216     COL_LIGHTBLUE,
217     COL_LIGHTGREEN,
218     COL_LIGHTCYAN,
219     COL_LIGHTRED,
220     COL_LIGHTMAGENTA,
221     COL_BROWN,
222     COL_GRAY,
223     COL_YELLOW,
224     COL_WHITE
225 };
226 
227 
228 //  token maps
229 
230 
231 // maps for SvXMLUnitConverter::convertEnum
232 
233 const SvXMLEnumMapEntry<bool> aStyleValueMap[] =
234 {
235     { XML_SHORT,            false },
236     { XML_LONG,             true },
237     { XML_TOKEN_INVALID,    false }
238 };
239 
240 const SvXMLEnumMapEntry<bool> aFormatSourceMap[] =
241 {
242     { XML_FIXED,            false },
243     { XML_LANGUAGE,         true },
244     { XML_TOKEN_INVALID,    false }
245 };
246 
247 namespace {
248 
249 struct SvXMLDefaultDateFormat
250 {
251     NfIndexTableOffset          eFormat;
252     SvXMLDateElementAttributes  eDOW;
253     SvXMLDateElementAttributes  eDay;
254     SvXMLDateElementAttributes  eMonth;
255     SvXMLDateElementAttributes  eYear;
256     SvXMLDateElementAttributes  eHours;
257     SvXMLDateElementAttributes  eMins;
258     SvXMLDateElementAttributes  eSecs;
259     bool                        bSystem;
260 };
261 
262 }
263 
264 const SvXMLDefaultDateFormat aDefaultDateFormats[] =
265 {
266     // format                           day-of-week     day             month               year            hours           minutes         seconds         format-source
267 
268     { NF_DATE_SYSTEM_SHORT,             XML_DEA_NONE,   XML_DEA_ANY,    XML_DEA_ANY,        XML_DEA_ANY,    XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   true },
269     { NF_DATE_SYSTEM_LONG,              XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_ANY,        XML_DEA_ANY,    XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   true },
270     { NF_DATE_SYS_MMYY,                 XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_LONG,       XML_DEA_SHORT,  XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
271     { NF_DATE_SYS_DDMMM,                XML_DEA_NONE,   XML_DEA_LONG,   XML_DEA_TEXTSHORT,  XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
272     { NF_DATE_SYS_DDMMYYYY,             XML_DEA_NONE,   XML_DEA_LONG,   XML_DEA_LONG,       XML_DEA_LONG,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
273     { NF_DATE_SYS_DDMMYY,               XML_DEA_NONE,   XML_DEA_LONG,   XML_DEA_LONG,       XML_DEA_SHORT,  XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
274     { NF_DATE_SYS_DMMMYY,               XML_DEA_NONE,   XML_DEA_SHORT,  XML_DEA_TEXTSHORT,  XML_DEA_SHORT,  XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
275     { NF_DATE_SYS_DMMMYYYY,             XML_DEA_NONE,   XML_DEA_SHORT,  XML_DEA_TEXTSHORT,  XML_DEA_LONG,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
276     { NF_DATE_SYS_DMMMMYYYY,            XML_DEA_NONE,   XML_DEA_SHORT,  XML_DEA_TEXTLONG,   XML_DEA_LONG,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
277     { NF_DATE_SYS_NNDMMMYY,             XML_DEA_SHORT,  XML_DEA_SHORT,  XML_DEA_TEXTSHORT,  XML_DEA_SHORT,  XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
278     { NF_DATE_SYS_NNDMMMMYYYY,          XML_DEA_SHORT,  XML_DEA_SHORT,  XML_DEA_TEXTLONG,   XML_DEA_LONG,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
279     { NF_DATE_SYS_NNNNDMMMMYYYY,        XML_DEA_LONG,   XML_DEA_SHORT,  XML_DEA_TEXTLONG,   XML_DEA_LONG,   XML_DEA_NONE,   XML_DEA_NONE,   XML_DEA_NONE,   false },
280     { NF_DATETIME_SYS_DDMMYYYY_HHMM,    XML_DEA_NONE,   XML_DEA_ANY,    XML_DEA_ANY,        XML_DEA_LONG,   XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_NONE,   false },
281     { NF_DATETIME_SYSTEM_SHORT_HHMM,    XML_DEA_NONE,   XML_DEA_ANY,    XML_DEA_ANY,        XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_NONE,   true },
282     { NF_DATETIME_SYS_DDMMYYYY_HHMMSS,  XML_DEA_NONE,   XML_DEA_ANY,    XML_DEA_ANY,        XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_ANY,    XML_DEA_ANY,    false }
283 };
284 
285 
286 //  SvXMLNumImpData
287 
288 
289 SvXMLNumImpData::SvXMLNumImpData(
290     SvNumberFormatter* pFmt,
291     const uno::Reference<uno::XComponentContext>& rxContext )
292 :   pFormatter(pFmt),
293     m_xContext(rxContext)
294 {
295     SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" );
296 }
297 
298 sal_uInt32 SvXMLNumImpData::GetKeyForName( std::u16string_view rName )
299 {
300     for (const auto& rObj : m_NameEntries)
301     {
302         if (rObj.aName == rName)
303             return rObj.nKey;              // found
304     }
305     return NUMBERFORMAT_ENTRY_NOT_FOUND;
306 }
307 
308 void SvXMLNumImpData::AddKey( sal_uInt32 nKey, const OUString& rName, bool bRemoveAfterUse )
309 {
310     if ( bRemoveAfterUse )
311     {
312         //  if there is already an entry for this key without the bRemoveAfterUse flag,
313         //  clear the flag for this entry, too
314 
315         for (const auto& rObj : m_NameEntries)
316         {
317             if (rObj.nKey == nKey && !rObj.bRemoveAfterUse)
318             {
319                 bRemoveAfterUse = false;        // clear flag for new entry
320                 break;
321             }
322         }
323     }
324     else
325     {
326         //  call SetUsed to clear the bRemoveAfterUse flag for other entries for this key
327         SetUsed( nKey );
328     }
329 
330     m_NameEntries.emplace_back(rName, nKey, bRemoveAfterUse);
331 }
332 
333 void SvXMLNumImpData::SetUsed( sal_uInt32 nKey )
334 {
335     for (auto& rObj : m_NameEntries)
336     {
337         if (rObj.nKey == nKey)
338         {
339             rObj.bRemoveAfterUse = false;      // used -> don't remove
340 
341             //  continue searching - there may be several entries for the same key
342             //  (with different names), the format must not be deleted if any one of
343             //  them is used
344         }
345     }
346 }
347 
348 void SvXMLNumImpData::RemoveVolatileFormats()
349 {
350     //  remove temporary (volatile) formats from NumberFormatter
351     //  called at the end of each import (styles and content), so volatile formats
352     //  from styles can't be used in content
353 
354     if ( !pFormatter )
355         return;
356 
357     for (const auto& rObj : m_NameEntries)
358     {
359         if (rObj.bRemoveAfterUse )
360         {
361             const SvNumberformat* pFormat = pFormatter->GetEntry(rObj.nKey);
362             if (pFormat && (pFormat->GetType() & SvNumFormatType::DEFINED))
363                 pFormatter->DeleteEntry(rObj.nKey);
364         }
365     }
366 }
367 
368 const LocaleDataWrapper& SvXMLNumImpData::GetLocaleData( LanguageType nLang )
369 {
370     if ( !pLocaleData || pLocaleData->getLanguageTag() != LanguageTag(nLang) )
371         pLocaleData = std::make_unique<LocaleDataWrapper>(
372                pFormatter ? pFormatter->GetComponentContext() : m_xContext,
373             LanguageTag( nLang ) );
374     return *pLocaleData;
375 }
376 
377 
378 //  SvXMLNumFmtMapContext
379 
380 
381 SvXMLNumFmtMapContext::SvXMLNumFmtMapContext( SvXMLImport& rImport,
382                                     sal_Int32 /*nElement*/,
383                                     SvXMLNumFormatContext& rParentContext,
384                                     const uno::Reference<xml::sax::XFastAttributeList>& xAttrList ) :
385     SvXMLImportContext( rImport ),
386     rParent( rParentContext )
387 {
388     for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
389     {
390         OUString sValue = aIter.toString();
391         switch(aIter.getToken())
392         {
393             case XML_ELEMENT(STYLE, XML_CONDITION):
394                 sCondition = sValue;
395                 break;
396             case XML_ELEMENT(STYLE, XML_APPLY_STYLE_NAME):
397                 sName = sValue;
398                 break;
399             default:
400                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
401         }
402     }
403 }
404 
405 void SvXMLNumFmtMapContext::endFastElement(sal_Int32 )
406 {
407     rParent.AddCondition( sCondition, sName );
408 }
409 
410 
411 //  SvXMLNumFmtPropContext
412 
413 
414 SvXMLNumFmtPropContext::SvXMLNumFmtPropContext( SvXMLImport& rImport,
415                                     sal_Int32 /*nElement*/,
416                                     SvXMLNumFormatContext& rParentContext,
417                                     const uno::Reference<xml::sax::XFastAttributeList>& xAttrList ) :
418     SvXMLImportContext( rImport ),
419     rParent( rParentContext ),
420     m_nColor( 0 ),
421     bColSet( false )
422 {
423     for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
424     {
425         switch ( aIter.getToken())
426         {
427             case XML_ELEMENT(FO, XML_COLOR):
428             case XML_ELEMENT(FO_COMPAT, XML_COLOR):
429                 bColSet = ::sax::Converter::convertColor( m_nColor, aIter.toView() );
430                 break;
431             default:
432                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
433         }
434     }
435 }
436 
437 void SvXMLNumFmtPropContext::endFastElement(sal_Int32 )
438 {
439     if (bColSet)
440         rParent.AddColor( m_nColor );
441 }
442 
443 
444 //  SvXMLNumFmtEmbeddedTextContext
445 
446 
447 SvXMLNumFmtEmbeddedTextContext::SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport,
448                                     sal_Int32 /*nElement*/,
449                                     SvXMLNumFmtElementContext& rParentContext,
450                                     const uno::Reference<xml::sax::XFastAttributeList>& xAttrList ) :
451     SvXMLImportContext( rImport ),
452     rParent( rParentContext ),
453     nTextPosition( 0 )
454 {
455     sal_Int32 nAttrVal;
456 
457     for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
458     {
459         if ( aIter.getToken() == XML_ELEMENT(NUMBER, XML_POSITION) )
460         {
461             if (::sax::Converter::convertNumber( nAttrVal, aIter.toView() ))
462                 nTextPosition = nAttrVal;
463         }
464         else
465             XMLOFF_WARN_UNKNOWN("xmloff", aIter);
466     }
467 }
468 
469 void SvXMLNumFmtEmbeddedTextContext::characters( const OUString& rChars )
470 {
471     aContent.append( rChars );
472 }
473 
474 void SvXMLNumFmtEmbeddedTextContext::endFastElement(sal_Int32 )
475 {
476     rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear() );
477 }
478 
479 static bool lcl_ValidChar( sal_Unicode cChar, const SvXMLNumFormatContext& rParent )
480 {
481     SvXMLStylesTokens nFormatType = rParent.GetType();
482 
483     // Treat space equal to non-breaking space separator.
484     const sal_Unicode cNBSP = 0x00A0;
485     sal_Unicode cTS;
486     if ( ( nFormatType == SvXMLStylesTokens::NUMBER_STYLE ||
487            nFormatType == SvXMLStylesTokens::CURRENCY_STYLE ||
488            nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE ) &&
489             (cChar == (cTS = rParent.GetLocaleData().getNumThousandSep()[0]) ||
490              (cChar == ' ' && cTS == cNBSP)) )
491     {
492         //  #i22394# Extra occurrences of thousands separator must be quoted, so they
493         //  aren't mis-interpreted as display-factor.
494         //  This must be limited to the format types that can contain a number element,
495         //  because the same character can be a date separator that should not be quoted
496         //  in date formats.
497 
498         return false;   // force quotes
499     }
500 
501     //  see ImpSvNumberformatScan::Next_Symbol
502 
503     // All format types except BOOLEAN may contain minus sign or delimiter.
504     if ( cChar == '-' )
505         return nFormatType != SvXMLStylesTokens::BOOLEAN_STYLE;
506 
507     if ( ( cChar == ' ' ||
508            cChar == '/' ||
509            cChar == '.' ||
510            cChar == ',' ||
511            cChar == ':' ||
512            cChar == '\''   ) &&
513          ( nFormatType == SvXMLStylesTokens::CURRENCY_STYLE ||
514            nFormatType == SvXMLStylesTokens::DATE_STYLE ||
515            nFormatType == SvXMLStylesTokens::TIME_STYLE ) ) // other formats do not require delimiter tdf#97837
516         return true;
517 
518     //  percent sign must be used without quotes for percentage styles only
519     if ( nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE && cChar == '%' )
520         return true;
521 
522     //  don't put quotes around single parentheses (often used for negative numbers)
523     if ( ( nFormatType == SvXMLStylesTokens::NUMBER_STYLE ||
524            nFormatType == SvXMLStylesTokens::CURRENCY_STYLE ||
525            nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE ) &&
526          ( cChar == '(' || cChar == ')' ) )
527         return true;
528 
529     return false;
530 }
531 
532 static void lcl_EnquoteIfNecessary( OUStringBuffer& rContent, const SvXMLNumFormatContext& rParent )
533 {
534     bool bQuote = true;
535     sal_Int32 nLength = rContent.getLength();
536     const SvXMLStylesTokens nFormatType = rParent.GetType();
537 
538     if (nFormatType != SvXMLStylesTokens::BOOLEAN_STYLE &&
539             ((nLength == 1 && lcl_ValidChar( rContent[0], rParent)) ||
540              (nLength == 2 &&
541               ((rContent[0] == ' ' && rContent[1] == '-') ||
542                (rContent[1] == ' ' && lcl_ValidChar( rContent[0], rParent))))))
543     {
544         //  Don't quote single separator characters like space or percent,
545         //  or separator characters followed by space (used in date formats).
546         //  Or space followed by minus (used in currency formats) that would
547         //  lead to almost duplicated formats with built-in formats just with
548         //  the difference of quotes.
549         bQuote = false;
550     }
551     else if ( nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE && nLength > 1 )
552     {
553         //  the percent character in percentage styles must be left out of quoting
554         //  (one occurrence is enough even if there are several percent characters in the string)
555 
556         sal_Int32 nPos = rContent.indexOf( '%' );
557         if ( nPos >= 0 )
558         {
559             if ( nPos + 1 < nLength )
560             {
561                 if ( nPos + 2 == nLength && lcl_ValidChar( rContent[nPos + 1], rParent ) )
562                 {
563                     //  single character that doesn't need quoting
564                 }
565                 else
566                 {
567                     //  quote text behind percent character
568                     rContent.insert( nPos + 1, '"' );
569                     rContent.append( '"' );
570                 }
571             }
572             if ( nPos > 0 )
573             {
574                 if ( nPos == 1 && lcl_ValidChar( rContent[0], rParent ) )
575                 {
576                     //  single character that doesn't need quoting
577                 }
578                 else
579                 {
580                     //  quote text before percent character
581                     rContent.insert( nPos, '"' );
582                     rContent.insert( 0, '"' );
583                 }
584             }
585             bQuote = false;
586         }
587         // else: normal quoting (below)
588     }
589 
590     if ( !bQuote )
591         return;
592 
593     // #i55469# quotes in the string itself have to be escaped
594     bool bEscape = ( rContent.indexOf( '"' ) >= 0 );
595     if ( bEscape )
596     {
597         // A quote is turned into "\"" - a quote to end quoted text, an escaped quote,
598         // and a quote to resume quoting.
599         OUString aInsert(  "\"\\\""  );
600 
601         sal_Int32 nPos = 0;
602         while ( nPos < rContent.getLength() )
603         {
604             if ( rContent[nPos] == '"' )
605             {
606                 rContent.insert( nPos, aInsert );
607                 nPos += aInsert.getLength();
608             }
609             ++nPos;
610         }
611     }
612 
613     //  quote string literals
614     rContent.insert( 0, '"' );
615     rContent.append( '"' );
616 
617     // remove redundant double quotes at start or end
618     if ( !bEscape )
619         return;
620 
621     if ( rContent.getLength() > 2 &&
622          rContent[0] == '"' &&
623          rContent[1] == '"' )
624     {
625         rContent.remove(0, 2);
626     }
627 
628     sal_Int32 nLen = rContent.getLength();
629     if ( nLen > 2 &&
630          rContent[nLen - 1] == '"' &&
631          rContent[nLen - 2] == '"' )
632     {
633         rContent.truncate(nLen - 2);
634     }
635 }
636 
637 
638 //  SvXMLNumFmtElementContext
639 
640 
641 SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport,
642                                     sal_Int32 /*nElement*/,
643                                     SvXMLNumFormatContext& rParentContext, SvXMLStyleTokens nNewType,
644                                     const uno::Reference<xml::sax::XFastAttributeList>& xAttrList ) :
645     SvXMLImportContext( rImport ),
646     rParent( rParentContext ),
647     nType( nNewType ),
648     nElementLang( LANGUAGE_SYSTEM ),
649     bLong( false ),
650     bTextual( false )
651 {
652     LanguageTagODF aLanguageTagODF;
653     sal_Int32 nAttrVal;
654     bool bAttrBool(false);
655     bool bVarDecimals = false;
656     bool bIsMaxDenominator = false;
657     double fAttrDouble;
658 
659     for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
660     {
661         switch (aIter.getToken())
662         {
663             case XML_ELEMENT(NUMBER, XML_DECIMAL_PLACES):
664                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
665                 {
666                     // fdo#58539 & gnome#627420: limit number of digits during import
667                     aNumInfo.nDecimals = std::min<sal_Int32>(nAttrVal, NF_MAX_FORMAT_SYMBOLS);
668                 }
669                 break;
670             case XML_ELEMENT(LO_EXT, XML_MIN_DECIMAL_PLACES):
671             case XML_ELEMENT(NUMBER, XML_MIN_DECIMAL_PLACES):
672                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
673                     aNumInfo.nMinDecimalDigits = nAttrVal;
674                 break;
675             case XML_ELEMENT(NUMBER, XML_MIN_INTEGER_DIGITS):
676                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
677                     aNumInfo.nInteger = nAttrVal;
678                 break;
679             case XML_ELEMENT(LO_EXT, XML_MAX_BLANK_INTEGER_DIGITS):
680             case XML_ELEMENT(NUMBER, XML_MAX_BLANK_INTEGER_DIGITS):
681                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
682                     aNumInfo.nBlankInteger = nAttrVal;
683                 break;
684             case XML_ELEMENT(NUMBER, XML_GROUPING):
685                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
686                     aNumInfo.bGrouping = bAttrBool;
687                 break;
688             case XML_ELEMENT(NUMBER, XML_DISPLAY_FACTOR):
689                 if (::sax::Converter::convertDouble( fAttrDouble, aIter.toView() ))
690                     aNumInfo.fDisplayFactor = fAttrDouble;
691                 break;
692             case XML_ELEMENT(NUMBER, XML_DECIMAL_REPLACEMENT):
693                 if ( aIter.toView() == " " )
694                 {
695                     aNumInfo.bDecAlign = true; // space replacement for "?"
696                     bVarDecimals = true;
697                 }
698                 else
699                     if ( aIter.isEmpty() )
700                         bVarDecimals = true;   // empty replacement string: variable decimals
701                     else                                // all other strings
702                         aNumInfo.bDecReplace = true;    // decimal replacement with dashes
703                 break;
704             case XML_ELEMENT(NUMBER, XML_MIN_EXPONENT_DIGITS):
705                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
706                     aNumInfo.nExpDigits = std::min<sal_Int32>(nAttrVal, NF_MAX_FORMAT_SYMBOLS);
707                 break;
708             case XML_ELEMENT(NUMBER, XML_EXPONENT_INTERVAL):
709             case XML_ELEMENT(LO_EXT, XML_EXPONENT_INTERVAL):
710                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
711                     aNumInfo.nExpInterval = nAttrVal;
712                 break;
713             case XML_ELEMENT(NUMBER, XML_FORCED_EXPONENT_SIGN):
714             case XML_ELEMENT(LO_EXT, XML_FORCED_EXPONENT_SIGN):
715                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
716                     aNumInfo.bExpSign = bAttrBool;
717                 break;
718             case XML_ELEMENT(NUMBER, XML_MIN_NUMERATOR_DIGITS):
719                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
720                     aNumInfo.nMinNumerDigits = nAttrVal;
721                 break;
722             case XML_ELEMENT(NUMBER, XML_MIN_DENOMINATOR_DIGITS):
723                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
724                     aNumInfo.nMinDenomDigits = nAttrVal;
725                 break;
726             case XML_ELEMENT(LO_EXT, XML_MAX_NUMERATOR_DIGITS):
727                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 ))  // at least one '#'
728                     aNumInfo.nMaxNumerDigits = nAttrVal;
729                 break;
730             case XML_ELEMENT(NUMBER, XML_DENOMINATOR_VALUE):
731                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 )) // 0 is not valid
732                 {
733                     aNumInfo.nFracDenominator = nAttrVal;
734                     bIsMaxDenominator = false;
735                 }
736                 break;
737             case XML_ELEMENT(NUMBER, XML_MAX_DENOMINATOR_VALUE):  // part of ODF 1.3
738             case XML_ELEMENT(LO_EXT, XML_MAX_DENOMINATOR_VALUE):
739                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 ) && aNumInfo.nFracDenominator <= 0)
740                 {   // if denominator value not yet defined
741                     aNumInfo.nFracDenominator = nAttrVal;
742                     bIsMaxDenominator = true;
743                 }
744                 break;
745             case XML_ELEMENT(LO_EXT, XML_ZEROS_NUMERATOR_DIGITS):
746             case XML_ELEMENT(NUMBER, XML_ZEROS_NUMERATOR_DIGITS):
747                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
748                     aNumInfo.nZerosNumerDigits = nAttrVal;
749                 break;
750             case XML_ELEMENT(NUMBER, XML_ZEROS_DENOMINATOR_DIGITS):
751             case XML_ELEMENT(LO_EXT, XML_ZEROS_DENOMINATOR_DIGITS):
752                 if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
753                     aNumInfo.nZerosDenomDigits = nAttrVal;
754                  break;
755             case XML_ELEMENT(NUMBER, XML_INTEGER_FRACTION_DELIMITER):
756             case XML_ELEMENT(LO_EXT, XML_INTEGER_FRACTION_DELIMITER):
757                 aNumInfo.aIntegerFractionDelimiter = aIter.toString();
758                 break;
759             case XML_ELEMENT(NUMBER, XML_RFC_LANGUAGE_TAG):
760                 aLanguageTagODF.maRfcLanguageTag = aIter.toString();
761                 break;
762             case XML_ELEMENT(NUMBER, XML_LANGUAGE):
763                 aLanguageTagODF.maLanguage = aIter.toString();
764                 break;
765             case XML_ELEMENT(NUMBER, XML_SCRIPT):
766                 aLanguageTagODF.maScript = aIter.toString();
767                 break;
768             case XML_ELEMENT(NUMBER, XML_COUNTRY):
769                 aLanguageTagODF.maCountry = aIter.toString();
770                 break;
771             case XML_ELEMENT(NUMBER, XML_STYLE):
772                 SvXMLUnitConverter::convertEnum( bLong, aIter.toView(), aStyleValueMap );
773                 break;
774             case XML_ELEMENT(NUMBER, XML_TEXTUAL):
775                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
776                     bTextual = bAttrBool;
777                 break;
778             case XML_ELEMENT(NUMBER, XML_CALENDAR):
779                 sCalendar = aIter.toString();
780                 break;
781             default:
782                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
783         }
784     }
785     if ( aNumInfo.nBlankInteger > aNumInfo.nInteger )
786         aNumInfo.nInteger = aNumInfo.nBlankInteger;
787     if ( aNumInfo.nMinDecimalDigits == -1)
788     {
789         if ( bVarDecimals || aNumInfo.bDecReplace )
790             aNumInfo.nMinDecimalDigits = 0;
791         else
792             aNumInfo.nMinDecimalDigits = aNumInfo.nDecimals;
793     }
794     if ( aNumInfo.nZerosDenomDigits > 0 )
795     {   // nMin = count of '0' and '?'
796         if ( aNumInfo.nMinDenomDigits < aNumInfo.nZerosDenomDigits )
797             aNumInfo.nMinDenomDigits = aNumInfo.nZerosDenomDigits;
798     }
799     else
800         aNumInfo.nZerosDenomDigits = 0;
801     if ( aNumInfo.nMinDenomDigits >= 0 )
802         if ( aNumInfo.nMaxDenomDigits < aNumInfo.nMinDenomDigits )
803             aNumInfo.nMaxDenomDigits = ( aNumInfo.nMinDenomDigits ? aNumInfo.nMinDenomDigits : 1 );
804     if ( aNumInfo.nZerosNumerDigits > 0 )
805     {
806         if ( aNumInfo.nMinNumerDigits < aNumInfo.nZerosNumerDigits )
807             aNumInfo.nMinNumerDigits = aNumInfo.nZerosNumerDigits;
808     }
809     else
810         aNumInfo.nZerosNumerDigits = 0;
811     if ( aNumInfo.nMinNumerDigits >= 0 )
812         if ( aNumInfo.nMaxNumerDigits < aNumInfo.nMinNumerDigits )
813             aNumInfo.nMaxNumerDigits = ( aNumInfo.nMinNumerDigits ? aNumInfo.nMinNumerDigits : 1 );
814     if ( bIsMaxDenominator && aNumInfo.nFracDenominator > 0 )
815     {
816         aNumInfo.nMaxDenomDigits = floor( log10( aNumInfo.nFracDenominator ) ) + 1;
817         aNumInfo.nFracDenominator = -1;  // Max denominator value only gives number of digits at denominator
818     }
819     if ( aNumInfo.nMaxDenomDigits > 0 )
820     {
821         if ( aNumInfo.nMinDenomDigits < 0 )
822             aNumInfo.nMinDenomDigits = 0;
823         else if ( aNumInfo.nMinDenomDigits > aNumInfo.nMaxDenomDigits )
824             aNumInfo.nMinDenomDigits = aNumInfo.nMaxDenomDigits;
825     }
826 
827     if ( !aLanguageTagODF.isEmpty() )
828     {
829         nElementLang = aLanguageTagODF.getLanguageTag().getLanguageType( false);
830         if ( nElementLang == LANGUAGE_DONTKNOW )
831             nElementLang = LANGUAGE_SYSTEM;         //! error handling for unknown locales?
832     }
833 
834     if ( aNumInfo.aIntegerFractionDelimiter.isEmpty() )
835         aNumInfo.aIntegerFractionDelimiter = " ";
836 }
837 
838 css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLNumFmtElementContext::createFastChildContext(
839     sal_Int32 nElement,
840     const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
841 {
842     //  only number:number supports number:embedded-text child element
843 
844     if ( nType == SvXMLStyleTokens::Number &&
845          nElement == XML_ELEMENT(NUMBER, XML_EMBEDDED_TEXT) )
846     {
847         return new SvXMLNumFmtEmbeddedTextContext( GetImport(), nElement, *this, xAttrList );
848     }
849     else
850         XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
851     return nullptr;
852 }
853 
854 void SvXMLNumFmtElementContext::characters( const OUString& rChars )
855 {
856     aContent.append( rChars );
857 }
858 
859 void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, const OUString& rContent )
860 {
861     if (rContent.isEmpty())
862         return;
863 
864     auto iterPair = aNumInfo.m_EmbeddedElements.emplace(nFormatPos, rContent);
865     if (!iterPair.second)
866         // there's already an element at this position - append text to existing element
867         iterPair.first->second += rContent;
868 }
869 
870 void SvXMLNumFmtElementContext::endFastElement(sal_Int32 )
871 {
872     bool bEffLong = bLong;
873     switch (nType)
874     {
875         case SvXMLStyleTokens::Text:
876             if ( rParent.HasLongDoW() &&
877                  std::u16string_view(aContent) == rParent.GetLocaleData().getLongDateDayOfWeekSep() )
878             {
879                 //  skip separator constant after long day of week
880                 //  (NF_KEY_NNNN contains the separator)
881 
882                 if ( rParent.ReplaceNfKeyword( NF_KEY_NNN, NF_KEY_NNNN ) )
883                 {
884                     aContent.truncate();
885                 }
886 
887                 rParent.SetHasLongDoW( false );     // only once
888             }
889             if ( !aContent.isEmpty() )
890             {
891                 lcl_EnquoteIfNecessary( aContent, rParent );
892                 rParent.AddToCode( aContent );
893                 aContent.setLength(0);
894             }
895             else
896             {
897                 // Quoted empty text may be significant to separate.
898                 aContent.append("\"\"");
899                 rParent.AddToCode( aContent );
900                 aContent.setLength(0);
901                 rParent.SetHasTrailingEmptyText(true);  // *after* AddToCode()
902             }
903             break;
904 
905         case SvXMLStyleTokens::Number:
906             rParent.AddNumber( aNumInfo );
907             break;
908 
909         case SvXMLStyleTokens::CurrencySymbol:
910             rParent.AddCurrency( aContent.makeStringAndClear(), nElementLang );
911             break;
912 
913         case SvXMLStyleTokens::TextContent:
914             rParent.AddToCode( '@');
915             break;
916         case SvXMLStyleTokens::FillCharacter:
917             if ( !aContent.isEmpty() )
918             {
919                 rParent.AddToCode( '*' );
920                 rParent.AddToCode( aContent[0] );
921             }
922             break;
923         case SvXMLStyleTokens::Boolean:
924             rParent.AddNfKeyword( NF_KEY_BOOLEAN );
925             break;
926 
927         case SvXMLStyleTokens::Day:
928             rParent.UpdateCalendar( sCalendar );
929 //! I18N doesn't provide SYSTEM or extended date information yet
930 
931             rParent.AddNfKeyword(
932                 sal::static_int_cast< sal_uInt16 >(
933                     bEffLong ? NF_KEY_DD : NF_KEY_D ) );
934             break;
935         case SvXMLStyleTokens::Month:
936             rParent.UpdateCalendar( sCalendar );
937 //! I18N doesn't provide SYSTEM or extended date information yet
938 
939             rParent.AddNfKeyword(
940                 sal::static_int_cast< sal_uInt16 >(
941                     bTextual
942                     ? ( bEffLong ? NF_KEY_MMMM : NF_KEY_MMM )
943                     : ( bEffLong ? NF_KEY_MM : NF_KEY_M ) ) );
944             break;
945         case SvXMLStyleTokens::Year:
946 //! I18N doesn't provide SYSTEM or extended date information yet
947             {
948                 // Y after G (era) is replaced by E for a secondary calendar.
949                 // Do not replace for default calendar.
950                 // Also replace Y by E if we're switching to the secondary
951                 // calendar of a locale if it is known to implicitly use E.
952                 rParent.UpdateCalendar( sCalendar);
953                 const SvXMLNumFormatContext::ImplicitCalendar eCal = rParent.GetImplicitCalendarState();
954                 if (eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY
955                         || eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY_FROM_OTHER)
956                 {
957                     rParent.AddNfKeyword(
958                             sal::static_int_cast< sal_uInt16 >(
959                                 bEffLong ? NF_KEY_EEC : NF_KEY_EC ) );
960                 }
961                 else
962                 {
963                     rParent.AddNfKeyword(
964                             sal::static_int_cast< sal_uInt16 >(
965                                 bEffLong ? NF_KEY_YYYY : NF_KEY_YY ) );
966                 }
967             }
968             break;
969         case SvXMLStyleTokens::Era:
970             rParent.UpdateCalendar( sCalendar );
971 //! I18N doesn't provide SYSTEM or extended date information yet
972             rParent.AddNfKeyword(
973                 sal::static_int_cast< sal_uInt16 >(
974                     bEffLong ? NF_KEY_GGG : NF_KEY_G ) );
975             //  HasEra flag is set
976             break;
977         case SvXMLStyleTokens::DayOfWeek:
978 //! I18N doesn't provide SYSTEM or extended date information yet
979             {
980                 // Implicit secondary calendar uses A keyword, default and
981                 // explicit calendar N keyword.
982                 rParent.UpdateCalendar( sCalendar);
983                 const SvXMLNumFormatContext::ImplicitCalendar eCal = rParent.GetImplicitCalendarState();
984                 if (eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY
985                         || eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY_FROM_OTHER)
986                 {
987                     rParent.AddNfKeyword(
988                             sal::static_int_cast< sal_uInt16 >(
989                                 bEffLong ? NF_KEY_AAAA : NF_KEY_AAA ) );
990                 }
991                 else
992                 {
993                     rParent.AddNfKeyword(
994                             sal::static_int_cast< sal_uInt16 >(
995                                 bEffLong ? NF_KEY_NNNN : NF_KEY_NN ) );
996                 }
997             }
998             break;
999         case SvXMLStyleTokens::WeekOfYear:
1000             rParent.UpdateCalendar( sCalendar );
1001             rParent.AddNfKeyword( NF_KEY_WW );
1002             break;
1003         case SvXMLStyleTokens::Quarter:
1004             rParent.UpdateCalendar( sCalendar );
1005             rParent.AddNfKeyword(
1006                 sal::static_int_cast< sal_uInt16 >(
1007                     bEffLong ? NF_KEY_QQ : NF_KEY_Q ) );
1008             break;
1009         case SvXMLStyleTokens::Hours:
1010             rParent.AddNfKeyword(
1011                 sal::static_int_cast< sal_uInt16 >(
1012                     bEffLong ? NF_KEY_HH : NF_KEY_H ) );
1013             break;
1014         case SvXMLStyleTokens::AmPm:
1015             //! short/long?
1016             rParent.AddNfKeyword( NF_KEY_AMPM );
1017             break;
1018         case SvXMLStyleTokens::Minutes:
1019             rParent.AddNfKeyword(
1020                 sal::static_int_cast< sal_uInt16 >(
1021                     bEffLong ? NF_KEY_MMI : NF_KEY_MI ) );
1022             break;
1023         case SvXMLStyleTokens::Seconds:
1024             rParent.AddNfKeyword(
1025                 sal::static_int_cast< sal_uInt16 >(
1026                     bEffLong ? NF_KEY_SS : NF_KEY_S ) );
1027             if ( aNumInfo.nDecimals > 0 )
1028             {
1029                 //  manually add the decimal places
1030                 rParent.AddToCode(rParent.GetLocaleData().getNumDecimalSep());
1031                 for (sal_Int32 i=0; i<aNumInfo.nDecimals; i++)
1032                 {
1033                     rParent.AddToCode( '0');
1034                 }
1035             }
1036             break;
1037 
1038         case SvXMLStyleTokens::Fraction:
1039             {
1040                 if ( aNumInfo.nInteger >= 0 )
1041                 {
1042                     // add integer part only if min-integer-digits attribute is there
1043                     aNumInfo.nDecimals = 0;
1044                     rParent.AddNumber( aNumInfo );      // number without decimals
1045                     OUStringBuffer sIntegerFractionDelimiter(aNumInfo.aIntegerFractionDelimiter);
1046                     lcl_EnquoteIfNecessary( sIntegerFractionDelimiter, rParent );
1047                     rParent.AddToCode( sIntegerFractionDelimiter ); // default is ' '
1048                 }
1049 
1050                 //! build string and add at once
1051 
1052                 sal_Int32 i;
1053                 for (i=aNumInfo.nMaxNumerDigits; i > 0; i--)
1054                 {
1055                     if ( i > aNumInfo.nMinNumerDigits )
1056                         rParent.AddToCode( '#' );
1057                     else if ( i > aNumInfo.nZerosNumerDigits )
1058                         rParent.AddToCode( '?' );
1059                     else
1060                         rParent.AddToCode( '0' );
1061                 }
1062                 rParent.AddToCode( '/' );
1063                 if ( aNumInfo.nFracDenominator > 0 )
1064                 {
1065                     rParent.AddToCode(  OUString::number( aNumInfo.nFracDenominator ) );
1066                 }
1067                 else
1068                 {
1069                     for (i=aNumInfo.nMaxDenomDigits; i > 0 ; i--)
1070                     {
1071                         if ( i > aNumInfo.nMinDenomDigits )
1072                             rParent.AddToCode( '#' );
1073                         else if ( i > aNumInfo.nZerosDenomDigits )
1074                             rParent.AddToCode( '?' );
1075                         else
1076                             rParent.AddToCode( '0' );
1077                     }
1078                 }
1079             }
1080             break;
1081 
1082         case SvXMLStyleTokens::ScientificNumber:
1083             {
1084                 // exponential interval for engineering notation
1085                 if( !aNumInfo.bGrouping && aNumInfo.nExpInterval > aNumInfo.nInteger )
1086                 {
1087                     for (sal_Int32 i=aNumInfo.nInteger; i<aNumInfo.nExpInterval; i++)
1088                     {
1089                         rParent.AddToCode( '#' );
1090                     }
1091                 }
1092                 rParent.AddNumber( aNumInfo );      // simple number
1093 
1094                 if ( aNumInfo.bExpSign )
1095                     rParent.AddToCode( u"E+" );
1096                 else
1097                     rParent.AddToCode( u"E" );
1098                 for (sal_Int32 i=0; i<aNumInfo.nExpDigits; i++)
1099                 {
1100                     rParent.AddToCode( '0' );
1101                 }
1102             }
1103             break;
1104 
1105         default:
1106             assert(false && "invalid element ID");
1107     }
1108 }
1109 
1110 sal_uInt16 SvXMLNumFmtDefaults::GetDefaultDateFormat( SvXMLDateElementAttributes eDOW,
1111                 SvXMLDateElementAttributes eDay, SvXMLDateElementAttributes eMonth,
1112                 SvXMLDateElementAttributes eYear, SvXMLDateElementAttributes eHours,
1113                 SvXMLDateElementAttributes eMins, SvXMLDateElementAttributes eSecs,
1114                 bool bSystem )
1115 {
1116     for (const auto & rEntry : aDefaultDateFormats)
1117     {
1118         if ( bSystem == rEntry.bSystem &&
1119             ( eDOW   == rEntry.eDOW   || ( rEntry.eDOW   == XML_DEA_ANY && eDOW   != XML_DEA_NONE ) ) &&
1120             ( eDay   == rEntry.eDay   || ( rEntry.eDay   == XML_DEA_ANY && eDay   != XML_DEA_NONE ) ) &&
1121             ( eMonth == rEntry.eMonth || ( rEntry.eMonth == XML_DEA_ANY && eMonth != XML_DEA_NONE ) ) &&
1122             ( eYear  == rEntry.eYear  || ( rEntry.eYear  == XML_DEA_ANY && eYear  != XML_DEA_NONE ) ) &&
1123             ( eHours == rEntry.eHours || ( rEntry.eHours == XML_DEA_ANY && eHours != XML_DEA_NONE ) ) &&
1124             ( eMins  == rEntry.eMins  || ( rEntry.eMins  == XML_DEA_ANY && eMins  != XML_DEA_NONE ) ) &&
1125             ( eSecs  == rEntry.eSecs  || ( rEntry.eSecs  == XML_DEA_ANY && eSecs  != XML_DEA_NONE ) ) )
1126         {
1127             return sal::static_int_cast< sal_uInt16 >(rEntry.eFormat);
1128         }
1129     }
1130 
1131     return NF_INDEX_TABLE_ENTRIES;  // invalid
1132 }
1133 
1134 
1135 //  SvXMLNumFormatContext
1136 
1137 SvXMLNumFormatContext::SvXMLNumFormatContext( SvXMLImport& rImport,
1138                                     sal_Int32 /*nElement*/,
1139                                     SvXMLNumImpData* pNewData, SvXMLStylesTokens nNewType,
1140                                     const uno::Reference<xml::sax::XFastAttributeList>& xAttrList,
1141                                     SvXMLStylesContext& rStyles ) :
1142     SvXMLStyleContext( rImport ),
1143     pData( pNewData ),
1144     pStyles( &rStyles ),
1145     nType( nNewType ),
1146     nKey(-1),
1147     eImplicitCalendar(ImplicitCalendar::DEFAULT),
1148     nFormatLang( LANGUAGE_SYSTEM ),
1149     bAutoOrder( false ),
1150     bFromSystem( false ),
1151     bTruncate( true ),
1152     bAutoDec( false ),
1153     bAutoInt( false ),
1154     bHasExtraText( false ),
1155     bHasTrailingEmptyText( false ),
1156     bHasLongDoW( false ),
1157     bHasDateTime( false ),
1158     bRemoveAfterUse( false ),
1159     eDateDOW( XML_DEA_NONE ),
1160     eDateDay( XML_DEA_NONE ),
1161     eDateMonth( XML_DEA_NONE ),
1162     eDateYear( XML_DEA_NONE ),
1163     eDateHours( XML_DEA_NONE ),
1164     eDateMins( XML_DEA_NONE ),
1165     eDateSecs( XML_DEA_NONE ),
1166     bDateNoDefault( false )
1167 {
1168     LanguageTagODF aLanguageTagODF;
1169     css::i18n::NativeNumberXmlAttributes aNatNumAttr;
1170     OUString aSpellout;
1171     bool bAttrBool(false);
1172 
1173     for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
1174     {
1175         switch (aIter.getToken())
1176         {
1177         //  attributes for a style
1178             case XML_ELEMENT(STYLE, XML_NAME):
1179                 break;
1180             case XML_ELEMENT(NUMBER, XML_RFC_LANGUAGE_TAG):
1181                 aLanguageTagODF.maRfcLanguageTag = aIter.toString();
1182                 break;
1183             case XML_ELEMENT(NUMBER, XML_LANGUAGE):
1184                 aLanguageTagODF.maLanguage = aIter.toString();
1185                 break;
1186             case XML_ELEMENT(NUMBER, XML_SCRIPT):
1187                 aLanguageTagODF.maScript = aIter.toString();
1188                 break;
1189             case XML_ELEMENT(NUMBER, XML_COUNTRY):
1190                 aLanguageTagODF.maCountry = aIter.toString();
1191                 break;
1192             case XML_ELEMENT(NUMBER, XML_TITLE):
1193                 sFormatTitle = aIter.toString();
1194                 break;
1195             case XML_ELEMENT(NUMBER, XML_AUTOMATIC_ORDER):
1196                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
1197                     bAutoOrder = bAttrBool;
1198                 break;
1199             case XML_ELEMENT(NUMBER, XML_FORMAT_SOURCE):
1200                 SvXMLUnitConverter::convertEnum( bFromSystem, aIter.toView(), aFormatSourceMap );
1201                 break;
1202             case XML_ELEMENT(NUMBER, XML_TRUNCATE_ON_OVERFLOW):
1203                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
1204                     bTruncate = bAttrBool;
1205                 break;
1206             case XML_ELEMENT(STYLE, XML_VOLATILE):
1207                 //  volatile formats can be removed after importing
1208                 //  if not used in other styles
1209                 if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
1210                     bRemoveAfterUse = bAttrBool;
1211                 break;
1212             case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_FORMAT):
1213                 aNatNumAttr.Format = aIter.toString();
1214                 break;
1215             case XML_ELEMENT(LO_EXT, XML_TRANSLITERATION_SPELLOUT):
1216             case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_SPELLOUT):
1217                 aSpellout = aIter.toString();
1218                 break;
1219             case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_LANGUAGE):
1220                 aNatNumAttr.Locale.Language = aIter.toString();
1221                 break;
1222             case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_COUNTRY):
1223                 aNatNumAttr.Locale.Country = aIter.toString();
1224                 break;
1225             case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_STYLE):
1226                 aNatNumAttr.Style = aIter.toString();
1227                 break;
1228             default:
1229                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
1230         }
1231     }
1232 
1233     if (!aLanguageTagODF.isEmpty())
1234     {
1235         nFormatLang = aLanguageTagODF.getLanguageTag().getLanguageType( false);
1236         if ( nFormatLang == LANGUAGE_DONTKNOW )
1237             nFormatLang = LANGUAGE_SYSTEM;          //! error handling for unknown locales?
1238     }
1239 
1240     if (aNatNumAttr.Format.isEmpty() && aSpellout.isEmpty())
1241         return;
1242 
1243     LanguageTag aLanguageTag( OUString(), aNatNumAttr.Locale.Language,
1244                 std::u16string_view(), aNatNumAttr.Locale.Country);
1245     aNatNumAttr.Locale = aLanguageTag.getLocale( false);
1246 
1247     // NatNum12 spell out formula (cardinal, ordinal, ordinal-feminine etc.)
1248     if ( !aSpellout.isEmpty() )
1249     {
1250         aFormatCode.append( "[NatNum12 " );
1251         aFormatCode.append( aSpellout );
1252     } else {
1253         SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
1254         if ( !pFormatter ) return;
1255 
1256         sal_Int32 nNatNum = pFormatter->GetNatNum()->convertFromXmlAttributes( aNatNumAttr );
1257         aFormatCode.append( "[NatNum" );
1258         aFormatCode.append( nNatNum );
1259     }
1260 
1261     LanguageType eLang = aLanguageTag.getLanguageType( false );
1262     if ( eLang == LANGUAGE_DONTKNOW )
1263         eLang = LANGUAGE_SYSTEM;            //! error handling for unknown locales?
1264     if ( eLang != nFormatLang && eLang != LANGUAGE_SYSTEM )
1265     {
1266         aFormatCode.append( "][$-" );
1267         // language code in upper hex:
1268         aFormatCode.append(OUString::number(static_cast<sal_uInt16>(eLang), 16).toAsciiUpperCase());
1269     }
1270     aFormatCode.append( ']' );
1271 }
1272 
1273 SvXMLNumFormatContext::SvXMLNumFormatContext( SvXMLImport& rImport,
1274                                     const OUString& rName,
1275                                     const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/,
1276                                     const sal_Int32 nTempKey, LanguageType nLang,
1277                                     SvXMLStylesContext& rStyles ) :
1278     SvXMLStyleContext( rImport, XmlStyleFamily::DATA_STYLE ),
1279     pData( nullptr ),
1280     pStyles( &rStyles ),
1281     nType( SvXMLStylesTokens::NUMBER_STYLE ),
1282     nKey(nTempKey),
1283     eImplicitCalendar(ImplicitCalendar::DEFAULT),
1284     nFormatLang( nLang ),
1285     bAutoOrder( false ),
1286     bFromSystem( false ),
1287     bTruncate( true ),
1288     bAutoDec( false ),
1289     bAutoInt( false ),
1290     bHasExtraText( false ),
1291     bHasTrailingEmptyText( false ),
1292     bHasLongDoW( false ),
1293     bHasDateTime( false ),
1294     bRemoveAfterUse( false ),
1295     eDateDOW( XML_DEA_NONE ),
1296     eDateDay( XML_DEA_NONE ),
1297     eDateMonth( XML_DEA_NONE ),
1298     eDateYear( XML_DEA_NONE ),
1299     eDateHours( XML_DEA_NONE ),
1300     eDateMins( XML_DEA_NONE ),
1301     eDateSecs( XML_DEA_NONE ),
1302     bDateNoDefault( false )
1303 {
1304     SetAttribute(XML_ELEMENT(STYLE, XML_NAME), rName);
1305 }
1306 
1307 SvXMLNumFormatContext::~SvXMLNumFormatContext()
1308 {
1309 }
1310 
1311 css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLNumFormatContext::createFastChildContext(
1312     sal_Int32 nElement,
1313     const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
1314 {
1315     SvXMLImportContext* pContext = nullptr;
1316 
1317     switch (nElement)
1318     {
1319         case XML_ELEMENT(LO_EXT, XML_TEXT):
1320         case XML_ELEMENT(NUMBER, XML_TEXT):
1321             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1322                                                         *this, SvXMLStyleTokens::Text, xAttrList );
1323             break;
1324         case XML_ELEMENT(LO_EXT, XML_FILL_CHARACTER):
1325         case XML_ELEMENT(NUMBER, XML_FILL_CHARACTER):
1326             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1327                                                         *this, SvXMLStyleTokens::FillCharacter, xAttrList );
1328             break;
1329         case XML_ELEMENT(NUMBER, XML_NUMBER):
1330             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1331                                                         *this, SvXMLStyleTokens::Number, xAttrList );
1332             break;
1333         case XML_ELEMENT(NUMBER, XML_SCIENTIFIC_NUMBER):
1334             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1335                                                         *this, SvXMLStyleTokens::ScientificNumber, xAttrList );
1336             break;
1337         case XML_ELEMENT(NUMBER, XML_FRACTION):
1338             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1339                                                         *this, SvXMLStyleTokens::Fraction, xAttrList );
1340             break;
1341         case XML_ELEMENT(NUMBER, XML_CURRENCY_SYMBOL):
1342             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1343                                                         *this, SvXMLStyleTokens::CurrencySymbol, xAttrList );
1344             break;
1345         case XML_ELEMENT(NUMBER, XML_DAY):
1346             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1347                                                         *this, SvXMLStyleTokens::Day, xAttrList );
1348             break;
1349         case XML_ELEMENT(NUMBER, XML_MONTH):
1350             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1351                                                         *this, SvXMLStyleTokens::Month, xAttrList );
1352             break;
1353         case XML_ELEMENT(NUMBER, XML_YEAR):
1354             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1355                                                         *this, SvXMLStyleTokens::Year, xAttrList );
1356             break;
1357         case XML_ELEMENT(NUMBER, XML_ERA):
1358             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1359                                                         *this, SvXMLStyleTokens::Era, xAttrList );
1360             break;
1361         case XML_ELEMENT(NUMBER, XML_DAY_OF_WEEK):
1362             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1363                                                         *this, SvXMLStyleTokens::DayOfWeek, xAttrList );
1364             break;
1365         case XML_ELEMENT(NUMBER, XML_WEEK_OF_YEAR):
1366             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1367                                                         *this, SvXMLStyleTokens::WeekOfYear, xAttrList );
1368             break;
1369         case XML_ELEMENT(NUMBER, XML_QUARTER):
1370             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1371                                                         *this, SvXMLStyleTokens::Quarter, xAttrList );
1372             break;
1373         case XML_ELEMENT(NUMBER, XML_HOURS):
1374             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1375                                                         *this, SvXMLStyleTokens::Hours, xAttrList );
1376             break;
1377         case XML_ELEMENT(NUMBER, XML_AM_PM):
1378             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1379                                                         *this, SvXMLStyleTokens::AmPm, xAttrList );
1380             break;
1381         case XML_ELEMENT(NUMBER, XML_MINUTES):
1382             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1383                                                         *this, SvXMLStyleTokens::Minutes, xAttrList );
1384             break;
1385         case XML_ELEMENT(NUMBER, XML_SECONDS):
1386             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1387                                                         *this, SvXMLStyleTokens::Seconds, xAttrList );
1388             break;
1389         case XML_ELEMENT(NUMBER, XML_BOOLEAN):
1390             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1391                                                         *this, SvXMLStyleTokens::Boolean, xAttrList );
1392             break;
1393         case XML_ELEMENT(NUMBER, XML_TEXT_CONTENT):
1394             pContext = new SvXMLNumFmtElementContext( GetImport(), nElement,
1395                                                         *this, SvXMLStyleTokens::TextContent, xAttrList );
1396             break;
1397 
1398         case XML_ELEMENT(STYLE, XML_TEXT_PROPERTIES):
1399             pContext = new SvXMLNumFmtPropContext( GetImport(), nElement,
1400                                                         *this, xAttrList );
1401             break;
1402         case XML_ELEMENT(STYLE, XML_MAP):
1403             {
1404                 //  SvXMLNumFmtMapContext::EndElement adds to aMyConditions,
1405                 //  so there's no need for an extra flag
1406                 pContext = new SvXMLNumFmtMapContext( GetImport(), nElement,
1407                                                             *this, xAttrList );
1408             }
1409             break;
1410     }
1411 
1412     if( !pContext )
1413     {
1414         SAL_WARN("xmloff.core", "No context for unknown-element " << SvXMLImport::getPrefixAndNameFromToken(nElement));
1415         pContext = new SvXMLImportContext(GetImport());
1416     }
1417 
1418     return pContext;
1419 }
1420 
1421 sal_Int32 SvXMLNumFormatContext::GetKey()
1422 {
1423     if (nKey > -1)
1424     {
1425         if (bRemoveAfterUse)
1426         {
1427             //  format is used -> don't remove
1428             bRemoveAfterUse = false;
1429             if (pData)
1430                 pData->SetUsed(nKey);
1431 
1432             //  Add to import's list of keys now - CreateAndInsert didn't add
1433             //  the style if bRemoveAfterUse was set.
1434             GetImport().AddNumberStyle( nKey, GetName() );
1435         }
1436         return nKey;
1437     }
1438     else
1439     {
1440         // reset bRemoveAfterUse before CreateAndInsert, so AddKey is called without bRemoveAfterUse set
1441         bRemoveAfterUse = false;
1442         CreateAndInsert(true);
1443         return nKey;
1444     }
1445 }
1446 
1447 sal_Int32 SvXMLNumFormatContext::PrivateGetKey()
1448 {
1449     //  used for map elements in CreateAndInsert - don't reset bRemoveAfterUse flag
1450 
1451     if (nKey > -1)
1452         return nKey;
1453     else
1454     {
1455         CreateAndInsert(true);
1456         return nKey;
1457     }
1458 }
1459 
1460 sal_Int32 SvXMLNumFormatContext::CreateAndInsert( css::uno::Reference< css::util::XNumberFormatsSupplier > const & xFormatsSupplier )
1461 {
1462     if (nKey <= -1)
1463     {
1464         SvNumberFormatter* pFormatter = nullptr;
1465         SvNumberFormatsSupplierObj* pObj =
1466                         comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xFormatsSupplier );
1467         if (pObj)
1468             pFormatter = pObj->GetNumberFormatter();
1469 
1470         if ( pFormatter )
1471             return CreateAndInsert( pFormatter );
1472         else
1473             return -1;
1474     }
1475     else
1476         return nKey;
1477 }
1478 
1479 void SvXMLNumFormatContext::CreateAndInsert(bool /*bOverwrite*/)
1480 {
1481     if (nKey <= -1)
1482         CreateAndInsert(pData->GetNumberFormatter());
1483 }
1484 
1485 sal_Int32 SvXMLNumFormatContext::CreateAndInsert(SvNumberFormatter* pFormatter)
1486 {
1487     if (!pFormatter)
1488     {
1489         OSL_FAIL("no number formatter");
1490         return -1;
1491     }
1492 
1493     sal_uInt32 nIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
1494 
1495     for (size_t i = 0; i < aMyConditions.size(); i++)
1496     {
1497         SvXMLNumFormatContext* pStyle = const_cast<SvXMLNumFormatContext*>( static_cast<const SvXMLNumFormatContext *>(pStyles->FindStyleChildContext(
1498             XmlStyleFamily::DATA_STYLE, aMyConditions[i].sMapName)));
1499         if (this == pStyle)
1500         {
1501             SAL_INFO("xmloff.style", "invalid style:map references containing style");
1502             pStyle = nullptr;
1503         }
1504         if (pStyle)
1505         {
1506             if (pStyle->PrivateGetKey() > -1)     // don't reset pStyle's bRemoveAfterUse flag
1507                 AddCondition(i);
1508         }
1509     }
1510 
1511     sal_Int32 nBufLen;
1512     if ( aFormatCode.isEmpty() )
1513     {
1514         //  insert empty format as empty string (with quotes)
1515         //  #93901# this check has to be done before inserting the conditions
1516         aFormatCode.append("\"\"");    // ""
1517     }
1518     else if (bHasTrailingEmptyText && (nBufLen = aFormatCode.getLength()) >= 3)
1519     {
1520         // Remove a trailing empty text. Earlier this may had been written to
1521         // file, like in "General;General" written with elements for
1522         // 'General"";General""' (whyever); when reading, empty text was
1523         // ignored, which it isn't anymore, so get rid of those.
1524         if (aFormatCode[nBufLen-1] == '"' && aFormatCode[nBufLen-2] == '"')
1525             aFormatCode.truncate( nBufLen - 2);
1526     }
1527 
1528     aFormatCode.insert( 0, aConditions );
1529     aConditions.setLength(0);
1530     OUString sFormat = aFormatCode.makeStringAndClear();
1531 
1532     //  test special cases
1533 
1534     if ( bAutoDec )         // automatic decimal places
1535     {
1536         //  #99391# adjust only if the format contains no text elements, no conditions
1537         //  and no color definition (detected by the '[' at the start)
1538 
1539         if ( nType == SvXMLStylesTokens::NUMBER_STYLE && !bHasExtraText &&
1540                 aMyConditions.empty() && sFormat.toChar() != '[' )
1541             nIndex = pFormatter->GetStandardIndex( nFormatLang );
1542     }
1543     if ( bAutoInt )         // automatic integer digits
1544     {
1545         //! only if two decimal places was set?
1546 
1547         if ( nType == SvXMLStylesTokens::NUMBER_STYLE && !bHasExtraText &&
1548                 aMyConditions.empty() && sFormat.toChar() != '[' )
1549             nIndex = pFormatter->GetFormatIndex( NF_NUMBER_SYSTEM, nFormatLang );
1550     }
1551 
1552     if ( nType == SvXMLStylesTokens::BOOLEAN_STYLE && !bHasExtraText &&
1553             aMyConditions.empty() && sFormat.toChar() != '[' )
1554         nIndex = pFormatter->GetFormatIndex( NF_BOOLEAN, nFormatLang );
1555 
1556     //  check for default date formats
1557     if ( nType == SvXMLStylesTokens::DATE_STYLE && bAutoOrder && !bDateNoDefault )
1558     {
1559         NfIndexTableOffset eFormat = static_cast<NfIndexTableOffset>(SvXMLNumFmtDefaults::GetDefaultDateFormat(
1560             eDateDOW, eDateDay, eDateMonth, eDateYear,
1561             eDateHours, eDateMins, eDateSecs, bFromSystem ));
1562         if ( eFormat < NF_INDEX_TABLE_RESERVED_START )
1563         {
1564             //  #109651# if a date format has the automatic-order attribute and
1565             //  contains exactly the elements of one of the default date formats,
1566             //  use that default format, with the element order and separators
1567             //  from the current locale settings
1568 
1569             nIndex = pFormatter->GetFormatIndex( eFormat, nFormatLang );
1570         }
1571     }
1572 
1573     if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND && !sFormat.isEmpty() )
1574     {
1575         //  insert by format string
1576 
1577         OUString aFormatStr( sFormat );
1578         nIndex = pFormatter->GetEntryKey( aFormatStr, nFormatLang );
1579         if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND )
1580         {
1581             sal_Int32  nErrPos = 0;
1582             SvNumFormatType l_nType = SvNumFormatType::ALL;
1583             bool bOk = pFormatter->PutEntry( aFormatStr, nErrPos, l_nType, nIndex, nFormatLang );
1584             if ( !bOk && nErrPos == 0 && aFormatStr != sFormat )
1585             {
1586                 //  if the string was modified by PutEntry, look for an existing format
1587                 //  with the modified string
1588                 nIndex = pFormatter->GetEntryKey( aFormatStr, nFormatLang );
1589                 if ( nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND )
1590                     bOk = true;
1591             }
1592             if (!bOk)
1593                 nIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
1594         }
1595     }
1596 
1597 //! I18N doesn't provide SYSTEM or extended date information yet
1598     if ( nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND && !bAutoOrder )
1599     {
1600         //  use fixed-order formats instead of SYS... if bAutoOrder is false
1601         //  (only if the format strings are equal for the locale)
1602 
1603         NfIndexTableOffset eOffset = pFormatter->GetIndexTableOffset( nIndex );
1604         if ( eOffset == NF_DATE_SYS_DMMMYYYY )
1605         {
1606             sal_uInt32 nNewIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_DMMMYYYY, nFormatLang );
1607             const SvNumberformat* pOldEntry = pFormatter->GetEntry( nIndex );
1608             const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewIndex );
1609             if ( pOldEntry && pNewEntry && pOldEntry->GetFormatstring() == pNewEntry->GetFormatstring() )
1610                 nIndex = nNewIndex;
1611         }
1612         else if ( eOffset == NF_DATE_SYS_DMMMMYYYY )
1613         {
1614             sal_uInt32 nNewIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_DMMMMYYYY, nFormatLang );
1615             const SvNumberformat* pOldEntry = pFormatter->GetEntry( nIndex );
1616             const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewIndex );
1617             if ( pOldEntry && pNewEntry && pOldEntry->GetFormatstring() == pNewEntry->GetFormatstring() )
1618                 nIndex = nNewIndex;
1619         }
1620     }
1621 
1622     if ((nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND) && !sFormatTitle.isEmpty())
1623     {
1624         SvNumberformat* pFormat = const_cast<SvNumberformat*>(pFormatter->GetEntry( nIndex ));
1625         if (pFormat)
1626         {
1627             pFormat->SetComment(sFormatTitle);
1628         }
1629     }
1630 
1631     if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND )
1632     {
1633         OSL_FAIL("invalid number format");
1634         nIndex = pFormatter->GetStandardIndex( nFormatLang );
1635     }
1636 
1637     pData->AddKey( nIndex, GetName(), bRemoveAfterUse );
1638     nKey = nIndex;
1639 
1640     //  Add to import's list of keys (shared between styles and content import)
1641     //  only if not volatile - formats are removed from NumberFormatter at the
1642     //  end of each import (in SvXMLNumFmtHelper dtor).
1643     //  If bRemoveAfterUse is reset later in GetKey, AddNumberStyle is called there.
1644 
1645     if (!bRemoveAfterUse)
1646         GetImport().AddNumberStyle( nKey, GetName() );
1647 
1648     return nKey;
1649 }
1650 
1651 const LocaleDataWrapper& SvXMLNumFormatContext::GetLocaleData() const
1652 {
1653     return pData->GetLocaleData( nFormatLang );
1654 }
1655 
1656 void SvXMLNumFormatContext::AddToCode( sal_Unicode c )
1657 {
1658     aFormatCode.append( c );
1659     bHasExtraText = true;
1660 }
1661 
1662 void SvXMLNumFormatContext::AddToCode( std::u16string_view rString )
1663 {
1664     aFormatCode.append( rString );
1665     bHasExtraText = true;
1666     bHasTrailingEmptyText = false;  // is set by caller again if so
1667 }
1668 
1669 void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
1670 {
1671     SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
1672     if (!pFormatter)
1673         return;
1674 
1675     //  store special conditions
1676     bAutoDec = ( rInfo.nDecimals < 0 );
1677     bAutoInt = ( rInfo.nInteger < 0 );
1678 
1679     sal_uInt16 nPrec = 0;
1680     sal_uInt16 nLeading = 0;
1681     if ( rInfo.nDecimals >= 0 )                     //  < 0 : Default
1682         nPrec = static_cast<sal_uInt16>(rInfo.nDecimals);
1683     if ( rInfo.nInteger >= 0 )                      //  < 0 : Default
1684         nLeading = static_cast<sal_uInt16>(rInfo.nInteger);
1685 
1686     if ( bAutoDec )
1687     {
1688         if ( nType == SvXMLStylesTokens::CURRENCY_STYLE )
1689         {
1690             //  for currency formats, "automatic decimals" is used for the automatic
1691             //  currency format with (fixed) decimals from the locale settings
1692 
1693             const LocaleDataWrapper& rLoc = pData->GetLocaleData( nFormatLang );
1694             nPrec = rLoc.getCurrDigits();
1695         }
1696         else
1697         {
1698             //  for other types, "automatic decimals" means dynamic determination of
1699             //  decimals, as achieved with the "general" keyword
1700 
1701             aFormatCode.append( pFormatter->GetStandardName( nFormatLang ) );
1702             return;
1703         }
1704     }
1705     if ( bAutoInt )
1706     {
1707         //!...
1708     }
1709 
1710     sal_uInt16 nGenPrec = nPrec;
1711     if ( rInfo.nMinDecimalDigits >= 0 )
1712         nGenPrec = rInfo.nMinDecimalDigits;
1713     if ( rInfo.bDecReplace )
1714         nGenPrec = 0;               // generate format without decimals...
1715 
1716     bool bGrouping = rInfo.bGrouping;
1717     size_t const nEmbeddedCount = rInfo.m_EmbeddedElements.size();
1718     if ( nEmbeddedCount && rInfo.m_EmbeddedElements.rbegin()->first > 0 )
1719         bGrouping = false;      // grouping and embedded characters in integer part can't be used together
1720 
1721     sal_uInt32 nStdIndex = pFormatter->GetStandardIndex( nFormatLang );
1722     OUStringBuffer aNumStr(pFormatter->GenerateFormat( nStdIndex, nFormatLang,
1723                                                          bGrouping, false, nGenPrec, nLeading ));
1724 
1725     if ( rInfo.nExpDigits >= 0 && nLeading == 0 && !bGrouping && nEmbeddedCount == 0 )
1726     {
1727         // #i43959# For scientific numbers, "#" in the integer part forces a digit,
1728         // so it has to be removed if nLeading is 0 (".00E+0", not "#.00E+0").
1729 
1730         aNumStr.stripStart('#');
1731     }
1732 
1733     if ( rInfo.nBlankInteger > 0 )
1734     {
1735         // Replace nBlankInteger '0' by '?'
1736         sal_Int32 nIndex = 0;
1737         sal_Int32 nBlanks = rInfo.nBlankInteger;
1738         sal_Int32 nIntegerEnd = aNumStr.indexOf( pFormatter->GetNumDecimalSep() );
1739         if ( nIntegerEnd < 0 )
1740             nIntegerEnd = aNumStr.getLength();
1741         while ( nIndex < nIntegerEnd && nBlanks > 0 )
1742         {
1743             if ( aNumStr[nIndex] == '0' )
1744             {
1745                 aNumStr[nIndex] = '?';
1746                 nBlanks--;
1747             }
1748             nIndex++;
1749         }
1750     }
1751 
1752     if ( bGrouping && rInfo.nExpInterval > rInfo.nInteger )
1753     {
1754         sal_Int32 nIndex = 0;
1755         sal_Int32 nDigits = rInfo.nInteger;
1756         sal_Int32 nIntegerEnd = aNumStr.indexOf( pFormatter->GetNumDecimalSep() );
1757         if ( nIntegerEnd < 0 )
1758             nIntegerEnd = aNumStr.getLength();
1759         while ( nIndex >= 0 && nIndex < nIntegerEnd )
1760         {
1761             if ( ( nIndex = aNumStr.indexOf( '#', nIndex ) ) >= 0 )
1762             {
1763                 nDigits ++;
1764                 nIndex ++;
1765             }
1766             else
1767                 nIndex = -1;
1768         }
1769         while ( rInfo.nExpInterval > nDigits )
1770         {
1771             nDigits++;
1772             aNumStr.insert( 0, '#' );
1773         }
1774     }
1775 
1776     if ( ( rInfo.bDecReplace || rInfo.nMinDecimalDigits < rInfo.nDecimals ) && nPrec )     // add decimal replacement (dashes)
1777     {
1778         //  add dashes for explicit decimal replacement, # or ? for variable decimals
1779         sal_Unicode cAdd = rInfo.bDecReplace ? '-' : ( rInfo.bDecAlign ? '?': '#' );
1780 
1781         if ( rInfo.nMinDecimalDigits == 0 )
1782             aNumStr.append( pData->GetLocaleData( nFormatLang ).getNumDecimalSep() );
1783         for ( sal_uInt16 i=rInfo.nMinDecimalDigits; i<nPrec; i++)
1784             aNumStr.append( cAdd );
1785     }
1786 
1787     if ( nEmbeddedCount )
1788     {
1789         //  insert embedded strings into number string
1790         //  support integer (position >=0) and decimal (position <0) part
1791         //  nZeroPos is the string position where format position 0 is inserted
1792 
1793         sal_Int32 nZeroPos = aNumStr.indexOf( pData->GetLocaleData( nFormatLang ).getNumDecimalSep() );
1794         if ( nZeroPos < 0 )
1795         {
1796             nZeroPos = aNumStr.getLength();
1797         }
1798 
1799         // m_EmbeddedElements is sorted - last entry has the largest position (leftmost)
1800         sal_Int32 const nLastFormatPos = rInfo.m_EmbeddedElements.rbegin()->first;
1801         if ( nLastFormatPos >= nZeroPos )
1802         {
1803             //  add '#' characters so all embedded texts are really embedded in digits
1804             //  (there always has to be a digit before the leftmost embedded text)
1805 
1806             sal_Int32 nAddCount = nLastFormatPos + 1 - nZeroPos;
1807             for(sal_Int32 index = 0; index < nAddCount; ++index)
1808             {
1809                 aNumStr.insert(0, '#');
1810             }
1811             nZeroPos = nZeroPos + nAddCount;
1812         }
1813 
1814         // m_EmbeddedElements is sorted with ascending positions - loop is from right to left
1815         for (auto const& it : rInfo.m_EmbeddedElements)
1816         {
1817             sal_Int32 const nFormatPos = it.first;
1818             sal_Int32 nInsertPos = nZeroPos - nFormatPos;
1819             if ( nInsertPos >= 0 )
1820             {
1821                 //  #107805# always quote embedded strings - even space would otherwise
1822                 //  be recognized as thousands separator in French.
1823 
1824                 aNumStr.insert(nInsertPos, '"');
1825                 aNumStr.insert(nInsertPos, it.second);
1826                 aNumStr.insert(nInsertPos, '"');
1827             }
1828         }
1829     }
1830 
1831     aFormatCode.append( aNumStr );
1832 
1833     //  add extra thousands separators for display factor
1834 
1835     if (rInfo.fDisplayFactor == 1.0 || rInfo.fDisplayFactor <= 0.0)
1836         return;
1837 
1838     //  test for 1.0 is just for optimization - nSepCount would be 0
1839 
1840     //  one separator for each factor of 1000
1841     sal_Int32 nSepCount = static_cast<sal_Int32>(::rtl::math::round( log10(rInfo.fDisplayFactor) / 3.0 ));
1842     if ( nSepCount > 0 )
1843     {
1844         OUString aSep = pData->GetLocaleData( nFormatLang ).getNumThousandSep();
1845         for ( sal_Int32 i=0; i<nSepCount; i++ )
1846             aFormatCode.append( aSep );
1847     }
1848 }
1849 
1850 void SvXMLNumFormatContext::AddCurrency( const OUString& rContent, LanguageType nLang )
1851 {
1852     bool bAutomatic = false;
1853     OUString aSymbol = rContent;
1854     if ( aSymbol.isEmpty())
1855     {
1856         SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
1857         if ( pFormatter )
1858         {
1859             pFormatter->ChangeIntl( nFormatLang );
1860             OUString sCurString, sDummy;
1861             pFormatter->GetCompatibilityCurrency( sCurString, sDummy );
1862             aSymbol = sCurString;
1863 
1864             bAutomatic = true;
1865         }
1866     }
1867     else if ( nLang == LANGUAGE_SYSTEM && aSymbol == "CCC" )
1868     {
1869         //  "CCC" is used for automatic long symbol
1870         bAutomatic = true;
1871     }
1872 
1873     if ( bAutomatic )
1874     {
1875         //  remove unnecessary quotes before automatic symbol (formats like "-(0DM)")
1876         //  otherwise the currency symbol isn't recognized (#94048#)
1877 
1878         sal_Int32 nLength = aFormatCode.getLength();
1879         if ( nLength > 1 && aFormatCode[nLength - 1] == '"' )
1880         {
1881             //  find start of quoted string
1882             //  When SvXMLNumFmtElementContext::EndElement creates escaped quotes,
1883             //  they must be handled here, too.
1884 
1885             sal_Int32 nFirst = nLength - 2;
1886             while ( nFirst >= 0 && aFormatCode[nFirst] != '"' )
1887                 --nFirst;
1888             if ( nFirst >= 0 )
1889             {
1890                 //  remove both quotes from aFormatCode
1891                 OUString aOld = aFormatCode.makeStringAndClear();
1892                 if ( nFirst > 0 )
1893                     aFormatCode.append( aOld.subView( 0, nFirst ) );
1894                 if ( nLength > nFirst + 2 )
1895                     aFormatCode.append( aOld.subView( nFirst + 1, nLength - nFirst - 2 ) );
1896             }
1897         }
1898     }
1899 
1900     if (!bAutomatic)
1901         aFormatCode.append( "[$" );            // intro for "new" currency symbols
1902 
1903     aFormatCode.append( aSymbol );
1904 
1905     if (!bAutomatic)
1906     {
1907         if ( nLang != LANGUAGE_SYSTEM )
1908         {
1909             //  '-' sign and language code in hex:
1910             aFormatCode.append("-" + OUString(OUString::number(sal_uInt16(nLang), 16)).toAsciiUpperCase());
1911         }
1912 
1913         aFormatCode.append( ']' );    // end of "new" currency symbol
1914     }
1915 }
1916 
1917 void SvXMLNumFormatContext::AddNfKeyword( sal_uInt16 nIndex )
1918 {
1919     SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
1920     if (!pFormatter)
1921         return;
1922 
1923     if ( nIndex == NF_KEY_NNNN )
1924     {
1925         nIndex = NF_KEY_NNN;
1926         bHasLongDoW = true;         // to remove string constant with separator
1927     }
1928 
1929     OUString sKeyword = pFormatter->GetKeyword( nFormatLang, nIndex );
1930 
1931     if ( nIndex == NF_KEY_H  || nIndex == NF_KEY_HH  ||
1932          nIndex == NF_KEY_MI || nIndex == NF_KEY_MMI ||
1933          nIndex == NF_KEY_S  || nIndex == NF_KEY_SS )
1934     {
1935         if ( !bTruncate && !bHasDateTime )
1936         {
1937             //  with truncate-on-overflow = false, add "[]" to first time part
1938             aFormatCode.append("[" + sKeyword + "]");
1939         }
1940         else
1941         {
1942             aFormatCode.append( sKeyword );
1943         }
1944         bHasDateTime = true;
1945     }
1946     else
1947     {
1948         aFormatCode.append( sKeyword );
1949     }
1950     //  collect the date elements that the format contains, to recognize default date formats
1951     switch ( nIndex )
1952     {
1953         case NF_KEY_NN:     eDateDOW = XML_DEA_SHORT;       break;
1954         case NF_KEY_NNN:
1955         case NF_KEY_NNNN:   eDateDOW = XML_DEA_LONG;        break;
1956         case NF_KEY_D:      eDateDay = XML_DEA_SHORT;       break;
1957         case NF_KEY_DD:     eDateDay = XML_DEA_LONG;        break;
1958         case NF_KEY_M:      eDateMonth = XML_DEA_SHORT;     break;
1959         case NF_KEY_MM:     eDateMonth = XML_DEA_LONG;      break;
1960         case NF_KEY_MMM:    eDateMonth = XML_DEA_TEXTSHORT; break;
1961         case NF_KEY_MMMM:   eDateMonth = XML_DEA_TEXTLONG;  break;
1962         case NF_KEY_YY:     eDateYear = XML_DEA_SHORT;      break;
1963         case NF_KEY_YYYY:   eDateYear = XML_DEA_LONG;       break;
1964         case NF_KEY_H:      eDateHours = XML_DEA_SHORT;     break;
1965         case NF_KEY_HH:     eDateHours = XML_DEA_LONG;      break;
1966         case NF_KEY_MI:     eDateMins = XML_DEA_SHORT;      break;
1967         case NF_KEY_MMI:    eDateMins = XML_DEA_LONG;       break;
1968         case NF_KEY_S:      eDateSecs = XML_DEA_SHORT;      break;
1969         case NF_KEY_SS:     eDateSecs = XML_DEA_LONG;       break;
1970         case NF_KEY_AP:
1971         case NF_KEY_AMPM:   break;          // AM/PM may or may not be in date/time formats -> ignore by itself
1972         default:
1973             bDateNoDefault = true;      // any other element -> no default format
1974     }
1975 }
1976 
1977 static bool lcl_IsAtEnd( OUStringBuffer& rBuffer, std::u16string_view rToken )
1978 {
1979     sal_Int32 nBufLen = rBuffer.getLength();
1980     sal_Int32 nTokLen = rToken.size();
1981 
1982     if ( nTokLen > nBufLen )
1983         return false;
1984 
1985     sal_Int32 nStartPos = nBufLen - nTokLen;
1986     for ( sal_Int32 nTokPos = 0; nTokPos < nTokLen; nTokPos++ )
1987         if ( rToken[ nTokPos ] != rBuffer[nStartPos + nTokPos] )
1988             return false;
1989 
1990     return true;
1991 }
1992 
1993 bool SvXMLNumFormatContext::ReplaceNfKeyword( sal_uInt16 nOld, sal_uInt16 nNew )
1994 {
1995     //  replaces one keyword with another if it is found at the end of the code
1996 
1997     SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
1998     if (!pFormatter)
1999         return false;
2000 
2001     OUString sOldStr = pFormatter->GetKeyword( nFormatLang, nOld );
2002     if ( lcl_IsAtEnd( aFormatCode, sOldStr ) )
2003     {
2004         // remove old keyword
2005         aFormatCode.setLength( aFormatCode.getLength() - sOldStr.getLength() );
2006 
2007         // add new keyword
2008         OUString sNewStr = pFormatter->GetKeyword( nFormatLang, nNew );
2009         aFormatCode.append( sNewStr );
2010 
2011         return true;    // changed
2012     }
2013     return false;       // not found
2014 }
2015 
2016 void SvXMLNumFormatContext::AddCondition( const sal_Int32 nIndex )
2017 {
2018     OUString rApplyName = aMyConditions[nIndex].sMapName;
2019     OUString rCondition = aMyConditions[nIndex].sCondition;
2020     SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
2021     sal_uInt32 l_nKey = pData->GetKeyForName( rApplyName );
2022 
2023     OUString sRealCond;
2024     if ( !(pFormatter && l_nKey != NUMBERFORMAT_ENTRY_NOT_FOUND &&
2025             rCondition.startsWith("value()", &sRealCond)) )
2026         return;
2027 
2028     //! test for valid conditions
2029     //! test for default conditions
2030 
2031     bool bDefaultCond = false;
2032 
2033     //! collect all conditions first and adjust default to >=0, >0 or <0 depending on count
2034     //! allow blanks in conditions
2035     if ( aConditions.isEmpty() && aMyConditions.size() == 1 && sRealCond == ">=0" )
2036         bDefaultCond = true;
2037 
2038     if ( nType == SvXMLStylesTokens::TEXT_STYLE && static_cast<size_t>(nIndex) == aMyConditions.size() - 1 )
2039     {
2040         //  The last condition in a number format with a text part can only
2041         //  be "all other numbers", the condition string must be empty.
2042         bDefaultCond = true;
2043     }
2044 
2045     if (!bDefaultCond)
2046     {
2047         // Convert != to <>
2048         sal_Int32 nPos = sRealCond.indexOf( "!=" );
2049         if ( nPos >= 0 )
2050         {
2051             sRealCond = sRealCond.replaceAt( nPos, 2, u"<>" );
2052         }
2053 
2054         nPos = sRealCond.indexOf( '.' );
2055         if ( nPos >= 0 )
2056         {
2057             // #i8026# #103991# localize decimal separator
2058             const OUString& rDecSep = GetLocaleData().getNumDecimalSep();
2059             if ( rDecSep.getLength() > 1 || rDecSep[0] != '.' )
2060             {
2061                 sRealCond = sRealCond.replaceAt( nPos, 1, rDecSep );
2062             }
2063         }
2064         aConditions.append("[" + sRealCond + "]");
2065     }
2066 
2067     const SvNumberformat* pFormat = pFormatter->GetEntry(l_nKey);
2068     if ( pFormat )
2069         aConditions.append( pFormat->GetFormatstring() );
2070 
2071     aConditions.append( ';' );
2072 }
2073 
2074 void SvXMLNumFormatContext::AddCondition( const OUString& rCondition, const OUString& rApplyName )
2075 {
2076     MyCondition aCondition;
2077     aCondition.sCondition = rCondition;
2078     aCondition.sMapName = rApplyName;
2079     aMyConditions.push_back(aCondition);
2080 }
2081 
2082 void SvXMLNumFormatContext::AddColor( Color const nColor )
2083 {
2084     SvNumberFormatter* pFormatter = pData->GetNumberFormatter();
2085     if (!pFormatter)
2086         return;
2087 
2088     OUStringBuffer aColName;
2089     for ( sal_uInt16 i=0; i<XML_NUMF_COLORCOUNT; i++ )
2090         if (nColor == aNumFmtStdColors[i])
2091         {
2092             aColName = pFormatter->GetKeyword( nFormatLang, sal::static_int_cast< sal_uInt16 >(NF_KEY_FIRSTCOLOR + i) );
2093             break;
2094         }
2095 
2096     if ( !aColName.isEmpty() )
2097     {
2098         aColName.insert( 0, '[' );
2099         aColName.append( ']' );
2100         aFormatCode.insert( 0, aColName );
2101     }
2102 }
2103 
2104 void SvXMLNumFormatContext::UpdateCalendar( const OUString& rNewCalendar )
2105 {
2106     if ( rNewCalendar == sCalendar )
2107         return;
2108 
2109     if (rNewCalendar.isEmpty() || rNewCalendar == aImplicitCalendar[0])
2110     {
2111         eImplicitCalendar = (eImplicitCalendar == ImplicitCalendar::OTHER ?
2112                 ImplicitCalendar::DEFAULT_FROM_OTHER : ImplicitCalendar::DEFAULT);
2113     }
2114     else if (aImplicitCalendar[0].isEmpty() && rNewCalendar == GetLocaleData().getDefaultCalendar()->Name)
2115     {
2116         eImplicitCalendar = (eImplicitCalendar == ImplicitCalendar::OTHER ?
2117                 ImplicitCalendar::DEFAULT_FROM_OTHER : ImplicitCalendar::DEFAULT);
2118         aImplicitCalendar[0] = rNewCalendar;
2119     }
2120     else if (rNewCalendar == aImplicitCalendar[1])
2121     {
2122         eImplicitCalendar = (eImplicitCalendar == ImplicitCalendar::OTHER ?
2123                 ImplicitCalendar::SECONDARY_FROM_OTHER : ImplicitCalendar::SECONDARY);
2124     }
2125     else if (aImplicitCalendar[1].isEmpty() && GetLocaleData().doesSecondaryCalendarUseEC( rNewCalendar))
2126     {
2127         eImplicitCalendar = (eImplicitCalendar == ImplicitCalendar::OTHER ?
2128                 ImplicitCalendar::SECONDARY_FROM_OTHER : ImplicitCalendar::SECONDARY);
2129         aImplicitCalendar[1] = rNewCalendar;
2130     }
2131     else
2132     {
2133         eImplicitCalendar = ImplicitCalendar::OTHER;
2134     }
2135 
2136     if (eImplicitCalendar != ImplicitCalendar::DEFAULT && eImplicitCalendar != ImplicitCalendar::SECONDARY)
2137     {
2138         // A switch from empty default calendar to named default calendar or
2139         // vice versa is not a switch.
2140         bool bSameDefault = false;
2141         if (sCalendar.isEmpty() || rNewCalendar.isEmpty())
2142         {
2143             // As both are not equal, only one can be empty here, the other
2144             // can not.
2145             const OUString& rDefaultCalendar = GetLocaleData().getDefaultCalendar()->Name;
2146             // So if one is the named default calendar the other is the
2147             // empty default calendar.
2148             bSameDefault = (rNewCalendar == rDefaultCalendar || sCalendar == rDefaultCalendar);
2149         }
2150         if (!bSameDefault)
2151         {
2152             aFormatCode.append( "[~" );   // intro for calendar code
2153             if (rNewCalendar.isEmpty())
2154             {
2155                 // Empty calendar name here means switching to default calendar
2156                 // from a different calendar. Needs to be explicitly stated in
2157                 // format code.
2158                 aFormatCode.append( GetLocaleData().getDefaultCalendar()->Name );
2159             }
2160             else
2161             {
2162                 aFormatCode.append( rNewCalendar );
2163             }
2164             aFormatCode.append( ']' );    // end of calendar code
2165         }
2166     }
2167     sCalendar = rNewCalendar;
2168 }
2169 
2170 bool SvXMLNumFormatContext::IsSystemLanguage() const
2171 {
2172     return nFormatLang == LANGUAGE_SYSTEM;
2173 }
2174 
2175 
2176 //  SvXMLNumFmtHelper
2177 
2178 
2179 SvXMLNumFmtHelper::SvXMLNumFmtHelper(
2180     const uno::Reference<util::XNumberFormatsSupplier>& rSupp,
2181     const uno::Reference<uno::XComponentContext>& rxContext )
2182 {
2183     SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" );
2184 
2185     SvNumberFormatter* pFormatter = nullptr;
2186     SvNumberFormatsSupplierObj* pObj =
2187                     comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( rSupp );
2188     if (pObj)
2189         pFormatter = pObj->GetNumberFormatter();
2190 
2191     pData = std::make_unique<SvXMLNumImpData>( pFormatter, rxContext );
2192 }
2193 
2194 SvXMLNumFmtHelper::SvXMLNumFmtHelper(
2195     SvNumberFormatter* pNumberFormatter,
2196     const uno::Reference<uno::XComponentContext>& rxContext )
2197 {
2198     SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" );
2199 
2200     pData = std::make_unique<SvXMLNumImpData>( pNumberFormatter, rxContext );
2201 }
2202 
2203 SvXMLNumFmtHelper::~SvXMLNumFmtHelper()
2204 {
2205     //  remove temporary (volatile) formats from NumberFormatter
2206     pData->RemoveVolatileFormats();
2207 }
2208 
2209 
2210 SvXMLStyleContext*  SvXMLNumFmtHelper::CreateChildContext( SvXMLImport& rImport,
2211                 sal_Int32 nElement,
2212                 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList,
2213                 SvXMLStylesContext& rStyles )
2214 {
2215     SvXMLStylesTokens nStyleToken;
2216     switch (nElement)
2217     {
2218         case XML_ELEMENT(NUMBER, XML_NUMBER_STYLE):
2219             nStyleToken = SvXMLStylesTokens::NUMBER_STYLE;
2220             break;
2221         case XML_ELEMENT(NUMBER, XML_CURRENCY_STYLE):
2222             nStyleToken = SvXMLStylesTokens::CURRENCY_STYLE;
2223             break;
2224         case XML_ELEMENT(NUMBER, XML_PERCENTAGE_STYLE):
2225             nStyleToken = SvXMLStylesTokens::PERCENTAGE_STYLE;
2226             break;
2227         case XML_ELEMENT(NUMBER, XML_DATE_STYLE):
2228             nStyleToken = SvXMLStylesTokens::DATE_STYLE;
2229             break;
2230         case XML_ELEMENT(NUMBER, XML_TIME_STYLE):
2231             nStyleToken = SvXMLStylesTokens::TIME_STYLE;
2232             break;
2233         case XML_ELEMENT(NUMBER, XML_BOOLEAN_STYLE):
2234             nStyleToken = SvXMLStylesTokens::BOOLEAN_STYLE;
2235             break;
2236         case XML_ELEMENT(NUMBER, XML_TEXT_STYLE):
2237             nStyleToken = SvXMLStylesTokens::TEXT_STYLE;
2238             break;
2239         default:
2240             // return NULL if not a data style, caller must handle other elements
2241             return nullptr;
2242     }
2243     return new SvXMLNumFormatContext( rImport, nElement,
2244                                       pData.get(), nStyleToken, xAttrList, rStyles );
2245 }
2246 
2247 LanguageType SvXMLNumFmtHelper::GetLanguageForKey(sal_Int32 nKey) const
2248 {
2249     if (pData->GetNumberFormatter())
2250     {
2251         const SvNumberformat* pEntry = pData->GetNumberFormatter()->GetEntry(nKey);
2252         if (pEntry)
2253             return pEntry->GetLanguage();
2254     }
2255 
2256     return LANGUAGE_SYSTEM;
2257 }
2258 
2259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2260