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 21, 2019
1 parent 23b2105 commit 5f19a76
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public short getNodeType() {
public DOMAttr getOwnerAttr() {
return ownerAttr;
}

@Override
public DOMDocument getOwnerDocument() {
return ownerAttr.getOwnerDocument();
}
}

public DOMAttr(String name, DOMNode ownerElement) {
Expand Down
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,129 @@
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.eclipse.lsp4xml.utils.StringUtils;
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 String lineSeparator = System.lineSeparator();

public enum DataTypeType {
PRIMITIVE
}

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;

private final DataTypeType type;

private String documentation;

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

public String getName() {
return name;
}

public String getUrl() {
return url;
}

public String getDescription() {
return description;
}

public DataTypeType getType() {
return type;
}

public String getDocumentation() {
if (documentation == null) {
documentation = createDocumentation();
}
return documentation;
}

private String createDocumentation() {
StringBuilder doc = new StringBuilder();
doc.append("**");
doc.append(getName());
doc.append("**");
if (!StringUtils.isEmpty(url)) {
doc.append(lineSeparator);
doc.append("See [documentation](");
doc.append(getUrl());
doc.append(") for more informations.");
}
return doc.toString();
}

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"), DataTypeType.valueOf(attributes.getValue("type")));
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,74 @@
*/
package org.eclipse.lsp4xml.extensions.xsd.participants;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MarkupKind;
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.DataType.DataTypeType;
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();
boolean includePrimitiveType = !XSDUtils.isXSExtensionBase(originAttr);
DataType.getDataTypes().forEach(dataType -> {
if (DataTypeType.PRIMITIVE.equals(dataType.getType()) && !includePrimitiveType) {
return;
}
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.setDocumentation(new MarkupContent(MarkupKind.MARKDOWN, dataType.getDocumentation()));
item.setKind(CompletionItemKind.Value);
response.addCompletionAttribute(item);
});
}
}
}
Loading

0 comments on commit 5f19a76

Please sign in to comment.