Skip to content

Commit

Permalink
XSI attributes are provided as completion items
Browse files Browse the repository at this point in the history
Fixes eclipse#163

Signed-off-by: Nikolas Komonen <[email protected]>
  • Loading branch information
NikolasKomonen committed Nov 13, 2018
1 parent 0b15ba1 commit 9be967e
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ public boolean hasNoNamespaceSchemaLocation() {
}

/**
* Returns true id document defines namespaces (with xmlns) and false otherwise.
* Returns true if document defines namespaces (with xmlns) and false otherwise.
*
* @return true id document defines namespaces (with xmlns) and false otherwise.
* @return true if document defines namespaces (with xmlns) and false otherwise.
*/
public boolean hasNamespaces() {
initializeReferencedSchemaIfNeeded();
Expand All @@ -187,6 +187,16 @@ public String getSchemaInstancePrefix() {
return schemaInstancePrefix;
}

/**
* Returns true if (xsi) schema instance prefix exists.
*
* @return true if (xsi) schema instance prefix exists.
*/
public boolean hasSchemaInstancePrefix() {
initializeReferencedSchemaIfNeeded();
return schemaInstancePrefix != null;
}

/**
* Initialize schemaLocation, noNamespaceSchemaLocation and hasNamespaces
* information if needed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.Element;
import org.eclipse.lsp4xml.dom.XMLDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lsp4xml.extensions.contentmodel.utils.XMLGenerator;
import org.eclipse.lsp4xml.services.XSISchemaModel;
import org.eclipse.lsp4xml.services.extensions.CompletionParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
Expand Down Expand Up @@ -103,8 +105,9 @@ private void fillWithChildrenElementDeclaration(Element element, Collection<CMEl
@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
// TODO: manage xsi: completions

if(request.getXMLDocument().hasSchemaInstancePrefix()) {
computeXSIAttributes(fullRange, request, response);
}
// otherwise, manage completion based on XML Schema, DTD.
Element parentElement = request.getNode().isElement() ? (Element) request.getNode() : null;
if (parentElement == null) {
Expand Down Expand Up @@ -187,4 +190,20 @@ public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuo
// XML Schema, DTD is loading, ignore this error
}
}

/**
* Creates and sets (xsi) completion items if needed.
* @param editRange
* @param request
* @param response
* @throws BadLocationException
*/
private void computeXSIAttributes(Range editRange, ICompletionRequest request, ICompletionResponse response) throws BadLocationException {
XMLDocument document = request.getXMLDocument();
Element rootElement = document.getDocumentElement();
int offset = document.offsetAt(editRange.getStart());
if(rootElement.equals(document.findNodeAt(offset))) {
XSISchemaModel.computeCompletionResponses(request, response, editRange, document);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2018 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*/
package org.eclipse.lsp4xml.services;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4xml.dom.Element;
import org.eclipse.lsp4xml.dom.XMLDocument;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;

/**
* 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, Range editRange, XMLDocument document) {
String snippet = "";
boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported();
if(request.getCompletionSettings().isCompletionSnippetsSupported()) {
snippet = "$0";
}
String actualPrefix = document.getSchemaInstancePrefix();
String name;
String documentation;
Element 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")) {
name = actualPrefix + ":nil";
documentation = "Indicates that no values are allowed inside an XML element";
String nilSnippet = "true" + snippet;
createCompletionItem(name, isSnippetsSupported, nilSnippet, editRange, response, documentation);
}
//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 = "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.";
name = actualPrefix + ":type";
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation);
}
//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 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·.";
name = actualPrefix + ":schemaLocation";
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation);

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

private static void createCompletionItem(String text, boolean isSnippetsSupported,
String defaultAndSnippet, Range editRange, ICompletionResponse response, String documentation) {
CompletionItem item = new CompletionItem();
item.setLabel(text);
item.setKind(CompletionItemKind.Value);
item.setFilterText(text);
item.setInsertTextFormat(isSnippetsSupported ? InsertTextFormat.Snippet : InsertTextFormat.PlainText);
item.setTextEdit(new TextEdit(editRange, text + "=\"" + defaultAndSnippet + "\""));
item.setDocumentation(documentation);
response.addCompletionItem(item);
}

private static boolean attributeAlreadyExists(Element root, String actualPrefix, String suffix) {
return root.getAttributeNode(actualPrefix + ":" + suffix) != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ public void completionWithXMLSchemaContentChanged() throws Exception {
+ " <xs:attribute name=\"variant\" type=\"xs:string\" use=\"required\"/>\r\n"
+ " </xs:complexType>\r\n" + " </xs:element>\r\n" + "</xs:schema>";
Files.write(Paths.get("target/xsd/resources.xsd"), schema.getBytes());
XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/resources.xml", 1, false,
XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/resources.xml", 3, false,
c("variant", "variant=\"\""));

// Update resources.xsd, Schema doesn't define variant attribute -> no
Expand All @@ -366,7 +366,7 @@ public void completionWithXMLSchemaContentChanged() throws Exception {
// + " <xs:attribute name=\"variant\" type=\"xs:string\" use=\"required\"/>\r\n"
+ " </xs:complexType>\r\n" + " </xs:element>\r\n" + "</xs:schema>";
Files.write(Paths.get("target/xsd/resources.xsd"), schema.getBytes());
XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/resources.xml", 0, false);
XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/resources.xml", 2, false);

}

Expand Down Expand Up @@ -401,6 +401,81 @@ public void issue214() throws BadLocationException {
c("ComplexType", "<ComplexType Name=\"\"></ComplexType>"));
}

@Test
public void xsiCompletionTestAllItems() 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" +
" |>\r\n" +
"</project>";
XMLAssert.testCompletionFor(xml, 4, c("xsi:nil", "xsi:nil=\"true\""), c("xsi:type", "xsi:type=\"\""), c("xsi:noNamespaceSchemaLocation", "xsi:noNamespaceSchemaLocation=\"\""), c("xsi:schemaLocation", "xsi:schemaLocation=\"\""));
}

@Test
public void xsiCompletionDoesNotAppearInNonRootElement() 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 xs|></modelVersion>\r\n" +
"</project>";

XMLAssert.testCompletionFor(xml, 0);
}

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

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

@Test
public void xsiCompletionDoesntAppearSinceDoesntExist() throws BadLocationException {
String xml =
"<project\r\n" +
" xmlns=\"http://maven.apache.org/POM/4.0.0\"\r\n" +
" xsi:|>\r\n" +
" <modelVersion></modelVersion>\r\n" +
"</project>";

XMLAssert.testCompletionFor(xml, 0);
}

@Test
public void xsiCompletionSchemaLocationExists() 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></modelVersion>\r\n" +
"</project>";

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

@Test
public void xsiCompletionNoNamespaceSchemaLocationExists() 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:noNamespaceSchemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" |>\r\n" +
" <modelVersion></modelVersion>\r\n" +
"</project>";

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

private void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException {
XMLAssert.testCompletionFor(xml, "src/test/resources/catalogs/catalog.xml", expectedItems);
}
Expand Down

0 comments on commit 9be967e

Please sign in to comment.