From 0adf69c0a17dabf14f1a9fb0e3b4862e4cc3454c Mon Sep 17 00:00:00 2001 From: azerr Date: Mon, 17 Jun 2019 10:32:07 -0400 Subject: [PATCH] XSD 1.1 working Fix #363 Signed-off-by: azerr --- README.md | 1 + org.eclipse.lsp4xml/pom.xml | 5 +- .../contentmodel/ContentModelPlugin.java | 14 +-- .../model/ContentModelManager.java | 11 ++ .../ContentModelDiagnosticsParticipant.java | 14 +-- .../LSPXMLParserConfiguration.java | 34 +++++- .../diagnostics/XMLValidator.java | 14 +-- .../settings/XMLValidationSettings.java | 83 +++++++++++++- .../lsp4xml/extensions/xsd/XSDPlugin.java | 8 +- .../CMXSDContentModelProvider.java | 30 +++++- .../xsd/contentmodel/CMXSDDocument.java | 18 ++-- .../contentmodel/CMXSDElementDeclaration.java | 81 ++++++++++++-- .../XSDDiagnosticsParticipant.java | 11 +- .../diagnostics/XSDValidator.java | 14 ++- .../diagnostics/LSPMessageFormatter.java | 4 +- ...ntentModelManagerDependsOnGrammarTest.java | 2 +- .../xsd/XSD11ValidationExtensionsTest.java | 102 ++++++++++++++++++ .../xsd/XSDValidationExtensionsTest.java | 2 +- 18 files changed, 395 insertions(+), 53 deletions(-) create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSD11ValidationExtensionsTest.java diff --git a/README.md b/README.md index 2878ef7a8d..72e1c7cad9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Features * [textDocument/hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover). * [textDocument/rangeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting) * [textDocument/rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_rename). +* XML Schema `1.0` and `1.1` support.t See screenshots in the [wiki](https://github.com/angelozerr/lsp4xml/wiki/Features). diff --git a/org.eclipse.lsp4xml/pom.xml b/org.eclipse.lsp4xml/pom.xml index fb095d0345..96b4fb64ab 100644 --- a/org.eclipse.lsp4xml/pom.xml +++ b/org.eclipse.lsp4xml/pom.xml @@ -97,10 +97,11 @@ org.eclipse.lsp4j.jsonrpc - xerces + org.exist-db.thirdparty.xerces xercesImpl 2.12.0 - + xml-schema-1.1 + xml-apis xml-apis diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelPlugin.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelPlugin.java index 8f24224983..7e1caf618a 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelPlugin.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelPlugin.java @@ -37,7 +37,7 @@ public class ContentModelPlugin implements IXMLExtension { private final IHoverParticipant hoverParticipant; - private final IDiagnosticsParticipant diagnosticsParticipant; + private IDiagnosticsParticipant diagnosticsParticipant; private final ICodeActionParticipant codeActionParticipant; @@ -47,12 +47,9 @@ public class ContentModelPlugin implements IXMLExtension { ContentModelManager contentModelManager; - private ContentModelSettings cmSettings; - public ContentModelPlugin() { completionParticipant = new ContentModelCompletionParticipant(); hoverParticipant = new ContentModelHoverParticipant(); - diagnosticsParticipant = new ContentModelDiagnosticsParticipant(this); codeActionParticipant = new ContentModelCodeActionParticipant(); documentLinkParticipant = new ContentModelDocumentLinkParticipant(); typeDefinitionParticipant = new ContentModelTypeDefinitionParticipant(); @@ -83,10 +80,12 @@ public void doSave(ISaveContext context) { private void updateSettings(ISaveContext saveContext) { Object initializationOptionsSettings = saveContext.getSettings(); - cmSettings = ContentModelSettings.getContentModelXMLSettings(initializationOptionsSettings); + ContentModelSettings cmSettings = ContentModelSettings + .getContentModelXMLSettings(initializationOptionsSettings); if (cmSettings != null) { updateSettings(cmSettings, saveContext); } + contentModelManager.setSettings(cmSettings); } private void updateSettings(ContentModelSettings settings, ISaveContext context) { @@ -126,6 +125,7 @@ private void updateSettings(ContentModelSettings settings, ISaveContext context) @Override public void start(InitializeParams params, XMLExtensionsRegistry registry) { + diagnosticsParticipant = new ContentModelDiagnosticsParticipant(registry); URIResolverExtensionManager resolverManager = registry.getComponent(URIResolverExtensionManager.class); contentModelManager = new ContentModelManager(resolverManager); registry.registerComponent(contentModelManager); @@ -149,8 +149,4 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterDocumentLinkParticipant(documentLinkParticipant); registry.unregisterTypeDefinitionParticipant(typeDefinitionParticipant); } - - public ContentModelSettings getContentModelSettings() { - return cmSettings; - } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java index 179c1b1b47..7b2feac5c3 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/ContentModelManager.java @@ -19,6 +19,7 @@ import org.eclipse.lsp4xml.dom.DOMDocument; import org.eclipse.lsp4xml.dom.DOMElement; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.ContentModelSettings; import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLFileAssociation; import org.eclipse.lsp4xml.extensions.contentmodel.uriresolver.XMLCacheResolverExtension; import org.eclipse.lsp4xml.extensions.contentmodel.uriresolver.XMLCatalogResolverExtension; @@ -43,6 +44,8 @@ public class ContentModelManager { private final XMLCatalogResolverExtension catalogResolverExtension; private final XMLFileAssociationResolverExtension fileAssociationResolver; + private ContentModelSettings settings; + public ContentModelManager(URIResolverExtensionManager resolverManager) { this.resolverManager = resolverManager; modelProviders = new ArrayList<>(); @@ -57,6 +60,14 @@ public ContentModelManager(URIResolverExtensionManager resolverManager) { setUseCache(true); } + public void setSettings(ContentModelSettings settings) { + this.settings = settings; + } + + public ContentModelSettings getSettings() { + return settings; + } + public CMElementDeclaration findCMElement(DOMElement element) throws Exception { return findCMElement(element, element.getNamespaceURI()); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/ContentModelDiagnosticsParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/ContentModelDiagnosticsParticipant.java index 8cdd01582f..75232bb2c3 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/ContentModelDiagnosticsParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/ContentModelDiagnosticsParticipant.java @@ -16,7 +16,8 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4xml.dom.DOMDocument; -import org.eclipse.lsp4xml.extensions.contentmodel.ContentModelPlugin; +import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry; import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lsp4xml.utils.DOMUtils; @@ -26,11 +27,10 @@ * */ public class ContentModelDiagnosticsParticipant implements IDiagnosticsParticipant { + private final XMLExtensionsRegistry registry; - private final ContentModelPlugin contentModelPlugin; - - public ContentModelDiagnosticsParticipant(ContentModelPlugin contentModelPlugin) { - this.contentModelPlugin = contentModelPlugin; + public ContentModelDiagnosticsParticipant(XMLExtensionsRegistry registry) { + this.registry = registry; } @Override @@ -43,8 +43,8 @@ public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, // associations settings., ...) XMLEntityResolver entityResolver = xmlDocument.getResolverExtensionManager(); // Process validation - XMLValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, - contentModelPlugin.getContentModelSettings(), monitor); + ContentModelManager manager = registry.getComponent(ContentModelManager.class); + XMLValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, manager.getSettings(), monitor); } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java index 523e80f11d..9532e228d4 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java @@ -9,7 +9,9 @@ *******************************************************************************/ package org.eclipse.lsp4xml.extensions.contentmodel.participants.diagnostics; +import org.apache.xerces.impl.Constants; import org.apache.xerces.impl.dtd.XMLDTDValidator; +import org.apache.xerces.impl.xs.XMLSchemaValidator; import org.apache.xerces.parsers.XIncludeAwareParserConfiguration; import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.parser.XMLComponentManager; @@ -25,12 +27,42 @@ */ class LSPXMLParserConfiguration extends XIncludeAwareParserConfiguration { + private static final String XML_SCHEMA_VERSION = Constants.XERCES_PROPERTY_PREFIX + + Constants.XML_SCHEMA_VERSION_PROPERTY; + + private static final String SCHEMA_VALIDATOR = Constants.XERCES_PROPERTY_PREFIX + + Constants.SCHEMA_VALIDATOR_PROPERTY; + + private final String namespaceSchemaVersion; + private final boolean disableDTDValidation; - public LSPXMLParserConfiguration(boolean disableDTDValidation) { + public LSPXMLParserConfiguration(String namespaceSchemaVersion, boolean disableDTDValidation) { + this.namespaceSchemaVersion = namespaceSchemaVersion; this.disableDTDValidation = disableDTDValidation; } + @Override + protected void configurePipeline() { + super.configurePipeline(); + configureSchemaVersion(); + } + + @Override + protected void configureXML11Pipeline() { + super.configureXML11Pipeline(); + configureSchemaVersion(); + } + + private void configureSchemaVersion() { + if (namespaceSchemaVersion != null) { + XMLSchemaValidator validator = (XMLSchemaValidator) super.getProperty(SCHEMA_VALIDATOR); + if (validator != null) { + validator.setProperty(XML_SCHEMA_VERSION, namespaceSchemaVersion); + } + } + } + @Override protected void reset() throws XNIException { super.reset(); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/XMLValidator.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/XMLValidator.java index d0359948d3..b9f6213748 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/XMLValidator.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/diagnostics/XMLValidator.java @@ -58,14 +58,17 @@ public class XMLValidator { private static final String DTD_NOT_FOUND = "Cannot find DTD ''{0}''.\nCreate the DTD file or configure an XML catalog for this DTD."; public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityResolver, - List diagnostics, ContentModelSettings contentModelSettings, CancelChecker monitor) { + List diagnostics, ContentModelSettings settings, CancelChecker monitor) { try { // It should be better to cache XML Schema with XMLGrammarCachingConfiguration, // but we cannot use // XMLGrammarCachingConfiguration because cache is done with target namespaces. // There are conflicts when // 2 XML Schemas don't define target namespaces. - LSPXMLParserConfiguration configuration = new LSPXMLParserConfiguration( + + // Configure the XSD schema version + String namespaceSchemaVersion = XMLValidationSettings.getNamespaceSchemaVersion(settings); + LSPXMLParserConfiguration configuration = new LSPXMLParserConfiguration(namespaceSchemaVersion, isDisableOnlyDTDValidation(document)); if (entityResolver != null) { @@ -74,6 +77,7 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR final LSPErrorReporterForXML reporter = new LSPErrorReporterForXML(document, diagnostics); boolean externalDTDValid = checkExternalDTD(document, reporter, configuration); + SAXParser parser = new SAXParser(configuration); // Add LSP error reporter to fill LSP diagnostics from Xerces errors parser.setProperty("http://apache.org/xml/properties/internal/error-reporter", reporter); @@ -87,9 +91,7 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR boolean hasGrammar = document.hasGrammar(); // If diagnostics for Schema preference is enabled - XMLValidationSettings validationSettings = contentModelSettings != null - ? contentModelSettings.getValidation() - : null; + XMLValidationSettings validationSettings = settings != null ? settings.getValidation() : null; if ((validationSettings == null) || validationSettings.isSchema()) { checkExternalSchema(document.getExternalSchemaLocation(), parser); @@ -97,7 +99,7 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR parser.setFeature("http://apache.org/xml/features/validation/schema", hasGrammar); //$NON-NLS-1$ // warn if XML document is not bound to a grammar according the settings - warnNoGrammar(document, diagnostics, contentModelSettings); + warnNoGrammar(document, diagnostics, settings); } else { hasGrammar = false; // validation for Schema was disabled } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/settings/XMLValidationSettings.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/settings/XMLValidationSettings.java index 345d3448fd..7e3fce6443 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/settings/XMLValidationSettings.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/settings/XMLValidationSettings.java @@ -11,17 +11,47 @@ package org.eclipse.lsp4xml.extensions.contentmodel.settings; +import org.apache.xerces.impl.Constants; import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4xml.utils.StringUtils; /** * XMLValidationSettings */ public class XMLValidationSettings { + /** + * Schema version. + * + *

