Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hover for attribute values using XSD #337

Merged
merged 1 commit into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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