Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support xsi:type overrides when resolving content model #1647

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 +82,7 @@ public class CMXSDDocument implements CMDocument, XSElementDeclHelper {
private final XSModel model;

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

private Collection<CMElementDeclaration> elements;

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

Expand Down Expand Up @@ -176,21 +179,60 @@ 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;
Map<XSTypeDefinition, CMXSDElementDeclaration> refinedElementMappingsForDeclaration =
refinedElementMappings.computeIfAbsent(baseDeclaration,
_key -> new HashMap<>());

declaration = refinedElementMappingsForDeclaration.get(exactType);
if (declaration == null) {
declaration = baseDeclaration.refineType(exactType);
refinedElementMappingsForDeclaration.put(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
Loading