Skip to content

Commit

Permalink
Doclink for catalog entries with 'catalog' attr
Browse files Browse the repository at this point in the history
Any of the catalog entry elements that have the 'catalog' attribute
to refer to another catalog are now doclinks.

Use xml:base when making catalog doc links

Doc links now work inside `<group></group>`.
xml:base is taken into account when calculating where a doc link should
point in catalog files.

Closes eclipse-lemminx#845

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 committed Sep 30, 2020
1 parent 526e2b0 commit 9e78d66
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* 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.nio.file.Paths;

import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

/**
* Represents a catalog entry that uses the "catalog" attribute to reference an external document
*/
public class CatalogCatalogEntry extends CatalogEntry {

public CatalogCatalogEntry(@NonNull String baseURI, DOMElement entryElement) {
super(baseURI, entryElement);
}

@Override
public DOMRange getLinkRange() {
DOMAttr catalogAttr = CatalogUtils.getCatalogEntryCatalog(getEntryElement());
if (catalogAttr == null) {
return null;
}
return catalogAttr.getNodeAttrValue();
}

@Override
public String getResolvedURI() {
DOMAttr catalogAttr = CatalogUtils.getCatalogEntryCatalog(getEntryElement());
if (catalogAttr == null) {
return null;
}
String lastSegment = catalogAttr.getValue();
if (StringUtils.isBlank(lastSegment)) {
return null;
}
return Paths.get(getBaseURI(), lastSegment).toString();
}

}
Original file line number Diff line number Diff line change
@@ -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 org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;

