2 *******************************************************************************
\r
3 * Copyright (C) 2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.impl.locale;
\r
9 import java.util.ArrayList;
\r
10 import java.util.Collections;
\r
11 import java.util.HashMap;
\r
12 import java.util.List;
\r
13 import java.util.Map;
\r
14 import java.util.Set;
\r
15 import java.util.SortedMap;
\r
16 import java.util.TreeMap;
\r
17 import java.util.Map.Entry;
\r
19 public class LanguageTag {
\r
21 private static final boolean JDKIMPL = false;
\r
26 public static final String SEP = "-";
\r
27 public static final String PRIVATEUSE = "x";
\r
28 public static String UNDETERMINED = "und";
\r
30 private static final String JAVAVARIANT = "variant";
\r
31 private static final String JAVASEP = "_";
\r
33 private static final SortedMap<Character, Extension> EMPTY_EXTENSION_MAP = new TreeMap<Character, Extension>();
\r
36 // Language tag parser instances
\r
38 public static final Parser DEFAULT_PARSER = new Parser(false);
\r
39 public static final Parser JAVA_VARIANT_COMPATIBLE_PARSER = new Parser(true);
\r
42 // Language subtag fields
\r
44 private String _grandfathered = ""; // grandfathered tag
\r
45 private String _language = ""; // language subtag
\r
46 private String _script = ""; // script subtag
\r
47 private String _region = ""; // region subtag
\r
48 private String _privateuse = ""; // privateuse, not including leading "x-"
\r
49 private List<String> _extlangs = Collections.emptyList(); // extlang subtags
\r
50 private List<String> _variants = Collections.emptyList(); // variant subtags
\r
51 private SortedMap<Character, Extension> _extensions = EMPTY_EXTENSION_MAP; // extension key/value pairs
\r
53 private boolean _javaCompatVariants = false;
\r
55 // Map contains grandfathered tags and its preferred mappings from
\r
56 // http://www.ietf.org/rfc/rfc5646.txt
\r
57 private static final Map<AsciiUtil.CaseInsensitiveKey, String[]> GRANDFATHERED =
\r
58 new HashMap<AsciiUtil.CaseInsensitiveKey, String[]>();
\r
61 // grandfathered = irregular ; non-redundant tags registered
\r
62 // / regular ; during the RFC 3066 era
\r
64 // irregular = "en-GB-oed" ; irregular tags do not match
\r
65 // / "i-ami" ; the 'langtag' production and
\r
66 // / "i-bnn" ; would not otherwise be
\r
67 // / "i-default" ; considered 'well-formed'
\r
68 // / "i-enochian" ; These tags are all valid,
\r
69 // / "i-hak" ; but most are deprecated
\r
70 // / "i-klingon" ; in favor of more modern
\r
71 // / "i-lux" ; subtags or subtag
\r
72 // / "i-mingo" ; combination
\r
82 // regular = "art-lojban" ; these tags match the 'langtag'
\r
83 // / "cel-gaulish" ; production, but their subtags
\r
84 // / "no-bok" ; are not extended language
\r
85 // / "no-nyn" ; or variant subtags: their meaning
\r
86 // / "zh-guoyu" ; is defined by their registration
\r
87 // / "zh-hakka" ; and all of these are deprecated
\r
88 // / "zh-min" ; in favor of a more modern
\r
89 // / "zh-min-nan" ; subtag or sequence of subtags
\r
92 final String[][] entries = {
\r
93 //{"tag", "preferred"},
\r
94 {"art-lojban", "jbo"},
\r
95 {"cel-gaulish", "cel-gaulish"}, // gaulish is parsed as a variant
\r
96 {"en-GB-oed", "en-GB"}, // oed (Oxford English Dictionary spelling) is ignored
\r
99 {"i-default", UNDETERMINED}, // fallback
\r
100 {"i-enochian", UNDETERMINED}, // fallback
\r
102 {"i-klingon", "tlh"},
\r
104 {"i-mingo", UNDETERMINED}, // fallback
\r
105 {"i-navajo", "nv"},
\r
112 {"sgn-BE-FR", "sfb"},
\r
113 {"sgn-BE-NL", "vgt"},
\r
114 {"sgn-CH-DE", "sgg"},
\r
115 {"zh-guoyu", "cmn"},
\r
116 {"zh-hakka", "hak"},
\r
117 {"zh-min", "zh"}, // fallback
\r
118 {"zh-min-nan", "nan"},
\r
119 {"zh-xiang", "hsn"},
\r
121 for (String[] e : entries) {
\r
122 GRANDFATHERED.put(new AsciiUtil.CaseInsensitiveKey(e[0]), e);
\r
126 private LanguageTag() {
\r
130 // Getter methods for language subtag fields
\r
133 public String getLanguage() {
\r
137 public List<String> getExtlangs() {
\r
138 return Collections.unmodifiableList(_extlangs);
\r
141 public String getScript() {
\r
145 public String getRegion() {
\r
149 public List<String> getVariants() {
\r
150 return Collections.unmodifiableList(_variants);
\r
153 public SortedMap<Character, Extension> getExtensions() {
\r
154 return Collections.unmodifiableSortedMap(_extensions);
\r
157 public String getPrivateuse() {
\r
158 return _privateuse;
\r
161 public String getGrandfathered() {
\r
162 return _grandfathered;
\r
165 private String getJavaVariant() {
\r
166 StringBuilder buf = new StringBuilder();
\r
167 for (String var : _variants) {
\r
168 if (buf.length() > 0) {
\r
169 buf.append(JAVASEP);
\r
173 if (_javaCompatVariants) {
\r
174 return getJavaCompatibleVariant(buf.toString(), _privateuse);
\r
177 return buf.toString();
\r
180 private String getJavaPrivateuse() {
\r
181 if (_javaCompatVariants) {
\r
182 return getJavaCompatiblePrivateuse(_privateuse);
\r
184 return _privateuse;
\r
187 static String getJavaCompatibleVariant(String bcpVariants, String bcpPrivuse) {
\r
188 StringBuilder buf = new StringBuilder(bcpVariants);
\r
189 if (bcpPrivuse.length() > 0) {
\r
191 if (bcpPrivuse.startsWith(JAVAVARIANT + SEP)) {
\r
192 idx = (JAVAVARIANT + SEP).length();
\r
194 idx = bcpPrivuse.indexOf(SEP + JAVAVARIANT + SEP);
\r
196 idx += (SEP + JAVAVARIANT + SEP).length();
\r
200 if (buf.length() != 0) {
\r
201 buf.append(JAVASEP);
\r
203 buf.append(bcpPrivuse.substring(idx).replace(SEP, JAVASEP));
\r
206 return buf.toString();
\r
209 static String getJavaCompatiblePrivateuse(String bcpPrivuse) {
\r
210 if (bcpPrivuse.length() > 0) {
\r
212 if (bcpPrivuse.startsWith(JAVAVARIANT + SEP)) {
\r
215 idx = bcpPrivuse.indexOf(SEP + JAVAVARIANT + SEP);
\r
218 return bcpPrivuse.substring(0, idx);
\r
224 public BaseLocale getBaseLocale() {
\r
225 String lang = _language;
\r
226 if (_extlangs.size() > 0) {
\r
227 // Extended language subtags are used for various historical
\r
228 // and compatibility reasons. Each extended language subtag
\r
229 // has a "Preferred-Value', that is exactly same with the extended
\r
230 // language subtag itself. For example,
\r
234 // Description: Algerian Saharan Arabic
\r
235 // Added: 2009-07-29
\r
236 // Preferred-Value: aao
\r
238 // Macrolanguage: ar
\r
240 // For example, language tag "ar-aao-DZ" is equivalent to
\r
243 // Strictly speaking, the mapping requires prefix validation
\r
244 // (e.g. primary language must be "ar" in the example above).
\r
245 // However, this implementation does not check the prefix
\r
246 // and simply use the first extlang value as locale's language.
\r
247 lang = _extlangs.get(0);
\r
249 if (lang.equals(UNDETERMINED)) {
\r
252 return BaseLocale.getInstance(lang, _script, _region, getJavaVariant());
\r
255 public LocaleExtensions getLocaleExtensions() {
\r
256 String javaPrivuse = getJavaPrivateuse();
\r
257 if (_extensions == null && javaPrivuse.length() == 0) {
\r
258 return LocaleExtensions.EMPTY_EXTENSIONS;
\r
260 SortedMap<Character, Extension> exts = new TreeMap<Character, Extension>();
\r
261 if (_extensions != null) {
\r
262 exts.putAll(_extensions);
\r
264 if (javaPrivuse.length() > 0) {
\r
265 PrivateuseExtension pext = new PrivateuseExtension(javaPrivuse);
\r
266 exts.put(Character.valueOf(PrivateuseExtension.SINGLETON), pext);
\r
268 return LocaleExtensions.getInstance(exts);
\r
271 public String getID() {
\r
272 if (_grandfathered.length() > 0) {
\r
273 return _grandfathered;
\r
275 StringBuilder buf = new StringBuilder();
\r
276 if (_language.length() > 0) {
\r
277 buf.append(_language);
\r
278 if (_extlangs.size() > 0) {
\r
279 for (String el : _extlangs) {
\r
284 if (_script.length() > 0) {
\r
286 buf.append(_script);
\r
288 if (_region.length() > 0) {
\r
290 buf.append(_region);
\r
292 if (_variants.size() > 0) {
\r
293 for (String var : _variants) {
\r
298 if (_extensions.size() > 0) {
\r
299 Set<Entry<Character, Extension>> exts = _extensions.entrySet();
\r
300 for (Entry<Character, Extension> ext : exts) {
\r
302 buf.append(ext.getKey());
\r
304 buf.append(ext.getValue().getValue());
\r
308 if (_privateuse.length() > 0) {
\r
309 if (buf.length() > 0) {
\r
312 buf.append(PRIVATEUSE);
\r
314 buf.append(_privateuse);
\r
316 return buf.toString();
\r
319 public String toString() {
\r
324 // Language subtag syntax checking methods
\r
327 public static boolean isLanguage(String s) {
\r
328 // language = 2*3ALPHA ; shortest ISO 639 code
\r
329 // ["-" extlang] ; sometimes followed by
\r
330 // ; extended language subtags
\r
331 // / 4ALPHA ; or reserved for future use
\r
332 // / 5*8ALPHA ; or registered language subtag
\r
333 return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaString(s);
\r
336 public static boolean isExtlang(String s) {
\r
337 // extlang = 3ALPHA ; selected ISO 639 codes
\r
338 // *2("-" 3ALPHA) ; permanently reserved
\r
339 return (s.length() == 3) && AsciiUtil.isAlphaString(s);
\r
342 public static boolean isScript(String s) {
\r
343 // script = 4ALPHA ; ISO 15924 code
\r
344 return (s.length() == 4) && AsciiUtil.isAlphaString(s);
\r
347 public static boolean isRegion(String s) {
\r
348 // region = 2ALPHA ; ISO 3166-1 code
\r
349 // / 3DIGIT ; UN M.49 code
\r
350 return ((s.length() == 2) && AsciiUtil.isAlphaString(s))
\r
351 || ((s.length() == 3) && AsciiUtil.isNumericString(s));
\r
354 public static boolean isVariant(String s) {
\r
355 // variant = 5*8alphanum ; registered variants
\r
356 // / (DIGIT 3alphanum)
\r
357 int len = s.length();
\r
358 if (len >= 5 && len <= 8) {
\r
359 return AsciiUtil.isAlphaNumericString(s);
\r
362 return AsciiUtil.isNumeric(s.charAt(0))
\r
363 && AsciiUtil.isAlphaNumeric(s.charAt(1))
\r
364 && AsciiUtil.isAlphaNumeric(s.charAt(2))
\r
365 && AsciiUtil.isAlphaNumeric(s.charAt(3));
\r
370 public static boolean isExtensionSingleton(String s) {
\r
371 // singleton = DIGIT ; 0 - 9
\r
372 // / %x41-57 ; A - W
\r
373 // / %x59-5A ; Y - Z
\r
374 // / %x61-77 ; a - w
\r
375 // / %x79-7A ; y - z
\r
377 return (s.length() == 1)
\r
378 && AsciiUtil.isAlphaString(s)
\r
379 && !AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s);
\r
382 public static boolean isExtensionSubtag(String s) {
\r
383 // extension = singleton 1*("-" (2*8alphanum))
\r
384 return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s);
\r
387 public static boolean isPrivateuseSingleton(String s) {
\r
388 // privateuse = "x" 1*("-" (1*8alphanum))
\r
389 return (s.length() == 1)
\r
390 && AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s);
\r
393 public static boolean isPrivateuseSubtag(String s) {
\r
394 // privateuse = "x" 1*("-" (1*8alphanum))
\r
395 return (s.length() >= 1) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s);
\r
399 // Language subtag canonicalization methods
\r
402 public static String canonicalizeLanguage(String s) {
\r
403 return AsciiUtil.toLowerString(s);
\r
406 public static String canonicalizeExtlang(String s) {
\r
407 return AsciiUtil.toLowerString(s);
\r
410 public static String canonicalizeScript(String s) {
\r
411 return AsciiUtil.toTitleString(s);
\r
414 public static String canonicalizeRegion(String s) {
\r
415 return AsciiUtil.toUpperString(s);
\r
418 public static String canonicalizeVariant(String s) {
\r
419 return AsciiUtil.toLowerString(s);
\r
422 public static String canonicalizeExtensionSingleton(String s) {
\r
423 return AsciiUtil.toLowerString(s);
\r
426 public static String canonicalizeExtensionSubtag(String s) {
\r
427 return AsciiUtil.toLowerString(s);
\r
430 public static String canonicalizePrivateuseSubtag(String s) {
\r
431 return AsciiUtil.toLowerString(s);
\r
435 public static LanguageTag parse(String str, boolean javaCompatVar) {
\r
436 LanguageTag tag = new LanguageTag();
\r
437 tag.parseString(str, javaCompatVar);
\r
441 public static LanguageTag parseStrict(String str, boolean javaCompatVar) throws LocaleSyntaxException {
\r
442 LanguageTag tag = new LanguageTag();
\r
443 ParseStatus sts = tag.parseString(str, javaCompatVar);
\r
444 if (sts.isError()) {
\r
445 throw new LocaleSyntaxException(sts.errorMsg, sts.errorIndex);
\r
450 public static LanguageTag parseLocale(BaseLocale base, LocaleExtensions locExts) {
\r
451 LanguageTag tag = new LanguageTag();
\r
452 tag._javaCompatVariants = true;
\r
454 String language = base.getLanguage();
\r
455 String script = base.getScript();
\r
456 String region = base.getRegion();
\r
457 String variant = base.getVariant();
\r
459 String privuseVar = null; // store ill-formed variant subtags
\r
461 if (language.length() > 0 && isLanguage(language)) {
\r
462 // Convert a deprecated language code used by Java to
\r
464 language = canonicalizeLanguage(language);
\r
465 if (language.equals("iw")) {
\r
467 } else if (language.equals("ji")) {
\r
469 } else if (language.equals("in")) {
\r
472 tag._language = language;
\r
474 if (script.length() > 0 && isScript(script)) {
\r
475 tag._script = canonicalizeScript(script);
\r
477 if (region.length() > 0 && isRegion(region)) {
\r
478 tag._region = canonicalizeRegion(region);
\r
480 if (variant.length() > 0) {
\r
481 List<String> variants = null;
\r
482 StringTokenIterator varitr = new StringTokenIterator(variant, JAVASEP);
\r
483 while (!varitr.isDone()) {
\r
484 String var = varitr.current();
\r
485 if (!isVariant(var)) {
\r
488 if (variants == null) {
\r
489 variants = new ArrayList<String>();
\r
492 variants.add(var); // Do not canonicalize!
\r
494 variants.add(canonicalizeVariant(var));
\r
498 if (variants != null) {
\r
499 tag._variants = variants;
\r
501 if (!varitr.isDone()) {
\r
502 // ill-formed variant subtags
\r
503 StringBuilder buf = new StringBuilder();
\r
504 while (!varitr.isDone()) {
\r
505 String prvv = varitr.current();
\r
506 if (!isPrivateuseSubtag(prvv)) {
\r
507 // cannot use private use subtag - truncated
\r
510 if (buf.length() > 0) {
\r
514 prvv = AsciiUtil.toLowerString(prvv);
\r
519 if (buf.length() > 0) {
\r
520 privuseVar = buf.toString();
\r
525 TreeMap<Character, Extension> extensions = null;
\r
526 String privateuse = null;
\r
528 Set<Character> locextKeys = locExts.getKeys();
\r
529 for (Character locextKey : locextKeys) {
\r
530 Extension ext = locExts.getExtension(locextKey);
\r
531 if (ext instanceof PrivateuseExtension) {
\r
532 privateuse = ext.getValue();
\r
534 if (extensions == null) {
\r
535 extensions = new TreeMap<Character, Extension>();
\r
537 extensions.put(locextKey, ext);
\r
541 if (extensions != null) {
\r
542 tag._extensions = extensions;
\r
545 // append ill-formed variant subtags to private use
\r
546 if (privuseVar != null) {
\r
547 if (privateuse == null) {
\r
548 privateuse = JAVAVARIANT + SEP + privuseVar;
\r
550 privateuse = privateuse + SEP + JAVAVARIANT + SEP + privuseVar.replace(JAVASEP, SEP);
\r
554 if (privateuse != null) {
\r
555 tag._privateuse = privateuse;
\r
556 } else if (tag._language.length() == 0) {
\r
557 // use "und" if neither language nor privateuse is available
\r
558 tag._language = UNDETERMINED;
\r
564 private ParseStatus parseString(String str, boolean javaCompatVar) {
\r
565 // Check if the tag is grandfathered
\r
566 String[] gfmap = GRANDFATHERED.get(new AsciiUtil.CaseInsensitiveKey(str));
\r
568 if (gfmap != null) {
\r
569 _grandfathered = gfmap[0];
\r
570 sts = parseLanguageTag(gfmap[1], javaCompatVar);
\r
571 sts.parseLength = str.length();
\r
573 _grandfathered = "";
\r
574 sts = parseLanguageTag(str, javaCompatVar);
\r
580 * Parse Language-Tag, except grandfathered.
\r
584 * Language-Tag = langtag ; normal language tags
\r
585 * / privateuse ; private use tag
\r
586 * / grandfathered ; grandfathered tags
\r
589 * langtag = language
\r
596 * language = 2*3ALPHA ; shortest ISO 639 code
\r
597 * ["-" extlang] ; sometimes followed by
\r
598 * ; extended language subtags
\r
599 * / 4ALPHA ; or reserved for future use
\r
600 * / 5*8ALPHA ; or registered language subtag
\r
602 * extlang = 3ALPHA ; selected ISO 639 codes
\r
603 * *2("-" 3ALPHA) ; permanently reserved
\r
605 * script = 4ALPHA ; ISO 15924 code
\r
607 * region = 2ALPHA ; ISO 3166-1 code
\r
608 * / 3DIGIT ; UN M.49 code
\r
610 * variant = 5*8alphanum ; registered variants
\r
611 * / (DIGIT 3alphanum)
\r
613 * extension = singleton 1*("-" (2*8alphanum))
\r
615 * ; Single alphanumerics
\r
616 * ; "x" reserved for private use
\r
617 * singleton = DIGIT ; 0 - 9
\r
618 * / %x41-57 ; A - W
\r
619 * / %x59-5A ; Y - Z
\r
620 * / %x61-77 ; a - w
\r
621 * / %x79-7A ; y - z
\r
623 * privateuse = "x" 1*("-" (1*8alphanum))
\r
626 private ParseStatus parseLanguageTag(String langtag, boolean javaCompat) {
\r
627 ParseStatus sts = new ParseStatus();
\r
628 StringTokenIterator itr = new StringTokenIterator(langtag, SEP);
\r
629 Parser parser = javaCompat ? JAVA_VARIANT_COMPATIBLE_PARSER : DEFAULT_PARSER;
\r
631 _javaCompatVariants = javaCompat;
\r
633 // langtag must start with either language or privateuse
\r
634 _language = parser.parseLanguage(itr, sts);
\r
635 if (_language.length() > 0) {
\r
636 _extlangs = parser.parseExtlangs(itr, sts);
\r
637 _script = parser.parseScript(itr, sts);
\r
638 _region = parser.parseRegion(itr, sts);
\r
639 _variants = parser.parseVariants(itr, sts);
\r
640 _extensions = parser.parseExtensions(itr, sts);
\r
642 _privateuse = parser.parsePrivateuse(itr, sts);
\r
644 if (!itr.isDone() && !sts.isError()) {
\r
645 String s = itr.current();
\r
646 sts.errorIndex = itr.currentStart();
\r
647 if (s.length() == 0) {
\r
648 sts.errorMsg = "Empty subtag";
\r
650 sts.errorMsg = "Invalid subtag: " + s;
\r
657 public static class ParseStatus {
\r
658 int parseLength = 0;
\r
659 int errorIndex = -1;
\r
660 String errorMsg = null;
\r
662 public void reset() {
\r
668 boolean isError() {
\r
669 return (errorIndex >= 0);
\r
673 static class Parser {
\r
674 private boolean _javaCompatVar;
\r
676 Parser(boolean javaCompatVar) {
\r
677 _javaCompatVar = javaCompatVar;
\r
681 // Language subtag parsers
\r
684 public String parseLanguage(StringTokenIterator itr, ParseStatus sts) {
\r
685 String language = "";
\r
687 if (itr.isDone() || sts.isError()) {
\r
691 String s = itr.current();
\r
692 if (isLanguage(s)) {
\r
693 language = canonicalizeLanguage(s);
\r
694 sts.parseLength = itr.currentEnd();
\r
700 public List<String> parseExtlangs(StringTokenIterator itr, ParseStatus sts) {
\r
701 List<String> extlangs = null;
\r
703 if (itr.isDone() || sts.isError()) {
\r
704 return Collections.emptyList();
\r
707 while (!itr.isDone()) {
\r
708 String s = itr.current();
\r
709 if (!isExtlang(s)) {
\r
712 if (extlangs == null) {
\r
713 extlangs = new ArrayList<String>(3);
\r
715 extlangs.add(canonicalizeExtlang(s));
\r
716 sts.parseLength = itr.currentEnd();
\r
719 if (extlangs.size() == 3) {
\r
720 // Maximum 3 extlangs
\r
725 if (extlangs == null) {
\r
726 return Collections.emptyList();
\r
732 public String parseScript(StringTokenIterator itr, ParseStatus sts) {
\r
733 String script = "";
\r
735 if (itr.isDone() || sts.isError()) {
\r
739 String s = itr.current();
\r
741 script = canonicalizeScript(s);
\r
742 sts.parseLength = itr.currentEnd();
\r
749 public String parseRegion(StringTokenIterator itr, ParseStatus sts) {
\r
750 String region = "";
\r
752 if (itr.isDone() || sts.isError()) {
\r
756 String s = itr.current();
\r
758 region = canonicalizeRegion(s);
\r
759 sts.parseLength = itr.currentEnd();
\r
766 public List<String> parseVariants(StringTokenIterator itr, ParseStatus sts) {
\r
767 List<String> variants = null;
\r
769 if (itr.isDone() || sts.isError()) {
\r
770 return Collections.emptyList();
\r
773 while (!itr.isDone()) {
\r
774 String s = itr.current();
\r
775 if (!isVariant(s)) {
\r
778 if (variants == null) {
\r
779 variants = new ArrayList<String>(3);
\r
781 if (_javaCompatVar) {
\r
782 // preserve casing when Java compatibility option
\r
786 variants.add(canonicalizeVariant(s));
\r
788 sts.parseLength = itr.currentEnd();
\r
792 if (variants == null) {
\r
793 return Collections.emptyList();
\r
799 public SortedMap<Character, Extension> parseExtensions(StringTokenIterator itr, ParseStatus sts) {
\r
800 SortedMap<Character, Extension> extensionMap = null;
\r
802 if (itr.isDone() || sts.isError()) {
\r
803 return EMPTY_EXTENSION_MAP;
\r
806 while (!itr.isDone()) {
\r
807 String s = itr.current();
\r
808 if (!isExtensionSingleton(s)) {
\r
811 if (!itr.hasNext()) {
\r
812 sts.errorIndex = itr.currentStart();
\r
813 sts.errorMsg = "Missing extension subtag for extension :" + s;
\r
817 if (extensionMap == null) {
\r
818 extensionMap = new TreeMap<Character, Extension>();
\r
821 String singletonStr = canonicalizeExtensionSingleton(s);
\r
822 Character singleton = Character.valueOf(singletonStr.charAt(0));
\r
824 if (extensionMap.containsKey(singleton)) {
\r
825 sts.errorIndex = itr.currentStart();
\r
826 sts.errorMsg = "Duplicated extension: " + s;
\r
831 Extension ext = Extension.create(singleton.charValue(), itr, sts);
\r
833 extensionMap.put(singleton, ext);
\r
835 if (sts.isError()) {
\r
840 if (extensionMap == null || extensionMap.size() == 0) {
\r
841 return EMPTY_EXTENSION_MAP;
\r
844 return extensionMap;
\r
847 public String parsePrivateuse(StringTokenIterator itr, ParseStatus sts) {
\r
848 String privateuse = "";
\r
850 if (itr.isDone() || sts.isError()) {
\r
854 String s = itr.current();
\r
855 if (isPrivateuseSingleton(s)) {
\r
856 StringBuilder buf = new StringBuilder();
\r
857 int singletonOffset = itr.currentStart();
\r
858 boolean preserveCasing = false;
\r
861 while (!itr.isDone()) {
\r
863 if (!isPrivateuseSubtag(s)) {
\r
866 if (buf.length() != 0) {
\r
869 if (!preserveCasing) {
\r
870 s = canonicalizePrivateuseSubtag(s);
\r
873 sts.parseLength = itr.currentEnd();
\r
875 if (_javaCompatVar && s.equals(JAVAVARIANT)) {
\r
876 // preserve casing after the special
\r
877 // java reserved private use subtag
\r
878 // when java compatibility variant option
\r
880 preserveCasing = true;
\r
885 if (buf.length() == 0) {
\r
886 // need at least 1 private subtag
\r
887 sts.errorIndex = singletonOffset;
\r
888 sts.errorMsg = "Incomplete privateuse";
\r
890 privateuse = buf.toString();
\r