diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java index 35e5b310763..0fcbf32e5d2 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Convert.java @@ -26,17 +26,23 @@ import net.sf.saxon.type.BuiltInAtomicType; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentImpl; +import org.exist.dom.persistent.NodeProxy; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; import org.exist.xquery.functions.array.ArrayType; import org.exist.xquery.functions.fn.FnTransform; +import org.exist.xquery.functions.map.AbstractMapType; import org.exist.xquery.value.*; import org.w3c.dom.Document; import org.w3c.dom.Node; +import io.lacuna.bifurcan.IEntry; + import javax.xml.transform.dom.DOMSource; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Type conversion to and from Saxon @@ -119,11 +125,18 @@ static net.sf.saxon.s9api.QName of(final QNameValue qName) { } XdmValue of(final Item item) throws XPathException { + if (item instanceof NodeProxy) { + return ofNode(((NodeProxy) item).getNode()); + } final int itemType = item.getType(); if (Type.subTypeOf(itemType, Type.ATOMIC)) { return ofAtomic((AtomicValue) item); } else if (Type.subTypeOf(itemType, Type.NODE)) { return ofNode((Node) item); + } else if (Type.subTypeOf(itemType, Type.MAP)) { + return ofMap((AbstractMapType) item); + } else if (Type.subTypeOf(itemType, Type.ARRAY)) { + return ofArray((ArrayType) item); } throw new XPathException(ErrorCodes.XPTY0004, "Item " + item + " of type " + Type.getTypeName(itemType) + COULD_NOT_BE_CONVERTED + "XdmValue"); @@ -140,14 +153,12 @@ static private XdmValue ofAtomic(final AtomicValue atomicValue) throws XPathExce } else if (Type.subTypeOf(itemType, Type.STRING)) { return XdmValue.makeValue(((StringValue) atomicValue).getStringValue()); } - throw new XPathException(ErrorCodes.XPTY0004, "Atomic value " + atomicValue + " of type " + Type.getTypeName(itemType) + COULD_NOT_BE_CONVERTED + "XdmValue"); } private XdmValue ofNode(final Node node) throws XPathException { - final DocumentBuilder sourceBuilder = newDocumentBuilder(); try { if (node instanceof DocumentImpl) { @@ -167,6 +178,25 @@ private XdmValue ofNode(final Node node) throws XPathException { } } + private XdmValue ofMap(final AbstractMapType map) throws XPathException { + Map xdmMap = new HashMap(); + for (IEntry entry : map) { + XdmAtomicValue key = (XdmAtomicValue) ofAtomic(entry.key()); + XdmValue value = of(entry.value()); + xdmMap.put(key, value); + } + return new XdmMap(xdmMap); + } + + private XdmValue ofArray(final ArrayType array) throws XPathException { + int size = array.getSize(); + XdmValue[] members = new XdmValue[size]; + for (int i = 0; i < size; ++i) { + members[i] = of(array.get(i)); + } + return new XdmArray(members); + } + XdmValue[] of(final ArrayType values) throws XPathException { final int size = values.getSize(); final XdmValue[] result = new XdmValue[size]; diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java index 34e2b5e33f1..62aa04ebbde 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java @@ -192,8 +192,10 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro final Transform.TemplateInvocation invocation = new Transform.TemplateInvocation( options, sourceNode, delivery, xslt30Transformer, resultDocuments); return invocation.invoke(); - } catch (final SaxonApiException | UncheckedXPathException e) { - throw originalXPathException("Could not transform input: ", e, ErrorCodes.FOXT0003); + } catch (final SaxonApiException e) { + throw originalXPathException("Could not transform with "+options.xsltSource._1+" line "+e.getLineNumber()+": ", e, ErrorCodes.FOXT0003); + } catch (final UncheckedXPathException e) { + throw originalXPathException("Could not transform with "+options.xsltSource._1+" line "+e.getXPathException().getLocationAsString()+": ", e, ErrorCodes.FOXT0003); } } else { diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/TreeUtils.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/TreeUtils.java index dae639ea3bf..e710d6ec60d 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/TreeUtils.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/TreeUtils.java @@ -24,6 +24,7 @@ import net.sf.saxon.s9api.XdmNode; import org.exist.xquery.value.NodeValue; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -64,20 +65,41 @@ static StringBuilder pathTo(final Node node) { static List treeIndex(final Node node) { final Node parent = node.getParentNode(); if (parent == null) { - return new ArrayList<>(); + final List index = new ArrayList<>(); + // The root element always index 0 within the document node. + // Some node implementations (e.g., org.exist.dom.memtree.NodeImpl) do not always have an associated document. + // In this case, the nodeIndex must get an extra 0 index to be valid for xdmDocument. + if (! (node instanceof Document)) { + index.add(0); + } + return index; } final List index = treeIndex(parent); - Node sibling = node.getPreviousSibling(); + Node sibling = previousSiblingNotAttribute(node); int position = 0; while (sibling != null) { position += 1; - sibling = sibling.getPreviousSibling(); + sibling = previousSiblingNotAttribute(sibling); } index.add(position); return index; } + /** + * A org.exist.dom.persistent.StoredNode returns attributes of an element as previous siblings of the element's children. + * This is not compatible with the way xdmNodeAtIndex works, so we need to compensate for this. + * @param node + * @return the previous sibling of `node` that is not an attribute. + */ + private static Node previousSiblingNotAttribute(Node node) { + Node sibling = node.getPreviousSibling(); + if (sibling instanceof Attr) { + return null; + } + return sibling; + } + static XdmNode xdmNodeAtIndex(final XdmNode xdmNode, final List index) { if (index.isEmpty()) { return xdmNode;