xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/GlossonymConstructor.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.ibm.icu.util.Output;
4 import java.util.Set;
5 
6 /**
7  * Automatically construct language names (glossonyms)
8  *
9  * <p>Example: in German (de), for the path
10  *
11  * <p>//ldml/localeDisplayNames/languages/language[@type="ro_MD"]
12  *
13  * <p>the value "Rumänisch (Republik Moldau)" is automatically constructed based on the code
14  * "ro_MD".
15  *
16  * <p>The constructed value is a default if no preferable value is submitted or inherited. A
17  * different (non-constructed) value, such as "Moldauisch", may become the winning value instead of
18  * the constructed value.
19  */
20 public class GlossonymConstructor {
21 
22     /** Some paths with this prefix can get automatically constructed values */
23     public static final String PATH_PREFIX =
24             "//ldml/localeDisplayNames/languages/language[@type=\"";
25 
26     /**
27      * The code such as "ro_MD" must contain an underscore, otherwise there is no constructed value.
28      * Underscore also serves as a clue for recognizing a value that is from code-fallback; for
29      * example, when the value "ro_MD" is inherited, the underscore implies it's a raw code ("bogus
30      * value") and should be replaced by a constructed value like "Rumänisch (Republik Moldau)"; but
31      * when the value "Moldauisch" (without underscore) is inherited, it should not be replaced by a
32      * constructed value
33      */
34     private static final String CODE_SEPARATOR = "_";
35 
36     /**
37      * For "pathWhereFound" when the value is constructed. It is non-null to satisfy
38      * TestPathHeadersAndValues. It should not be treated as an actual path; for example, the Survey
39      * Tool Info Panel should not show a broken "Jump to original" link.
40      */
41     public static final String PSEUDO_PATH = "constructed";
42 
43     /**
44      * Is the given path eligible for getting a constructed value?
45      *
46      * @param xpath the given path
47      * @return true if eligible
48      */
pathIsEligible(String xpath)49     public static boolean pathIsEligible(String xpath) {
50         return xpath.startsWith(PATH_PREFIX) && xpath.contains(CODE_SEPARATOR);
51     }
52 
53     /**
54      * Is the given value bogus, and therefore eligible for getting replaced by a constructed value?
55      *
56      * @param value the given value
57      * @return true if bogus
58      */
valueIsBogus(String value)59     public static boolean valueIsBogus(String value) {
60         return (value == null
61                 || value.contains(CODE_SEPARATOR)
62                 || RegexUtilities.PATTERN_3_OR_4_DIGITS.matcher(value).find());
63     }
64 
65     private final CLDRFile cldrFile;
66 
GlossonymConstructor(CLDRFile cldrFile)67     public GlossonymConstructor(CLDRFile cldrFile) {
68         this.cldrFile = cldrFile;
69         if (!cldrFile.isResolved()) {
70             throw new IllegalArgumentException(
71                     "Unresolved CLDRFile in GlossonymConstructor constructor");
72         }
73     }
74 
75     /**
76      * Get the constructed value and fill in tracking information about where it was found
77      *
78      * @param xpath the path
79      * @param pathWhereFound if not null, to be filled in
80      * @param localeWhereFound if not null, to be filled in
81      * @return the constructed value, or null
82      */
getValueAndTrack( String xpath, Output<String> pathWhereFound, Output<String> localeWhereFound)83     public String getValueAndTrack(
84             String xpath, Output<String> pathWhereFound, Output<String> localeWhereFound) {
85         final String constructedValue = getValue(xpath);
86         if (constructedValue != null) {
87             if (localeWhereFound != null) {
88                 localeWhereFound.value = cldrFile.getLocaleID();
89             }
90             if (pathWhereFound != null) {
91                 pathWhereFound.value = PSEUDO_PATH;
92             }
93             return constructedValue;
94         }
95         return null;
96     }
97 
98     /**
99      * Get the constructed value for the given path
100      *
101      * @param xpath the given path
102      * @return the constructed value, or null
103      */
getValue(String xpath)104     public String getValue(String xpath) {
105         if (pathIsEligible(xpath)) {
106             return reallyGetValue(xpath);
107         }
108         return null;
109     }
110 
reallyGetValue(String xpath)111     private synchronized String reallyGetValue(String xpath) {
112         final XPathParts parts = XPathParts.getFrozenInstance(xpath);
113         final String type = parts.getAttributeValue(-1, "type");
114         if (type.contains(CODE_SEPARATOR)) {
115             final String alt = parts.getAttributeValue(-1, "alt");
116             final CLDRFile.SimpleAltPicker altPicker =
117                     (alt == null) ? null : new CLDRFile.SimpleAltPicker(alt);
118             final String value = cldrFile.getName(type, true, altPicker);
119             if (!valueIsBogus(value)) {
120                 return value;
121             }
122         }
123         return null;
124     }
125 
getPathsWhereFound(String xpath, Set<String> paths)126     public Set<String> getPathsWhereFound(String xpath, Set<String> paths) {
127         final XPathParts parts = XPathParts.getFrozenInstance(xpath);
128         final String type = parts.getAttributeValue(-1, "type");
129         if (type.contains(CODE_SEPARATOR)) {
130             final String alt = parts.getAttributeValue(-1, "alt");
131             final CLDRFile.SimpleAltPicker altPicker =
132                     (alt == null) ? null : new CLDRFile.SimpleAltPicker(alt);
133             final String value = cldrFile.getName(type, true, altPicker, paths);
134             if (!valueIsBogus(value)) {
135                 return paths;
136             }
137         }
138         return null;
139     }
140 }
141