| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| package org.apache.openoffice.ooxml.schema.parser; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| |
| import org.apache.openoffice.ooxml.schema.model.attribute.Attribute; |
| import org.apache.openoffice.ooxml.schema.model.attribute.AttributeGroup; |
| import org.apache.openoffice.ooxml.schema.model.attribute.AttributeGroupReference; |
| import org.apache.openoffice.ooxml.schema.model.attribute.AttributeReference; |
| import org.apache.openoffice.ooxml.schema.model.base.INode; |
| import org.apache.openoffice.ooxml.schema.model.base.Location; |
| import org.apache.openoffice.ooxml.schema.model.base.Node; |
| import org.apache.openoffice.ooxml.schema.model.base.QualifiedName; |
| import org.apache.openoffice.ooxml.schema.model.complex.All; |
| import org.apache.openoffice.ooxml.schema.model.complex.Any; |
| import org.apache.openoffice.ooxml.schema.model.complex.Choice; |
| import org.apache.openoffice.ooxml.schema.model.complex.ComplexContent; |
| import org.apache.openoffice.ooxml.schema.model.complex.ComplexType; |
| import org.apache.openoffice.ooxml.schema.model.complex.Element; |
| import org.apache.openoffice.ooxml.schema.model.complex.ElementReference; |
| import org.apache.openoffice.ooxml.schema.model.complex.Extension; |
| import org.apache.openoffice.ooxml.schema.model.complex.Group; |
| import org.apache.openoffice.ooxml.schema.model.complex.GroupReference; |
| import org.apache.openoffice.ooxml.schema.model.complex.OccurrenceIndicator; |
| import org.apache.openoffice.ooxml.schema.model.complex.Sequence; |
| import org.apache.openoffice.ooxml.schema.model.schema.Schema; |
| import org.apache.openoffice.ooxml.schema.model.schema.SchemaBase; |
| import org.apache.openoffice.ooxml.schema.model.simple.List; |
| import org.apache.openoffice.ooxml.schema.model.simple.Restriction; |
| import org.apache.openoffice.ooxml.schema.model.simple.SimpleContent; |
| import org.apache.openoffice.ooxml.schema.model.simple.SimpleType; |
| import org.apache.openoffice.ooxml.schema.model.simple.SimpleTypeReference; |
| import org.apache.openoffice.ooxml.schema.model.simple.Union; |
| |
| |
| /** Parser for single schema file. |
| * Imports and includes of other schema files are stored and can be retrieved |
| * by calling GetImportedSchemas(). |
| * |
| * Typical usage: |
| * 1) Create SchemaParser for top-level schema file. |
| * 2) Call Parse(). |
| * 3) Repeat the same recursively for all imported schemas (as returned by |
| * GetImportedSchemas()). |
| * |
| * All top level types (complex types, simple types, elements, etc.) are |
| * stored in the given Schema object. |
| */ |
| public class SchemaParser |
| { |
| public SchemaParser ( |
| final File aSchemaFile, |
| final Schema aSchema, |
| final SchemaBase aSchemaBase) |
| { |
| maSchema = aSchema; |
| maSchemaBase = aSchemaBase; |
| maReader = GetStreamReader(aSchemaFile); |
| msBasename = aSchemaFile.getName(); |
| maDirectory = aSchemaFile.getParentFile(); |
| maLocalNamespaceMap = new HashMap<>(); |
| maImportedSchemas = new Vector<>(); |
| maLastLocation = null; |
| } |
| |
| |
| |
| |
| /** Parse the schema file. |
| * @return |
| * Return false if there is any error. |
| * @throws XMLStreamException |
| */ |
| public void Parse () |
| throws XMLStreamException |
| { |
| if (maReader == null) |
| return; |
| |
| while (maReader.hasNext()) |
| { |
| final int nCode = maReader.next(); |
| switch (nCode) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| if (maReader.getLocalName().equals("schema")) |
| { |
| ProcessSchemaTag(); |
| ParseSchema(); |
| |
| maLastLocation = maReader.getLocation(); |
| } |
| else |
| { |
| throw CreateErrorException("expecting top level element to be 'schema'"); |
| } |
| break; |
| |
| case XMLStreamReader.END_DOCUMENT: |
| return; |
| |
| default: |
| throw CreateErrorException("unexpected XML event %d", nCode); |
| } |
| } |
| } |
| |
| |
| |
| |
| public Iterable<File> GetImportedSchemaFilenames () |
| { |
| return maImportedSchemas; |
| } |
| |
| |
| |
| |
| public int GetLineCount () |
| { |
| if (maLastLocation != null) |
| return maLastLocation.getLineNumber(); |
| else |
| return 0; |
| } |
| |
| |
| |
| |
| public int GetByteCount () |
| { |
| if (maLastLocation != null) |
| return maLastLocation.getCharacterOffset(); |
| else |
| return 0; |
| } |
| |
| |
| |
| |
| /** Process the namespace definitions in the outer 'schema' element. |
| */ |
| private void ProcessSchemaTag () |
| { |
| GetOptionalAttributeValue("id", null); |
| meAttributeFormDefault = FormDefault.valueOf( |
| GetOptionalAttributeValue("attributeFormDefault", "unqualified")); |
| meElementFormDefault = FormDefault.valueOf( |
| GetOptionalAttributeValue("elementFormDefault", "unqualified")); |
| GetOptionalAttributeValue("blockDefault", null);//=(#all|list of (extension|restriction|substitution)) |
| GetOptionalAttributeValue("finalDefault", null);//=(#all|list of (extension|restriction|list|union)) |
| msTargetNamespace = GetOptionalAttributeValue("targetNamespace", null); |
| GetOptionalAttributeValue("version", null); |
| |
| for (int nIndex=0; nIndex<maReader.getNamespaceCount(); ++nIndex) |
| { |
| final String sPrefix = maReader.getNamespacePrefix(nIndex); |
| final String sURI = maReader.getNamespaceURI(nIndex); |
| maLocalNamespaceMap.put(sPrefix, sURI); |
| maSchemaBase.Namespaces.ProvideNamespace(sURI, sPrefix); |
| } |
| |
| maLocalNamespaceMap.put(null, msTargetNamespace); |
| maSchemaBase.Namespaces.ProvideNamespace(msTargetNamespace, null); |
| } |
| |
| |
| |
| |
| private void ParseSchema () |
| throws XMLStreamException |
| { |
| while (true) |
| { |
| switch (Next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| ProcessTopLevelStartElement(); |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return; |
| |
| default: |
| throw CreateErrorException("unexpected event (expteced START_ELEMENT): %d", maReader.getEventType()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private void ProcessTopLevelStartElement () |
| throws XMLStreamException |
| { |
| assert(GetAttributeValue("minOccurs") == null); |
| assert(GetAttributeValue("maxOccurs") == null); |
| |
| switch (maReader.getLocalName()) |
| { |
| case "attribute": |
| maSchemaBase.Attributes.Add(ParseAttribute()); |
| break; |
| |
| case "attributeGroup": |
| maSchemaBase.AttributeGroups.Add(ParseAttributeGroup()); |
| break; |
| |
| case "complexType": |
| maSchemaBase.ComplexTypes.Add(ParseComplexType()); |
| break; |
| |
| case "element": |
| final Element aElement = ParseElement(null); |
| if (maSchema != null) |
| maSchema.TopLevelElements.Add(aElement); |
| maSchemaBase.TopLevelElements.Add(aElement); |
| break; |
| |
| case "group": |
| maSchemaBase.Groups.Add(ParseGroup(null)); |
| break; |
| |
| case "import": |
| ParseImport(); |
| break; |
| |
| case "include": |
| ParseInclude(); |
| break; |
| |
| case "simpleType": |
| maSchemaBase.SimpleTypes.Add(ParseSimpleType(null)); |
| break; |
| |
| default: |
| throw CreateErrorException("unexpected top level element %s", maReader.getLocalName()); |
| } |
| } |
| |
| |
| |
| |
| private void ProcessStartElement (final Node aParent) |
| throws XMLStreamException |
| { |
| final String sMinimumOccurrence = GetOptionalAttributeValue("minOccurs", "1"); |
| final String sMaximumOccurrence = GetOptionalAttributeValue("maxOccurs", "1"); |
| |
| final Node aLocalParent; |
| if ( ! (sMinimumOccurrence.equals("1") && sMaximumOccurrence.equals("1"))) |
| { |
| // Occurrence does not only have default values (min=max=1). |
| // Have to create an intermediate node for the occurrence indicator. |
| final OccurrenceIndicator aIndicator = new OccurrenceIndicator( |
| aParent, |
| sMinimumOccurrence, |
| sMaximumOccurrence, |
| GetLocation()); |
| aParent.AddChild(aIndicator); |
| aLocalParent = aIndicator; |
| } |
| else |
| aLocalParent = aParent; |
| |
| switch (maReader.getLocalName()) |
| { |
| case "all": |
| aLocalParent.AddChild(ParseAll(aLocalParent)); |
| break; |
| |
| case "any": |
| aLocalParent.AddChild(ParseAny(aLocalParent)); |
| break; |
| |
| case "attribute": |
| aLocalParent.AddAttribute(ParseAttributeOrReference()); |
| break; |
| |
| case "attributeGroup": |
| aLocalParent.AddAttribute(ParseAttributeGroupOrReference()); |
| break; |
| |
| case "choice": |
| aLocalParent.AddChild(ParseChoice(aLocalParent)); |
| break; |
| |
| case "complexContent": |
| aLocalParent.AddChild(ParseComplexContent(aLocalParent)); |
| break; |
| |
| case "element": |
| aLocalParent.AddChild(ParseElementOrReference(aLocalParent)); |
| break; |
| |
| case "extension": |
| aLocalParent.AddChild(ParseExtension(aLocalParent)); |
| break; |
| |
| case "group": |
| aLocalParent.AddChild(ParseGroupOrReference(aLocalParent)); |
| break; |
| |
| case "list": |
| aLocalParent.AddChild(ParseList(aLocalParent)); |
| break; |
| |
| case "sequence": |
| aLocalParent.AddChild(ParseSequence(aLocalParent)); |
| break; |
| |
| case "simpleContent": |
| aLocalParent.AddChild(ParseSimpleContent(aLocalParent)); |
| break; |
| |
| default: |
| throw CreateErrorException("unsupported content type %s", maReader.getLocalName()); |
| } |
| } |
| |
| |
| |
| |
| private void IgnoreAnnotation () |
| throws XMLStreamException |
| { |
| IgnoreContent(); |
| } |
| |
| |
| |
| |
| private void IgnoreContent () |
| throws XMLStreamException |
| { |
| while(true) |
| { |
| switch(maReader.next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| IgnoreContent(); |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return; |
| |
| case XMLStreamReader.COMMENT: |
| case XMLStreamReader.CHARACTERS: |
| break; |
| |
| default: |
| throw CreateErrorException( |
| "unexpected XML event %d while ignoring content", |
| maReader.getEventType()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private All ParseAll (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs")); |
| |
| final All aAll = new All(aParent, GetLocation()); |
| ParseContent(aAll); |
| return aAll; |
| } |
| |
| |
| |
| |
| private Any ParseAny (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs", "namespace", "processContents")); |
| |
| final Any aAny = new Any( |
| aParent, |
| GetLocation(), |
| GetOptionalAttributeValue("processContents", "strict"), |
| GetOptionalAttributeValue("namespace", "##any")); |
| ExpectEndElement("ParseAny"); |
| return aAny; |
| } |
| |
| |
| |
| |
| private AttributeGroup ParseAttributeGroup () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("name")); |
| |
| final AttributeGroup aGroup = new AttributeGroup(GetOptionalQualifiedName("name"), GetLocation()); |
| |
| while (true) |
| { |
| switch (Next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| if ( ! maReader.getLocalName().equals("attribute")) |
| throw CreateErrorException( |
| "attributeGroup expects element 'attribute' but got %s", |
| maReader.getLocalName()); |
| else |
| aGroup.AddAttribute(ParseAttributeOrReference()); |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return aGroup; |
| |
| default: |
| throw CreateErrorException( |
| "unexpected event when parsing attributeGroup: %d", |
| maReader.getEventType()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private INode ParseAttributeGroupOrReference () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("ref")); |
| |
| final INode aGroup; |
| if (GetAttributeValue("schemaLocation") != null) |
| { |
| aGroup = ParseAttributeGroup(); |
| } |
| else |
| { |
| aGroup = new AttributeGroupReference( |
| CreateQualifiedName( |
| GetAttributeValue("ref")), |
| GetLocation()); |
| ExpectEndElement("attribute group or reference"); |
| } |
| return aGroup; |
| } |
| |
| |
| |
| |
| private INode ParseAttributeOrReference () |
| throws XMLStreamException |
| { |
| final INode aAttribute; |
| if (GetAttributeValue("name") != null) |
| { |
| aAttribute = ParseAttribute(); |
| } |
| else |
| { |
| assert(HasOnlyAttributes("default", "fixed", "ref", "use")); |
| |
| aAttribute = new AttributeReference( |
| GetQualifiedName("ref"), |
| GetOptionalAttributeValue("use", "optional"), |
| GetOptionalAttributeValue("default", null), |
| GetOptionalAttributeValue("fixed", null), |
| meAttributeFormDefault, |
| GetLocation()); |
| ExpectEndElement("attribute reference"); |
| } |
| return aAttribute; |
| } |
| |
| |
| |
| |
| private Attribute ParseAttribute () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("default", "fixed", "name", "type", "use")); |
| |
| final Attribute aAttribute = new Attribute( |
| GetQualifiedName("name"), |
| GetQualifiedName("type"), |
| GetOptionalAttributeValue("use", "optional"), |
| GetOptionalAttributeValue("default", null), |
| GetOptionalAttributeValue("fixed", null), |
| meAttributeFormDefault, |
| GetLocation()); |
| ExpectEndElement("attribute"); |
| |
| return aAttribute; |
| } |
| |
| |
| |
| private Choice ParseChoice (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs")); |
| |
| final Choice aChoice = new Choice(aParent, GetLocation()); |
| ParseContent(aChoice); |
| return aChoice; |
| } |
| |
| |
| |
| |
| private ComplexContent ParseComplexContent (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs")); |
| |
| final ComplexContent aNode = new ComplexContent(aParent, GetLocation()); |
| ParseContent(aNode); |
| return aNode; |
| } |
| |
| |
| |
| |
| private ComplexType ParseComplexType () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("mixed", "name")); |
| |
| final ComplexType aComplexType = new ComplexType( |
| null, |
| GetQualifiedName("name"), |
| GetLocation()); |
| |
| ParseContent(aComplexType); |
| |
| return aComplexType; |
| } |
| |
| |
| |
| |
| private Element ParseElement (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs", "name", "type")); |
| |
| final Element aElement = new Element( |
| aParent, |
| GetQualifiedName("name"), |
| GetQualifiedName("type"), |
| GetLocation()); |
| |
| ExpectEndElement("element"); |
| |
| return aElement; |
| } |
| |
| |
| |
| |
| private Element ParseElementOrReference (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs", "name", "ref", "type")); |
| |
| final Element aElement; |
| final String sName = GetOptionalAttributeValue("name", null); |
| if (sName != null) |
| { |
| aElement = ParseElement(aParent); |
| } |
| else |
| { |
| final String sElementReference = GetOptionalAttributeValue("ref", null); |
| if (sElementReference != null) |
| { |
| ExpectEndElement("element reference"); |
| aElement = new ElementReference( |
| aParent, |
| CreateQualifiedName(sElementReference), |
| GetLocation()); |
| } |
| else |
| { |
| throw CreateErrorException("element has no name and no ref"); |
| } |
| } |
| return aElement; |
| } |
| |
| |
| |
| |
| private Extension ParseExtension (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("base", "minOccurs", "maxOccurs")); |
| |
| final Extension aNode = new Extension( |
| aParent, |
| CreateQualifiedName(GetAttributeValue("base")), |
| GetLocation()); |
| ParseContent(aNode); |
| return aNode; |
| } |
| |
| |
| |
| |
| private Group ParseGroup (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("name")); |
| |
| final Group aGroup = new Group( |
| aParent, |
| GetQualifiedName("name"), |
| GetLocation()); |
| ParseContent(aGroup); |
| return aGroup; |
| } |
| |
| |
| |
| |
| private Node ParseGroupOrReference (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs", "name", "ref")); |
| |
| final Node aGroup; |
| final String sName = GetOptionalAttributeValue("name", null); |
| if (sName != null) |
| { |
| aGroup = ParseGroup(aParent); |
| } |
| else |
| { |
| final String sGroupReference = GetOptionalAttributeValue("ref", null); |
| if (sGroupReference != null) |
| { |
| ExpectEndElement("group reference"); |
| aGroup = new GroupReference( |
| aParent, |
| CreateQualifiedName(sGroupReference), |
| GetLocation()); |
| } |
| else |
| { |
| throw CreateErrorException("group has no name and no ref"); |
| } |
| } |
| return aGroup; |
| } |
| |
| |
| |
| |
| private Restriction ParseRestriction (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("base", "value")); |
| |
| final String sBaseType = GetAttributeValue("base"); |
| final Restriction aRestriction = new Restriction( |
| aParent, |
| CreateQualifiedName(sBaseType), |
| GetLocation()); |
| |
| while (true) |
| { |
| switch (Next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| final String sValue = GetAttributeValue("value"); |
| switch (maReader.getLocalName()) |
| { |
| case "enumeration": |
| aRestriction.AddEnumeration(sValue); |
| break; |
| |
| case "minInclusive": |
| aRestriction.SetMinInclusive(sValue); |
| break; |
| |
| case "minExclusive": |
| aRestriction.SetMinExclusive(sValue); |
| break; |
| |
| case "maxInclusive": |
| aRestriction.SetMaxInclusive(sValue); |
| break; |
| |
| case "maxExclusive": |
| aRestriction.SetMaxExclusive(sValue); |
| break; |
| |
| case "length": |
| aRestriction.SetLength(sValue); |
| break; |
| |
| case "minLength": |
| aRestriction.SetMinLength(sValue); |
| break; |
| |
| case "maxLength": |
| aRestriction.SetMaxLength(sValue); |
| break; |
| |
| case "pattern": |
| aRestriction.SetPattern(sValue); |
| break; |
| |
| default: |
| throw CreateErrorException("unsupported restriction type "+maReader.getLocalName()); |
| } |
| ExpectEndElement("restriction"); |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return aRestriction; |
| |
| default: |
| throw CreateErrorException("unexpected XML event while parsing restrictions: %d", maReader.getEventType()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private List ParseList (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("itemType")); |
| |
| final List aList = new List( |
| aParent, |
| GetQualifiedName("itemType"), |
| GetLocation()); |
| ExpectEndElement("list"); |
| return aList; |
| } |
| |
| |
| |
| |
| private Sequence ParseSequence (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs", "name")); |
| |
| final Sequence aSequence = new Sequence( |
| aParent, |
| GetOptionalQualifiedName("name"), |
| GetLocation()); |
| ParseContent(aSequence); |
| return aSequence; |
| } |
| |
| |
| |
| |
| private SimpleContent ParseSimpleContent (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("minOccurs", "maxOccurs")); |
| |
| final SimpleContent aSimpleContent = new SimpleContent( |
| aParent, |
| GetLocation()); |
| ParseContent(aSimpleContent); |
| return aSimpleContent; |
| } |
| |
| |
| |
| |
| private SimpleType ParseSimpleType (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("final", "name")); |
| |
| final SimpleType aType = new SimpleType( |
| aParent, |
| GetQualifiedName("name"), |
| GetLocation()); |
| final String sFinalValue = GetOptionalAttributeValue("final", null); |
| if (sFinalValue != null) |
| aType.SetFinal(sFinalValue.split("\\s+")); |
| |
| while (true) |
| { |
| switch (Next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| switch(maReader.getLocalName()) |
| { |
| case "list": |
| aType.AddChild(ParseList(aType)); |
| break; |
| |
| case "restriction": |
| aType.AddChild(ParseRestriction(aType)); |
| break; |
| |
| case "union": |
| aType.AddChild(ParseUnion(aType)); |
| break; |
| |
| default: |
| throw CreateErrorException("unsupported simple type part %s", maReader.getLocalName()); |
| } |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return aType; |
| |
| default: |
| throw CreateErrorException("unexpected XML event in ParseSimpleType: %s", maReader.getEventType()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private Union ParseUnion (final Node aParent) |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("memberTypes")); |
| |
| final Union aUnion = new Union( |
| aParent, |
| GetLocation()); |
| final String[] aMemberTypes = GetAttributeValue("memberTypes").split("\\s+"); |
| for (int nIndex=0; nIndex<aMemberTypes.length; ++nIndex) |
| aUnion.AddChild( |
| new SimpleTypeReference( |
| aUnion, |
| CreateQualifiedName(aMemberTypes[nIndex]), |
| GetLocation())); |
| |
| ParseContent(aUnion); |
| |
| return aUnion; |
| } |
| |
| |
| private void ParseContent (final Node aParent) |
| throws XMLStreamException |
| { |
| while(true) |
| { |
| switch(Next()) |
| { |
| case XMLStreamReader.START_ELEMENT: |
| ProcessStartElement(aParent); |
| break; |
| |
| case XMLStreamReader.END_ELEMENT: |
| return; |
| |
| default: |
| throw CreateErrorException( |
| "unexpected XML event %d while parsing content of %s", |
| maReader.getEventType(), |
| aParent.toString()); |
| } |
| } |
| } |
| |
| |
| |
| |
| private void ParseImport () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("id", "namespace", "schemaLocation")); |
| |
| final String sFilename = GetOptionalAttributeValue("schemaLocation", null); |
| if (sFilename == null) |
| { |
| final String sNamespaceName = GetAttributeValue("namespace"); |
| if (sNamespaceName.equals(XmlNamespace.URI)) |
| { |
| XmlNamespace.Apply(maSchemaBase); |
| maLocalNamespaceMap.put(XmlNamespace.Prefix, XmlNamespace.URI); |
| } |
| else |
| throw CreateErrorException("invalid import"); |
| } |
| else |
| { |
| maImportedSchemas.add(new File(maDirectory, sFilename)); |
| } |
| |
| ExpectEndElement("import"); |
| } |
| |
| |
| |
| |
| private void ParseInclude () |
| throws XMLStreamException |
| { |
| assert(HasOnlyAttributes("id", "schemaLocation")); |
| |
| final String sFilename = GetOptionalAttributeValue("schemaLocation", null); |
| if (sFilename == null) |
| { |
| throw CreateErrorException("invalid include"); |
| } |
| else |
| { |
| maImportedSchemas.add(new File(maDirectory, sFilename)); |
| } |
| |
| ExpectEndElement("include"); |
| } |
| |
| |
| |
| |
| private void ExpectEndElement (final String sCaller) |
| throws XMLStreamException |
| { |
| final int nNextEvent = Next(); |
| if (nNextEvent != XMLStreamReader.END_ELEMENT) |
| throw CreateErrorException("expected END_ELEMENT of %s but got %s", |
| sCaller, |
| nNextEvent); |
| } |
| |
| |
| |
| |
| /** Return the next relevant token from the XML stream. |
| * Ignores comments. |
| * @return |
| * Returns the event code of the next relevant XML event or END_DOCUMENT when the end of the file has been reached. |
| */ |
| private int Next () |
| throws XMLStreamException |
| { |
| while (maReader.hasNext()) |
| { |
| switch (maReader.next()) |
| { |
| case XMLStreamReader.COMMENT: |
| // Ignore comments. |
| break; |
| |
| case XMLStreamReader.CHARACTERS: |
| // Ignore whitespace. |
| if (maReader.getText().matches("^\\s+$")) |
| break; |
| else |
| { |
| // Character events are not expected in schema files |
| // and therefore not supported. |
| // Alternatively, they could easily be ignored. |
| throw CreateErrorException("unexpected CHARACTERS event with text [%s]", maReader.getText()); |
| } |
| |
| case XMLStreamReader.START_ELEMENT: |
| switch (maReader.getLocalName()) |
| { |
| case "annotation": |
| IgnoreAnnotation(); |
| break; |
| case "unique": |
| // Not supported. |
| IgnoreContent(); |
| break; |
| default: |
| return XMLStreamReader.START_ELEMENT; |
| } |
| break; |
| |
| default: |
| return maReader.getEventType(); |
| } |
| } |
| return XMLStreamReader.END_DOCUMENT; |
| } |
| |
| |
| |
| |
| private String GetAttributeValue (final String sAttributeLocalName) |
| { |
| return maReader.getAttributeValue(null, sAttributeLocalName); |
| } |
| |
| |
| |
| |
| private String GetOptionalAttributeValue ( |
| final String sAttributeLocalName, |
| final String sDefaultValue) |
| { |
| final String sValue = maReader.getAttributeValue(null, sAttributeLocalName); |
| if (sValue == null) |
| return sDefaultValue; |
| else |
| return sValue; |
| } |
| |
| |
| |
| |
| /** Read the specified attribute and return its value as QualifiedName object. |
| */ |
| private QualifiedName GetQualifiedName (final String sAttributeLocalName) |
| { |
| final String sName = maReader.getAttributeValue(null, sAttributeLocalName); |
| if (sName == null) |
| throw CreateErrorException( |
| "did not find a qualified name as value of attribute '%s' at %s:%s", |
| sAttributeLocalName, |
| msBasename, |
| maReader.getLocation()); |
| else |
| return CreateQualifiedName(sName); |
| } |
| |
| |
| |
| |
| private QualifiedName GetOptionalQualifiedName (final String sAttributeLocalName) |
| { |
| final String sName = maReader.getAttributeValue(null, sAttributeLocalName); |
| if (sName == null) |
| return null; |
| else |
| return CreateQualifiedName(sName); |
| } |
| |
| |
| |
| |
| /** Create a QualifiedName object from the given string. |
| * @param sName |
| * May or may not contain a namespace prefix (separated from the name |
| * by a colon). |
| */ |
| private QualifiedName CreateQualifiedName (final String sName) |
| { |
| final String[] aParts = sName.split(":"); |
| final String sNamespaceURL; |
| final String sLocalPart; |
| switch (aParts.length) |
| { |
| case 1: |
| // sName only consists of a local part. |
| // Use the target namespace as namespace. |
| sNamespaceURL = msTargetNamespace; |
| sLocalPart = aParts[0]; |
| break; |
| |
| case 2: |
| // sName is of the form prefix:local. |
| sNamespaceURL = maLocalNamespaceMap.get(aParts[0]); |
| sLocalPart = aParts[1]; |
| break; |
| |
| default: |
| throw new RuntimeException( |
| "the string '"+sName+"' can not be transformed into a qualified name"); |
| } |
| if (sNamespaceURL == null) |
| throw CreateErrorException("can not determine namespace for '%s'", sName); |
| // Transform the local namespace prefix into a global namespace prefix |
| // (different schema files can use different prefixes for the same |
| // namespace URI). |
| final String sGlobalNamespacePrefix = maSchemaBase.Namespaces.GetNamespacePrefix(sNamespaceURL); |
| return new QualifiedName( |
| sNamespaceURL, |
| sGlobalNamespacePrefix, |
| sLocalPart); |
| } |
| |
| |
| |
| |
| /** Create an XMLStreamReader for the given file. |
| * Returns null when there is an error. |
| */ |
| private static XMLStreamReader GetStreamReader (final File aSchemaFile) |
| { |
| final XMLInputFactory aFactory = (XMLInputFactory)XMLInputFactory.newInstance(); |
| aFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); |
| aFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); |
| aFactory.setProperty(XMLInputFactory.IS_COALESCING, false); |
| |
| try |
| { |
| return aFactory.createXMLStreamReader( |
| aSchemaFile.getName(), |
| new FileInputStream(aSchemaFile)); |
| } |
| catch (Exception aException) |
| { |
| aException.printStackTrace(); |
| return null; |
| } |
| } |
| |
| |
| |
| |
| private RuntimeException CreateErrorException ( |
| final String sFormat, |
| final Object ... aArgumentList) |
| { |
| return new RuntimeException(String.format(sFormat, aArgumentList) |
| + String.format(" in %s at L%dC%dP%d", |
| msBasename, |
| maReader.getLocation().getLineNumber(), |
| maReader.getLocation().getColumnNumber(), |
| maReader.getLocation().getCharacterOffset())); |
| } |
| |
| |
| |
| |
| /** This predicate is only used for debugging to assert |
| * that no unsupported attributes are present. |
| * If there where then the parser has to be extended. |
| */ |
| private boolean HasOnlyAttributes (final String ... aAttributeNameList) |
| { |
| for (int nIndex=0,nCount=maReader.getAttributeCount(); nIndex<nCount; ++nIndex) |
| { |
| final String sAttributeName = maReader.getAttributeLocalName(nIndex); |
| boolean bFound = false; |
| for (final String sName : aAttributeNameList) |
| if (sAttributeName.equals(sName)) |
| { |
| bFound = true; |
| break; |
| } |
| if ( ! bFound) |
| { |
| // Attribute name was not found in the given list. |
| System.err.printf("attribute '%s' is not handled\n", sAttributeName); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| |
| |
| private Location GetLocation () |
| { |
| final javax.xml.stream.Location aLocation = maReader.getLocation(); |
| return new Location( |
| msBasename, |
| aLocation.getLineNumber(), |
| aLocation.getColumnNumber(), |
| aLocation.getCharacterOffset()); |
| } |
| |
| |
| |
| |
| private final Schema maSchema; |
| private final SchemaBase maSchemaBase; |
| private final XMLStreamReader maReader; |
| private final String msBasename; |
| private final File maDirectory; |
| private String msTargetNamespace; |
| /// Map of namespace prefix to URI, local to the currently read schema file. |
| private final Map<String,String> maLocalNamespaceMap; |
| /// The names of other schema files referenced by import elements. |
| private final Vector<File> maImportedSchemas; |
| /// Some values for tracking the number of read bytes and lines. |
| private javax.xml.stream.Location maLastLocation; |
| private FormDefault meAttributeFormDefault; |
| private FormDefault meElementFormDefault; |
| } |