Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/xmloff/source/style/chrlohdl.cxx
Line
Count
Source
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 "chrlohdl.hxx"
21
#include <xmloff/xmltoken.hxx>
22
#include <xmloff/xmluconv.hxx>
23
#include <unotools/saveopt.hxx>
24
#include <i18nlangtag/languagetag.hxx>
25
#include <sal/log.hxx>
26
#include <com/sun/star/uno/Any.hxx>
27
#include <com/sun/star/lang/Locale.hpp>
28
29
using namespace ::com::sun::star;
30
using namespace ::xmloff::token;
31
32
/* TODO-BCP47: this fiddling with Locale is quite ugly and fragile, especially
33
 * for the fo:script temporarily stored in Variant, it would be better to use
34
 * LanguageTagODF but we have that nasty UNO API requirement here.
35
 * => make LanguageTagODF (unpublished) API? */
36
37
// For runtime performance, instead of converting back and forth between
38
// css::Locale and LanguageTag to decide if script or tag are
39
// needed, this code takes advantage of knowledge about the internal
40
// representation of BCP 47 language tags in a Locale if present as done in a
41
// LanguageTag.
42
43
XMLCharLanguageHdl::~XMLCharLanguageHdl()
44
134k
{
45
    // nothing to do
46
134k
}
47
48
bool XMLCharLanguageHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const
49
0
{
50
0
    bool bRet = false;
51
0
    lang::Locale aLocale1, aLocale2;
52
53
0
    if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) )
54
0
    {
55
0
        bool bEmptyOrScriptVariant1 = (aLocale1.Variant.isEmpty() || aLocale1.Variant[0] == '-');
56
0
        bool bEmptyOrScriptVariant2 = (aLocale2.Variant.isEmpty() || aLocale2.Variant[0] == '-');
57
0
        if (bEmptyOrScriptVariant1 && bEmptyOrScriptVariant2)
58
0
            bRet = ( aLocale1.Language == aLocale2.Language );
59
0
        else
60
0
        {
61
0
            OUString aLanguage1, aLanguage2;
62
0
            if (bEmptyOrScriptVariant1)
63
0
                aLanguage1 = aLocale1.Language;
64
0
            else
65
0
                aLanguage1 = LanguageTag( aLocale1).getLanguage();
66
0
            if (bEmptyOrScriptVariant2)
67
0
                aLanguage2 = aLocale2.Language;
68
0
            else
69
0
                aLanguage2 = LanguageTag( aLocale2).getLanguage();
70
0
            bRet = ( aLanguage1 == aLanguage2 );
71
0
        }
72
0
    }
73
74
0
    return bRet;
75
0
}
76
77
bool XMLCharLanguageHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const
78
15.9k
{
79
15.9k
    lang::Locale aLocale;
80
15.9k
    rValue >>= aLocale;
81
82
15.9k
    if( !IsXMLToken(rStrImpValue, XML_NONE) )
83
15.9k
    {
84
15.9k
        if (aLocale.Variant.isEmpty())
85
15.9k
            aLocale.Language = rStrImpValue;
86
0
        else
87
0
        {
88
0
            if (!aLocale.Language.isEmpty() || aLocale.Variant[0] != '-')
89
0
            {
90
0
                SAL_WARN_IF( aLocale.Language != I18NLANGTAG_QLT, "xmloff.style",
91
0
                        "XMLCharLanguageHdl::importXML - attempt to import language twice");
92
0
            }
93
0
            else
94
0
            {
95
0
                aLocale.Variant = rStrImpValue + aLocale.Variant;
96
0
                if (!aLocale.Country.isEmpty())
97
0
                    aLocale.Variant += "-" + aLocale.Country;
98
0
                aLocale.Language = I18NLANGTAG_QLT;
99
0
            }
100
0
        }
101
15.9k
    }
102
103
15.9k
    rValue <<= aLocale;
104
15.9k
    return true;
105
15.9k
}
106
107
bool XMLCharLanguageHdl::exportXML( OUString& rStrExpValue, const uno::Any& rValue, const SvXMLUnitConverter& ) const
108
0
{
109
0
    lang::Locale aLocale;
110
0
    if(!(rValue >>= aLocale))
111
0
        return false;
112
113
0
    if (aLocale.Variant.isEmpty())
114
0
        rStrExpValue = aLocale.Language;
115
0
    else
116
0
    {
117
0
        LanguageTag aLanguageTag( aLocale);
118
0
        OUString aScript, aCountry;
119
0
        aLanguageTag.getIsoLanguageScriptCountry( rStrExpValue, aScript, aCountry);
120
        // Do not write *:language='none' for a non-ISO language with
121
        // *:rfc-language-tag that is written if Variant is not empty. If there
122
        // is no match do not write this attribute at all.
123
0
        if (rStrExpValue.isEmpty())
124
0
            return false;
125
0
    }
126
127
0
    if( rStrExpValue.isEmpty() )
128
0
        rStrExpValue = GetXMLToken( XML_NONE );
129
130
0
    return true;
131
0
}
132
133
XMLCharScriptHdl::~XMLCharScriptHdl()
134
134k
{
135
    // nothing to do
136
134k
}
137
138
bool XMLCharScriptHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const
139
0
{
140
0
    bool bRet = false;
141
0
    lang::Locale aLocale1, aLocale2;
142
143
0
    if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) )
