Skip to content

Commit

Permalink
Validate XML Schema with Xerces XSD validator. See #190
Browse files Browse the repository at this point in the history
Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed May 24, 2019
1 parent f549ca7 commit fe3f9bc
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.ContentModelPlugin;
import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant;
import org.eclipse.lsp4xml.utils.DOMUtils;

/**
* Validate XML files with Xerces for general SYNTAX validation and XML Schema, DTD.
* Validate XML files with Xerces for general SYNTAX validation and XML Schema,
* DTD.
*
*/
public class ContentModelDiagnosticsParticipant implements IDiagnosticsParticipant {
Expand All @@ -33,8 +35,8 @@ public ContentModelDiagnosticsParticipant(ContentModelPlugin contentModelPlugin)

@Override
public void doDiagnostics(DOMDocument xmlDocument, List<Diagnostic> diagnostics, CancelChecker monitor) {
if (xmlDocument.isDTD()) {
// Don't validate DTD with XML validator
if (xmlDocument.isDTD() || DOMUtils.isXSD(xmlDocument)) {
// Don't validate DTD / XML Schema with XML validator
return;
}
// Get entity resolver (XML catalog resolver, XML schema from the file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,20 @@ public static Range toLSPRange(XMLLocator location, XSDErrorCode code, Object[]
case s4s_att_not_allowed:
return XMLPositionUtility.selectAttributeNameAt(offset, document);
case s4s_att_invalid_value: {
String attrName = "";
String attrName = (String) arguments[1];
return XMLPositionUtility.selectAttributeValueAt(attrName, offset, document);
}
case s4s_elt_character:
return XMLPositionUtility.selectContent(offset, document);
case src_resolve_4_2:
case src_resolve:
case src_resolve_4_2: {
String attrValue = (String) arguments[2];
return XMLPositionUtility.selectAttributeValueByGivenValueAt(attrValue, offset, document);
}
case src_resolve: {
String attrValue = (String) arguments[0];
return XMLPositionUtility.selectAttributeValueByGivenValueAt(attrValue, offset, document);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.XMLSyntaxErrorCode;
import org.eclipse.lsp4xml.extensions.xsd.participants.XSDErrorCode;
import org.eclipse.lsp4xml.services.extensions.diagnostics.AbstractLSPErrorReporter;
import org.xml.sax.ErrorHandler;
Expand Down Expand Up @@ -54,6 +55,13 @@ protected Range toLSPRange(XMLLocator location, String key, Object[] arguments,
return range;
}
}
XMLSyntaxErrorCode syntaxCode = XMLSyntaxErrorCode.get(key);
if (syntaxCode != null) {
Range range = XMLSyntaxErrorCode.toLSPRange(location, syntaxCode, arguments, document);
if (range != null) {
return range;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ public void doDiagnostics(DOMDocument xmlDocument, List<Diagnostic> diagnostics,
// associations settings., ...)
XMLEntityResolver entityResolver = xmlDocument.getResolverExtensionManager();
// Process validation
// XSDValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics,
// monitor);
XSDValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, monitor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@
package org.eclipse.lsp4xml.extensions.xsd.participants.diagnostics;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.xerces.impl.Constants;
import org.apache.xerces.impl.XMLErrorReporter;
import org.apache.xerces.impl.xs.XMLSchemaLoader;
import org.apache.xerces.impl.xs.opti.SchemaDOMParser;
import org.apache.xerces.impl.xs.traversers.XSDHandler;
import org.apache.xerces.parsers.XMLGrammarPreparser;
import org.apache.xerces.util.XMLGrammarPoolImpl;
import org.apache.xerces.xni.grammars.XMLGrammarDescription;
Expand All @@ -38,8 +46,12 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR
List<Diagnostic> diagnostics, CancelChecker monitor) {

try {
XMLErrorReporter reporter = new LSPErrorReporterForXSD(document, diagnostics);

XMLGrammarPreparser grammarPreparser = new LSPXMLGrammarPreparser();
grammarPreparser.registerPreparser(XMLGrammarDescription.XML_SCHEMA, null/* schemaLoader */);
XMLSchemaLoader schemaLoader = createSchemaLoader(reporter);

grammarPreparser.registerPreparser(XMLGrammarDescription.XML_SCHEMA, schemaLoader);

grammarPreparser.setProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY,
new XMLGrammarPoolImpl());
Expand All @@ -57,50 +69,43 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR
grammarPreparser.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.WARN_ON_DUPLICATE_ATTDEF_FEATURE,
true);

/*
* if(configuration.getFeature(XSDValidationConfiguration.
* HONOUR_ALL_SCHEMA_LOCATIONS)) { try {
* grammarPreparser.setFeature(Constants.XERCES_FEATURE_PREFIX +
* "honour-all-schemaLocations", true); //$NON-NLS-1$ } catch (Exception e) { //
* catch the exception and ignore } }
*
* if(configuration.getFeature(XSDValidationConfiguration.
* FULL_SCHEMA_CONFORMANCE)) { try {
* grammarPreparser.setFeature(Constants.XERCES_FEATURE_PREFIX +
* Constants.SCHEMA_FULL_CHECKING, true); } catch (Exception e) { // ignore
* since we don't want to set it or can't. }
*
* }
*/

// Add LSP content handler to stop XML parsing if monitor is canceled.
// grammarPreparser.setContentHandler(new LSPContentHandler(monitor));

// Add LSP error reporter to fill LSP diagnostics from Xerces errors
grammarPreparser.setProperty("http://apache.org/xml/properties/internal/error-reporter",
new LSPErrorReporterForXSD(document, diagnostics));
grammarPreparser.setProperty("http://apache.org/xml/properties/internal/error-reporter", reporter);

if (entityResolver != null) {
grammarPreparser.setEntityResolver(entityResolver);
}

try {
String content = document.getText();
String uri = document.getDocumentURI();
InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
XMLInputSource is = new XMLInputSource(null, uri, uri, inputStream, null);
grammarPreparser.getLoader(XMLGrammarDescription.XML_SCHEMA);
grammarPreparser.preparseGrammar(XMLGrammarDescription.XML_SCHEMA, is);
} catch (Exception e) {
// parser will return null pointer exception if the document is structurally
// invalid
// TODO: log error message
// System.out.println(e);
}
String content = document.getText();
String uri = document.getDocumentURI();
InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
XMLInputSource is = new XMLInputSource(null, uri, uri, inputStream, null);
grammarPreparser.getLoader(XMLGrammarDescription.XML_SCHEMA);
grammarPreparser.preparseGrammar(XMLGrammarDescription.XML_SCHEMA, is);
} catch (IOException | CancellationException exception) {
// ignore error
} catch (Exception e) {
// TODO: log error.
// System.out.println(e);
LOGGER.log(Level.SEVERE, "Unexpected XMLValidator error", e);
}
}

private static XMLSchemaLoader createSchemaLoader(XMLErrorReporter reporter)
throws NoSuchFieldException, IllegalAccessException {
XMLSchemaLoader schemaLoader = new XMLSchemaLoader();

Field f = XMLSchemaLoader.class.getDeclaredField("fSchemaHandler");
f.setAccessible(true);
XSDHandler handler = (XSDHandler) f.get(schemaLoader);

Field g = XSDHandler.class.getDeclaredField("fSchemaParser");
g.setAccessible(true);
SchemaDOMParser domParser = (SchemaDOMParser) g.get(handler);

domParser.setProperty("http://apache.org/xml/properties/internal/error-reporter", reporter);
return schemaLoader;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DOMProcessingInstruction;
import org.eclipse.lsp4xml.dom.DOMText;
import org.eclipse.lsp4xml.dom.DTDAttlistDecl;
import org.eclipse.lsp4xml.dom.DTDDeclNode;
import org.eclipse.lsp4xml.dom.DTDDeclParameter;
Expand Down Expand Up @@ -385,6 +386,9 @@ public static Range selectContent(int offset, DOMDocument document) {
}
// node has NO content (ex: <root></root>, select the start tag
return selectStartTag(node);
} else if (node.isText()) {
DOMText text = (DOMText) node;
return createRange(text.getStartContent(), text.getEndContent(), document);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,107 @@
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4xml.XMLAssert;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.XMLSchemaErrorCode;
import org.eclipse.lsp4xml.extensions.xsd.participants.XSDErrorCode;
import org.eclipse.lsp4xml.extensions.xsd.participants.diagnostics.XSDValidator;
import org.junit.Test;

/**
* XSD diagnostics tests which test the {@link XSDURIResolverExtension}.
* XSD diagnostics tests which test the {@link XSDValidator}.
*
*/
public class XSDValidationExtensionsTest {

@Test
public void xsdInvalid() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n"
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\"> \r\n"
+ //
" <foo>bar</foo>\r\n" + // <- error foo doesn't exist
public void s4s_elt_invalid_content_1() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <foo></foo>\r\n" + // <- error foo doesn't exist
"</xs:schema>";
testDiagnosticsFor(xml, d(2, 4, 2, 7, XMLSchemaErrorCode.cvc_complex_type_2_4_a));
testDiagnosticsFor(xml, d(3, 2, 3, 5, XSDErrorCode.s4s_elt_invalid_content_1));
}

private void testDiagnosticsFor(String xml, Diagnostic... expected) throws BadLocationException {
XMLAssert.testDiagnosticsFor(xml, expected);
@Test
public void s4s_elt_character() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <xs:element name=\"foo\">bar</xs:element>\r\n" + // <- error with bar text
"</xs:schema>";
testDiagnosticsFor(xml, d(3, 24, 3, 27, XSDErrorCode.s4s_elt_character));
}

@Test
public void s4s_att_must_appear() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <xs:element></xs:element>\r\n" + // <- error with @name missing
"</xs:schema>";
testDiagnosticsFor(xml, d(3, 2, 3, 12, XSDErrorCode.s4s_att_must_appear));
}

@Test
public void s4s_att_not_allowed() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <xs:element foo=\"bar\" ></xs:element>\r\n" + // <- error with foo attribute which is not allowed
"</xs:schema>";
testDiagnosticsFor(xml, d(3, 13, 3, 16, XSDErrorCode.s4s_att_not_allowed),
d(3, 2, 3, 12, XSDErrorCode.s4s_att_must_appear));
}

@Test
public void s4s_att_invalid_value() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <xs:element name=\"\" ></xs:element>\r\n" + // <- error with @name which is empty
"</xs:schema>";
testDiagnosticsFor(xml, d(3, 18, 3, 20, XSDErrorCode.s4s_att_invalid_value),
d(3, 2, 3, 12, XSDErrorCode.s4s_att_must_appear));
}

@Test
public void src_resolve() throws BadLocationException {
String xml = "<?xml version=\"1.1\"?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
" <xs:element name=\"A\">\r\n" + //
" <xs:complexType>\r\n" + //
" <xs:sequence>\r\n" + //
" <xs:element name=\"A.1\" type=\"xs:string\" />\r\n" + //
" <xs:element name=\"A.2\" type=\"XXXXX\" /> \r\n" + // <- error with XXXXX
" </xs:sequence>\r\n" + //
" </xs:complexType>\r\n" + //
" </xs:element> \r\n" + //
"</xs:schema>";
testDiagnosticsFor(xml, d(7, 32, 7, 39, XSDErrorCode.src_resolve));
}

@Test
public void src_resolve2() throws BadLocationException {
String xml = "<?xml version=\"1.1\" ?>\r\n" + //
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\r\n" + //
" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">\r\n" + //
"\r\n" + //
" <xs:simpleType name=\"carType\">\r\n" + //
" <xs:restriction base=\"xs:string\">\r\n" + //
" <xs:enumeration value=\"Audi\" />\r\n" + //
" <xs:enumeration value=\"Golf\" />\r\n" + //
" <xs:enumeration value=\"BMW\" />\r\n" + //
" </xs:restriction>\r\n" + //
" </xs:simpleType>\r\n" + //
"\r\n" + //
" <xs:element name=\"car\" type=\"carType\" />\r\n" + //
" <xs:element name=\"foo\" type=\"fooType\" />\r\n" + // <- error with fooType which doesn't exists
"\r\n" + //
"</xs:schema>";
testDiagnosticsFor(xml, d(13, 29, 13, 38, XSDErrorCode.src_resolve));
}

private static void testDiagnosticsFor(String xml, Diagnostic... expected) throws BadLocationException {
XMLAssert.testDiagnosticsFor(xml, null, null, "test.xsd", expected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name=""></xs:element>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element></xs:element>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element foo="bar"></xs:element>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="foo">bar</xs:element>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<foo></foo>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="A">
<xs:complexType>
<xs:sequence>
<xs:element name="A.1" type="xs:string" />
<xs:element name="A.2" type="XXXXX" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.1" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">

<xs:simpleType name="carType">
<xs:restriction base="xs:string">
<xs:enumeration value="Audi" />
<xs:enumeration value="Golf" />
<xs:enumeration value="BMW" />
</xs:restriction>
</xs:simpleType>

<xs:element name="car" type="carType" />
<xs:element name="foo" type="fooType" />

</xs:schema>

0 comments on commit fe3f9bc

Please sign in to comment.