Skip to content

Commit

Permalink
Completion provides invalid suggestions for xsd:sequence
Browse files Browse the repository at this point in the history
Fix #347

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Jul 8, 2019
1 parent f7f18e9 commit 75d1bff
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Collection;

import org.eclipse.lsp4xml.dom.DOMElement;
import org.w3c.dom.Element;

/**
* Content model document which abstracts element declaration from a given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import java.util.Collection;

import org.eclipse.lsp4xml.dom.DOMElement;

/**
* Content model element which abstracts element declaration from a given
* grammar (XML Schema, DTD).
Expand Down Expand Up @@ -59,6 +61,14 @@ default String getName(String prefix) {
*/
Collection<CMElementDeclaration> getElements();

/**
* Returns the possible declared elements for the given DOM after element.
*
* @param afterElement the after element
* @return the possible declared elements for the given DOM after element.
*/
Collection<CMElementDeclaration> getPossibleElements(DOMElement afterElement);

/**
* Returns the declared element which matches the given XML tag name / namespace
* and null otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
Expand Down Expand Up @@ -66,10 +67,18 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
CMElementDeclaration cmElement = contentModelManager.findCMElement(parentElement);
String defaultPrefix = null;

// get the previous sibling element to filter completion according XML Schema
// constraints.
DOMElement previousSiblingElement = null;
DOMNode previousSibling = request.getNode();// .getPreviousSibling();
if (previousSibling != null && previousSibling.isElement() && !previousSibling.equals(parentElement)
&& previousSibling.isClosed()) {
previousSiblingElement = (DOMElement) previousSibling;
}
if (cmElement != null) {
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmElement.getElements(), defaultPrefix, false,
request, response, schemaURI);
fillWithChildrenElementDeclaration(parentElement, cmElement.getPossibleElements(previousSiblingElement),
defaultPrefix, false, request, response, schemaURI);
}
if (parentElement.isDocumentElement()) {
// completion on root document element
Expand All @@ -95,8 +104,9 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
CMElementDeclaration cmInternalElement = contentModelManager.findInternalCMElement(parentElement);
if (cmInternalElement != null) {
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmInternalElement.getElements(), defaultPrefix, false,
request, response, schemaURI);
fillWithChildrenElementDeclaration(parentElement,
cmInternalElement.getPossibleElements(previousSiblingElement), defaultPrefix, false, request,
response, schemaURI);
}
} catch (CacheResourceDownloadingException e) {
// XML Schema, DTD is loading, ignore this error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.List;

import org.apache.xerces.impl.dtd.XMLElementDecl;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;

Expand Down Expand Up @@ -62,6 +63,12 @@ public Collection<CMElementDeclaration> getElements() {
return elements;
}

@Override
public Collection<CMElementDeclaration> getPossibleElements(DOMElement afterElement) {
// TODO: support valid element declaration for DTD
return getElements();
}

@Override
public CMElementDeclaration findCMElement(String tag, String namespace) {
for (CMElementDeclaration cmElement : getElements()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import java.util.Map;

import org.apache.xerces.impl.dv.XSSimpleType;
import org.apache.xerces.impl.xs.XSElementDecl;
import org.apache.xerces.impl.xs.XSElementDeclHelper;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xs.StringList;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
Expand All @@ -35,7 +38,7 @@
* XSD document implementation.
*
*/
public class CMXSDDocument implements CMDocument {
public class CMXSDDocument implements CMDocument, XSElementDeclHelper {

private final XSModel model;

Expand Down Expand Up @@ -173,4 +176,9 @@ static boolean isBooleanType(XSSimpleTypeDefinition typeDefinition) {
}
return false;
}

@Override
public XSElementDecl getGlobalElementDecl(QName element) {
return (XSElementDecl) model.getElementDeclaration(element.localpart, element.uri);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl;
import org.apache.xerces.impl.xs.SubstitutionGroupHandler;
import org.apache.xerces.impl.xs.XSComplexTypeDecl;
import org.apache.xerces.impl.xs.models.CMBuilder;
import org.apache.xerces.impl.xs.models.CMNodeFactory;
import org.apache.xerces.impl.xs.models.XSCMValidator;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
Expand All @@ -27,15 +34,38 @@
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.apache.xerces.xs.XSTerm;
import org.apache.xerces.xs.XSTypeDefinition;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.utils.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* XSD element declaration implementation.
*
*/
public class CMXSDElementDeclaration implements CMElementDeclaration {

private static class QNameInfo extends QName {

private int count;

public QNameInfo(String prefix, String localpart, String rawname, String uri) {
super(prefix, localpart, rawname, uri);
increment();
}

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

private final CMXSDDocument document;

private final XSElementDeclaration elementDeclaration;
Expand Down Expand Up @@ -107,6 +137,117 @@ public Collection<CMElementDeclaration> getElements() {
return elements;
}

@Override
public Collection<CMElementDeclaration> getPossibleElements(DOMElement afterElement) {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
// The type definition is complex (ex: xs:all; xs:sequence), returns list of
// element declaration according those constraint

// compute list of QName from the parent element of after element to the after
// element
List<QNameInfo> qnames = new ArrayList<>();
if (afterElement != null) {
QNameInfo last = null;
DOMElement parent = afterElement.getParentElement();
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) child;
if (last != null && last.localpart.equals(element.getLocalName())) {
last.increment();
} else {
last = createQName((Element) child);
qnames.add(last);
}
if (child.equals(afterElement)) {
break;
}
}
}
}

// Compute list of possible elements
Collection<CMElementDeclaration> possibleElements = new ArrayList<>();
CMBuilder cmBuilder = new CMBuilder(new CMNodeFactory());
XSCMValidator validator = cmBuilder.getContentModel((XSComplexTypeDecl) typeDefinition, true);
int[] states = validator.startContentModel();
// Get list of XSElementDeclaration according the XML Schema constraints and
// QNames list
Vector<?> result = whatCanGoHere(validator, states, qnames, 0);
if (result != null) {
QNameInfo last = qnames.size() > 0 ? qnames.get(qnames.size() - 1) : null;
for (Object object : result) {
if (object instanceof XSElementDeclaration) {
XSElementDeclaration elementDecl = (XSElementDeclaration) object;
// check if DOM element match element declaration occurences
if (last != null && last.localpart.equals(elementDecl.getName())) {
final int[] occurenceInfo = validator.occurenceInfo(states);
if (occurenceInfo != null && occurenceInfo[1] <= last.count) {
continue;
}
}
document.collectElement(elementDecl, possibleElements);
}
}
}
return possibleElements;
}
return getElements();
}