+ * Supported version by Xerces are 1.0, 1.1 and 1.0EX. + *

+ * + * @see https://github.com/apache/xerces2-j/blob/xml-schema-1.1-dev/src/org/apache/xerces/impl/Constants.java#L42 + * + */ + public enum SchemaVersion { + + V10("1.0"), V11("1.1"), V10EX("1.0EX"); + + private final String version; + + private SchemaVersion(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } + + } + private Boolean schema; private Boolean enabled; + private String schemaVersion; + /** * This severity preference to mark the root element of XML document which is * not bound to a XML Schema/DTD. @@ -31,7 +61,7 @@ public class XMLValidationSettings { private String noGrammar; public XMLValidationSettings() { - //set defaults + // set defaults schema = true; enabled = true; } @@ -64,6 +94,30 @@ public void setSchema(boolean schema) { this.schema = schema; } + /** + * Returns the schema version. + * + *

+ * Supported version by Xerces are 1.0, 1.1 and 1.0EX. + *

+ * + * @see https://github.com/apache/xerces2-j/blob/xml-schema-1.1-dev/src/org/apache/xerces/impl/Constants.java#L42 + * + * @return the schema version + */ + public String getSchemaVersion() { + return schemaVersion; + } + + /** + * Set the schema version + * + * @param schemaVersion the schema version + */ + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + public void setNoGrammar(String noGrammar) { this.noGrammar = noGrammar; } @@ -100,12 +154,35 @@ public static DiagnosticSeverity getNoGrammarSeverity(ContentModelSettings setti return defaultSeverity; } + /** + * Returns the Xerces namespace of the schema version to use and 1.0 otherwise. + * + * @param settings the settings + * @return the Xerces namespace of the schema version to use and 1.0 otherwise. + */ + public static String getNamespaceSchemaVersion(ContentModelSettings settings) { + if (settings == null || settings.getValidation() == null) { + return Constants.W3C_XML_SCHEMA10_NS_URI; + } + String schemaVersion = settings.getValidation().getSchemaVersion(); + if (StringUtils.isEmpty(schemaVersion)) { + return Constants.W3C_XML_SCHEMA10_NS_URI; + } + if (SchemaVersion.V11.getVersion().equals(schemaVersion)) { + return Constants.W3C_XML_SCHEMA11_NS_URI; + } + if (SchemaVersion.V10EX.getVersion().equals(schemaVersion)) { + return Constants.W3C_XML_SCHEMA10EX_NS_URI; + } + return Constants.W3C_XML_SCHEMA10_NS_URI; + } + public XMLValidationSettings merge(XMLValidationSettings settings) { - if(settings != null) { + if (settings != null) { this.schema = settings.schema; this.enabled = settings.enabled; } return this; } - + } \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/XSDPlugin.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/XSDPlugin.java index a4a56a9301..00e8327156 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/XSDPlugin.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/XSDPlugin.java @@ -41,8 +41,7 @@ public class XSDPlugin implements IXMLExtension { private final IDefinitionParticipant definitionParticipant; - private final IDiagnosticsParticipant diagnosticsParticipant; - + private IDiagnosticsParticipant diagnosticsParticipant; private final IReferenceParticipant referenceParticipant; private final ICodeLensParticipant codeLensParticipant; private final IHighlightingParticipant highlightingParticipant; @@ -53,7 +52,6 @@ public class XSDPlugin implements IXMLExtension { public XSDPlugin() { completionParticipant = new XSDCompletionParticipant(); definitionParticipant = new XSDDefinitionParticipant(); - diagnosticsParticipant = new XSDDiagnosticsParticipant(); referenceParticipant = new XSDReferenceParticipant(); codeLensParticipant = new XSDCodeLensParticipant(); highlightingParticipant = new XSDHighlightingParticipant(); @@ -73,12 +71,14 @@ public void doSave(ISaveContext context) { @Override public void start(InitializeParams params, XMLExtensionsRegistry registry) { + diagnosticsParticipant = new XSDDiagnosticsParticipant(registry); // Register resolver uiResolver = new XSDURIResolverExtension(registry.getDocumentProvider()); registry.getResolverExtensionManager().registerResolver(uiResolver); // register XSD content model provider - ContentModelProvider modelProvider = new CMXSDContentModelProvider(registry.getResolverExtensionManager()); modelManager = registry.getComponent(ContentModelManager.class); + ContentModelProvider modelProvider = new CMXSDContentModelProvider(registry.getResolverExtensionManager(), + modelManager); modelManager.registerModelProvider(modelProvider); // register completion, diagnostic participant registry.registerCompletionParticipant(completionParticipant); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDContentModelProvider.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDContentModelProvider.java index 1ce365a70e..5d552dc1a4 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDContentModelProvider.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDContentModelProvider.java @@ -12,12 +12,16 @@ import org.apache.xerces.impl.Constants; import org.apache.xerces.impl.xs.XSLoaderImpl; +import org.apache.xerces.impl.xs.models.CMBuilder; +import org.apache.xerces.impl.xs.models.CMNodeFactory; import org.apache.xerces.xs.XSModel; import org.eclipse.lsp4xml.dom.DOMDocument; import org.eclipse.lsp4xml.dom.NoNamespaceSchemaLocation; import org.eclipse.lsp4xml.dom.SchemaLocation; import org.eclipse.lsp4xml.extensions.contentmodel.model.CMDocument; +import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelProvider; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLValidationSettings; import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException; import org.eclipse.lsp4xml.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4xml.utils.DOMUtils; @@ -29,12 +33,21 @@ */ public class CMXSDContentModelProvider implements ContentModelProvider { + private static final String XML_SCHEMA_VERSION = Constants.XERCES_PROPERTY_PREFIX + + Constants.XML_SCHEMA_VERSION_PROPERTY; + private final URIResolverExtensionManager resolverExtensionManager; + private final ContentModelManager modelManager; + private XSLoaderImpl loader; - public CMXSDContentModelProvider(URIResolverExtensionManager resolverExtensionManager) { + private CMBuilder cmBuilder; + + public CMXSDContentModelProvider(URIResolverExtensionManager resolverExtensionManager, + ContentModelManager modelManager) { this.resolverExtensionManager = resolverExtensionManager; + this.modelManager = modelManager; } @Override @@ -73,7 +86,7 @@ public CMDocument createCMDocument(String key) { XSModel model = getLoader().loadURI(key); if (model != null) { // XML Schema can be loaded - return new CMXSDDocument(model, key); + return new CMXSDDocument(model, key, cmBuilder); } return null; } @@ -87,9 +100,22 @@ public XSLoaderImpl getLoader() { if (loader == null) { loader = getSynchLoader(); } + String version = XMLValidationSettings.getNamespaceSchemaVersion(modelManager.getSettings()); + loader.setParameter(XML_SCHEMA_VERSION, version); + cmBuilder = new CMBuilder(new CMNodeFactory()); + cmBuilder.setSchemaVersion(getSchemaVersion(version)); return loader; } + private static short getSchemaVersion(String version) { + if (Constants.W3C_XML_SCHEMA10_NS_URI.equals(version)) { + return Constants.SCHEMA_VERSION_1_0; + } else if (Constants.W3C_XML_SCHEMA11_NS_URI.equals(version)) { + return Constants.SCHEMA_VERSION_1_1; + } + return Constants.SCHEMA_VERSION_1_0_EXTENDED; + } + private synchronized XSLoaderImpl getSynchLoader() { if (loader != null) { return loader; diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDDocument.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDDocument.java index 4123cbb54e..150a43a458 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDDocument.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDDocument.java @@ -26,6 +26,7 @@ import org.apache.xerces.impl.xs.XSComplexTypeDecl; import org.apache.xerces.impl.xs.XSElementDecl; import org.apache.xerces.impl.xs.XSElementDeclHelper; +import org.apache.xerces.impl.xs.models.CMBuilder; import org.apache.xerces.impl.xs.util.SimpleLocator; import org.apache.xerces.xni.QName; import org.apache.xerces.xni.XMLLocator; @@ -70,18 +71,17 @@ public class CMXSDDocument implements CMDocument, XSElementDeclHelper { private final Map elementMappings; - private Collection elements; + private final CMBuilder cmBuilder; + + private final String uri; - private String uri; + private Collection elements; - public CMXSDDocument(XSModel model) { + public CMXSDDocument(XSModel model, String uri, CMBuilder cmBuilder) { this.model = model; this.elementMappings = new HashMap<>(); - } - - public CMXSDDocument(XSModel model, String uri) { - this(model); this.uri = uri; + this.cmBuilder = cmBuilder; } @Override @@ -554,4 +554,8 @@ private static LocationLink findXSAttribute(DOMAttr originAttribute, NodeList ch } return null; } + + CMBuilder getCMBuilder() { + return cmBuilder; + } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java index d6c0de117c..e94beb3237 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java @@ -13,7 +13,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.Vector; import org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl; @@ -22,7 +24,7 @@ import org.apache.xerces.impl.xs.XSComplexTypeDecl; import org.apache.xerces.impl.xs.XSElementDecl; import org.apache.xerces.impl.xs.models.CMBuilder; -import org.apache.xerces.impl.xs.models.CMNodeFactory; +import org.apache.xerces.impl.xs.models.XS11AllCM; import org.apache.xerces.impl.xs.models.XSCMValidator; import org.apache.xerces.xni.QName; import org.apache.xerces.xs.XSAttributeUse; @@ -30,7 +32,6 @@ import org.apache.xerces.xs.XSConstants; import org.apache.xerces.xs.XSElementDeclaration; import org.apache.xerces.xs.XSModelGroup; -import org.apache.xerces.xs.XSNamespaceItem; import org.apache.xerces.xs.XSObject; import org.apache.xerces.xs.XSObjectList; import org.apache.xerces.xs.XSParticle; @@ -133,25 +134,40 @@ public Collection getPossibleElements(DOMElement parentEle List qNames = toQNames(parentElement, offset); // Initialize Xerces validator - CMBuilder cmBuilder = new CMBuilder(new CMNodeFactory()); + CMBuilder cmBuilder = document.getCMBuilder(); XSCMValidator validator = ((XSComplexTypeDecl) typeDefinition).getContentModel(cmBuilder); SubstitutionGroupHandler handler = new SubstitutionGroupHandler(document); // Loop for each element (QName) and check if it is valid according the XML // Schema constraint int[] states = validator.startContentModel(); + // Xerces has a bug with xs:all and xs:element maxOccurs="unbound" + // see https://issues.apache.org/jira/browse/XERCESJ-1710, to fix it we add the + // missing xs:element in a set + boolean xsALl11 = isXSAll11(validator); + Set missingEltDeclForXSALL11 = null; for (QName elementName : qNames) { - Object decl = validator.oneTransition(elementName, states, handler); + Object decl = validator.oneTransition(elementName, states, handler, document); if (decl == null) { + // the current elemntName doesn't match the XSD constraints, return an empty + // list return Collections.emptyList(); } + if (xsALl11) { + // Check if the xs:element has defined a maxOccurs="unbound" + if (isMaxOccursUnbounded11(decl)) { + if (missingEltDeclForXSALL11 == null) { + missingEltDeclForXSALL11 = new HashSet<>(); + } + missingEltDeclForXSALL11.add((XSElementDeclaration) decl); + } + } } - // At this step, all child elements are valid, the call of // XSCMValidator#oneTransition has updated the states flag. // Collect the next valid elements according the XML Schema constraints Vector result = validator.whatCanGoHere(states); - if (result.isEmpty()) { + if (result.isEmpty() && missingEltDeclForXSALL11 == null) { return Collections.emptyList(); } @@ -161,6 +177,16 @@ public Collection getPossibleElements(DOMElement parentEle if (object instanceof XSElementDeclaration) { XSElementDeclaration elementDecl = (XSElementDeclaration) object; document.collectElement(elementDecl, possibleElements); + // Remove the element declaration from the missing xs:element for xs:all 1.1 + if (missingEltDeclForXSALL11 != null) { + missingEltDeclForXSALL11.remove(elementDecl); + } + } + } + // Add missing xs:element for xs:all 1.1 + if (missingEltDeclForXSALL11 != null) { + for (XSElementDeclaration decl : missingEltDeclForXSALL11) { + document.collectElement(decl, possibleElements); } } return possibleElements; @@ -168,6 +194,49 @@ public Collection getPossibleElements(DOMElement parentEle return getElements(); } + /** + * Returns true if the given validator is a xs:all for XSD 1.1 and false + * otherwise. + * + * @param validator the CM Xerces validator + * @return true if the given validator is a xs:all for XSD 1.1 and false + * otherwise. + */ + private static boolean isXSAll11(XSCMValidator validator) { + return validator instanceof XS11AllCM; + } + + /** + * Returns true if the current xs:element defined a maxOccurs="unbound" and + * false otherwise. + * + * @param decl the xs:element + * @return true if the current xs:element defined a maxOccurs="unbound" and + * false otherwise. + */ + private static boolean isMaxOccursUnbounded11(Object decl) { + if (!(decl instanceof XSElementDecl)) { + return false; + } + // Get the xs:all type + XSElementDecl elementDecl = (XSElementDecl) decl; + XSComplexTypeDefinition allType = elementDecl.getEnclosingCTDefinition(); + if (allType == null || allType.getParticle() == null || allType.getParticle().getTerm() == null + || !(allType.getParticle().getTerm() instanceof XSModelGroup)) { + return false; + } + XSModelGroup group = (XSModelGroup) allType.getParticle().getTerm(); + // Loop for each particle to retrieve the proper particle of the xs:element + XSObjectList particles = group.getParticles(); + for (int i = 0; i < particles.getLength(); i++) { + XSParticle particle = (XSParticle) particles.item(i); + if (elementDecl.equals(particle.getTerm())) { + return particle.getMaxOccursUnbounded(); + } + } + return false; + } + /** * Returns list of element (QName) of child elements of the given parent element * upon the given offset diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDDiagnosticsParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDDiagnosticsParticipant.java index bf9b5ec5e1..164ca2fc10 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDDiagnosticsParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDDiagnosticsParticipant.java @@ -16,6 +16,8 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4xml.dom.DOMDocument; +import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry; import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lsp4xml.utils.DOMUtils; @@ -25,6 +27,12 @@ */ public class XSDDiagnosticsParticipant implements IDiagnosticsParticipant { + private final XMLExtensionsRegistry registry; + + public XSDDiagnosticsParticipant(XMLExtensionsRegistry registry) { + this.registry = registry; + } + @Override public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, CancelChecker monitor) { if (!DOMUtils.isXSD(xmlDocument)) { @@ -35,7 +43,8 @@ public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, // associations settings., ...) XMLEntityResolver entityResolver = xmlDocument.getResolverExtensionManager(); // Process validation - XSDValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, monitor); + ContentModelManager manager = registry.getComponent(ContentModelManager.class); + XSDValidator.doDiagnostics(xmlDocument, entityResolver, diagnostics, manager.getSettings(), monitor); } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDValidator.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDValidator.java index d1b797caa7..d42804df6c 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDValidator.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/diagnostics/XSDValidator.java @@ -34,6 +34,8 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4xml.dom.DOMDocument; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.ContentModelSettings; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLValidationSettings; /** * XSD validator utilities class. @@ -41,12 +43,15 @@ */ public class XSDValidator { + private static final String XML_SCHEMA_VERSION = Constants.XERCES_PROPERTY_PREFIX + + Constants.XML_SCHEMA_VERSION_PROPERTY; + private static final Logger LOGGER = Logger.getLogger(XSDValidator.class.getName()); private static boolean canCustomizeReporter = true; public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityResolver, - List diagnostics, CancelChecker monitor) { + List diagnostics, ContentModelSettings settings, CancelChecker monitor) { try { XMLErrorReporter reporter = new LSPErrorReporterForXSD(document, diagnostics); @@ -82,6 +87,12 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR grammarPreparser.setEntityResolver(entityResolver); } + // Configure the XSD schema version + String namespaceSchemaVersion = XMLValidationSettings.getNamespaceSchemaVersion(settings); + if (namespaceSchemaVersion != null) { + grammarPreparser.setProperty(XML_SCHEMA_VERSION, namespaceSchemaVersion); + } + String content = document.getText(); String uri = document.getDocumentURI(); InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); @@ -125,6 +136,7 @@ private static XMLSchemaLoader createSchemaLoader(XMLErrorReporter reporter) { SchemaDOMParser domParser = (SchemaDOMParser) g.get(handler); domParser.setProperty("http://apache.org/xml/properties/internal/error-reporter", reporter); } catch (Exception e) { + e.printStackTrace(); canCustomizeReporter = false; } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/diagnostics/LSPMessageFormatter.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/diagnostics/LSPMessageFormatter.java index 14eea7b156..2821a6de63 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/diagnostics/LSPMessageFormatter.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/diagnostics/LSPMessageFormatter.java @@ -150,7 +150,7 @@ private static String reformatElementNames(boolean hasNamespace, String names) { stream.advance(2); // Consume quotation and':' } sb.append(" - "); - while (stream.peekChar() != _CCB && stream.peekChar() != _CMA) { // } | , + while (stream.peekChar() != _CCB && stream.peekChar() != _CMA && !stream.eos()) { // } | , sb.append(Character.toString((char) stream.peekChar())); stream.advance(1); } @@ -176,7 +176,7 @@ private static String reformatArrayElementNames(String names) { while (!stream.eos()) { // ] stream.advance(1);// Consume ' ' or '[' if first item sb.append(" - "); - while (stream.peekChar() != _CSB && stream.peekChar() != _CMA) { // ] | , + while (stream.peekChar() != _CSB && stream.peekChar() != _CMA && !stream.eos()) { // ] | , sb.append(Character.toString((char) stream.peekChar())); stream.advance(1); } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelManagerDependsOnGrammarTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelManagerDependsOnGrammarTest.java index 00359b0998..3baeb885b0 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelManagerDependsOnGrammarTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/ContentModelManagerDependsOnGrammarTest.java @@ -41,7 +41,7 @@ public void setup() { modelManager.registerModelProvider(modelProvider); // XSD -> See XSDPlugin - modelProvider = new CMXSDContentModelProvider(resolverExtensionManager); + modelProvider = new CMXSDContentModelProvider(resolverExtensionManager, modelManager); modelManager.registerModelProvider(modelProvider); } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSD11ValidationExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSD11ValidationExtensionsTest.java new file mode 100644 index 0000000000..ad70e4f987 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSD11ValidationExtensionsTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* +* Copyright (c) 2019 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lsp4xml.extensions.xsd; + +import static org.eclipse.lsp4xml.XMLAssert.d; + +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4xml.XMLAssert; +import org.eclipse.lsp4xml.commons.BadLocationException; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.ContentModelSettings; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLValidationSettings; +import org.eclipse.lsp4xml.extensions.contentmodel.settings.XMLValidationSettings.SchemaVersion; +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 XSDValidator} with XSD 1.1. + * + */ +public class XSD11ValidationExtensionsTest { + + @Test + public void minVersion_WithXSD1_0() throws BadLocationException { + // This test use XSD 1.0 processor + + // Here minVersion is set to 1.0, there are 2 errors: + // - because maxOccurs="unbound" on xs:element is not allowed + // - is not allow in a XSD + String xml = getXMLForXSD1_1Test("1.0"); + testDiagnosticsFor(xml, SchemaVersion.V10, d(15, 2, 15, 3, XSDErrorCode.s4s_elt_invalid_content_1), // <- error + // with + d(11, 54, 11, 65, XSDErrorCode.cos_all_limited_2)); // <- error with maxOccurs="unbound" + + // Here minVersion is set to 1.1, Xerces doesn't validate the XSD file + xml = getXMLForXSD1_1Test("1.1"); + testDiagnosticsFor(xml, SchemaVersion.V10); + } + + @Test + public void minVersion_WithXSD1_1() throws BadLocationException { + // This test use XSD 1.1 processor + + // Here minVersion is set to 1.0, there are 1 error: + // - is not allow in a XSD + String xml = getXMLForXSD1_1Test("1.0"); + testDiagnosticsFor(xml, SchemaVersion.V11, d(15, 2, 15, 3, XSDErrorCode.s4s_elt_invalid_content_1)); // <- error + // with + // + + // Here minVersion is set to 1.1, there are 1 error: + // - is not allow in a XSD + xml = getXMLForXSD1_1Test("1.1"); + testDiagnosticsFor(xml, SchemaVersion.V11, d(15, 2, 15, 3, XSDErrorCode.s4s_elt_invalid_content_1)); // <- error + // with + // + } + + private static String getXMLForXSD1_1Test(String minVersion) { + return "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + " \r\n" // <- error with maxOccurs="unbound" XSD 1.0 but + // not with XSD 1.1 + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // <-- we add an error here to check + ""; + } + + private static void testDiagnosticsFor(String xml, Diagnostic... expected) throws BadLocationException { + XMLAssert.testDiagnosticsFor(xml, null, null, "test.xsd", expected); + } + + private static void testDiagnosticsFor(String xml, SchemaVersion schemaVersion, Diagnostic... expected) + throws BadLocationException { + XMLAssert.testDiagnosticsFor(xml, null, ls -> { + ContentModelSettings settings = new ContentModelSettings(); + XMLValidationSettings problems = new XMLValidationSettings(); + problems.setSchemaVersion(schemaVersion.getVersion()); + settings.setValidation(problems); + ls.doSave(new XMLAssert.SettingsSaveContext(settings)); + }, "test.xsd", expected); + } +} diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDValidationExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDValidationExtensionsTest.java index 085a411c40..c817650b9c 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDValidationExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDValidationExtensionsTest.java @@ -334,7 +334,7 @@ public void src_import_1_2() throws BadLocationException { testDiagnosticsFor(xml, d); testCodeActionsFor(xml, d, ca(d, te(2, 8, 2, 8, " namespace=\"\"")), ca(d, te(1, 48, 1, 48, " targetNamespace=\"\""))); } - + private static void testDiagnosticsFor(String xml, Diagnostic... expected) throws BadLocationException { XMLAssert.testDiagnosticsFor(xml, null, null, "test.xsd", expected); }