• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.unittest;
2 
3 import com.ibm.icu.impl.Relation;
4 import com.ibm.icu.impl.Row;
5 import com.ibm.icu.impl.Row.R2;
6 import com.ibm.icu.impl.Row.R3;
7 import com.ibm.icu.impl.Utility;
8 import java.util.Arrays;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.LinkedHashSet;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.TreeMap;
17 import java.util.TreeSet;
18 import org.unicode.cldr.util.CLDRConfig;
19 import org.unicode.cldr.util.CLDRFile;
20 import org.unicode.cldr.util.DtdType;
21 import org.unicode.cldr.util.ElementAttributeInfo;
22 import org.unicode.cldr.util.XPathParts;
23 
24 public class TestDTDAttributes extends TestFmwkPlus {
25 
26     static CLDRConfig testInfo = CLDRConfig.getInstance();
27 
28     /**
29      * Simple test that loads each file in the cldr directory, thus verifying that the DTD works,
30      * and also checks that the PrettyPaths work.
31      *
32      * @author markdavis
33      */
main(String[] args)34     public static void main(String[] args) {
35         new TestDTDAttributes().run(args);
36     }
37 
TestOrdering()38     public void TestOrdering() { // Not deemed to be a useful test at this time
39         // - JCE
40         return;
41         // for (DtdType type : DtdType.values()) {
42         // checkOrdering(type);
43         // }
44     }
45 
checkOrdering(DtdType type)46     private void checkOrdering(DtdType type) {
47         Relation<String, String> toChildren =
48                 ElementAttributeInfo.getInstance(type).getElement2Children();
49         // Relation<String, String> toParents =
50         // ElementAttributeInfo.getElement2Parents(type);
51         Set<String> containsOrdered = new LinkedHashSet<String>();
52         for (String element : toChildren.keySet()) {
53             int ordered = 0;
54             Set<String> containedElements = toChildren.getAll(element);
55             for (String contained : containedElements) {
56                 if (contained.equals("cp")) continue;
57                 int currentOrdered = CLDRFile.isOrdered(contained, null) ? 1 : -1;
58                 if (currentOrdered > 0) {
59                     containsOrdered.add(element);
60                 }
61                 if (ordered == 0) {
62                     ordered = currentOrdered;
63                 } else {
64                     if (ordered != currentOrdered) {
65                         String error =
66                                 type + "\tMixed ordering inside\t" + element + ":\tordered:\t";
67                         for (String contained2 : containedElements) {
68                             if (contained.equals("cp") || !CLDRFile.isOrdered(contained2, null))
69                                 continue;
70                             error += " " + contained2;
71                         }
72                         error += ":\tunordered:\t";
73                         for (String contained2 : containedElements) {
74                             if (contained.equals("cp") || CLDRFile.isOrdered(contained2, null))
75                                 continue;
76                             error += " " + contained2;
77                         }
78                         errln(error);
79                         break;
80                     }
81                 }
82             }
83         }
84         if (containsOrdered.size() != 0) {
85             logln("                  " + "// DTD: " + type);
86             for (String element : containsOrdered) {
87                 logln("                  " + "// <" + element + "> children");
88                 StringBuilder line = new StringBuilder("                  ");
89                 for (String contained : toChildren.getAll(element)) {
90                     line.append("\"" + contained + "\", ");
91                 }
92                 logln(line.toString());
93             }
94         }
95     }
96 
TestDistinguishing()97     public void TestDistinguishing() {
98         for (DtdType type : DtdType.values()) {
99             if (type.getStatus() != DtdType.DtdStatus.active) {
100                 continue;
101             }
102             if (type == DtdType.ldmlICU) {
103                 continue;
104             }
105             showDistinguishing(type);
106         }
107     }
108 
showDistinguishing(DtdType dtdType)109     private void showDistinguishing(DtdType dtdType) {
110         Relation<String, String> elementToAttributes =
111                 ElementAttributeInfo.getInstance(dtdType).getElement2Attributes();
112         Relation<String, String> distinguishingAttributeToElements =
113                 Relation.<String, String>of(new TreeMap<String, Set<String>>(), TreeSet.class);
114         Relation<String, String> nondistinguishingAttributeToElements =
115                 Relation.<String, String>of(new TreeMap<String, Set<String>>(), TreeSet.class);
116         Relation<String, String> orderedAttributeToElements =
117                 Relation.<String, String>of(new TreeMap<String, Set<String>>(), TreeSet.class);
118         Map<R2<String, String>, R3<Set<String>, String, String>> attributeData =
119                 ElementAttributeInfo.getInstance(dtdType).getElementAttribute2Data();
120 
121         for (String element : elementToAttributes.keySet()) {
122             boolean isOrdered = CLDRFile.isOrdered(element, dtdType);
123             Set<String> attributes = elementToAttributes.getAll(element);
124             for (String attribute : attributes) {
125                 if (isOrdered) {
126                     orderedAttributeToElements.put(attribute, element);
127                 }
128                 if (CLDRFile.isDistinguishing(dtdType, element, attribute)) {
129                     distinguishingAttributeToElements.put(attribute, element);
130                 } else {
131                     nondistinguishingAttributeToElements.put(attribute, element);
132                 }
133             }
134         }
135 
136         // distinguishing elements should be required
137         // make exception for alt
138         for (String attribute : distinguishingAttributeToElements.keySet()) {
139             if (attribute.equals("alt")) continue;
140             for (String element : distinguishingAttributeToElements.getAll(attribute)) {
141                 R3<Set<String>, String, String> attributeInfo =
142                         attributeData.get(Row.of(element, attribute));
143                 if (!"REQUIRED".equals(attributeInfo.get1())) {
144                     logln(
145                             dtdType
146                                     + "\t"
147                                     + element
148                                     + "\t"
149                                     + attribute
150                                     + "\tDistinguishing attribute, but not REQUIRED in DTD");
151                 }
152             }
153         }
154 
155         Set<String> nondistinguishing =
156                 new TreeSet<String>(nondistinguishingAttributeToElements.keySet());
157         nondistinguishing.removeAll(distinguishingAttributeToElements.keySet());
158         logln("// " + dtdType + "\tnondistinguishing: " + nondistinguishing);
159 
160         Set<String> distinguishing =
161                 new TreeSet<String>(distinguishingAttributeToElements.keySet());
162         nondistinguishing.removeAll(nondistinguishingAttributeToElements.keySet());
163         logln("// " + dtdType + "\tdistinguishing: " + distinguishing);
164 
165         Set<String> both = new TreeSet<String>(distinguishingAttributeToElements.keySet());
166         both.retainAll(nondistinguishingAttributeToElements.keySet());
167         logln("// " + dtdType + "\tboth: " + both);
168 
169         for (String attribute : distinguishing) {
170             logln(
171                     "{\"" + "dist" + "\", \"" + dtdType + "\", \"" + "*" + "\", \"" + attribute
172                             + "\"},");
173         }
174         for (String attribute : both) {
175             for (String element : nondistinguishingAttributeToElements.getAll(attribute)) {
176                 logln(
177                         "{\"" + "nondist" + "\", \"" + dtdType + "\", \"" + element + "\", \""
178                                 + attribute + "\"},");
179             }
180         }
181     }
182 
TestAttributes()183     public void TestAttributes() {
184         checkAttributes(DtdType.ldml, DtdType.supplementalData);
185         checkAttributes(DtdType.ldml, DtdType.ldmlBCP47);
186         checkAttributes(DtdType.ldmlBCP47, DtdType.supplementalData);
187     }
188 
checkAttributes(DtdType dtdType1, DtdType dtdType2)189     private void checkAttributes(DtdType dtdType1, DtdType dtdType2) {
190         Map<R2<String, String>, R3<Set<String>, String, String>> mainData =
191                 ElementAttributeInfo.getInstance(dtdType1).getElementAttribute2Data();
192         Map<R2<String, String>, R3<Set<String>, String, String>> suppData =
193                 ElementAttributeInfo.getInstance(dtdType2).getElementAttribute2Data();
194         Set<R2<String, String>> commonKeys =
195                 getCommon(mainData.keySet(), suppData.keySet(), new TreeSet<R2<String, String>>());
196         Set<R2<String, String>> same = new TreeSet<R2<String, String>>();
197         for (R2<String, String> key : commonKeys) {
198             R3<Set<String>, String, String> mainValue = mainData.get(key);
199             R3<Set<String>, String, String> suppValue = suppData.get(key);
200             if (mainValue.equals(suppValue)) {
201                 same.add(key);
202             } else {
203                 errln(
204                         "Different attribute properties across DTDs"
205                                 + key.toString()
206                                 + "\t"
207                                 + dtdType1.toString()
208                                 + ":\t"
209                                 + mainValue.toString()
210                                 + "\t"
211                                 + dtdType2.toString()
212                                 + ":\t"
213                                 + suppValue.toString());
214             }
215         }
216         for (R2<String, String> key : same) {
217             logln(dtdType1 + " and " + dtdType2 + ":\t" + key + "\t" + mainData.get(key));
218         }
219         for (R2<String, String> key : mainData.keySet()) {
220             if (commonKeys.contains(key)) continue;
221             logln(dtdType1 + ":\t" + key + "\t" + mainData.get(key));
222         }
223         for (R2<String, String> key : suppData.keySet()) {
224             if (commonKeys.contains(key)) continue;
225             logln(dtdType2 + ":\t" + key + "\t" + suppData.get(key));
226         }
227     }
228 
229     @SuppressWarnings({"rawtypes", "unchecked"})
getCommon(T a, T b, T result)230     private <T extends Collection> T getCommon(T a, T b, T result) {
231         result.addAll(a);
232         result.retainAll(b);
233         return result;
234     }
235 
TestElements()236     public void TestElements() { // Not deemed to be a useful test at this time
237         // - JCE
238         // checkElements(DtdType.ldml, DtdType.supplementalData);
239         // checkElements(DtdType.ldml, DtdType.ldmlBCP47);
240         // checkElements(DtdType.ldmlBCP47, DtdType.supplementalData);
241     }
242 
checkElements(DtdType dtdType1, DtdType dtdType2)243     private void checkElements(DtdType dtdType1, DtdType dtdType2) {
244         Relation<String, String> mainData =
245                 ElementAttributeInfo.getInstance(dtdType1).getElement2Children();
246         Relation<String, String> suppData =
247                 ElementAttributeInfo.getInstance(dtdType2).getElement2Children();
248         Set<String> commonKeys =
249                 getCommon(mainData.keySet(), suppData.keySet(), new TreeSet<String>());
250         Set<String> same = new TreeSet<String>();
251         for (String key : commonKeys) {
252             Set<String> mainValues = mainData.getAll(key);
253             Set<String> suppValues = suppData.getAll(key);
254             if (mainValues.equals(suppValues)) {
255                 same.add(key);
256                 continue;
257             }
258             errln(
259                     "DTD elements have different children\t"
260                             + key
261                             + "\t"
262                             + dtdType1
263                             + ":\t"
264                             + mainValues
265                             + "\t"
266                             + dtdType2
267                             + ":\t"
268                             + suppValues);
269         }
270         for (String key : same) {
271             logln(dtdType1 + " and " + dtdType2 + ":\t" + key + "\t" + mainData.getAll(key));
272         }
273         for (String key : mainData.keySet()) {
274             if (commonKeys.contains(key)) continue;
275             logln(dtdType1 + ":\t" + key + "\t" + mainData.getAll(key));
276         }
277         for (String key : suppData.keySet()) {
278             if (commonKeys.contains(key)) continue;
279             logln(dtdType2 + ":\t" + key + "\t" + suppData.getAll(key));
280         }
281     }
282 
TestEmpty()283     public void TestEmpty() { // not deemed to be a useful test at this time -
284         // JCE
285         // for (DtdType type : DtdType.values()) {
286         // checkEmpty(type);
287         // }
288     }
289 
290     static final Set<String> COLLATION_SINGLETONS =
291             new HashSet<String>(
292                     Arrays.asList(
293                             new String[] {
294                                 "first_non_ignorable",
295                                 "first_primary_ignorable",
296                                 "first_secondary_ignorable",
297                                 "first_tertiary_ignorable",
298                                 "first_trailing",
299                                 "first_variable",
300                                 "last_non_ignorable",
301                                 "last_primary_ignorable",
302                                 "last_secondary_ignorable",
303                                 "last_tertiary_ignorable",
304                                 "last_trailing last_variable",
305                                 "last_trailing",
306                                 "last_variable"
307                             }));
308 
checkEmpty(DtdType type)309     private void checkEmpty(DtdType type) {
310         Relation<String, String> mainData =
311                 ElementAttributeInfo.getInstance(type).getElement2Parents();
312         Relation<String, String> elementToAttributes =
313                 ElementAttributeInfo.getInstance(type).getElement2Attributes();
314         Map<R2<String, String>, R3<Set<String>, String, String>> eaData =
315                 ElementAttributeInfo.getInstance(type).getElementAttribute2Data();
316 
317         Set<String> empties = mainData.getAll("EMPTY");
318         for (String empty : empties) {
319             if (COLLATION_SINGLETONS.contains(empty)) continue;
320             Set<String> attributes = elementToAttributes.getAll(empty);
321             if (attributes == null) {
322                 errln("Is EMPTY but no attributes:\t" + type + ", " + empty);
323                 continue;
324             }
325             for (String attribute : attributes) {
326                 if (attribute.equals("draft") || attribute.equals("references")) continue;
327                 logln(
328                         type
329                                 + ", "
330                                 + empty
331                                 + ", "
332                                 + attribute
333                                 + ", "
334                                 + eaData.get(Row.of(empty, attribute)));
335             }
336         }
337     }
338 
TestLeaf()339     public void TestLeaf() { // Not deemed to be a useful test at this time -
340         // JCE
341         // for (DtdType type : DtdType.values()) {
342         // checkLeaf(type, "PCDATA");
343         // }
344     }
345 
checkLeaf(DtdType type, String contained)346     private void checkLeaf(DtdType type, String contained) {
347         Relation<String, String> mainData =
348                 ElementAttributeInfo.getInstance(type).getElement2Parents();
349         Relation<String, String> elementToAttributes =
350                 ElementAttributeInfo.getInstance(type).getElement2Attributes();
351         Map<R2<String, String>, R3<Set<String>, String, String>> eaData =
352                 ElementAttributeInfo.getInstance(type).getElementAttribute2Data();
353 
354         Set<String> data = mainData.getAll(contained);
355         if (data == null) return;
356         for (String element : data) {
357             Set<String> attributes = elementToAttributes.getAll(element);
358             if (attributes == null) {
359                 logln(type + ", " + contained + ", " + element + ", No attributes!");
360                 continue;
361             }
362             for (String attribute : attributes) {
363                 if (attribute.equals("draft") || attribute.equals("references")) continue;
364                 logln(
365                         type
366                                 + ", "
367                                 + contained
368                                 + ", "
369                                 + element
370                                 + ", "
371                                 + attribute
372                                 + ", "
373                                 + eaData.get(Row.of(element, attribute)));
374             }
375         }
376     }
377 
TestZNodeData()378     public void TestZNodeData() { // Not deemed to be a useful test at this time
379         // - JCE
380         // for (DtdType type : DtdType.values()) {
381         // checkNodeData(type);
382         // }
383     }
384 
checkNodeData(DtdType type)385     public void checkNodeData(DtdType type) {
386         CurrentData.NodeData data = CurrentData.fullNodeData.get(type);
387         Relation<String, String> element2Parents =
388                 ElementAttributeInfo.getInstance(type).getElement2Parents();
389         Relation<String, String> element2Children =
390                 ElementAttributeInfo.getInstance(type).getElement2Children();
391         Relation<String, String> elementToAttributes = data.elementToAttributes;
392 
393         Relation<String, String> dtdElementToAttributes =
394                 ElementAttributeInfo.getInstance(type).getElement2Attributes();
395         Map<R2<String, String>, R3<Set<String>, String, String>> attributeData =
396                 ElementAttributeInfo.getInstance(type).getElementAttribute2Data();
397 
398         Set<String> possibleElements = dtdElementToAttributes.keySet();
399         Set<String> foundElements = elementToAttributes.keySet();
400         if (!foundElements.equals(possibleElements)) {
401             Set<String> missing = new TreeSet<String>(possibleElements);
402             missing.removeAll(foundElements);
403             errln(type + "\t" + "Elements defined but not in data:\t" + missing);
404         }
405 
406         // attributes not found
407         for (String element : dtdElementToAttributes.keySet()) {
408             Set<String> dtdAttributes = dtdElementToAttributes.getAll(element);
409             Set<String> actualAttributes = remove_q(elementToAttributes.getAll(element));
410             Set<String> attributesAlwaysFound =
411                     remove_q(data.elementToAttributesAlwaysFound.getAll(element));
412 
413             if (!dtdAttributes.containsAll(actualAttributes)
414                     || !dtdAttributes.containsAll(attributesAlwaysFound)) {
415                 errln(
416                         type
417                                 + "\t"
418                                 + "Actual attributes exceed DTD attributes:\t"
419                                 + type
420                                 + ", "
421                                 + element
422                                 + ", "
423                                 + dtdAttributes
424                                 + ", "
425                                 + actualAttributes
426                                 + ", "
427                                 + attributesAlwaysFound);
428             }
429 
430             Set<String> notFound = new TreeSet<String>(dtdAttributes);
431             notFound.removeAll(actualAttributes);
432             notFound.remove("draft");
433             notFound.remove("references");
434             notFound.remove("standard");
435             notFound.remove("alt");
436             notFound.remove("validSubLocales");
437             if (notFound.size() != 0) {
438                 warnln(
439                         type
440                                 + "\tAttributes not found for:\t"
441                                 + element
442                                 + "\tnotFound:\t"
443                                 + notFound
444                                 + "\tfound:\t"
445                                 + actualAttributes);
446             }
447 
448             for (String attributeAlwaysFound : attributesAlwaysFound) {
449                 // make sure REQUIRED; not really an error, but for now...
450                 R3<Set<String>, String, String> attributeDatum =
451                         attributeData.get(Row.of(element, attributeAlwaysFound));
452                 if (attributeDatum == null) {
453                     errln(
454                             type
455                                     + "\tData not found for "
456                                     + type
457                                     + ", "
458                                     + element
459                                     + ", "
460                                     + attributeAlwaysFound);
461                     continue;
462                 }
463                 if (!"#REQUIRED".equals(attributeDatum.get1())) {
464                     if (attributeDatum.get1() == null) {
465                         warnln(
466                                 type
467                                         + "\tAttribute not REQUIRED but ALWAYS found, element:\t"
468                                         + element
469                                         + "\tattribute:\t"
470                                         + attributeAlwaysFound);
471                     }
472                 }
473             }
474         }
475 
476         Set<String> empties = element2Parents.getAll("EMPTY");
477 
478         Set<String> overlap =
479                 getCommon(
480                         data.valueNodes.keySet(), data.branchNodes.keySet(), new TreeSet<String>());
481         for (String s : overlap) {
482             warnln(
483                     type
484                             + "\tOverlap in value and branch!!\t"
485                             + s
486                             + "\tvalue:\t"
487                             + data.valueNodes.get(s)
488                             + "\tbranch:\t"
489                             + data.branchNodes.get(s));
490         }
491         for (String s : data.valueNodes.keySet()) {
492             if (overlap.contains(s)) continue;
493             logln(type + "\tLeaf:\t" + s + "\t\t" + data.valueNodes.get(s));
494         }
495         for (String s : data.branchNodes.keySet()) {
496             if (overlap.contains(s)) continue;
497             logln(type + "\tBranch:\t" + s + "\t\t" + data.branchNodes.get(s));
498         }
499 
500         overlap =
501                 getCommon(
502                         data.valueNodes.keySet(),
503                         data.valuelessNodes.keySet(),
504                         new TreeSet<String>());
505         for (String s : overlap) {
506             warnln(
507                     type
508                             + "\tOverlap in value and valueless!!\t"
509                             + s
510                             + "\tvalue:\t"
511                             + data.valueNodes.get(s)
512                             + "\tvalueless:\t"
513                             + data.valuelessNodes.get(s));
514         }
515 
516         for (String s : data.valueNodes.keySet()) {
517             if (overlap.contains(s)) continue;
518             logln(type + "\tValue:\t" + s + "\t\t" + data.valueNodes.get(s));
519         }
520         for (String s : data.valuelessNodes.keySet()) {
521             if (overlap.contains(s)) continue;
522             logln(type + "\tValueless:\t" + s + "\t\t" + data.valuelessNodes.get(s));
523             Set<String> containing = element2Children.getAll(s);
524             if (!empties.contains(s) && containing.contains("PCDATA")) {
525                 errln(type + "\t***Should be empty in DTD but isn't:\t" + s + "\t" + containing);
526             }
527         }
528     }
529 
remove_q(Set<String> actualAttributes)530     private Set<String> remove_q(Set<String> actualAttributes) {
531         if (actualAttributes == null) {
532             actualAttributes = Collections.emptySet();
533         } else if (actualAttributes.contains("_q")) {
534             actualAttributes = new LinkedHashSet<String>(actualAttributes);
535             actualAttributes.remove("_q");
536         }
537         return actualAttributes;
538     }
539 
540     static class CurrentData {
541         static class NodeData {
542             final DtdType myType;
543             Map<String, R2<String, String>> branchNodes = new TreeMap<String, R2<String, String>>();
544             Map<String, R3<String, String, String>> valueNodes =
545                     new TreeMap<String, R3<String, String, String>>();
546             Map<String, R2<String, String>> valuelessNodes =
547                     new TreeMap<String, R2<String, String>>();
548             Relation<String, String> elementToAttributes =
549                     Relation.<String, String>of(new TreeMap<String, Set<String>>(), TreeSet.class);
550             Relation<String, String> elementToAttributesAlwaysFound =
551                     Relation.<String, String>of(new TreeMap<String, Set<String>>(), TreeSet.class);
552 
NodeData(DtdType type)553             NodeData(DtdType type) {
554                 myType = type;
555             }
556         }
557 
558         static Map<DtdType, NodeData> fullNodeData = new HashMap<DtdType, NodeData>();
559 
560         static {
561             for (String locale : testInfo.getCldrFactory().getAvailable()) {
562                 CLDRFile file = testInfo.getCLDRFile(locale, false);
563                 NodeData nodeData = null;
564                 for (String xpath : file) {
565                     String value = file.getStringValue(xpath);
566                     String fullXpath = file.getFullXPath(xpath);
567                     XPathParts parts = XPathParts.getFrozenInstance(fullXpath);
568                     if (nodeData == null) {
569                         String root = parts.getElement(0);
570                         DtdType type = DtdType.valueOf(root);
571                         nodeData = fullNodeData.get(type);
572                         if (nodeData == null) {
fullNodeData.put(type, nodeData = new NodeData(type))573                             fullNodeData.put(type, nodeData = new NodeData(type));
574                         }
575                     }
576                     int last = parts.size() - 1;
577                     String element = null;
578                     for (int i = 0; i <= last; ++i) {
579                         element = parts.getElement(i);
580                         Collection<String> attributes = parts.getAttributeKeys(i);
nodeData.elementToAttributes.putAll(element, attributes)581                         nodeData.elementToAttributes.putAll(element, attributes);
582                         Set<String> oldAlways =
583                                 nodeData.elementToAttributesAlwaysFound.getAll(element);
584                         if (oldAlways == null) {
nodeData.elementToAttributesAlwaysFound.putAll(element, attributes)585                             nodeData.elementToAttributesAlwaysFound.putAll(element, attributes);
586                         } else {
587                             // need retainAll, removeAll
588                             for (String old : new TreeSet<String>(oldAlways)) {
589                                 if (!attributes.contains(old)) {
nodeData.elementToAttributesAlwaysFound.remove(element, old)590                                     nodeData.elementToAttributesAlwaysFound.remove(element, old);
591                                 }
592                             }
593                         }
594                         if (i != last) {
putIfNew(nodeData.branchNodes, element, locale, xpath)595                             putIfNew(nodeData.branchNodes, element, locale, xpath);
596                         } else {
597                             if (value.length() > 0) {
putIfNew(nodeData.valueNodes, element, value, locale, xpath)598                                 putIfNew(nodeData.valueNodes, element, value, locale, xpath);
599                             } else {
putIfNew(nodeData.valuelessNodes, element, locale, xpath)600                                 putIfNew(nodeData.valuelessNodes, element, locale, xpath);
601                             }
602                         }
603                     }
604                 }
605             }
606         }
607 
putIfNew( Map<String, R2<String, String>> map, String key, String locale, String xpath)608         static void putIfNew(
609                 Map<String, R2<String, String>> map, String key, String locale, String xpath) {
610             if (!map.containsKey(key)) {
611                 map.put(key, Row.of(locale, xpath));
612             }
613         }
614 
putIfNew( Map<String, R3<String, String, String>> map, String key, String value, String locale, String xpath)615         static void putIfNew(
616                 Map<String, R3<String, String, String>> map,
617                 String key,
618                 String value,
619                 String locale,
620                 String xpath) {
621             if (!map.containsKey(key)) {
622                 if (value.length() > 30) value = value.substring(0, 30) + "...";
623                 value = value.replace("\n", "\\n");
624                 map.put(key, Row.of(value, locale, xpath));
625             }
626         }
627     }
628 
629     // public void TestStructure() {
630     // for (DtdType type : DtdType.values()) {
631     // logln("*= distinguished, \u2021=ordered, \u2020=default");
632     // Relation<String, String> toChildren =
633     // ElementAttributeInfo.getInstance(type).getElement2Children();
634     // Relation<String, String> toAttributes =
635     // ElementAttributeInfo.getInstance(type).getElement2Attributes();
636     // Map<R2<String, String>, R3<Set<String>, String, String>> toAttributeData
637     // = ElementAttributeInfo
638     // .getInstance(type).getElementAttribute2Data();
639     // checkStructure(type, type.toString(), 0, toChildren, toAttributes,
640     // toAttributeData);
641     // }
642     // }
643 
checkStructure( DtdType dtdType, String element, int indent, Relation<String, String> toChildren, Relation<String, String> toAttributes, Map<R2<String, String>, R3<Set<String>, String, String>> toAttributeData)644     private void checkStructure(
645             DtdType dtdType,
646             String element,
647             int indent,
648             Relation<String, String> toChildren,
649             Relation<String, String> toAttributes,
650             Map<R2<String, String>, R3<Set<String>, String, String>> toAttributeData) {
651         Set<String> myChildren = toChildren.getAll(element);
652         String values = "";
653         boolean skipChildren = false;
654         if (myChildren != null) {
655             if (myChildren.contains("PCDATA")) {
656                 values = "\tVALUE=" + myChildren.toString();
657                 skipChildren = true;
658             } else if (myChildren.contains("EMPTY")) {
659                 // values = ";\t\tVALUE=" + myChildren.toString();
660                 skipChildren = true;
661             }
662         }
663         String elementName = element;
664         if (CLDRFile.isOrdered(element, dtdType)) {
665             elementName += "\u2021";
666         }
667         logln(
668                 Utility.repeat("\t", indent)
669                         + elementName
670                         + checkAttributeStructure(
671                                 dtdType, element, toAttributes.getAll(element), toAttributeData)
672                         + values);
673         if (myChildren == null || skipChildren) return;
674         for (String child : myChildren) {
675             checkStructure(dtdType, child, indent + 1, toChildren, toAttributes, toAttributeData);
676         }
677     }
678 
checkAttributeStructure( DtdType dtdType, String element, Set<String> attributes, Map<R2<String, String>, R3<Set<String>, String, String>> toAttributeData)679     private String checkAttributeStructure(
680             DtdType dtdType,
681             String element,
682             Set<String> attributes,
683             Map<R2<String, String>, R3<Set<String>, String, String>> toAttributeData) {
684         if (attributes == null) return "";
685         String result = "";
686         for (String attribute : attributes) {
687             if (attribute.equals("alt")
688                     || attribute.equals("draft")
689                     || attribute.equals("standard")
690                     || attribute.equals("references")) continue;
691             if (result.length() != 0) result += "\t";
692             R3<Set<String>, String, String> data = toAttributeData.get(Row.of(element, attribute));
693             if (CLDRFile.isDistinguishing(dtdType, element, attribute)) {
694                 attribute += "*";
695             }
696             attribute += "=" + formatForAttribute(data.get0(), data.get1(), data.get2());
697             result += attribute;
698         }
699         if (result.length() == 0) return result;
700         return "\t" + result;
701         // draft, standard, references, alt
702     }
703 
formatForAttribute(Set<String> type, String mode, String value)704     private String formatForAttribute(Set<String> type, String mode, String value) {
705         String first = type.iterator().next();
706         String typeName = attributeTypes.contains(first) ? first : type.toString();
707         if (mode == null) {
708             if (value == null) {
709                 throw new IllegalArgumentException(type + ", " + mode + ", " + value);
710             }
711             return typeName.replace(value, value + "\u2020");
712         }
713         if (mode.equals("#FIXED")) return value;
714         if (value != null) {
715             throw new IllegalArgumentException(type + ", " + mode + ", " + value);
716         }
717         if (mode.equals("#REQUIRED")) {
718             return typeName;
719         }
720         if (mode.equals("#IMPLIED")) {
721             return typeName + "?";
722         }
723         throw new IllegalArgumentException(type + ", " + mode + ", " + value);
724     }
725 
726     static Set<String> attributeTypes =
727             new HashSet<String>(
728                     Arrays.asList(
729                             new String[] {
730                                 "CDATA",
731                                 "ID",
732                                 "IDREF",
733                                 "IDREFS",
734                                 "ENTITY",
735                                 "ENTITIES",
736                                 "NMTOKEN",
737                                 "NMTOKENS"
738                             }));
739 
740     static Set<String> attributeMode =
741             new HashSet<String>(Arrays.asList(new String[] {"#REQUIRED", "#IMPLIED", "#FIXED"}));
742 }
743