/**
* Represents a catalog entry that references an external document
*/
public abstract class CatalogEntry {

private final String baseURI;
private final DOMElement entryElement;

/**
*
* @param baseURI <catalog>'s xml:base + <group>'s xml:base (if in a
* <group>)
* @param entryElement the element that corresponds with this catalog entry
*/
protected CatalogEntry(String baseURI, DOMElement entryElement) {
this.baseURI = baseURI;
this.entryElement = entryElement;
}

/**
* Returns the base URI for this catalog entry
*
* @return the base URI for this catalog entry
*/
public String getBaseURI() {
return baseURI;
}

/**
* Returns the element that corresponds with this catalog entry
*
* @return the element that corresponds with this catalog entry
*/
protected DOMElement getEntryElement() {
return entryElement;
}

/**
* Returns the range in the document where the link to an external document is,
* or null if this catalog entry does not refer to an external document
*
* @return the range in the document where the link to an external document is
*/
public abstract DOMRange getLinkRange();

/**
* Returns the URI for the document that this catalog entry references, or null
* if this catalog entry does not refer to an external document
*
* @return the URI for the document that this catalog entry references
*/
public abstract String getResolvedURI();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@

package org.eclipse.lemminx.extensions.catalog;

import java.nio.file.Paths;
import java.util.ArrayList;
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;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

/**
* Utility functions for working with XML catalog documents
Expand All @@ -30,30 +32,39 @@ public class CatalogUtils {
/**
* The catalog entries that have a 'uri' attribute
*/
private static final Collection<String> CATALOG_NAMES = Arrays.asList("public", "system", "uri", "systemSuffix",
private static final Collection<String> HAS_URI_ATTRIBUTE = Arrays.asList("public", "system", "uri", "systemSuffix",
"uriSuffix");

/**
* The catalog entries that have a 'catalog' attribute
*/
private static final Collection<String> HAS_CATALOG_ATTRIBUTE = Arrays.asList("delegatePublic", "delegateSystem",
"delegateUri", "nextCatalog");

private static final String XML_BASE_ATTRIBUTE = "xml:base";

private static final String CATALOG_ENTITY_NAME = "catalog";

private static final String GROUP_ENTITY_NAME = "group";

private static final String URI_ATTRIBUTE_NAME = "uri";

private static final String CATALOG_ATTRIBUTE_NAME = "catalog";

/**
* Returns a list of all the catalog entries that have the attribute 'uri', or
* an empty list if the document is not a catalog.
* Returns a list of all the catalog entries in the given document, 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.
* @return a list of all the catalog entries in the given document, or an empty
* list if the document is not a catalog.
*/
public static List<DOMElement> getCatalogEntries(DOMDocument document) {
public static List<CatalogEntry> 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 collectCatalogEntries((DOMElement) n);
}
}
return Collections.emptyList();
Expand All @@ -72,15 +83,114 @@ public static DOMAttr getCatalogEntryURI(DOMElement element) {
}

/**
* Checks if this node is a catalog entry that is required to have the 'uri'
* attribute
* Returns the catalog attribute node of the given catalog entry or null if
* there is no catalog attribute
*
* @param element The catalog entry to get the catalog attribute of
* @return the catalog attribute node of the given catalog entry or null if
* there is no catalog attribute
*/
public static DOMAttr getCatalogEntryCatalog(DOMElement element) {
return element.getAttributeNode(CATALOG_ATTRIBUTE_NAME);
}

/**
* Returns true if this element is a catalog entry that is required to have the
* 'uri' attribute and false otherwise
*
* @param node The node to check
* @return true if this node is an catalog entry that is required to have the
* @param element The element to check
* @return true if this element 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());
private static boolean isCatalogEntryWithURI(DOMElement element) {
return HAS_URI_ATTRIBUTE.contains(element.getNodeName());
}

/**
* Returns true if this element is a catalog entry that is required to have the
* 'catalog' attribute and false otherwise
*
* @param element The element to check
* @return true if this element is an catalog entry that is required to have the
* 'catalog' attribute and false otherwise
*/
private static boolean isCatalogEntryWithCatalog(DOMElement element) {
return HAS_CATALOG_ATTRIBUTE.contains(element.getNodeName());
}

/**
* Returns true if the given element is a group element and false otherwise
*
* @param element The element to check
* @return true if the given element is a group element and false otherwise
*/
private static boolean isGroupCatalogEntry(DOMElement element) {
return GROUP_ENTITY_NAME.equals(element.getNodeName());
}

/**
* Get a list of all catalog entry elements that are in the given catalog
*
* @param catalog the catalog element to collect the entries for
* @return A list of all the catalog entities in this catalog
*/
private static List<CatalogEntry> collectCatalogEntries(@NonNull DOMElement catalog) {
List<CatalogEntry> entries = new ArrayList<>();
String baseURI = catalog.getAttribute(XML_BASE_ATTRIBUTE);
baseURI = baseURI == null ? "" : baseURI;
for (DOMNode node : catalog.getChildren()) {
if (node.isElement()) {
DOMElement element = (DOMElement) node;
CatalogEntry catalogEntry = createCatalogEntry(baseURI, element);
if (catalogEntry != null) {
entries.add(catalogEntry);
} else if (isGroupCatalogEntry(element)) {
entries.addAll(collectGroupEntries(element, baseURI));
}
}
}
return entries;
}

/**
* Get a list of all catalog entry elements that are in the given group
*
* @param group the group element to collect the entries for
* @param baseURI the baseURI of the catalog
* @return A list of all the catalog entities in this group
*/
private static List<CatalogEntry> collectGroupEntries(DOMElement group, @NonNull String baseURI) {
List<CatalogEntry> entries = new ArrayList<>();
String groupSegment = group.getAttribute(XML_BASE_ATTRIBUTE);
if (groupSegment != null) {
baseURI = Paths.get(baseURI, groupSegment).toString();
}
for (DOMNode node : group.getChildren()) {
if (node.isElement()) {
DOMElement element = (DOMElement) node;
CatalogEntry catalogEntry = createCatalogEntry(baseURI, element);
if (catalogEntry != null) {
entries.add(catalogEntry);
}
}
}
return entries;
}

/**
* Returns a catalog entry for the given element and null if a catalog entry can't be made
*
* @param baseURI the base URI of the catalog entry
* @param element the element to turn into a catalog entry
* @return a catalog entry for the given element and null if a catalog entry can't be made
*/
private static CatalogEntry createCatalogEntry(@NonNull String baseURI, DOMElement element) {
if (isCatalogEntryWithURI(element)) {
return new URICatalogEntry(baseURI, element);
} else if (isCatalogEntryWithCatalog(element)) {
return new CatalogCatalogEntry(baseURI, element);
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* 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.nio.file.Paths;

import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;

/**
* Represents a catalog entry that uses the "uri" attribute to reference an external document
*/
public class URICatalogEntry extends CatalogEntry {

public URICatalogEntry(@NonNull String baseURI, DOMElement entryElement) {
super(baseURI, entryElement);
}

@Override
public DOMRange getLinkRange() {
DOMAttr uriAttr = CatalogUtils.getCatalogEntryURI(getEntryElement());
if (uriAttr == null) {
return null;
}
return uriAttr.getNodeAttrValue();
}

@Override
public String getResolvedURI() {
DOMAttr uriAttr = CatalogUtils.getCatalogEntryURI(getEntryElement());
if (uriAttr == null) {
return null;
}
String lastSegment = uriAttr.getValue();
if (StringUtils.isBlank(lastSegment)) {
return null;
}
return Paths.get(getBaseURI(), lastSegment).toString();
}

}
Loading

0 comments on commit 9e78d66

Please sign in to comment.