diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java index 9511935f4a..fa6ae3d432 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java @@ -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) { diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java index 7f3fcb7819..dda3fb1485 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java @@ -55,6 +55,7 @@ public class DOMDocument extends DOMNode implements Document { private boolean hasNamespaces; private Map externalSchemaLocation; private String schemaInstancePrefix; + private String schemaPrefix; private boolean hasExternalGrammar; private CancelChecker cancelChecker; @@ -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; } @@ -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()) { @@ -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); } } } @@ -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; @@ -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); } @@ -847,36 +853,47 @@ public Collection 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(); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java new file mode 100644 index 0000000000..2b145047fd --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java @@ -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 dataTypes; + + static { + dataTypes = loadDataTypes(); + } + + public static DataType getDataType(String name) { + return dataTypes.get(name); + } + + public static Collection 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 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 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 getDataTypes() { + return dataTypes; + } + + } + +} diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java index 8764b216d2..c8baaefce4 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java @@ -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 + * + *
    + *
  • xs:element/@type -> xs:complexType/@name
  • * + *
  • xs:extension/@base -> xs:complexType/@name
  • + *
* */ 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); + }); + } } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java index c52bb2ba0c..3b38019578 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java @@ -23,7 +23,7 @@ import org.eclipse.lsp4xml.utils.XMLPositionUtility; /** - * XSD definition which manages teh following definition: + * XSD definition which manages the following definition: * *
    *
  • xs:element/@type -> xs:complexType/@name
  • * @@ -48,7 +48,8 @@ protected void findDefinition(DOMNode node, Position position, int offset, List< DOMAttr attr = node.findAttrAt(offset); if (XSDUtils.isBoundToComplexTypes(attr)) { XSDUtils.collectComplexTypes(attr, true, (targetNamespacePrefix, targetAttr) -> { - LocationLink location = XMLPositionUtility.createLocationLink(attr, targetAttr); + LocationLink location = XMLPositionUtility.createLocationLink(attr.getNodeAttrValue(), + targetAttr.getNodeAttrValue()); locations.add(location); }); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java index 466a8f7a2a..96669fce8a 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java @@ -41,17 +41,25 @@ public static boolean isBoundToComplexTypes(DOMAttr attr) { if (attr == null) { return false; } - if ("type".equals(attr.getName()) && "element".equals(attr.getOwnerElement().getLocalName())) { + if (isXSElementType(attr)) { // - xs:element/@type -> xs:complexType/@name return true; } - if ("base".equals(attr.getName()) && "extension".equals(attr.getOwnerElement().getLocalName())) { + if (isXSExtensionBase(attr)) { // - xs:extension/@base -> xs:complexType/@name return true; } return false; } + public static boolean isXSElementType(DOMAttr attr) { + return "type".equals(attr.getName()) && "element".equals(attr.getOwnerElement().getLocalName()); + } + + public static boolean isXSExtensionBase(DOMAttr attr) { + return "base".equals(attr.getName()) && "extension".equals(attr.getOwnerElement().getLocalName()); + } + /** * Collect complexType/@name attributes declared inside the document of the * given attribute. diff --git a/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml b/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml new file mode 100644 index 0000000000..79a38c58af --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java new file mode 100644 index 0000000000..33c86a43b2 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java @@ -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"); + } +}