144
0
    {
145
0
        bool bEmptyVariant1 = aLocale1.Variant.isEmpty();
146
0
        bool bEmptyVariant2 = aLocale2.Variant.isEmpty();
147
0
        if (bEmptyVariant1 && bEmptyVariant2)
148
0
            bRet = true;
149
0
        else if (bEmptyVariant1 != bEmptyVariant2)
150
0
            ;   // stays false
151
0
        else
152
0
        {
153
0
            OUString aScript1, aScript2;
154
0
            if (aLocale1.Variant[0] == '-')
155
0
                aScript1 = aLocale1.Variant.copy(1);
156
0
            else
157
0
                aScript1 = LanguageTag( aLocale1).getScript();
158
0
            if (aLocale2.Variant[0] == '-')
159
0
                aScript2 = aLocale2.Variant.copy(1);
160
0
            else
161
0
                aScript2 = LanguageTag( aLocale2).getScript();
162
0
            bRet = ( aScript1 == aScript2 );
163
0
        }
164
0
    }
165
166
0
    return bRet;
167
0
}
168
169
bool XMLCharScriptHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const
170
0
{
171
0
    lang::Locale aLocale;
172
0
    rValue >>= aLocale;
173
174
0
    if( !IsXMLToken( rStrImpValue, XML_NONE ) )
175
0
    {
176
        // Import the script only if we don't have a full BCP 47 language tag
177
        // in Variant yet.
178
0
        if (aLocale.Variant.isEmpty())
179
0
        {
180
0
            if (aLocale.Language.isEmpty())
181
0
            {
182
0
                SAL_INFO( "xmloff.style", "XMLCharScriptHdl::importXML - script but no language yet");
183
                // Temporarily store in Variant and hope the best (we will get
184
                // a language later, yes?)
185
0
                aLocale.Variant = "-" + rStrImpValue;
186
0
            }
187
0
            else
188
0
            {
189
0
                aLocale.Variant = aLocale.Language + "-" + rStrImpValue;
190
0
                if (!aLocale.Country.isEmpty())
191
0
                    aLocale.Variant += "-" + aLocale.Country;
192
0
                aLocale.Language = I18NLANGTAG_QLT;
193
0
            }
194
0
        }
195
0
        else if (aLocale.Variant[0] == '-')
196
0
        {
197
0
            SAL_WARN( "xmloff.style", "XMLCharScriptHdl::importXML - attempt to insert script twice: "
198
0
                    << rStrImpValue << " -> " << aLocale.Variant);
199
0
        }
200
0
        else
201
0
        {
202
            // Assume that if there already is a script or anything else BCP 47
203
            // it was read by XMLCharRfcLanguageTagHdl() and takes precedence.
204
            // On the other hand, an *:rfc-language-tag without script and a
205
            // *:script ?!?
206
#if OSL_DEBUG_LEVEL > 0 || defined(DBG_UTIL)
207
            LanguageTag aLanguageTag( aLocale);
208
            if (!aLanguageTag.hasScript())
209
            {
210
                SAL_WARN( "xmloff.style", "XMLCharScriptHdl::importXML - attempt to insert script over bcp47: "
211
                        << rStrImpValue << " -> " << aLanguageTag.getBcp47());
212
            }
213
#endif
214
0
        }
215
0
    }
216
217
0
    rValue <<= aLocale;
218
0
    return true;
219
0
}
220
221
bool XMLCharScriptHdl::exportXML(OUString& rStrExpValue,
222
        const uno::Any& rValue, const SvXMLUnitConverter& rUnitConv) const
