diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPErrorReporterForXML.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPErrorReporterForXML.java index 490f179e5..259fb7e35 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPErrorReporterForXML.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPErrorReporterForXML.java @@ -74,4 +74,11 @@ protected Range toLSPRange(XMLLocator location, String key, Object[] arguments, } return null; } + + @Override + protected boolean isIgnoreFatalError(String key) { + // Don't stop the validation when there are + // * EntityNotDeclared error + return DTDErrorCode.EntityNotDeclared.name().equals(key); + } } 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 new file mode 100644 index 000000000..2d6ef7d0b --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/diagnostics/LSPSAXParser.java @@ -0,0 +1,200 @@ +/******************************************************************************* +* Copyright (c) 2020 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics; + +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.text.MessageFormat; + +import org.apache.xerces.impl.Constants; +import org.apache.xerces.impl.XMLEntityManager; +import org.apache.xerces.impl.dtd.DTDGrammar; +import org.apache.xerces.impl.dtd.XMLDTDDescription; +import org.apache.xerces.impl.dtd.XMLEntityDecl; +import org.apache.xerces.impl.validation.ValidationManager; +import org.apache.xerces.parsers.SAXParser; +import org.apache.xerces.xni.Augmentations; +import org.apache.xerces.xni.NamespaceContext; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.grammars.XMLGrammarPool; +import org.apache.xerces.xni.parser.XMLParserConfiguration; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMDocumentType; +import org.eclipse.lemminx.extensions.contentmodel.participants.DTDErrorCode; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Range; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +/** + * Extension of Xerces SAX Parser to fix some Xerces bugs: + * + * + * + * @author Angelo ZERR + * + */ +public class LSPSAXParser extends SAXParser { + + private static final String DTD_NOT_FOUND = "Cannot find DTD ''{0}''.\nCreate the DTD file or configure an XML catalog for this DTD."; + + protected static final String VALIDATION_MANAGER = Constants.XERCES_PROPERTY_PREFIX + + Constants.VALIDATION_MANAGER_PROPERTY; + + protected static final String ENTITY_MANAGER = Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_MANAGER_PROPERTY; + + private final DOMDocument document; + + private final LSPErrorReporterForXML reporter; + + private final XMLGrammarPool grammarPool; + + public LSPSAXParser(DOMDocument document, LSPErrorReporterForXML reporter, XMLParserConfiguration config, + XMLGrammarPool grammarPool) { + super(config); + this.document = document; + this.reporter = reporter; + this.grammarPool = grammarPool; + init(reporter); + } + + private void init(LSPErrorReporterForXML reporter) { + try { + // Add LSP error reporter to fill LSP diagnostics from Xerces errors + super.setProperty("http://apache.org/xml/properties/internal/error-reporter", reporter); + super.setFeature("http://apache.org/xml/features/continue-after-fatal-error", false); //$NON-NLS-1$ + super.setFeature("http://xml.org/sax/features/namespace-prefixes", true); //$NON-NLS-1$ + super.setFeature("http://xml.org/sax/features/namespaces", true); //$NON-NLS-1$ + super.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true); + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + // Should never occur. + } + } + + private XMLLocator locator; + + @Override + public void startDocument(XMLLocator locator, String encoding, NamespaceContext namespaceContext, + Augmentations augs) throws XNIException { + this.locator = locator; + super.startDocument(locator, encoding, namespaceContext, augs); + } + + @Override + public void doctypeDecl(String rootElement, String publicId, String systemId, Augmentations augs) + throws XNIException { + if (systemId != null) { + // There a declared DTD in the DOCTYPE + // + String eid = null; + try { + eid = XMLEntityManager.expandSystemId(systemId, locator.getExpandedSystemId(), false); + } catch (java.io.IOException e) { + } + if (!isDTDExists(eid)) { + // The declared DTD doesn't exist + // + try { + // Report the error + DOMDocumentType docType = document.getDoctype(); + Range range = new Range(document.positionAt(docType.getSystemIdNode().getStart()), + document.positionAt(docType.getSystemIdNode().getEnd())); + reporter.addDiagnostic(range, MessageFormat.format(DTD_NOT_FOUND, eid), DiagnosticSeverity.Error, + DTDErrorCode.dtd_not_found.getCode()); + } catch (BadLocationException e) { + // Do nothing + } + + // FIX [BUG 1] + // To avoid breaking the validation (ex : syntax validation) we mark + // the cache DTD as true to avoid having an IOException error which breaks the + // validation. + // boolean readExternalSubset must be false in + // Xerces + // https://github.com/apache/xerces2-j/blob/e5a239b96fd2cff6566a29e7a4a3a4a2bbf9b0d4/src/org/apache/xerces/impl/XMLDocumentScannerImpl.java#L950 + ValidationManager fValidationManager = (ValidationManager) fConfiguration + .getProperty(VALIDATION_MANAGER); + if (fValidationManager != null) { + fValidationManager.setCachedDTD(true); + } + } else { + if (grammarPool != null) { + // FIX [BUG 2] + // DTD exists, get the DTD grammar from the cache + XMLEntityManager entityManager = (XMLEntityManager) fConfiguration.getProperty(ENTITY_MANAGER); + XMLDTDDescription grammarDesc = new XMLDTDDescription(publicId, systemId, + locator.getExpandedSystemId(), eid, rootElement); + DTDGrammar grammar = (DTDGrammar) grammarPool.retrieveGrammar(grammarDesc); + if (grammar != null) { + // The DTD grammar is in cache, we need to fill XML entity manager with the + // entities declared in the cached DTD grammar + fillEntities(grammar, entityManager); + } + } + } + } + super.doctypeDecl(rootElement, publicId, systemId, augs); + } + + private static boolean isDTDExists(String expandedSystemId) { + if (expandedSystemId == null || expandedSystemId.isEmpty()) { + return true; + } + try { + URL location = new URL(expandedSystemId); + URLConnection connect = location.openConnection(); + if (!(connect instanceof HttpURLConnection)) { + InputStream stream = connect.getInputStream(); + stream.close(); + } + } catch (Exception e) { + return false; + } + return true; + } + + /** + * Fill entities from the given DTD grammar to the given entity manager. + * + * @param grammar the DTD grammar + * @param entityManager the entitymanager to update with entities of the DTD + * grammar. + */ + private static void fillEntities(DTDGrammar grammar, XMLEntityManager entityManager) { + int index = 0; + XMLEntityDecl entityDecl = new XMLEntityDecl() { + + @Override + public void setValues(String name, String publicId, String systemId, String baseSystemId, String notation, + String value, boolean isPE, boolean inExternal) { + if (inExternal) { + // Only entities declared in the cached DTD grammar must be added in the XML + // entity manager. + entityManager.addInternalEntity(name, value); + } + }; + }; + while (grammar.getEntityDecl(index, entityDecl)) { + index++; + } + } +} 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 523a30277..4bad45cb7 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 @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 Angelo ZERR + * 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 @@ -14,25 +14,18 @@ import java.io.IOException; import java.io.StringReader; -import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.concurrent.CancellationException; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.xerces.impl.XMLEntityManager; import org.apache.xerces.parsers.SAXParser; -import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.grammars.XMLGrammarPool; import org.apache.xerces.xni.parser.XMLEntityResolver; -import org.apache.xerces.xni.parser.XMLInputSource; -import org.apache.xerces.xni.parser.XMLParserConfiguration; -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.extensions.contentmodel.participants.DTDErrorCode; import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; import org.eclipse.lemminx.services.extensions.diagnostics.LSPContentHandler; @@ -57,8 +50,6 @@ public class XMLValidator { private static final Logger LOGGER = Logger.getLogger(XMLValidator.class.getName()); - 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, XMLGrammarPool grammarPool, CancelChecker monitor) { @@ -74,14 +65,9 @@ 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); - parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", false); //$NON-NLS-1$ - parser.setFeature("http://xml.org/sax/features/namespace-prefixes", true /* document.hasNamespaces() */); //$NON-NLS-1$ - parser.setFeature("http://xml.org/sax/features/namespaces", true /* document.hasNamespaces() */); //$NON-NLS-1$ + SAXParser parser = new LSPSAXParser(document, reporter, configuration, grammarPool); + // Add LSP content handler to stop XML parsing if monitor is canceled. parser.setContentHandler(new LSPContentHandler(monitor)); @@ -99,9 +85,7 @@ public static void doDiagnostics(DOMDocument document, XMLEntityResolver entityR } else { hasGrammar = false; // validation for Schema was disabled } - - parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", externalDTDValid); - parser.setFeature("http://xml.org/sax/features/validation", hasGrammar && externalDTDValid); //$NON-NLS-1$ + parser.setFeature("http://xml.org/sax/features/validation", hasGrammar); //$NON-NLS-1$ // Parse XML String content = document.getText(); @@ -148,81 +132,6 @@ private static boolean isDisableOnlyDTDValidation(DOMDocument document) { return !docType.getChildren().stream().anyMatch(node -> node.isDTDElementDecl() || node.isDTDAttListDecl()); } - /** - * Returns true if the given document has a valid DTD (or doesn't define a DTD) - * and false otherwise. - * - * @param document the DOM document - * @param reporter the reporter - * @param configuration the configuration - * @return true if the given document has a valid DTD (or doesn't define a DTD) - * and false otherwise. - */ - private static boolean checkExternalDTD(DOMDocument document, LSPErrorReporterForXML reporter, - XMLParserConfiguration configuration) { - if (!document.hasDTD()) { - return true; - } - DOMDocumentType docType = document.getDoctype(); - if (docType.getKindNode() == null) { - return true; - } - - // When XML is bound with a DTD path which doesn't exist, Xerces throws an - // IOException which breaks the validation of XML syntax instead of reporting it - // (like XML Schema). Here we parse only the - // DOCTYPE to catch this error. If there is an error - // the next validation with be disabled by using - // http://xml.org/sax/features/validation & - // http://apache.org/xml/features/nonvalidating/load-external-dtd (disable uses - // of DTD for validation) - - // Parse only the DOCTYPE of the DOM document - - int end = document.getDoctype().getEnd(); - String xml = document.getText().substring(0, end); - xml += ""; - try { - - // Customize the entity manager to collect the error when DTD doesn't exist. - XMLEntityManager entityManager = new XMLEntityManager() { - @Override - public String setupCurrentEntity(String name, XMLInputSource xmlInputSource, boolean literal, - boolean isExternal) throws IOException, XNIException { - // Catch the setupCurrentEntity method which throws an IOException when DTD is - // not found - try { - return super.setupCurrentEntity(name, xmlInputSource, literal, isExternal); - } catch (IOException e) { - // Report the DTD invalid error - try { - Range range = new Range(document.positionAt(docType.getSystemIdNode().getStart()), - document.positionAt(docType.getSystemIdNode().getEnd())); - reporter.addDiagnostic(range, - MessageFormat.format(DTD_NOT_FOUND, xmlInputSource.getSystemId()), - DiagnosticSeverity.Error, DTDErrorCode.dtd_not_found.getCode()); - } catch (BadLocationException e1) { - // Do nothing - } - throw e; - } - } - }; - entityManager.reset(configuration); - - SAXParser parser = new SAXParser(configuration); - parser.setProperty("http://apache.org/xml/properties/internal/entity-manager", entityManager); - parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true); - - parseXML(xml, document.getDocumentURI(), parser); - } catch (SAXException | CancellationException exception) { - // ignore error - } catch (IOException e) { - return false; - } - return true; - } - /** * Warn if XML document is not bound to a grammar according the settings * diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/diagnostics/AbstractLSPErrorReporter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/diagnostics/AbstractLSPErrorReporter.java index 1f4b00643..10c6c5d08 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/diagnostics/AbstractLSPErrorReporter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/diagnostics/AbstractLSPErrorReporter.java @@ -89,7 +89,7 @@ public String reportError(XMLLocator location, String domain, String key, Object return null; } - if (severity == SEVERITY_FATAL_ERROR && !fContinueAfterFatalError) { + if (severity == SEVERITY_FATAL_ERROR && !fContinueAfterFatalError && !isIgnoreFatalError(key)) { XMLParseException parseException = (exception != null) ? new XMLParseException(location, message, exception) : new XMLParseException(location, message); throw parseException; @@ -97,6 +97,10 @@ public String reportError(XMLLocator location, String domain, String key, Object return message; } + protected boolean isIgnoreFatalError(String key) { + return false; + } + public boolean addDiagnostic(Range adjustedRange, String message, DiagnosticSeverity severity, String key) { Diagnostic d = new Diagnostic(adjustedRange, message, severity, source, key); if (diagnostics.contains(d)) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDDiagnosticsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDDiagnosticsTest.java index af41d4cef..344f7355a 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDDiagnosticsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/DTDDiagnosticsTest.java @@ -25,7 +25,7 @@ * */ public class DTDDiagnosticsTest { - + @Test public void MSG_ELEMENT_NOT_DECLARED() throws Exception { String xml = " \r\n" + // @@ -39,21 +39,21 @@ public void MSG_ELEMENT_NOT_DECLARED() throws Exception { testDiagnosticsFor(xml, d(6, 2, 5, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED), d(5, 1, 8, DTDErrorCode.MSG_CONTENT_INVALID)); } - + @Test public void MSG_CONTENT_INVALID() throws Exception { String xml = "\r\n" + // "\r\n" + // + "\r\n" + // " \r\n" + // - " \r\n" + // - " \r\n" + // + " \r\n" + // + " \r\n" + // " \r\n" + // "]>\r\n" + // - "\r\n" + // - " Jani\r\n" + // - " Reminder\r\n" + // - " Don't forget me this weekend\r\n" + // + "\r\n" + // + " Jani\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // ""; XMLAssert.testDiagnosticsFor(xml, d(8, 1, 5, DTDErrorCode.MSG_CONTENT_INVALID)); } @@ -70,7 +70,7 @@ public void MSG_ATTRIBUTE_NOT_DECLARED() throws Exception { "]>\r\n" + // "\r\n" + // " \r\n" + // - " Jani\r\n" + // <- error + " Jani\r\n" + // <- error " Reminder\r\n" + // " Don't forget me this weekend\r\n" + // " "; @@ -96,13 +96,13 @@ public void MSG_FIXED_ATTVALUE_INVALID() throws Exception { public void MSG_ATTRIBUTE_VALUE_NOT_IN_LIST() throws Exception { String xml = "\r\n" + // "\r\n" + // - " \r\n" + // - " \r\n" + // - "]>\r\n" + // - "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // " toto\r\n" + // + ">toto\r\n" + // ""; XMLAssert.testDiagnosticsFor(xml, d(7, 15, 21, DTDErrorCode.MSG_ATTRIBUTE_VALUE_NOT_IN_LIST)); } @@ -125,11 +125,11 @@ public void MSG_CONTENT_INCOMPLETE() throws Exception { public void MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED() throws Exception { String xml = "\r\n" + // "\r\n" + // - " \r\n" + // - " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // "]>\r\n" + // - "\r\n" + // + "\r\n" + // " \r\n" + // <- error ("fruit" attribute is missing) ""; XMLAssert.testDiagnosticsFor(xml, d(7, 5, 8, DTDErrorCode.MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED)); @@ -139,22 +139,22 @@ public void MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED() throws Exception { public void MSG_ELEMENT_WITH_ID_REQUIRED() throws Exception { String xml = "\r\n" + // "\r\n" + // - " \r\n" + // - " \r\n" + // - " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // " \r\n" + // " \r\n" + // " \r\n" + // " ]>\r\n" + // "\r\n" + // - " \r\n" + // + " \r\n" + // " Bob\r\n" + // " \r\n" + // ""; XMLAssert.testDiagnosticsFor(xml, d(10, 1, 6, DTDErrorCode.MSG_ELEMENT_WITH_ID_REQUIRED)); } - + @Test public void IDInvalidWithNamespaces() throws Exception { String xml = "\r\n" + // @@ -170,13 +170,13 @@ public void IDInvalidWithNamespaces() throws Exception { public void IDREFInvalidWithNamespaces() throws Exception { String xml = "\r\n" + // "\r\n" + // - " \r\n" + // + " \r\n" + // + " \r\n" + // "]>\r\n" + // ""; // <- error on @Friend value XMLAssert.testDiagnosticsFor(xml, d(5, 15, 17, DTDErrorCode.IDREFInvalidWithNamespaces)); } - + @Test public void IDREFSInvalid() throws Exception { String xml = "\r\n" + // @@ -193,8 +193,7 @@ public void MSG_MARKUP_NOT_RECOGNIZED_IN_DTD() throws Exception { String xml = "\r\n" + // "\r\n" + // - " Bad Value " + - " \r\n" + // + " Bad Value " + " \r\n" + // "]>\r\n" + // ""; // <- error on @Likes value XMLAssert.testDiagnosticsFor(xml, d(2, 24, 3, 16, DTDErrorCode.MSG_MARKUP_NOT_RECOGNIZED_IN_DTD)); @@ -257,7 +256,8 @@ public void MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL() throws Exce " \r\n" + // "]>\r\n" + // ""; // <- error on @Likes value - XMLAssert.testDiagnosticsFor(xml, d(2, 16, 17, DTDErrorCode.MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL)); + XMLAssert.testDiagnosticsFor(xml, + d(2, 16, 17, DTDErrorCode.MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL)); } @Test @@ -292,13 +292,8 @@ public void NotationDeclUnterminated() throws Exception { @Test public void EntityNotDeclared() throws Exception { - String xml = "\r\n" + - "\r\n" + - "]>\r\n" + - "
\r\n" + - "  \r\n" + - "
"; + String xml = "\r\n" + "\r\n" + "]>\r\n" + "
\r\n" + "  \r\n" + "
"; XMLAssert.testDiagnosticsFor(xml, d(5, 1, 7, DTDErrorCode.EntityNotDeclared)); } @@ -319,7 +314,7 @@ public void PEReferenceWithinMarkup() throws Exception { " \r\n" + // <- error on "(%bar;)*" "]>\r\n" + // - ""; // + ""; // XMLAssert.testDiagnosticsFor(xml, d(2, 18, 28, DTDErrorCode.PEReferenceWithinMarkup)); } @@ -330,37 +325,24 @@ public void MSG_ELEMENT_ALREADY_DECLARED() throws Exception { " \r\n" + // " \r\n" + // <- error on 'ELEMENT' "]>\r\n" + // - ""; // + ""; // XMLAssert.testDiagnosticsFor(xml, d(3, 3, 10, DTDErrorCode.MSG_ELEMENT_ALREADY_DECLARED)); } @Test public void testDoctypeDiagnosticsRefresh() throws Exception { - //@formatter:off - String xml = "\n" + - "\n" + - " \n" + - "]>\n" + - "\n" + - " Smith\n" + - " 567896\n" + - ""; - //@formatter:on + // @formatter:off + String xml = "\n" + "\n" + + " \n" + "]>\n" + "\n" + " Smith\n" + + " 567896\n" + ""; + // @formatter:on XMLAssert.testDiagnosticsFor(xml, d(7, 3, 5, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED)); - //@formatter:off - xml = "\n" + - "\n" + - " \n" + - " \n" + - "]>\n" + - "\n" + - " Smith\n" + - " 567896\n" + - ""; - //@formatter:on + // @formatter:off + xml = "\n" + "\n" + + " \n" + " \n" + "]>\n" + "\n" + + " Smith\n" + " 567896\n" + ""; + // @formatter:on XMLAssert.testDiagnosticsFor(xml, new Diagnostic[0]); } @@ -368,33 +350,37 @@ public void testDoctypeDiagnosticsRefresh() throws Exception { @Test public void testDTDNotFoundWithSYSTEM() throws Exception { String xml = "\r\n" + // - "\r\n" + // <- error DTD not found - "\r\n" + // + "\r\n" + // <- [1] error DTD not found + "\r\n" + // [2] " \r\n" + // " \r\n" + // - " 10000.00\r\n" + // <- error, it misses 's' for + " 10000.00\r\n" + // <- [3] and [4] error, it misses 's' for " 36\r\n" + // " \r\n" + // ""; - XMLAssert.testDiagnosticsFor(xml, d(1, 29, 1, 46, DTDErrorCode.dtd_not_found), - d(5, 23, 5, 30, XMLSyntaxErrorCode.ETagRequired)); + XMLAssert.testDiagnosticsFor(xml, d(1, 29, 1, 46, DTDErrorCode.dtd_not_found),// [1] + d(2, 1, 12, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED), // [2] + d(5, 4, 12, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED), // [3] + d(5, 23, 5, 30, XMLSyntaxErrorCode.ETagRequired)); // [4] } @Test public void testDTDNotFoundWithPUBLIC() throws Exception { String xml = "\r\n" + // - "\r\n" + // <- error DTD not found - "\r\n" + // + "\r\n" + // <- [1] error DTD not found + "\r\n" + // [2] " \r\n" + // " \r\n" + // - " 10000.00\r\n" + // <- error, it misses 's' for + " 10000.00\r\n" + // <- [3] and [4] error, it misses 's' for " 36\r\n" + // " \r\n" + // ""; - XMLAssert.testDiagnosticsFor(xml, d(1, 33, 1, 50, DTDErrorCode.dtd_not_found), - d(5, 23, 5, 30, XMLSyntaxErrorCode.ETagRequired)); + XMLAssert.testDiagnosticsFor(xml, d(1, 33, 1, 50, DTDErrorCode.dtd_not_found), // [1] + d(2, 1, 12, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED), // [2] + d(5, 4, 12, DTDErrorCode.MSG_ELEMENT_NOT_DECLARED), // [3] + d(5, 23, 5, 30, XMLSyntaxErrorCode.ETagRequired)); // [4] } - + private static void testDiagnosticsFor(String xml, Diagnostic... expected) { XMLAssert.testDiagnosticsFor(xml, "src/test/resources/catalogs/catalog.xml", expected); } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLValidationPoolCacheTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLValidationPoolCacheTest.java index 9edf5bebb..76e72f12d 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLValidationPoolCacheTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLValidationPoolCacheTest.java @@ -205,7 +205,7 @@ public void includedSchemaLocation() throws IOException { } @Test - public void dtd() throws IOException { + public void dtdELEMENT() throws IOException { XMLLanguageService xmlLanguageService = new XMLLanguageService(); String dtdPath = tempDirUri.getPath() + "/note.dtd"; @@ -242,6 +242,62 @@ public void dtd() throws IOException { testDiagnosticsFor(xmlLanguageService, xml, d); } + @Test + public void dtdENTITY() throws IOException { + // See https://github.com/redhat-developer/vscode-xml/issues/234 + + XMLLanguageService xmlLanguageService = new XMLLanguageService(); + + String dtdPath = tempDirUri.getPath() + "/base.dtd"; + String dtd = "\r\n" + // + "\r\n" + // + ""; // + createFile(dtdPath, dtd); + + // One error + String xml = "\r\n" + // + "\r\n" + // + "]>\r\n" + // + "\r\n" + // + "&local;\r\n" + // + "&external;\r\n" + // + "&undeclared;\r\n" + // + ""; + + // First validation (DTD grammar is not in the cache) + Diagnostic d1 = d(7, 0, 12, DTDErrorCode.EntityNotDeclared); + testDiagnosticsFor(xmlLanguageService, xml, d1); + + // Second validation (DTD grammar is in the cache) + testDiagnosticsFor(xmlLanguageService, xml, d1); + + // Update DTD + dtd = "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; // + updateFile(dtdPath, dtd); + + // First validation with updated DTD (DTD grammar is in the cache) + testDiagnosticsFor(xmlLanguageService, xml); + // Second validation (DTD grammar is in the cache) + testDiagnosticsFor(xmlLanguageService, xml); + + // Remove entity local from the XML + xml = "\r\n" + // + "\r\n" + // + "]>\r\n" + // + "\r\n" + // + "&local;\r\n" + // + "&external;\r\n" + // + "&undeclared;\r\n" + // + ""; + Diagnostic d2 = d(4, 0, 7, DTDErrorCode.EntityNotDeclared); + testDiagnosticsFor(xmlLanguageService, xml, d2); + } + private static void testDiagnosticsFor(XMLLanguageService xmlLanguageService, String xml, Diagnostic... expected) { String catalogPath = "src/test/resources/catalogs/catalog.xml"; ContentModelSettings settings = new ContentModelSettings();