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 3d1a91af7..dffa14760 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 000000000..4885c656a --- /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 0132abc7b..a0ba86f60 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 @@ -18,11 +18,10 @@ import java.util.Map; import org.apache.xerces.xni.XMLLocator; -import org.eclipse.lemminx.commons.BadLocationException; 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; @@ -41,7 +40,6 @@ import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.utils.DOMUtils; import org.eclipse.lemminx.utils.XMLPositionUtility; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ResourceOperationKind; @@ -174,34 +172,27 @@ 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(); - try { - Position startPosition = document.positionAt(startOffset); - Position endPosition = document.positionAt(endOffset); - return new Range(startPosition, endPosition); - } catch (BadLocationException e) { - return null; - } - } - return null; + return locationRange != null ? XMLPositionUtility.createRange(locationRange) : null; } case cvc_attribute_3: case cvc_complex_type_3_1: @@ -272,9 +263,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 000000000..de377aff5 --- /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 5c9c6a781..0e8e13411 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" + //