223
0
{
224
0
    lang::Locale aLocale;
225
0
    if(!(rValue >>= aLocale))
226
0
        return false;
227
228
    // Do not write script='none' for default script.
229
230
0
    if (aLocale.Variant.isEmpty())
231
0
        return false;
232
233
0
    LanguageTag aLanguageTag( aLocale);
234
0
    if (!aLanguageTag.hasScript())
235
0
        return false;
236
237
0
    if (rUnitConv.getSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012)
238
0
        return false;
239
240
0
    OUString aLanguage, aCountry;
241
0
    aLanguageTag.getIsoLanguageScriptCountry( aLanguage, rStrExpValue, aCountry);
242
    // For non-ISO language it does not make sense to write *:script if
243
    // *:language is not written either, does it? It's all in
244
    // *:rfc-language-tag
245
0
    return !aLanguage.isEmpty() && !rStrExpValue.isEmpty();
246
0
}
247
248
XMLCharCountryHdl::~XMLCharCountryHdl()
249
134k
{
250
    // nothing to do
251
134k
}
252
253
bool XMLCharCountryHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const
254
0
{
255
0
    bool bRet = false;
256
0
    lang::Locale aLocale1, aLocale2;
257
258
0
    if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) )
259
0
        bRet = ( aLocale1.Country == aLocale2.Country );
260
261
0
    return bRet;
262
0
}
263
264
bool XMLCharCountryHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const
265
15.8k
{
266
15.8k
    lang::Locale aLocale;
267
15.8k
    rValue >>= aLocale;
268
269
15.8k
    if( !IsXMLToken( rStrImpValue, XML_NONE ) )
270
15.2k
    {
271
15.2k
        if (aLocale.Country.isEmpty())
272
14.8k
        {
273
14.8k
            aLocale.Country = rStrImpValue;
274
14.8k
            if (aLocale.Variant.getLength() >= 7 && aLocale.Language == I18NLANGTAG_QLT)
275
0
            {
276
                // already assembled language tag, at least ll-Ssss and not
277
                // ll-CC or lll-CC
278
0
                sal_Int32 i = aLocale.Variant.indexOf('-');     // separator to script
279
0
                if (2 <= i && i < aLocale.Variant.getLength())
280
0
                {
281
0
                    i = aLocale.Variant.indexOf( '-', i+1);
282
0
                    if (i < 0)                                  // no other separator
283
0
                        aLocale.Variant += "-" + rStrImpValue;  // append country
284
0
                }
285
0
            }
286
14.8k
        }
287
15.2k
    }
288
289
15.8k
    rValue <<= aLocale;
290
15.8k
    return true;
291
15.8k
}
292
293
bool XMLCharCountryHdl::exportXML( OUString& rStrExpValue, const uno::Any& rValue, const SvXMLUnitConverter& ) const
294
0
{
295
0
    lang::Locale aLocale;
296
0
    if(!(rValue >>= aLocale))
297
0
        return false;
298
299
0
    if (aLocale.Variant.isEmpty())
300
0
        rStrExpValue = aLocale.Country;
301
0
    else
302
0
    {
303
0
        LanguageTag aLanguageTag( aLocale);
304
0
        OUString aLanguage, aScript;
305
0
        aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, rStrExpValue);
306
        // Do not write *:country='none' for a non-ISO country with
307
        // *:rfc-language-tag that is written if Variant is not empty. If there
308
        // is no match do not write this attribute at all.
309
0
        if (rStrExpValue.isEmpty())
310
0
            return false;
311
0
    }
312
313
0
    if( rStrExpValue.isEmpty() )
314
0
        rStrExpValue = GetXMLToken( XML_NONE );
315
316
0
    return true;
317
0
}
318
319
XMLCharRfcLanguageTagHdl::~XMLCharRfcLanguageTagHdl()
320
134k
{
321
    // nothing to do
322
134k
}
323
324
bool XMLCharRfcLanguageTagHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const
325
0
{
326
0
    bool bRet = false;
327
0
    lang::Locale aLocale1, aLocale2;
328
329
0
    if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) )
330
0
        bRet = ( aLocale1.Variant == aLocale2.Variant );
331
332
0
    return bRet;