/**
* Loop for each qnames and return list of possible element declarations.
*
* @param validator the validator
* @param states the states
* @param qnames the list of QNames
* @param index current index of QName
* @return
*/
private Vector<?> whatCanGoHere(XSCMValidator validator, int[] states, List<QNameInfo> qnames, int index) {
if (qnames.size() <= index) {
return validator.whatCanGoHere(states);
}
// Get current QName
QNameInfo current = qnames.get(index);
// Loop for possible elements declaration
Vector<?> result = validator.whatCanGoHere(states);
for (Object object : result) {
if (object instanceof XSElementDeclaration) {
// check if possible element declaration match the current QName
XSElementDeclaration elementDecl = (XSElementDeclaration) object;
if (!current.localpart.equals(elementDecl.getName())) {
continue;
}
// check occurences
final int[] occurenceInfo = validator.occurenceInfo(states);
if (occurenceInfo != null
&& !(occurenceInfo[0] <= current.count && occurenceInfo[1] >= current.count)) {
return null;
}
index++;
if (qnames.size() < index) {
return null;
}
// get the next element declaration and continue the process
validator.oneTransition(current, states, new SubstitutionGroupHandler(document));
return whatCanGoHere(validator, states, qnames, index);
}
}
return null;
}

private static QNameInfo createQName(Element tag) {
// intern must be called since Xerces uses == to compare String ?
// -> see
// https://github.com/apache/xerces2-j/blob/trunk/src/org/apache/xerces/impl/xs/SubstitutionGroupHandler.java#L55
String namespace = tag.getNamespaceURI();
return new QNameInfo(tag.getPrefix(), tag.getLocalName().intern(), tag.getTagName().intern(),
StringUtils.isEmpty(namespace) ? null : namespace.intern());
}

private void collectElementsDeclaration(XSElementDeclaration elementDecl,
Collection<CMElementDeclaration> elements) {
XSTypeDefinition typeDefinition = elementDecl.getTypeDefinition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,17 @@ public DOMNode getNode() {
@Override
public DOMElement getParentElement() {
DOMNode currentNode = getNode();
if (!currentNode.isElement() || currentNode.getEnd() < offset) {
if (!currentNode.isElement()) {
// Node is not an element, search parent element.
return currentNode.getParentElement();
}
DOMElement element = (DOMElement) currentNode;
// node is an element, there are 2 cases
// case 1: <| or <bean | > --> in this case we must search parent of bean
// element
if (element.isInStartTag(offset) || element.isInEndTag(offset)) {
// node is an element, there are 3 cases
// - case 1: <|
// - case 2: <bean | >
// - case 3: <bean /> | or <bean></bean> |
// --> in thoses cases we must search parent of bean element
if (element.isInStartTag(offset) || element.isInEndTag(offset) || (element.hasEndTag() && element.getEnd() <= offset)) {
return element.getParentElement();
}
// case 2: <bean> | --> in this case, parent element is the bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ private void collectOpenTagSuggestions(boolean hasOpenBracket, Range replaceRang
}
}
DOMElement parentNode = completionRequest.getParentElement();
if (parentNode != null && !completionResponse.hasSomeItemFromGrammar()) {
if (parentNode != null && !parentNode.getOwnerDocument().hasGrammar()) {
// no grammar, collect similar tags from the parent node
Set<String> seenElements = new HashSet<>();
if (parentNode != null && parentNode.isElement() && parentNode.hasChildNodes()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void completion() throws BadLocationException {
"<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\r\n" + //
" |";

XMLAssert.testCompletionFor(xml, 16, c("public", "<public publicId=\"\" uri=\"\" />"));
XMLAssert.testCompletionFor(xml, 15, c("public", "<public publicId=\"\" uri=\"\" />"));
}

@Test
Expand Down
Loading

0 comments on commit 75d1bff

Please sign in to comment.