Skip to content

Commit

Permalink
Hover for attribute values using XSD
Browse files Browse the repository at this point in the history
Fixes eclipse#12
Fixes eclipse#339

Signed-off-by: Nikolas Komonen <nikolaskomonen@gmail.com>
  • Loading branch information
NikolasKomonen committed Apr 3, 2019
1 parent 845bb47 commit 64b5ec1
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ public void setNodeAttrValue(DOMNode nodeAttrValue) {
this.nodeAttrValue = nodeAttrValue;
}

public boolean valueContainsOffset(int offset) {
return nodeAttrValue != null && offset >= nodeAttrValue.getStart() && offset < nodeAttrValue.getEnd();
}

public boolean isIncluded(int offset) {
return DOMNode.isIncluded(getStart(), getEnd(), offset);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface CMAttributeDeclaration {

String getDocumentation();

String getValueDocumentation(String value);

/**
* Returns true if the attribute is required and false otherwise.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public Hover onAttributeName(IHoverRequest hoverRequest) throws Exception {

try {
ContentModelManager contentModelManager = hoverRequest.getComponent(ContentModelManager.class);

CMElementDeclaration cmElement = contentModelManager.findCMElement(attribute.getOwnerElement());
if (cmElement != null) {
String attributeName = attribute.getName();
Expand All @@ -78,6 +77,42 @@ public Hover onAttributeName(IHoverRequest hoverRequest) throws Exception {
return null;
}

@Override
public Hover onAttributeValue(IHoverRequest hoverRequest) throws Exception {
DOMAttr attribute = (DOMAttr) hoverRequest.getNode();

//Attempts to compute specifically for XSI related attributes since
//the XSD itself does not have enough information. Should create a mock XSD eventually.
Hover temp = XSISchemaModel.computeHoverResponse(attribute, hoverRequest);
if(temp != null) {
return temp;
}

try {
ContentModelManager contentModelManager = hoverRequest.getComponent(ContentModelManager.class);

CMElementDeclaration cmElement = contentModelManager.findCMElement(attribute.getOwnerElement());
if (cmElement != null) {
String attributeName = attribute.getName();
CMAttributeDeclaration cmAttribute = cmElement.findCMAttribute(attributeName);

String attributeValue = attribute.getValue();
if (cmAttribute != null) {
String doc = cmAttribute.getValueDocumentation(attributeValue);
if (doc != null && doc.length() > 0) {
MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.PLAINTEXT);
content.setValue(doc);
return new Hover(content);
}
}
}
} catch (CacheResourceDownloadingException e) {
return getCacheWarningHover(e);
}
return null;
}

private Hover getCacheWarningHover(CacheResourceDownloadingException e) {
// Here cache is enabled and some XML Schema, DTD, etc are loading
MarkupContent content = new MarkupContent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen
DOMNode node = document.findNodeAt(offset);
if (node != null && node.isElement()) {
DOMElement element = (DOMElement) node;
int startOffset = element.getStartTagCloseOffset();
int startOffset;
if(element.isSelfClosed()) {
startOffset = element.getEnd();
}
else {
startOffset = element.getStartTagCloseOffset();
}
int endOffset = element.getEnd();
Range diagnosticRange = XMLPositionUtility.createRange(startOffset, endOffset, document);
CodeAction removeContentAction = CodeActionFactory.replace("Set element as empty", diagnosticRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,9 @@ public boolean isRequired() {
return super.simpleType.defaultType == XMLSimpleType.DEFAULT_TYPE_REQUIRED;
}

@Override
public String getValueDocumentation(String value) {
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@

import org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl;
import org.apache.xerces.impl.xs.XSComplexTypeDecl;
import org.apache.xerces.xs.StringList;
import org.apache.xerces.xs.XSAttributeDeclaration;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSMultiValueFacet;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.apache.xerces.xs.XSTypeDefinition;
import org.apache.xerces.xs.XSValue;
import org.apache.xerces.xs.datatypes.ObjectList;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;

/**
Expand Down Expand Up @@ -63,6 +66,17 @@ public String getDocumentation() {
return documentation;
}

@Override
public String getValueDocumentation(String value) {
if (documentation != null) {
return documentation;
}
// Try get xs:annotation from the element declaration or type
XSObjectList annotations = getValueAnnotations();
documentation = XSDAnnotationModel.getDocumentation(annotations, value);
return documentation;
}

/**
* Returns list of xs:annotation from the element declaration or type
* declaration.
Expand All @@ -74,6 +88,7 @@ private XSObjectList getAnnotations() {
// Try get xs:annotation from the element declaration
XSAttributeDeclaration attributeDeclaration = getAttrDeclaration();
XSObjectList annotation = attributeDeclaration.getAnnotations();

if (annotation != null && annotation.getLength() > 0) {
return annotation;
}
Expand All @@ -90,6 +105,69 @@ private XSObjectList getAnnotations() {
return null;
}

/**
* Returns list of xs:annotation from the element declaration or type
* declaration.
*
* Indicated by:
* https://msdn.microsoft.com/en-us/library/ms256143(v=vs.110).aspx
* xs:attribute tags have content of either an xs:annotation or xs:simpleType
*
* @return list of xs:annotation from the element declaration or type
* declaration.
*/
private XSObjectList getValueAnnotations() {
// Try get xs:annotation from the element declaration
XSAttributeDeclaration attributeDeclaration = getAttrDeclaration();
XSSimpleTypeDefinition simpleTypeDefinition = attributeDeclaration.getTypeDefinition();
XSSimpleTypeDecl simpleTypeDecl;


XSObjectList annotation = null; // The XSD tag that holds the documentation tag

if(simpleTypeDefinition instanceof XSSimpleTypeDecl) {
simpleTypeDecl = (XSSimpleTypeDecl) simpleTypeDefinition;
XSObjectList multiFacets = simpleTypeDecl.getMultiValueFacets();
if(!multiFacets.isEmpty()) {
XSMultiValueFacet facet = (XSMultiValueFacet) multiFacets.get(0);
multiFacets = facet.getAnnotations();
Object[] annotationArray = multiFacets.toArray();
if(!onlyContainsNull(annotationArray)) { // if multiValueFacets has annotations
annotation = simpleTypeDecl.getMultiValueFacets();
}
}
}
if(annotation == null){ // There was no specific documentation for the value, so use the general attribute documentation
annotation = attributeDeclaration.getAnnotations();
}
if (annotation != null && annotation.getLength() > 0) {
return annotation;
}
// Try get xs:annotation from the type of element declaration
XSTypeDefinition typeDefinition = attributeDeclaration.getTypeDefinition();
if (typeDefinition == null) {
return null;
}
if (typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
return ((XSComplexTypeDecl) typeDefinition).getAnnotations();
} else if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
return ((XSSimpleTypeDecl) typeDefinition).getAnnotations();
}
return null;
}

private boolean onlyContainsNull(Object[] arr) {
if(arr == null || arr.length == 0) {
return true;
}
for (Object o : arr) {
if(o != null) {
return false;
}
}
return true;
}

@Override
public boolean isRequired() {
return attributeUse.getRequired();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.xerces.impl.dv.ValidatedInfo;
import org.apache.xerces.xs.XSAnnotation;
import org.apache.xerces.xs.XSMultiValueFacet;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.datatypes.ObjectList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
Expand Down Expand Up @@ -47,12 +50,36 @@ public String getDocumentation() {
}

public static String getDocumentation(XSObjectList annotations) {
return getDocumentation(annotations, null);
}

public static String getDocumentation(XSObjectList annotations, String value) {
if (annotations == null) {
return "";
}
StringBuilder doc = new StringBuilder();
for (Object object : annotations) {
XSAnnotation annotation = (XSAnnotation) object;
XSAnnotation annotation = null;
if(object instanceof XSMultiValueFacet && value != null) {
XSMultiValueFacet multiValueFacet = (XSMultiValueFacet) object;
ObjectList enumerationValues = multiValueFacet.getEnumerationValues();
XSObjectList annotationValues = multiValueFacet.getAnnotations();
for (int i = 0; i < enumerationValues.getLength(); i++) {
Object enumValue = enumerationValues.get(i);

//Assuming always ValidatedInfo
String enumString = ((ValidatedInfo) enumValue).stringValue();

if(value.equals(enumString)) {
annotation = (XSAnnotation) annotationValues.get(i);
break;
}
}
}
else if(object instanceof XSAnnotation) {
annotation = (XSAnnotation) object;
}

XSDAnnotationModel annotationModel = XSDAnnotationModel.load(annotation);
if (annotationModel != null) {
if (annotationModel.getAppInfo() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
Expand Down Expand Up @@ -69,8 +70,12 @@ public Hover doHover(DOMDocument xmlDocument, Position position) {
return getTagHover(hoverRequest, tagRange, true);
}
} else if (node.isAttribute()) {
DOMAttr attr = (DOMAttr) node;
if(attr.valueContainsOffset(offset)) {
return getAttrValueHover(hoverRequest, null);
}
// Attribute is hover
return getAttrHover(hoverRequest, null);
return getAttrNameHover(hoverRequest, null);
}
return null;
}
Expand Down Expand Up @@ -126,7 +131,7 @@ private Range getTagNameRange(TokenType tokenType, int startOffset, int offset,
* @param attrRange the attribute range
* @return the LSP hover from the hovered attribute.
*/
private Hover getAttrHover(HoverRequest hoverRequest, Range attrRange) {
private Hover getAttrNameHover(HoverRequest hoverRequest, Range attrRange) {
//hoverRequest.setTagRange(tagRange);
//hoverRequest.setOpen(open);
for (IHoverParticipant participant : extensionsRegistry.getHoverParticipants()) {
Expand All @@ -141,4 +146,27 @@ private Hover getAttrHover(HoverRequest hoverRequest, Range attrRange) {
}
return null;
}

/**
* Returns the LSP hover from the hovered attribute.
*
* @param hoverRequest the hover request.
* @param attrRange the attribute range
* @return the LSP hover from the hovered attribute.
*/
private Hover getAttrValueHover(HoverRequest hoverRequest, Range attrRange) {
//hoverRequest.setTagRange(tagRange);
//hoverRequest.setOpen(open);
for (IHoverParticipant participant : extensionsRegistry.getHoverParticipants()) {
try {
Hover hover = participant.onAttributeValue(hoverRequest);
if (hover != null) {
return hover;
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "While performing IHoverParticipant#onTag", e);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ public void cvc_complex_type_2_1() throws Exception {
testDiagnosticsFor(xml, d);
testCodeActionsFor(xml, d, ca(d, te(5, 25, 5, 38, "/>")));
}

@Test
public void cvc_complex_type_2_1_SelfClosing() throws Exception {
String xml = "<money xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"src/test/resources/xsd/money.xsd\" currency=\"euros\"> </money>";

Diagnostic d = d(0, 143, 0, 144, XMLSchemaErrorCode.cvc_complex_type_2_1);
testDiagnosticsFor(xml, d);
testCodeActionsFor(xml, d, ca(d, te(0, 142, 0, 152, "/>")));
}

@Test
public void cvc_complex_type_2_1WithLinefeed() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public void testTagHover() throws BadLocationException {
assertHover(xml,
"Defines a single (usually named) bean. A bean definition may contain nested tags for constructor arguments, property values, lookup methods, and replaced methods. Mixing constructor injection and setter injection on the same bean is explicitly supported.",
2);

};

@Test
Expand Down Expand Up @@ -148,6 +147,40 @@ public void testTagHoverForXSISchemaNotRoot() throws BadLocationException {
null, null);
};

@Test
public void testHoverAttributeValueEuro() throws BadLocationException {
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<money xmlns=\"http://money\" currency=\"eu|ros\"\r\n" + // <- Hover
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" +
" xsi:schemaLocation=\"http://money xsd/money.xsd\"></money>";
XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/money.xml",
"Euro Hover", null);
};

@Test
public void testHoverAttributeValuePound() throws BadLocationException {
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<money xmlns=\"http://money\" currency=\"pou|nds\"\r\n" + // <- Hover
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" +
" xsi:schemaLocation=\"http://money xsd/money.xsd\"></money>";
XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/money.xml",
"Pound Hover", null);
};


@Test
public void testHoverAttributeValueNonExistent() throws BadLocationException {
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<money xmlns=\"http://money\" curr|ency=\"pounds\"\r\n" + // <- Hover
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" +
" xsi:schemaLocation=\"http://money xsd/money.xsd\"></money>";
XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/money.xml",
"Currency name Hover", null);
};

private static void assertHover(String value, String expectedHoverLabel, Integer expectedHoverOffset)
throws BadLocationException {
XMLAssert.assertHover(new XMLLanguageService(), value, "src/test/resources/catalogs/catalog.xml", null,
Expand Down
2 changes: 2 additions & 0 deletions org.eclipse.lsp4xml/src/test/resources/catalogs/catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

<system systemId="http://invoice.xsd" uri="../xsd/invoice.xsd" />

<system systemId="http://money.xsd" uri="../xsd/money.xsd" />

<uri name="http://docs.oasis-open.org/odata/ns/edmx"
uri="../xsd/edmx.xsd" />
<uri name="http://docs.oasis-open.org/odata/ns/edm"
Expand Down
Loading

0 comments on commit 64b5ec1

Please sign in to comment.