Skip to content

Commit

Permalink
Hover for XSI and XSISchemaModel refactoring
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Komonen <[email protected]>
  • Loading branch information
NikolasKomonen committed Dec 4, 2018
1 parent f0254a4 commit e97f12a
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ private void fillWithChildrenElementDeclaration(DOMElement element, Collection<C
@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
if (request.getXMLDocument().hasSchemaInstancePrefix()) {
computeXSIAttributes(generateValue, fullRange, request, response);
if(request.getXMLDocument().hasSchemaInstancePrefix()) {
XSISchemaModel.computeCompletionResponses(request, response, fullRange, request.getXMLDocument(), generateValue);
}
// otherwise, manage completion based on XML Schema, DTD.
DOMElement parentElement = request.getNode().isElement() ? (DOMElement) request.getNode() : null;
Expand Down Expand Up @@ -167,21 +167,5 @@ public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuo
}
}

/**
* Creates and sets (xsi) completion items if needed.
*
* @param editRange
* @param request
* @param response
* @throws BadLocationException
*/
private void computeXSIAttributes(boolean generateValue, Range editRange, ICompletionRequest request,
ICompletionResponse response) throws BadLocationException {
DOMDocument document = request.getXMLDocument();
DOMElement rootElement = document.getDocumentElement();
int offset = document.offsetAt(editRange.getStart());
if (rootElement.equals(document.findNodeAt(offset))) {
XSISchemaModel.computeCompletionResponses(request, response, generateValue, editRange, document);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lsp4xml.services.XSISchemaModel;
import org.eclipse.lsp4xml.services.extensions.HoverParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.IHoverRequest;
import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException;
Expand Down Expand Up @@ -51,9 +52,19 @@ public Hover onTag(IHoverRequest hoverRequest) throws Exception {

@Override
public Hover onAttributeName(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);
DOMAttr attribute = (DOMAttr) hoverRequest.getNode();

CMElementDeclaration cmElement = contentModelManager.findCMElement(attribute.getOwnerElement());
if (cmElement != null) {
String attributeName = attribute.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,98 @@

import java.util.Collection;

import org.apache.xml.serialize.LineSeparator;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MarkupKind;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
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;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.services.extensions.IHoverRequest;
import org.eclipse.lsp4xml.utils.StringUtils;

/**
* This class holds values that represent the XSI xsd. Can be seen at
* https://www.w3.org/2001/XMLSchema-instance
*/
public class XSISchemaModel {

public static void computeCompletionResponses(ICompletionRequest request, ICompletionResponse response,
boolean generateValue, Range editRange, DOMDocument document) {
private static String lineSeparator = System.lineSeparator();
public static final String TYPE_DOC = "Specifies the type of an element. This attribute labels an element as a particular type, even though there might not be an element declaration in the schema binding that element to the type.";
public static final String NIL_DOC = "Indicates if an element should contain content. Valid values are `true` or `false`";
public static final String SCHEMA_LOCATION_DOC =
"The xsi:schemaLocation attribute can be used in an XML document " +
"to reference an XML Schema document that has a target namespace. " + lineSeparator +
"```xml " + lineSeparator +
"<ns:root "+ lineSeparator +
" xmlns:ns=\"http://example.com/ns\" " + lineSeparator +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + lineSeparator +
" xsi:schemaLocation=\"http://example.com/ns example.xsd\">" + lineSeparator +
" <!-- ... --> " + lineSeparator +
"</ns:root> " + lineSeparator +
"```" ;
public static final String NO_NAMESPACE_SCHEMA_LOCATION_DOC=
"The xsi:noNamespaceSchemaLocation attribute can be used in an XML document " + lineSeparator +
"to reference an XML Schema document that does not have a target namespace. " + lineSeparator +
"```xml " + lineSeparator +
"<root " + lineSeparator +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "+ lineSeparator +
" xsi:noNamespaceSchemaLocation=\"example.xsd\">" + lineSeparator +
" <!-- ... --> " + lineSeparator +
"</root> " + lineSeparator +
"```" ;
public static void computeCompletionResponses(ICompletionRequest request,
ICompletionResponse response, Range editRange, DOMDocument document, boolean generateValue) throws BadLocationException {

DOMElement rootElement = document.getDocumentElement();
int offset = document.offsetAt(editRange.getStart());
boolean inRootElement = false;
DOMElement nodeAtOffset = (DOMElement) document.findNodeAt(offset);
if(rootElement.equals(nodeAtOffset)) {
inRootElement = true;
}

boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported();
String actualPrefix = document.getSchemaInstancePrefix();
String name;
String documentation;
DOMElement root = document.getDocumentElement();

boolean schemaLocationExists = document.hasSchemaLocation();
boolean noNamespaceSchemaLocationExists = document.hasNoNamespaceSchemaLocation();
// Indicates that no values are allowed inside an XML element
if (!attributeAlreadyExists(root, actualPrefix, "nil")) {
documentation = "Indicates if an element should contain content. Valid values are true/false";
//Indicates that no values are allowed inside an XML element
if(!attributeAlreadyExists(nodeAtOffset, actualPrefix, "nil")) {
documentation = NIL_DOC;
name = actualPrefix + ":nil";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, StringUtils.TRUE,
StringUtils.TRUE_FALSE_ARRAY, documentation, response);
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, StringUtils.TRUE,
StringUtils.TRUE_FALSE_ARRAY, documentation, response);
}
// Signals that an element should be accepted as ·valid· when it has no content
// despite
// a content type which does not require or even necessarily allow empty
// content.
// An element may be ·valid· without content if it has the attribute xsi:nil
// with
// the value true.
if (!attributeAlreadyExists(root, actualPrefix, "type")) {
documentation = "Specifies the type of an element. This attribute labels an element as a particular type, even though there might not be an element declaration in the schema binding that element to the type.";
//Signals that an element should be accepted as ·valid· when it has no content despite
//a content type which does not require or even necessarily allow empty content.
//An element may be ·valid· without content if it has the attribute xsi:nil with
//the value true.
if(!attributeAlreadyExists(nodeAtOffset, actualPrefix, "type")) {
documentation = TYPE_DOC;
name = actualPrefix + ":type";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation,
response);
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);
}
// The xsi:schemaLocation and xsi:noNamespaceSchemaLocation attributes can be
// used in a document
// to provide hints as to the physical location of schema documents which may be
// used for ·assessment·.
if (!schemaLocationExists && !noNamespaceSchemaLocationExists) {
documentation = "The xsi:schemaLocation attribute can be used in an XML document "
+ "to reference an XML Schema document that has a target namespace.\r\n " + "```xml \r\n"
+ "<ns:root xmlns:ns=\"http://example.com/ns\""
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
+ " xsi:schemaLocation=\"http://example.com/ns example-ns.xsd\">\r\n " + " <!-- ... -->\r\n "
+ "</ns:root>\r\n " + "```";
//The xsi:schemaLocation and xsi:noNamespaceSchemaLocation attributes can be used in a document
//to provide hints as to the physical location of schema documents which may be used for ·assessment·.
if(inRootElement && !schemaLocationExists && !noNamespaceSchemaLocationExists) {
documentation = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
name = actualPrefix + ":schemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation,
response);

documentation = "The xsi:noNamespaceSchemaLocation attribute can be used in an XML document "
+ "to reference an XML Schema document that does not have a target namespace.\r\n "
+ "```xml \r\n" + "\r\n<root xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ " xsi:noNamespaceSchemaLocation=\"example.xsd\"> \r\n" + " <!-- ... --> \r\n"
+ "</root> \r\n" + "```";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);

documentation = SCHEMA_LOCATION_DOC;
name = actualPrefix + ":noNamespaceSchemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation,
response);
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);
}
}

Expand All @@ -98,4 +122,44 @@ private static void createCompletionItem(String attrName, boolean canSupportSnip
private static boolean attributeAlreadyExists(DOMElement root, String actualPrefix, String suffix) {
return root.getAttributeNode(actualPrefix + ":" + suffix) != null;
}

public static Hover computeHoverResponse(DOMAttr attribute, IHoverRequest request) {

String name = attribute.getName();
if(!name.startsWith(request.getXMLDocument().getSchemaInstancePrefix() + ":")) {
return null;
}

DOMDocument document = request.getXMLDocument();
DOMElement root = document.getDocumentElement();
String doc = null;
if(root != null) {
if(root.equals(document.findNodeAt(attribute.getStart()))) {
if(name.endsWith(":schemaLocation")) {
doc = SCHEMA_LOCATION_DOC;
}
else if(name.endsWith(":noNamespaceSchemaLocation")) {
doc = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
}
}
} else {
return null;
}
if(doc == null) {
if(name.endsWith(":nil")) {
doc = NIL_DOC;
}
else if(name.endsWith(":type")) {
doc = TYPE_DOC;
}
else {
return null;
}
}

MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.MARKDOWN);
content.setValue(doc);
return new Hover(content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ public void xsiCompletionTestAllItems() throws BadLocationException {
}

@Test
public void xsiCompletionDoesNotAppearInNonRootElement() throws BadLocationException {
public void xsiCompletionNonRootElement() throws BadLocationException {
String xml =
"<project\r\n" +
" xmlns=\"http://maven.apache.org/POM/4.0.0\"\r\n" +
Expand All @@ -422,7 +422,20 @@ public void xsiCompletionDoesNotAppearInNonRootElement() throws BadLocationExcep
" <modelVersion xs|></modelVersion>\r\n" +
"</project>";

XMLAssert.testCompletionFor(xml, 0);
XMLAssert.testCompletionFor(xml, 2, c("xsi:nil", "xsi:nil=\"true\""), c("xsi:type", "xsi:type=\"\""));
}

@Test
public void xsiCompletionNonRootElement2() throws BadLocationException {
String xml =
"<project\r\n" +
" xmlns=\"http://maven.apache.org/POM/4.0.0\"\r\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" +
" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\r\n" +
" <modelVersion xsi:nil=\"\" |></modelVersion>\r\n" +
"</project>";

XMLAssert.testCompletionFor(xml, 1, c("xsi:type", "xsi:type=\"\""));
}

@Test
Expand Down
Loading

0 comments on commit e97f12a

Please sign in to comment.