diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java index 1d0a1c0031..51105493a9 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMDocument.java @@ -335,7 +335,7 @@ private static SchemaLocation createSchemaLocation(DOMNode root, String schemaIn if (attr == null) { return null; } - return new SchemaLocation(root.getOwnerDocument().getDocumentURI(), attr); + return new SchemaLocation(attr); } private static NoNamespaceSchemaLocation createNoNamespaceSchemaLocation(DOMNode root, diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocation.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocation.java index 98939a8546..d1f7d4231a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocation.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocation.java @@ -12,40 +12,72 @@ */ package org.eclipse.lemminx.dom; +import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * + * * The declared "xsi:schemaLocation" */ public class SchemaLocation { - private final Map schemaLocationValuePairs; + private final Map schemaLocationValuePairs; private final DOMAttr attr; - public SchemaLocation(String base, DOMAttr attr) { + // The text to match is of the form: + // http://example.org/schema/root root.xsd http://example.org/schema/bison bison.xsd http://example.org/schema/potato potato.xsd + private static final Pattern SCHEMA_LOCATION_PAIR_PATTERN = Pattern.compile("\\s*([^\\s]+)\\s+([^\\s]+)\\s*"); + + public SchemaLocation(DOMAttr attr) { this.attr = attr; this.schemaLocationValuePairs = new HashMap<>(); String value = attr.getValue(); - StringTokenizer st = new StringTokenizer(value); - do { - String namespaceURI = st.hasMoreTokens() ? st.nextToken() : null; - String locationHint = st.hasMoreTokens() ? st.nextToken() : null; + Matcher locPairMatcher = SCHEMA_LOCATION_PAIR_PATTERN.matcher(value); + while (locPairMatcher.find()) { + String namespaceURI = locPairMatcher.group(1); + String locationHint = locPairMatcher.group(2); if (namespaceURI == null || locationHint == null) break; - schemaLocationValuePairs.put(namespaceURI, locationHint); - } while (true); + DOMNode valNode = attr.getNodeAttrValue(); + // matcher matches 1 char ahead, but valNode's start is at beginning of " + int start = valNode.getStart() + locPairMatcher.start(2); + // matcher matches end correctly, but range needs to be one past end + // to highlight the end, and one to make up for " + int end = valNode.getStart() + locPairMatcher.end(2) + 2; + schemaLocationValuePairs.put(namespaceURI, + new SchemaLocationHint(start, end, locationHint, this)); + } } + /** + * Get the location hint, as a string, that is associated with the given namespace URI. + * + * If the given namespace URI was not referred to in this xsi:schemaLocation, then null is returned. + * + * @param namespaceURI The namespace URI to fid the location hint for + * @return The associated location hint, as a string, or null + * if the namespace was not refered to in xsi:schemaLocation + */ public String getLocationHint(String namespaceURI) { - return schemaLocationValuePairs.get(namespaceURI); + SchemaLocationHint locHint = schemaLocationValuePairs.get(namespaceURI); + return locHint == null ? null : locHint.getHint(); } public DOMAttr getAttr() { return attr; } + /** + * Gets all the location hints given in this xsi:schemaLocation attribute + * + * @return A Collection of all the location hints as SchemaLocationHint + */ + public Collection getSchemaLocationHints() { + return schemaLocationValuePairs.values(); + } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocationHint.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocationHint.java new file mode 100644 index 0000000000..d94482a7df --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/SchemaLocationHint.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2018 Red Hat Inc. + * 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: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.dom; + +/** + * Represents one of the location hints provided in an xsi:schemaLocation attribute. + */ +public class SchemaLocationHint implements DOMRange { + + private final int start, end; + + private final String hint; + + private final SchemaLocation parent; + + /** + * Create a new SchemaLocationHint object that represents one of the location hints + * given in the parent SchemaLocation. + * + * @param start The offset from the beginning of the document where this location hint starts + * @param end The offset from the beginning of the document where this location hint ends + * @param hint The hint to the location of a schema (A URI that points to a schema) + * @param parent The SchemaLocation in which this hint was given + */ + public SchemaLocationHint(int start, int end, String hint, SchemaLocation parent) { + this.start = start; + this.end = end; + this.hint = hint; + this.parent = parent; + } + + /** + * Get the location hint that this SchemaLocationHint represents + * + * @return The location hint, a URI to a schema, as a String + */ + public String getHint() { + return this.hint; + } + + /** + * Returns the offset from the beginning of the document where this location hint starts + * + * @return The offset from the beginning of the document where this location hint starts + */ + @Override + public int getStart() { + return this.start; + } + + /** + * Returns the offset from the beginning of the document where this location hint ends + * + * @return The offset from the beginning of the document where this location hint ends + */ + @Override + public int getEnd() { + return this.end; + } + + @Override + public DOMDocument getOwnerDocument() { + return parent.getAttr().getOwnerDocument(); + } + +} \ No newline at end of file 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 ce3392d9d2..5a4a924eb4 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 @@ -14,6 +14,7 @@ import static org.eclipse.lemminx.utils.XMLPositionUtility.createDocumentLink; +import java.util.Collection; import java.util.List; import org.apache.xerces.impl.XMLEntityManager; @@ -23,18 +24,21 @@ import org.eclipse.lemminx.dom.DOMDocumentType; import org.eclipse.lemminx.dom.DOMRange; 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.lsp4j.DocumentLink; /** * Document link for : - * + * *
    *
  • XML Schema xsi:noNamespaceSchemaLocation
  • *
  • DTD SYSTEM (ex : + *
  • XML Schema xsi:schemaLocation
  • *
- * + * * @author Angelo ZERR * */ @@ -88,11 +92,27 @@ public void findDocumentLinks(DOMDocument document, List links) { } } } + // Doc link for xsi:schemaLocation + SchemaLocation schemaLocation = document.getSchemaLocation(); + if (schemaLocation != null) { + try { + Collection schemaLocationHints = schemaLocation.getSchemaLocationHints(); + String location; + for (SchemaLocationHint schemaLocationHint : schemaLocationHints) { + location = getResolvedLocation(document.getDocumentURI(), schemaLocationHint.getHint()); + if (location != null) { + links.add(createDocumentLink(schemaLocationHint, location)); + } + } + } catch (BadLocationException e) { + // Do nothing + } + } } /** * Returns the expanded system location - * + * * @return the expanded system location */ private static String getResolvedLocation(String documentURI, String location) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDocumentLinkTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDocumentLinkTest.java index 47b45e53ea..f3c9b0c7d6 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDocumentLinkTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDocumentLinkTest.java @@ -35,4 +35,58 @@ public void noNamespaceSchemaLocation() throws BadLocationException { XMLAssert.testDocumentLinkFor(xml, "src/test/resources/Format.xml", dl(r(1, 100, 1, 114), "src/test/resources/xsd/Format.xsd")); } + + @Test + public void singleSchemaLocation() throws BadLocationException { + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " "; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/Format.xml", + dl(r(1, 122, 1, 136), "src/test/resources/xsd/Format.xsd")); + } + + @Test + public void manySchemaLocation() throws BadLocationException { + String xml = "\r\n" + // + "\r\n"; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/beans-and-salad.xml", + dl(r(5, 63, 5, 83), "src/test/resources/xsd/spring-beans.xsd"), + dl(r(5, 178, 5, 191), "src/test/resources/xsd/salad.xsd"), + dl(r(5, 121, 5, 141), "src/test/resources/xsd/camel-spring.xsd") + ); + } + + @Test + public void lineBreakManySchemaLocation() throws BadLocationException { + String xml = "\r\n" + // + "\r\n"; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/beans-and-salad.xml", + dl(r(6, 45, 6, 65), "src/test/resources/xsd/spring-beans.xsd"), + dl(r(8, 45, 8, 58), "src/test/resources/xsd/salad.xsd"), + dl(r(7, 46, 7, 66), "src/test/resources/xsd/camel-spring.xsd")); + } + + public void blankSchemaLocation() throws BadLocationException { + String xml = "\r\n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/bean.xml"); + + } }