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 3d1a91af79..dffa147600 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
@@ -21,6 +21,7 @@
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
+import org.eclipse.lemminx.dom.DOMRange;
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.dtd_not_foundCodeAction;
@@ -74,7 +75,7 @@ public enum DTDErrorCode implements IXMLErrorCode {
QuoteRequiredInPublicID, //
QuoteRequiredInSystemID, //
SpaceRequiredAfterSYSTEM, //
- dtd_not_found("dtd-not-found");
+ dtd_not_found;
private final String code;
@@ -194,6 +195,15 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
case MSG_ELEMENT_TYPE_REQUIRED_IN_ELEMENTDECL: {
return XMLPositionUtility.selectDTDDeclTagNameAt(offset, document);
}
+ case dtd_not_found: {
+ // Check if DTD location comes from a xml-model/@href
+ String hrefLocation = (String) arguments[1];
+ DOMRange locationRange = XMLModelUtils.getHrefNode(document, hrefLocation);
+ if (locationRange != null) {
+ return XMLPositionUtility.createRange(locationRange);
+ }
+ return null;
+ }
default:
try {
return new Range(new Position(0, 0), document.positionAt(document.getEnd()));
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLModelUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLModelUtils.java
new file mode 100644
index 0000000000..4885c656a3
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLModelUtils.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+* 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;
+
+import java.util.List;
+
+import org.apache.xerces.impl.XMLEntityManager;
+import org.apache.xerces.util.URI.MalformedURIException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.dom.DOMRange;
+import org.eclipse.lemminx.dom.XMLModel;
+
+/**
+ * XML model utilities.
+ *
+ */
+public class XMLModelUtils {
+
+ /**
+ * Returns the DOM range of the href of the xml-model processing instruction
+ * which matches the given hrefLocation.
+ *
+ * @param document the DOM document.
+ * @param hrefLocation the href location.
+ * @return the DOM range of the href of the xml-model processing instruction
+ * which matches the given hrefLocation.
+ */
+ public static DOMRange getHrefNode(DOMDocument document, String hrefLocation) {
+ if (hrefLocation == null) {
+ return null;
+ }
+ // Check if location comes from a xml-model/@href
+ List xmlModels = document.getXMLModels();
+ if (!xmlModels.isEmpty()) {
+ String documentURI = document.getDocumentURI();
+ for (XMLModel xmlModel : xmlModels) {
+ String href = xmlModel.getHref();
+ String xmlModelLocation = getResolvedLocation(documentURI, href);
+ if (hrefLocation.equals(xmlModelLocation)) {
+ return xmlModel.getHrefNode();
+ }
+ }
+ }
+ 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;
+ }
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSchemaErrorCode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSchemaErrorCode.java
index 0132abc7b1..b32b61d8f5 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSchemaErrorCode.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/XMLSchemaErrorCode.java
@@ -22,7 +22,7 @@
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
-import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.dom.NoNamespaceSchemaLocation;
import org.eclipse.lemminx.dom.SchemaLocation;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.TargetNamespace_1CodeAction;
@@ -174,25 +174,30 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
}
case SchemaLocation:
case schema_reference_4: {
- DOMNode attrValueNode = null;
+ DOMRange locationRange = null;
if (code.equals(SchemaLocation)) {
SchemaLocation schemaLocation = document.getSchemaLocation();
- attrValueNode = schemaLocation.getAttr().getNodeAttrValue();
+ locationRange = schemaLocation.getAttr().getNodeAttrValue();
} else {
- NoNamespaceSchemaLocation noNamespaceSchemaLocation = document.getNoNamespaceSchemaLocation();
- if (noNamespaceSchemaLocation != null) {
- attrValueNode = noNamespaceSchemaLocation.getAttr().getNodeAttrValue();
- } else {
- SchemaLocation schemaLocation = document.getSchemaLocation();
- if (schemaLocation != null) {
- attrValueNode = schemaLocation.getAttr().getNodeAttrValue();
+ String hrefLocation = arguments.length == 1 ? (String) arguments[0] : null;
+ // Check if location comes from a xml-model/@href
+ locationRange = XMLModelUtils.getHrefNode(document, hrefLocation);
+ if (locationRange == null) {
+ NoNamespaceSchemaLocation noNamespaceSchemaLocation = document.getNoNamespaceSchemaLocation();
+ if (noNamespaceSchemaLocation != null) {
+ locationRange = noNamespaceSchemaLocation.getAttr().getNodeAttrValue();
+ } else {
+ SchemaLocation schemaLocation = document.getSchemaLocation();
+ if (schemaLocation != null) {
+ locationRange = schemaLocation.getAttr().getNodeAttrValue();
+ }
}
}
}
- if (attrValueNode != null) {
- int startOffset = attrValueNode.getStart();
- int endOffset = attrValueNode.getEnd();
+ if (locationRange != null) {
+ int startOffset = locationRange.getStart();
+ int endOffset = locationRange.getEnd();
try {
Position startPosition = document.positionAt(startOffset);
Position endPosition = document.positionAt(endOffset);
@@ -272,9 +277,10 @@ public static void registerCodeActionParticipants(Map
+ * This class is a copy/paste of Xerces
+ * org.apache.xerces.impl.msg.XMLMessageFormatter class.
+ *
+ *
+ * @author Eric Ye, IBM
+ * @author Angelo ZERR
+ */
+public class XMLModelMessageFormatter implements MessageFormatter {
+ /**
+ * The domain of messages concerning the XML 1.0 specification.
+ */
+ public static final String XML_MODEL_DOMAIN = "https://www.w3.org/TR/xml-model/";
+
+ // private objects to cache the locale and resource bundle
+ private Locale fLocale = null;
+ private ResourceBundle fResourceBundle = null;
+
+ //
+ // MessageFormatter methods
+ //
+
+ /**
+ * Formats a message with the specified arguments using the given locale
+ * information.
+ *
+ * @param locale The locale of the message.
+ * @param key The message key.
+ * @param arguments The message replacement text arguments. The order of the
+ * arguments must match that of the placeholders in the actual
+ * message.
+ *
+ * @return Returns the formatted message.
+ *
+ * @throws MissingResourceException Thrown if the message with the specified key
+ * cannot be found.
+ */
+ public String formatMessage(Locale locale, String key, Object[] arguments) throws MissingResourceException {
+
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ if (locale != fLocale) {
+ fResourceBundle = ResourceBundle.getBundle("org.eclipse.lemminx.extensions.xerces.xmlmodel.msg.XMLMessages",
+ locale);
+ // memorize the most-recent locale
+ fLocale = locale;
+ }
+
+ // format message
+ String msg;
+ try {
+ msg = fResourceBundle.getString(key);
+ if (arguments != null) {
+ try {
+ msg = java.text.MessageFormat.format(msg, arguments);
+ } catch (Exception e) {
+ msg = fResourceBundle.getString("FormatFailed");
+ msg += " " + fResourceBundle.getString(key);
+ }
+ }
+ }
+
+ // error
+ catch (MissingResourceException e) {
+ msg = fResourceBundle.getString("BadMessageKey");
+ throw new MissingResourceException(key, msg, key);
+ }
+
+ // no message
+ if (msg == null) {
+ msg = key;
+ if (arguments.length > 0) {
+ StringBuilder str = new StringBuilder(msg);
+ str.append('?');
+ for (int i = 0; i < arguments.length; i++) {
+ if (i > 0) {
+ str.append('&');
+ }
+ str.append(String.valueOf(arguments[i]));
+ }
+ }
+ }
+
+ return msg;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/extensions/xerces/xmlmodel/msg/XMLMessages.properties b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/extensions/xerces/xmlmodel/msg/XMLMessages.properties
new file mode 100644
index 0000000000..de377aff50
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/resources/org/eclipse/lemminx/extensions/xerces/xmlmodel/msg/XMLMessages.properties
@@ -0,0 +1,2 @@
+ # added
+ dtd_not_found = Cannot find DTD ''{1}''.
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLModelDiagnosticsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLModelDiagnosticsTest.java
index 5c9c6a7815..0e8e134110 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLModelDiagnosticsTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLModelDiagnosticsTest.java
@@ -25,6 +25,14 @@
*/
public class XMLModelDiagnosticsTest {
+ @Test
+ public void xmlModelWithBadDTD() throws Exception {
+ String xml = " \r\n" + //
+ "\r\n" + //
+ " ";
+ testDiagnosticsFor(xml, d(1, 17, 26, DTDErrorCode.dtd_not_found));
+ }
+
@Test
public void xmlModelWithDTD() throws Exception {
String xml = " \r\n" + //
@@ -36,6 +44,15 @@ public void xmlModelWithDTD() throws Exception {
d(2, 1, 8, DTDErrorCode.MSG_CONTENT_INVALID));
}
+ @Test
+ public void xmlModelWithBadXSD() throws Exception {
+ String xml = " \r\n" + //
+ "\r\n" + //
+ " ";
+ testDiagnosticsFor(xml, d(1, 17, 26, XMLSchemaErrorCode.schema_reference_4), //
+ d(2, 1, 5, XMLSchemaErrorCode.cvc_elt_1_a));
+ }
+
@Test
public void xmlModelWithXSD() throws Exception {
String xml = "\r\n" + //