Skip to content

Commit

Permalink
Support xsi:type overrides when resolving content model
Browse files Browse the repository at this point in the history
  • Loading branch information
Equinox- authored and angelozerr committed Oct 24, 2024
1 parent d3a1235 commit d1e719c
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.apache.xerces.impl.dv.XSSimpleType;
import org.apache.xerces.impl.dv.xs.XSSimpleTypeDecl;
import org.apache.xerces.impl.xs.SchemaGrammar;
Expand Down Expand Up @@ -61,6 +63,7 @@
import org.eclipse.lemminx.extensions.contentmodel.model.FilesChangedTracker;
import org.eclipse.lemminx.extensions.xerces.ReflectionUtils;
import org.eclipse.lemminx.extensions.xsd.utils.XSDUtils;
import org.eclipse.lemminx.extensions.xsi.XSISchemaModel;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.URIUtils;
Expand All @@ -81,6 +84,7 @@ public class CMXSDDocument implements CMDocument, XSElementDeclHelper {
private final XSModel model;

private final Map<XSElementDeclaration, CMXSDElementDeclaration> elementMappings;
private final Table<CMXSDElementDeclaration, XSTypeDefinition, CMXSDElementDeclaration> refinedElementMappings;

private Collection<CMElementDeclaration> elements;

Expand All @@ -92,6 +96,7 @@ public CMXSDDocument(XSModel model, XSLoaderImpl xsLoaderImpl) {
this.model = model;
this.xsLoader = xsLoaderImpl;
this.elementMappings = new HashMap<>();
this.refinedElementMappings = HashBasedTable.create();
this.tracker = createFilesChangedTracker(model);
}

Expand Down Expand Up @@ -176,21 +181,56 @@ public CMElementDeclaration findCMElement(DOMElement element, String namespace)
paths.add(0, element);
element = element.getParentNode() instanceof DOMElement ? (DOMElement) element.getParentNode() : null;
}
CMElementDeclaration declaration = null;
CMXSDElementDeclaration declaration = null;
for (int i = 0; i < paths.size(); i++) {
DOMElement elt = paths.get(i);
if (i == 0) {
declaration = findElementDeclaration(elt.getLocalName(), namespace);
declaration = (CMXSDElementDeclaration) findElementDeclaration(elt.getLocalName(), namespace);
} else {
declaration = declaration != null ? declaration.findCMElement(elt.getLocalName(), namespace) : null;
declaration = (CMXSDElementDeclaration) declaration.findCMElement(elt.getLocalName(), namespace);
}
if (declaration == null) {
break;
}
// Refine CMElementDeclaration with specific type.
XSTypeDefinition exactType = findXsiType(elt);
if (exactType != null) {
CMXSDElementDeclaration baseDeclaration = declaration;
declaration = refinedElementMappings.get(baseDeclaration, exactType);
if (declaration == null) {
declaration = baseDeclaration.refineType(exactType);
refinedElementMappings.put(baseDeclaration, exactType, declaration);
}
}
}
return declaration;
}

private XSTypeDefinition findXsiType(DOMElement element) {
org.w3c.dom.NamedNodeMap attrs = element.getAttributes();
if (attrs == null) {
return null;
}
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
if (attr.getLocalName().equals("type") && XSISchemaModel.XSI_WEBSITE.equals(attr.getNamespaceURI())) {
String[] possiblyQualifiedType = attr.getNodeValue().split(":", 2);
javax.xml.namespace.QName qualifiedType;
if (possiblyQualifiedType.length == 1) {
qualifiedType = new javax.xml.namespace.QName(
null,
possiblyQualifiedType[0]);
} else {
qualifiedType = new javax.xml.namespace.QName(
element.getNamespaceURI(possiblyQualifiedType[0]),
possiblyQualifiedType[1]);
}
return (XSTypeDefinition) model.getComponents(XSConstants.TYPE_DEFINITION).get(qualifiedType);
}
}
return null;
}

