From c8d838286b760db3515a965f7117febec4e35d07 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 5 Aug 2020 17:00:20 -0400 Subject: [PATCH] Document links for catalog The 'uri' attribute in all the catalog entries that have it is now a document link. Closes #220 Signed-off-by: David Thompson --- .../extensions/catalog/CatalogUtils.java | 86 ++++++++++++++ .../XMLCatalogDocumentLinkParticipant.java | 67 +++++++++++ .../extensions/catalog/XMLCatalogPlugin.java | 11 +- .../catalog/XMLCatalogDocumentLinkTest.java | 105 ++++++++++++++++++ 4 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogUtils.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogUtils.java new file mode 100644 index 000000000..8f8f1250a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/CatalogUtils.java @@ -0,0 +1,86 @@ +/******************************************************************************* +* 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.catalog; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +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.utils.DOMUtils; + +/** + * Utility functions for working with XML catalog documents + */ +public class CatalogUtils { + + /** + * The catalog entries that have a 'uri' attribute + */ + private static final Collection CATALOG_NAMES = Arrays.asList("public", "system", "uri", "systemSuffix", + "uriSuffix"); + + private static final String CATALOG_ENTITY_NAME = "catalog"; + + private static final String URI_ATTRIBUTE_NAME = "uri"; + + /** + * Returns a list of all the catalog entries that have the attribute 'uri', or + * an empty list if the document is not a catalog. + * + * @param document The document to collect the catalog entries from + * @return A list of all the catalog entries that have the attribute 'uri', or + * an empty list if the document is not a catalog. + */ + public static List getCatalogEntries(DOMDocument document) { + if (!DOMUtils.isCatalog(document)) { + return Collections.emptyList(); + } + for (DOMNode n : document.getChildren()) { + if (CATALOG_ENTITY_NAME.equals(n.getNodeName())) { + return n.getChildren().stream().filter(CatalogUtils::isCatalogURIEntry).map((el) -> { + return (DOMElement) el; + }).collect(Collectors.toList()); + } + } + return Collections.emptyList(); + } + + /** + * Returns the uri attribute node of the given catalog entry or null if there is + * no uri attribute + * + * @param element The catalog entry to get the uri attribute of + * @return the uri attribute node of the given catalog entry or null if there is + * no uri attribute + */ + public static DOMAttr getCatalogEntryURI(DOMElement element) { + return element.getAttributeNode(URI_ATTRIBUTE_NAME); + } + + /** + * Checks if this node is a catalog entry that is required to have the 'uri' + * attribute + * + * @param node The node to check + * @return true if this node is an catalog entry that is required to have the + * 'uri' attribute and false otherwise + */ + private static boolean isCatalogURIEntry(DOMNode node) { + return node.isElement() && CATALOG_NAMES.contains(node.getNodeName()); + } + +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java new file mode 100644 index 000000000..1c5ba8d52 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkParticipant.java @@ -0,0 +1,67 @@ +/******************************************************************************* +* 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.catalog; + +import java.util.List; +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.services.extensions.IDocumentLinkParticipant; +import org.eclipse.lemminx.utils.FilesUtils; +import org.eclipse.lemminx.utils.XMLPositionUtility; +import org.eclipse.lsp4j.DocumentLink; + +/** + * Document links that are specific to catalogs + */ +public class XMLCatalogDocumentLinkParticipant implements IDocumentLinkParticipant { + + private static Logger LOGGER = Logger.getLogger(XMLCatalogDocumentLinkParticipant.class.getName()); + + @Override + public void findDocumentLinks(DOMDocument document, List links) { + for (DOMElement entry : CatalogUtils.getCatalogEntries(document)) { + DOMAttr catalogReference = CatalogUtils.getCatalogEntryURI(entry); + DOMNode valueLocation = catalogReference.getNodeAttrValue(); + try { + String path = getResolvedLocation(FilesUtils.removeFileScheme(document.getDocumentURI()), + catalogReference.getValue()); + links.add(XMLPositionUtility.createDocumentLink(valueLocation, path, true)); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, "Creation of document link failed", e); + } + } + } + + /** + * 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; + } + } + +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java index 2381b91f4..f3f7b4947 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogPlugin.java @@ -23,6 +23,7 @@ import org.eclipse.lemminx.client.InvalidPathWarner; import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings; import org.eclipse.lemminx.services.IXMLNotificationService; +import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; import org.eclipse.lemminx.services.extensions.save.ISaveContext; @@ -34,9 +35,14 @@ public class XMLCatalogPlugin implements IXMLExtension { private XMLCatalogURIResolverExtension uiResolver; + private final IDocumentLinkParticipant documentLinkParticipant; private InvalidPathWarner pathWarner; + public XMLCatalogPlugin() { + documentLinkParticipant = new XMLCatalogDocumentLinkParticipant(); + } + @Override public void doSave(ISaveContext context) { Object initializationOptionsSettings = context.getSettings(); @@ -50,11 +56,12 @@ public void doSave(ISaveContext context) { @Override public void start(InitializeParams params, XMLExtensionsRegistry registry) { uiResolver = new XMLCatalogURIResolverExtension(registry); - registry.getResolverExtensionManager().registerResolver(uiResolver); + registry.getResolverExtensionManager().registerResolver(uiResolver); IXMLNotificationService notificationService = registry.getNotificationService(); if (notificationService != null) { this.pathWarner = new InvalidPathWarner(notificationService); } + registry.registerDocumentLinkParticipant(documentLinkParticipant); } @Override @@ -68,7 +75,7 @@ private void validateCatalogPaths(ContentModelSettings cmSettings) { } String[] catalogs = cmSettings.getCatalogs(); Set invalidCatalogs = Arrays.stream(catalogs).filter(c -> !isXMLCatalogFileValid(c)).collect(Collectors.toSet()); - + if (invalidCatalogs.size() > 0) { this.pathWarner.onInvalidFilePath(invalidCatalogs, PathFeature.CATALOGS); } else { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkTest.java new file mode 100644 index 000000000..add39131f --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/catalog/XMLCatalogDocumentLinkTest.java @@ -0,0 +1,105 @@ +/******************************************************************************* +* 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.catalog; + +import org.junit.jupiter.api.Test; + +import static org.eclipse.lemminx.XMLAssert.testDocumentLinkFor; +import static org.eclipse.lemminx.XMLAssert.dl; +import static org.eclipse.lemminx.XMLAssert.r; + +/** + * Tests for the document links in XML catalog files + */ +public class XMLCatalogDocumentLinkTest { + + private static String CATALOG_PATH = "src/test/resources/catalog.xml"; + + @Test + public void testPublicEntryDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(1, 39, 1, 51), "src/test/resources/mySchema.xsd")); + } + + @Test + public void testSystemEntryDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(2, 9, 2, 24), "src/test/resources/otherSchema.xsd")); + } + + @Test + public void testURIEntryDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(3, 9, 3, 23), "src/test/resources/neatSchema.xsd")); + } + + @Test + public void testSystemSuffixEntryDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(1, 45, 1, 57), "src/test/resources/mySchema.xsd")); + } + + @Test + public void testURISuffixEntryDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(1, 42, 1, 54), "src/test/resources/mySchema.xsd")); + } + + @Test + public void testMustBeCatalog1() { + String xml = ""; + testDocumentLinkFor(xml, CATALOG_PATH); + } + + @Test + public void testMustBeCatalog2() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH); + } + + @Test + public void testOnlyEntriesHaveLinks() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH); + } + + @Test + public void testPublicEntryWithSpacesDocumentLink() { + String xml = "\n" + // + " \n" + // + ""; + testDocumentLinkFor(xml, CATALOG_PATH, // + dl(r(1, 39, 1, 54), "src/test/resources/my schema.xsd")); + } + +} \ No newline at end of file