Skip to content

Commit

Permalink
Add support for textDocument/completion for xs:element/@name /
Browse files Browse the repository at this point in the history
xs:extension/@base

Fix #451

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jun 20, 2019
1 parent 140a8c7 commit c040498
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class DOMDocument extends DOMNode implements Document {
private boolean hasNamespaces;
private Map<String, String> externalSchemaLocation;
private String schemaInstancePrefix;
private String schemaPrefix;
private boolean hasExternalGrammar;
private CancelChecker cancelChecker;

Expand All @@ -64,11 +65,11 @@ public DOMDocument(TextDocument textDocument, URIResolverExtensionManager resolv
this.resolverExtensionManager = resolverExtensionManager;
resetGrammar();
}

public void setCancelChecker(CancelChecker cancelChecker) {
this.cancelChecker = cancelChecker;
}

public CancelChecker getCancelChecker() {
return cancelChecker;
}
Expand Down Expand Up @@ -246,6 +247,7 @@ private synchronized void initializeReferencedSchema() {
return;
}
schemaInstancePrefix = null;
schemaPrefix = null;
// Search if document element root declares namespace with "xmlns".
if (documentElement.hasAttributes()) {
for (DOMAttr attr : documentElement.getAttributeNodes()) {
Expand All @@ -256,17 +258,21 @@ private synchronized void initializeReferencedSchema() {
hasNamespaces = true;
}
String attributeValue = documentElement.getAttribute(attributeName);
if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/") //$NON-NLS-1$
&& attributeValue.endsWith("/XMLSchema-instance")) //$NON-NLS-1$
{
schemaInstancePrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName); //$NON-NLS-1$ //$NON-NLS-2$
if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/")) {
if (attributeValue.endsWith("/XMLSchema-instance")) {
schemaInstancePrefix = attributeName.equals("xmlns") ? ""
: getUnprefixedName(attributeName);
} else if (attributeValue.endsWith("/XMLSchema")) {
schemaPrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName);
}
}
}
}
if (schemaInstancePrefix != null) {
// DOM document can declared xsi:noNamespaceSchemaLocation and xsi:schemaLocation both even it's not valid
// DOM document can declared xsi:noNamespaceSchemaLocation and
// xsi:schemaLocation both even it's not valid
noNamespaceSchemaLocation = createNoNamespaceSchemaLocation(documentElement, schemaInstancePrefix);
schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix);
schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix);
}
}
}
Expand Down Expand Up @@ -788,21 +794,21 @@ public boolean isDTD() {
}