private CMElementDeclaration findElementDeclaration(String tag, String namespace) {
for (CMElementDeclaration cmElement : getElements()) {
if (cmElement.getLocalName().equals(tag)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public class CMXSDElementDeclaration implements CMElementDeclaration {

private final XSElementDeclaration elementDeclaration;

private final XSTypeDefinition typeDefinition;

private Collection<CMAttributeDeclaration> attributes;

private Collection<CMElementDeclaration> elements;
Expand All @@ -79,9 +81,19 @@ public class CMXSDElementDeclaration implements CMElementDeclaration {

private Map<String, Boolean> elementOptionality;

public CMXSDElementDeclaration(CMXSDDocument document, XSElementDeclaration elementDeclaration) {
private CMXSDElementDeclaration(CMXSDDocument document, XSElementDeclaration elementDeclaration,
XSTypeDefinition typeDefinition) {
this.document = document;
this.elementDeclaration = elementDeclaration;
this.typeDefinition = typeDefinition;
}

public CMXSDElementDeclaration(CMXSDDocument document, XSElementDeclaration elementDeclaration) {
this(document, elementDeclaration, elementDeclaration.getTypeDefinition());
}

public CMXSDElementDeclaration refineType(XSTypeDefinition refinedType) {
return new CMXSDElementDeclaration(document, elementDeclaration, refinedType);
}

@Override
Expand Down Expand Up @@ -111,7 +123,6 @@ public Collection<CMAttributeDeclaration> getAttributes() {

private void collectAttributesDeclaration(XSElementDeclaration elementDecl,
Collection<CMAttributeDeclaration> attributes) {
XSTypeDefinition typeDefinition = elementDecl.getTypeDefinition();
switch (typeDefinition.getTypeCategory()) {
case XSTypeDefinition.SIMPLE_TYPE:
// TODO...
Expand Down Expand Up @@ -147,7 +158,6 @@ public Collection<CMElementDeclaration> getElements() {

@Override
public Collection<CMElementDeclaration> getPossibleElements(DOMElement parentElement, int offset) {
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 XML Schema constraints
Expand Down Expand Up @@ -301,7 +311,6 @@ private static QName createQName(Element tag) {

private void collectElementsDeclaration(XSElementDeclaration elementDecl,
Collection<CMElementDeclaration> elements) {
XSTypeDefinition typeDefinition = elementDecl.getTypeDefinition();
switch (typeDefinition.getTypeCategory()) {
case XSTypeDefinition.SIMPLE_TYPE:
// TODO...
Expand All @@ -323,7 +332,6 @@ private void collectElementsDeclaration(XSComplexTypeDefinition typeDefinition,
public boolean isOptional(String childElementName) {
if (elementOptionality == null) {
this.elementOptionality = new HashMap<String, Boolean>();
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
switch (typeDefinition.getTypeCategory()) {
case XSTypeDefinition.SIMPLE_TYPE:
break;
Expand Down Expand Up @@ -404,7 +412,6 @@ private XSObjectList getElementAnnotations() {
return annotation;
}
// Try get xs:annotation from the type of element declaration
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition == null) {
return null;
}
Expand Down Expand Up @@ -467,7 +474,6 @@ public String toString() {

@Override
public boolean isEmpty() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
XSComplexTypeDefinition complexTypeDefinition = (XSComplexTypeDefinition) typeDefinition;
return complexTypeDefinition.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_EMPTY;
Expand All @@ -482,7 +488,6 @@ public boolean isNillable() {

@Override
public Collection<String> getEnumerationValues() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null) {
XSSimpleTypeDefinition simpleDefinition = null;
if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
Expand Down Expand Up @@ -530,7 +535,6 @@ private Map<String, String> createTextsDocumentation(ISharedSettingsRequest requ
}

private XSObjectList getTextAnnotations(String textContent) {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null) {
XSSimpleTypeDefinition simpleDefinition = null;
if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
Expand Down Expand Up @@ -560,7 +564,6 @@ public String getDocumentURI() {

@Override
public boolean isStringType() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null) {
XSSimpleTypeDefinition simpleDefinition = null;
if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
Expand All @@ -577,7 +580,6 @@ public boolean isStringType() {

@Override
public boolean isMixedContent() {
XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition();
if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
XSComplexTypeDefinition complexTypeDefinition = (XSComplexTypeDefinition) typeDefinition;
return complexTypeDefinition.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
import org.eclipse.lsp4j.HoverCapabilities;
import org.eclipse.lsp4j.MarkupKind;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
* XML hover tests with XML Schema.
*
*/
public class XMLSchemaHoverDocumentationTypeTest extends AbstractCacheBasedTest {
public abstract class XMLSchemaHoverDocumentationTypeTest extends AbstractCacheBasedTest {

private static final String schemaName = "docAppinfo.xsd";
private static final String schemaPath = "src/test/resources/" + schemaName;
Expand All @@ -40,6 +41,7 @@ public class XMLSchemaHoverDocumentationTypeTest extends AbstractCacheBasedTest
private static String plainTextDocPrefix = "xs:documentation:" + System.lineSeparator() + System.lineSeparator();
private static String plainTextAppinfoPrefix = "xs:appinfo:" + System.lineSeparator() + System.lineSeparator();
private static String plainTextSource;
private String extraAttributes = "";

@BeforeAll
public static void setup() throws MalformedURIException {
Expand Down Expand Up @@ -213,6 +215,7 @@ private void assertAttributeNameDocHover(String expected, SchemaDocumentationTyp
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" attribu|teNameOnlyDocumentation=\"onlyDocumentation\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -225,6 +228,7 @@ private void assertAttributeValueDocHover(String expected, SchemaDocumentationTy
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" attributeNameOnlyDocumentation=\"o|nlyDocumentation\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -237,6 +241,7 @@ private void assertAttributeNameAppinfoHover(String expected, SchemaDocumentatio
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" a|ttributeNameOnlyAppinfo=\"onlyAppinfo\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -249,6 +254,7 @@ private void assertAttributeValueAppinfoHover(String expected, SchemaDocumentati
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" attributeNameOnlyAppinfo=\"o|nlyAppinfo\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -261,6 +267,7 @@ private void assertAttributeNameBothHover(String expected, SchemaDocumentationTy
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" a|ttributeNameBoth=\"bothDocumentationAndAppinfo\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -273,6 +280,7 @@ private void assertAttributeValueBothHover(String expected, SchemaDocumentationT
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" attributeNameBoth=\"b|othDocumentationAndAppinfo\">\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -285,6 +293,7 @@ private void assertElementDocHover(String expected, SchemaDocumentationType docS
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" <e|lementOnlyDocumentation></elementOnlyDocumentation>\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -297,6 +306,7 @@ private void assertElementAppinfoHover(String expected, SchemaDocumentationType
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" <e|lementOnlyAppinfo></elementOnlyAppinfo>\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -309,6 +319,7 @@ private void assertElementBothHover(String expected, SchemaDocumentationType doc
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" <e|lementBoth></elementBoth>\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -321,6 +332,7 @@ private void assertElementMultipleBothHover(String expected, SchemaDocumentation
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
extraAttributes + //
" <e|lementMultipleBoth></elementMultipleBoth>\n" +
"</root>\n";
assertHover(xml, expected, docSource, markdownSupported);
Expand All @@ -332,6 +344,7 @@ private void assertElementHoverNoAnnotation(SchemaDocumentationType docSource, b
" xmlns=\"http://docAppinfo\"\n" + //
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + //
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" + //
extraAttributes + //
" <e|lementNoAnnotation></elementNoAnnotation>\n" + //
"</root>\n";
assertHover(xml, null, docSource, markdownSupported);
Expand All @@ -343,6 +356,7 @@ private void assertElementHoverWhitespaceAnnotation(SchemaDocumentationType docS
" xmlns=\"http://docAppinfo\"\n" + //
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + //
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" + //
extraAttributes + //
" <e|lementWhitespaceAnnotation></elementWhitespaceAnnotation>\n" + //
"</root>\n";
assertHover(xml, null, docSource, markdownSupported);
Expand Down Expand Up @@ -379,5 +393,56 @@ private static String getXMLSchemaFileURI(String schemaURI) throws MalformedURIE
"/");
}

@Nested
public static class BaseTypeTest extends XMLSchemaHoverDocumentationTypeTest {
}

@Nested
public static class DerivedTypeTest extends XMLSchemaHoverDocumentationTypeTest {
public DerivedTypeTest() {
super.extraAttributes = " xsi:type=\"Derived\"";
}

@Test
public void testHoverDerivedAttributeNameDoc() throws BadLocationException, MalformedURIException {
assertDerivedAttributeNameDocHover("derived attribute name documentation", SchemaDocumentationType.documentation, true);
assertDerivedAttributeNameDocHover(null, SchemaDocumentationType.appinfo, true);
assertDerivedAttributeNameDocHover("derived attribute name documentation", SchemaDocumentationType.all, true);
assertDerivedAttributeNameDocHover(null, SchemaDocumentationType.none, true);
};

@Test
public void testHoverDerivedElementDoc() throws BadLocationException, MalformedURIException {
assertDerivedElementDocHover("derived element documentation", SchemaDocumentationType.documentation, true);
assertDerivedElementDocHover(null, SchemaDocumentationType.appinfo, true);
assertDerivedElementDocHover("derived element documentation", SchemaDocumentationType.all, true);
assertDerivedElementDocHover(null, SchemaDocumentationType.none, true);
};

private void assertDerivedAttributeNameDocHover(String expected, SchemaDocumentationType docSource,
boolean markdownSupported) throws BadLocationException {
String xml =
"<root\n" +
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
super.extraAttributes + //
" derivedAttribu|teNameOnlyDocumentation=\"onlyDocumentation\">\n" +
"</root>\n";
super.assertHover(xml, expected, docSource, markdownSupported);
}

private void assertDerivedElementDocHover(String expected, SchemaDocumentationType docSource,
boolean markdownSupported) throws BadLocationException {
String xml =
"<root\n" +
" xmlns=\"http://docAppinfo\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://docAppinfo xsd/" + schemaName + "\"\n" +
super.extraAttributes + //
" <derivedE|lementOnlyDocumentation></derivedElementOnlyDocumentation>\n" +
"</root>\n";
super.assertHover(xml, expected, docSource, markdownSupported);
}
}
}
Loading

0 comments on commit d1e719c

Please sign in to comment.