From e9f3317d29bd7dffdd082746cc7701f7d2641452 Mon Sep 17 00:00:00 2001 From: azerr Date: Mon, 19 Aug 2019 17:25:07 +0200 Subject: [PATCH] Support completion with xs:any See https://github.com/redhat-developer/vscode-xml/issues/177 Signed-off-by: azerr --- .../org/eclipse/lsp4xml/dom/DOMElement.java | 83 +++++----- .../contentmodel/model/CMDocument.java | 25 +++ .../model/CMElementDeclaration.java | 25 ++- .../ContentModelCompletionParticipant.java | 143 +++++++++++++++--- .../contentmodel/CMXSDElementDeclaration.java | 73 ++++++++- .../eclipse/lsp4xml/dom/DOMDocumentTest.java | 42 +++++ .../XMLSchemaCompletionExtensionsTest.java | 112 +++++++++++++- 7 files changed, 418 insertions(+), 85 deletions(-) diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMElement.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMElement.java index 46d529392..947b9b718 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMElement.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMElement.java @@ -29,18 +29,16 @@ */ public class DOMElement extends DOMNode implements org.w3c.dom.Element { - - String tag; boolean selfClosed; - - //DomElement.start == startTagOpenOffset + + // DomElement.start == startTagOpenOffset int startTagOpenOffset = NULL_VALUE; // | int startTagCloseOffset = NULL_VALUE; // int endTagOpenOffset = NULL_VALUE; // | int endTagCloseOffset = NULL_VALUE;// - //DomElement.end = | , is always scanner.getTokenEnd() + // DomElement.end = | , is always scanner.getTokenEnd() public DOMElement(int start, int end) { super(start, end); @@ -121,21 +119,17 @@ public String getPrefix() { @Override public String getNamespaceURI() { String prefix = getPrefix(); - boolean hasPrefix = !StringUtils.isEmpty(prefix); - // Try to get xmlns attribute in the element - String rootElementNamespaceDeclarationName = (hasPrefix) ? XMLNS_NO_DEFAULT_ATTR + prefix // $NON-NLS-1$ - : XMLNS_ATTR; // $NON-NLS-1$ - String rootElementNamespace = this.getAttribute(rootElementNamespaceDeclarationName); - if (!StringUtils.isEmpty(rootElementNamespace)) { - return rootElementNamespace; + // Try to get xmlns attribute from the element + String namespaceURI = getNamespaceURI(prefix, this); + if (namespaceURI != null) { + return namespaceURI; } - // try to get the namespace in the parent element + // try to get the namespace from the parent element DOMNode parent = getParentNode(); while (parent != null) { if (parent.getNodeType() == DOMNode.ELEMENT_NODE) { DOMElement parentElement = ((DOMElement) parent); - String namespaceURI = hasPrefix ? parentElement.getAttribute(XMLNS_NO_DEFAULT_ATTR + prefix) - : parentElement.getNamespaceURI(); + namespaceURI = getNamespaceURI(prefix, parentElement); if (namespaceURI != null) { return namespaceURI; } @@ -145,6 +139,20 @@ public String getNamespaceURI() { return null; } + /** + * Returns the namespace URI from the given prefix declared in the given element + * and null otherwise. + * + * @param prefix the prefix + * @param element the DOM element + * @return the namespace URI from the given prefix declared in the given element + * and null otherwise. + */ + public static String getNamespaceURI(String prefix, DOMElement element) { + boolean hasPrefix = !StringUtils.isEmpty(prefix); + return hasPrefix ? element.getAttribute(XMLNS_NO_DEFAULT_ATTR + prefix) : element.getAttribute(XMLNS_ATTR); + } + public Collection getAllPrefixes() { if (hasAttributes()) { Collection prefixes = new ArrayList<>(); @@ -171,7 +179,7 @@ public String getPrefix(String namespaceURI) { if (hasAttributes()) { for (DOMAttr attr : getAttributeNodes()) { String prefix = attr.getPrefixIfMatchesURI(namespaceURI); - if(prefix != null) { + if (prefix != null) { return prefix; } } @@ -191,13 +199,6 @@ public String getPrefix(String namespaceURI) { return null; } - public String getNamespaceURI(String prefix) { - if (prefix == null || prefix.isEmpty()) { - return getNamespaceURI(); - } - return getAttribute(XMLNS_NO_DEFAULT_ATTR + prefix); - } - public boolean isDocumentElement() { return this.equals(getOwnerDocument().getDocumentElement()); } @@ -206,31 +207,29 @@ public boolean isSelfClosed() { return selfClosed; } - /** - * Will traverse backwards from the start offset - * returning an offset of the given character if it's found - * before another character. Whitespace is ignored. + * Will traverse backwards from the start offset returning an offset of the + * given character if it's found before another character. Whitespace is + * ignored. * * Returns null if the character is not found. * - * The initial value for the start offset is not included. - * So have the offset 1 position after the character you want - * to start at. + * The initial value for the start offset is not included. So have the offset 1 + * position after the character you want to start at. */ public Integer endsWith(char c, int startOffset) { String text = this.getOwnerDocument().getText(); - if(startOffset > text.length() || startOffset < 0) { + if (startOffset > text.length() || startOffset < 0) { return null; } startOffset--; - while(startOffset >= 0) { + while (startOffset >= 0) { char current = text.charAt(startOffset); - if(Character.isWhitespace(current)) { + if (Character.isWhitespace(current)) { startOffset--; continue; } - if(current != c) { + if (current != c) { return null; } return startOffset; @@ -240,17 +239,17 @@ public Integer endsWith(char c, int startOffset) { public Integer isNextChar(char c, int startOffset) { String text = this.getOwnerDocument().getText(); - if(startOffset > text.length() || startOffset < 0) { + if (startOffset > text.length() || startOffset < 0) { return null; } - - while(startOffset < text.length()) { + + while (startOffset < text.length()) { char current = text.charAt(startOffset); - if(Character.isWhitespace(current)) { + if (Character.isWhitespace(current)) { startOffset++; continue; } - if(current != c) { + if (current != c) { return null; } return startOffset; @@ -258,7 +257,6 @@ public Integer isNextChar(char c, int startOffset) { return null; } - /** * Returns true if the given tag is the same tag of this element and false * otherwise. @@ -328,7 +326,6 @@ public int getEndTagOpenOffset() { return endTagOpenOffset; } - /** * Returns the end tag close offset and {@link DOMNode#NULL_VALUE} if it doesn't * exist. @@ -377,9 +374,9 @@ public boolean isStartTagClosed() { public boolean isEndTagClosed() { return getEndTagCloseOffset() != NULL_VALUE; } - + /** - * If Element has a closing end tag eg: -> true , -> false + * If Element has a closing end tag eg: -> true , -> false */ @Override public boolean isClosed() { diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMDocument.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMDocument.java index bf1c50217..1cd616511 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMDocument.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMDocument.java @@ -11,6 +11,8 @@ package org.eclipse.lsp4xml.extensions.contentmodel.model; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4xml.dom.DOMElement; @@ -70,4 +72,27 @@ public interface CMDocument { * @return true if the content model document is dirty and false otherwise. */ boolean isDirty(); + + /** + * Returns the all elements declaration of the model. + * + * @return the all elements declaration of the model. + */ + default Collection getAllElements() { + Set all = new HashSet<>(); + for (CMElementDeclaration element : this.getElements()) { + fillElements(element, all); + } + return all; + } + + default void fillElements(CMElementDeclaration element, Set all) { + if (!all.add(element)) { + return; + } + for (CMElementDeclaration child : element.getElements()) { + fillElements(child, all); + } + } + } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMElementDeclaration.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMElementDeclaration.java index a38cd6176..c120e8c38 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMElementDeclaration.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/model/CMElementDeclaration.java @@ -10,7 +10,9 @@ */ package org.eclipse.lsp4xml.extensions.contentmodel.model; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import org.eclipse.lsp4xml.dom.DOMElement; @@ -20,6 +22,9 @@ */ public interface CMElementDeclaration { + public static final Collection ANY_ELEMENT_DECLARATIONS = Collections + .unmodifiableCollection(Arrays.asList()); + /** * Returns the declared element name. * @@ -62,11 +67,13 @@ default String getName(String prefix) { Collection getElements(); /** - * Returns the possible declared elements for the given DOM after element. + * Returns the possible declared elements at the given offset of the given + * parent element. * - * @param afterElement the after element - * @param i - * @return the possible declared elements for the given DOM after element. + * @param parentElement the parent element + * @param offset the offset + * @return the possible declared elements at the given offset of the given + * parent element. */ Collection getPossibleElements(DOMElement parentElement, int offset); @@ -105,8 +112,18 @@ default String getName(String prefix) { */ boolean isEmpty(); + /** + * Return the enumeration values. + * + * @return the enumeration values. + */ Collection getEnumerationValues(); + /** + * Returns the owner document URI where the element is declared. + * + * @return the owner document URI where the element is declared. + */ String getDocumentURI(); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index 2f907e250..49ff66d9e 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -11,6 +11,8 @@ package org.eclipse.lsp4xml.extensions.contentmodel.participants; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; @@ -32,6 +34,9 @@ import org.eclipse.lsp4xml.services.extensions.ICompletionResponse; import org.eclipse.lsp4xml.settings.XMLFormattingOptions; import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * Extension to support XML completion based on content model (XML Schema @@ -51,7 +56,8 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) // XML Schema is done with pattern and not with XML root element) CMDocument cmDocument = contentModelManager.findCMDocument(document, null); if (cmDocument != null) { - fillWithChildrenElementDeclaration(null, cmDocument.getElements(), null, false, request, response); + fillWithChildrenElementDeclaration(null, null, cmDocument.getElements(), null, false, request, + response); } return; } @@ -65,8 +71,7 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) if (cmElement != null) { defaultPrefix = parentElement.getPrefix(); - fillWithChildrenElementDeclaration(parentElement, - cmElement.getPossibleElements(parentElement, request.getOffset()), defaultPrefix, false, + fillWithPossibleElementDeclaration(parentElement, cmElement, defaultPrefix, contentModelManager, request, response); } if (parentElement.isDocumentElement()) { @@ -76,14 +81,14 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) if (defaultPrefix != null && prefix.equals(defaultPrefix)) { continue; } - String namespaceURI = parentElement.getNamespaceURI(prefix); + String namespaceURI = DOMElement.getNamespaceURI(prefix, parentElement); if (cmRootDocument == null || !cmRootDocument.hasNamespace(namespaceURI)) { // The model document root doesn't define the namespace, try to load the // external model document (XML Schema, DTD) CMDocument cmDocument = contentModelManager.findCMDocument(parentElement, namespaceURI); if (cmDocument != null) { - fillWithChildrenElementDeclaration(parentElement, cmDocument.getElements(), prefix, true, - request, response); + fillWithChildrenElementDeclaration(parentElement, null, cmDocument.getElements(), prefix, + true, request, response); } } } @@ -93,8 +98,7 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) CMElementDeclaration cmInternalElement = contentModelManager.findInternalCMElement(parentElement); if (cmInternalElement != null) { defaultPrefix = parentElement.getPrefix(); - fillWithChildrenElementDeclaration(parentElement, - cmInternalElement.getPossibleElements(parentElement, request.getOffset()), defaultPrefix, false, + fillWithPossibleElementDeclaration(parentElement, cmInternalElement, defaultPrefix, contentModelManager, request, response); } } catch (CacheResourceDownloadingException e) { @@ -102,25 +106,118 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) } } - private void fillWithChildrenElementDeclaration(DOMElement element, Collection cmElements, - String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response) - throws BadLocationException { + /** + * Fill with possible element declarations. + * + * @param parentElement the parent DOM element + * @param cmElement the content model element declaration + * @param defaultPrefix + * @param contentModelManager + * @param request + * @param response + * @throws BadLocationException + */ + private static void fillWithPossibleElementDeclaration(DOMElement parentElement, CMElementDeclaration cmElement, + String defaultPrefix, ContentModelManager contentModelManager, ICompletionRequest request, + ICompletionResponse response) throws BadLocationException { + // Get possible elements + Collection possibleElements = cmElement.getPossibleElements(parentElement, + request.getOffset()); + boolean isAny = CMElementDeclaration.ANY_ELEMENT_DECLARATIONS.equals(possibleElements); + CMDocument cmDocument = null; + if (isAny) { + // It's a xs:any, get the XML Schema/DTD document to retrieve the all elements + // declarations + cmDocument = contentModelManager.findCMDocument(parentElement.getOwnerDocument(), + parentElement.getNamespaceURI()); + } + fillWithChildrenElementDeclaration(parentElement, cmDocument, possibleElements, defaultPrefix, false, request, + response); + } + + /** + * Fill with children element declarations + * + * @param element + * @param document + * @param cmElements + * @param defaultPrefix + * @param forceUseOfPrefix + * @param request + * @param response + * @throws BadLocationException + */ + private static void fillWithChildrenElementDeclaration(DOMElement element, CMDocument document, + Collection cmElements, String defaultPrefix, boolean forceUseOfPrefix, + ICompletionRequest request, ICompletionResponse response) throws BadLocationException { XMLGenerator generator = request.getXMLGenerator(); - for (CMElementDeclaration child : cmElements) { - String prefix = forceUseOfPrefix ? p : (element != null ? element.getPrefix(child.getNamespace()) : null); - String label = child.getName(prefix); - CompletionItem item = new CompletionItem(label); - item.setFilterText(request.getFilterForStartTagName(label)); - item.setKind(CompletionItemKind.Property); - MarkupContent documentation = XMLGenerator.createMarkupContent(child, request); - item.setDocumentation(documentation); - String xml = generator.generate(child, prefix); - item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); - item.setInsertTextFormat(InsertTextFormat.Snippet); - response.addCompletionItem(item, true); + if (document != null) { + // xs:any case + Set tags = new HashSet<>(); + // Fill with all CM element declarations + for (CMElementDeclaration child : document.getAllElements()) { + CompletionItem item = addCompletionItem(child, element, defaultPrefix, forceUseOfPrefix, request, + response, generator); + tags.add(item.getLabel()); + } + // Fill with all tags elements from the document + Document document2 = element.getOwnerDocument(); + NodeList list = document2.getChildNodes(); + addTagName(list, tags, request, response); + } else { + for (CMElementDeclaration child : cmElements) { + addCompletionItem(child, element, defaultPrefix, forceUseOfPrefix, request, response, generator); + } + } + } + + /** + * Add completion item with all tag names of the node list. + * + * @param list + * @param tags + * @param request + * @param response + */ + private static void addTagName(NodeList list, Set tags, ICompletionRequest request, + ICompletionResponse response) { + for (int i = 0; i < list.getLength(); i++) { + Node node = list.item(i); + if (Node.ELEMENT_NODE == node.getNodeType()) { + DOMElement elt = (DOMElement) node; + String tagName = elt.getTagName(); + if (!tags.contains(tagName)) { + CompletionItem item = new CompletionItem(tagName); + item.setKind(CompletionItemKind.Property); + item.setFilterText(request.getFilterForStartTagName(tagName)); + String xml = elt.getOwnerDocument().getText().substring(elt.getStart(), elt.getEnd()); + item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); + response.addCompletionItem(item); + tags.add(item.getLabel()); + } + addTagName(elt.getChildNodes(), tags, request, response); + } } } + private static CompletionItem addCompletionItem(CMElementDeclaration elementDeclaration, DOMElement element, + String defaultPrefix, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response, + XMLGenerator generator) { + String prefix = forceUseOfPrefix ? defaultPrefix + : (element != null ? element.getPrefix(elementDeclaration.getNamespace()) : null); + String label = elementDeclaration.getName(prefix); + CompletionItem item = new CompletionItem(label); + item.setFilterText(request.getFilterForStartTagName(label)); + item.setKind(CompletionItemKind.Property); + MarkupContent documentation = XMLGenerator.createMarkupContent(elementDeclaration, request); + item.setDocumentation(documentation); + String xml = generator.generate(elementDeclaration, prefix); + item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); + item.setInsertTextFormat(InsertTextFormat.Snippet); + response.addCompletionItem(item, true); + return item; + } + @Override public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) throws Exception { diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java index d6c0de117..85268dfe3 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/contentmodel/CMXSDElementDeclaration.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Vector; @@ -20,7 +21,6 @@ import org.apache.xerces.impl.xs.SchemaGrammar; import org.apache.xerces.impl.xs.SubstitutionGroupHandler; import org.apache.xerces.impl.xs.XSComplexTypeDecl; -import org.apache.xerces.impl.xs.XSElementDecl; import org.apache.xerces.impl.xs.models.CMBuilder; import org.apache.xerces.impl.xs.models.CMNodeFactory; import org.apache.xerces.impl.xs.models.XSCMValidator; @@ -30,13 +30,13 @@ import org.apache.xerces.xs.XSConstants; import org.apache.xerces.xs.XSElementDeclaration; import org.apache.xerces.xs.XSModelGroup; -import org.apache.xerces.xs.XSNamespaceItem; import org.apache.xerces.xs.XSObject; import org.apache.xerces.xs.XSObjectList; import org.apache.xerces.xs.XSParticle; import org.apache.xerces.xs.XSSimpleTypeDefinition; import org.apache.xerces.xs.XSTerm; import org.apache.xerces.xs.XSTypeDefinition; +import org.apache.xerces.xs.XSWildcard; import org.eclipse.lsp4xml.dom.DOMElement; import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration; import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration; @@ -51,6 +51,8 @@ */ public class CMXSDElementDeclaration implements CMElementDeclaration { + private static final short PC_UNKWOWN = -1; + private final CMXSDDocument document; private final XSElementDeclaration elementDeclaration; @@ -108,7 +110,6 @@ private void collectAttributesDeclaration(XSComplexTypeDefinition typeDefinition XSAttributeUse attributeUse = (XSAttributeUse) object; attributes.add(new CMXSDAttributeDeclaration(attributeUse)); } - } } } @@ -129,14 +130,16 @@ public Collection getPossibleElements(DOMElement parentEle // The type definition is complex (ex: xs:all; xs:sequence), returns list of // element declaration according those XML Schema constraints - // Compute list of child element (QName) - List qNames = toQNames(parentElement, offset); - // Initialize Xerces validator CMBuilder cmBuilder = new CMBuilder(new CMNodeFactory()); XSCMValidator validator = ((XSComplexTypeDecl) typeDefinition).getContentModel(cmBuilder); - SubstitutionGroupHandler handler = new SubstitutionGroupHandler(document); + if (validator == null) { + return Collections.emptyList(); + } + SubstitutionGroupHandler handler = new SubstitutionGroupHandler(document); + // Compute list of child element (QName) + List qNames = toQNames(parentElement, offset); // Loop for each element (QName) and check if it is valid according the XML // Schema constraint int[] states = validator.startContentModel(); @@ -156,11 +159,20 @@ public Collection getPossibleElements(DOMElement parentEle } // Compute list of possible elements - Collection possibleElements = new ArrayList<>(); + Collection possibleElements = new HashSet<>(); for (Object object : result) { if (object instanceof XSElementDeclaration) { XSElementDeclaration elementDecl = (XSElementDeclaration) object; document.collectElement(elementDecl, possibleElements); + } else { + // case with xs:any. Ex: + // + // + // + Collection anyElements = getXSAnyElements(object); + if (anyElements != null) { + return anyElements; + } } } return possibleElements; @@ -168,6 +180,51 @@ public Collection getPossibleElements(DOMElement parentEle return getElements(); } + /** + * Returns the possible elements declaration if the given declaration is an + * xs:any and null otherwise. + * + * @param declaration the element, wildcard declaration. + * @return the possible elements declaration if the given declaration is an + * xs:any and null otherwise. + * + */ + private Collection getXSAnyElements(Object declaration) { + short processContents = getXSAnyProcessContents(declaration); + if (processContents != PC_UNKWOWN) { + // xs:any + switch (processContents) { + case XSWildcard.PC_STRICT: + // + // only global element declaration from the XML Schema are allowed + return document.getElements(); + default: + // or + // all tags are allowed. + return ANY_ELEMENT_DECLARATIONS; + } + } + return null; + } + + /** + * Returns the value of the xs:any/@processContents if the given element is a + * xs:any and {@link #PC_UNKWOWN} otherwise. + * + * @param declaration the element, wildcard declaration. + * @return the value of the xs:any/@processContents if the given element is a + * xs:any and {@link #PC_UNKWOWN} otherwise. + */ + private static short getXSAnyProcessContents(Object declaration) { + if (declaration instanceof XSWildcard) { + XSWildcard wildcard = (XSWildcard) declaration; + if ((wildcard.getConstraintType() == XSWildcard.NSCONSTRAINT_ANY)) { + return wildcard.getProcessContents(); + } + } + return PC_UNKWOWN; + } + /** * Returns list of element (QName) of child elements of the given parent element * upon the given offset diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/DOMDocumentTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/DOMDocumentTest.java index d17a53e36..a912e1f52 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/DOMDocumentTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/DOMDocumentTest.java @@ -194,4 +194,46 @@ public void testDOMAsDTD() { DOMNode modElemmodDecl = modDocType.getChild(0); Assert.assertTrue(modElemmodDecl.isDTDElementDecl()); } + + @Test + public void defaultNamespaceURI() { + String xml = "" + ""; + DOMDocument dom = DOMParser.getInstance().parse(xml, "test.xml", null); + + DOMElement bean = (DOMElement) dom.getDocumentElement().getFirstChild(); + Assert.assertNull(bean.getPrefix()); + Assert.assertEquals("http://www.springframework.org/schema/beans", bean.getNamespaceURI()); + + DOMElement camel = (DOMElement) bean.getNextSibling(); + Assert.assertEquals("camel", camel.getPrefix()); + Assert.assertEquals("http://camel.apache.org/schema/spring", camel.getNamespaceURI()); + + } + + @Test + public void noDefaultNamespaceURI() { + String xml = "" + ""; + DOMDocument dom = DOMParser.getInstance().parse(xml, "test.xml", null); + + DOMElement bean = (DOMElement) dom.getDocumentElement().getFirstChild(); + Assert.assertNull(bean.getPrefix()); + Assert.assertNull(bean.getNamespaceURI()); + + DOMElement camel = (DOMElement) bean.getNextSibling(); + Assert.assertEquals("camel", camel.getPrefix()); + Assert.assertEquals("http://camel.apache.org/schema/spring", camel.getNamespaceURI()); + + } } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java index 13d500c24..cf4fda794 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java @@ -13,6 +13,7 @@ import static org.eclipse.lsp4xml.XMLAssert.c; import static org.eclipse.lsp4xml.XMLAssert.te; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -206,8 +207,8 @@ public void noNamespaceSchemaLocationCompletion() throws BadLocationException { " \r\n" + // " <|"; // Completion only with Name - XMLAssert.testCompletionFor(xml, null, "src/test/resources/Format.xml", 4 + 2 /* CDATA and Comments */, c("Name", ""), - c("End with ''", "/Configuration>"), + XMLAssert.testCompletionFor(xml, null, "src/test/resources/Format.xml", 4 + 2 /* CDATA and Comments */, + c("Name", ""), c("End with ''", "/Configuration>"), c("End with ''", "/ViewDefinitions>"), c("End with ''", "/View>")); xml = "\r\n" + // @@ -229,16 +230,16 @@ public void schemaLocationWithXSDFileSystemCompletion() throws BadLocationExcept + " xsi:schemaLocation=\"http://invoice xsd/invoice-ns.xsd \">\r\n" + // " <|"; // Completion only for date - XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", 2 + 2 /* CDATA and Comments */, c("date", ""), - c("End with ''", "")); + XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", 2 + 2 /* CDATA and Comments */, + c("date", ""), c("End with ''", "")); // Completion only for number xml = "\r\n" + // "\r\n" + // " |"; - XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", 2 + 2 /* CDATA and Comments */, c("number", ""), - c("End with ''", "")); + XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", 2 + 2 /* CDATA and Comments */, + c("number", ""), c("End with ''", "")); } @Test @@ -662,8 +663,105 @@ public void sequence() throws BadLocationException { // optional3 is not return by completion since optional3 has a max=2 occurences XMLAssert.testCompletionFor(xml, null, "src/test/resources/sequence.xml", 1 + 2 /* CDATA and Comments */, c("End with ''", "")); + } + @Test + public void xsAny() throws IOException, BadLocationException { + Path dir = Paths.get("target/xsd/"); + if (!Files.isDirectory(dir)) { + Files.createDirectory(dir); + } + Files.deleteIfExists(Paths.get(dir.toString(), "any.xsd")); + XMLLanguageService xmlLanguageService = new XMLLanguageService(); + // Test completion with xs:any processContents="strict" + String schema = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // <-- xs:any processContents="strict" + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + Files.write(Paths.get("target/xsd/any.xsd"), schema.getBytes()); + + String xml = "\r\n" + + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4 + 1, true, + c("title", "")); + + // xs:any completion with strict -> only XML Schema global element declaration + // available in completion + xml = "\r\n" + + // + " \r\n" + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4 + 2, true, + c("ui:page", ""), c("ui:textbox", "")); + + // no completion + xml = "\r\n" + + // + " \r\n" + // + " \r\n" + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4, true); + + // Test completion with xs:any processContents="lax" (or processContents="skip") + schema = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // <-- xs:any processContents="lax" + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + + Files.write(Paths.get("target/xsd/any.xsd"), schema.getBytes()); + + xml = "\r\n" + + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4 + 1, true, + c("title", "")); + + // xs:any completion with strict -> all XML Schema element declaration + // available in completion + tags completion + xml = "\r\n" + + // + " \r\n" + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4 + 4, true, + c("title", ""), c("a", ""), c("ui:page", ""), c("ui:textbox", "")); + + // no completion + xml = "\r\n" + + // + " \r\n" + // + " \r\n" + // + " | \r\n" + // + " " + // + ""; + XMLAssert.testCompletionFor(xmlLanguageService, xml, null, null, "target/any.xml", 4, true); } @Test @@ -674,7 +772,7 @@ public void tag() throws BadLocationException { " |"; XMLAssert.testCompletionFor(xml, null, "src/test/resources/sequence.xml", 4 + 2 /* CDATA and Comments */, c("tag", ""), c("End with ''", ""), c("#region", ""), - c("#endregion", ""), c("cdata", ""), + c("#endregion", ""), c("cdata", ""), // c("comment", "")); xml = "\r\n" + //