/**
* Returns true if 'offset' is within an internal DOCTYPE dtd.
* Else false.
* Returns true if 'offset' is within an internal DOCTYPE dtd. Else false.
*
* @param offset
* @return
* @return
*/
public boolean isWithinInternalDTD(int offset) {
DOMDocumentType doctype = this.getDoctype();
if(doctype != null && doctype.internalSubset != null) {
if (doctype != null && doctype.internalSubset != null) {
return offset > doctype.internalSubset.start && offset < doctype.internalSubset.end;
}
return false;
}

public Range getTrimmedRange(Range range) {
if(range != null) {
if (range != null) {
return getTrimmedRange(range.getStart().getCharacter(), range.getEnd().getCharacter());
}
return null;
Expand All @@ -812,16 +818,16 @@ public Range getTrimmedRange(Range range) {
public Range getTrimmedRange(int start, int end) {
String text = getText();
char c = text.charAt(start);
while(Character.isWhitespace(c)) {
while (Character.isWhitespace(c)) {
start++;
c = text.charAt(start);
}
if(start == end) {
if (start == end) {
return null;
}
end--;
c = text.charAt(end);
while(Character.isWhitespace(c)) {
while (Character.isWhitespace(c)) {
end--;
c = text.charAt(end);
}
Expand All @@ -847,36 +853,47 @@ public Collection<DOMNode> findDTDAttrList(String elementName) {
}

/**
* Given a schema URI, this will return true if the given schemaURI
* matches the one defined in this DOMDocument(xml document).
* Given a schema URI, this will return true if the given schemaURI matches the
* one defined in this DOMDocument(xml document).
*
* It will check either xsi:schemaLocation or xsi:noNamespaceSchemaLocation.
*/
public boolean usesSchema(String xsdURI) {
String rootURI = URI.create(textDocument.getUri()).getPath(); //remove "file://" if exists
if(rootURI == null || xsdURI == null) {
String rootURI = URI.create(textDocument.getUri()).getPath(); // remove "file://" if exists
if (rootURI == null || xsdURI == null) {
return false;
}

Path rootPath = Paths.get(rootURI).getParent();
Path rootPath = Paths.get(rootURI).getParent();
xsdURI = URI.create(xsdURI).getPath();
Path xsdPath = Paths.get(xsdURI);

if(schemaLocation != null) {
return schemaLocation.usesSchema(rootPath ,xsdPath);
}
else if (noNamespaceSchemaLocation != null) {
if (schemaLocation != null) {
return schemaLocation.usesSchema(rootPath, xsdPath);
} else if (noNamespaceSchemaLocation != null) {
String noNamespaceURI = URI.create(noNamespaceSchemaLocation.getLocation()).getPath();
Path noNamespacePath = Paths.get(noNamespaceURI).normalize();

if(!noNamespacePath.isAbsolute()) {
if (!noNamespacePath.isAbsolute()) {
noNamespacePath = rootPath.resolve(noNamespacePath);
}
return xsdPath.equals(noNamespacePath);
}
return false;
}


/**
* Returns the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*
* @return the XML Schema prefix (ex : 'xs' for
* xmlns:xs="http://www.w3.org/2001/XMLSchema")
*/
public String getSchemaPrefix() {
initializeReferencedSchemaIfNeeded();
return schemaPrefix;
}

private void checkCanceled() {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.eclipse.lsp4xml.extensions.xsd;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class DataType {

private static final Map<String, DataType> dataTypes;

static {
dataTypes = loadDataTypes();
}

public static DataType getDataType(String name) {
return dataTypes.get(name);
}

public static Collection<DataType> getDataTypes() {
return dataTypes.values();
}

private final String name;

private final String url;

private final String description;

public DataType(String name, String url, String description) {
this.name = name;
this.url = url;
this.description = description;
}

public String getName() {
return name;
}

public String getUrl() {
return url;
}

public String getDescription() {
return description;
}

private static Map<String, DataType> loadDataTypes() {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
DataTypeHandler handler = new DataTypeHandler();
saxParser.parse(new InputSource(DataType.class.getResourceAsStream("/schemas/xsd/datatypes.xml")), handler);
return handler.getDataTypes();
} catch (Exception e) {
return null;
}
}

private static class DataTypeHandler extends DefaultHandler {

private final Map<String, DataType> dataTypes;

public DataTypeHandler() {
dataTypes = new HashMap<>();
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if ("datatype".contentEquals(qName)) {
DataType dataType = new DataType(attributes.getValue("name"), attributes.getValue("url"),
attributes.getValue("description"));
dataTypes.put(dataType.getName(), dataType);
}
super.startElement(uri, localName, qName, attributes);
}

public Map<String, DataType> getDataTypes() {
return dataTypes;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,67 @@
*/
package org.eclipse.lsp4xml.extensions.xsd.participants;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.xsd.DataType;
import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils;
import org.eclipse.lsp4xml.services.extensions.CompletionParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.settings.SharedSettings;
import org.eclipse.lsp4xml.utils.DOMUtils;

/**
* XSD completion for xs:
* XSD completion for
*
* <ul>
* <li>xs:element/@type -> xs:complexType/@name</li> *
* <li>xs:extension/@base -> xs:complexType/@name</li>
* </ul>
*
*/
public class XSDCompletionParticipant extends CompletionParticipantAdapter {

@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request,
ICompletionResponse response, SharedSettings settings) throws Exception {
// TODO: manage compeltion for types declared in XML Schema xsd
DOMNode node = request.getNode();
DOMDocument document = node.getOwnerDocument();
if (!DOMUtils.isXSD(document)) {
return;
}
DOMAttr originAttr = node.findAttrAt(request.getOffset());
if (XSDUtils.isBoundToComplexTypes(originAttr)) {
XSDUtils.collectComplexTypes(originAttr, false, (targetNamespacePrefix, targetAttr) -> {
CompletionItem item = new CompletionItem();
StringBuilder label = new StringBuilder();
if (targetNamespacePrefix != null) {
label.append(targetNamespacePrefix);
label.append(":");
}
label.append(targetAttr.getValue());
item.setLabel(label.toString());
item.setKind(CompletionItemKind.Value);
response.addCompletionAttribute(item);
});
String prefix = document.getSchemaPrefix();
DataType.getDataTypes().forEach(dataType -> {

CompletionItem item = new CompletionItem();
StringBuilder label = new StringBuilder();
if (prefix != null) {
label.append(prefix);
label.append(":");
}
label.append(dataType.getName());
item.setLabel(label.toString());
item.setKind(CompletionItemKind.Value);
response.addCompletionAttribute(item);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.eclipse.lsp4xml.utils.XMLPositionUtility;

/**
* XSD definition which manages teh following definition:
* XSD definition which manages the following definition:
*
* <ul>
* <li>xs:element/@type -> xs:complexType/@name</li> *
Expand Down
17 changes: 17 additions & 0 deletions org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<datatypes>
<!-- See all built-in datatypes at https://www.w3.org/TR/xmlschema11-2/#built-in-datatypes -->

<!-- PRIMITIVE DATATYPES -> https://www.w3.org/TR/xmlschema11-2/#built-in-primitive-datatypes -->
<datatype name="string" url="https://www.w3.org/TR/xmlschema11-2/#string" />
<datatype name="boolean" url="https://www.w3.org/TR/xmlschema11-2/#boolean" />
<datatype name="decimal" url="https://www.w3.org/TR/xmlschema11-2/#decimal" />
<datatype name="float" url="https://www.w3.org/TR/xmlschema11-2/#float" />
<datatype name="double" url="https://www.w3.org/TR/xmlschema11-2/#double" />
<datatype name="duration" url="https://www.w3.org/TR/xmlschema11-2/#duration" />
<datatype name="dateTime" url="https://www.w3.org/TR/xmlschema11-2/#dateTime" />
<datatype name="time" url="https://www.w3.org/TR/xmlschema11-2/#time" />
<datatype name="date" url="https://www.w3.org/TR/xmlschema11-2/#date" />
<datatype name="gYearMonth" url="https://www.w3.org/TR/xmlschema11-2/#gYearMonth" />
<datatype name="gYear" url="https://www.w3.org/TR/xmlschema11-2/#gYear" />

</datatypes>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.eclipse.lsp4xml.extensions.xsd;

import org.junit.Assert;
import org.junit.Test;

public class DataTypeTest {

@Test
public void xsString() {
DataType string = DataType.getDataType("string");
Assert.assertNotNull(string);
Assert.assertEquals(string.getName(), "string");
}
}

0 comments on commit c040498

Please sign in to comment.