333
0
}
334
335
bool XMLCharRfcLanguageTagHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const
336
0
{
337
0
    lang::Locale aLocale;
338
0
    rValue >>= aLocale;
339
340
0
    if( !IsXMLToken( rStrImpValue, XML_NONE ) )
341
0
    {
342
        // Stored may be a *:rfc-language-tag in violation of ODF v1.3
343
        // 19.516 style:rfc-language-tag "It shall only be used if its value
344
        // cannot be expressed as a valid combination of the fo:language
345
        // 19.871, fo:script 19.242 and fo:country 19.234 attributes".
346
        // That could override a more detailed fo:* and we also don't want an
347
        // unjustified I18NLANGTAG_QLT extended locale tag, but fetch the
348
        // values in case fo:* doesn't follow.
349
        // Rule out the obvious.
350
0
        if (rStrImpValue.getLength() < 7)
351
0
        {
352
0
            SAL_WARN("xmloff.style","rfc-language-tag too short: {" << rStrImpValue << "} Set: "
353
0
                    << aLocale.Language <<","<< aLocale.Country <<","<< aLocale.Variant);
354
            // Ignore empty and keep Ssss or any earlier qlt already set.
355
0
            if (!rStrImpValue.isEmpty() && aLocale.Language != I18NLANGTAG_QLT)
356
0
            {
357
                // Shorter than ll-Ssss, so try ll-CC or lll-CC or ll or lll
358
0
                sal_Int32 h = rStrImpValue.indexOf('-');
359
0
                OUString aLang;
360
0
                if (2 <= h && h <= 3)
361
0
                    aLang = rStrImpValue.copy(0, h);
362
0
                else if (h < 0 && 2 <= rStrImpValue.getLength() && rStrImpValue.getLength() <= 3)
363
0
                    aLang = rStrImpValue;
364
0
                OUString aCoun;
365
0
                if (!aLang.isEmpty() && aLang.getLength() + 3 == rStrImpValue.getLength())
366
0
                    aCoun = rStrImpValue.copy( aLang.getLength() + 1);
367
                // Ignore identical value or less information.
368
0
                if ((!aLang.isEmpty() && aLang != aLocale.Language) ||
369
0
                    (!aCoun.isEmpty() && aCoun != aLocale.Country))
370
0
                {
371
                    // Do not override existing values.
372
0
                    if (aLocale.Language.isEmpty())
373
0
                        aLocale.Language = aLang;
374
0
                    if (aLocale.Country.isEmpty())
375
0
                        aLocale.Country = aCoun;
376
0
                    if (aLang != aLocale.Language || aCoun != aLocale.Country)
377
0
                    {
378
                        // No match, so we still need the qlt anyway. Whatever..
379
0
                        aLocale.Variant = rStrImpValue;
380
0
                        aLocale.Language = I18NLANGTAG_QLT;
381
0
                    }
382
0
                }
383
0
                else if (aLang.isEmpty() && aCoun.isEmpty())
384
0
                {
385
                    // Both empty, some other tag.
386
0
                    aLocale.Variant = rStrImpValue;
387
0
                    aLocale.Language = I18NLANGTAG_QLT;
388
0
                }
389
0
            }
390
0
            SAL_WARN("xmloff.style","rfc-language-tag too short: now set: "
391
0
                    << aLocale.Language <<","<< aLocale.Country <<","<< aLocale.Variant);
392
0
        }
393
0
        else
394
0
        {
395
0
            aLocale.Variant = rStrImpValue;
396
0
            aLocale.Language = I18NLANGTAG_QLT;
397
0
        }
398
0
    }
399
400
0
    rValue <<= aLocale;
401
0
    return true;
402
0
}
403
404
bool XMLCharRfcLanguageTagHdl::exportXML(OUString& rStrExpValue,
405
        const uno::Any& rValue, const SvXMLUnitConverter& rUnitConv) const
406
0
{
407
0
    lang::Locale aLocale;
408
0
    if(!(rValue >>= aLocale))
409
0
        return false;
410
411
    // Do not write rfc-language-tag='none' if BCP 47 is not needed.
412
0
    if (aLocale.Variant.isEmpty())
413
0
        return false;
414
415
0
    if (rUnitConv.getSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012)
416
0
        return false;
417
418
0
    rStrExpValue = aLocale.Variant;
419
420
0
    return true;
421
0
}
422
423
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */