Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 14 additions & 3 deletions exist-core/src/main/java/org/exist/xquery/Atomize.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import org.exist.dom.persistent.DocumentSet;
import org.exist.xquery.functions.array.ArrayType;
import org.exist.xquery.functions.map.AbstractMapType;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
Expand Down Expand Up @@ -75,15 +76,25 @@ public static Sequence atomize(Sequence input) throws XPathException {
if (input.isEmpty())
{return Sequence.EMPTY_SEQUENCE;}
input = ArrayType.flatten(input);
if (input.hasOne()) {return
input.itemAt(0).atomize();
if (input.hasOne()) {
final Item single = input.itemAt(0);
// XQ4: maps are atomizable — expand to their values before atomizing
if (single instanceof AbstractMapType && ((AbstractMapType) single).isXq4Atomizable()) {
return ((AbstractMapType) single).atomizeValues();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use direct cast using local variable here:

Suggested change
if (single instanceof AbstractMapType && ((AbstractMapType) single).isXq4Atomizable()) {
return ((AbstractMapType) single).atomizeValues();
}
if (single instanceof AbstractMapType mapType && mapType.isXq4Atomizable()) {
return mapType.atomizeValues();
}

return single.atomize();
}

Item next;
final ValueSequence result = new ValueSequence();
for(final SequenceIterator i = input.iterate(); i.hasNext(); ) {
next = i.nextItem();
result.add(next.atomize());
// XQ4: maps are atomizable — expand to their values before atomizing
if (next instanceof AbstractMapType && ((AbstractMapType) next).isXq4Atomizable()) {
result.addAll(((AbstractMapType) next).atomizeValues());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Suggested change
if (next instanceof AbstractMapType && ((AbstractMapType) next).isXq4Atomizable()) {
result.addAll(((AbstractMapType) next).atomizeValues());
if (next instanceof AbstractMapType mapType && mapType.isXq4Atomizable()) {
result.addAll(mapType.atomizeValues());

} else {
result.add(next.atomize());
}
}
return result;
}
Expand Down
14 changes: 12 additions & 2 deletions exist-core/src/main/java/org/exist/xquery/ConcatExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package org.exist.xquery;

import org.exist.xquery.functions.map.AbstractMapType;
import org.exist.xquery.util.Error;
import org.exist.xquery.value.*;

Expand Down Expand Up @@ -65,8 +66,17 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
final Sequence seq = step.eval(contextSequence, contextItem);
for (final SequenceIterator i = seq.iterate(); i.hasNext(); ) {
final Item item = i.nextItem();
if (Type.subTypeOf(item.getType(), Type.FUNCTION))
{throw new XPathException(this, ErrorCodes.FOTY0013, "Got a function item as operand in string concatenation");}
if (Type.subTypeOf(item.getType(), Type.FUNCTION)) {
// XQ4: maps are no longer function items for atomization purposes
if (item instanceof AbstractMapType && ((AbstractMapType) item).isXq4Atomizable()) {
final Sequence atomized = ((AbstractMapType) item).atomizeValues();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Suggested change
if (item instanceof AbstractMapType && ((AbstractMapType) item).isXq4Atomizable()) {
final Sequence atomized = ((AbstractMapType) item).atomizeValues();
if (item instanceof AbstractMapType mapType && mapType.isXq4Atomizable()) {
final Sequence atomized = mapType.atomizeValues();

for (final SequenceIterator ai = atomized.iterate(); ai.hasNext(); ) {
concat.append(ai.nextItem().getStringValue());
}
continue;
}
throw new XPathException(this, ErrorCodes.FOTY0013, "Got a function item as operand in string concatenation");
}
concat.append(item.getStringValue());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,61 @@ protected static boolean sameKey(@Nullable final Collator collator, final Atomic
return false;
}

/**
* Returns {@code true} if this map can be atomized under the current XQuery version.
* In XQuery 4.0, maps are no longer function items and can be atomized
* (returning the atomized values of their entries).
*
* @return true if the map is atomizable (XQuery 4.0+)
*/
public boolean isXq4Atomizable() {
return context != null && context.getXQueryVersion() >= 40;
}

/**
* Atomize all values in this map, returning them as a flat sequence of atomic values.
* This implements the XQuery 4.0 semantics where atomizing a map returns the
* concatenation of the atomized values of its entries.
*
* @return a sequence of atomic values from all map entry values
* @throws XPathException if any value cannot be atomized
*/
public Sequence atomizeValues() throws XPathException {
if (size() == 0) {
return Sequence.EMPTY_SEQUENCE;
}
final ValueSequence result = new ValueSequence();
for (final IEntry<AtomicValue, Sequence> entry : this) {
final Sequence value = entry.value();
for (final SequenceIterator vi = value.iterate(); vi.hasNext(); ) {
result.add(vi.nextItem().atomize());
}
}
return result;
}

@Override
public AtomicValue atomize() throws XPathException {
if (isXq4Atomizable()) {
if (size() == 0) {
// follows the ArrayType pattern for empty containers
return null;
}
final Sequence atomized = atomizeValues();
if (atomized.isEmpty()) {
return null;
}
if (atomized.getItemCount() > 1) {
throw new XPathException(getExpression(), ErrorCodes.XPTY0004,
"Atomization of a map with multiple values requires a sequence context");
}
return (AtomicValue) atomized.itemAt(0);
}
// XQuery 3.1 and earlier: maps are function items and cannot be atomized
throw new XPathException(getExpression(), ErrorCodes.FOTY0013,
"A function item other than an array cannot be atomized");
}

@Override
public String toString() {
final StringBuilder buf = new StringBuilder("map {");
Expand Down