Skip to content

Commit

Permalink
Document links for catalog
Browse files Browse the repository at this point in the history
The 'uri' attribute in all the catalog entries that have
it is now a document link.

Closes #220

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 authored and angelozerr committed Aug 6, 2020
1 parent 2aad97f commit c8d8382
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 2 deletions.
@@ -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<String> 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<DOMElement> 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());
}

}
@@ -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<DocumentLink> 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;
}
}

}
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -68,7 +75,7 @@ private void validateCatalogPaths(ContentModelSettings cmSettings) {
}
String[] catalogs = cmSettings.getCatalogs();
Set<String> invalidCatalogs = Arrays.stream(catalogs).filter(c -> !isXMLCatalogFileValid(c)).collect(Collectors.toSet());

if (invalidCatalogs.size() > 0) {
this.pathWarner.onInvalidFilePath(invalidCatalogs, PathFeature.CATALOGS);
} else {
Expand Down
@@ -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 = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <public id=\"http://example.org\" uri=\"mySchema.xsd\" />\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(1, 39, 1, 51), "src/test/resources/mySchema.xsd"));
}

@Test
public void testSystemEntryDocumentLink() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <system id=\"http://example.org\"\n" + //
" uri=\"otherSchema.xsd\" />\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(2, 9, 2, 24), "src/test/resources/otherSchema.xsd"));
}

@Test
public void testURIEntryDocumentLink() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <uri\n" + //
" id=\"http://example.org\"\n" + //
" uri=\"neatSchema.xsd\" />\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(3, 9, 3, 23), "src/test/resources/neatSchema.xsd"));
}

@Test
public void testSystemSuffixEntryDocumentLink() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <systemSuffix id=\"http://example.org\" uri=\"mySchema.xsd\"></systemSuffix>\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(1, 45, 1, 57), "src/test/resources/mySchema.xsd"));
}

@Test
public void testURISuffixEntryDocumentLink() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <uriSuffix id=\"http://example.org\" uri=\"mySchema.xsd\"></uriSuffix>\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(1, 42, 1, 54), "src/test/resources/mySchema.xsd"));
}

@Test
public void testMustBeCatalog1() {
String xml = "<catalog><public url=\"document.xsd\" /></catalog>";
testDocumentLinkFor(xml, CATALOG_PATH);
}

@Test
public void testMustBeCatalog2() {
String xml = "<aaa xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <public url=\"document.xsd\" />\n" + //
"</aaa>";
testDocumentLinkFor(xml, CATALOG_PATH);
}

@Test
public void testOnlyEntriesHaveLinks() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <aaa id=\"http://example.org\" uri=\"mySchema.xsd\"></aaa>\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH);
}

@Test
public void testPublicEntryWithSpacesDocumentLink() {
String xml = "<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n" + //
" <public id=\"http://example.org\" uri=\"my%20schema.xsd\" />\n" + //
"</catalog>";
testDocumentLinkFor(xml, CATALOG_PATH, //
dl(r(1, 39, 1, 54), "src/test/resources/my schema.xsd"));
}

}

0 comments on commit c8d8382

Please sign in to comment.