From a7645bcdbec1246d1ceac6d443944707b1c180cb Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Thu, 23 Jul 2026 13:22:23 +0200 Subject: [PATCH 1/5] Accept EQName literals in attribute constructor names, check for duplicate attributes. --- .../DirElemConstructorRuntimeIterator.java | 22 +++++++++++++++ .../runtime/xml/NamespaceBindingUtils.java | 28 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/main/java/org/rumbledb/runtime/xml/DirElemConstructorRuntimeIterator.java b/src/main/java/org/rumbledb/runtime/xml/DirElemConstructorRuntimeIterator.java index f914855e1e..d7cc2bb343 100644 --- a/src/main/java/org/rumbledb/runtime/xml/DirElemConstructorRuntimeIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/DirElemConstructorRuntimeIterator.java @@ -25,6 +25,7 @@ import org.rumbledb.context.Name; import org.rumbledb.context.RuntimeStaticContext; import org.rumbledb.exceptions.AttributeOrNamespaceAfterNonAttributeException; +import org.rumbledb.exceptions.DuplicateAttributeException; import org.rumbledb.items.ItemFactory; import org.rumbledb.items.xml.ElementItem; import org.rumbledb.items.xml.XMLDocumentPosition; @@ -34,7 +35,9 @@ import java.io.Serial; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Runtime iterator for direct element constructors. @@ -198,6 +201,7 @@ public Item materializeFirstItemOrNull(DynamicContext dynamicContext) { iterator.close(); } } + validateNoDuplicateAttributes(attributes); // create and return the element item this.hasNext = false; ElementItem elementItem = (ElementItem) ItemFactory.getInstance() @@ -224,6 +228,24 @@ public Item materializeFirstItemOrNull(DynamicContext dynamicContext) { return elementItem; } + private void validateNoDuplicateAttributes(List attributes) { + Set attributeNames = new HashSet<>(); + + for (Item attribute : attributes) { + if (!attribute.isAttributeNode()) { + continue; + } + Name expanded = attribute.nodeName(); + if (expanded == null) { + continue; + } + if (attributeNames.contains(expanded)) { + throw new DuplicateAttributeException(expanded.toString(), getMetadata()); + } + attributeNames.add(expanded); + } + } + private static List createChildList( List content, List attributes diff --git a/src/main/java/org/rumbledb/runtime/xml/NamespaceBindingUtils.java b/src/main/java/org/rumbledb/runtime/xml/NamespaceBindingUtils.java index 2b6320a62c..45d5ebf657 100644 --- a/src/main/java/org/rumbledb/runtime/xml/NamespaceBindingUtils.java +++ b/src/main/java/org/rumbledb/runtime/xml/NamespaceBindingUtils.java @@ -368,6 +368,34 @@ public static Name parseLexicalQNameForComputedAttribute( NamespaceResolver namespaceResolver, ExceptionMetadata metadata ) { + if (lexical.startsWith("Q{")) { + int closeBrace = lexical.indexOf('}', 2); + if (closeBrace < 0) { + throw new InvalidLexicalValueException( + "Invalid URIQualifiedName (no closing '}') : " + lexical, + metadata + ); + } + String uriRaw = lexical.substring(2, closeBrace); + String local = lexical.substring(closeBrace + 1); + if (local.isEmpty()) { + throw new InvalidLexicalValueException( + "Invalid URIQualifiedName (missing local name): " + lexical, + metadata + ); + } + if (!isValidNcName(local)) { + throw new InvalidLexicalValueException( + "Invalid URIQualifiedName local name: " + lexical, + metadata + ); + } + String namespace = uriRaw.trim().replaceAll("\\s+", " "); + if (namespace.isEmpty()) { + return new Name(null, null, local); + } + return new Name(namespace, null, local); + } LexicalQNameSplit split = splitAndValidateLexicalQName(lexical, metadata); if (split.prefix == null) { return new Name(null, null, split.local); From 2b2b728557e232ac0aff717cf315ceca6898222f Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Thu, 23 Jul 2026 13:26:17 +0200 Subject: [PATCH 2/5] Fix xml:id() semantics, fix issue with document positions on fresh text nodes. --- src/main/java/org/rumbledb/items/xml/TextItem.java | 8 +++++++- .../ComputedAttributeConstructorRuntimeIterator.java | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/rumbledb/items/xml/TextItem.java b/src/main/java/org/rumbledb/items/xml/TextItem.java index 36f4dd4843..ff91d8500e 100644 --- a/src/main/java/org/rumbledb/items/xml/TextItem.java +++ b/src/main/java/org/rumbledb/items/xml/TextItem.java @@ -69,7 +69,10 @@ public boolean equals(Object other) { if (!(other instanceof TextItem otherTextItem)) { return false; } - return this.getXmlDocumentPosition().equals(otherTextItem.getXmlDocumentPosition()); + if (this.documentPos == null || otherTextItem.documentPos == null) { + return false; + } + return this.documentPos.equals(otherTextItem.documentPos); } @Override @@ -97,6 +100,9 @@ public void read(Kryo kryo, Input input) { } public int hashCode() { + if (this.documentPos == null) { + return System.identityHashCode(this); + } return this.documentPos.hashCode(); } diff --git a/src/main/java/org/rumbledb/runtime/xml/ComputedAttributeConstructorRuntimeIterator.java b/src/main/java/org/rumbledb/runtime/xml/ComputedAttributeConstructorRuntimeIterator.java index 3446752af6..d96711ac4c 100644 --- a/src/main/java/org/rumbledb/runtime/xml/ComputedAttributeConstructorRuntimeIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/ComputedAttributeConstructorRuntimeIterator.java @@ -200,7 +200,9 @@ public Item materializeFirstItemOrNull(DynamicContext dynamicContext) { String attributeValue = contentExpressionBuilder.toString(); // 5: If the attribute name is xml:id, then xml:id processing is performed - // Note: we currently do not support xml:id processing + if (isXmlIdAttribute(attributeName.getQNameValue())) { + attributeValue = attributeValue.replaceAll("\\s+", " ").trim(); + } // 6: If the attribute name is xml:id, the is-id property of the resulting attribute node is set to true; // otherwise the is-id property is set to false. The is-idrefs property of the attribute node is unconditionally @@ -225,4 +227,10 @@ public Item materializeFirstItemOrNull(DynamicContext dynamicContext) { } return attributeItem; } + + private static boolean isXmlIdAttribute(Name attributeName) { + return attributeName != null + && "id".equals(attributeName.getLocalName()) + && Name.XML_NS.equals(attributeName.getNamespace()); + } } From e4eee2232850d631515cb3101f5e337490bbee30 Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Thu, 23 Jul 2026 13:32:34 +0200 Subject: [PATCH 3/5] Fix following and precedent axes. --- .../java/org/rumbledb/runtime/xml/axis/AxisIterator.java | 7 +++++++ .../runtime/xml/axis/forward/FollowingAxisIterator.java | 2 +- .../runtime/xml/axis/reverse/PrecedingAxisIterator.java | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/rumbledb/runtime/xml/axis/AxisIterator.java b/src/main/java/org/rumbledb/runtime/xml/axis/AxisIterator.java index 6fe026e8ae..ad0a5899fa 100644 --- a/src/main/java/org/rumbledb/runtime/xml/axis/AxisIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/axis/AxisIterator.java @@ -60,6 +60,13 @@ protected List getDescendants(Item node) { return descendants; } + protected List getDescendantsOrSelf(Item node) { + List descendantsOrSelf = new ArrayList<>(); + descendantsOrSelf.add(node); + descendantsOrSelf.addAll(getDescendants(node)); + return descendantsOrSelf; + } + protected List getAncestors(Item node) { List ancestors = new ArrayList<>(); Item parent = node.parent(); diff --git a/src/main/java/org/rumbledb/runtime/xml/axis/forward/FollowingAxisIterator.java b/src/main/java/org/rumbledb/runtime/xml/axis/forward/FollowingAxisIterator.java index 578fe305ca..8736f9aa23 100644 --- a/src/main/java/org/rumbledb/runtime/xml/axis/forward/FollowingAxisIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/axis/forward/FollowingAxisIterator.java @@ -53,7 +53,7 @@ private List getFollowingNodes(Item parent, Item node) { } } for (int i = followingIndex; i > 0 && i < parentChildren.size(); ++i) { - followingNodes.addAll(getDescendants(parentChildren.get(i))); + followingNodes.addAll(getDescendantsOrSelf(parentChildren.get(i))); } followingNodes.addAll(getFollowingNodes(parent.parent(), parent)); return followingNodes; diff --git a/src/main/java/org/rumbledb/runtime/xml/axis/reverse/PrecedingAxisIterator.java b/src/main/java/org/rumbledb/runtime/xml/axis/reverse/PrecedingAxisIterator.java index 2284fe9904..50ee26546b 100644 --- a/src/main/java/org/rumbledb/runtime/xml/axis/reverse/PrecedingAxisIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/axis/reverse/PrecedingAxisIterator.java @@ -54,7 +54,7 @@ private List getPrecedingNode(Item parent, Item node) { } } for (int i = 0; i < nodeIndex; ++i) { - precedingNodes.addAll(getDescendants(parentChildren.get(i))); + precedingNodes.addAll(getDescendantsOrSelf(parentChildren.get(i))); } precedingNodes.addAll(getPrecedingNode(parent.parent(), parent)); return precedingNodes; From a83020619bbf69f6b21ed82a319c057f930f6b70 Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Mon, 27 Jul 2026 10:19:46 +0200 Subject: [PATCH 4/5] Fix element atomization to not include comments and PI. --- src/main/java/org/rumbledb/items/xml/ElementItem.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/rumbledb/items/xml/ElementItem.java b/src/main/java/org/rumbledb/items/xml/ElementItem.java index fff83e781a..822ffe7f17 100644 --- a/src/main/java/org/rumbledb/items/xml/ElementItem.java +++ b/src/main/java/org/rumbledb/items/xml/ElementItem.java @@ -503,14 +503,11 @@ public List atomizedValue() { return Collections.singletonList(typedValue); } // For untyped elements, atomization yields the element's typed value as xs:untypedAtomic. - // We still approximate typed-value by concatenating children for now, but preserve the - // untypedAtomic dynamic type instead of collapsing to xs:string. - StringBuilder stringValueBuilder = new StringBuilder(); - for (Item child : this.children) { - stringValueBuilder.append(child.atomizedValue().get(0).getStringValue()); - } + // For element nodes, typed-value is based on the element's string value, which is the + // concatenation of descendant text nodes in document order and therefore excludes comment + // and processing-instruction content. return Collections.singletonList( - ItemFactory.getInstance().createUntypedAtomicItem(stringValueBuilder.toString()) + ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue) ); } From 71044a6f4c79952cd0e5bf48c334f2a42d88113c Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Mon, 27 Jul 2026 10:26:25 +0200 Subject: [PATCH 5/5] Clean up accessors. --- src/main/java/org/rumbledb/api/Item.java | 51 +++++++++---------- .../org/rumbledb/items/AnnotatedItem.java | 10 +--- .../java/org/rumbledb/items/ArrayItem.java | 4 +- .../java/org/rumbledb/items/FunctionItem.java | 2 +- .../java/org/rumbledb/items/MapEntryItem.java | 2 +- src/main/java/org/rumbledb/items/MapItem.java | 2 +- .../items/MapWithAdditionalEntryItem.java | 2 +- .../items/MapWithRemovedEntryItem.java | 2 +- .../java/org/rumbledb/items/ObjectItem.java | 2 +- .../org/rumbledb/items/SequenceArrayItem.java | 4 +- .../org/rumbledb/items/xml/AttributeItem.java | 2 +- .../org/rumbledb/items/xml/CommentItem.java | 2 +- .../org/rumbledb/items/xml/DocumentItem.java | 15 ++---- .../org/rumbledb/items/xml/ElementItem.java | 40 +++++++-------- .../org/rumbledb/items/xml/NamespaceItem.java | 2 +- .../items/xml/ProcessingInstructionItem.java | 2 +- .../java/org/rumbledb/items/xml/TextItem.java | 2 +- .../flwor/clauses/GroupByClauseIterator.java | 2 +- .../arrays/ArraySortFunctionIterator.java | 2 +- .../maps/MapFunctionCallIterator.java | 2 +- .../maps/MapGetFunctionIterator.java | 3 +- .../maps/MapPutFunctionIterator.java | 3 +- .../maps/MapRemoveFunctionIterator.java | 2 +- .../sequences/general/AtomizationClosure.java | 2 +- .../general/DataFunctionIterator.java | 4 +- .../general/SortFunctionIterator.java | 2 +- .../value/DeepEqualFunctionIterator.java | 2 +- .../MapConstructorRuntimeIterator.java | 2 +- .../rumbledb/runtime/typing/CastIterator.java | 2 +- .../runtime/xml/PostfixLookupClosure.java | 2 +- .../runtime/xml/PostfixLookupIterator.java | 2 +- .../runtime/xml/UnaryLookupIterator.java | 2 +- 32 files changed, 78 insertions(+), 102 deletions(-) diff --git a/src/main/java/org/rumbledb/api/Item.java b/src/main/java/org/rumbledb/api/Item.java index 7ccc63c78d..0e775013d5 100644 --- a/src/main/java/org/rumbledb/api/Item.java +++ b/src/main/java/org/rumbledb/api/Item.java @@ -952,9 +952,13 @@ default void removeSequenceAt(int index) throws UnsupportedOperationException { // endregion arrays /** - * Returns the string value of the item, if it is an atomic item. + * XDM 3.1 string-value Accessor. * - * @return the string value. + * For node items, this method corresponds to the {@code dm:string-value} accessor and + * returns the node's string value as defined by its node kind. For atomic items, it + * returns the lexical string value of the atomic item. + * + * @return the string value of the item. */ default String getStringValue() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); @@ -1463,6 +1467,8 @@ default void addParentToDescendants() { * "The dm:attributes accessor returns the dynamic, unordered set of attribute nodes that * have the node as their parent. It is defined only on element and document nodes; for * other node kinds it returns the empty sequence." + * + * This method corresponds directly to that accessor. */ default List attributes() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); @@ -1476,6 +1482,8 @@ default List attributes() { * "The dm:children accessor returns the dynamic, ordered sequence of child nodes of the * node. It is defined on all node kinds except attribute and namespace nodes; for those * node kinds it returns the empty sequence." + * + * This method corresponds directly to that accessor. */ default List children() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); @@ -1489,20 +1497,22 @@ default List children() { * "The dm:namespace-nodes accessor returns the dynamic, unordered set of Namespace Nodes. It * is defined on all seven node kinds." * - * This default implementation is only a placeholder on the generic Item interface and must - * be overridden by XML node implementations that support namespaces. + * This method corresponds directly to that accessor. The default implementation is only a + * placeholder on the generic Item interface and must be overridden by XML node + * implementations that support namespaces. */ default List namespaceNodes() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); } /** - * Helper accessor for XML element nodes: returns namespace nodes for the namespace bindings - * declared directly on the element. This does not include inherited or statically known - * namespaces — only the bindings explicitly declared on the element (for example via - * xmlns attributes). + * Helper derived from the XDM 3.1 {@code dm:namespace-nodes} accessor for XML element + * nodes: returns namespace nodes for the namespace bindings declared directly on the + * element. This does not include inherited or statically known namespaces, only the + * bindings explicitly declared on the element (for example via {@code xmlns} attributes). * - * Non-element nodes must override this to return the empty sequence. + * Unlike {@link #namespaceNodes()}, this is not a standard XDM accessor; it exposes the + * subset of namespace nodes that are locally declared on the element. */ default List declaredNamespaceNodes() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); @@ -1612,7 +1622,10 @@ default List typeName() { * atomic items in the XDM sense. */ default List typedValue() { - return this.atomizedValue(); + if (isAtomic()) { + return Collections.singletonList(this); + } + throw new UnsupportedOperationException("Operation not defined for class " + this.getClass().getName()); } /** @@ -1670,24 +1683,6 @@ default Item parent() { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); } - /** - * XDM 3.1 Section 5.12 string-value Accessor. - * - * dm:string-value($n as node()) as xs:string - * - * "The dm:string-value accessor returns the string-value of the node as defined for each - * node kind." - * - * In this API, node string values are exposed via getStringValue() and the default - * implementation of dm:typed-value delegates to atomizedValue(). - */ - default List atomizedValue() { - if (isAtomic()) - return Collections.singletonList(this); - else - throw new UnsupportedOperationException("Operation not defined for class " + this.getClass().getName()); - } - default void setParent(Item parent) { throw new UnsupportedOperationException("Operation not defined for type " + this.getDynamicType()); } diff --git a/src/main/java/org/rumbledb/items/AnnotatedItem.java b/src/main/java/org/rumbledb/items/AnnotatedItem.java index 2172759a9f..2f6fc5fd7b 100644 --- a/src/main/java/org/rumbledb/items/AnnotatedItem.java +++ b/src/main/java/org/rumbledb/items/AnnotatedItem.java @@ -784,7 +784,8 @@ public List typeName() { @Override public List typedValue() { - return this.itemToAnnotate.typedValue(); + // An annotated atomic item yields itself as typed value so its annotation is preserved. + return this.isAtomic() ? List.of(this) : this.itemToAnnotate.typedValue(); } @Override @@ -882,13 +883,6 @@ public void setTopLevelOrder(double topLevelOrder) { this.itemToAnnotate.setTopLevelOrder(topLevelOrder); } - @Override - public List atomizedValue() { - // An annotated atomic item atomizes to itself. Delegating to the - // wrapped item would discard the annotation and lose its subtype. - return this.isAtomic() ? List.of(this) : this.itemToAnnotate.atomizedValue(); - } - @Override public String serialize() { return Item.super.serialize(); diff --git a/src/main/java/org/rumbledb/items/ArrayItem.java b/src/main/java/org/rumbledb/items/ArrayItem.java index c8d14ea722..730fd9be62 100644 --- a/src/main/java/org/rumbledb/items/ArrayItem.java +++ b/src/main/java/org/rumbledb/items/ArrayItem.java @@ -350,10 +350,10 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { List result = new ArrayList<>(); for (Item member : this.arrayItems) { - result.addAll(member.atomizedValue()); + result.addAll(member.typedValue()); } return result; } diff --git a/src/main/java/org/rumbledb/items/FunctionItem.java b/src/main/java/org/rumbledb/items/FunctionItem.java index 07386b5234..2e5a5fa3a0 100644 --- a/src/main/java/org/rumbledb/items/FunctionItem.java +++ b/src/main/java/org/rumbledb/items/FunctionItem.java @@ -449,7 +449,7 @@ public void setModuleDynamicContext(DynamicContext dynamicModuleContext) { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Function", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/MapEntryItem.java b/src/main/java/org/rumbledb/items/MapEntryItem.java index 5a9212a9d8..58ef25676b 100644 --- a/src/main/java/org/rumbledb/items/MapEntryItem.java +++ b/src/main/java/org/rumbledb/items/MapEntryItem.java @@ -313,7 +313,7 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Map", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/MapItem.java b/src/main/java/org/rumbledb/items/MapItem.java index a1104a0fcb..af7a820537 100644 --- a/src/main/java/org/rumbledb/items/MapItem.java +++ b/src/main/java/org/rumbledb/items/MapItem.java @@ -512,7 +512,7 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Map", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/MapWithAdditionalEntryItem.java b/src/main/java/org/rumbledb/items/MapWithAdditionalEntryItem.java index aa9e75a760..0ad4dde41d 100644 --- a/src/main/java/org/rumbledb/items/MapWithAdditionalEntryItem.java +++ b/src/main/java/org/rumbledb/items/MapWithAdditionalEntryItem.java @@ -339,7 +339,7 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Map", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/MapWithRemovedEntryItem.java b/src/main/java/org/rumbledb/items/MapWithRemovedEntryItem.java index 47d5e67bbd..79b194d878 100644 --- a/src/main/java/org/rumbledb/items/MapWithRemovedEntryItem.java +++ b/src/main/java/org/rumbledb/items/MapWithRemovedEntryItem.java @@ -332,7 +332,7 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Map", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/ObjectItem.java b/src/main/java/org/rumbledb/items/ObjectItem.java index 808cea7f2a..675c05ec92 100644 --- a/src/main/java/org/rumbledb/items/ObjectItem.java +++ b/src/main/java/org/rumbledb/items/ObjectItem.java @@ -577,7 +577,7 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { throw new CannotAtomizeException("tried to atomize Object", ExceptionMetadata.EMPTY_METADATA); } diff --git a/src/main/java/org/rumbledb/items/SequenceArrayItem.java b/src/main/java/org/rumbledb/items/SequenceArrayItem.java index 9bff6502a0..b1a7cc35af 100644 --- a/src/main/java/org/rumbledb/items/SequenceArrayItem.java +++ b/src/main/java/org/rumbledb/items/SequenceArrayItem.java @@ -401,11 +401,11 @@ public String getSparkSQLType() { } @Override - public List atomizedValue() { + public List typedValue() { List result = new ArrayList<>(); for (List memberSequence : this.memberSequences) { for (Item item : memberSequence) { - result.addAll(item.atomizedValue()); + result.addAll(item.typedValue()); } } return result; diff --git a/src/main/java/org/rumbledb/items/xml/AttributeItem.java b/src/main/java/org/rumbledb/items/xml/AttributeItem.java index ba656f37bb..f73ca2f9dc 100644 --- a/src/main/java/org/rumbledb/items/xml/AttributeItem.java +++ b/src/main/java/org/rumbledb/items/xml/AttributeItem.java @@ -205,7 +205,7 @@ public int hashCode() { } @Override - public List atomizedValue() { + public List typedValue() { if (this.typeAnnotation != null) { Item typedValue = CastIterator.castItemToType( ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue), diff --git a/src/main/java/org/rumbledb/items/xml/CommentItem.java b/src/main/java/org/rumbledb/items/xml/CommentItem.java index d28cfc9dde..412b78eed9 100644 --- a/src/main/java/org/rumbledb/items/xml/CommentItem.java +++ b/src/main/java/org/rumbledb/items/xml/CommentItem.java @@ -105,7 +105,7 @@ public String getStringValue() { } @Override - public List atomizedValue() { + public List typedValue() { return Collections.singletonList(ItemFactory.getInstance().createStringItem(this.content)); } diff --git a/src/main/java/org/rumbledb/items/xml/DocumentItem.java b/src/main/java/org/rumbledb/items/xml/DocumentItem.java index d9269398c0..27b76686d4 100644 --- a/src/main/java/org/rumbledb/items/xml/DocumentItem.java +++ b/src/main/java/org/rumbledb/items/xml/DocumentItem.java @@ -271,11 +271,14 @@ public List typeName() { * For a Document Node, dm:typed-value returns the typed value of the document node as a * sequence of zero or more atomic values. * - * This implementation delegates to atomizedValue(). + * This implementation delegates to the typed value of the document element. */ @Override public List typedValue() { - return this.atomizedValue(); + if (this.documentElement != null) { + return this.documentElement.typedValue(); + } + return Collections.singletonList(ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue)); } /** @@ -316,14 +319,6 @@ public int hashCode() { return this.documentPos.hashCode(); } - @Override - public List atomizedValue() { - if (this.documentElement != null) { - return this.documentElement.typedValue(); - } - return Collections.singletonList(ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue)); - } - @Override public List namespaceNodes() { return Collections.emptyList(); diff --git a/src/main/java/org/rumbledb/items/xml/ElementItem.java b/src/main/java/org/rumbledb/items/xml/ElementItem.java index 822ffe7f17..fcefb02c67 100644 --- a/src/main/java/org/rumbledb/items/xml/ElementItem.java +++ b/src/main/java/org/rumbledb/items/xml/ElementItem.java @@ -397,13 +397,26 @@ public List typeName() { * "For an Element Node, dm:typed-value returns the typed value of the element node as a * sequence of zero or more atomic values." * - * This implementation delegates to atomizedValue(), which currently computes a - * best-effort typed value by concatenating the atomized values of the element's - * children in document order. + * This implementation computes a best-effort typed value from the element's string value + * and optional type annotation. */ @Override public List typedValue() { - return this.atomizedValue(); + if (this.typeAnnotation != null) { + Item typedValue = CastIterator.castItemToType( + ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue), + this.typeAnnotation, + org.rumbledb.exceptions.ExceptionMetadata.EMPTY_METADATA + ); + return Collections.singletonList(typedValue); + } + // For untyped elements, atomization yields the element's typed value as xs:untypedAtomic. + // For element nodes, typed-value is based on the element's string value, which is the + // concatenation of descendant text nodes in document order and therefore excludes comment + // and processing-instruction content. + return Collections.singletonList( + ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue) + ); } @Override @@ -492,25 +505,6 @@ public int hashCode() { return this.documentPos.hashCode(); } - @Override - public List atomizedValue() { - if (this.typeAnnotation != null) { - Item typedValue = CastIterator.castItemToType( - ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue), - this.typeAnnotation, - org.rumbledb.exceptions.ExceptionMetadata.EMPTY_METADATA - ); - return Collections.singletonList(typedValue); - } - // For untyped elements, atomization yields the element's typed value as xs:untypedAtomic. - // For element nodes, typed-value is based on the element's string value, which is the - // concatenation of descendant text nodes in document order and therefore excludes comment - // and processing-instruction content. - return Collections.singletonList( - ItemFactory.getInstance().createUntypedAtomicItem(this.stringValue) - ); - } - @Override public boolean getEffectiveBooleanValue() { return true; diff --git a/src/main/java/org/rumbledb/items/xml/NamespaceItem.java b/src/main/java/org/rumbledb/items/xml/NamespaceItem.java index d12268ea87..20aa879bc4 100644 --- a/src/main/java/org/rumbledb/items/xml/NamespaceItem.java +++ b/src/main/java/org/rumbledb/items/xml/NamespaceItem.java @@ -162,7 +162,7 @@ public int hashCode() { } @Override - public List atomizedValue() { + public List typedValue() { // Spec: "dm: typed-value Returns the value of the uri property as an xs:string ." return Collections.singletonList(ItemFactory.getInstance().createStringItem(this.uri)); } diff --git a/src/main/java/org/rumbledb/items/xml/ProcessingInstructionItem.java b/src/main/java/org/rumbledb/items/xml/ProcessingInstructionItem.java index d950b69a68..cef3a42d7e 100644 --- a/src/main/java/org/rumbledb/items/xml/ProcessingInstructionItem.java +++ b/src/main/java/org/rumbledb/items/xml/ProcessingInstructionItem.java @@ -100,7 +100,7 @@ public boolean isProcessingInstructionNode() { } @Override - public List atomizedValue() { + public List typedValue() { return Collections.singletonList(ItemFactory.getInstance().createStringItem(this.content)); } diff --git a/src/main/java/org/rumbledb/items/xml/TextItem.java b/src/main/java/org/rumbledb/items/xml/TextItem.java index ff91d8500e..8b426ba42a 100644 --- a/src/main/java/org/rumbledb/items/xml/TextItem.java +++ b/src/main/java/org/rumbledb/items/xml/TextItem.java @@ -167,7 +167,7 @@ public List children() { } @Override - public List atomizedValue() { + public List typedValue() { return Collections.singletonList(ItemFactory.getInstance().createUntypedAtomicItem(this.content)); } diff --git a/src/main/java/org/rumbledb/runtime/flwor/clauses/GroupByClauseIterator.java b/src/main/java/org/rumbledb/runtime/flwor/clauses/GroupByClauseIterator.java index 35dd2c038d..7ef76c165c 100644 --- a/src/main/java/org/rumbledb/runtime/flwor/clauses/GroupByClauseIterator.java +++ b/src/main/java/org/rumbledb/runtime/flwor/clauses/GroupByClauseIterator.java @@ -217,7 +217,7 @@ private HashMap> mapTuplesToPairs() { .getLocalVariableValue(groupVariableName, getMetadata()); List atomizedGroupValues = new ArrayList<>(); for (Item groupVariableValue : groupVariableValues) { - atomizedGroupValues.addAll(groupVariableValue.atomizedValue()); + atomizedGroupValues.addAll(groupVariableValue.typedValue()); } if (atomizedGroupValues.size() > 1) { throw new UnexpectedTypeException( diff --git a/src/main/java/org/rumbledb/runtime/functions/arrays/ArraySortFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/arrays/ArraySortFunctionIterator.java index 809767330d..eed9a43b4e 100644 --- a/src/main/java/org/rumbledb/runtime/functions/arrays/ArraySortFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/arrays/ArraySortFunctionIterator.java @@ -232,7 +232,7 @@ private void fnDataAppend(Item item, List out) { if (item.isFunction()) { throw new CannotAtomizeException("The sequence cannot be atomized.", getMetadata()); } - out.addAll(item.atomizedValue()); + out.addAll(item.typedValue()); } private List invokeKeyFunction( diff --git a/src/main/java/org/rumbledb/runtime/functions/maps/MapFunctionCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/maps/MapFunctionCallIterator.java index a4a28b4a39..ac66c3b241 100644 --- a/src/main/java/org/rumbledb/runtime/functions/maps/MapFunctionCallIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/maps/MapFunctionCallIterator.java @@ -79,7 +79,7 @@ private void initializeResults(DynamicContext context) { this.keyIterator.materialize(context, rawKey); List atomized = new ArrayList<>(); for (Item it : rawKey) { - atomized.addAll(it.atomizedValue()); + atomized.addAll(it.typedValue()); } if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { throw new UnexpectedTypeException( diff --git a/src/main/java/org/rumbledb/runtime/functions/maps/MapGetFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/maps/MapGetFunctionIterator.java index 28c8ade904..ad8362cefe 100644 --- a/src/main/java/org/rumbledb/runtime/functions/maps/MapGetFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/maps/MapGetFunctionIterator.java @@ -84,7 +84,7 @@ private void initializeResults(DynamicContext context) { List atomized = new ArrayList<>(); for (Item it : rawKey) { - atomized.addAll(it.atomizedValue()); + atomized.addAll(it.typedValue()); } if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { @@ -146,4 +146,3 @@ public JSoundDataFrame getDataFrame(DynamicContext dynamicContext) { throw new OurBadException("map:get is currently supported only in local execution mode."); } } - diff --git a/src/main/java/org/rumbledb/runtime/functions/maps/MapPutFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/maps/MapPutFunctionIterator.java index 869539c73f..01c367aaca 100644 --- a/src/main/java/org/rumbledb/runtime/functions/maps/MapPutFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/maps/MapPutFunctionIterator.java @@ -74,7 +74,7 @@ public Item materializeFirstItemOrNull(DynamicContext context) { List atomized = new ArrayList<>(); for (Item it : rawKey) { - atomized.addAll(it.atomizedValue()); + atomized.addAll(it.typedValue()); } if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { @@ -126,4 +126,3 @@ public Item materializeFirstItemOrNull(DynamicContext context) { } } } - diff --git a/src/main/java/org/rumbledb/runtime/functions/maps/MapRemoveFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/maps/MapRemoveFunctionIterator.java index 353dc556a0..808e93cf45 100644 --- a/src/main/java/org/rumbledb/runtime/functions/maps/MapRemoveFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/maps/MapRemoveFunctionIterator.java @@ -87,7 +87,7 @@ private void initializeResult(DynamicContext context) { List keysToRemove = new ArrayList<>(); for (Item it : rawKeys) { - List atomized = it.atomizedValue(); + List atomized = it.typedValue(); for (Item a : atomized) { if (a == null || !a.isAtomic()) { throw new UnexpectedTypeException( diff --git a/src/main/java/org/rumbledb/runtime/functions/sequences/general/AtomizationClosure.java b/src/main/java/org/rumbledb/runtime/functions/sequences/general/AtomizationClosure.java index aea2d16318..9af54f3658 100644 --- a/src/main/java/org/rumbledb/runtime/functions/sequences/general/AtomizationClosure.java +++ b/src/main/java/org/rumbledb/runtime/functions/sequences/general/AtomizationClosure.java @@ -16,6 +16,6 @@ public AtomizationClosure() { @Override public Iterator call(Item arg0) throws Exception { - return arg0.atomizedValue().iterator(); + return arg0.typedValue().iterator(); } }; diff --git a/src/main/java/org/rumbledb/runtime/functions/sequences/general/DataFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/sequences/general/DataFunctionIterator.java index 05676981df..e31e596416 100644 --- a/src/main/java/org/rumbledb/runtime/functions/sequences/general/DataFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/sequences/general/DataFunctionIterator.java @@ -118,7 +118,7 @@ public void setNextResult() { return; } try { - this.nextResults = this.sequenceIterator.next().atomizedValue(); + this.nextResults = this.sequenceIterator.next().typedValue(); if (this.nextResults.isEmpty()) { this.hasNext = false; } else { @@ -137,7 +137,7 @@ public void setNextResult() { if (items.size() != 1) { throw new OurBadException("The context item is not a singleton.", getMetadata()); } - this.nextResults = items.get(0).atomizedValue(); + this.nextResults = items.get(0).typedValue(); if (this.nextResults.isEmpty()) { this.hasNext = false; } else { diff --git a/src/main/java/org/rumbledb/runtime/functions/sequences/general/SortFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/sequences/general/SortFunctionIterator.java index b06c6a2782..a3e480a211 100644 --- a/src/main/java/org/rumbledb/runtime/functions/sequences/general/SortFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/sequences/general/SortFunctionIterator.java @@ -169,7 +169,7 @@ private void fnDataAppend(Item item, List out) { if (item.isObject() || item.isFunction()) { throw new CannotAtomizeException("The sequence cannot be atomized.", getMetadata()); } - out.addAll(item.atomizedValue()); + out.addAll(item.typedValue()); } private List invokeKeyFunction( diff --git a/src/main/java/org/rumbledb/runtime/functions/sequences/value/DeepEqualFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/sequences/value/DeepEqualFunctionIterator.java index 701af574e6..28cb6784ef 100644 --- a/src/main/java/org/rumbledb/runtime/functions/sequences/value/DeepEqualFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/sequences/value/DeepEqualFunctionIterator.java @@ -341,7 +341,7 @@ private boolean checkAttributeNodesDeepEqual(Item attr1, Item attr2) { // 4b: The typed value of $i1 is deep-equal to the typed value of $i2. // Note: we do not support type annotations on attribute nodes yet. // For now, the typed value of the attribute node is the same as its string value - return checkDeepEqual(attr1.atomizedValue(), attr2.atomizedValue()); + return checkDeepEqual(attr1.typedValue(), attr2.typedValue()); } /** diff --git a/src/main/java/org/rumbledb/runtime/primary/MapConstructorRuntimeIterator.java b/src/main/java/org/rumbledb/runtime/primary/MapConstructorRuntimeIterator.java index ab441db95e..2f12e76769 100644 --- a/src/main/java/org/rumbledb/runtime/primary/MapConstructorRuntimeIterator.java +++ b/src/main/java/org/rumbledb/runtime/primary/MapConstructorRuntimeIterator.java @@ -64,7 +64,7 @@ private static Item atomizeSingleMapKey( keyIterator.materialize(dynamicContext, keySequence); List atomized = new ArrayList<>(); for (Item item : keySequence) { - atomized.addAll(item.atomizedValue()); + atomized.addAll(item.typedValue()); } if (atomized.size() != 1) { throw new UnexpectedTypeException( diff --git a/src/main/java/org/rumbledb/runtime/typing/CastIterator.java b/src/main/java/org/rumbledb/runtime/typing/CastIterator.java index 85fb737b09..0e9693abdb 100644 --- a/src/main/java/org/rumbledb/runtime/typing/CastIterator.java +++ b/src/main/java/org/rumbledb/runtime/typing/CastIterator.java @@ -146,7 +146,7 @@ public static Item castItemToType( // first we try to atomize if item is not atomic if (!item.isAtomic()) { try { - List atomized = item.atomizedValue(); + List atomized = item.typedValue(); if (atomized.size() > 1) { throw new UnexpectedTypeException( "Atomization in cast resulted in more than one item.", diff --git a/src/main/java/org/rumbledb/runtime/xml/PostfixLookupClosure.java b/src/main/java/org/rumbledb/runtime/xml/PostfixLookupClosure.java index 28d2038d9c..aae6c6da6c 100644 --- a/src/main/java/org/rumbledb/runtime/xml/PostfixLookupClosure.java +++ b/src/main/java/org/rumbledb/runtime/xml/PostfixLookupClosure.java @@ -66,7 +66,7 @@ public Iterator call(Item arg0) throws Exception { } } else { for (Item rawKey : this.keys) { - List atomized = rawKey.atomizedValue(); + List atomized = rawKey.typedValue(); if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { throw new UnexpectedTypeException( "Map lookup key must atomize to a single atomic value [err:XPTY0004].", diff --git a/src/main/java/org/rumbledb/runtime/xml/PostfixLookupIterator.java b/src/main/java/org/rumbledb/runtime/xml/PostfixLookupIterator.java index 9c3d208a1d..b85480a0dd 100644 --- a/src/main/java/org/rumbledb/runtime/xml/PostfixLookupIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/PostfixLookupIterator.java @@ -117,7 +117,7 @@ public void setNextResult() { } } else { for (Item rawKey : this.lookupKeys) { - List atomized = rawKey.atomizedValue(); + List atomized = rawKey.typedValue(); if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { throw new UnexpectedTypeException( "Map lookup key must atomize to a single atomic value [err:XPTY0004].", diff --git a/src/main/java/org/rumbledb/runtime/xml/UnaryLookupIterator.java b/src/main/java/org/rumbledb/runtime/xml/UnaryLookupIterator.java index b0d9c98019..f5b4c72d79 100644 --- a/src/main/java/org/rumbledb/runtime/xml/UnaryLookupIterator.java +++ b/src/main/java/org/rumbledb/runtime/xml/UnaryLookupIterator.java @@ -84,7 +84,7 @@ public void open(DynamicContext context) { } else { for (Item rawKey : this.lookupKeys) { - List atomized = rawKey.atomizedValue(); + List atomized = rawKey.typedValue(); if (atomized.size() != 1 || !atomized.get(0).isAtomic()) { throw new UnexpectedTypeException( "Map lookup key must atomize to a single atomic value [err:XPTY0004].",