From f72787a746b9967328b2d33717c15f1f3d769e92 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 3 Feb 2022 18:29:59 +0100 Subject: [PATCH] validation only happens on starting VS code Fixes https://github.com/redhat-developer/vscode-xml/issues/647 Signed-off-by: azerr --- .../eclipse/lemminx/dom/DTDEntityDecl.java | 4 + .../ContentModelDocumentLinkParticipant.java | 19 +++ .../participants/DTDErrorCode.java | 42 +++++- .../diagnostics/LSPSAXParser.java | 26 ++-- .../diagnostics/LSPXMLEntityManager.java | 124 ++++++++++++++++++ .../diagnostics/LSPXMLGrammarPoolWrapper.java | 77 +++++++++++ .../LSPXMLParserConfiguration.java | 9 +- .../diagnostics/XMLValidator.java | 26 +++- .../lemminx/extensions/dtd/DTDPlugin.java | 6 + .../DTDDocumentLinkParticipant.java | 91 +++++++++++++ .../lemminx/utils/XMLPositionUtility.java | 20 +-- 11 files changed, 414 insertions(+), 30 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLEntityManager.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLGrammarPoolWrapper.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/DTDDocumentLinkParticipant.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDEntityDecl.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDEntityDecl.java index 6541cc44b1..b327afee5f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDEntityDecl.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DTDEntityDecl.java @@ -135,6 +135,10 @@ public void setSystemId(int start, int end) { systemId = addNewParameter(start, end); } + public DTDDeclParameter getSystemIdNode() { + return systemId; + } + /* * (non-Javadoc) * diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelDocumentLinkParticipant.java index 0ce9f069ce..6e41aff1d0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelDocumentLinkParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelDocumentLinkParticipant.java @@ -21,6 +21,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMDocumentType; import org.eclipse.lemminx.dom.DOMRange; +import org.eclipse.lemminx.dom.DTDEntityDecl; import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation; import org.eclipse.lemminx.dom.SchemaLocation; import org.eclipse.lemminx.dom.SchemaLocationHint; @@ -28,6 +29,7 @@ import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4j.DocumentLink; +import org.w3c.dom.NamedNodeMap; /** * Document link for : @@ -82,6 +84,23 @@ public void findDocumentLinks(DOMDocument document, List links) { // Do nothing } } + + NamedNodeMap entities = docType.getEntities(); + for (int i = 0; i < entities.getLength(); i++) { + DTDEntityDecl entity = (DTDEntityDecl) entities.item(i); + location = resolverManager.resolve(document.getDocumentURI(), entity.getPublicId(), + entity.getSystemId()); + if (location != null) { + try { + DOMRange systemIdRange = entity.getSystemIdNode(); + if (systemIdRange != null) { + links.add(createDocumentLink(systemIdRange, location, true)); + } + } catch (BadLocationException e) { + // Do nothing + } + } + } } // Document link for xml-model/href List xmlModels = document.getXMLModels(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java index 2399073a35..2d3eadfcd6 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java @@ -17,12 +17,15 @@ import java.util.HashMap; import java.util.Map; +import org.apache.xerces.impl.XMLEntityManager; +import org.apache.xerces.util.URI.MalformedURIException; import org.apache.xerces.xni.XMLLocator; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMDocumentType; import org.eclipse.lemminx.dom.DOMElement; import org.eclipse.lemminx.dom.DOMRange; +import org.eclipse.lemminx.dom.DTDEntityDecl; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ElementDeclUnterminatedCodeAction; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.EntityNotDeclaredCodeAction; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.FixMissingSpaceCodeAction; @@ -35,6 +38,7 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ResourceOperationKind; +import org.w3c.dom.NamedNodeMap; /** * DTD error code. @@ -212,7 +216,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[] } case dtd_not_found: { - // Check if DTD location comes from a xml-model/@href + // Check if DTD location is declared with xml-model/@href String hrefLocation = (String) arguments[1]; DOMRange locationRange = XMLModelUtils.getHrefNode(document, hrefLocation); if (locationRange != null) { @@ -220,11 +224,25 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[] } try { DOMDocumentType docType = document.getDoctype(); - return new Range(document.positionAt(docType.getSystemIdNode().getStart()), - document.positionAt(docType.getSystemIdNode().getEnd())); + if (docType != null) { + String documentURI = document.getDocumentURI(); + String dtdLocation = getResolvedLocation(documentURI, docType.getSystemIdWithoutQuotes()); + if (hrefLocation.equals(dtdLocation)) { + return new Range(document.positionAt(docType.getSystemIdNode().getStart()), + document.positionAt(docType.getSystemIdNode().getEnd())); + } + NamedNodeMap entities = docType.getEntities(); + for (int i = 0; i < entities.getLength(); i++) { + DTDEntityDecl entity = (DTDEntityDecl) entities.item(i); + String entityLocation = getResolvedLocation(documentURI, entity.getSystemId()); + if (hrefLocation.equals(entityLocation)) { + return XMLPositionUtility.createRange(entity.getSystemIdNode()); + } + } + } } catch (BadLocationException e) { } - return null; + return XMLPositionUtility.selectRootStartTag(document); } default: try { @@ -235,6 +253,22 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[] return null; } + /** + * Returns the expanded system location + * + * @return the expanded system location + */ + private static String getResolvedLocation(String documentURI, String location) { + if (location == null) { + return null; + } + try { + return XMLEntityManager.expandSystemId(location, documentURI, false); + } catch (MalformedURIException e) { + return location; + } + } + public static void registerCodeActionParticipants(Map codeActions, SharedSettings sharedSettings) { codeActions.put(ElementDeclUnterminated.getCode(), new ElementDeclUnterminatedCodeAction()); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPSAXParser.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPSAXParser.java index 9515329646..e2e3ce8562 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPSAXParser.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPSAXParser.java @@ -104,16 +104,16 @@ public void doctypeDecl(String rootElement, String publicId, String systemId, Au String eid = grammarDesc.getExpandedSystemId(); try { - XMLInputSource input = entityManager.resolveEntity(grammarDesc); - String resolvedSystemId = input.getSystemId(); - if (resolvedSystemId != null && resolvedSystemId.startsWith(FilesUtils.FILE_SCHEME)) { - // The resolved DTD is a file, check if the file exists. - if (!FilesUtils.toFile(resolvedSystemId).exists()) { - // The declared DTD file doesn't exist - // - throw new FileNotFoundException(resolvedSystemId); - } - } +// XMLInputSource input = entityManager.resolveEntity(grammarDesc); +// String resolvedSystemId = input.getSystemId(); +// if (resolvedSystemId != null && resolvedSystemId.startsWith(FilesUtils.FILE_SCHEME)) { +// // The resolved DTD is a file, check if the file exists. +// if (!FilesUtils.toFile(resolvedSystemId).exists()) { +// // The declared DTD file doesn't exist +// // +// throw new FileNotFoundException(resolvedSystemId); +// } +// } if (grammarPool != null) { // FIX [BUG 2] @@ -141,7 +141,7 @@ public void doctypeDecl(String rootElement, String publicId, String systemId, Au ValidationManager fValidationManager = (ValidationManager) fConfiguration .getProperty(VALIDATION_MANAGER); if (fValidationManager != null) { - fValidationManager.setCachedDTD(true); + //fValidationManager.setCachedDTD(true); } // As we don't throw an error when DTD content is downloaded, we need to remove @@ -198,10 +198,10 @@ private static void fillEntities(DTDGrammar grammar, XMLEntityManager entityMana @Override public void setValues(String name, String publicId, String systemId, String baseSystemId, String notation, String value, boolean isPE, boolean inExternal) { - if (inExternal) { + if (inExternal && value != null) { // Only entities declared in the cached DTD grammar must be added in the XML // entity manager. - entityManager.addInternalEntity(name, value); + //entityManager.addInternalEntity(name, value); } }; }; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLEntityManager.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLEntityManager.java new file mode 100644 index 0000000000..18c098143a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLEntityManager.java @@ -0,0 +1,124 @@ +package org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics; + +import java.io.IOException; +import java.io.StringReader; + +import org.apache.xerces.impl.XMLEntityManager; +import org.apache.xerces.impl.XMLErrorReporter; +import org.apache.xerces.impl.xs.XSDDescription; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XMLResourceIdentifier; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.parser.XMLInputSource; +import org.eclipse.lemminx.extensions.contentmodel.participants.DTDErrorCode; +import org.eclipse.lemminx.extensions.xerces.xmlmodel.msg.XMLModelMessageFormatter; + +class LSPXMLEntityManager extends XMLEntityManager { + + private final XMLErrorReporter errorReporter; + + private final LSPXMLGrammarPool grammarPool; + private boolean hasProblemsWithReferencedDTD; + + public LSPXMLEntityManager(XMLErrorReporter errorReporter, LSPXMLGrammarPool grammarPool) { + this.errorReporter = errorReporter; + this.grammarPool = grammarPool; + this.hasProblemsWithReferencedDTD = false; + } + + @Override + public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws IOException, XNIException { + if(resourceIdentifier instanceof XSDDescription) { + return super.resolveEntity(resourceIdentifier); + } + try { + return super.resolveEntity(resourceIdentifier); + } catch (Exception e) { + reportError(resourceIdentifier.getLiteralSystemId(), e); + XMLInputSource in = new XMLInputSource(resourceIdentifier); + in.setCharacterStream(new StringReader("")); + return in; + } + } + + @Override + public String setupCurrentEntity(String name, XMLInputSource xmlInputSource, boolean literal, boolean isExternal) + throws IOException, XNIException { + try { + return super.setupCurrentEntity(name, xmlInputSource, literal, isExternal); + } catch (Exception e) { + reportError(xmlInputSource.getSystemId(), e); + XMLInputSource in = new XMLInputSource(xmlInputSource.getPublicId(), xmlInputSource.getSystemId(), + xmlInputSource.getBaseSystemId(), new StringReader(""), null); + return super.setupCurrentEntity(name, in, literal, isExternal); + } + } + + private void reportError(String location, Exception e) { + hasProblemsWithReferencedDTD = true; + errorReporter.reportError(new XMLLocator() { + + @Override + public String getXMLVersion() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPublicId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getLiteralSystemId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getLineNumber() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getExpandedSystemId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getEncoding() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getColumnNumber() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getCharacterOffset() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getBaseSystemId() { + // TODO Auto-generated method stub + return null; + } + }, XMLModelMessageFormatter.XML_MODEL_DOMAIN, DTDErrorCode.dtd_not_found.getCode(), + new Object[] { null, location }, XMLErrorReporter.SEVERITY_ERROR, e); + } + + public void dispose() { + if (hasProblemsWithReferencedDTD) { + grammarPool.clear(); + } + // problems.forEach(uri -> grammarPool.removeGrammar(uri)); + } +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLGrammarPoolWrapper.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLGrammarPoolWrapper.java new file mode 100644 index 0000000000..4c6dee911d --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLGrammarPoolWrapper.java @@ -0,0 +1,77 @@ +package org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.xerces.xni.grammars.Grammar; +import org.apache.xerces.xni.grammars.XMLGrammarDescription; + +public class LSPXMLGrammarPoolWrapper extends LSPXMLGrammarPool { + + private final LSPXMLGrammarPool delegate; + + private final List cachedGrammars; + + public LSPXMLGrammarPoolWrapper(LSPXMLGrammarPool delegate) { + this.delegate = delegate; + this.cachedGrammars = new ArrayList<>(); + } + + public Grammar[] retrieveInitialGrammarSet(String grammarType) { + return delegate.retrieveInitialGrammarSet(grammarType); + } + + public void cacheGrammars(String grammarType, Grammar[] grammars) { + for (Grammar grammar : grammars) { + cachedGrammars.add(grammar); + } + delegate.cacheGrammars(grammarType, grammars); + } + + public int hashCode() { + return delegate.hashCode(); + } + + public Grammar retrieveGrammar(XMLGrammarDescription desc) { + return delegate.retrieveGrammar(desc); + } + + public Grammar removeGrammar(XMLGrammarDescription desc) { + return delegate.removeGrammar(desc); + } + + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + public void removeGrammar(String grammarURI) { + delegate.removeGrammar(grammarURI); + } + + public void lockPool() { + delegate.lockPool(); + } + + public void unlockPool() { + delegate.unlockPool(); + } + + public void clear() { + for (Grammar grammar : cachedGrammars) { + delegate.removeGrammar(grammar.getGrammarDescription()); + } + } + + public boolean equals(XMLGrammarDescription desc1, XMLGrammarDescription desc2) { + return delegate.equals(desc1, desc2); + } + + public int hashCode(XMLGrammarDescription desc) { + return delegate.hashCode(desc); + } + + public String toString() { + return delegate.toString(); + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java index e7499c9b46..2505900862 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPXMLParserConfiguration.java @@ -15,6 +15,7 @@ import java.util.logging.Logger; import org.apache.xerces.impl.Constants; +import org.apache.xerces.impl.XMLEntityManager; import org.apache.xerces.impl.dtd.XMLDTDValidator; import org.apache.xerces.util.SecurityManager; import org.apache.xerces.xni.XMLDocumentHandler; @@ -57,6 +58,7 @@ class LSPXMLParserConfiguration extends XMLModelAwareParserConfiguration { public LSPXMLParserConfiguration(XMLGrammarPool grammarPool, boolean disableDTDValidation, LSPErrorReporterForXML reporterForXML, LSPErrorReporterForXML reporterForGrammar, + XMLEntityManager entityManager, XMLValidationSettings validationSettings) { super(null, grammarPool, reporterForGrammar); this.disableDTDValidation = disableDTDValidation; @@ -68,6 +70,7 @@ public LSPXMLParserConfiguration(XMLGrammarPool grammarPool, boolean disableDTDV : false; super.setFeature("http://xml.org/sax/features/external-general-entities", resolveExternalEntities); super.setFeature("http://xml.org/sax/features/external-parameter-entities", resolveExternalEntities); + super.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", resolveExternalEntities); // Security manager SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit( @@ -76,6 +79,10 @@ public LSPXMLParserConfiguration(XMLGrammarPool grammarPool, boolean disableDTDV .setMaxOccurNodeLimit(getPropertyValue(MAX_OCCUR_LIMIT_PROPERTY_NAME, MAX_OCCUR_LIMIT_DEFAULT_VALUE)); super.setProperty(SECURITY_MANAGER, securityManager); fErrorReporter = reporterForXML; + + fEntityManager = entityManager; + fProperties.put(ENTITY_MANAGER, fEntityManager); + addCommonComponent(fEntityManager); } @Override @@ -88,7 +95,7 @@ protected void reset() throws XNIException { } } - private void disableDTDValidation() { + public void disableDTDValidation() { XMLDTDValidator validator = (XMLDTDValidator) super.getProperty(DTD_VALIDATOR); if (validator != null) { // Calling XMLDTDValidator#setFeature("http://xml.org/sax/features/validation", diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java index 9b678c0784..50b00e39b4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/XMLValidator.java @@ -24,9 +24,14 @@ import java.util.logging.Logger; import org.apache.xerces.impl.XMLEntityManager; +import org.apache.xerces.impl.XMLErrorReporter; +import org.apache.xerces.impl.XMLEntityManager.Entity; import org.apache.xerces.parsers.SAXParser; import org.apache.xerces.util.URI.MalformedURIException; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.parser.XMLEntityResolver; +import org.apache.xerces.xni.parser.XMLInputSource; import org.eclipse.lemminx.dom.DOMAttr; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMDocumentType; @@ -34,6 +39,7 @@ import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation; import org.eclipse.lemminx.dom.SchemaLocationHint; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.extensions.contentmodel.participants.DTDErrorCode; import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode; import org.eclipse.lemminx.extensions.contentmodel.settings.NamespacesEnabled; import org.eclipse.lemminx.extensions.contentmodel.settings.SchemaEnabled; @@ -41,8 +47,9 @@ import org.eclipse.lemminx.extensions.contentmodel.settings.XMLSchemaSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; import org.eclipse.lemminx.extensions.xerces.ReferencedGrammarDiagnosticsInfo; +import org.eclipse.lemminx.extensions.xerces.xmlmodel.msg.XMLModelMessageFormatter; import org.eclipse.lemminx.services.extensions.diagnostics.LSPContentHandler; -import org.eclipse.lemminx.uriresolver.CacheResourceDownloadingException; +import org.eclipse.lemminx.uriresolver.CacheResourceException; import org.eclipse.lemminx.uriresolver.IExternalGrammarLocationProvider; import org.eclipse.lemminx.utils.StringUtils; import org.eclipse.lemminx.utils.XMLPositionUtility; @@ -67,7 +74,9 @@ public class XMLValidator { public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityResolver, List diagnostics, XMLValidationSettings validationSettings, ContentModelManager contentModelManager, CancelChecker monitor) { - LSPXMLGrammarPool grammarPool = contentModelManager.getGrammarPool(); + + LSPXMLGrammarPool pool = contentModelManager.getGrammarPool(); + LSPXMLGrammarPoolWrapper grammarPool = pool != null ? new LSPXMLGrammarPoolWrapper(pool) : null; Map referencedGrammarDiagnosticsInfoCache = new HashMap<>(); final LSPErrorReporterForXML reporterForXML = new LSPErrorReporterForXML(document, diagnostics, contentModelManager, validationSettings != null ? validationSettings.isRelatedInformation() : false, @@ -79,9 +88,11 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR final LSPErrorReporterForXML reporterForGrammar = new LSPErrorReporterForXML(document, diagnostics, contentModelManager, validationSettings != null ? validationSettings.isRelatedInformation() : false, referencedGrammarDiagnosticsInfoCache); + LSPXMLEntityManager entityManager = new LSPXMLEntityManager(reporterForXML, grammarPool); try { + LSPXMLParserConfiguration configuration = new LSPXMLParserConfiguration(grammarPool, - isDisableOnlyDTDValidation(document), reporterForXML, reporterForGrammar, validationSettings); + isDisableOnlyDTDValidation(document), reporterForXML, reporterForGrammar, entityManager, validationSettings); if (entityResolver != null) { configuration.setProperty("http://apache.org/xml/properties/internal/entity-resolver", entityResolver); //$NON-NLS-1$ @@ -117,19 +128,26 @@ && isSchemaValidationEnabled(document, validationSettings) parser.setFeature("http://xml.org/sax/features/namespace-prefixes", namespacesValidationEnabled); //$NON-NLS-1$ parser.setFeature("http://xml.org/sax/features/namespaces", namespacesValidationEnabled); //$NON-NLS-1$ + //parser.setProperty("http://apache.org/xml/properties/internal/entity-manager", entityManager); //$NON-NLS-1$ + // Parse XML String content = document.getText(); String uri = document.getDocumentURI(); parseXML(content, uri, parser); } catch (IOException | SAXException | CancellationException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof CacheResourceException) { + //throw (CacheResourceException) cause; + } // ignore error - } catch (CacheResourceDownloadingException e) { + } catch (CacheResourceException e) { throw e; } catch (Exception e) { LOGGER.log(Level.SEVERE, "Unexpected XMLValidator error", e); } finally { reporterForXML.endReport(); reporterForGrammar.endReport(); + entityManager.dispose(); } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/DTDPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/DTDPlugin.java index a70c1a26f2..199650425c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/DTDPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/DTDPlugin.java @@ -17,6 +17,7 @@ import org.eclipse.lemminx.extensions.dtd.contentmodel.CMDTDContentModelProvider; import org.eclipse.lemminx.extensions.dtd.participants.DTDCodeLensParticipant; import org.eclipse.lemminx.extensions.dtd.participants.DTDDefinitionParticipant; +import org.eclipse.lemminx.extensions.dtd.participants.DTDDocumentLinkParticipant; import org.eclipse.lemminx.extensions.dtd.participants.DTDHighlightingParticipant; import org.eclipse.lemminx.extensions.dtd.participants.DTDReferenceParticipant; import org.eclipse.lemminx.extensions.dtd.participants.diagnostics.DTDDiagnosticsParticipant; @@ -27,6 +28,7 @@ import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; +import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4j.InitializeParams; /** @@ -39,6 +41,7 @@ public class DTDPlugin implements IXMLExtension { private final IHighlightingParticipant highlightingParticipant; private final IReferenceParticipant referenceParticipant; private final ICodeLensParticipant codeLensParticipant; + private DTDDocumentLinkParticipant documentLinkParticipant; public DTDPlugin() { definitionParticipant = new DTDDefinitionParticipant(); @@ -64,6 +67,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerReferenceParticipant(referenceParticipant); // register codelens participant registry.registerCodeLensParticipant(codeLensParticipant); + URIResolverExtensionManager resolverManager = registry.getComponent(URIResolverExtensionManager.class); + documentLinkParticipant = new DTDDocumentLinkParticipant(resolverManager); } @Override @@ -78,5 +83,6 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterReferenceParticipant(referenceParticipant); // unregister codelens participant registry.unregisterCodeLensParticipant(codeLensParticipant); + registry.unregisterDocumentLinkParticipant(documentLinkParticipant); } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/DTDDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/DTDDocumentLinkParticipant.java new file mode 100644 index 0000000000..94006b8791 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/participants/DTDDocumentLinkParticipant.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2018-2020 Angelo ZERR + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo Zerr - initial API and implementation + */ +package org.eclipse.lemminx.extensions.dtd.participants; + +import static org.eclipse.lemminx.utils.XMLPositionUtility.createDocumentLink; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMDocumentType; +import org.eclipse.lemminx.dom.DOMRange; +import org.eclipse.lemminx.dom.DTDEntityDecl; +import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation; +import org.eclipse.lemminx.dom.SchemaLocation; +import org.eclipse.lemminx.dom.SchemaLocationHint; +import org.eclipse.lemminx.dom.XMLModel; +import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; +import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; +import org.eclipse.lsp4j.DocumentLink; +import org.w3c.dom.NamedNodeMap; + +/** + * Document link for : + * + *
    + *
  • XML Schema xsi:noNamespaceSchemaLocation
  • + *
  • DTD SYSTEM (ex : + *
  • XML Schema xsi:schemaLocation
  • + *
+ * + * @author Angelo ZERR + * + */ +public class DTDDocumentLinkParticipant implements IDocumentLinkParticipant { + + private final URIResolverExtensionManager resolverManager; + + public DTDDocumentLinkParticipant(URIResolverExtensionManager resolverManager) { + this.resolverManager = resolverManager; + } + + @Override + public void findDocumentLinks(DOMDocument document, List links) { + // Document link for DTD + DOMDocumentType docType = document.getDoctype(); + if (docType != null) { + String location = resolverManager.resolve(document.getDocumentURI(), docType.getPublicIdWithoutQuotes(), + docType.getSystemIdWithoutQuotes()); + if (location != null) { + try { + DOMRange systemIdRange = docType.getSystemIdNode(); + if (systemIdRange != null) { + links.add(createDocumentLink(systemIdRange, location, true)); + } + } catch (BadLocationException e) { + // Do nothing + } + } + + NamedNodeMap entities = docType.getEntities(); + for (int i = 0; i < entities.getLength(); i++) { + DTDEntityDecl entity = (DTDEntityDecl) entities.item(i); + location = resolverManager.resolve(document.getDocumentURI(), entity.getPublicId(), + entity.getSystemId()); + if (location != null) { + try { + DOMRange systemIdRange = entity.getSystemIdNode(); + if (systemIdRange != null) { + links.add(createDocumentLink(systemIdRange, location, true)); + } + } catch (BadLocationException e) { + // Do nothing + } + } + } + } + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java index 0c9ca950c3..566c22a7ef 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java @@ -210,15 +210,17 @@ public static Range selectAttributeFromGivenNameAt(String attrName, int offset, } /** - * Returns the range of the attribute value of a specific child node, if it exists + * Returns the range of the attribute value of a specific child node, if it + * exists * * @param childNodeName the tag name of the child node/tag - * @param attrName the attribute name - * @param offset text offset from beginning of document - * @param document the DOM document. + * @param attrName the attribute name + * @param offset text offset from beginning of document + * @param document the DOM document. * @return the child node attribute value range and null otherwise. */ - public static Range selectChildNodeAttributeValueFromGivenNameAt(String childNodeName, String attrName, int offset, DOMDocument document) { + public static Range selectChildNodeAttributeValueFromGivenNameAt(String childNodeName, String attrName, int offset, + DOMDocument document) { List childNodes = document.findNodeAt(offset).getChildren(); if (childNodes.size() == 0) { return null; @@ -553,7 +555,7 @@ public static EntityReferenceRange selectEntityReference(int offset, DOMDocument public static EntityReferenceRange selectEntityReference(int offset, DOMDocument document, boolean endsWithSemicolon) { String text = document.getText(); - // Search '&' character on the left of the offset + // Search '&' or '%' character on the left of the offset int entityReferenceStart = getEntityReferenceStartOffset(text, offset); if (entityReferenceStart == -1) { return null; @@ -587,7 +589,8 @@ public static int getEntityReferenceStartOffset(String text, int offset) { // case where offset is on the first character return -1; } - if (text.charAt(offset) == '&') { + char c = text.charAt(offset); + if (c == '&' || c == '%') { // case with &|abcd return offset; } @@ -600,7 +603,8 @@ public static int getEntityReferenceStartOffset(String text, int offset) { return -1; } // check if the left character is '&' - if (text.charAt(startEntityOffset - 1) != '&') { + c = text.charAt(startEntityOffset - 1); + if (c != '&' && c != '%') { return -1; } return startEntityOffset - 1;