Line data Source code
1 : /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 : /*
3 : * lt-tag.c
4 : * Copyright (C) 2011-2012 Akira TAGOH
5 : *
6 : * Authors:
7 : * Akira TAGOH <akira@tagoh.org>
8 : *
9 : * You may distribute under the terms of either the GNU
10 : * Lesser General Public License or the Mozilla Public
11 : * License, as specified in the README file.
12 : */
13 : #ifdef HAVE_CONFIG_H
14 : #include "config.h"
15 : #endif
16 :
17 : #include <ctype.h>
18 : #ifndef _WIN32
19 : #include <langinfo.h>
20 : #endif
21 : #include <locale.h>
22 : #ifndef HAVE_STDINT_H
23 : typedef int int32_t;
24 : #else
25 : #include <stdint.h>
26 : #endif
27 : #include <string.h>
28 : #include <libxml/xpath.h>
29 : #include "lt-database.h"
30 : #include "lt-error.h"
31 : #include "lt-ext-module-private.h"
32 : #include "lt-extension-private.h"
33 : #include "lt-localealias.h"
34 : #include "lt-mem.h"
35 : #include "lt-messages.h"
36 : #include "lt-string.h"
37 : #include "lt-utils.h"
38 : #include "lt-xml.h"
39 : #include "lt-tag.h"
40 : #include "lt-tag-private.h"
41 :
42 :
43 : /**
44 : * SECTION: lt-tag
45 : * @Short_Description: A container class for Language tag
46 : * @Title: Container - Tag
47 : *
48 : * This container class provides an interface to deal with the language tag.
49 : */
50 : typedef struct _lt_tag_scanner_t {
51 : lt_mem_t parent;
52 : char *string;
53 : size_t length;
54 : size_t position;
55 : } lt_tag_scanner_t;
56 :
57 : struct _lt_tag_t {
58 : lt_mem_t parent;
59 : int32_t wildcard_map;
60 : lt_tag_state_t state;
61 : lt_string_t *tag_string;
62 : lt_lang_t *language;
63 : lt_extlang_t *extlang;
64 : lt_script_t *script;
65 : lt_region_t *region;
66 : lt_list_t *variants;
67 : lt_extension_t *extension;
68 : lt_string_t *privateuse;
69 : lt_grandfathered_t *grandfathered;
70 : };
71 :
72 : /*< private >*/
73 : static lt_bool_t
74 0 : _lt_tag_string_compare(const lt_string_t *v1,
75 : const lt_string_t *v2)
76 : {
77 0 : lt_bool_t retval = FALSE;
78 : char *s1, *s2;
79 :
80 0 : if (v1 == v2)
81 0 : return TRUE;
82 :
83 0 : s1 = v1 ? lt_strlower(strdup(lt_string_value(v1))) : NULL;
84 0 : s2 = v2 ? lt_strlower(strdup(lt_string_value(v2))) : NULL;
85 :
86 0 : if (lt_strcmp0(s1, "*") == 0 ||
87 0 : lt_strcmp0(s2, "*") == 0) {
88 0 : retval = TRUE;
89 0 : goto bail;
90 : }
91 :
92 0 : retval = lt_strcmp0(s1, s2) == 0;
93 : bail:
94 0 : free(s1);
95 0 : free(s2);
96 :
97 0 : return retval;
98 : }
99 :
100 : static lt_tag_scanner_t *
101 22 : lt_tag_scanner_new(const char *tag)
102 : {
103 22 : lt_tag_scanner_t *retval = lt_mem_alloc_object(sizeof (lt_tag_scanner_t));
104 :
105 22 : if (retval) {
106 22 : retval->string = strdup(tag);
107 22 : lt_mem_add_ref(&retval->parent, retval->string, free);
108 22 : retval->length = strlen(tag);
109 : }
110 :
111 22 : return retval;
112 : }
113 :
114 : static void
115 24 : lt_tag_scanner_unref(lt_tag_scanner_t *scanner)
116 : {
117 24 : if (scanner)
118 22 : lt_mem_unref(&scanner->parent);
119 24 : }
120 :
121 : static lt_bool_t
122 54 : lt_tag_scanner_get_token(lt_tag_scanner_t *scanner,
123 : char **retval,
124 : size_t *length,
125 : lt_error_t **error)
126 : {
127 54 : lt_string_t *string = NULL;
128 : char c;
129 54 : lt_error_t *err = NULL;
130 :
131 54 : lt_return_val_if_fail (scanner != NULL, FALSE);
132 :
133 54 : if (scanner->position >= scanner->length) {
134 0 : lt_error_set(&err, LT_ERR_EOT,
135 : "No more tokens in buffer");
136 0 : goto bail;
137 : }
138 :
139 54 : string = lt_string_new(NULL);
140 168 : while (scanner->position < scanner->length) {
141 114 : c = scanner->string[scanner->position++];
142 114 : if (c == 0) {
143 0 : if (lt_string_length(string) == 0) {
144 0 : lt_error_set(&err, LT_ERR_EOT,
145 : "No more tokens in buffer");
146 : }
147 0 : scanner->position--;
148 0 : break;
149 : }
150 114 : if (c == '*') {
151 0 : if (lt_string_length(string) > 0) {
152 0 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
153 : "Invalid wildcard: positon = %zd",
154 0 : scanner->position - 1);
155 0 : break;
156 : }
157 114 : } else if (!isalnum(c) && c != '-' && c != 0) {
158 0 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
159 : "Invalid character for tag: '%c'", c);
160 0 : break;
161 : }
162 114 : lt_string_append_c(string, c);
163 :
164 114 : if (c == '-' ||
165 : c == '*')
166 : break;
167 160 : if (scanner->string[scanner->position] == '-' ||
168 68 : scanner->string[scanner->position] == 0)
169 : break;
170 : }
171 : bail:
172 54 : if (lt_error_is_set(err, LT_ERR_ANY)) {
173 0 : if (error)
174 0 : *error = lt_error_ref(err);
175 : else
176 0 : lt_error_print(err, LT_ERR_ANY);
177 0 : lt_error_unref(err);
178 0 : lt_string_unref(string);
179 0 : *retval = NULL;
180 0 : *length = 0;
181 :
182 0 : return FALSE;
183 : }
184 :
185 54 : *length = lt_string_length(string);
186 54 : *retval = lt_string_free(string, FALSE);
187 :
188 54 : return TRUE;
189 : }
190 :
191 : static lt_bool_t
192 74 : lt_tag_scanner_is_eof(lt_tag_scanner_t *scanner)
193 : {
194 74 : lt_return_val_if_fail (scanner != NULL, TRUE);
195 74 : lt_return_val_if_fail (scanner->position <= scanner->length, TRUE);
196 :
197 128 : return scanner->string[scanner->position] == 0 ||
198 54 : scanner->position >= scanner->length;
199 : }
200 :
201 : static int
202 0 : _lt_tag_variant_compare(const lt_pointer_t a,
203 : const lt_pointer_t b)
204 : {
205 0 : return (unsigned long)a - (unsigned long)b;
206 : }
207 :
208 : #define DEFUNC_TAG_FREE(__func__) \
209 : LT_INLINE_FUNC void \
210 : lt_tag_free_ ##__func__ (lt_tag_t *tag) \
211 : { \
212 : if (tag->__func__) { \
213 : lt_mem_delete_ref(&tag->parent, tag->__func__); \
214 : tag->__func__ = NULL; \
215 : } \
216 : }
217 :
218 30 : DEFUNC_TAG_FREE (language)
219 24 : DEFUNC_TAG_FREE (extlang)
220 34 : DEFUNC_TAG_FREE (script)
221 38 : DEFUNC_TAG_FREE (region)
222 24 : DEFUNC_TAG_FREE (variants)
223 24 : DEFUNC_TAG_FREE (extension)
224 48 : DEFUNC_TAG_FREE (grandfathered)
225 32 : DEFUNC_TAG_FREE (tag_string)
226 :
227 : #undef DEFUNC_TAG_FREE
228 :
229 : #define DEFUNC_TAG_SET(__func__, __unref_func__) \
230 : LT_INLINE_FUNC void \
231 : lt_tag_set_ ##__func__ (lt_tag_t *tag, lt_pointer_t p) \
232 : { \
233 : lt_tag_free_ ##__func__ (tag); \
234 : if (p) { \
235 : tag->__func__ = p; \
236 : lt_mem_add_ref(&tag->parent, tag->__func__, \
237 : (lt_destroy_func_t)__unref_func__); \
238 : } \
239 : }
240 :
241 4 : DEFUNC_TAG_SET (language, lt_lang_unref)
242 0 : DEFUNC_TAG_SET (extlang, lt_extlang_unref)
243 8 : DEFUNC_TAG_SET (script, lt_script_unref)
244 10 : DEFUNC_TAG_SET (region, lt_region_unref)
245 0 : DEFUNC_TAG_SET (extension, lt_extension_unref)
246 24 : DEFUNC_TAG_SET (grandfathered, lt_grandfathered_unref)
247 :
248 : LT_INLINE_FUNC void
249 0 : lt_tag_set_variant(lt_tag_t *tag,
250 : lt_pointer_t p)
251 : {
252 0 : lt_bool_t no_variants = (tag->variants == NULL);
253 :
254 0 : if (p) {
255 0 : tag->variants = lt_list_append(tag->variants, p, (lt_destroy_func_t)lt_variant_unref);
256 0 : if (no_variants)
257 0 : lt_mem_add_ref(&tag->parent, tag->variants, lt_list_free);
258 : } else {
259 0 : lt_warn_if_reached();
260 : }
261 0 : }
262 :
263 : #undef DEFUNC_TAG_SET
264 :
265 : LT_INLINE_FUNC void
266 46 : lt_tag_add_tag_string(lt_tag_t *tag,
267 : const char *s)
268 : {
269 46 : if (!tag->tag_string) {
270 34 : tag->tag_string = lt_string_new(NULL);
271 34 : lt_mem_add_ref(&tag->parent, tag->tag_string,
272 : (lt_destroy_func_t)lt_string_unref);
273 : }
274 46 : if (s) {
275 46 : if (lt_string_length(tag->tag_string) > 0)
276 12 : lt_string_append_c(tag->tag_string, '-');
277 46 : lt_string_append(tag->tag_string, s);
278 : } else {
279 0 : lt_warn_if_reached();
280 : }
281 46 : }
282 :
283 : static const char *
284 0 : lt_tag_get_locale_from_locale_alias(const char *alias)
285 : {
286 : int i;
287 :
288 0 : lt_return_val_if_fail (alias != NULL, NULL);
289 :
290 0 : for (i = 0; __lt_localealias_tables[i].alias != NULL; i++) {
291 0 : if (lt_strcasecmp(alias, __lt_localealias_tables[i].alias) == 0)
292 0 : return __lt_localealias_tables[i].locale;
293 : }
294 :
295 0 : return NULL;
296 : }
297 :
298 : static void
299 0 : lt_tag_fill_wildcard(lt_tag_t *tag,
300 : lt_tag_state_t begin,
301 : lt_tag_state_t end)
302 : {
303 : lt_tag_state_t i;
304 : lt_lang_db_t *langdb;
305 : lt_extlang_db_t *extlangdb;
306 : lt_script_db_t *scriptdb;
307 : lt_region_db_t *regiondb;
308 : lt_variant_db_t *variantdb;
309 : lt_extension_t *e;
310 :
311 0 : for (i = begin; i < end; i++) {
312 0 : tag->wildcard_map |= (1 << (i - 1));
313 0 : switch (i) {
314 : case STATE_LANG:
315 0 : langdb = lt_db_get_lang();
316 0 : lt_tag_set_language(tag, lt_lang_db_lookup(langdb, "*"));
317 0 : lt_lang_db_unref(langdb);
318 0 : break;
319 : case STATE_EXTLANG:
320 0 : extlangdb = lt_db_get_extlang();
321 0 : lt_tag_set_extlang(tag, lt_extlang_db_lookup(extlangdb, "*"));
322 0 : lt_extlang_db_unref(extlangdb);
323 0 : break;
324 : case STATE_SCRIPT:
325 0 : scriptdb = lt_db_get_script();
326 0 : lt_tag_set_script(tag, lt_script_db_lookup(scriptdb, "*"));
327 0 : lt_script_db_unref(scriptdb);
328 0 : break;
329 : case STATE_REGION:
330 0 : regiondb = lt_db_get_region();
331 0 : lt_tag_set_region(tag, lt_region_db_lookup(regiondb, "*"));
332 0 : lt_region_db_unref(regiondb);
333 0 : break;
334 : case STATE_VARIANT:
335 0 : variantdb = lt_db_get_variant();
336 0 : lt_tag_set_variant(tag, lt_variant_db_lookup(variantdb, "*"));
337 0 : lt_variant_db_unref(variantdb);
338 0 : break;
339 : case STATE_EXTENSION:
340 0 : e = lt_extension_create();
341 0 : lt_extension_add_singleton(e, '*', NULL, NULL);
342 0 : lt_tag_set_extension(tag, e);
343 0 : break;
344 : case STATE_PRIVATEUSE:
345 0 : lt_string_clear(tag->privateuse);
346 0 : lt_string_append(tag->privateuse, "*");
347 0 : break;
348 : default:
349 0 : break;
350 : }
351 : }
352 0 : }
353 :
354 : static void
355 24 : lt_tag_parser_init(lt_tag_t *tag)
356 : {
357 24 : lt_tag_clear(tag);
358 24 : tag->state = STATE_NONE;
359 24 : }
360 :
361 : static lt_bool_t
362 54 : lt_tag_parse_prestate(lt_tag_t *tag,
363 : const char *token,
364 : size_t length,
365 : lt_error_t **error)
366 : {
367 54 : lt_bool_t retval = TRUE;
368 :
369 54 : if (lt_strcmp0(token, "-") == 0) {
370 22 : switch (tag->state) {
371 : case STATE_PRE_EXTLANG:
372 18 : tag->state = STATE_EXTLANG;
373 18 : break;
374 : case STATE_PRE_SCRIPT:
375 0 : tag->state = STATE_SCRIPT;
376 0 : break;
377 : case STATE_PRE_REGION:
378 4 : tag->state = STATE_REGION;
379 4 : break;
380 : case STATE_PRE_VARIANT:
381 0 : tag->state = STATE_VARIANT;
382 0 : break;
383 : case STATE_PRE_EXTENSION:
384 0 : tag->state = STATE_EXTENSION;
385 0 : break;
386 : case STATE_IN_EXTENSION:
387 0 : tag->state = STATE_EXTENSIONTOKEN;
388 0 : break;
389 : case STATE_IN_EXTENSIONTOKEN:
390 0 : tag->state = STATE_EXTENSIONTOKEN2;
391 0 : break;
392 : case STATE_PRE_PRIVATEUSE:
393 0 : tag->state = STATE_PRIVATEUSE;
394 0 : break;
395 : case STATE_IN_PRIVATEUSE:
396 0 : tag->state = STATE_PRIVATEUSETOKEN;
397 0 : break;
398 : case STATE_IN_PRIVATEUSETOKEN:
399 0 : tag->state = STATE_PRIVATEUSETOKEN2;
400 0 : break;
401 : default:
402 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
403 : "Invalid syntax found during parsing a token: %s",
404 : token);
405 0 : retval = FALSE;
406 0 : break;
407 : }
408 : } else {
409 32 : retval = FALSE;
410 : }
411 :
412 54 : return retval;
413 : }
414 :
415 : static lt_bool_t
416 32 : lt_tag_parse_state(lt_tag_t *tag,
417 : const char *token,
418 : size_t length,
419 : lt_error_t **error)
420 : {
421 32 : lt_bool_t retval = TRUE;
422 : const char *p;
423 :
424 32 : switch (tag->state) {
425 : case STATE_LANG:
426 22 : if (length == 1) {
427 0 : if (lt_strcasecmp(token, "x") == 0) {
428 0 : lt_string_append(tag->privateuse, token);
429 0 : tag->state = STATE_IN_PRIVATEUSE;
430 0 : break;
431 : } else {
432 : invalid_tag:
433 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
434 : "Invalid language subtag: %s", token);
435 0 : break;
436 : }
437 42 : } else if (length >= 2 && length <= 3) {
438 20 : lt_lang_db_t *langdb = lt_db_get_lang();
439 :
440 : /* shortest ISO 639 code */
441 20 : tag->language = lt_lang_db_lookup(langdb, token);
442 20 : lt_lang_db_unref(langdb);
443 20 : if (!tag->language) {
444 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
445 : "Unknown ISO 639 code: %s",
446 : token);
447 0 : break;
448 : }
449 : /* validate if it's really shortest one */
450 20 : p = lt_lang_get_tag(tag->language);
451 20 : if (!p || lt_strcasecmp(token, p) != 0) {
452 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
453 : "No such language subtag: %s",
454 : token);
455 0 : lt_lang_unref(tag->language);
456 0 : tag->language = NULL;
457 0 : break;
458 : }
459 20 : lt_mem_add_ref(&tag->parent, tag->language,
460 : (lt_destroy_func_t)lt_lang_unref);
461 20 : tag->state = STATE_PRE_EXTLANG;
462 2 : } else if (length == 4) {
463 : /* reserved for future use */
464 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
465 : "Reserved for future use: %s",
466 : token);
467 2 : } else if (length >= 5 && length <= 8) {
468 : /* registered language subtag */
469 2 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
470 : "XXX: registered language tag: %s",
471 : token);
472 : } else {
473 : goto invalid_tag;
474 : }
475 22 : break;
476 : case STATE_EXTLANG:
477 6 : if (length == 3) {
478 0 : lt_extlang_db_t *extlangdb = lt_db_get_extlang();
479 :
480 0 : tag->extlang = lt_extlang_db_lookup(extlangdb, token);
481 0 : lt_extlang_db_unref(extlangdb);
482 0 : if (tag->extlang) {
483 0 : const char *prefix = lt_extlang_get_prefix(tag->extlang);
484 0 : const char *subtag = lt_extlang_get_tag(tag->extlang);
485 0 : const char *lang = lt_lang_get_better_tag(tag->language);
486 :
487 0 : if (prefix &&
488 0 : lt_strcasecmp(prefix, lang) != 0) {
489 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
490 : "extlang '%s' is supposed to be used with %s, but %s",
491 : subtag, prefix, lang);
492 0 : lt_extlang_unref(tag->extlang);
493 0 : tag->extlang = NULL;
494 : } else {
495 0 : lt_mem_add_ref(&tag->parent, tag->extlang,
496 : (lt_destroy_func_t)lt_extlang_unref);
497 0 : tag->state = STATE_PRE_SCRIPT;
498 : }
499 0 : break;
500 : }
501 : /* try to check something else */
502 : } else {
503 : /* it may be a script */
504 : }
505 : case STATE_SCRIPT:
506 6 : if (length == 4) {
507 4 : lt_script_db_t *scriptdb = lt_db_get_script();
508 :
509 4 : lt_tag_set_script(tag, lt_script_db_lookup(scriptdb, token));
510 4 : lt_script_db_unref(scriptdb);
511 4 : if (tag->script) {
512 4 : tag->state = STATE_PRE_REGION;
513 4 : break;
514 : }
515 : /* try to check something else */
516 : } else {
517 : /* it may be a region */
518 : }
519 : case STATE_REGION:
520 6 : if (length == 2 ||
521 0 : (length == 3 &&
522 0 : isdigit(token[0]) &&
523 0 : isdigit(token[1]) &&
524 0 : isdigit(token[2]))) {
525 6 : lt_region_db_t *regiondb = lt_db_get_region();
526 :
527 6 : lt_tag_set_region(tag, lt_region_db_lookup(regiondb, token));
528 6 : lt_region_db_unref(regiondb);
529 6 : if (tag->region) {
530 6 : tag->state = STATE_PRE_VARIANT;
531 6 : break;
532 : }
533 : /* try to check something else */
534 : } else {
535 : /* it may be a variant */
536 : }
537 : case STATE_VARIANT:
538 0 : if ((length >=5 && length <= 8) ||
539 0 : (length == 4 && isdigit(token[0]))) {
540 0 : lt_variant_db_t *variantdb = lt_db_get_variant();
541 : lt_variant_t *variant;
542 :
543 0 : variant = lt_variant_db_lookup(variantdb, token);
544 0 : lt_variant_db_unref(variantdb);
545 0 : if (variant) {
546 0 : const lt_list_t *prefixes = lt_variant_get_prefix(variant), *l;
547 0 : char *langtag = lt_tag_canonicalize(tag, error);
548 0 : lt_string_t *str_prefixes = lt_string_new(NULL);
549 0 : lt_bool_t matched = FALSE;
550 :
551 0 : if (error && lt_error_is_set(*error, LT_ERR_ANY)) {
552 : /* ignore it and fallback to the original tag string */
553 0 : lt_error_clear(*error);
554 0 : *error = NULL;
555 0 : langtag = strdup(lt_string_value(tag->tag_string));
556 : }
557 0 : for (l = prefixes; l != NULL; l = lt_list_next(l)) {
558 0 : const char *s = lt_list_value(l);
559 :
560 0 : if (lt_string_length(str_prefixes) > 0)
561 0 : lt_string_append(str_prefixes, ",");
562 0 : lt_string_append(str_prefixes, s);
563 :
564 0 : if (lt_strncasecmp(s, langtag, strlen(s)) == 0) {
565 0 : matched = TRUE;
566 0 : break;
567 : }
568 : }
569 0 : if (prefixes && !matched) {
570 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
571 : "variant '%s' is supposed to be used with %s, but %s",
572 : token, lt_string_value(str_prefixes), langtag);
573 0 : lt_variant_unref(variant);
574 : } else {
575 0 : if (!tag->variants) {
576 0 : lt_tag_set_variant(tag, variant);
577 : } else {
578 0 : lt_list_t *prefixes = (lt_list_t *)lt_variant_get_prefix(variant);
579 : const char *tstr;
580 :
581 0 : lt_tag_free_tag_string(tag);
582 0 : tstr = lt_tag_get_string(tag);
583 0 : if (prefixes && !lt_list_find_custom(prefixes, (const lt_pointer_t)tstr, (lt_compare_func_t)lt_strcmp0)) {
584 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
585 : "Variant isn't allowed for %s: %s",
586 : tstr,
587 : lt_variant_get_tag(variant));
588 0 : lt_variant_unref(variant);
589 0 : } else if (!prefixes && lt_list_find_custom(tag->variants, variant, _lt_tag_variant_compare)) {
590 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
591 : "Duplicate variants: %s",
592 : lt_variant_get_tag(variant));
593 0 : lt_variant_unref(variant);
594 : } else {
595 0 : tag->variants = lt_list_append(tag->variants,
596 : variant,
597 : (lt_destroy_func_t)lt_variant_unref);
598 : }
599 : }
600 : /* multiple variants are allowed. */
601 0 : tag->state = STATE_PRE_VARIANT;
602 : }
603 0 : if (langtag)
604 0 : free(langtag);
605 0 : lt_string_unref(str_prefixes);
606 0 : break;
607 : }
608 : /* try to check something else */
609 : } else {
610 : /* it may be an extension */
611 : }
612 : case STATE_EXTENSION:
613 : extension:
614 0 : if (length == 1 &&
615 0 : token[0] != 'x' &&
616 0 : token[0] != 'X' &&
617 0 : token[0] != '*' &&
618 0 : token[0] != '-') {
619 0 : if (!tag->extension)
620 0 : lt_tag_set_extension(tag, lt_extension_create());
621 0 : if (lt_extension_has_singleton(tag->extension, token[0])) {
622 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
623 : "Duplicate singleton for extension: %s", token);
624 : } else {
625 0 : if (lt_extension_add_singleton(tag->extension,
626 0 : token[0],
627 : tag, error)) {
628 0 : tag->state = STATE_IN_EXTENSION;
629 : }
630 : }
631 0 : break;
632 : } else {
633 : /* it may be a private use */
634 : }
635 : case STATE_PRIVATEUSE:
636 0 : if (length == 1 && (token[0] == 'x' || token[0] == 'X')) {
637 0 : lt_string_append(tag->privateuse, token);
638 0 : tag->state = STATE_IN_PRIVATEUSE;
639 : } else {
640 : /* No state to try */
641 0 : retval = FALSE;
642 : }
643 0 : break;
644 : case STATE_EXTENSIONTOKEN:
645 : case STATE_EXTENSIONTOKEN2:
646 0 : if (length >= 2 && length <= 8) {
647 0 : if (lt_extension_add_tag(tag->extension,
648 : token, error))
649 0 : tag->state = STATE_IN_EXTENSIONTOKEN;
650 : } else {
651 0 : if (tag->state == STATE_EXTENSIONTOKEN2 &&
652 0 : lt_extension_validate_state(tag->extension)) {
653 : /* No need to destroy the previous tokens.
654 : * fallback to check the extension again.
655 : */
656 0 : goto extension;
657 : }
658 0 : lt_extension_cancel_tag(tag->extension);
659 : /* No state to try */
660 0 : retval = FALSE;
661 : }
662 0 : break;
663 : case STATE_PRIVATEUSETOKEN:
664 : case STATE_PRIVATEUSETOKEN2:
665 0 : if (length <= 8) {
666 0 : lt_string_append_printf(tag->privateuse, "-%s", token);
667 0 : tag->state = STATE_IN_PRIVATEUSETOKEN;
668 : } else {
669 : /* 'x'/'X' is reserved singleton for the private use subtag.
670 : * so nothing to fallback to anything else.
671 : */
672 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
673 : "Invalid tag for the private use: token = '%s'",
674 : token);
675 : }
676 0 : break;
677 : default:
678 0 : lt_error_set(error, LT_ERR_FAIL_ON_SCANNER,
679 : "Unable to parse tag: %s, token = '%s' state = %d",
680 0 : lt_string_value(tag->tag_string), token, tag->state);
681 0 : break;
682 : }
683 32 : if (lt_error_is_set(*error, LT_ERR_ANY))
684 2 : retval = FALSE;
685 :
686 32 : return retval;
687 : }
688 :
689 : static lt_bool_t
690 24 : _lt_tag_parse(lt_tag_t *tag,
691 : const char *langtag,
692 : lt_bool_t allow_wildcard,
693 : lt_error_t **error)
694 : {
695 24 : lt_tag_scanner_t *scanner = NULL;
696 : lt_grandfathered_db_t *grandfathereddb;
697 24 : char *token = NULL;
698 24 : size_t len = 0;
699 24 : lt_error_t *err = NULL;
700 24 : lt_bool_t retval = TRUE;
701 24 : lt_tag_state_t wildcard = STATE_NONE;
702 24 : int count = 0;
703 :
704 24 : lt_return_val_if_fail (tag != NULL, FALSE);
705 24 : lt_return_val_if_fail (langtag != NULL, FALSE);
706 :
707 24 : if (tag->state == STATE_NONE) {
708 24 : grandfathereddb = lt_db_get_grandfathered();
709 24 : lt_tag_set_grandfathered(tag, lt_grandfathered_db_lookup(grandfathereddb, langtag));
710 24 : lt_grandfathered_db_unref(grandfathereddb);
711 24 : if (tag->grandfathered) {
712 : /* no need to lookup anymore. */
713 2 : goto bail;
714 : }
715 22 : tag->state = STATE_LANG;
716 : } else {
717 0 : if (tag->state == STATE_PRE_EXTLANG ||
718 0 : tag->state == STATE_PRE_SCRIPT ||
719 0 : tag->state == STATE_PRE_REGION ||
720 0 : tag->state == STATE_PRE_VARIANT ||
721 0 : tag->state == STATE_PRE_EXTENSION ||
722 0 : tag->state == STATE_PRE_PRIVATEUSE)
723 0 : tag->state++;
724 : }
725 :
726 22 : scanner = lt_tag_scanner_new(langtag);
727 96 : while (!lt_tag_scanner_is_eof(scanner)) {
728 54 : if (token) {
729 32 : free(token);
730 32 : token = NULL;
731 : }
732 54 : if (!lt_tag_scanner_get_token(scanner, &token, &len, &err)) {
733 0 : if (err)
734 0 : break;
735 0 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
736 : "Unrecoverable error");
737 0 : break;
738 : }
739 54 : count++;
740 54 : if (!token || len == 0) {
741 0 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
742 : "No valid tokens found");
743 0 : break;
744 : }
745 54 : if (!lt_tag_parse_prestate(tag, token, len, &err)) {
746 32 : if (err)
747 0 : break;
748 32 : if (allow_wildcard && lt_strcmp0(token, "*") == 0) {
749 0 : wildcard = tag->state;
750 0 : if (tag->state == STATE_LANG)
751 0 : tag->state += 1;
752 : else
753 0 : tag->state -= 1;
754 : } else {
755 32 : if (!lt_tag_parse_state(tag, token, len, &err))
756 2 : break;
757 30 : if (wildcard != STATE_NONE) {
758 0 : lt_tag_fill_wildcard(tag, wildcard, tag->state - 1);
759 0 : wildcard = STATE_NONE;
760 : }
761 : }
762 : }
763 : }
764 22 : if (wildcard != STATE_NONE) {
765 0 : lt_tag_fill_wildcard(tag, wildcard, STATE_END);
766 : }
767 42 : if (!err &&
768 38 : tag->state != STATE_PRE_EXTLANG &&
769 36 : tag->state != STATE_PRE_SCRIPT &&
770 36 : tag->state != STATE_PRE_REGION &&
771 30 : tag->state != STATE_PRE_VARIANT &&
772 24 : tag->state != STATE_PRE_EXTENSION &&
773 24 : tag->state != STATE_PRE_PRIVATEUSE &&
774 24 : tag->state != STATE_IN_EXTENSIONTOKEN &&
775 24 : tag->state != STATE_IN_PRIVATEUSETOKEN &&
776 12 : tag->state != STATE_NONE) {
777 12 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
778 : "Invalid tag: %s, last token = '%s', state = %d, parsed count = %d",
779 12 : langtag, token, tag->state, count);
780 : }
781 : bail:
782 24 : lt_tag_add_tag_string(tag, langtag);
783 24 : lt_tag_scanner_unref(scanner);
784 24 : if (lt_error_is_set(err, LT_ERR_ANY)) {
785 14 : if (error)
786 14 : *error = lt_error_ref(err);
787 : else
788 0 : lt_error_print(err, LT_ERR_ANY);
789 14 : lt_error_unref(err);
790 14 : retval = FALSE;
791 : }
792 24 : if (token)
793 22 : free(token);
794 :
795 24 : return retval;
796 : }
797 :
798 : static lt_bool_t
799 0 : _lt_tag_match(const lt_tag_t *v1,
800 : lt_tag_t *v2,
801 : lt_tag_state_t state)
802 : {
803 0 : lt_return_val_if_fail (v1 != NULL, FALSE);
804 0 : lt_return_val_if_fail (v2 != NULL, FALSE);
805 :
806 0 : if (state > STATE_EXTLANG && !v2->extlang && v1->extlang) {
807 0 : lt_extlang_db_t *db = lt_db_get_extlang();
808 :
809 0 : lt_tag_set_extlang(v2, lt_extlang_db_lookup(db, ""));
810 0 : lt_extlang_db_unref(db);
811 : }
812 0 : if (state > STATE_SCRIPT && !v2->script && v1->script) {
813 0 : lt_script_db_t *db = lt_db_get_script();
814 :
815 0 : lt_tag_set_script(v2, lt_script_db_lookup(db, ""));
816 0 : lt_script_db_unref(db);
817 : }
818 0 : if (state > STATE_REGION && !v2->region && v1->region) {
819 0 : lt_region_db_t *db = lt_db_get_region();
820 :
821 0 : lt_tag_set_region(v2, lt_region_db_lookup(db, ""));
822 0 : lt_region_db_unref(db);
823 : }
824 0 : if (state > STATE_VARIANT && !v2->variants && v1->variants) {
825 0 : lt_variant_db_t *db = lt_db_get_variant();
826 :
827 0 : lt_tag_set_variant(v2, lt_variant_db_lookup(db, ""));
828 0 : lt_variant_db_unref(db);
829 : }
830 0 : if (state > STATE_EXTENSION && !v2->extension && v1->extension) {
831 0 : lt_extension_t *e = lt_extension_create();
832 :
833 0 : lt_extension_add_singleton(e, ' ', NULL, NULL);
834 0 : lt_tag_set_extension(v2, e);
835 : }
836 :
837 0 : return lt_tag_compare(v1, v2);
838 : }
839 :
840 : static void
841 0 : _lt_tag_subtract(lt_tag_t *tag,
842 : const lt_tag_t *rtag)
843 : {
844 0 : if (rtag->language) {
845 0 : lt_tag_free_language(tag);
846 : }
847 0 : if (rtag->extlang) {
848 0 : lt_tag_free_extlang(tag);
849 : }
850 0 : if (rtag->script) {
851 0 : lt_tag_free_script(tag);
852 : }
853 0 : if (rtag->region) {
854 0 : lt_tag_free_region(tag);
855 : }
856 0 : if (rtag->variants) {
857 0 : lt_tag_free_variants(tag);
858 : }
859 0 : if (rtag->extension) {
860 : /* XXX: how to deal with the multiple extensions? */
861 0 : lt_tag_free_extension(tag);
862 : }
863 0 : if (rtag->privateuse) {
864 0 : if (tag->privateuse)
865 0 : lt_string_clear(tag->privateuse);
866 : }
867 0 : }
868 :
869 : static void
870 0 : _lt_tag_replace(lt_tag_t *tag,
871 : const lt_tag_t *rtag)
872 : {
873 0 : if (rtag->language) {
874 0 : lt_return_if_fail (!tag->language);
875 0 : lt_tag_set_language(tag, lt_lang_ref(rtag->language));
876 : }
877 0 : if (rtag->extlang) {
878 0 : lt_return_if_fail (!tag->extlang);
879 0 : lt_tag_set_extlang(tag, lt_extlang_ref(rtag->extlang));
880 : }
881 0 : if (rtag->script) {
882 0 : lt_return_if_fail (!tag->script);
883 0 : lt_tag_set_script(tag, lt_script_ref(rtag->script));
884 : }
885 0 : if (rtag->region) {
886 0 : lt_return_if_fail (!tag->region);
887 0 : lt_tag_set_region(tag, lt_region_ref(rtag->region));
888 : }
889 0 : if (rtag->variants) {
890 0 : lt_list_t *l = rtag->variants;
891 :
892 0 : lt_return_if_fail (!tag->variants);
893 :
894 0 : while (l != NULL) {
895 0 : lt_tag_set_variant(tag, lt_variant_ref(lt_list_value(l)));
896 0 : l = lt_list_next(l);
897 : }
898 : }
899 0 : if (rtag->extension) {
900 0 : lt_return_if_fail (!tag->extension);
901 0 : lt_tag_set_extension(tag, lt_extension_ref(rtag->extension));
902 : }
903 0 : if (rtag->privateuse) {
904 0 : lt_string_clear(tag->privateuse);
905 0 : lt_string_append(tag->privateuse, lt_string_value(rtag->privateuse));
906 : }
907 : }
908 :
909 : /* borrowed the modifier related code from localehelper:
910 : * http://people.redhat.com/caolanm/BCP47/localehelper-1.0.0.tar.gz
911 : */
912 : /*
913 : * glibc typically uses these modifiers to indicate particular
914 : * scripts that the language is written in
915 : * See ISO-15924 http://unicode.org/iso15924/iso15924-codes.html
916 : */
917 : static lt_bool_t
918 0 : _lt_tag_convert_script_from_locale_modifier(const char *modifier,
919 : const char **ret)
920 : {
921 : /* XXX: think about how to get rid of the hardcoded mapping table */
922 : static const char * const maps[][2] = {
923 : { "Arabic", "Arab" },
924 : { "Imperial_Aramaic", "Armi" },
925 : { "Armenian", "Armn" },
926 : { "Avestan", "Avst" },
927 : { "Balinese", "Bali" },
928 : { "Bamum", "Bamu" },
929 : { "Bengali", "Beng" },
930 : { "Bopomofo", "Bopo" },
931 : { "Braille", "Brai" },
932 : { "Buginese", "Bugi" },
933 : { "Buhid", "Buhd" },
934 : { "Canadian_Aboriginal", "Cans" },
935 : { "Carian", "Cari" },
936 : { "Cham", "Cham" },
937 : { "Cherokee", "Cher" },
938 : { "Coptic", "Copt" },
939 : { "Cypriot", "Cprt" },
940 : { "Cyrillic", "Cyrl" },
941 : { "Devanagari", "Deva" },
942 : { "Deseret", "Dsrt" },
943 : { "Egyptian_Hierogyphs", "Egyp" },
944 : { "Ethiopic", "Ethi" },
945 : { "Georgian", "Geor" },
946 : { "Glagolitic", "Glag" },
947 : { "Gothic", "Goth" },
948 : { "Greek", "Grek" },
949 : { "Gujarati", "Gujr" },
950 : { "Gurmukhi", "Guru" },
951 : { "Hangul", "Hang" },
952 : { "Han", "Hani" },
953 : { "Hanunoo", "Hano" },
954 : { "Hebrew", "Hebr" },
955 : { "Hiragana", "Hira" },
956 : { "Katakana_Or_Hiragana", "Hrkt" },
957 : { "Old_Italic", "Ital" },
958 : { "Javanese", "Java" },
959 : { "Kayah_Li", "Kali" },
960 : { "Katakana", "Kana" },
961 : { "Kharoshthi", "Khar" },
962 : { "Khmer", "Khmr" },
963 : { "Kannada", "Knda" },
964 : { "Kaithi", "Kthi" },
965 : { "Tai_Tham", "Lana" },
966 : { "Lao", "Laoo" },
967 : { "Latin", "Latn" },
968 : { "Lepcha", "Lepc" },
969 : { "Limbu", "Limb" },
970 : { "Linear_B", "Linb" },
971 : { "Lisu", "Lisu" },
972 : { "Lycian", "Lyci" },
973 : { "Lydian", "Lydi" },
974 : { "Malayalam", "Mlym" },
975 : { "Mongolian", "Mong" },
976 : { "Meetei_Mayek", "Mtei" },
977 : { "Myanmar", "Mymr" },
978 : { "Nko", "Nkoo" },
979 : { "Ogham", "Ogam" },
980 : { "Ol_Chiki", "Olck" },
981 : { "Old_Turkic", "Orkh" },
982 : { "Oriya", "Orya" },
983 : { "Osmanya", "Osma" },
984 : { "Phags_Pa", "Phag" },
985 : { "Inscriptional_Pahlavi", "Phli" },
986 : { "Phoenician", "Phnx" },
987 : { "Inscriptional_Parthian", "Prti" },
988 : { "Rejang", "Rjng" },
989 : { "Runic", "Runr" },
990 : { "Samaritan", "Samr" },
991 : { "Old_South_Arabian", "Sarb" },
992 : { "Saurashtra", "Saur" },
993 : { "Shavian", "Shaw" },
994 : { "Sinhala", "Sinh" },
995 : { "Sundanese", "Sund" },
996 : { "Syloti_Nagri", "Sylo" },
997 : { "Syriac", "Syrc" },
998 : { "Tagbanwa", "Tagb" },
999 : { "Tai_Le", "Tale" },
1000 : { "New_Tai_Lue", "Talu" },
1001 : { "Tamil", "Taml" },
1002 : { "Tai_Viet", "Tavt" },
1003 : { "Telugu", "Telu" },
1004 : { "Tifinagh", "Tfng" },
1005 : { "Tagalog", "Tglg" },
1006 : { "Thaana", "Thaa" },
1007 : { "Thai", "Thai" },
1008 : { "Tibetan", "Tibt" },
1009 : { "Ugaritic", "Ugar" },
1010 : { "Vai", "Vaii" },
1011 : { "Old_Persian", "Xpeo" },
1012 : { "Cuneiform", "Xsux" },
1013 : { "Yi", "Yiii" },
1014 : { "Inherited", "Zinh" },
1015 : { "Common", "Zyyy" },
1016 : { "Unknown", "Zzzz" },
1017 : };
1018 : size_t i;
1019 :
1020 0 : if (modifier) {
1021 : /*
1022 : * Special case this one. The script is definitely Latin
1023 : * and not Cyrillic. But lets bubble the transliteration scheme
1024 : * through another layer with return 0
1025 : */
1026 0 : if (lt_strcasecmp(modifier, "iqtelif") == 0) {
1027 0 : _lt_tag_convert_script_from_locale_modifier("Latin", ret);
1028 0 : return FALSE;
1029 : }
1030 0 : for (i = 0; i < sizeof (maps) / sizeof (char *[2]); i++) {
1031 0 : if (lt_strcasecmp(modifier, maps[i][0]) == 0) {
1032 0 : *ret = maps[i][1];
1033 0 : return TRUE;
1034 : }
1035 : }
1036 : }
1037 :
1038 0 : return FALSE;
1039 : }
1040 :
1041 : /*
1042 : * Occasionally (ca_ES@valencia) some modifiers indicate a language variant
1043 : * See http://www.iana.org/assignments/language-subtag-registry
1044 : * for IANA language subtag assignments output codes
1045 : */
1046 : static lt_bool_t
1047 0 : _lt_tag_convert_variant_from_locale_modifier(const char *modifier,
1048 : const char **ret)
1049 : {
1050 : /* XXx: think about how to get rid of the hardcoded mapping table */
1051 : static const char * const maps[][2] = {
1052 : { "valencia", "valencia" }
1053 : };
1054 : size_t i;
1055 :
1056 0 : if (modifier) {
1057 0 : for (i = 0; i < sizeof (maps) / sizeof (char *[2]); i++) {
1058 0 : if (lt_strcasecmp(modifier, maps[i][0]) == 0) {
1059 0 : *ret = maps[i][1];
1060 0 : return TRUE;
1061 : }
1062 : }
1063 : }
1064 :
1065 0 : return FALSE;
1066 : }
1067 :
1068 : static const char * const
1069 0 : _lt_tag_convert_privaseuse_from_locale_modifier(const char *modifier)
1070 : {
1071 : /* XXX: think about how to get rid of the hardcoded mapping table */
1072 : static const char * const maps[][2] = {
1073 : /*
1074 : * Old mechanism to denote that the euro currency is in use,
1075 : * ignore it.
1076 : */
1077 : { "euro", NULL },
1078 : /*
1079 : * http://www.mail-archive.com/cygwin@cygwin.com/msg97848.html
1080 : *
1081 : * A modifier that indicates what width to assign to an
1082 : * ambiguous width char, ignore it.
1083 : *
1084 : * http://unicode.org/reports/tr11/
1085 : */
1086 : { "cjknarrow", NULL },
1087 : /*
1088 : * http://www.geez.org/Collation/
1089 : *
1090 : * Abegede Collation for Ge'ez (as opposed to Halehame, I believe)
1091 : *
1092 : * http://www.iana.org/assignments/language-subtag-registry has
1093 : * nothing to describe it, so using a private code
1094 : *
1095 : * http://tools.ietf.org/html/draft-davis-u-langtag-ext-01
1096 : * http://www.unicode.org/reports/tr35/ maybe u-co-something some day
1097 : */
1098 : { "abegede", "abegede" },
1099 : /*
1100 : * http://www.alvestrand.no/pipermail/ietf-languages/2006-September/005017.html
1101 : *
1102 : * "iqtelif" Latin orthography
1103 : *
1104 : * Bit of a mess really. Unsure if tt-Latn is sufficient, i.e. if this is
1105 : * the default latin orghography in practice but a private code
1106 : * doesn't hurt I guess
1107 : */
1108 : { "iqtelif", "iqtel" }
1109 : };
1110 : size_t i;
1111 :
1112 0 : if (modifier) {
1113 0 : for (i = 0; i < sizeof (maps) / sizeof (char *[2]); i++) {
1114 0 : if (lt_strcasecmp(modifier, maps[i][0]) == 0)
1115 0 : return maps[i][1];
1116 : }
1117 :
1118 0 : lt_warning("Unknown modifiers: %s", modifier);
1119 :
1120 0 : return modifier;
1121 : }
1122 :
1123 0 : return NULL;
1124 : }
1125 :
1126 : static lt_tag_t *
1127 0 : _lt_tag_convert_from_locale_string(const char *locale,
1128 : lt_error_t **error)
1129 : {
1130 : char *s, *territory, *codeset, *modifier;
1131 : lt_tag_t *tag;
1132 0 : lt_error_t *err = NULL;
1133 :
1134 0 : s = strdup(locale);
1135 0 : tag = lt_tag_new();
1136 0 : if (!s || s[0] == 0 ||
1137 0 : lt_strcmp0(s, "C") == 0 ||
1138 0 : lt_strcmp0(s, "POSIX") == 0) {
1139 0 : if (!lt_tag_parse(tag, "en-US-u-va-posix", &err))
1140 0 : goto bail;
1141 : } else {
1142 : lt_string_t *tag_string;
1143 0 : const char *script = NULL, *variant = NULL, *privateuse = NULL;
1144 :
1145 0 : modifier = strchr(s, '@');
1146 0 : if (modifier) {
1147 0 : *modifier = 0;
1148 0 : modifier++;
1149 : }
1150 0 : codeset = strchr(s, '.');
1151 0 : if (codeset) {
1152 0 : *codeset = 0;
1153 0 : codeset++;
1154 : }
1155 0 : territory = strchr(s, '_');
1156 0 : if (territory) {
1157 0 : *territory = 0;
1158 0 : territory++;
1159 : }
1160 0 : if (codeset &&
1161 0 : (lt_strcasecmp(codeset, "utf-8") == 0 ||
1162 0 : lt_strcasecmp(codeset, "utf8") == 0)) {
1163 0 : codeset = NULL;
1164 : }
1165 : /* check if the language is a locale alias */
1166 0 : if (strlen(s) > 3 &&
1167 0 : !territory &&
1168 0 : !codeset &&
1169 : !modifier) {
1170 0 : const char *loc = lt_tag_get_locale_from_locale_alias(s);
1171 : lt_tag_t *t;
1172 :
1173 0 : if (loc && (t = _lt_tag_convert_from_locale_string(loc, &err)) != NULL) {
1174 0 : lt_tag_unref(tag);
1175 0 : tag = t;
1176 : goto bail;
1177 : }
1178 : }
1179 0 : if (!_lt_tag_convert_script_from_locale_modifier(modifier, &script))
1180 0 : if (!_lt_tag_convert_variant_from_locale_modifier(modifier, &variant))
1181 0 : privateuse = _lt_tag_convert_privaseuse_from_locale_modifier(modifier);
1182 :
1183 0 : tag_string = lt_string_new(s);
1184 0 : if (script)
1185 0 : lt_string_append_printf(tag_string, "-%s", script);
1186 0 : if (territory)
1187 0 : lt_string_append_printf(tag_string, "-%s", territory);
1188 0 : if (variant)
1189 0 : lt_string_append_printf(tag_string, "-%s", variant);
1190 0 : if (codeset || privateuse) {
1191 0 : lt_string_append(tag_string, "-x");
1192 0 : if (codeset)
1193 0 : lt_string_append_printf(tag_string, "-codeset-%s", codeset);
1194 0 : if (privateuse)
1195 0 : lt_string_append_printf(tag_string, "-%s", privateuse);
1196 : }
1197 0 : if (!lt_tag_parse(tag, lt_string_value(tag_string), &err)) {
1198 0 : lt_string_unref(tag_string);
1199 : goto bail;
1200 : }
1201 0 : lt_string_unref(tag_string);
1202 : }
1203 :
1204 : bail:
1205 0 : if (s)
1206 0 : free(s);
1207 :
1208 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1209 0 : if (error)
1210 0 : *error = lt_error_ref(err);
1211 : else
1212 0 : lt_error_print(err, LT_ERR_ANY);
1213 0 : lt_error_unref(err);
1214 0 : lt_tag_unref(tag);
1215 0 : tag = NULL;
1216 : }
1217 :
1218 0 : return tag;
1219 : }
1220 :
1221 : /*< protected >*/
1222 : lt_tag_state_t
1223 0 : lt_tag_parse_wildcard(lt_tag_t *tag,
1224 : const char *tag_string,
1225 : lt_error_t **error)
1226 : {
1227 0 : lt_error_t *err = NULL;
1228 : lt_bool_t ret;
1229 :
1230 0 : lt_tag_parser_init(tag);
1231 0 : ret = _lt_tag_parse(tag, tag_string, TRUE, &err);
1232 :
1233 0 : if (!ret && !err) {
1234 0 : lt_error_set(&err, LT_ERR_FAIL_ON_SCANNER,
1235 : "Unknown error during parsing a tag.");
1236 : }
1237 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1238 0 : if (error)
1239 0 : *error = lt_error_ref(err);
1240 : else
1241 0 : lt_error_print(err, LT_ERR_ANY);
1242 0 : lt_error_unref(err);
1243 : }
1244 :
1245 0 : return tag->state;
1246 : }
1247 :
1248 : /*< public >*/
1249 : /**
1250 : * lt_tag_new:
1251 : *
1252 : * Create a new instance of #lt_tag_t.
1253 : *
1254 : * Returns: (transfer full): a new instance of #lt_tag_t.
1255 : */
1256 : lt_tag_t *
1257 24 : lt_tag_new(void)
1258 : {
1259 24 : lt_tag_t *retval = lt_mem_alloc_object(sizeof (lt_tag_t));
1260 :
1261 24 : if (retval) {
1262 24 : retval->state = STATE_NONE;
1263 24 : retval->privateuse = lt_string_new(NULL);
1264 24 : lt_mem_add_ref(&retval->parent, retval->privateuse,
1265 : (lt_destroy_func_t)lt_string_unref);
1266 : }
1267 :
1268 24 : return retval;
1269 : }
1270 :
1271 : /**
1272 : * lt_tag_ref:
1273 : * @tag: a #lt_tag_t.
1274 : *
1275 : * Increases the reference count of @tag.
1276 : *
1277 : * Returns: (transfer none): the same @tag object.
1278 : */
1279 : lt_tag_t *
1280 0 : lt_tag_ref(lt_tag_t *tag)
1281 : {
1282 0 : lt_return_val_if_fail (tag != NULL, NULL);
1283 :
1284 0 : return lt_mem_ref(&tag->parent);
1285 : }
1286 :
1287 : /**
1288 : * lt_tag_unref:
1289 : * @tag: a #lt_tag_t.
1290 : *
1291 : * Decreases the reference count of @tag. when its reference count
1292 : * drops to 0, the object is finalized (i.e. its memory is freed).
1293 : */
1294 : void
1295 24 : lt_tag_unref(lt_tag_t *tag)
1296 : {
1297 24 : if (tag)
1298 24 : lt_mem_unref(&tag->parent);
1299 24 : }
1300 :
1301 : /**
1302 : * lt_tag_clear:
1303 : * @tag: a #lt_tag_t.
1304 : *
1305 : * (Re-)Initialize all of the subtag information stored in @tag.
1306 : */
1307 : void
1308 24 : lt_tag_clear(lt_tag_t *tag)
1309 : {
1310 48 : lt_return_if_fail (tag != NULL);
1311 :
1312 24 : lt_tag_free_tag_string(tag);
1313 24 : lt_tag_free_language(tag);
1314 24 : lt_tag_free_extlang(tag);
1315 24 : lt_tag_free_script(tag);
1316 24 : lt_tag_free_region(tag);
1317 24 : lt_tag_free_variants(tag);
1318 24 : lt_tag_free_extension(tag);
1319 24 : if (tag->privateuse) {
1320 24 : lt_string_clear(tag->privateuse);
1321 : }
1322 24 : lt_tag_free_grandfathered(tag);
1323 : }
1324 :
1325 : /**
1326 : * lt_tag_parse:
1327 : * @tag: a #lt_tag_t.
1328 : * @tag_string: language tag to be parsed.
1329 : * @error: (allow-none): a #lt_error_t or %NULL.
1330 : *
1331 : * Parse @tag_string and create appropriate instances for subtags.
1332 : *
1333 : * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
1334 : */
1335 : lt_bool_t
1336 24 : lt_tag_parse(lt_tag_t *tag,
1337 : const char *tag_string,
1338 : lt_error_t **error)
1339 : {
1340 24 : lt_tag_parser_init(tag);
1341 :
1342 24 : return _lt_tag_parse(tag, tag_string, FALSE, error);
1343 : }
1344 :
1345 : /**
1346 : * lt_tag_parse_with_extra_token:
1347 : * @tag: a #lt_tag_t.
1348 : * @tag_string: a language tag to be parsed much more.
1349 : * @error: (allow-none): a #lt_error_t or %NULL.
1350 : *
1351 : * Continue to parse a language tag with @tag_string. please use lt_tag_parse()
1352 : * at first.
1353 : *
1354 : * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
1355 : */
1356 : lt_bool_t
1357 0 : lt_tag_parse_with_extra_token(lt_tag_t *tag,
1358 : const char *tag_string,
1359 : lt_error_t **error)
1360 : {
1361 0 : lt_return_val_if_fail (tag != NULL, FALSE);
1362 0 : lt_return_val_if_fail (tag->state != STATE_NONE, FALSE);
1363 :
1364 0 : return _lt_tag_parse(tag, tag_string, FALSE, error);
1365 : }
1366 :
1367 : /**
1368 : * lt_tag_copy:
1369 : * @tag: a #lt_tag_t.
1370 : *
1371 : * Create a copy instance of @tag.
1372 : *
1373 : * Returns: (transfer full): a new instance of #lt_tag_t or %NULL if fails.
1374 : */
1375 : lt_tag_t *
1376 4 : lt_tag_copy(const lt_tag_t *tag)
1377 : {
1378 : lt_tag_t *retval;
1379 :
1380 4 : lt_return_val_if_fail (tag != NULL, NULL);
1381 :
1382 4 : retval = lt_tag_new();
1383 4 : retval->wildcard_map = tag->wildcard_map;
1384 4 : retval->state = tag->state;
1385 4 : if (tag->language) {
1386 4 : lt_tag_set_language(retval, lt_lang_ref(tag->language));
1387 : }
1388 4 : if (tag->extlang) {
1389 0 : lt_tag_set_extlang(retval, lt_extlang_ref(tag->extlang));
1390 : }
1391 4 : if (tag->script) {
1392 4 : lt_tag_set_script(retval, lt_script_ref(tag->script));
1393 : }
1394 4 : if (tag->region) {
1395 4 : lt_tag_set_region(retval, lt_region_ref(tag->region));
1396 : }
1397 4 : if (tag->variants) {
1398 : lt_list_t *l;
1399 :
1400 0 : for (l = tag->variants; l != NULL; l = lt_list_next(l)) {
1401 0 : retval->variants = lt_list_append(retval->variants,
1402 0 : lt_variant_ref(lt_list_value(l)),
1403 : (lt_destroy_func_t)lt_variant_unref);
1404 : }
1405 : }
1406 4 : if (tag->extension) {
1407 0 : lt_tag_set_extension(retval, lt_extension_copy(tag->extension));
1408 : }
1409 4 : if (tag->privateuse) {
1410 4 : lt_string_append(retval->privateuse, lt_string_value(tag->privateuse));
1411 : }
1412 4 : if (tag->grandfathered) {
1413 0 : lt_tag_set_grandfathered(retval, lt_grandfathered_ref(tag->grandfathered));
1414 : }
1415 :
1416 4 : return retval;
1417 : }
1418 :
1419 : /**
1420 : * lt_tag_truncate:
1421 : * @tag: a #lt_tag_t.
1422 : * @error: (allow-none): a #lt_error_t.
1423 : *
1424 : * Truncate the last subtag.
1425 : *
1426 : * Returns: %TRUE if a subtag was truncated, otherwise %FALSE.
1427 : */
1428 : lt_bool_t
1429 8 : lt_tag_truncate(lt_tag_t *tag,
1430 : lt_error_t **error)
1431 : {
1432 8 : lt_error_t *err = NULL;
1433 8 : lt_bool_t retval = TRUE;
1434 :
1435 8 : lt_return_val_if_fail (tag != NULL, FALSE);
1436 :
1437 8 : if (tag->grandfathered) {
1438 0 : lt_error_set(&err, LT_ERR_NO_TAG,
1439 : "Grandfathered subtag can't be truncated.");
1440 0 : goto bail;
1441 : }
1442 : while (1) {
1443 8 : if (tag->privateuse && lt_string_length(tag->privateuse) > 0) {
1444 0 : lt_string_clear(tag->privateuse);
1445 0 : break;
1446 : }
1447 8 : if (tag->extension) {
1448 : int i;
1449 : char c;
1450 0 : lt_bool_t has_tag = FALSE;
1451 :
1452 0 : lt_extension_truncate(tag->extension);
1453 0 : for (i = 0; i < LT_MAX_EXT_MODULES; i++) {
1454 0 : c = lt_ext_module_singleton_int_to_char(i);
1455 :
1456 0 : if (c == 'x')
1457 0 : continue;
1458 0 : has_tag = lt_extension_has_singleton(tag->extension, c);
1459 0 : if (has_tag)
1460 0 : break;
1461 : }
1462 0 : if (!has_tag) {
1463 0 : lt_tag_free_extension(tag);
1464 : }
1465 0 : break;
1466 : }
1467 8 : if (tag->variants) {
1468 0 : lt_list_t *l = lt_list_last(tag->variants);
1469 :
1470 0 : if (tag->variants == l) {
1471 0 : lt_mem_remove_ref(&tag->parent, tag->variants);
1472 0 : tag->variants = lt_list_delete_link(tag->variants, l);
1473 0 : if (tag->variants)
1474 0 : lt_mem_add_ref(&tag->parent, tag->variants, lt_list_free);
1475 : } else {
1476 0 : l = lt_list_delete_link(l, l);
1477 : }
1478 0 : break;
1479 : }
1480 8 : if (tag->region) {
1481 4 : lt_tag_free_region(tag);
1482 4 : break;
1483 : }
1484 4 : if (tag->script) {
1485 2 : lt_tag_free_script(tag);
1486 2 : break;
1487 : }
1488 2 : if (tag->extlang) {
1489 0 : lt_tag_free_extlang(tag);
1490 0 : break;
1491 : }
1492 2 : if (tag->language) {
1493 2 : lt_tag_free_language(tag);
1494 2 : break;
1495 : }
1496 0 : lt_error_set(&err, LT_ERR_NO_TAG,
1497 : "No tags to be truncated.");
1498 0 : goto bail;
1499 : }
1500 8 : lt_tag_free_tag_string(tag);
1501 : bail:
1502 8 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1503 0 : if (error)
1504 0 : *error = lt_error_ref(err);
1505 : else
1506 0 : lt_error_print(err, LT_ERR_ANY);
1507 0 : lt_error_unref(err);
1508 0 : retval = FALSE;
1509 : }
1510 :
1511 8 : return retval;
1512 : }
1513 :
1514 : /**
1515 : * lt_tag_get_string:
1516 : * @tag: a #lt_tag_t.
1517 : *
1518 : * Obtains a language tag in string.
1519 : *
1520 : * Returns: a language tag string.
1521 : */
1522 : const char *
1523 12 : lt_tag_get_string(lt_tag_t *tag)
1524 : {
1525 : lt_list_t *l;
1526 :
1527 12 : if (tag->tag_string)
1528 0 : return lt_string_value(tag->tag_string);
1529 :
1530 12 : if (tag->grandfathered)
1531 0 : lt_tag_add_tag_string(tag, lt_grandfathered_get_tag(tag->grandfathered));
1532 12 : else if (tag->language) {
1533 10 : lt_tag_add_tag_string(tag, lt_lang_get_tag(tag->language));
1534 10 : if (tag->extlang)
1535 0 : lt_tag_add_tag_string(tag, lt_extlang_get_tag(tag->extlang));
1536 10 : if (tag->script)
1537 8 : lt_tag_add_tag_string(tag, lt_script_get_tag(tag->script));
1538 10 : if (tag->region)
1539 4 : lt_tag_add_tag_string(tag, lt_region_get_tag(tag->region));
1540 10 : l = tag->variants;
1541 20 : while (l != NULL) {
1542 0 : lt_tag_add_tag_string(tag, lt_variant_get_tag(lt_list_value(l)));
1543 0 : l = lt_list_next(l);
1544 : }
1545 10 : if (tag->extension)
1546 0 : lt_tag_add_tag_string(tag, lt_extension_get_tag(tag->extension));
1547 10 : if (tag->privateuse && lt_string_length(tag->privateuse) > 0)
1548 0 : lt_tag_add_tag_string(tag, lt_string_value(tag->privateuse));
1549 2 : } else if (tag->privateuse && lt_string_length(tag->privateuse) > 0) {
1550 0 : lt_tag_add_tag_string(tag, lt_string_value(tag->privateuse));
1551 : } else {
1552 2 : return NULL;
1553 : }
1554 :
1555 10 : return lt_string_value(tag->tag_string);
1556 : }
1557 :
1558 : /**
1559 : * lt_tag_canonicalize:
1560 : * @tag: a #lt_tag_t.
1561 : * @error: (allow-none): a #lt_error_t or %NULL.
1562 : *
1563 : * Canonicalize the language tag according to various information of subtags.
1564 : *
1565 : * Returns: a language tag string.
1566 : */
1567 : char *
1568 6 : lt_tag_canonicalize(lt_tag_t *tag,
1569 : lt_error_t **error)
1570 : {
1571 6 : char *retval = NULL;
1572 6 : lt_string_t *string = NULL;
1573 6 : lt_error_t *err = NULL;
1574 : lt_list_t *l;
1575 6 : lt_redundant_db_t *rdb = NULL;
1576 6 : lt_redundant_t *r = NULL;
1577 6 : lt_tag_t *ctag = NULL;
1578 :
1579 6 : lt_return_val_if_fail (tag != NULL, NULL);
1580 :
1581 6 : string = lt_string_new(NULL);
1582 6 : if (tag->grandfathered) {
1583 2 : lt_string_append(string, lt_grandfathered_get_better_tag(tag->grandfathered));
1584 2 : goto bail1;
1585 : }
1586 :
1587 4 : ctag = lt_tag_copy(tag);
1588 4 : rdb = lt_db_get_redundant();
1589 : while (1) {
1590 12 : const char *tag_string = lt_tag_get_string(ctag);
1591 :
1592 12 : if (tag_string == NULL || tag_string[0] == 0)
1593 : break;
1594 10 : if (r)
1595 0 : lt_redundant_unref(r);
1596 10 : r = lt_redundant_db_lookup(rdb, tag_string);
1597 10 : if (r) {
1598 2 : const char *preferred = lt_redundant_get_preferred_tag(r);
1599 :
1600 2 : if (preferred) {
1601 0 : lt_tag_t *rtag = lt_tag_new();
1602 0 : lt_tag_t *ntag = lt_tag_new();
1603 :
1604 0 : if (!lt_tag_parse(rtag, lt_redundant_get_tag(r), &err)) {
1605 0 : lt_tag_unref(rtag);
1606 0 : lt_tag_unref(ntag);
1607 0 : goto bail1;
1608 : }
1609 0 : if (!lt_tag_parse(ntag, preferred, &err)) {
1610 0 : lt_tag_unref(rtag);
1611 0 : lt_tag_unref(ntag);
1612 0 : goto bail1;
1613 : }
1614 0 : _lt_tag_subtract(tag, rtag);
1615 0 : _lt_tag_replace(tag, ntag);
1616 0 : lt_tag_unref(rtag);
1617 0 : lt_tag_unref(ntag);
1618 : }
1619 2 : break;
1620 : } else {
1621 8 : if (!lt_tag_truncate(ctag, &err))
1622 0 : goto bail1;
1623 : }
1624 8 : }
1625 :
1626 4 : if (tag->language) {
1627 : size_t len;
1628 4 : lt_extlang_db_t *edb = lt_db_get_extlang();
1629 : lt_extlang_t *e;
1630 :
1631 : /* If the language tag starts with a primary language subtag
1632 : * that is also an extlang subtag, then the language tag is
1633 : * prepended with the extlang's 'Prefix'.
1634 : */
1635 4 : e = lt_extlang_db_lookup(edb, lt_lang_get_better_tag(tag->language));
1636 4 : if (e) {
1637 0 : const char *prefix = lt_extlang_get_prefix(e);
1638 :
1639 0 : if (prefix)
1640 0 : lt_string_append_printf(string, "%s-", prefix);
1641 0 : lt_extlang_unref(e);
1642 : }
1643 4 : lt_extlang_db_unref(edb);
1644 :
1645 4 : lt_string_append(string, lt_lang_get_better_tag(tag->language));
1646 4 : if (tag->extlang) {
1647 0 : const char *preferred = lt_extlang_get_preferred_tag(tag->extlang);
1648 :
1649 0 : if (preferred) {
1650 0 : lt_string_clear(string);
1651 0 : lt_string_append(string, preferred);
1652 : } else {
1653 0 : lt_string_append_printf(string, "-%s",
1654 0 : lt_extlang_get_tag(tag->extlang));
1655 : }
1656 : }
1657 4 : if (tag->script) {
1658 4 : const char *script = lt_script_get_tag(tag->script);
1659 4 : const char *suppress = lt_lang_get_suppress_script(tag->language);
1660 :
1661 6 : if (!suppress ||
1662 2 : lt_strcasecmp(suppress, script))
1663 2 : lt_string_append_printf(string, "-%s", script);
1664 : }
1665 4 : if (tag->region) {
1666 4 : lt_string_append_printf(string, "-%s", lt_region_get_better_tag(tag->region));
1667 : }
1668 4 : len = lt_string_length(string);
1669 4 : for (l = tag->variants; l != NULL; l = lt_list_next(l)) {
1670 0 : lt_variant_t *variant = lt_list_value(l);
1671 0 : const char *better = lt_variant_get_better_tag(variant);
1672 0 : const char *s = lt_variant_get_tag(variant);
1673 :
1674 0 : if (better && lt_strcasecmp(s, better) != 0) {
1675 : /* ignore all of variants prior to this one */
1676 0 : lt_string_truncate(string, len);
1677 : }
1678 0 : lt_string_append_printf(string, "-%s", better ? better : s);
1679 : }
1680 4 : if (tag->extension) {
1681 0 : char *s = lt_extension_get_canonicalized_tag(tag->extension);
1682 :
1683 0 : lt_string_append_printf(string, "-%s", s);
1684 0 : free(s);
1685 : }
1686 : }
1687 4 : if (tag->privateuse && lt_string_length(tag->privateuse) > 0) {
1688 0 : lt_string_append_printf(string, "%s%s",
1689 0 : lt_string_length(string) > 0 ? "-" : "",
1690 0 : lt_string_value(tag->privateuse));
1691 : }
1692 4 : if (lt_string_length(string) == 0) {
1693 0 : lt_error_set(&err, LT_ERR_NO_TAG,
1694 : "No tag to convert.");
1695 : }
1696 : bail1:
1697 6 : if (ctag)
1698 4 : lt_tag_unref(ctag);
1699 6 : if (rdb)
1700 4 : lt_redundant_db_unref(rdb);
1701 6 : if (r)
1702 2 : lt_redundant_unref(r);
1703 6 : retval = lt_string_free(string, FALSE);
1704 6 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1705 0 : if (error)
1706 0 : *error = lt_error_ref(err);
1707 : else
1708 0 : lt_error_print(err, LT_ERR_ANY);
1709 0 : lt_error_unref(err);
1710 0 : if (retval)
1711 0 : free(retval);
1712 0 : retval = NULL;
1713 : }
1714 :
1715 6 : return retval;
1716 : }
1717 :
1718 : /**
1719 : * lt_tag_convert_from_locale:
1720 : * @error: (allow-none): a #lt_error_t.
1721 : *
1722 : * Convert current locale to the language tag.
1723 : *
1724 : * Returns: (transfer full): a #lt_tag_t, %NULL if fails.
1725 : */
1726 : lt_tag_t *
1727 0 : lt_tag_convert_from_locale(lt_error_t **error)
1728 : {
1729 : const char *locale;
1730 :
1731 0 : locale = setlocale(LC_CTYPE, NULL);
1732 0 : if (!locale)
1733 0 : locale = setlocale(LC_ALL, NULL);
1734 0 : return _lt_tag_convert_from_locale_string(locale, error);
1735 : }
1736 :
1737 : /**
1738 : * lt_tag_convert_to_locale:
1739 : * @tag: a #lt_tag_t.
1740 : * @error: (allow-none): a #lt_error_t or %NULL.
1741 : *
1742 : * Convert the language tag to the locale.
1743 : *
1744 : * Returns: a locale string or %NULL if fails
1745 : */
1746 : char *
1747 0 : lt_tag_convert_to_locale(lt_tag_t *tag,
1748 : lt_error_t **error)
1749 : {
1750 0 : char *retval = NULL;
1751 0 : lt_string_t *string = NULL;
1752 0 : lt_error_t *err = NULL;
1753 0 : const char *mod = NULL;
1754 0 : char *canonical_tag = NULL;
1755 : lt_tag_t *ctag;
1756 :
1757 0 : lt_return_val_if_fail (tag != NULL, NULL);
1758 :
1759 0 : canonical_tag = lt_tag_canonicalize(tag, &err);
1760 0 : if (!canonical_tag)
1761 0 : goto bail;
1762 0 : ctag = lt_tag_new();
1763 0 : if (!lt_tag_parse(ctag, canonical_tag, &err)) {
1764 0 : lt_tag_unref(ctag);
1765 0 : goto bail;
1766 : }
1767 0 : string = lt_string_new(NULL);
1768 0 : lt_string_append(string, lt_lang_get_better_tag(ctag->language));
1769 0 : if (ctag->region)
1770 0 : lt_string_append_printf(string, "_%s",
1771 0 : lt_region_get_tag(ctag->region));
1772 0 : if (ctag->script) {
1773 0 : mod = lt_script_convert_to_modifier(ctag->script);
1774 0 : if (mod)
1775 0 : lt_string_append_printf(string, "@%s", mod);
1776 : }
1777 0 : lt_tag_unref(ctag);
1778 :
1779 : bail:
1780 0 : if (canonical_tag)
1781 0 : free(canonical_tag);
1782 0 : if (string)
1783 0 : retval = lt_string_free(string, FALSE);
1784 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1785 0 : if (error)
1786 0 : *error = lt_error_ref(err);
1787 : else
1788 0 : lt_error_print(err, LT_ERR_ANY);
1789 0 : lt_error_unref(err);
1790 0 : if (retval)
1791 0 : free(retval);
1792 0 : retval = NULL;
1793 : }
1794 :
1795 0 : return retval;
1796 : }
1797 :
1798 : /**
1799 : * lt_tag_dump:
1800 : * @tag: a #lt_tag_t.
1801 : *
1802 : * Dumps the container information to the standard output.
1803 : */
1804 : void
1805 0 : lt_tag_dump(const lt_tag_t *tag)
1806 : {
1807 : lt_list_t *l;
1808 :
1809 0 : lt_return_if_fail (tag != NULL);
1810 :
1811 0 : if (tag->grandfathered) {
1812 0 : lt_grandfathered_dump(tag->grandfathered);
1813 0 : return;
1814 : }
1815 0 : lt_lang_dump(tag->language);
1816 0 : if (tag->extlang)
1817 0 : lt_extlang_dump(tag->extlang);
1818 0 : if (tag->script)
1819 0 : lt_script_dump(tag->script);
1820 0 : if (tag->region)
1821 0 : lt_region_dump(tag->region);
1822 0 : for (l = tag->variants; l != NULL; l = lt_list_next(l)) {
1823 0 : lt_variant_t *variant = lt_list_value(l);
1824 :
1825 0 : lt_variant_dump(variant);
1826 : }
1827 0 : if (tag->extension)
1828 0 : lt_extension_dump(tag->extension);
1829 0 : if (lt_string_length(tag->privateuse) > 0)
1830 0 : lt_info("Private Use: %s", lt_string_value(tag->privateuse));
1831 : }
1832 :
1833 : /**
1834 : * lt_tag_compare:
1835 : * @v1: a #lt_tag_t.
1836 : * @v2: a #lt_tag_t.
1837 : *
1838 : * Compare if @v1 and @v2 is the same object or not.
1839 : *
1840 : * Returns: %TRUE if it's the same, otherwise %FALSE.
1841 : */
1842 : lt_bool_t
1843 0 : lt_tag_compare(const lt_tag_t *v1,
1844 : const lt_tag_t *v2)
1845 : {
1846 0 : lt_bool_t retval = TRUE;
1847 : const lt_list_t *l1, *l2;
1848 :
1849 0 : lt_return_val_if_fail (v1 != NULL, FALSE);
1850 0 : lt_return_val_if_fail (v2 != NULL, FALSE);
1851 0 : lt_return_val_if_fail (v1->grandfathered == NULL, FALSE);
1852 0 : lt_return_val_if_fail (v2->grandfathered == NULL, FALSE);
1853 :
1854 0 : retval &= lt_lang_compare(v1->language, v2->language);
1855 0 : if (v2->extlang)
1856 0 : retval &= lt_extlang_compare(v1->extlang, v2->extlang);
1857 0 : if (v2->script)
1858 0 : retval &= lt_script_compare(v1->script, v2->script);
1859 0 : if (v2->region)
1860 0 : retval &= lt_region_compare(v1->region, v2->region);
1861 0 : l1 = v1->variants;
1862 0 : l2 = v2->variants;
1863 0 : while (l2 != NULL) {
1864 : lt_variant_t *vv1, *vv2;
1865 :
1866 0 : vv1 = l1 ? lt_list_value(l1) : NULL;
1867 0 : vv2 = l2 ? lt_list_value(l2) : NULL;
1868 0 : retval &= lt_variant_compare(vv1, vv2);
1869 0 : l1 = lt_list_next(l1);
1870 0 : l2 = lt_list_next(l2);
1871 : }
1872 0 : if (v2->extension)
1873 0 : retval &= lt_extension_compare(v1->extension, v2->extension);
1874 0 : if (v2->privateuse && lt_string_length(v2->privateuse) > 0)
1875 0 : retval &= _lt_tag_string_compare(v1->privateuse, v2->privateuse);
1876 :
1877 0 : return retval;
1878 : }
1879 :
1880 : /**
1881 : * lt_tag_match:
1882 : * @v1: a #lt_tag_t.
1883 : * @v2: a language range string.
1884 : * @error: (allow-none): a #lt_error_t or %NULL.
1885 : *
1886 : * Try matching of @v1 and @v2. any of subtags in @v2 is allowed to use
1887 : * the wildcard according to the syntax in RFC 4647.
1888 : *
1889 : * Returns: %TRUE if it matches, otherwise %FALSE.
1890 : */
1891 : lt_bool_t
1892 0 : lt_tag_match(const lt_tag_t *v1,
1893 : const char *v2,
1894 : lt_error_t **error)
1895 : {
1896 0 : lt_bool_t retval = FALSE;
1897 0 : lt_tag_t *t2 = NULL;
1898 0 : lt_tag_state_t state = STATE_NONE;
1899 0 : lt_error_t *err = NULL;
1900 :
1901 0 : lt_return_val_if_fail (v1 != NULL, FALSE);
1902 0 : lt_return_val_if_fail (v2 != NULL, FALSE);
1903 :
1904 0 : t2 = lt_tag_new();
1905 0 : state = lt_tag_parse_wildcard(t2, v2, &err);
1906 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
1907 0 : if (error)
1908 0 : *error = lt_error_ref(err);
1909 : else
1910 0 : lt_error_print(err, LT_ERR_ANY);
1911 0 : lt_error_unref(err);
1912 0 : retval = FALSE;
1913 : } else {
1914 0 : retval = _lt_tag_match(v1, t2, state);
1915 : }
1916 0 : if (t2)
1917 0 : lt_tag_unref(t2);
1918 :
1919 0 : return retval;
1920 : }
1921 :
1922 : /**
1923 : * lt_tag_lookup:
1924 : * @tag: a #lt_tag_t.
1925 : * @pattern: a language range string.
1926 : * @error: (allow-none): a #lt_error_t or %NULL.
1927 : *
1928 : * Lookup the language tag that @tag meets with @pattern.
1929 : * Any of subtags in @pattern is allowed to use the wildcard according to
1930 : * the syntax in RFC 4647.
1931 : *
1932 : * Returns: a language tag string if any matches, otherwise %NULL.
1933 : */
1934 : char *
1935 0 : lt_tag_lookup(const lt_tag_t *tag,
1936 : const char *pattern,
1937 : lt_error_t **error)
1938 : {
1939 0 : lt_tag_t *t2 = NULL;
1940 0 : lt_tag_state_t state = STATE_NONE;
1941 0 : lt_error_t *err = NULL;
1942 : lt_list_t *l;
1943 0 : char *retval = NULL;
1944 :
1945 0 : lt_return_val_if_fail (tag != NULL, NULL);
1946 0 : lt_return_val_if_fail (pattern != NULL, NULL);
1947 :
1948 0 : t2 = lt_tag_new();
1949 0 : state = lt_tag_parse_wildcard(t2, pattern, &err);
1950 0 : if (err)
1951 0 : goto bail;
1952 0 : if (_lt_tag_match(tag, t2, state)) {
1953 : int32_t i;
1954 :
1955 0 : for (i = 0; i < (STATE_END - 1); i++) {
1956 0 : if (t2->wildcard_map & (1 << i)) {
1957 0 : switch (i + 1) {
1958 : case STATE_LANG:
1959 0 : lt_tag_set_language(t2, lt_lang_ref(tag->language));
1960 0 : break;
1961 : case STATE_EXTLANG:
1962 0 : lt_tag_free_extlang(t2);
1963 0 : if (tag->extlang) {
1964 0 : lt_tag_set_extlang(t2, lt_extlang_ref(tag->extlang));
1965 : }
1966 0 : break;
1967 : case STATE_SCRIPT:
1968 0 : lt_tag_free_script(t2);
1969 0 : if (tag->script) {
1970 0 : lt_tag_set_script(t2, lt_script_ref(tag->script));
1971 : }
1972 0 : break;
1973 : case STATE_REGION:
1974 0 : lt_tag_free_region(t2);
1975 0 : if (tag->region) {
1976 0 : lt_tag_set_region(t2, lt_region_ref(tag->region));
1977 : }
1978 0 : break;
1979 : case STATE_VARIANT:
1980 0 : lt_tag_free_variants(t2);
1981 0 : l = tag->variants;
1982 0 : while (l != NULL) {
1983 0 : lt_tag_set_variant(t2, lt_variant_ref(lt_list_value(l)));
1984 0 : l = lt_list_next(l);
1985 : }
1986 0 : break;
1987 : case STATE_EXTENSION:
1988 : case STATE_EXTENSIONTOKEN:
1989 : case STATE_EXTENSIONTOKEN2:
1990 0 : lt_tag_free_extension(t2);
1991 0 : if (tag->extension) {
1992 0 : lt_tag_set_extension(t2, lt_extension_ref(tag->extension));
1993 : }
1994 0 : break;
1995 : case STATE_PRIVATEUSE:
1996 : case STATE_PRIVATEUSETOKEN:
1997 : case STATE_PRIVATEUSETOKEN2:
1998 0 : if (t2->privateuse) {
1999 0 : lt_string_clear(t2->privateuse);
2000 : }
2001 0 : if (tag->privateuse) {
2002 0 : lt_string_append(t2->privateuse,
2003 0 : lt_string_value(tag->privateuse));
2004 : }
2005 0 : break;
2006 : default:
2007 0 : break;
2008 : }
2009 : }
2010 : }
2011 0 : lt_tag_free_tag_string(t2);
2012 0 : retval = strdup(lt_tag_get_string(t2));
2013 : }
2014 : bail:
2015 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
2016 0 : if (error)
2017 0 : *error = lt_error_ref(err);
2018 : else
2019 0 : lt_error_print(err, LT_ERR_ANY);
2020 0 : lt_error_unref(err);
2021 : }
2022 0 : if (t2)
2023 0 : lt_tag_unref(t2);
2024 :
2025 0 : return retval;
2026 : }
2027 :
2028 : /**
2029 : * lt_tag_transform:
2030 : * @tag: a #lt_tag_t.
2031 : * @error: (allow-none): a #lt_error_t or %NULL.
2032 : *
2033 : * Transform @tag according to the likelySubtags database provided by CLDR.
2034 : *
2035 : * Returns: a string.
2036 : */
2037 : char *
2038 0 : lt_tag_transform(lt_tag_t *tag,
2039 : lt_error_t **error)
2040 : {
2041 0 : lt_xml_t *xml = NULL;
2042 : const char *tag_string;
2043 0 : char *retval = NULL;
2044 0 : lt_error_t *err = NULL;
2045 :
2046 0 : lt_return_val_if_fail (tag != NULL, NULL);
2047 :
2048 0 : tag_string = lt_tag_get_string(tag);
2049 0 : if (tag_string) {
2050 : xmlDocPtr doc;
2051 0 : xmlXPathContextPtr xctxt = NULL;
2052 0 : xmlXPathObjectPtr xobj = NULL;
2053 : xmlNodePtr ent;
2054 : xmlChar *to;
2055 0 : char *xpath_string = NULL;
2056 : int n;
2057 : lt_string_t *s;
2058 : int i;
2059 : size_t len;
2060 :
2061 0 : xml = lt_xml_new();
2062 0 : doc = lt_xml_get_cldr(xml, LT_XML_CLDR_SUPPLEMENTAL_LIKELY_SUBTAGS);
2063 0 : xctxt = xmlXPathNewContext(doc);
2064 0 : if (!xctxt) {
2065 0 : lt_error_set(&err, LT_ERR_OOM,
2066 : "Unable to create an instance of xmlXPathContextPtr.");
2067 0 : goto bail;
2068 : }
2069 0 : xpath_string = lt_strdup_printf("/supplementalData/likelySubtags/likelySubtag[@from = '%s']", tag_string);
2070 0 : xobj = xmlXPathEvalExpression((const xmlChar *)xpath_string, xctxt);
2071 0 : if (!xobj) {
2072 0 : lt_error_set(&err, LT_ERR_FAIL_ON_XML,
2073 : "No valid elements for %s",
2074 : doc->name);
2075 0 : goto bail;
2076 : }
2077 0 : n = xmlXPathNodeSetGetLength(xobj->nodesetval);
2078 0 : if (n > 1)
2079 0 : lt_warning("Multiple subtag data to be transformed for %s: %d",
2080 : tag_string, n);
2081 :
2082 0 : ent = xmlXPathNodeSetItem(xobj->nodesetval, 0);
2083 0 : if (!ent) {
2084 0 : lt_error_set(&err, LT_ERR_FAIL_ON_XML,
2085 : "Unable to obtain the xml node via XPath.");
2086 0 : goto bail;
2087 : }
2088 0 : to = xmlGetProp(ent, (const xmlChar *)"to");
2089 0 : s = lt_string_new((const char *)to);
2090 0 : xmlFree(to);
2091 0 : len = lt_string_length(s);
2092 0 : for (i = 0; i < len; i++) {
2093 0 : if (lt_string_at(s, i) == '_')
2094 0 : lt_string_replace_c(s, i, '-');
2095 : }
2096 0 : retval = lt_string_free(s, FALSE);
2097 : bail:
2098 0 : free(xpath_string);
2099 0 : if (xobj)
2100 0 : xmlXPathFreeObject(xobj);
2101 0 : if (xctxt)
2102 0 : xmlXPathFreeContext(xctxt);
2103 0 : lt_xml_unref(xml);
2104 : }
2105 0 : if (lt_error_is_set(err, LT_ERR_ANY)) {
2106 0 : if (error)
2107 0 : *error = lt_error_ref(err);
2108 : else
2109 0 : lt_error_print(err, LT_ERR_ANY);
2110 0 : lt_error_unref(err);
2111 : }
2112 :
2113 0 : return retval;
2114 : }
2115 :
2116 : #define DEFUNC_GET_SUBTAG(__func__,__type__) \
2117 : const __type__ * \
2118 : lt_tag_get_ ##__func__ (const lt_tag_t *tag) \
2119 : { \
2120 : lt_return_val_if_fail (tag != NULL, NULL); \
2121 : \
2122 : return tag->__func__; \
2123 : }
2124 :
2125 : /**
2126 : * lt_tag_get_language:
2127 : * @tag: a #lt_tag_t.
2128 : *
2129 : * Obtain a #lt_lang_t instance in @tag.
2130 : *
2131 : * Returns: (transfer none): a #lt_lang_t.
2132 : */
2133 34 : DEFUNC_GET_SUBTAG (language, lt_lang_t)
2134 : /**
2135 : * lt_tag_get_extlang:
2136 : * @tag: a #lt_tag_t.
2137 : *
2138 : * Obtain a #lt_extlang_t instance in @tag.
2139 : *
2140 : * Returns: (transfer none): a #lt_extlang_t.
2141 : */
2142 0 : DEFUNC_GET_SUBTAG (extlang, lt_extlang_t)
2143 : /**
2144 : * lt_tag_get_script:
2145 : * @tag: a #lt_tag_t.
2146 : *
2147 : * Obtain a #lt_script_t instance in @tag.
2148 : *
2149 : * Returns: (transfer none): a #lt_script_t.
2150 : */
2151 8 : DEFUNC_GET_SUBTAG (script, lt_script_t)
2152 : /**
2153 : * lt_tag_get_region:
2154 : * @tag: a #lt_tag_t.
2155 : *
2156 : * Obtain a #lt_region_t instance in @tag.
2157 : *
2158 : * Returns: (transfer none): a #lt_region_t.
2159 : */
2160 40 : DEFUNC_GET_SUBTAG (region, lt_region_t)
2161 : /**
2162 : * lt_tag_get_variants:
2163 : * @tag: a #lt_tag_t.
2164 : *
2165 : * Obtain a list of #lt_variant_t instance in @tag.
2166 : *
2167 : * Returns: (transfer none): a #lt_list_t containing #lt_variant_t.
2168 : */
2169 0 : DEFUNC_GET_SUBTAG (variants, lt_list_t)
2170 : /**
2171 : * lt_tag_get_extension:
2172 : * @tag: a #lt_tag_t.
2173 : *
2174 : * Obtain a #lt_extension_t instance in @tag.
2175 : *
2176 : * Returns: (transfer none): a #lt_extension_t.
2177 : */
2178 0 : DEFUNC_GET_SUBTAG (extension, lt_extension_t)
2179 : /**
2180 : * lt_tag_get_privateuse:
2181 : * @tag: a #lt_tag_t.
2182 : *
2183 : * Obtain a #lt_string_t instance in @tag.
2184 : *
2185 : * Returns: (transfer none): a #lt_string_t.
2186 : */
2187 0 : DEFUNC_GET_SUBTAG (privateuse, lt_string_t)
2188 : /**
2189 : * lt_tag_get_grandfathered:
2190 : * @tag: a #lt_tag_t.
2191 : *
2192 : * Obtain a #lt_grandfathered_t instance in @tag.
2193 : *
2194 : * Returns: (transfer none): a #lt_grandfathered_t.
2195 : */
2196 0 : DEFUNC_GET_SUBTAG (grandfathered, lt_grandfathered_t)
2197 :
2198 : #undef DEFUNC_GET_SUBTAG
|