XsdParser is a library that parses a XML Definition file (.xsd) into a list of java objects. Each different XSD tag has
a corresponding Java class and the attributes of a given XSD type are represented as fields of that class. All these classes derive from the
same abstract class, XsdAbstractElement. All Java representations of the XSD elements follow the schema definition
for XSD. For example, the xsd:annotation tag only allows xsd:appinfo and xsd:documentation as children nodes,
and also can have an attribute named id, therefore XsdParser has the following class (simplified for example purposes):
public class XsdAnnotation extends XsdAbstractElement {
private String id;
private List<XsdAppInfo> appInfoList = new ArrayList<>();
private List<XsdDocumentation> documentations = new ArrayList<>();
// (...)
}
The set of rules followed by this library can be consulted in the following URL: XSD Schema
First, in order to include it to your Maven project, simply add this dependency:
<dependency>
<groupId>com.github.xmlet</groupId>
<artifactId>xsdParser</artifactId>
<version>1.2.6</version>
</dependency>
A simple example:
public class ParserApp {
public static void main(String [] args) {
String filePath = "Your file path here.";
XsdParser parserInstance1 = new XsdParser(filePath);
//or
String jarPath = "Your jar path here.";
String jarXsdPath = "XSD file path, relative to the jar root.";
XsdParserJar parserInstance2 = new XsdParserJar(jarPath, jarXsdPath);
Stream<XsdElement> elementsStream = parserInstance1.getResultXsdElements();
Stream<XsdSchema> schemasStream = parserInstance1.getResultXsdSchemas();
}
}
After parsing the file like shown above it's possible to start to navigate in the resulting parsed elements. In the
image below it is presented the class diagram that could be useful before trying to start navigating in the result.
There are multiple abstract classes that allow to implement shared features and reduce duplicated code.
Below a simple example is presented. After parsing the XSD snippet the parsed elements can be accessed with the respective
java code.
<?xml version='1.0' encoding='utf-8' ?>
<xsd:schema xmlns='http://schemas.microsoft.com/intellisense/html-5' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<xsd:group name="flowContent">
<xsd:all>
<xsd:element name="elem1"/>
</xsd:all>
</xsd:group>
<xs:element name="html">
<xs:complexType>
<xsd:choice>
<xsd:group ref="flowContent"/>
</xsd:choice>
<xs:attribute name="manifest" type="xsd:anyURI" />
</xs:complexType>
</xs:element>
</xsd:schema>
The result could be consulted in the following way:
public class ParserApp {
public static void main(String [] args) {
//(...)
XsdElement htmlElement = elementsStream.findFirst().get();
XsdComplexType htmlComplexType = htmlElement.getXsdComplexType();
XsdAttribute manifestAttribute = htmlComplexType.getXsdAttributes().findFirst().get();
XsdChoice choiceElement = htmlComplexType.getChildAsChoice();
XsdGroup flowContentGroup = choiceElement.getChildrenGroups().findFirst().get();
XsdAll flowContentAll = flowContentGroup.getChildAsAll();
XsdElement elem1 = flowContentAll.getChildrenElements().findFirst().get();
}
}
In order to minimize the number of passages in the file, which take more time to perform, this library chose to parse
all the elements and then resolve the references present. To parse the XSD file we use the DOM library, which converts
all the XSD elements into Node objects, from where we extract all the XSD information into our XSD respective classes.
Our parse process is also based on a tree approach, which means that when we invoke the XsdSchema parse function the whole document will be parsed, because each XsdAbstractElement class extracts its respective information, i.e. a XsdSchema instance extracts information from the received xsd:schema Node object, and also invokes the respective parse function for each children elements present in its current Node object.
Our parse process is also based on a tree approach, which means that when we invoke the XsdSchema parse function the whole document will be parsed, because each XsdAbstractElement class extracts its respective information, i.e. a XsdSchema instance extracts information from the received xsd:schema Node object, and also invokes the respective parse function for each children elements present in its current Node object.
This library was born with an objective in mind, it should strictly follow the XSD language rules. To guarantee that
we used the Visitor pattern. We used this pattern to add a layer of control regarding different XSD types interactions.
In the presented code snippet we can observe how this works:
class XsdComplexContentVisitor extends XsdAnnotatedElementsVisitor {
private final XsdComplexContent owner;
@Override
public void visit(XsdRestriction element) {
owner.setRestriction(ReferenceBase.createFromXsd(element));
}
@Override
public void visit(XsdExtension element) {
owner.setExtension(ReferenceBase.createFromXsd(element));
}
}
In this example we can see that XsdComplexContentVisitor class only implements two methods, visit(XsdRestriction element)
and visit(XsdExtension element). This means that the XsdComplexContentVisitor type only allows these two
types, i.e. XsdRestriction and XsdExtension, to interact with XsdComplexContent, since these two
types are the only types allowed as XsdComplexContent children elements.
The XSD syntax also especifies some other restrictions, namely regarding attribute possible values or types. For example
the finalDefault attribute of the xsd:schema elements have their value restricted to six distinct values:
There are other validations, such as veryfing if a given attribute is a positiveInteger, a nonNegativeInteger, etc. If any of these validations fail an exception will be thrown with a message detailing the failed validation.
- DEFAULT ("")
- EXTENSION ("extension")
- RESTRICTION ("restriction")
- LIST("list")
- UNION("union")
- ALL ("#all")
There are other validations, such as veryfing if a given attribute is a positiveInteger, a nonNegativeInteger, etc. If any of these validations fail an exception will be thrown with a message detailing the failed validation.
Apart from the type validations the XSD syntax specifies some other rules. These rules are associated with a given XSD
type and therefore are verified when an instance of that respective object is parsed. A simple example of such rule is the following rule:
"A xsd:element cannot have a ref attribute if its parent is a xsd:schema element."
This means that after creating the XsdElement instance and populating its fields we invoke a method to verify this rule. If the rule is violated then an exception is thrown with a message detailing the issue.
"A xsd:element cannot have a ref attribute if its parent is a xsd:schema element."
This means that after creating the XsdElement instance and populating its fields we invoke a method to verify this rule. If the rule is violated then an exception is thrown with a message detailing the issue.
This is a big feature of this library. In XSD files the usage of the ref attribute is frequent, in order to avoid
repetition of XML code. This generates two problems when handling the parsing which are detailed below. Either the
referred element is missing or the element is present and an exchange should be performed. To help in this process
we create a new layer with four classes:
UnsolvedElement - Wrapper class to each element that has a ref attribute.
ConcreteElement - Wrapper class to each element that is present in the file.
NamedConcreteElement - Wrapper class to each element that is present in the file and has a name attribute present.
ReferenceBase - A common interface between UnsolvedReference and ConcreteElement.
These classes simplify the reference solving process by serving as a classifier to the element that they wrap. Now we will shown a short example to explain how this works:
UnsolvedElement - Wrapper class to each element that has a ref attribute.
ConcreteElement - Wrapper class to each element that is present in the file.
NamedConcreteElement - Wrapper class to each element that is present in the file and has a name attribute present.
ReferenceBase - A common interface between UnsolvedReference and ConcreteElement.
These classes simplify the reference solving process by serving as a classifier to the element that they wrap. Now we will shown a short example to explain how this works:
<?xml version='1.0' encoding='utf-8' ?>
<xsd:schema xmlns='http://schemas.microsoft.com/intellisense/html-5' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<xsd:group id="replacement" name="flowContent"> <!-- NamedConcreteType wrapping a XsdGroup -->
(...)
</xsd:group>
<xsd:choice> <!-- ConcreteElement wrapping a XsdChoice -->
<xsd:group id="toBeReplaced" ref="flowContent"/> <!-- UnsolvedReference wrapping a XsdGroup -->
</xsd:choice>
</xsd:schema>
In this short example we have a XsdChoice element with an element XsdGroup with a reference attribute as a child.
In this case the XsdChoice will be wrapped in a ConcreteElement object and its children XsdGroup
will be wrapped in a UnsolvedReference object. When replacing the UnsolvedReference objects the
XsdGroup with the ref attribute is going to be replaced by a copy of
the already parsed XsdGroup with the name attribute, which is wrapped in a NamedConcreteElement object.
This is achieved by accessing the parent of the element, i.e. the parent of the XsdGroup with the ref attribute,
and replacing the XsdGroup with the ref attribute by the XsdGroup with the name attribute.
Resuming the approach:
Resuming the approach:
- Obtain all the NamedConcreteElements objects.
- Obtain all the UnsolvedReference objects. Iterate them to perform a lookup search in the previously obtained NamedConcreteElements objects by comparing the UnsolvedReference ref with the NamedConcreteElements name attributes.
- If a match is found, replace the UnsolvedReference wrapped object with the NamedConcreteElements wrapped object.
Any remaining UnsolvedReference objects can be consulted after the file is parsed by using the method
getUnsolvedReferences(). Those are the references that were not in the file and the user of the XsdParser
library should resolve it by either adding the missing elements to the file or just acknowledging that there are elements missing.
This parser supports xsd:base tags, which allow the use of hierarchies in the XSD files.
The extended xsd:element is referenced in the element containing the xsd:base tag so it's possible to
navigate to the extended elements.
There are some tests available using the HTML5 schema and the Android layouts schema, you can give a look at that
examples and tweak them in order to gain a better understanding of how the parsing works. The tests also cover most
of the code, if you are interested in verifying the code quality, vulnerabilities and other various metrics, check
the following link:
Sonarcloud Statistics
Sonarcloud Statistics
name attribute - XsdParser uses the name attribute as a tool in order to resolve references, therefore it
should be unique in the file. Using multiple times the same name will generate unexpected behaviour.
- Details - Changes XsdMultipleElements to maintain order of elements inside Choice/Sequence/All. Fixes by Simon Cockx.
- Details - Changes XsdRestriction to support multiple patterns instead of one. Fixes by Daniel Radtke.
- Details - Adds fallback when searching for XsdSchema while solving Unsolved References. Fixes by Carsten Zerbst.
- Details - Changes XsdRestriction to support XsdComplexTypes related with the base attribute.
- Details Possible Breaking Change - Changed DocumentBuilderFactory to be NameSpaceAware. Namespaces will now be validated.
- Details File path bug fixes by Wael-Fathallah.
- Details Adds support for XsdAll, XsdChoice, XsdGroup and XsdSequence inside a XsdRestriction.
-
Fixed cloning. Reference resolving now properly clones the referred element and all its children, also cloning
the possible unsolved references of the clone. This has two effects:
- The reference solving process is now more accurate - The referred element unsolved references are also resolved. Previously if an element resolved 10 references the 10 references would have unsolved references and the base element which was the source of the cloned objects would have the references solved.
- To avoid eating up memory I had to implement a temporary solution which will cause the getParent call to return null in certain elements which were children of cloned elements.
- Details This should be finally solved due to the more accurate reference solving process.
- Details Solved due to the more accurate reference solving process.
- Details Minor correction to fix the problem.
- Details Adds safeguard to usage of prefixes of a namespace inside itself.
- Fixes some XML Injection vulnerabilities.
- Code cleanup and adjustments for SonarCloud validation.
- Details - Adds support for xsd:includes elements with schema locations with http or https locations. Previously it only supported local XSD files. Fixes the usage of xsd:import to properly resolve namespace references.
- Details - Fixes clones not cloning attributes and elements of XsdMultipleElements.
- Details - Removes assignment of the XsdElement as parent of the XsdComplexType when the type attribute was being resolved for the XsdElement. This was the cause of some circular reference issues.
- Adds XsdAbstractElement.getXsdSchema() which returns the XsdSchema of any given XsdAbstractElement based on the parent of the XsdAbstractElement. Suggested by: hein4daddel
- Details - Changes xsd:minInclusive, xsd:maxInclusive, xsd:minExclusive and xsd:maxExclusive to have a String value instead of a Double value.
- Changes XsdParserCore.addFileToParse to not allow files with paths that start with http, as this isn't supported.
- Details - Adds a new XsdAbstractElement to represent XsdBuiltInDataTypes;
- Details - Changes the way Visitors work in order to detach elements from visitors. Now the elements and visitors are associated by configuration.
- Details - Fixes a bug where a verification was missing while using getComplexType.
- Changes some of the static fields of XsdAbstractElement to allow avoiding using hardcoded strings while getting attributes.
- Details - Changes the parse for Xsd:AppInfo and Xsd:Documentation - Now the parser returns the exact contents of these two elements.
- Details - Adds proper exception propagation when an exception occurs.
- Details - Changes XsdParser to support imports with paths relative to the importing file.
- Details - Fixes CDATA nodes not being parsed XSD elements with raw text such as "xsd:documentation" and "xsd:appinfo".
- Details - Fixed substitutionGroup automatically overwriting XsdElement types. To access the substitution element types use XsdElement::getXsdSubstitutionGroup() and then access the types.
- Addresses multiple namespace usage while parsing. Also reworks how "xsd:includes" and "xsd:import" work. Full explanation here
- Replaces XsdExtension using XsdElement as value for its base attribute with XsdComplexType and XsdSimpleType types.
- Fixes XsdElement substitutionGroup not being used to replace elements contents.
- Adds support for parsing XSD files inside Jar files.
- Project-wide documentation.
- Minor bug fixes.
- Adds XSD complex rule validations.
- Adds exceptions with detailed messages, providing more information to the user.
- Adds attribute type validations and validations of possible values with Enum classes.