• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2010-2013, International Business Machines Corporation and    *
7  * others. All Rights Reserved.                                                *
8  *******************************************************************************
9  */
10 package ohos.global.icu.impl.locale;
11 
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 
19 /**
20  * @hide exposed on OHOS
21  */
22 public class LanguageTag {
23     private static final boolean JDKIMPL = false;
24 
25     //
26     // static fields
27     //
28     public static final String SEP = "-";
29     public static final String PRIVATEUSE = "x";
30     public static String UNDETERMINED = "und";
31     public static final String PRIVUSE_VARIANT_PREFIX = "lvariant";
32 
33     //
34     // Language subtag fields
35     //
36     private String _language = "";      // language subtag
37     private String _script = "";        // script subtag
38     private String _region = "";        // region subtag
39     private String _privateuse = "";    // privateuse
40 
41     private List<String> _extlangs = Collections.emptyList();   // extlang subtags
42     private List<String> _variants = Collections.emptyList();   // variant subtags
43     private List<String> _extensions = Collections.emptyList(); // extensions
44 
45     // Map contains grandfathered tags and its preferred mappings from
46     // http://www.ietf.org/rfc/rfc5646.txt
47     private static final Map<AsciiUtil.CaseInsensitiveKey, String[]> GRANDFATHERED =
48         new HashMap<AsciiUtil.CaseInsensitiveKey, String[]>();
49 
50     static {
51         // grandfathered = irregular           ; non-redundant tags registered
52         //               / regular             ; during the RFC 3066 era
53         //
54         // irregular     = "en-GB-oed"         ; irregular tags do not match
55         //               / "i-ami"             ; the 'langtag' production and
56         //               / "i-bnn"             ; would not otherwise be
57         //               / "i-default"         ; considered 'well-formed'
58         //               / "i-enochian"        ; These tags are all valid,
59         //               / "i-hak"             ; but most are deprecated
60         //               / "i-klingon"         ; in favor of more modern
61         //               / "i-lux"             ; subtags or subtag
62         //               / "i-mingo"           ; combination
63         //               / "i-navajo"
64         //               / "i-pwn"
65         //               / "i-tao"
66         //               / "i-tay"
67         //               / "i-tsu"
68         //               / "sgn-BE-FR"
69         //               / "sgn-BE-NL"
70         //               / "sgn-CH-DE"
71         //
72         // regular       = "art-lojban"        ; these tags match the 'langtag'
73         //               / "cel-gaulish"       ; production, but their subtags
74         //               / "no-bok"            ; are not extended language
75         //               / "no-nyn"            ; or variant subtags: their meaning
76         //               / "zh-guoyu"          ; is defined by their registration
77         //               / "zh-hakka"          ; and all of these are deprecated
78         //               / "zh-min"            ; in favor of a more modern
79         //               / "zh-min-nan"        ; subtag or sequence of subtags
80         //               / "zh-xiang"
81 
82         final String[][] entries = {
83           //{"tag",         "preferred"},
84             {"art-lojban",  "jbo"},
85             {"cel-gaulish", "xtg-x-cel-gaulish"},   // fallback
86             {"en-GB-oed",   "en-GB-x-oed"},         // fallback
87             {"i-ami",       "ami"},
88             {"i-bnn",       "bnn"},
89             {"i-default",   "en-x-i-default"},      // fallback
90             {"i-enochian",  "und-x-i-enochian"},    // fallback
91             {"i-hak",       "hak"},
92             {"i-klingon",   "tlh"},
93             {"i-lux",       "lb"},
94             {"i-mingo",     "see-x-i-mingo"},       // fallback
95             {"i-navajo",    "nv"},
96             {"i-pwn",       "pwn"},
97             {"i-tao",       "tao"},
98             {"i-tay",       "tay"},
99             {"i-tsu",       "tsu"},
100             {"no-bok",      "nb"},
101             {"no-nyn",      "nn"},
102             {"sgn-BE-FR",   "sfb"},
103             {"sgn-BE-NL",   "vgt"},
104             {"sgn-CH-DE",   "sgg"},
105             {"zh-guoyu",    "cmn"},
106             {"zh-hakka",    "hak"},
107             {"zh-min",      "nan-x-zh-min"},        // fallback
108             {"zh-min-nan",  "nan"},
109             {"zh-xiang",    "hsn"},
110         };
111         for (String[] e : entries) {
GRANDFATHERED.put(new AsciiUtil.CaseInsensitiveKey(e[0]), e)112             GRANDFATHERED.put(new AsciiUtil.CaseInsensitiveKey(e[0]), e);
113         }
114     }
115 
LanguageTag()116     private LanguageTag() {
117     }
118 
119     /*
120      * BNF in RFC5464
121      *
122      * Language-Tag  = langtag             ; normal language tags
123      *               / privateuse          ; private use tag
124      *               / grandfathered       ; grandfathered tags
125      *
126      *
127      * langtag       = language
128      *                 ["-" script]
129      *                 ["-" region]
130      *                 *("-" variant)
131      *                 *("-" extension)
132      *                 ["-" privateuse]
133      *
134      * language      = 2*3ALPHA            ; shortest ISO 639 code
135      *                 ["-" extlang]       ; sometimes followed by
136      *                                     ; extended language subtags
137      *               / 4ALPHA              ; or reserved for future use
138      *               / 5*8ALPHA            ; or registered language subtag
139      *
140      * extlang       = 3ALPHA              ; selected ISO 639 codes
141      *                 *2("-" 3ALPHA)      ; permanently reserved
142      *
143      * script        = 4ALPHA              ; ISO 15924 code
144      *
145      * region        = 2ALPHA              ; ISO 3166-1 code
146      *               / 3DIGIT              ; UN M.49 code
147      *
148      * variant       = 5*8alphanum         ; registered variants
149      *               / (DIGIT 3alphanum)
150      *
151      * extension     = singleton 1*("-" (2*8alphanum))
152      *
153      *                                     ; Single alphanumerics
154      *                                     ; "x" reserved for private use
155      * singleton     = DIGIT               ; 0 - 9
156      *               / %x41-57             ; A - W
157      *               / %x59-5A             ; Y - Z
158      *               / %x61-77             ; a - w
159      *               / %x79-7A             ; y - z
160      *
161      * privateuse    = "x" 1*("-" (1*8alphanum))
162      *
163      */
parse(String languageTag, ParseStatus sts)164     public static LanguageTag parse(String languageTag, ParseStatus sts) {
165         if (sts == null) {
166             sts = new ParseStatus();
167         } else {
168             sts.reset();
169         }
170 
171         StringTokenIterator itr;
172         boolean isGrandfathered = false;
173 
174         // Check if the tag is grandfathered
175         String[] gfmap = GRANDFATHERED.get(new AsciiUtil.CaseInsensitiveKey(languageTag));
176         // Language tag is at least 2 alpha so we can skip searching the first 2 chars.
177         int dash = 2;
178         while (gfmap == null && (dash = languageTag.indexOf('-', dash + 1)) != -1) {
179             gfmap = GRANDFATHERED.get(new AsciiUtil.CaseInsensitiveKey(languageTag.substring(0, dash)));
180         }
181 
182         if (gfmap != null) {
183             if (gfmap[0].length() == languageTag.length()) {
184                 // use preferred mapping
185                 itr = new StringTokenIterator(gfmap[1], SEP);
186             } else {
187                 // append the rest of the tag.
188                 itr = new StringTokenIterator(gfmap[1] + languageTag.substring(dash), SEP);
189             }
190             isGrandfathered = true;
191         } else {
192             itr = new StringTokenIterator(languageTag, SEP);
193         }
194 
195         LanguageTag tag = new LanguageTag();
196 
197         // langtag must start with either language or privateuse
198         if (tag.parseLanguage(itr, sts)) {
199             // ExtLang can only be preceded by 2-3 letter language subtag.
200             if (tag._language.length() <= 3)
201                 tag.parseExtlangs(itr, sts);
202             tag.parseScript(itr, sts);
203             tag.parseRegion(itr, sts);
204             tag.parseVariants(itr, sts);
205             tag.parseExtensions(itr, sts);
206         }
207         tag.parsePrivateuse(itr, sts);
208 
209         if (isGrandfathered) {
210             // Grandfathered tag is replaced with a well-formed tag above.
211             // However, the parsed length must be the original tag length.
212             assert (itr.isDone());
213             assert (!sts.isError());
214             sts._parseLength = languageTag.length();
215         } else if (!itr.isDone() && !sts.isError()) {
216             String s = itr.current();
217             sts._errorIndex = itr.currentStart();
218             if (s.length() == 0) {
219                 sts._errorMsg = "Empty subtag";
220             } else {
221                 sts._errorMsg = "Invalid subtag: " + s;
222             }
223         }
224 
225         return tag;
226     }
227 
228     //
229     // Language subtag parsers
230     //
231 
parseLanguage(StringTokenIterator itr, ParseStatus sts)232     private boolean parseLanguage(StringTokenIterator itr, ParseStatus sts) {
233         if (itr.isDone() || sts.isError()) {
234             return false;
235         }
236 
237         boolean found = false;
238 
239         String s = itr.current();
240         if (isLanguage(s)) {
241             found = true;
242             _language = s;
243             sts._parseLength = itr.currentEnd();
244             itr.next();
245         }
246 
247         return found;
248     }
249 
parseExtlangs(StringTokenIterator itr, ParseStatus sts)250     private boolean parseExtlangs(StringTokenIterator itr, ParseStatus sts) {
251         if (itr.isDone() || sts.isError()) {
252             return false;
253         }
254 
255         boolean found = false;
256 
257         while (!itr.isDone()) {
258             String s = itr.current();
259             if (!isExtlang(s)) {
260                 break;
261             }
262             found = true;
263             if (_extlangs.isEmpty()) {
264                 _extlangs = new ArrayList<String>(3);
265             }
266             _extlangs.add(s);
267             sts._parseLength = itr.currentEnd();
268             itr.next();
269 
270             if (_extlangs.size() == 3) {
271                 // Maximum 3 extlangs
272                 break;
273             }
274         }
275 
276         return found;
277     }
278 
parseScript(StringTokenIterator itr, ParseStatus sts)279     private boolean parseScript(StringTokenIterator itr, ParseStatus sts) {
280         if (itr.isDone() || sts.isError()) {
281             return false;
282         }
283 
284         boolean found = false;
285 
286         String s = itr.current();
287         if (isScript(s)) {
288             found = true;
289             _script = s;
290             sts._parseLength = itr.currentEnd();
291             itr.next();
292         }
293 
294         return found;
295     }
296 
parseRegion(StringTokenIterator itr, ParseStatus sts)297     private boolean parseRegion(StringTokenIterator itr, ParseStatus sts) {
298         if (itr.isDone() || sts.isError()) {
299             return false;
300         }
301 
302         boolean found = false;
303 
304         String s = itr.current();
305         if (isRegion(s)) {
306             found = true;
307             _region = s;
308             sts._parseLength = itr.currentEnd();
309             itr.next();
310         }
311 
312         return found;
313     }
314 
parseVariants(StringTokenIterator itr, ParseStatus sts)315     private boolean parseVariants(StringTokenIterator itr, ParseStatus sts) {
316         if (itr.isDone() || sts.isError()) {
317             return false;
318         }
319 
320         boolean found = false;
321 
322         while (!itr.isDone()) {
323             String s = itr.current();
324             if (!isVariant(s)) {
325                 break;
326             }
327             found = true;
328             if (_variants.isEmpty()) {
329                 _variants = new ArrayList<String>(3);
330             }
331             // Ignore repeated variant
332             s = s.toUpperCase();
333             if (!_variants.contains(s)) {
334                 _variants.add(s);
335             }
336             sts._parseLength = itr.currentEnd();
337             itr.next();
338         }
339 
340         return found;
341     }
342 
parseExtensions(StringTokenIterator itr, ParseStatus sts)343     private boolean parseExtensions(StringTokenIterator itr, ParseStatus sts) {
344         if (itr.isDone() || sts.isError()) {
345             return false;
346         }
347 
348         boolean found = false;
349 
350         while (!itr.isDone()) {
351             String s = itr.current();
352             if (isExtensionSingleton(s)) {
353                 int start = itr.currentStart();
354                 String singleton = s.toLowerCase();
355                 StringBuilder sb = new StringBuilder(singleton);
356 
357                 itr.next();
358                 while (!itr.isDone()) {
359                     s = itr.current();
360                     if (isExtensionSubtag(s)) {
361                         sb.append(SEP).append(s);
362                         sts._parseLength = itr.currentEnd();
363                     } else {
364                         break;
365                     }
366                     itr.next();
367                 }
368 
369                 if (sts._parseLength <= start) {
370                     sts._errorIndex = start;
371                     sts._errorMsg = "Incomplete extension '" + singleton + "'";
372                     break;
373                 }
374 
375                 if (_extensions.size() == 0) {
376                     _extensions = new ArrayList<String>(4);
377                 }
378                 // Ignore the extension if it is already in _extensions.
379                 boolean alreadyHas = false;
380                 for (String extension : _extensions) {
381                     alreadyHas |= extension.charAt(0) == sb.charAt(0);
382                 }
383                 if (!alreadyHas) {
384                   _extensions.add(sb.toString());
385                 }
386                 found = true;
387             } else {
388                 break;
389             }
390         }
391         return found;
392     }
393 
parsePrivateuse(StringTokenIterator itr, ParseStatus sts)394     private boolean parsePrivateuse(StringTokenIterator itr, ParseStatus sts) {
395         if (itr.isDone() || sts.isError()) {
396             return false;
397         }
398 
399         boolean found = false;
400 
401         String s = itr.current();
402         if (isPrivateusePrefix(s)) {
403             int start = itr.currentStart();
404             StringBuilder sb = new StringBuilder(s);
405 
406             itr.next();
407             while (!itr.isDone()) {
408                 s = itr.current();
409                 if (!isPrivateuseSubtag(s)) {
410                     break;
411                 }
412                 sb.append(SEP).append(s);
413                 sts._parseLength = itr.currentEnd();
414 
415                 itr.next();
416             }
417 
418             if (sts._parseLength <= start) {
419                 // need at least 1 private subtag
420                 sts._errorIndex = start;
421                 sts._errorMsg = "Incomplete privateuse";
422             } else {
423                 _privateuse = sb.toString();
424                 found = true;
425             }
426         }
427 
428         return found;
429     }
430 
parseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions)431     public static LanguageTag parseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions) {
432         LanguageTag tag = new LanguageTag();
433 
434         String language = baseLocale.getLanguage();
435         String script = baseLocale.getScript();
436         String region = baseLocale.getRegion();
437         String variant = baseLocale.getVariant();
438 
439         boolean hasSubtag = false;
440 
441         String privuseVar = null;   // store ill-formed variant subtags
442 
443         if (language.length() > 0 && isLanguage(language)) {
444             // Convert a deprecated language code used by Java to
445             // a new code
446             if (language.equals("iw")) {
447                 language = "he";
448             } else if (language.equals("ji")) {
449                 language = "yi";
450             } else if (language.equals("in")) {
451                 language = "id";
452             }
453             tag._language = language;
454         }
455 
456         if (script.length() > 0 && isScript(script)) {
457             tag._script = canonicalizeScript(script);
458             hasSubtag = true;
459         }
460 
461         if (region.length() > 0 && isRegion(region)) {
462             tag._region = canonicalizeRegion(region);
463             hasSubtag = true;
464         }
465 
466         if (JDKIMPL) {
467             // Special handling for no_NO_NY - use nn_NO for language tag
468             if (tag._language.equals("no") && tag._region.equals("NO") && variant.equals("NY")) {
469                 tag._language = "nn";
470                 variant = "";
471             }
472         }
473 
474         if (variant.length() > 0) {
475             List<String> variants = null;
476             StringTokenIterator varitr = new StringTokenIterator(variant, BaseLocale.SEP);
477             while (!varitr.isDone()) {
478                 String var = varitr.current();
479                 if (!isVariant(var)) {
480                     break;
481                 }
482                 if (variants == null) {
483                     variants = new ArrayList<String>();
484                 }
485                 if (JDKIMPL) {
486                     variants.add(var);  // Do not canonicalize!
487                 } else {
488                     variants.add(canonicalizeVariant(var));
489                 }
490                 varitr.next();
491             }
492             if (variants != null) {
493                 tag._variants = variants;
494                 hasSubtag = true;
495             }
496             if (!varitr.isDone()) {
497                 // ill-formed variant subtags
498                 StringBuilder buf = new StringBuilder();
499                 while (!varitr.isDone()) {
500                     String prvv = varitr.current();
501                     if (!isPrivateuseSubtag(prvv)) {
502                         // cannot use private use subtag - truncated
503                         break;
504                     }
505                     if (buf.length() > 0) {
506                         buf.append(SEP);
507                     }
508                     if (!JDKIMPL) {
509                         prvv = AsciiUtil.toLowerString(prvv);
510                     }
511                     buf.append(prvv);
512                     varitr.next();
513                 }
514                 if (buf.length() > 0) {
515                     privuseVar = buf.toString();
516                 }
517             }
518         }
519 
520         List<String> extensions = null;
521         String privateuse = null;
522 
523         Set<Character> locextKeys = localeExtensions.getKeys();
524         for (Character locextKey : locextKeys) {
525             Extension ext = localeExtensions.getExtension(locextKey);
526             if (isPrivateusePrefixChar(locextKey.charValue())) {
527                 privateuse = ext.getValue();
528             } else {
529                 if (extensions == null) {
530                     extensions = new ArrayList<String>();
531                 }
532                 extensions.add(locextKey.toString() + SEP + ext.getValue());
533             }
534         }
535 
536         if (extensions != null) {
537             tag._extensions = extensions;
538             hasSubtag = true;
539         }
540 
541         // append ill-formed variant subtags to private use
542         if (privuseVar != null) {
543             if (privateuse == null) {
544                 privateuse = PRIVUSE_VARIANT_PREFIX + SEP + privuseVar;
545             } else {
546                 privateuse = privateuse + SEP + PRIVUSE_VARIANT_PREFIX + SEP + privuseVar.replace(BaseLocale.SEP, SEP);
547             }
548         }
549 
550         if (privateuse != null) {
551             tag._privateuse = privateuse;
552         }
553 
554         if (tag._language.length() == 0 && (hasSubtag || privateuse == null)) {
555             // use lang "und" when 1) no language is available AND
556             // 2) any of other subtags other than private use are available or
557             // no private use tag is available
558             tag._language = UNDETERMINED;
559         }
560 
561         return tag;
562     }
563 
564     //
565     // Getter methods for language subtag fields
566     //
567 
getLanguage()568     public String getLanguage() {
569         return _language;
570     }
571 
getExtlangs()572     public List<String> getExtlangs() {
573         return Collections.unmodifiableList(_extlangs);
574     }
575 
getScript()576     public String getScript() {
577         return _script;
578     }
579 
getRegion()580     public String getRegion() {
581         return _region;
582     }
583 
getVariants()584     public List<String> getVariants() {
585         return Collections.unmodifiableList(_variants);
586     }
587 
getExtensions()588     public List<String> getExtensions() {
589         return Collections.unmodifiableList(_extensions);
590     }
591 
getPrivateuse()592     public String getPrivateuse() {
593         return _privateuse;
594     }
595 
596     //
597     // Language subtag syntax checking methods
598     //
599 
isLanguage(String s)600     public static boolean isLanguage(String s) {
601         // language      = 2*3ALPHA            ; shortest ISO 639 code
602         //                 ["-" extlang]       ; sometimes followed by
603         //                                     ;   extended language subtags
604         //               / 4ALPHA              ; or reserved for future use
605         //               / 5*8ALPHA            ; or registered language subtag
606         return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaString(s);
607     }
608 
isExtlang(String s)609     public static boolean isExtlang(String s) {
610         // extlang       = 3ALPHA              ; selected ISO 639 codes
611         //                 *2("-" 3ALPHA)      ; permanently reserved
612         return (s.length() == 3) && AsciiUtil.isAlphaString(s);
613     }
614 
isScript(String s)615     public static boolean isScript(String s) {
616         // script        = 4ALPHA              ; ISO 15924 code
617         return (s.length() == 4) && AsciiUtil.isAlphaString(s);
618     }
619 
isRegion(String s)620     public static boolean isRegion(String s) {
621         // region        = 2ALPHA              ; ISO 3166-1 code
622         //               / 3DIGIT              ; UN M.49 code
623         return ((s.length() == 2) && AsciiUtil.isAlphaString(s))
624                 || ((s.length() == 3) && AsciiUtil.isNumericString(s));
625     }
626 
isVariant(String s)627     public static boolean isVariant(String s) {
628         // variant       = 5*8alphanum         ; registered variants
629         //               / (DIGIT 3alphanum)
630         int len = s.length();
631         if (len >= 5 && len <= 8) {
632             return AsciiUtil.isAlphaNumericString(s);
633         }
634         if (len == 4) {
635             return AsciiUtil.isNumeric(s.charAt(0))
636                     && AsciiUtil.isAlphaNumeric(s.charAt(1))
637                     && AsciiUtil.isAlphaNumeric(s.charAt(2))
638                     && AsciiUtil.isAlphaNumeric(s.charAt(3));
639         }
640         return false;
641     }
642 
isExtensionSingleton(String s)643     public static boolean isExtensionSingleton(String s) {
644         // singleton     = DIGIT               ; 0 - 9
645         //               / %x41-57             ; A - W
646         //               / %x59-5A             ; Y - Z
647         //               / %x61-77             ; a - w
648         //               / %x79-7A             ; y - z
649 
650         return (s.length() == 1)
651                 && AsciiUtil.isAlphaNumericString(s)
652                 && !AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s);
653     }
654 
isExtensionSingletonChar(char c)655     public static boolean isExtensionSingletonChar(char c) {
656         return isExtensionSingleton(String.valueOf(c));
657     }
658 
isExtensionSubtag(String s)659     public static boolean isExtensionSubtag(String s) {
660         // extension     = singleton 1*("-" (2*8alphanum))
661         return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s);
662     }
663 
isPrivateusePrefix(String s)664     public static boolean isPrivateusePrefix(String s) {
665         // privateuse    = "x" 1*("-" (1*8alphanum))
666         return (s.length() == 1)
667                 && AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s);
668     }
669 
isPrivateusePrefixChar(char c)670     public static boolean isPrivateusePrefixChar(char c) {
671         return (AsciiUtil.caseIgnoreMatch(PRIVATEUSE, String.valueOf(c)));
672     }
673 
isPrivateuseSubtag(String s)674     public static boolean isPrivateuseSubtag(String s) {
675         // privateuse    = "x" 1*("-" (1*8alphanum))
676         return (s.length() >= 1) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s);
677     }
678 
679     //
680     // Language subtag canonicalization methods
681     //
682 
canonicalizeLanguage(String s)683     public static String canonicalizeLanguage(String s) {
684         return AsciiUtil.toLowerString(s);
685     }
686 
canonicalizeExtlang(String s)687     public static String canonicalizeExtlang(String s) {
688         return AsciiUtil.toLowerString(s);
689     }
690 
canonicalizeScript(String s)691     public static String canonicalizeScript(String s) {
692         return AsciiUtil.toTitleString(s);
693     }
694 
canonicalizeRegion(String s)695     public static String canonicalizeRegion(String s) {
696         return AsciiUtil.toUpperString(s);
697     }
698 
canonicalizeVariant(String s)699     public static String canonicalizeVariant(String s) {
700         return AsciiUtil.toLowerString(s);
701     }
702 
canonicalizeExtension(String s)703     public static String canonicalizeExtension(String s) {
704         s = AsciiUtil.toLowerString(s);
705         int found;
706         while (s.endsWith("-true")) {
707             s = s.substring(0, s.length() - 5);  // length of "-true" is 5
708         }
709         while ((found = s.indexOf("-true-")) > 0) {
710             s = s.substring(0, found) + s.substring(found + 5);  // length of "-true" is 5
711         }
712         while (s.endsWith("-yes")) {
713             s = s.substring(0, s.length() - 4);  // length of "-yes" is 4
714         }
715         while ((found = s.indexOf("-yes-")) > 0) {
716             s = s.substring(0, found) + s.substring(found + 4);  // length of "-yes" is 5
717         }
718         return s;
719     }
720 
canonicalizeExtensionSingleton(String s)721     public static String canonicalizeExtensionSingleton(String s) {
722         return AsciiUtil.toLowerString(s);
723     }
724 
canonicalizeExtensionSubtag(String s)725     public static String canonicalizeExtensionSubtag(String s) {
726         return AsciiUtil.toLowerString(s);
727     }
728 
canonicalizePrivateuse(String s)729     public static String canonicalizePrivateuse(String s) {
730         return AsciiUtil.toLowerString(s);
731     }
732 
canonicalizePrivateuseSubtag(String s)733     public static String canonicalizePrivateuseSubtag(String s) {
734         return AsciiUtil.toLowerString(s);
735     }
736 
737     @Override
toString()738     public String toString() {
739         StringBuilder sb = new StringBuilder();
740 
741         if (_language.length() > 0) {
742             sb.append(_language);
743 
744             for (String extlang : _extlangs) {
745                 sb.append(SEP).append(extlang);
746             }
747 
748             if (_script.length() > 0) {
749                 sb.append(SEP).append(_script);
750             }
751 
752             if (_region.length() > 0) {
753                 sb.append(SEP).append(_region);
754             }
755 
756             for (String variant : _variants) {
757                 sb.append(SEP).append(variant);
758             }
759 
760             for (String extension : _extensions) {
761                 sb.append(SEP).append(extension);
762             }
763         }
764         if (_privateuse.length() > 0) {
765             if (sb.length() > 0) {
766                 sb.append(SEP);
767             }
768             sb.append(_privateuse);
769         }
770 
771         return sb.toString();
772     }
773 }
774