From 0c47a0cb25225c68fbf568d74b018bdcdebf559b Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Thu, 30 May 2024 15:39:08 -0700 Subject: [PATCH 1/2] Support xsi:type overrides when resolving content model --- .../xsd/contentmodel/CMXSDDocument.java | 46 ++++- .../contentmodel/CMXSDElementDeclaration.java | 24 +-- .../XMLSchemaHoverDocumentationTypeTest.java | 67 +++++++- .../src/test/resources/xsd/docAppinfo.xsd | 162 ++++++++++-------- 4 files changed, 212 insertions(+), 87 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java index c2412f0d2..811136dc7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java @@ -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; @@ -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; @@ -81,6 +84,7 @@ public class CMXSDDocument implements CMDocument, XSElementDeclHelper { private final XSModel model; private final Map elementMappings; + private final Table refinedElementMappings; private Collection elements; @@ -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); } @@ -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)) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java index f6df20701..b84f27ab4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java @@ -67,6 +67,8 @@ public class CMXSDElementDeclaration implements CMElementDeclaration { private final XSElementDeclaration elementDeclaration; + private final XSTypeDefinition typeDefinition; + private Collection attributes; private Collection elements; @@ -79,9 +81,19 @@ public class CMXSDElementDeclaration implements CMElementDeclaration { private Map 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 @@ -111,7 +123,6 @@ public Collection getAttributes() { private void collectAttributesDeclaration(XSElementDeclaration elementDecl, Collection attributes) { - XSTypeDefinition typeDefinition = elementDecl.getTypeDefinition(); switch (typeDefinition.getTypeCategory()) { case XSTypeDefinition.SIMPLE_TYPE: // TODO... @@ -147,7 +158,6 @@ public Collection getElements() { @Override public Collection 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 @@ -301,7 +311,6 @@ private static QName createQName(Element tag) { private void collectElementsDeclaration(XSElementDeclaration elementDecl, Collection elements) { - XSTypeDefinition typeDefinition = elementDecl.getTypeDefinition(); switch (typeDefinition.getTypeCategory()) { case XSTypeDefinition.SIMPLE_TYPE: // TODO... @@ -323,7 +332,6 @@ private void collectElementsDeclaration(XSComplexTypeDefinition typeDefinition, public boolean isOptional(String childElementName) { if (elementOptionality == null) { this.elementOptionality = new HashMap(); - XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); switch (typeDefinition.getTypeCategory()) { case XSTypeDefinition.SIMPLE_TYPE: break; @@ -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; } @@ -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; @@ -482,7 +488,6 @@ public boolean isNillable() { @Override public Collection getEnumerationValues() { - XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); if (typeDefinition != null) { XSSimpleTypeDefinition simpleDefinition = null; if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { @@ -530,7 +535,6 @@ private Map createTextsDocumentation(ISharedSettingsRequest requ } private XSObjectList getTextAnnotations(String textContent) { - XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); if (typeDefinition != null) { XSSimpleTypeDefinition simpleDefinition = null; if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { @@ -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) { @@ -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; diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaHoverDocumentationTypeTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaHoverDocumentationTypeTest.java index 210bc2070..6e2b339fd 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaHoverDocumentationTypeTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaHoverDocumentationTypeTest.java @@ -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; @@ -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 { @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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 + // " \n" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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 + // " \n" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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 + // " \n" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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 + // " \n" + "\n"; assertHover(xml, expected, docSource, markdownSupported); @@ -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 + // " \n" + // "\n"; assertHover(xml, null, docSource, markdownSupported); @@ -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 + // " \n" + // "\n"; assertHover(xml, null, docSource, markdownSupported); @@ -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 = + "\n" + + "\n"; + super.assertHover(xml, expected, docSource, markdownSupported); + } + + private void assertDerivedElementDocHover(String expected, SchemaDocumentationType docSource, + boolean markdownSupported) throws BadLocationException { + String xml = + "\n" + + "\n"; + super.assertHover(xml, expected, docSource, markdownSupported); + } + } } diff --git a/org.eclipse.lemminx/src/test/resources/xsd/docAppinfo.xsd b/org.eclipse.lemminx/src/test/resources/xsd/docAppinfo.xsd index 9c5afd686..43a679fe8 100644 --- a/org.eclipse.lemminx/src/test/resources/xsd/docAppinfo.xsd +++ b/org.eclipse.lemminx/src/test/resources/xsd/docAppinfo.xsd @@ -22,84 +22,102 @@ - - - - - - element documentation - - - - - element appinfo - - - - - element documentation - element appinfo - - - - - first element documentation - second element documentation - third element documentation - - - - - first element appinfo - second element appinfo - third element appinfo - - - - - first element documentation - second element documentation - third element documentation - first element appinfo - second element appinfo - third element appinfo - - - - - - - + - - - - - - - - - - - - - + + + - attribute name documentation + element documentation - - + + - attribute name appinfo + element appinfo - - + + - attribute name documentation - attribute name appinfo + element documentation + element appinfo - - - + + + + first element documentation + second element documentation + third element documentation + + + + + first element appinfo + second element appinfo + third element appinfo + + + + + first element documentation + second element documentation + third element documentation + first element appinfo + second element appinfo + third element appinfo + + + + + + + + + + + + + + + + + + + + + + attribute name documentation + + + + + attribute name appinfo + + + + + attribute name documentation + attribute name appinfo + + + + + + + + + + + derived element documentation + + + + + + derived attribute name documentation + + + + + \ No newline at end of file From 231dc8956c795bb2a5e54416f6c38135f35a5bc1 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Wed, 23 Oct 2024 23:02:23 -0700 Subject: [PATCH 2/2] Use nested maps instead of guava table --- .../extensions/xsd/contentmodel/CMXSDDocument.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java index 811136dc7..496b92513 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java @@ -22,8 +22,6 @@ 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; @@ -84,7 +82,7 @@ public class CMXSDDocument implements CMDocument, XSElementDeclHelper { private final XSModel model; private final Map elementMappings; - private final Table refinedElementMappings; + private final Map> refinedElementMappings; private Collection elements; @@ -96,7 +94,7 @@ public CMXSDDocument(XSModel model, XSLoaderImpl xsLoaderImpl) { this.model = model; this.xsLoader = xsLoaderImpl; this.elementMappings = new HashMap<>(); - this.refinedElementMappings = HashBasedTable.create(); + this.refinedElementMappings = new HashMap<>(); this.tracker = createFilesChangedTracker(model); } @@ -196,10 +194,14 @@ public CMElementDeclaration findCMElement(DOMElement element, String namespace) XSTypeDefinition exactType = findXsiType(elt); if (exactType != null) { CMXSDElementDeclaration baseDeclaration = declaration; - declaration = refinedElementMappings.get(baseDeclaration, exactType); + Map refinedElementMappingsForDeclaration = + refinedElementMappings.computeIfAbsent(baseDeclaration, + _key -> new HashMap<>()); + + declaration = refinedElementMappingsForDeclaration.get(exactType); if (declaration == null) { declaration = baseDeclaration.refineType(exactType); - refinedElementMappings.put(baseDeclaration, exactType, declaration); + refinedElementMappingsForDeclaration.put(exactType, declaration); } } }