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