Skip to content

Commit

Permalink
Add support for textDocument/references for XML Schema types
Browse files Browse the repository at this point in the history
Fix #58

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jun 25, 2019
1 parent 9d8e118 commit 804d215
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ public CompletableFuture<Either<List<? extends Location>, List<? extends Locatio
TextDocumentPositionParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
if (definitionLinkSupport) {
return Either.forRight(getXMLLanguageService().findDefinition(xmlDocument, params.getPosition(), cancelChecker));
return Either.forRight(
getXMLLanguageService().findDefinition(xmlDocument, params.getPosition(), cancelChecker));
}
List<? extends Location> locations = getXMLLanguageService()
.findDefinition(xmlDocument, params.getPosition(), cancelChecker) //
Expand All @@ -299,7 +300,8 @@ public CompletableFuture<Either<List<? extends Location>, List<? extends Locatio
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
return computeDOMAsync(params.getTextDocument(), (cancelChecker, xmlDocument) -> {
return getXMLLanguageService().findReferences(xmlDocument, params.getPosition(), params.getContext());
return getXMLLanguageService().findReferences(xmlDocument, params.getPosition(), params.getContext(),
cancelChecker);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,11 +464,6 @@ public DOMDocumentType getDoctype() {
return null;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.lsp4xml.dom.Node#getOwnerDocument()
*/
@Override
public DOMDocument getOwnerDocument() {
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public DOMNode(int start, int end) {
*
* @return the owner document and null otherwise.
*/
@Override
public DOMDocument getOwnerDocument() {
Node node = parent;
while (node != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ public static String getDocumentation(DOMAttr attr) {
doc.append(attr.getValue());
doc.append("**");
DOMElement element = attr.getOwnerElement();
if (XSDUtils.isComplexType(element)) {
if (XSDUtils.isXSComplexType(element)) {
doc.append(lineSeparator);
doc.append(" - Type: `Complex Type` ");
} else if (XSDUtils.isSimpleType(element)) {
} else if (XSDUtils.isXSSimpleType(element)) {
doc.append(lineSeparator);
doc.append(" - Type: `Simple Type` ");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import org.eclipse.lsp4xml.extensions.xsd.contentmodel.CMXSDContentModelProvider;
import org.eclipse.lsp4xml.extensions.xsd.participants.XSDCompletionParticipant;
import org.eclipse.lsp4xml.extensions.xsd.participants.XSDDefinitionParticipant;
import org.eclipse.lsp4xml.extensions.xsd.participants.XSDReferenceParticipant;
import org.eclipse.lsp4xml.extensions.xsd.participants.diagnostics.XSDDiagnosticsParticipant;
import org.eclipse.lsp4xml.services.extensions.ICompletionParticipant;
import org.eclipse.lsp4xml.services.extensions.IDefinitionParticipant;
import org.eclipse.lsp4xml.services.extensions.IReferenceParticipant;
import org.eclipse.lsp4xml.services.extensions.IXMLExtension;
import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant;
Expand All @@ -37,12 +39,15 @@ public class XSDPlugin implements IXMLExtension {

private final IDiagnosticsParticipant diagnosticsParticipant;

private final IReferenceParticipant referenceParticipant;

private XSDURIResolverExtension uiResolver;

public XSDPlugin() {
completionParticipant = new XSDCompletionParticipant();
definitionParticipant = new XSDDefinitionParticipant();
diagnosticsParticipant = new XSDDiagnosticsParticipant();
referenceParticipant = new XSDReferenceParticipant();
}

@Override
Expand Down Expand Up @@ -70,6 +75,7 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
registry.registerCompletionParticipant(completionParticipant);
registry.registerDefinitionParticipant(definitionParticipant);
registry.registerDiagnosticsParticipant(diagnosticsParticipant);
registry.registerReferenceParticipant(referenceParticipant);
}

@Override
Expand All @@ -78,5 +84,6 @@ public void stop(XMLExtensionsRegistry registry) {
registry.unregisterCompletionParticipant(completionParticipant);
registry.unregisterDefinitionParticipant(definitionParticipant);
registry.unregisterDiagnosticsParticipant(diagnosticsParticipant);
registry.unregisterReferenceParticipant(referenceParticipant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2019 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.lsp4xml.extensions.xsd.participants;

import java.util.List;

import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.ReferenceContext;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils;
import org.eclipse.lsp4xml.services.extensions.AbstractReferenceParticipant;
import org.eclipse.lsp4xml.utils.DOMUtils;
import org.eclipse.lsp4xml.utils.XMLPositionUtility;

/**
* XSD reference
*
* @author Angelo ZERR
*
*/
public class XSDReferenceParticipant extends AbstractReferenceParticipant {

@Override
protected boolean match(DOMDocument document) {
return DOMUtils.isXSD(document);
}

@Override
protected void findReferences(DOMNode node, Position position, int offset, ReferenceContext context,
List<Location> locations, CancelChecker cancelChecker) {
DOMAttr attr = node.findAttrAt(offset);
if (attr != null) {
node = attr;
}
XSDUtils.collectXSReferenceTypes(node, (from, to) -> {
locations.add(XMLPositionUtility.createLocation(from));
}, cancelChecker);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.xsd.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.utils.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

Expand All @@ -29,6 +35,46 @@
*/
public class XSDUtils {

/**
* Attribute name which can be referenced by another attribute:
*
* <ul>
* <li>xs:complexType/@name</li>
* <li>xs:simpleType/@name</li>
* <li>xs:element/@name</li>
* <li>xs:group/@name</li>
* </ul>
*
*/
private static class ReferencedNode {

private final DOMAttr attr;
private BindingType referencedBindingType;
private boolean xsElement;

public ReferencedNode(DOMAttr node) {
this.attr = node;
this.xsElement = isXSElement(node.getOwnerElement());
this.referencedBindingType = getReferencedBindingType(node);
}

public DOMNode getNode() {
return attr;
}

public boolean isReference(String value, BindingType bindingType) {
if (referencedBindingType.equals(bindingType)
|| (referencedBindingType.isSimple() && bindingType.isSimple())
|| (referencedBindingType.isComplex() && bindingType.isComplex())
|| (xsElement && (BindingType.ELEMENT.equals(bindingType))
|| BindingType.REF.equals(bindingType))) {
return Objects.equal(value, attr.getValue());
}
return false;
}

}

/**
* Binding type of xs attribute.
*
Expand Down Expand Up @@ -68,11 +114,11 @@ public static BindingType getBindingType(DOMAttr attr) {
DOMElement element = attr.getOwnerElement();
DOMElement parent = element.getParentElement();
if (parent != null) {
if (parent.getLocalName().equals("complexContent") | isComplexType(parent)) {
if (parent.getLocalName().equals("complexContent") || isXSComplexType(parent)) {
// parent element is complexContent or complexType -> bounded type is complex
return BindingType.COMPLEX;
}
if (parent.getLocalName().equals("simpleContent") || isSimpleType(parent)) {
if (parent.getLocalName().equals("simpleContent") || isXSSimpleType(parent)) {
// parent element is simpleContent or simpleType -> bounded type is simple
return BindingType.SIMPLE;
}
Expand All @@ -99,6 +145,18 @@ public static BindingType getBindingType(DOMAttr attr) {
return BindingType.NONE;
}

private static BindingType getReferencedBindingType(DOMAttr attr) {
Element element = attr.getOwnerElement();
if (isXSSimpleType(element)) {
return BindingType.SIMPLE;
} else if (isXSComplexType(element)) {
return BindingType.COMPLEX;
} else if (isXSElement(element) || isXSGroup(element)) {
return BindingType.REF;
}
return BindingType.NONE;
}

/**
* Collect XSD types declared in the XML Schema according the given attribute
* and binding type.
Expand Down Expand Up @@ -168,26 +226,141 @@ public static void collectXSTypes(DOMAttr originAttr, BindingType bindingType, b
}

private static boolean canCollectElement(DOMAttr originAttr, Element targetElement, BindingType bindingType) {
if (isComplexType(targetElement)) {
if (isXSComplexType(targetElement)) {
return bindingType.isComplex();
} else if (isSimpleType(targetElement)) {
} else if (isXSSimpleType(targetElement)) {
return bindingType.isSimple();
} else if (bindingType == BindingType.REF) {
// - xs:element/@name attributes if originAttr is xs:element/@ref
// - xs:group/@name attributes if originAttr is xs:group/@ref
return (originAttr.getOwnerElement().getLocalName().equals(targetElement.getLocalName()));
} else if (bindingType == BindingType.ELEMENT) {
return "element".equals(targetElement.getLocalName());
return isXSElement(targetElement);
}
return false;
}

public static boolean isComplexType(Element element) {
/**
* Collect references types from the given referenced node.
*
* @param referencedNode the referenced node
* @param collector the collector to collect reference origin and target
* node.
*/
public static void collectXSReferenceTypes(DOMNode referencedNode, BiConsumer<DOMNode, DOMNode> collector,
CancelChecker cancelChecker) {
List<ReferencedNode> referencedNodes = new ArrayList<>();
Document document = referencedNode.getOwnerDocument();
switch (referencedNode.getNodeType()) {
case Node.ATTRIBUTE_NODE:
// The referenced node is an attribute, add it to search references from it.
case Node.ELEMENT_NODE:
// The referenced node is an element, get the attribute name) and add it to
// search references from it.
addReferenceNode(referencedNode, referencedNodes);
break;
case Node.DOCUMENT_NODE:
// The referenced node is the DOM document, collect all attributes
// xs:complexType/@name, xs:simpleType/@name, xs:element/@name, xs:group/@name
// which can be referenced
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node n = nodes.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
DOMElement element = (DOMElement) n;
if (isXSComplexType(element) || isXSSimpleType(element) || isXSElement(element)
|| isXSGroup(element)) {
addReferenceNode(element, referencedNodes);
}
}
}
}
if (referencedNodes.isEmpty()) {
// None referenced nodes, stop the search of references
return;
}

// Here referencedNodes is filled with a list of attributes
// xs:complexType/@name,
// xs:simpleType/@name, xs:element/@name, xs:group/@name

// Collect references for each references nodes
NodeList nodes = document.getDocumentElement().getChildNodes();
collectXSReferenceTypes(nodes, referencedNodes, collector, cancelChecker);
}

/**
* Add the given node as reference node if it is applicable.
*
* @param node the node to add.
* @param referencedNodes the list of referenced nodes.
*/
private static void addReferenceNode(DOMNode node, List<ReferencedNode> referencedNodes) {
DOMAttr attr = null;
switch (node.getNodeType()) {
case Node.ATTRIBUTE_NODE:
attr = (DOMAttr) node;
break;
case Node.ELEMENT_NODE:
attr = ((DOMElement) node).getAttributeNode("name");
break;
}
// Attribute must exists and her value must be not empty.
if (attr != null && !StringUtils.isEmpty(attr.getValue())) {
referencedNodes.add(new ReferencedNode(attr));
}
}

private static void collectXSReferenceTypes(NodeList nodes, List<ReferencedNode> referencedNodes,
BiConsumer<DOMNode, DOMNode> collector, CancelChecker cancelChecker) {
for (int i = 0; i < nodes.getLength(); i++) {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
}
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
DOMElement element = (DOMElement) node;
NamedNodeMap attributes = element.getAttributes();
if (attributes != null) {
for (int j = 0; j < attributes.getLength(); j++) {
DOMAttr attr = (DOMAttr) attributes.item(j);
BindingType bindingType = XSDUtils.getBindingType(attr);
if (bindingType != BindingType.NONE) {
String value = attr.getValue();
int index = value.indexOf(":");
if (index != -1) {
value = value.substring(index + 1, value.length());
}
for (ReferencedNode referencedNode : referencedNodes) {
if (referencedNode.isReference(value, bindingType)) {
collector.accept(attr, referencedNode.getNode());
}
}
}
}
}

}
if (node.hasChildNodes()) {
collectXSReferenceTypes(node.getChildNodes(), referencedNodes, collector, cancelChecker);
}
}

}

public static boolean isXSComplexType(Element element) {
return "complexType".equals(element.getLocalName());
}

public static boolean isSimpleType(Element element) {
public static boolean isXSSimpleType(Element element) {
return "simpleType".equals(element.getLocalName());
}

public static boolean isXSElement(Element element) {
return "element".equals(element.getLocalName());
}

public static boolean isXSGroup(Element element) {
return "group".equals(element.getLocalName());
}
}
Loading

0 comments on commit 804d215

Please sign in to comment.