/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: */ |