Branch data Line data Source code
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 : :
10 : : #include "i18npool/languagetag.hxx"
11 : : #include "i18npool/mslangid.hxx"
12 : : #include <rtl/ustrbuf.hxx>
13 : : #include <rtl/bootstrap.hxx>
14 : : #include <osl/file.hxx>
15 : :
16 : : //#define erDEBUG
17 : :
18 : : #if defined(ENABLE_LIBLANGTAG)
19 : : #include <liblangtag/langtag.h>
20 : : #else
21 : : /* Replacement code for LGPL phobic and Android systems.
22 : : * For iOS we could probably use NSLocale instead, that should have more or
23 : : * less required functionality. If it is good enough, it could be used for Mac
24 : : * OS X, too.
25 : : */
26 : : #include "simple-langtag.cxx"
27 : : #endif
28 : :
29 : : using rtl::OUString;
30 : : using rtl::OString;
31 : : using rtl::OUStringBuffer;
32 : : using namespace com::sun::star;
33 : :
34 : : // The actual pointer type of mpImplLangtag that is declared void* to not
35 : : // pollute the entire code base with liblangtag.
36 : : #define LANGTAGCAST(p) (reinterpret_cast<lt_tag_t*>(p))
37 : : #define MPLANGTAG LANGTAGCAST(mpImplLangtag)
38 : :
39 : : /** Convention to signal presence of BCP 47 language tag in a Locale's Variant
40 : : field. The Locale's Language field then will contain this ISO 639-2
41 : : reserved for local use code. */
42 : : #define ISO639_LANGUAGE_TAG "qlt"
43 : :
44 : :
45 : : /** A reference holder for liblangtag data de/initialization, one static
46 : : instance. Currently implemented such that the first "ref" inits and dtor
47 : : (our library deinitialized) tears down.
48 : : */
49 : : class LiblantagDataRef
50 : : {
51 : : public:
52 : : LiblantagDataRef();
53 : : ~LiblantagDataRef();
54 : 40 : inline void incRef()
55 : : {
56 [ + + ][ + - ]: 40 : if (mnRef != SAL_MAX_UINT32 && !mnRef++)
[ + + ]
57 : 5 : setup();
58 : 40 : }
59 : 1875 : inline void decRef()
60 : : {
61 [ + + ][ + + ]: 1875 : if (mnRef != SAL_MAX_UINT32 && mnRef && !--mnRef)
[ + - ][ + + ]
62 : 5 : teardown();
63 : 1875 : }
64 : : void presetDataPath( const rtl::OUString& rPath );
65 : : private:
66 : : rtl::OString maDataPath; // path to liblangtag data, "|" if system
67 : : sal_uInt32 mnRef;
68 : :
69 : : void setupDataPath();
70 : : void setup();
71 : : void teardown();
72 : : };
73 : :
74 : 1835 : static LiblantagDataRef theDataRef;
75 : :
76 : 1835 : LiblantagDataRef::LiblantagDataRef()
77 : : :
78 : 1835 : mnRef(0)
79 : : {
80 : 1835 : }
81 : :
82 : 1835 : LiblantagDataRef::~LiblantagDataRef()
83 : : {
84 : : // When destructed we're tearing down unconditionally.
85 [ + + ]: 1835 : if (mnRef)
86 : 5 : mnRef = 1;
87 [ + - ]: 1835 : decRef();
88 : 1835 : }
89 : :
90 : 5 : void LiblantagDataRef::setup()
91 : : {
92 [ - + ]: 5 : if (maDataPath.isEmpty())
93 : 0 : setupDataPath();
94 : 5 : lt_db_initialize();
95 : : // Hold ref eternally.
96 : 5 : mnRef = SAL_MAX_UINT32;
97 : 5 : }
98 : :
99 : 5 : void LiblantagDataRef::teardown()
100 : : {
101 : 5 : lt_db_finalize();
102 : 5 : }
103 : :
104 : 5 : void LiblantagDataRef::presetDataPath( const rtl::OUString& rPath )
105 : : {
106 [ + - ]: 5 : if (maDataPath.isEmpty())
107 : : {
108 : 5 : maDataPath = OUStringToOString( rPath, RTL_TEXTENCODING_UTF8);
109 : 5 : lt_db_set_datadir( maDataPath.getStr());
110 : : }
111 : 5 : }
112 : :
113 : 0 : void LiblantagDataRef::setupDataPath()
114 : : {
115 : : // maDataPath is assumed to be empty here.
116 : 0 : OUString aURL;
117 [ # # ]: 0 : if (!rtl::Bootstrap::get( "BRAND_BASE_DIR", aURL))
118 : : OSL_FAIL( "LiblantagDataRef: can't get BRAND_BASE_DIR");
119 : : else
120 : : {
121 : : // Check if data is in our own installation, else assume system
122 : : // installation.
123 : 0 : aURL += "/share/liblangtag";
124 : 0 : OUString aData( aURL);
125 : 0 : aData += "/language-subtag-registry.xml";
126 : 0 : osl::DirectoryItem aDirItem;
127 [ # # ][ # # ]: 0 : if (osl::DirectoryItem::get( aData, aDirItem) == osl::DirectoryItem::E_None)
128 : : {
129 : 0 : OUString aPath;
130 [ # # ][ # # ]: 0 : if (osl::FileBase::getSystemPathFromFileURL( aURL, aPath) == osl::FileBase::E_None)
131 [ # # ]: 0 : maDataPath = OUStringToOString( aPath, RTL_TEXTENCODING_UTF8);
132 [ # # ]: 0 : }
133 : : }
134 [ # # ]: 0 : if (maDataPath.isEmpty())
135 : 0 : maDataPath = "|"; // assume system
136 : : else
137 [ # # ]: 0 : lt_db_set_datadir( maDataPath.getStr());
138 : 0 : }
139 : :
140 : :
141 : : // static
142 : 5 : void LanguageTag::overrideDataPath( const rtl::OUString& rPath )
143 : : {
144 : 5 : theDataRef.presetDataPath( rPath);
145 : 5 : }
146 : :
147 : :
148 : 30 : LanguageTag::LanguageTag( const rtl::OUString & rBcp47LanguageTag, bool bCanonicalize )
149 : : :
150 : : maBcp47( rBcp47LanguageTag),
151 : : mpImplLangtag( NULL),
152 : : mnLangID( LANGUAGE_DONTKNOW),
153 : : meIsValid( DECISION_DONTKNOW),
154 : : meIsIsoLocale( DECISION_DONTKNOW),
155 : : meIsIsoODF( DECISION_DONTKNOW),
156 : : mbInitializedBcp47( true),
157 : : mbInitializedLocale( false),
158 : : mbInitializedLangID( false),
159 : : mbCachedLanguage( false),
160 : : mbCachedScript( false),
161 : 30 : mbCachedCountry( false)
162 : : {
163 [ + - ]: 30 : theDataRef.incRef();
164 : :
165 [ + - ]: 30 : if (bCanonicalize)
166 [ + - ]: 30 : canonicalize();
167 : 30 : }
168 : :
169 : :
170 : 5 : LanguageTag::LanguageTag( const com::sun::star::lang::Locale & rLocale )
171 : : :
172 : : maLocale( rLocale),
173 : : mpImplLangtag( NULL),
174 : : mnLangID( LANGUAGE_DONTKNOW),
175 : : meIsValid( DECISION_DONTKNOW),
176 : : meIsIsoLocale( DECISION_DONTKNOW),
177 : : meIsIsoODF( DECISION_DONTKNOW),
178 : : mbInitializedBcp47( false),
179 : : mbInitializedLocale( true),
180 : : mbInitializedLangID( false),
181 : : mbCachedLanguage( false),
182 : : mbCachedScript( false),
183 : 5 : mbCachedCountry( false)
184 : : {
185 [ + - ]: 5 : theDataRef.incRef();
186 : 5 : }
187 : :
188 : :
189 : 5 : LanguageTag::LanguageTag( LanguageType nLanguage )
190 : : :
191 : : mpImplLangtag( NULL),
192 : : mnLangID( nLanguage),
193 : : meIsValid( DECISION_DONTKNOW),
194 : : meIsIsoLocale( DECISION_DONTKNOW),
195 : : meIsIsoODF( DECISION_DONTKNOW),
196 : : mbInitializedBcp47( false),
197 : : mbInitializedLocale( false),
198 : : mbInitializedLangID( true),
199 : : mbCachedLanguage( false),
200 : : mbCachedScript( false),
201 : 5 : mbCachedCountry( false)
202 : : {
203 [ + - ]: 5 : theDataRef.incRef();
204 : 5 : }
205 : :
206 : :
207 : 0 : LanguageTag::LanguageTag( const rtl::OUString& rLanguage, const rtl::OUString& rCountry )
208 : : :
209 : : maLocale( rLanguage, rCountry, ""),
210 : : mpImplLangtag( NULL),
211 : : mnLangID( LANGUAGE_DONTKNOW),
212 : : meIsValid( DECISION_DONTKNOW),
213 : : meIsIsoLocale( DECISION_DONTKNOW),
214 : : meIsIsoODF( DECISION_DONTKNOW),
215 : : mbInitializedBcp47( false),
216 : : mbInitializedLocale( true),
217 : : mbInitializedLangID( false),
218 : : mbCachedLanguage( false),
219 : : mbCachedScript( false),
220 : 0 : mbCachedCountry( false)
221 : : {
222 [ # # ]: 0 : theDataRef.incRef();
223 : 0 : }
224 : :
225 : :
226 : 0 : LanguageTag::LanguageTag( const LanguageTag & rLanguageTag )
227 : : :
228 : : maLocale( rLanguageTag.maLocale),
229 : : maBcp47( rLanguageTag.maBcp47),
230 : : maCachedLanguage( rLanguageTag.maCachedLanguage),
231 : : maCachedScript( rLanguageTag.maCachedScript),
232 : : maCachedCountry( rLanguageTag.maCachedCountry),
233 : : mpImplLangtag( rLanguageTag.mpImplLangtag ?
234 : 0 : lt_tag_copy( LANGTAGCAST( rLanguageTag.mpImplLangtag)) : NULL),
235 : : mnLangID( rLanguageTag.mnLangID),
236 : : meIsValid( rLanguageTag.meIsValid),
237 : : meIsIsoLocale( rLanguageTag.meIsIsoLocale),
238 : : meIsIsoODF( rLanguageTag.meIsIsoODF),
239 : : mbInitializedBcp47( rLanguageTag.mbInitializedBcp47),
240 : : mbInitializedLocale( rLanguageTag.mbInitializedLocale),
241 : : mbInitializedLangID( rLanguageTag.mbInitializedLangID),
242 : : mbCachedLanguage( rLanguageTag.mbCachedLanguage),
243 : : mbCachedScript( rLanguageTag.mbCachedScript),
244 [ # # ][ # # ]: 0 : mbCachedCountry( rLanguageTag.mbCachedCountry)
245 : : {
246 [ # # ]: 0 : theDataRef.incRef();
247 : 0 : }
248 : :
249 : :
250 : 0 : LanguageTag& LanguageTag::operator=( const LanguageTag & rLanguageTag )
251 : : {
252 : 0 : theDataRef.incRef();
253 : :
254 : 0 : maLocale = rLanguageTag.maLocale;
255 : 0 : maBcp47 = rLanguageTag.maBcp47;
256 : 0 : maCachedLanguage = rLanguageTag.maCachedLanguage;
257 : 0 : maCachedScript = rLanguageTag.maCachedScript;
258 : 0 : maCachedCountry = rLanguageTag.maCachedCountry;
259 : 0 : mpImplLangtag = rLanguageTag.mpImplLangtag;
260 : : mpImplLangtag = rLanguageTag.mpImplLangtag ?
261 [ # # ]: 0 : lt_tag_copy( LANGTAGCAST( rLanguageTag.mpImplLangtag)) : NULL;
262 : 0 : mnLangID = rLanguageTag.mnLangID;
263 : 0 : meIsValid = rLanguageTag.meIsValid;
264 : 0 : meIsIsoLocale = rLanguageTag.meIsIsoLocale;
265 : 0 : meIsIsoODF = rLanguageTag.meIsIsoODF;
266 : 0 : mbInitializedBcp47 = rLanguageTag.mbInitializedBcp47;
267 : 0 : mbInitializedLocale = rLanguageTag.mbInitializedLocale;
268 : 0 : mbInitializedLangID = rLanguageTag.mbInitializedLangID;
269 : 0 : mbCachedLanguage = rLanguageTag.mbCachedLanguage;
270 : 0 : mbCachedScript = rLanguageTag.mbCachedScript;
271 : 0 : mbCachedCountry = rLanguageTag.mbCachedCountry;
272 : 0 : return *this;
273 : : }
274 : :
275 : :
276 : 40 : LanguageTag::~LanguageTag()
277 : : {
278 [ + - ]: 40 : lt_tag_unref( MPLANGTAG);
279 : :
280 [ + - ]: 40 : theDataRef.decRef();
281 : 40 : }
282 : :
283 : :
284 : 30 : bool LanguageTag::canonicalize() const
285 : : {
286 : : #ifdef erDEBUG
287 : : // dump once
288 : : struct dumper
289 : : {
290 : : void** mpp;
291 : : dumper( void** pp ) : mpp( *pp ? NULL : pp) {}
292 : : ~dumper() { if (mpp && *mpp) lt_tag_dump( LANGTAGCAST( *mpp)); }
293 : : };
294 : : dumper aDumper( &mpImplLangtag);
295 : : #endif
296 : :
297 : : // g_error_free() mocks about NULL, so ...
298 : : struct myerror
299 : : {
300 : : GError* p;
301 : 30 : myerror() : p(NULL) {}
302 [ + + ]: 30 : ~myerror() { if (p) g_error_free( p); }
303 : 30 : } aError;
304 : :
305 [ + - ]: 30 : getBcp47(); // side effect: have maBcp47 in any case
306 : : // Checking empty for system locale before having allocated mpImplLangtag
307 : : // may result in multiple calls of this method because that serves as flag
308 : : // whether this was canonicalized, but that's better than allocating
309 : : // lt_tag_t for all those system locales.
310 [ - + ]: 30 : if (maBcp47.isEmpty())
311 : : {
312 : 0 : meIsValid = DECISION_YES;
313 : 0 : return true;
314 : : }
315 [ + - ]: 30 : if (!mpImplLangtag)
316 [ + - ]: 30 : mpImplLangtag = lt_tag_new();
317 [ + - ][ + - ]: 30 : if (lt_tag_parse( MPLANGTAG, OUStringToOString( maBcp47, RTL_TEXTENCODING_UTF8).getStr(), &aError.p))
[ + + ]
318 : : {
319 [ + - ]: 25 : gchar* pTag = lt_tag_canonicalize( MPLANGTAG, &aError.p);
320 : : SAL_WARN_IF( !pTag || aError.p, "i18npool.langtag", "LanguageTag::canonicalize: could not canonicalize, " <<
321 : : (aError.p ? aError.p->message : ""));
322 [ + - ]: 25 : if (pTag)
323 : : {
324 : 25 : OUString aOld( maBcp47);
325 : 25 : maBcp47 = OUString::createFromAscii( pTag);
326 : : // Make the lt_tag_t follow the new string if different, which
327 : : // removes default script and such.
328 [ + + ]: 25 : if (maBcp47 != aOld)
329 : : {
330 [ + - ][ - + ]: 10 : if (!lt_tag_parse( MPLANGTAG, pTag, &aError.p))
331 : : {
332 : : SAL_WARN( "i18npool.langtag", "LanguageTag::canonicalize: could not reparse, " <<
333 : : (aError.p ? aError.p->message : ""));
334 [ # # ]: 0 : g_free( pTag);
335 : 0 : meIsValid = DECISION_NO;
336 : 0 : return false;
337 : : }
338 : : }
339 [ + - ]: 25 : g_free( pTag);
340 : 25 : meIsValid = DECISION_YES;
341 : 25 : return true;
342 : : }
343 : : }
344 : : else
345 : : {
346 : : SAL_WARN( "i18npool.langtag", "LanguageTag::canonicalize: could not parse, " <<
347 : : (aError.p ? aError.p->message : ""));
348 : : }
349 : 5 : meIsValid = DECISION_NO;
350 [ + - ]: 30 : return false;
351 : : }
352 : :
353 : :
354 : 10 : void LanguageTag::convertLocaleToBcp47()
355 : : {
356 [ - + ]: 10 : if (maLocale.Language.isEmpty())
357 : : {
358 : : // Special case system locale.
359 : 0 : maBcp47 = OUString();
360 : 0 : meIsIsoLocale = DECISION_YES;
361 : : }
362 [ - + ]: 10 : else if (maLocale.Language == ISO639_LANGUAGE_TAG)
363 : : {
364 : 0 : maBcp47 = maLocale.Variant;
365 : 0 : meIsIsoLocale = DECISION_NO;
366 : : }
367 : : else
368 : : {
369 : : /* XXX NOTE: most legacy code never evaluated the Variant field, so for
370 : : * now just concatenate language and country. In case we stumbled over
371 : : * variant aware code we'd have to take care of that. */
372 [ - + ]: 10 : if (maLocale.Country.isEmpty())
373 : 0 : maBcp47 = maLocale.Language;
374 : : else
375 : : {
376 : 10 : OUStringBuffer aBuf( maLocale.Language.getLength() + 1 + maLocale.Country.getLength());
377 [ + - ][ + - ]: 10 : aBuf.append( maLocale.Language).append( '-').append( maLocale.Country);
[ + - ]
378 [ + - ]: 10 : maBcp47 = aBuf.makeStringAndClear();
379 : : }
380 : : }
381 : 10 : mbInitializedBcp47 = true;
382 : 10 : }
383 : :
384 : :
385 : 35 : void LanguageTag::convertLocaleToLang()
386 : : {
387 : : /* FIXME: this is temporary until code base is converted to not use
388 : : * MsLangId::convert...() anymore. After that, proper new method has to be
389 : : * implemented to allow ISO639_LANGUAGE_TAG and sript tag and such. */
390 : 35 : mnLangID = MsLangId::convertLocaleToLanguage( maLocale);
391 : 35 : mbInitializedLangID = true;
392 : 35 : }
393 : :
394 : :
395 : 30 : void LanguageTag::convertBcp47ToLocale()
396 : : {
397 [ - + ]: 30 : if (maBcp47.isEmpty())
398 : : {
399 : : // Special case system locale.
400 : 0 : maLocale = lang::Locale();
401 : 0 : meIsIsoLocale = DECISION_YES;
402 : : }
403 : : else
404 : : {
405 : 30 : bool bIso = isIsoLocale();
406 [ + + ]: 30 : if (bIso)
407 : : {
408 : 20 : maLocale.Language = getLanguageFromLangtag();
409 : 20 : maLocale.Country = getRegionFromLangtag();
410 : 20 : maLocale.Variant = OUString();
411 : : }
412 : : else
413 : : {
414 : 10 : maLocale.Language = ISO639_LANGUAGE_TAG;
415 : 10 : maLocale.Country = getCountry();
416 : 10 : maLocale.Variant = maBcp47;
417 : : }
418 : : }
419 : 30 : mbInitializedLocale = true;
420 : 30 : }
421 : :
422 : :
423 : 35 : void LanguageTag::convertBcp47ToLang()
424 : : {
425 : : /* FIXME: this is temporary. If we support locales that consist not only of
426 : : * language and country, e.g. added script, this probably needs to be
427 : : * adapted. */
428 [ - + ]: 35 : if (!mbInitializedLocale)
429 : 0 : convertBcp47ToLocale();
430 : 35 : convertLocaleToLang();
431 : 35 : mbInitializedLangID = true;
432 : 35 : }
433 : :
434 : :
435 : 5 : void LanguageTag::convertLangToLocale()
436 : : {
437 : : /* FIXME: this is temporary until code base is converted to not use
438 : : * MsLangId::convert...() anymore. After that, proper new method has to be
439 : : * implemented to allow ISO639_LANGUAGE_TAG and script tag and such. */
440 : : // Do not resolve system here!
441 : 5 : maLocale = MsLangId::convertLanguageToLocale( mnLangID, false);
442 : 5 : mbInitializedLocale = true;
443 : 5 : }
444 : :
445 : :
446 : 0 : void LanguageTag::convertLangToBcp47()
447 : : {
448 : : /* FIXME: this is temporary. If we support locales that consist not only of
449 : : * language and country, e.g. added script, this probably needs to be
450 : : * adapted. */
451 [ # # ]: 0 : if (!mbInitializedLocale)
452 : 0 : convertLangToLocale();
453 : 0 : convertLocaleToBcp47();
454 : 0 : mbInitializedBcp47 = true;
455 : 0 : }
456 : :
457 : :
458 : 70 : rtl::OUString LanguageTag::getBcp47() const
459 : : {
460 [ + + ]: 70 : if (!mbInitializedBcp47)
461 : : {
462 [ + - ]: 10 : if (mbInitializedLocale)
463 : 10 : const_cast<LanguageTag*>(this)->convertLocaleToBcp47();
464 : : else
465 : 0 : const_cast<LanguageTag*>(this)->convertLangToBcp47();
466 : : }
467 : 70 : return maBcp47;
468 : : }
469 : :
470 : :
471 : 45 : rtl::OUString LanguageTag::getLanguageFromLangtag() const
472 : : {
473 : 45 : rtl::OUString aLanguage;
474 [ - + ]: 45 : if (!mpImplLangtag)
475 [ # # ]: 0 : canonicalize();
476 [ - + ]: 45 : if (maBcp47.isEmpty())
477 : 0 : return aLanguage;
478 [ + - ]: 45 : const lt_lang_t* pLangT = lt_tag_get_language( MPLANGTAG);
479 : : SAL_WARN_IF( !pLangT, "i18npool.langtag", "LanguageTag::getLanguageFromLangtag: pLangT==NULL");
480 [ - + ]: 45 : if (!pLangT)
481 : 0 : return aLanguage;
482 [ + - ]: 45 : const gchar* pLang = lt_lang_get_tag( pLangT);
483 : : SAL_WARN_IF( !pLang, "i18npool.langtag", "LanguageTag::getLanguageFromLangtag: pLang==NULL");
484 [ + - ]: 45 : if (pLang)
485 : 45 : aLanguage = OUString::createFromAscii( pLang);
486 : 45 : return aLanguage;
487 : : }
488 : :
489 : :
490 : 25 : rtl::OUString LanguageTag::getScriptFromLangtag() const
491 : : {
492 : 25 : rtl::OUString aScript;
493 [ - + ]: 25 : if (!mpImplLangtag)
494 [ # # ]: 0 : canonicalize();
495 [ - + ]: 25 : if (maBcp47.isEmpty())
496 : 0 : return aScript;
497 [ + - ]: 25 : const lt_script_t* pScriptT = lt_tag_get_script( MPLANGTAG);
498 : : // pScriptT==NULL is valid for default scripts
499 [ + + ]: 25 : if (!pScriptT)
500 : 20 : return aScript;
501 [ + - ]: 5 : const gchar* pScript = lt_script_get_tag( pScriptT);
502 : : SAL_WARN_IF( !pScript, "i18npool.langtag", "LanguageTag::getScriptFromLangtag: pScript==NULL");
503 [ + - ]: 5 : if (pScript)
504 : 5 : aScript = OUString::createFromAscii( pScript);
505 : 25 : return aScript;
506 : : }
507 : :
508 : :
509 : 55 : rtl::OUString LanguageTag::getRegionFromLangtag() const
510 : : {
511 : 55 : rtl::OUString aRegion;
512 [ - + ]: 55 : if (!mpImplLangtag)
513 [ # # ]: 0 : canonicalize();
514 [ - + ]: 55 : if (maBcp47.isEmpty())
515 : 0 : return aRegion;
516 [ + - ]: 55 : const lt_region_t* pRegionT = lt_tag_get_region( MPLANGTAG);
517 : : SAL_WARN_IF( !pRegionT, "i18npool.langtag", "LanguageTag::getRegionFromLangtag: pRegionT==NULL");
518 [ + + ]: 55 : if (!pRegionT)
519 : 15 : return aRegion;
520 [ + - ]: 40 : const gchar* pRegion = lt_region_get_tag( pRegionT);
521 : : SAL_WARN_IF( !pRegion, "i18npool.langtag", "LanguageTag::getRegionFromLangtag: pRegion==NULL");
522 [ + - ]: 40 : if (pRegion)
523 : 40 : aRegion = OUString::createFromAscii( pRegion);
524 : 55 : return aRegion;
525 : : }
526 : :
527 : :
528 : 40 : com::sun::star::lang::Locale LanguageTag::getLocale() const
529 : : {
530 [ + + ]: 40 : if (!mbInitializedLocale)
531 : : {
532 [ + + ]: 35 : if (mbInitializedBcp47)
533 : 30 : const_cast<LanguageTag*>(this)->convertBcp47ToLocale();
534 : : else
535 : 5 : const_cast<LanguageTag*>(this)->convertLangToLocale();
536 : : }
537 : 40 : return maLocale;
538 : : }
539 : :
540 : :
541 : 40 : LanguageType LanguageTag::getLanguageType() const
542 : : {
543 [ + + ]: 40 : if (!mbInitializedLangID)
544 : : {
545 [ + - ]: 35 : if (mbInitializedBcp47)
546 : 35 : const_cast<LanguageTag*>(this)->convertBcp47ToLang();
547 : : else
548 : 0 : const_cast<LanguageTag*>(this)->convertLocaleToLang();
549 : : }
550 : 40 : return mnLangID;
551 : : }
552 : :
553 : :
554 : : namespace
555 : : {
556 : :
557 : 85 : bool isLowerAscii( sal_Unicode c )
558 : : {
559 [ + - ][ + - ]: 85 : return 'a' <= c && c <= 'z';
560 : : }
561 : :
562 : 60 : bool isUpperAscii( sal_Unicode c )
563 : : {
564 [ + - ][ + - ]: 60 : return 'A' <= c && c <= 'Z';
565 : : }
566 : :
567 : : }
568 : :
569 : :
570 : : // static
571 : 25 : bool LanguageTag::isIsoLanguage( const rtl::OUString& rLanguage )
572 : : {
573 : : /* TODO: ignore case? For now let's see where rubbish is used. */
574 : : bool b2chars;
575 [ + + ][ + - : 80 : if (((b2chars = (rLanguage.getLength() == 2)) || rLanguage.getLength() == 3) &&
+ - + - ]
[ + + + - ]
[ + - ]
576 : 50 : isLowerAscii( rLanguage[0]) && isLowerAscii( rLanguage[1]) &&
577 : 5 : (b2chars || isLowerAscii( rLanguage[2])))
578 : 25 : return true;
579 : : SAL_WARN_IF( ((rLanguage.getLength() == 2 || rLanguage.getLength() == 3) &&
580 : : (isUpperAscii( rLanguage[0]) || isUpperAscii( rLanguage[1]))) ||
581 : : (rLanguage.getLength() == 3 && isUpperAscii( rLanguage[2])), "i18npool.langtag",
582 : : "LanguageTag::isIsoLanguage: rejecting upper case");
583 : 25 : return false;
584 : : }
585 : :
586 : :
587 : : // static
588 : 35 : bool LanguageTag::isIsoCountry( const rtl::OUString& rRegion )
589 : : {
590 : : /* TODO: ignore case? For now let's see where rubbish is used. */
591 [ + + + - : 110 : if (rRegion.isEmpty() ||
+ - + - ]
[ + - ]
592 : 75 : (rRegion.getLength() == 2 && isUpperAscii( rRegion[0]) && isUpperAscii( rRegion[1])))
593 : 35 : return true;
594 : : SAL_WARN_IF( rRegion.getLength() == 2 && (isLowerAscii( rRegion[0]) || isLowerAscii( rRegion[1])),
595 : : "i18npool.langtag", "LanguageTag::isIsoCountry: rejecting lower case");
596 : 35 : return false;
597 : : }
598 : :
599 : :
600 : : // static
601 : 30 : bool LanguageTag::isIsoScript( const rtl::OUString& rScript )
602 : : {
603 : : /* TODO: ignore case? For now let's see where rubbish is used. */
604 [ + + + - : 80 : if (rScript.isEmpty() ||
+ - + - +
- + - ]
[ + - ]
605 : 10 : (rScript.getLength() == 4 &&
606 : 20 : isUpperAscii( rScript[0]) && isLowerAscii( rScript[1]) &&
607 : 20 : isLowerAscii( rScript[2]) && isLowerAscii( rScript[3])))
608 : 30 : return true;
609 : : SAL_WARN_IF( rScript.getLength() == 4 &&
610 : : (isLowerAscii( rScript[0]) || isUpperAscii( rScript[1]) ||
611 : : isUpperAscii( rScript[2]) || isUpperAscii( rScript[3])),
612 : : "i18npool.langtag", "LanguageTag::isIsoScript: rejecting case mismatch");
613 : 30 : return false;
614 : : }
615 : :
616 : :
617 : 25 : rtl::OUString LanguageTag::getLanguage() const
618 : : {
619 [ + - ]: 25 : if (!mbCachedLanguage)
620 : : {
621 : 25 : maCachedLanguage = getLanguageFromLangtag();
622 : 25 : mbCachedLanguage = true;
623 : : }
624 : 25 : return maCachedLanguage;
625 : : }
626 : :
627 : :
628 : 30 : rtl::OUString LanguageTag::getScript() const
629 : : {
630 [ + + ]: 30 : if (!mbCachedScript)
631 : : {
632 : 25 : maCachedScript = getScriptFromLangtag();
633 : 25 : mbCachedScript = true;
634 : : }
635 : 30 : return maCachedScript;
636 : : }
637 : :
638 : :
639 : 0 : rtl::OUString LanguageTag::getLanguageAndScript() const
640 : : {
641 [ # # ]: 0 : OUString aLanguageScript( getLanguage());
642 [ # # ]: 0 : OUString aScript( getScript());
643 [ # # ]: 0 : if (!aScript.isEmpty())
644 : : {
645 : 0 : OUStringBuffer aBuf( aLanguageScript.getLength() + 1 + aScript.getLength());
646 [ # # ][ # # ]: 0 : aBuf.append( aLanguageScript).append( '-').append( aScript);
[ # # ]
647 [ # # ]: 0 : aLanguageScript = aBuf.makeStringAndClear();
648 : : }
649 : 0 : return aLanguageScript;
650 : : }
651 : :
652 : :
653 : 10 : rtl::OUString LanguageTag::getCountry() const
654 : : {
655 [ + - ]: 10 : if (!mbCachedCountry)
656 : : {
657 : 10 : maCachedCountry = getRegionFromLangtag();
658 [ - + ]: 10 : if (!isIsoCountry( maCachedCountry))
659 : 0 : maCachedCountry = OUString();
660 : 10 : mbCachedCountry = true;
661 : : }
662 : 10 : return maCachedCountry;
663 : : }
664 : :
665 : :
666 : 25 : rtl::OUString LanguageTag::getRegion() const
667 : : {
668 : 25 : return getRegionFromLangtag();
669 : : }
670 : :
671 : :
672 : 80 : bool LanguageTag::isIsoLocale() const
673 : : {
674 [ + + ]: 80 : if (meIsIsoLocale == DECISION_DONTKNOW)
675 : : {
676 [ - + ]: 30 : if (!mpImplLangtag)
677 : 0 : canonicalize();
678 : : // It must be at most ll-CC or lll-CC
679 : : // Do not use getCountry() here, use getRegion() instead.
680 : 30 : meIsIsoLocale = ((maBcp47.isEmpty() ||
681 [ + - ][ + - ]: 100 : (maBcp47.getLength() <= 6 && isIsoLanguage( getLanguage()) && isIsoCountry( getRegion()))) ?
[ + - ][ + - ]
[ + + ][ + + ]
[ # # # # ]
682 [ + - ][ + - ]: 100 : DECISION_YES : DECISION_NO);
[ + - + + ]
683 : : }
684 : 80 : return meIsIsoLocale == DECISION_YES;
685 : : }
686 : :
687 : :
688 : 25 : bool LanguageTag::isIsoODF() const
689 : : {
690 [ + - ]: 25 : if (meIsIsoODF == DECISION_DONTKNOW)
691 : : {
692 [ - + ]: 25 : if (!mpImplLangtag)
693 : 0 : canonicalize();
694 [ + - ][ - + ]: 25 : if (!isIsoScript( getScript()))
695 : 0 : return ((meIsIsoODF = DECISION_NO) == DECISION_YES);
696 : : // The usual case is lll-CC so simply check that first.
697 [ + + ]: 25 : if (isIsoLocale())
698 : 15 : return ((meIsIsoODF = DECISION_YES) == DECISION_YES);
699 : : // If this is not ISO locale for which script must not exist it can
700 : : // still be ISO locale plus ISO script lll-Ssss-CC
701 : 10 : meIsIsoODF = ((maBcp47.getLength() <= 11 &&
702 [ + - ][ + - ]: 25 : isIsoLanguage( getLanguage()) && isIsoCountry( getRegion()) && isIsoScript( getScript())) ?
[ + - ][ + - ]
[ + - ][ + - ]
[ + + ][ + + ]
[ + + ][ # #
# # # # ]
703 [ + - ][ + - ]: 25 : DECISION_YES : DECISION_NO);
[ + - ][ + + ]
704 : : }
705 : 25 : return meIsIsoODF == DECISION_YES;
706 : : }
707 : :
708 : :
709 : 25 : bool LanguageTag::isValidBcp47() const
710 : : {
711 [ - + ]: 25 : if (meIsValid == DECISION_DONTKNOW)
712 : : {
713 [ # # ]: 0 : if (!mpImplLangtag)
714 : 0 : canonicalize();
715 : : SAL_WARN_IF( meIsValid == DECISION_DONTKNOW, "i18npool.langtag",
716 : : "LanguageTag::isValidBcp47: canonicalize() doesn't set meIsValid");
717 : : }
718 : 25 : return meIsValid == DECISION_YES;
719 [ + - ][ + - ]: 5505 : }
720 : :
721 : :
722 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|