From d2aefbd3e49485955e9b7aa9ff6bce4b490c87d4 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 26 May 2020 15:53:48 -0400 Subject: [PATCH] Added document link for included schemas in .xsd The URI specified for the `schemaLocation` attribute of `xs:include` is now clickable. Fixes #689. Signed-off-by: David Thompson --- .../lemminx/extensions/xsd/XSDPlugin.java | 6 ++ .../XSDDocumentLinkParticipant.java | 82 ++++++++++++++++ .../extensions/xsd/utils/XSDUtils.java | 15 ++- .../xsd/XSDDocumentLinkingExtensionsTest.java | 96 +++++++++++++++++++ 4 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/XSDDocumentLinkingExtensionsTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java index 35dd645f8..6829b66e5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java @@ -19,12 +19,14 @@ import org.eclipse.lemminx.extensions.xsd.participants.XSDCodeLensParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDCompletionParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDDefinitionParticipant; +import org.eclipse.lemminx.extensions.xsd.participants.XSDDocumentLinkParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDHighlightingParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDReferenceParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDRenameParticipant; import org.eclipse.lemminx.extensions.xsd.participants.diagnostics.XSDDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; +import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.IHighlightingParticipant; import org.eclipse.lemminx.services.extensions.IReferenceParticipant; import org.eclipse.lemminx.services.extensions.IRenameParticipant; @@ -51,6 +53,7 @@ public class XSDPlugin implements IXMLExtension { private final ICodeLensParticipant codeLensParticipant; private final IHighlightingParticipant highlightingParticipant; private final IRenameParticipant renameParticipant; + private final IDocumentLinkParticipant documentLinkParticipant; private XSDURIResolverExtension uiResolver; private ContentModelManager modelManager; @@ -63,6 +66,7 @@ public XSDPlugin() { codeLensParticipant = new XSDCodeLensParticipant(); highlightingParticipant = new XSDHighlightingParticipant(); renameParticipant = new XSDRenameParticipant(); + documentLinkParticipant = new XSDDocumentLinkParticipant(); } @Override @@ -94,6 +98,7 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerCodeLensParticipant(codeLensParticipant); registry.registerHighlightingParticipant(highlightingParticipant); registry.registerRenameParticipant(renameParticipant); + registry.registerDocumentLinkParticipant(documentLinkParticipant); } @Override @@ -106,5 +111,6 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterCodeLensParticipant(codeLensParticipant); registry.unregisterHighlightingParticipant(highlightingParticipant); registry.unregisterRenameParticipant(renameParticipant); + registry.unregisterDocumentLinkParticipant(documentLinkParticipant); } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java new file mode 100644 index 000000000..9080f7ff6 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDDocumentLinkParticipant.java @@ -0,0 +1,82 @@ +/** + * 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 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.extensions.xsd.participants; + +import static org.eclipse.lemminx.utils.XMLPositionUtility.createDocumentLink; + +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.xerces.impl.XMLEntityManager; +import org.apache.xerces.util.URI.MalformedURIException; +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.extensions.xsd.utils.XSDUtils; +import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; +import org.eclipse.lemminx.utils.StringUtils; +import org.eclipse.lsp4j.DocumentLink; +import org.w3c.dom.Element; + +/** + * + * Implements document links in .xsd files for + * + * + */ +public class XSDDocumentLinkParticipant implements IDocumentLinkParticipant { + + private static final Logger LOGGER = Logger.getLogger(XSDDocumentLinkParticipant.class.getName()); + + @Override + public void findDocumentLinks(DOMDocument document, List links) { + DOMElement root = document.getDocumentElement(); + if (root == null || !XSDUtils.isXSSchema(root)) { + return; + } + String xmlSchemaPrefix = root.getPrefix(); + List children = root.getChildren(); + for (DOMNode child : children) { + if (child.isElement() && XSDUtils.isXSInclude((Element) child) + && Objects.equals(child.getPrefix(), xmlSchemaPrefix)) { + DOMElement includeElement = (DOMElement) child; + DOMAttr schemaLocationAttr = XSDUtils.getSchemaLocation(includeElement); + if (schemaLocationAttr != null && !StringUtils.isEmpty(schemaLocationAttr.getValue())) { + String location = getResolvedLocation(document.getDocumentURI(), schemaLocationAttr.getValue()); + DOMRange schemaLocationRange = schemaLocationAttr.getNodeAttrValue(); + try { + links.add(createDocumentLink(schemaLocationRange, location)); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, "Creation of document link failed", e); + } + } + } + } + } + + 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; + } + } + +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/utils/XSDUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/utils/XSDUtils.java index b3addd083..43ab190b2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/utils/XSDUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/utils/XSDUtils.java @@ -19,6 +19,8 @@ import java.util.Vector; import java.util.function.BiConsumer; +import com.google.common.base.Objects; + import org.apache.xerces.impl.xs.SchemaGrammar; import org.apache.xerces.xs.StringList; import org.eclipse.lemminx.dom.DOMAttr; @@ -37,8 +39,6 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import com.google.common.base.Objects; - /** * XSD utilities. * @@ -404,6 +404,10 @@ public static boolean isXSAttribute(DOMElement element) { return "attribute".equals(element.getLocalName()); } + public static boolean isXSSchema(Element element) { + return "schema".equals(element.getLocalName()); + } + public static FilesChangedTracker createFilesChangedTracker(SchemaGrammar grammar) { return createFilesChangedTracker(Collections.singleton(grammar)); } @@ -444,4 +448,11 @@ private static void updateTracker(SchemaGrammar grammar, Set trac } } } + + public static DOMAttr getSchemaLocation(DOMElement includeElement) { + if (!isXSInclude(includeElement)) { + return null; + } + return includeElement.getAttributeNode("schemaLocation"); + } } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/XSDDocumentLinkingExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/XSDDocumentLinkingExtensionsTest.java new file mode 100644 index 000000000..dc127481f --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsd/XSDDocumentLinkingExtensionsTest.java @@ -0,0 +1,96 @@ +/** + * 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 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.extensions.xsd; + +import static org.eclipse.lemminx.XMLAssert.dl; +import static org.eclipse.lemminx.XMLAssert.r; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.junit.jupiter.api.Test; + +/** + * Tests for the docuement links in .xsd provided by XSDDocumentLinkParticipant + * + * @see org.eclipse.lemminx.extensions.xsd.participants.XSDDocumentLinkParticipant + */ +public class XSDDocumentLinkingExtensionsTest { + + @Test + public void xsIncludeUsualNamespace() throws BadLocationException { + String xml = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/unnamed-integer.xsd", + dl(r(1, 32, 1, 42), "src/test/resources/xsd/choice.xsd")); + } + + @Test + public void xsIncludeDifferentNamespace() throws BadLocationException { + String xml = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/unnamed-integer.xsd", + dl(r(1, 45, 1, 55), "src/test/resources/xsd/choice.xsd")); + } + + @Test + public void xsIncludeEmptySchemaLocation() throws BadLocationException { + String xml = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/unnamed-integer.xsd"); + } + + @Test + public void xsIncludeManyOccurences() throws BadLocationException { + String xml = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/unnamed-integer.xsd", + dl(r(1, 32, 1, 42), "src/test/resources/xsd/choice.xsd"), + dl(r(2, 32, 2, 43), "src/test/resources/xsd/pattern.xsd")); + } + + @Test + public void xsIncludeNoSchemaLocation() throws BadLocationException { + String xml = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + ""; + XMLAssert.testDocumentLinkFor(xml, "src/test/resources/xsd/unnamed-integer.xsd"); + } + +} \ No newline at end of file