• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.xsdc;
18 
19 import com.android.xsdc.tag.*;
20 
21 import org.xml.sax.Attributes;
22 import org.xml.sax.Locator;
23 import org.xml.sax.SAXException;
24 import org.xml.sax.helpers.DefaultHandler;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Stack;
33 import java.util.stream.Collectors;
34 
35 import javax.xml.namespace.QName;
36 
37 public class XsdHandler extends DefaultHandler {
38     private static class State {
39         final String name;
40         final Map<String, String> attributeMap;
41         final List<XsdTag> tags;
42         boolean deprecated;
43 
State(String name, Map<String, String> attributeMap)44         State(String name, Map<String, String> attributeMap) {
45             this.name = name;
46             this.attributeMap = Collections.unmodifiableMap(attributeMap);
47             tags = new ArrayList<>();
48             deprecated = false;
49         }
50     }
51 
52     private XmlSchema schema;
53     private final Stack<State> stateStack;
54     private final Map<String, String> namespaces;
55     private Locator locator;
56     private boolean documentationFlag;
57     private boolean enumerationFlag;
58 
XsdHandler()59     public XsdHandler() {
60         stateStack = new Stack<>();
61         namespaces = new HashMap<>();
62         documentationFlag = false;
63         enumerationFlag = false;
64     }
65 
getSchema()66     public XmlSchema getSchema() {
67         return schema;
68     }
69 
70     @Override
setDocumentLocator(Locator locator)71     public void setDocumentLocator(Locator locator) {
72         this.locator = locator;
73     }
74 
75     @Override
startPrefixMapping(String prefix, String uri)76     public void startPrefixMapping(String prefix, String uri) {
77         namespaces.put(prefix, uri);
78     }
79 
80     @Override
endPrefixMapping(String prefix)81     public void endPrefixMapping(String prefix) {
82         namespaces.remove(prefix);
83     }
84 
parseQName(String str)85     private QName parseQName(String str) throws XsdParserException {
86         if (str == null) return null;
87         String[] parsed = str.split(":");
88         if (parsed.length == 2) {
89             return new QName(namespaces.get(parsed[0]), parsed[1]);
90         } else if (parsed.length == 1) {
91             return new QName(null, str);
92         }
93         throw new XsdParserException(String.format("QName parse error : %s", str));
94     }
95 
parseQNames(String str)96     private List<QName> parseQNames(String str) throws XsdParserException {
97         List<QName> qNames = new ArrayList<>();
98         if (str == null) return qNames;
99         String[] parsed = str.split("\\s+");
100         for (String s : parsed) {
101             qNames.add(parseQName(s));
102         }
103         return qNames;
104     }
105 
106     @Override
startElement( String uri, String localName, String qName, Attributes attributes)107     public void startElement(
108             String uri, String localName, String qName, Attributes attributes) {
109         // we need to copy attributes because it is mutable..
110         Map<String, String> attributeMap = new HashMap<>();
111         for (int i = 0; i < attributes.getLength(); ++i) {
112             attributeMap.put(attributes.getLocalName(i), attributes.getValue(i));
113         }
114         if (!documentationFlag) {
115             stateStack.push(new State(localName, attributeMap));
116         }
117         if (localName == "documentation") {
118             documentationFlag = true;
119         }
120     }
121 
122     @Override
endElement(String uri, String localName, String qName)123     public void endElement(String uri, String localName, String qName) throws SAXException {
124         if (documentationFlag && localName != "documentation") {
125             return;
126         }
127         try {
128             State state = stateStack.pop();
129             switch (state.name) {
130                 case "schema":
131                     schema = makeSchema(state);
132                     break;
133                 case "element":
134                     stateStack.peek().tags.add(makeElement(state));
135                     break;
136                 case "attribute":
137                     stateStack.peek().tags.add(makeAttribute(state));
138                     break;
139                 case "complexType":
140                     stateStack.peek().tags.add(makeComplexType(state));
141                     break;
142                 case "complexContent":
143                     stateStack.peek().tags.add(makeComplexContent(state));
144                     break;
145                 case "simpleContent":
146                     stateStack.peek().tags.add(makeSimpleContent(state));
147                     break;
148                 case "restriction":
149                     if (enumerationFlag) {
150                         stateStack.peek().tags.add(makeEnumRestriction(state));
151                         enumerationFlag = false;
152                     } else {
153                         stateStack.peek().tags.add(makeGeneralRestriction(state));
154                     }
155                     break;
156                 case "extension":
157                     stateStack.peek().tags.add(makeGeneralExtension(state));
158                     break;
159                 case "simpleType":
160                     stateStack.peek().tags.add(makeSimpleType(state));
161                     break;
162                 case "list":
163                     stateStack.peek().tags.add(makeSimpleTypeList(state));
164                     break;
165                 case "union":
166                     stateStack.peek().tags.add(makeSimpleTypeUnion(state));
167                     break;
168                 case "sequence":
169                     stateStack.peek().tags.addAll(makeSequence(state));
170                     break;
171                 case "choice":
172                     stateStack.peek().tags.addAll(makeChoice(state));
173                     break;
174                 case "all":
175                     stateStack.peek().tags.addAll(makeAll(state));
176                     break;
177                 case "enumeration":
178                     stateStack.peek().tags.add(makeEnumeration(state));
179                     enumerationFlag = true;
180                     break;
181                 case "fractionDigits":
182                 case "length":
183                 case "maxExclusive":
184                 case "maxInclusive":
185                 case "maxLength":
186                 case "minExclusive":
187                 case "minInclusive":
188                 case "minLength":
189                 case "pattern":
190                 case "totalDigits":
191                 case "whiteSpace":
192                     // Tags under simpleType <restriction>. They are ignored.
193                     break;
194                 case "annotation":
195                     stateStack.peek().deprecated = isDeprecated(state.attributeMap, state.tags);
196                     break;
197                 case "appinfo":
198                     // They function like comments, so are ignored.
199                     break;
200                 case "documentation":
201                     documentationFlag = false;
202                     break;
203                 case "key":
204                 case "keyref":
205                 case "selector":
206                 case "field":
207                 case "unique":
208                     // These tags are not related to xml parsing.
209                     // They are using when validating xml files via xsd file.
210                     // So they are ignored.
211                     break;
212                 default:
213                     throw new XsdParserException(String.format("unsupported tag : %s", state.name));
214             }
215         } catch (XsdParserException e) {
216             throw new SAXException(
217                     String.format("Line %d, Column %d - %s",
218                             locator.getLineNumber(), locator.getColumnNumber(), e.getMessage()));
219         }
220     }
221 
makeSchema(State state)222     private XmlSchema makeSchema(State state) {
223         Map<String, XsdElement> elementMap = new LinkedHashMap<>();
224         Map<String, XsdType> typeMap = new LinkedHashMap<>();
225         Map<String, XsdAttribute> attrMap = new LinkedHashMap<>();
226 
227         for (XsdTag tag : state.tags) {
228             if (tag == null) continue;
229             if (tag instanceof XsdElement) {
230                 elementMap.put(tag.getName(), (XsdElement) tag);
231             } else if (tag instanceof XsdAttribute) {
232                 attrMap.put(tag.getName(), (XsdAttribute) tag);
233             } else if (tag instanceof XsdType) {
234                 typeMap.put(tag.getName(), (XsdType) tag);
235             }
236         }
237 
238         return new XmlSchema(elementMap, typeMap, attrMap);
239     }
240 
makeElement(State state)241     private XsdElement makeElement(State state) throws XsdParserException {
242         String name = state.attributeMap.get("name");
243         QName typename = parseQName(state.attributeMap.get("type"));
244         QName ref = parseQName(state.attributeMap.get("ref"));
245         String isAbstract = state.attributeMap.get("abstract");
246         String defVal = state.attributeMap.get("default");
247         String substitutionGroup = state.attributeMap.get("substitutionGroup");
248         String maxOccurs = state.attributeMap.get("maxOccurs");
249 
250         if ("true".equals(isAbstract)) {
251             throw new XsdParserException("abstract element is not supported.");
252         }
253         if (defVal != null) {
254             throw new XsdParserException("default value of an element is not supported.");
255         }
256         if (substitutionGroup != null) {
257             throw new XsdParserException("substitution group of an element is not supported.");
258         }
259 
260         boolean multiple = false;
261         if (maxOccurs != null) {
262             if (maxOccurs.equals("0")) return null;
263             if (maxOccurs.equals("unbounded") || Integer.parseInt(maxOccurs) > 1) multiple = true;
264         }
265 
266         XsdType type = null;
267         if (typename != null) {
268             type = new XsdType(null, typename);
269         }
270         for (XsdTag tag : state.tags) {
271             if (tag == null) continue;
272             if (tag instanceof XsdType) {
273                 type = (XsdType) tag;
274             }
275         }
276 
277         return setDeprecated(new XsdElement(name, ref, type, multiple), state.deprecated);
278     }
279 
makeAttribute(State state)280     private XsdAttribute makeAttribute(State state) throws XsdParserException {
281         String name = state.attributeMap.get("name");
282         QName typename = parseQName(state.attributeMap.get("type"));
283         QName ref = parseQName(state.attributeMap.get("ref"));
284         String defVal = state.attributeMap.get("default");
285         String use = state.attributeMap.get("use");
286 
287         if (use != null && use.equals("prohibited")) return null;
288 
289         XsdType type = null;
290         if (typename != null) {
291             type = new XsdType(null, typename);
292         }
293         for (XsdTag tag : state.tags) {
294             if (tag == null) continue;
295             if (tag instanceof XsdType) {
296                 type = (XsdType) tag;
297             }
298         }
299 
300         return setDeprecated(new XsdAttribute(name, ref, type), state.deprecated);
301     }
302 
makeComplexType(State state)303     private XsdComplexType makeComplexType(State state) throws XsdParserException {
304         String name = state.attributeMap.get("name");
305         String isAbstract = state.attributeMap.get("abstract");
306         String mixed = state.attributeMap.get("mixed");
307 
308         if ("true".equals(isAbstract)) {
309             throw new XsdParserException("abstract complex type is not supported.");
310         }
311         if ("true".equals(mixed)) {
312             throw new XsdParserException("mixed option of a complex type is not supported.");
313         }
314 
315         List<XsdAttribute> attributes = new ArrayList<>();
316         List<XsdElement> elements = new ArrayList<>();
317         XsdComplexType type = null;
318 
319         for (XsdTag tag : state.tags) {
320             if (tag == null) continue;
321             if (tag instanceof XsdAttribute) {
322                 attributes.add((XsdAttribute) tag);
323             } else if (tag instanceof XsdElement) {
324                 elements.add((XsdElement) tag);
325             } else if (tag instanceof XsdComplexContent) {
326                 XsdComplexContent child = (XsdComplexContent) tag;
327                 type = setDeprecated(new XsdComplexContent(name, child.getBase(),
328                         child.getAttributes(), child.getElements()), state.deprecated);
329             } else if (tag instanceof XsdSimpleContent) {
330                 XsdSimpleContent child = (XsdSimpleContent) tag;
331                 type = setDeprecated(new XsdSimpleContent(name, child.getBase(),
332                         child.getAttributes()), state.deprecated);
333             }
334         }
335 
336         return (type != null) ? type : setDeprecated(new XsdComplexContent(name, null, attributes,
337                 elements), state.deprecated);
338     }
339 
makeComplexContent(State state)340     private XsdComplexContent makeComplexContent(State state) throws XsdParserException {
341         String mixed = state.attributeMap.get("mixed");
342         if ("true".equals(mixed)) {
343             throw new XsdParserException("mixed option of a complex content is not supported.");
344         }
345 
346         XsdComplexContent content = null;
347         for (XsdTag tag : state.tags) {
348             if (tag == null) continue;
349             if (tag instanceof XsdGeneralExtension) {
350                 XsdGeneralExtension extension = (XsdGeneralExtension) tag;
351                 content = new XsdComplexContent(null, extension.getBase(),
352                         extension.getAttributes(), extension.getElements());
353             } else if (tag instanceof XsdGeneralRestriction) {
354                 XsdGeneralRestriction restriction = (XsdGeneralRestriction) tag;
355                 XsdType base = restriction.getBase();
356                 if (base.getRef() != null && base.getRef().getNamespaceURI().equals(
357                         XsdConstants.XSD_NAMESPACE)) {
358                     // restriction of base 'xsd:anyType' is equal to complex content definition
359                     content = new XsdComplexContent(null, null, restriction.getAttributes(),
360                             restriction.getElements());
361                 } else {
362                     // otherwise ignore restrictions
363                     content = new XsdComplexContent(null, base, null, null);
364                 }
365             }
366         }
367 
368         return setDeprecated(content, state.deprecated);
369     }
370 
makeSimpleContent(State state)371     private XsdSimpleContent makeSimpleContent(State state) {
372         XsdSimpleContent content = null;
373 
374         for (XsdTag tag : state.tags) {
375             if (tag == null) continue;
376             if (tag instanceof XsdGeneralExtension) {
377                 XsdGeneralExtension extension = (XsdGeneralExtension) tag;
378                 content = new XsdSimpleContent(null, extension.getBase(),
379                         extension.getAttributes());
380             } else if (tag instanceof XsdGeneralRestriction) {
381                 XsdGeneralRestriction restriction = (XsdGeneralRestriction) tag;
382                 content = new XsdSimpleContent(null, restriction.getBase(), null);
383             }
384         }
385 
386         return setDeprecated(content, state.deprecated);
387     }
388 
makeGeneralRestriction(State state)389     private XsdGeneralRestriction makeGeneralRestriction(State state) throws XsdParserException {
390         QName base = parseQName(state.attributeMap.get("base"));
391 
392         XsdType type = null;
393         if (base != null) {
394             type = new XsdType(null, base);
395         }
396         List<XsdAttribute> attributes = new ArrayList<>();
397         List<XsdElement> elements = new ArrayList<>();
398         for (XsdTag tag : state.tags) {
399             if (tag == null) continue;
400             if (tag instanceof XsdAttribute) {
401                 attributes.add((XsdAttribute) tag);
402             } else if (tag instanceof XsdElement) {
403                 elements.add((XsdElement) tag);
404             }
405         }
406 
407         return setDeprecated(new XsdGeneralRestriction(type, attributes, elements),
408                 state.deprecated);
409     }
410 
makeGeneralExtension(State state)411     private XsdGeneralExtension makeGeneralExtension(State state) throws XsdParserException {
412         QName base = parseQName(state.attributeMap.get("base"));
413 
414         List<XsdAttribute> attributes = new ArrayList<>();
415         List<XsdElement> elements = new ArrayList<>();
416         for (XsdTag tag : state.tags) {
417             if (tag == null) continue;
418             if (tag instanceof XsdAttribute) {
419                 attributes.add((XsdAttribute) tag);
420             } else if (tag instanceof XsdElement) {
421                 elements.add((XsdElement) tag);
422             }
423         }
424         return setDeprecated(new XsdGeneralExtension(new XsdType(null, base), attributes, elements),
425                 state.deprecated);
426     }
427 
makeSimpleType(State state)428     private XsdSimpleType makeSimpleType(State state) throws XsdParserException {
429         String name = state.attributeMap.get("name");
430         XsdSimpleType type = null;
431         for (XsdTag tag : state.tags) {
432             if (tag == null) continue;
433             if (tag instanceof XsdList) {
434                 type = new XsdList(name, ((XsdList) tag).getItemType());
435             } else if (tag instanceof XsdGeneralRestriction) {
436                 type = new XsdRestriction(name, ((XsdGeneralRestriction) tag).getBase(), null);
437             } else if (tag instanceof XsdEnumRestriction) {
438                 type = new XsdRestriction(name, ((XsdEnumRestriction) tag).getBase(),
439                         ((XsdEnumRestriction) tag).getEnums());
440             } else if (tag instanceof XsdUnion) {
441                 type = new XsdUnion(name, ((XsdUnion) tag).getMemberTypes());
442             }
443         }
444         return setDeprecated(type, state.deprecated);
445     }
446 
makeSimpleTypeList(State state)447     private XsdList makeSimpleTypeList(State state) throws XsdParserException {
448         QName itemTypeName = parseQName(state.attributeMap.get("itemType"));
449 
450         XsdType itemType = null;
451         if (itemTypeName != null) {
452             itemType = new XsdType(null, itemTypeName);
453         }
454         for (XsdTag tag : state.tags) {
455             if (tag == null) continue;
456             if (tag instanceof XsdType) {
457                 itemType = (XsdType) tag;
458             }
459         }
460         return setDeprecated(new XsdList(null, itemType), state.deprecated);
461     }
462 
makeSimpleTypeUnion(State state)463     private XsdUnion makeSimpleTypeUnion(State state) throws XsdParserException {
464         List<QName> memberTypeNames = parseQNames(state.attributeMap.get("memberTypes"));
465         List<XsdType> memberTypes = memberTypeNames.stream().map(
466                 ref -> new XsdType(null, ref)).collect(Collectors.toList());
467 
468         for (XsdTag tag : state.tags) {
469             if (tag == null) continue;
470             if (tag instanceof XsdType) {
471                 memberTypes.add((XsdType) tag);
472             }
473         }
474 
475         return setDeprecated(new XsdUnion(null, memberTypes), state.deprecated);
476     }
477 
makeSequence(State state)478     private static List<XsdElement> makeSequence(State state) throws XsdParserException {
479         String minOccurs = state.attributeMap.get("minOccurs");
480         String maxOccurs = state.attributeMap.get("maxOccurs");
481 
482         if (minOccurs != null || maxOccurs != null) {
483             throw new XsdParserException(
484                     "minOccurs, maxOccurs options of a sequence is not supported");
485         }
486 
487         List<XsdElement> elements = new ArrayList<>();
488         for (XsdTag tag : state.tags) {
489             if (tag == null) continue;
490             if (tag instanceof XsdElement) {
491                 if (maxOccurs != null && (maxOccurs.equals("unbounded")
492                         || Integer.parseInt(maxOccurs) > 1)) {
493                     ((XsdElement)tag).setMultiple(true);
494                 }
495                 elements.add((XsdElement) tag);
496             }
497         }
498         return elements;
499     }
500 
makeChoice(State state)501     private static List<XsdElement> makeChoice(State state) throws XsdParserException {
502         String maxOccurs = state.attributeMap.get("maxOccurs");
503         List<XsdElement> elements = new ArrayList<>();
504 
505         for (XsdTag tag : state.tags) {
506             if (tag == null) continue;
507             if (tag instanceof XsdElement) {
508                 if (maxOccurs != null && (maxOccurs.equals("unbounded")
509                         || Integer.parseInt(maxOccurs) > 1)) {
510                     ((XsdElement)tag).setMultiple(true);
511                 }
512                 XsdElement element = (XsdElement)tag;
513                 elements.add(setDeprecated(new XsdChoice(element.getName(), element.getRef(),
514                         element.getType(), element.isMultiple()), element.isDeprecated()));
515             }
516         }
517         return elements;
518     }
519 
makeAll(State state)520     private static List<XsdElement> makeAll(State state) throws XsdParserException {
521         List<XsdElement> elements = new ArrayList<>();
522         for (XsdTag tag : state.tags) {
523             if (tag == null) continue;
524             if (tag instanceof XsdElement) {
525                 XsdElement element = (XsdElement)tag;
526                 elements.add(setDeprecated(new XsdAll(element.getName(), element.getRef(),
527                         element.getType(), element.isMultiple()), element.isDeprecated()));
528             }
529         }
530         return elements;
531     }
532 
makeEnumeration(State state)533     private XsdEnumeration makeEnumeration(State state) throws XsdParserException {
534         String value = state.attributeMap.get("value");
535         return setDeprecated(new XsdEnumeration(value), state.deprecated);
536     }
537 
makeEnumRestriction(State state)538     private XsdEnumRestriction makeEnumRestriction(State state) throws XsdParserException {
539         QName base = parseQName(state.attributeMap.get("base"));
540 
541         XsdType type = null;
542         if (base != null) {
543             type = new XsdType(null, base);
544         }
545         List<XsdEnumeration> enums = new ArrayList<>();
546         for (XsdTag tag : state.tags) {
547             if (tag == null) continue;
548             if (tag instanceof XsdEnumeration) {
549                 enums.add((XsdEnumeration) tag);
550             }
551         }
552 
553         return setDeprecated(new XsdEnumRestriction(type, enums), state.deprecated);
554     }
555 
isDeprecated(Map<String, String> attributeMap,List<XsdTag> tags)556     private boolean isDeprecated(Map<String, String> attributeMap,List<XsdTag> tags)
557             throws XsdParserException {
558         String name = attributeMap.get("name");
559         if ("Deprecated".equals(name)) {
560             return true;
561         }
562         return false;
563     }
564 
setDeprecated(T tag, boolean deprecated)565     private static <T extends XsdTag> T setDeprecated(T tag, boolean deprecated) {
566         if (tag != null) {
567             tag.setDeprecated(deprecated);
568         }
569         return tag;
570     }
571 }
572