net.sf.saxon.s9api.streams.Steps Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.s9api.streams;
import net.sf.saxon.lib.ConversionRules;
import net.sf.saxon.ma.arrays.ArrayItem;
import net.sf.saxon.om.AtomicSequence;
import net.sf.saxon.om.NameChecker;
import net.sf.saxon.om.NamespaceUri;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.s9api.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.type.AtomicType;
import net.sf.saxon.type.Converter;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.ValidationException;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.Whitespace;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static net.sf.saxon.s9api.streams.Predicates.hasLocalName;
import static net.sf.saxon.s9api.streams.Predicates.hasName;
import static net.sf.saxon.s9api.streams.Predicates.isDocument;
import static net.sf.saxon.s9api.streams.Predicates.isElement;
import static net.sf.saxon.s9api.streams.Predicates.isText;
/**
* This non-instantiable class provides a number of useful implementations of the {@link Step}
* interface, used to navigate XDM trees, typically as an argument to {@link XdmValue#select}.
*/
public class Steps {
/**
* Obtain a {@link Step} that selects the root node of the containing document (which may or may not
* be a document node)
* @return a step that delivers the root node of the tree containing the step's origin.
*/
public static Step root() {
return new Step() {
@Override
public Stream apply(XdmItem origin) {
return origin instanceof XdmNode
? Stream.of(((XdmNode)origin).getRoot())
: Stream.empty();
}
};
}
/**
* Obtain a {@link Step} that atomizes an item to produce a stream of atomic values. (Atomizing a node will
* usually produce a single atomic value, but in the case of schema-typed nodes using a list
* type, there may be more than one atomic value. Atomizing an array also returns multiple
* atomic values)
* @return a step that performs atomization
*/
public static Step atomize() {
return new Step() {
@Override
public Stream extends XdmAtomicValue> apply(XdmItem item) {
if (item instanceof XdmAtomicValue) {
return Stream.of((XdmAtomicValue)item);
} else if (item instanceof XdmNode) {
try {
return (XdmStream) ((XdmNode) item).getTypedValue().stream();
} catch (SaxonApiException e) {
throw new SaxonApiUncheckedException(e);
}
} else if (item instanceof XdmArray) {
try {
ArrayItem arrayItem = ((XdmArray)item).getUnderlyingValue();
AtomicSequence data = arrayItem.atomize();
return (XdmStream)XdmValue.wrap(data).stream();
} catch (XPathException e) {
throw new SaxonApiUncheckedException(new SaxonApiException(e));
}
} else {
throw new SaxonApiUncheckedException(new SaxonApiException("Cannot atomize supplied value"));
}
}
};
}
/**
* A step equivalent to the XPath "cast as" operator: the supplied item is atomized
* if necessary, and the resulting atomic values are cast to the required type
* @param type the target type of the cast
* @return a step that returns the result of the cast
*/
public static Step castAs(ItemType type) {
if (!ItemType.ANY_ATOMIC_VALUE.subsumes(type)) {
throw new IllegalArgumentException("Target of castAs must be an atomic type");
}
final net.sf.saxon.type.ItemType tType = type.getUnderlyingItemType().getPrimitiveItemType();
final ConversionRules rules = type.getConversionRules();
return atomize().then(new Step() {
@Override
public Stream extends XdmAtomicValue> apply(XdmItem xdmItem) {
try {
AtomicValue source = ((XdmAtomicValue)xdmItem).getUnderlyingValue();
Converter converter = rules.getConverter(source.getItemType(), (AtomicType)tType);
AtomicValue result = converter.convert(source).asAtomic();
return Stream.of((XdmAtomicValue) XdmValue.wrap(result));
} catch (ValidationException e) {
throw new SaxonApiUncheckedException(new SaxonApiException(e));
}
}
});
}
/**
* A {@code Step} to navigate from a node to its ancestors, in reverse document
* order (that is, nearest ancestor first, root node last)
*/
private final static Step ANCESTOR = new AxisStep(Axis.ANCESTOR);
/**
* A {@code Step} to navigate from a node to its ancestors, in reverse document
* order, with the node itself returned at the start of the sequence (that is, origin node first, root node last)
*/
private final static Step ANCESTOR_OR_SELF = new AxisStep(Axis.ANCESTOR_OR_SELF);
/**
* A {@code Step} to navigate from a node to its attributes
*/
private final static Step ATTRIBUTE = new AxisStep(Axis.ATTRIBUTE);
/**
* A Step to navigate from a node to its children
*/
private final static Step CHILD = new AxisStep(Axis.CHILD);
/**
* A Step to navigate from a node to its descendants, which are returned in document order
*/
private final static Step DESCENDANT = new AxisStep(Axis.DESCENDANT);
/**
* A Step to navigate from a node to its descendants, which are returned in document order,
* preceded by the origin node itself
*/
private final static Step DESCENDANT_OR_SELF = new AxisStep(Axis.DESCENDANT_OR_SELF);
/**
* A Step to navigate from a node to its following nodes (excluding descendants), which are returned in document order
*/
private final static Step FOLLOWING = new AxisStep(Axis.FOLLOWING);
/**
* A Step to navigate from a node to its following siblings, which are returned in document order
*/
private final static Step FOLLOWING_SIBLING = new AxisStep(Axis.FOLLOWING_SIBLING);
/**
* A {@code Step} to navigate from a node to its namespace nodes
*/
private final static Step NAMESPACE = new AxisStep(Axis.NAMESPACE);
/**
* A Step to navigate from a node to its parent
*/
private final static Step PARENT = new AxisStep(Axis.PARENT);
/**
* A Step to navigate from a node to its preceding siblings, which are returned in reverse document order
*/
private final static Step PRECEDING_SIBLING = new AxisStep(Axis.PRECEDING_SIBLING);
/**
* A Step to navigate from a node to its preceding nodes (excluding ancestors), which are returned in reverse document order
*/
private final static Step PRECEDING = new AxisStep(Axis.PRECEDING);
/**
* A Step to navigate from a node to itself (useful only if applying a predicate)
*/
private final static Step SELF = new AxisStep(Axis.SELF);
/**
* Obtain a Step that always returns an empty sequence, whatever the input
* @param the static item type of the result of the step
* @return a Step that always returns an empty sequence
*/
public static Step nothing() {
return new Step() {
@Override
public Stream apply(XdmItem xdmItem) {
return Stream.empty();
}
};
}
private static Predicate nodeTestPredicate (NodeTest test) {
return item -> test.test(item.getUnderlyingNode());
}
private static Predicate super XdmNode> localNamePredicate(String given) {
if ("*".equals(given)) {
return isElement();
} else {
return item -> {
NodeInfo node = item.getUnderlyingNode();
return node.getNodeKind() == Type.ELEMENT
&& node.getLocalPart().equals(given);
};
}
}
private static Predicate super XdmNode> expandedNamePredicate(String ns, String local) {
return expandedNamePredicate(NamespaceUri.of(ns), local);
}
private static Predicate super XdmNode> expandedNamePredicate(NamespaceUri nsUri, String local) {
return item -> {
NodeInfo node = item.getUnderlyingNode();
return node.getNodeKind() == Type.ELEMENT
&& node.getNamespaceUri().equals(nsUri) && node.getLocalPart().equals(local);
};
}
/**
* Obtain a {@code Step} to navigate from a node to its ancestors, in reverse document
* order (that is, nearest ancestor first, root node last)
* @return a Step that selects all nodes on the ancestor axis
*/
public static Step ancestor() {
return ANCESTOR;
}
/**
* Obtain a {@code Step} that navigates from a node to its ancestor elements having a specified
* local name, irrespective of the namespace. The nodes are returned in reverse document
* order (that is, nearest ancestor first, root node last)
*
* @param localName the local name of the ancestors to be selected by the {@code Step},
* or "*" to select all ancestors that are element nodes
* @return a {@code Step}, which selects the ancestors of a supplied node that have the
* required local name.
*/
public static Step ancestor(String localName) {
return ancestor().where(localNamePredicate(localName));
}
/**
* Return a {@code Step} that navigates from a node to its ancestors having a specified
* namespace URI and local name, in reverse document order (that is, nearest ancestor first,
* root node last)
*
* @param uri the namespace URI of the ancestors to be selected by the {@code Step}
* @param localName the local name of the ancestors to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @return a {@code Step}, which selects the ancestors (at most one) of a supplied node that have the
* required local name and namespace URI.
*/
public static Step ancestor(String uri, String localName) {
return ancestor().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@code Step} that filters the nodes found on the ancestor axis using a supplied {@code Predicate}.
* Nodes are returned in reverse document order (that is, nearest ancestor first, root node last)
* The function call {@code ancestor(predicate)} is equivalent to {@code ANCESTOR.where(predicate)}.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the ancestor axis using a supplied {@code Predicate}.
*/
public static Step ancestor(Predicate super XdmNode> filter) {
return ancestor().where(filter);
}
/**
* Obtain a {@code Step} to navigate from a node to its ancestors, in reverse document
* order, with the node itself returned at the start of the sequence (that is, origin node first,
* root node last)
* @return a Step that selects all nodes on the ancestor-or-self axis
*/
public static Step ancestorOrSelf() {
return ANCESTOR_OR_SELF;
}
/**
* Obtain a {@code Step} that navigates from a node to its ancestor elements having a specified
* local name, irrespective of the namespace. The nodes are returned in reverse document
* order (that is, nearest ancestor first, root node last), and include the node itself
*
* @param localName the local name of the ancestors to be selected by the {@code Step},
* or "*" to select all ancestor-or-self nodes that are element nodes
* @return a {@code Step}, which selects the ancestors-or-self of a supplied node that have the
* required local name.
*/
public static Step ancestorOrSelf(String localName) {
return ancestorOrSelf().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to its ancestors-or-self having a specified
* namespace URI and local name, in reverse document order (that is, nearest ancestor first,
* root node last)
*
* @param uri the namespace URI of the ancestors to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the ancestors to be selected by the {@code Step}
* @return a {@code Step}, which selects the ancestors-or-self of a supplied node that have the
* required local name and namespace URI.
*/
public static Step ancestorOrSelf(String uri, String localName) {
return ancestorOrSelf().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@code Step} that filters the nodes found on the ancestor-or-self axis using a supplied {@code Predicate}.
* Nodes are returned in reverse document order (that is, origin node first,
* root node last)
* The function call {@code ancestorOrSelf(predicate)} is equivalent to {@code ANCESTOR_OR_SELF.where(predicate)}.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the ancestor-or-self axis using a supplied {@code Predicate}.
*/
public static Step ancestorOrSelf(Predicate super XdmNode> filter) {
return ancestorOrSelf().where(filter);
}
/**
* Obtain a {@code Step} to navigate from a node to its attributes
* @return a Step that selects all nodes on the ancestor axis
*/
public static Step attribute() {
return ATTRIBUTE;
}
/**
* Obtain a {@code Step} that navigates from a node to its attributes having a specified
* local name, irrespective of the namespace
*
* @param localName the local name of the attributes to be selected by the {@code Step}, or
* "*" to select all attributes
* @return a {@code Step}, which selects the attributes of a supplied node that have the
* required local name.
*/
public static Step attribute(String localName) {
return "*".equals(localName) ? attribute() : attribute().where(hasLocalName(localName));
}
/**
* Return a {@code Step} that navigates from a node to its attribute having a specified
* namespace URI and local name
*
* @param uri the namespace URI of the attributes to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the attributes to be selected by the {@code Step}
* @return a {@code Step}, which selects the attributes (at most one) of a supplied node that have the
* required local name and namespace URI.
*/
public static Step attribute(String uri, String localName) {
return attribute().where(hasName(uri, localName));
}
/**
* Obtain a {@code Step} that filters the nodes found on the attribute axis using a supplied {@code Predicate}.
* The function call {@code attribute(predicate)} is equivalent to {@code ATTRIBUTE.where(predicate)}.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the attribute axis using a supplied {@code Predicate}.
*/
public static Step attribute(Predicate super XdmNode> filter) {
return attribute().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its children
* @return a Step that selects all nodes on the child axis
*/
public static Step child() {
return CHILD;
}
/**
* Obtain a {@code Step} that navigates from a node to the element children having a specified
* local name, irrespective of the namespace
* @param localName the local name of the child elements to be selected by the Step,
* or "*" to select all children that are element nodes
* @return a {@code Step}, which selects the element children of a supplied node that have the
* required local name.
*/
public static Step child(String localName) {
return child().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the element children having a specified
* namespace URI and local name
* @param uri the namespace URI of the child elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the child elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the element children of a supplied node that have the
* required local name and namespace URI.
*/
public static Step child(String uri, String localName) {
return child().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@code Step} that filters the nodes found on the child axis using a supplied {@code Predicate}.
* The function call {@code child(predicate)} is equivalent to {@code CHILD.where(predicate)}.
* For example, {@code child(isElement())} returns a Step that selects the element node children
* of a given node.
* @param filter the predicate to be applied
* @return a Step that filters the nodes found on the child axis using a supplied {@code Predicate}.
*/
public static Step child(Predicate super XdmNode> filter) {
return child().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its descendants, which are returned in document order
* @return a Step that selects all nodes on the descendant axis
*/
public static Step descendant() {
return DESCENDANT;
}
/**
* Obtain a {@code Step} that navigates from a node to the descendant elements having a specified
* local name, irrespective of the namespace. These are returned in document order.
*
* @param localName the local name of the descendant elements to be selected by the {@code Step},
* or "*" to select all descendants that are element nodes
* @return a {@code Step}, which selects the element descendants of a supplied node that have the
* required local name.
*/
public static Step descendant(String localName) {
return descendant().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the element descendants having a specified
* namespace URI and local name. These are returned in document order.
*
* @param uri the namespace URI of the descendant elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the descendant elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the element descendants of a supplied node that have the
* required local name and namespace URI.
*/
public static Step descendant(String uri, String localName) {
return descendant().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@link Step} to navigate from a node to its descendants, which are returned in document order,
* preceded by the origin node itself
* @return a Step that selects all nodes on the descendant-or-self axis
*/
public static Step descendantOrSelf() {
return DESCENDANT_OR_SELF;
}
/**
* Obtain a Step that filters the nodes found on the descendant axis using a supplied {@code Predicate}.
* The function call {@code descendant(predicate)} is equivalent to {@code DESCENDANT.where(predicate)}.
* For example, {@code descendant(isElement())} returns a Step that selects the element node descendants
* of a given node, while {@code descendant(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in document order.
*
* @param filter the predicate to be applied
* @return a Step that filters the nodes found on the descendant axis using a supplied Predicate.
*/
public static Step descendant(Predicate super XdmNode> filter) {
return descendant().where(filter);
}
/**
* Obtain a {@code Step} that navigates from a node to the descendant-or-self elements having a specified
* local name, irrespective of the namespace. These are returned in document order, preceded by the origin node
* itself if it matches the conditions.
*
* @param localName the local name of the descendant-or-self elements to be selected by the {@code Step},
* or "*" to select all descendant-or-self nodes that are element nodes
* @return a {@code Step}, which selects the element children of a supplied node that have the
* required local name.
*/
public static Step descendantOrSelf(String localName) {
return descendantOrSelf().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the descendant-or-self elements having a specified
* namespace URI and local name. These are returned in document order, preceded by the origin node
* itself if it matches the conditions.
*
* @param uri the namespace URI of the descendant-or-self elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the descendant-or-self elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the element descendants-or-self of a supplied node that have a
* given local name and namespace URI.
*/
public static Step descendantOrSelf(String uri, String localName) {
return descendantOrSelf().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a Step that filters the nodes found on the descendant-or-self axis using a supplied {@code Predicate}.
* The function call {@code descendant(predicate)} is equivalent to {@code DESCENDANT.where(predicate)}.
* For example, {@code descendant(isElement())} returns a Step that selects the element node descendants
* of a given node, while {@code descendant(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in document order.
*
* @param filter the predicate to be applied
* @return a Step that filters the nodes found on the descendant-or-self axis using a supplied Predicate.
*/
public static Step descendantOrSelf(Predicate super XdmNode> filter) {
return descendantOrSelf().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its following nodes
* (excluding descendants), which are returned in document order
* @return a Step that selects all nodes on the following axis
*/
public static Step following() {
return FOLLOWING;
}
/**
* Obtain a {@code Step} that navigates from a node to the following elements having a specified
* local name, irrespective of the namespace. These are returned in document order.
*
* @param localName the local name of the following elements to be selected by the {@code Step},
* or "*" to select all following nodes that are elements
* @return a {@code Step}, which selects the following elements of a supplied node that have the
* required local name.
*/
public static Step following(String localName) {
return following().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the following elements having a specified
* namespace URI and local name. These are returned in document order.
*
* @param uri the namespace URI of the following elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the following elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the following elements of a supplied node that have the
* required local name and namespace URI.
*/
public static Step following(String uri, String localName) {
return following().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a Step that filters the nodes found on the following axis using a supplied {@code Predicate}.
* The function call {@code followingSibling(predicate)} is equivalent to {@code FOLLOWING_SIBLING.where(predicate)}.
* For example, {@code followingSibling(isElement())} returns a {@code Step} that selects the following sibling elements
* of a given node, while {@code followingSibling(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in document order.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the following axis using a supplied {@code Predicate}.
*/
public static Step following(Predicate super XdmNode> filter) {
return following().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its following siblings, which are returned
* in document order
* @return a Step that selects all nodes on the following-sibling axis
*/
public static Step followingSibling() {
return FOLLOWING_SIBLING;
}
/**
* Obtain a {@code Step} that navigates from a node to the following sibling elements having a specified
* local name, irrespective of the namespace. These are returned in document order.
*
* @param localName the local name of the following sibling elements to be selected by the {@code Step},
* or "*" to select all following siblings that are element nodes
* @return a {@code Step}, which selects the following sibling elements of a supplied node that have the
* required local name.
*/
public static Step followingSibling(String localName) {
return followingSibling().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the following sibling elements having a specified
* namespace URI and local name. These are returned in document order.
*
* @param uri the namespace URI of the following sibling elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the following sibling elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the following sibling elements of a supplied node that have the
* required local name and namespace URI.
*/
public static Step followingSibling(String uri, String localName) {
return followingSibling().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a Step that filters the nodes found on the following sibling axis using a supplied {@code Predicate}.
* The function call {@code followingSibling(predicate)} is equivalent to {@code FOLLOWING_SIBLING.where(predicate)}.
* For example, {@code followingSibling(isElement())} returns a {@code Step} that selects the following sibling elements
* of a given node, while {@code followingSibling(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in document order.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the following sibling axis using a supplied {@code Predicate}.
*/
public static Step followingSibling(Predicate super XdmNode> filter) {
return followingSibling().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its namespace nodes
* @return a Step that selects all nodes on the namespace axis
*/
public static Step namespace() {
return NAMESPACE;
}
/**
* Obtain a {@code Step} that navigates from a node to its namespaces having a specified
* local name. The local name of a namespace node corresponds to the prefix used in the
* namespace binding.
*
* @param localName the local name (representing the namespace prefix) of the namespace nodes
* to be selected by the {@code Step}, or "*" to select all namespaces
* @return a {@code Step}, which selects the namespaces of a supplied node that have a
* given local name (prefix).
*/
public static Step namespace(String localName) {
return "*".equals(localName) ? namespace() : namespace().where(hasLocalName(localName));
}
/**
* Obtain a {@code Step} that filters the nodes found on the namespace axis using a supplied {@code Predicate}.
* The function call {@code namespace(predicate)} is equivalent to {@code namespace().where(predicate)}.
* For example, {@code namespace(eq("http://www.w3.org/1999/XSL/Transform")} selects a namespace node
* that binds a prefix to the XSLT namespace.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the namespace axis using a supplied
* {@code Predicate}.
*/
public static Step namespace(Predicate super XdmNode> filter) {
return namespace().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its parent
* @return a Step that selects all nodes on the parent axis (of which there is at most one)
*/
public static Step parent() {
return PARENT;
}
/**
* Obtain a {@code Step} that navigates from a node to the parent element provided it has a specified
* local name, irrespective of the namespace
*
* @param localName the local name of the parent element to be selected by the Step,
* or "*" to select the parent node provided it is an element
* @return a {@code Step}, which selects the parent of a supplied node provided it is an element with the
* required local name.
*/
public static Step parent(String localName) {
return parent().where(isElement()).where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the parent element provided it has a specified
* namespace URI and local name
*
* @param uri the namespace URI of the parent element to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the parent element to be selected by the {@code Step}
* @return a {@code Step}, which selects the parent element of a supplied node provided it is an
* element with the required local name and namespace URI.
*/
public static Step parent(String uri, String localName) {
return parent().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@code Step} that filters the node found on the parent axis using a supplied {@code Predicate}.
* The function call {@code parent(predicate)} is equivalent to {@code parent().where(predicate)}.
* For example, {@code parent(isElement())} returns a Step that selects the parent node provided
* it is an element
*
* @param filter the predicate to be applied
* @return a Step that filters the nodes found on the parent axis using a supplied {@code Predicate}.
*/
public static Step parent(Predicate super XdmNode> filter) {
return parent().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its preceding siblings, which are returned
* in reverse document order
* @return a Step that selects all nodes on the preceding-sibling axis
*/
public static Step precedingSibling() {
return PRECEDING_SIBLING;
}
/**
* Obtain a {@code Step} that navigates from a node to the preceding sibling elements having a specified
* local name, irrespective of the namespace. These are returned in reverse document order.
*
* @param localName the local name of the preceding sibling elements to be selected by the {@code Step},
* or "*" to select all descendants that are element nodes
* @return a {@code Step}, which selects the preceding sibling elements of a supplied node that have the
* required local name.
*/
public static Step precedingSibling(String localName) {
return precedingSibling().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the preceding sibling elements having a specified
* namespace URI and local name. These are returned in reverse document order.
*
* @param uri the namespace URI of the preceding sibling elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the preceding sibling elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the preceding sibling elements of a supplied node that have the
* required local name and namespace URI.
*/
public static Step precedingSibling(String uri, String localName) {
return precedingSibling().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a Step that filters the nodes found on the preceding sibling axis using a supplied {@code Predicate}.
* The function call {@code precedingSibling(predicate)} is equivalent to {@code precedingSibling().where(predicate)}.
* For example, {@code precedingSibling(isElement())} returns a {@code Step} that selects the preceding sibling elements
* of a given node, while {@code precedingSibling(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in reverse document order.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the following sibling axis using a supplied {@code Predicate}.
*/
public static Step precedingSibling(Predicate super XdmNode> filter) {
return precedingSibling().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to its preceding nodes (excluding ancestors),
* which are returned in reverse document order
* @return a Step that selects all nodes on the preceding axis
*/
public static Step preceding() {
return PRECEDING;
}
/**
* Obtain a {@code Step} that navigates from a node to the preceding elements having a specified
* local name. These are returned in reverse document order.
*
* @param localName the local name of the preceding elements to be selected by the {@code Step},
* or "*" to select all descendants that are element nodes
* @return a {@code Step}, which selects the preceding elements of a supplied node that have the
* required local name.
*/
public static Step preceding(String localName) {
return preceding().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to the preceding elements having a specified
* namespace URI and local name. These are returned in reverse document order.
*
* @param uri the namespace URI of the preceding elements to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the preceding sibling elements to be selected by the {@code Step}
* @return a {@code Step}, which selects the preceding sibling elements of a supplied node that have the
* required local name and namespace URI.
*/
public static Step preceding(String uri, String localName) {
return preceding().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a Step that filters the nodes found on the preceding axis using a supplied {@code Predicate}.
* The function call {@code preceding(predicate)} is equivalent to {@code PRECEDING.where(predicate)}.
* For example, {@code preceding(isElement())} returns a {@code Step} that selects the preceding elements
* of a given node, while {@code preceding(exists(attribute("id")))} selects those that have an attribute
* named "id". These are returned in reverse document order.
*
* @param filter the predicate to be applied
* @return a {@code Step} that filters the nodes found on the following sibling axis using a supplied {@code Predicate}.
*/
public static Step preceding(Predicate super XdmNode> filter) {
return preceding().where(filter);
}
/**
* Obtain a {@link Step} to navigate from a node to itself (useful only if applying a predicate)
* @return a Step that selects all nodes on the self axis (that is, the node itself)
*/
public static Step self() {
return SELF;
}
/**
* Obtain a {@code Step} that navigates from a node to itself provided it is an element with a specified
* local name, irrespective of the namespace
*
* @param localName the local name of the element to be selected by the Step,
* or "*" to select the node provided that it is an element node
* @return a {@code Step}, which selects the supplied node provided it has a
* given local name.
*/
public static Step self(String localName) {
return self().where(localNamePredicate(localName));
}
/**
* Obtain a {@code Step} that navigates from a node to itself provided it has a specified
* namespace URI and local name
*
* @param uri the namespace URI of the element to be selected by the {@code Step}:
* supply a zero-length string to indicate the null namespace
* @param localName the local name of the element to be selected by the {@code Step}
* @return a {@code Step}, which selects the supplied node provided it is an element with a
* given local name and namespace URI.
*/
public static Step self(String uri, String localName) {
return self().where(expandedNamePredicate(uri, localName));
}
/**
* Obtain a {@code Step} that filters the node found on the self axis using a supplied {@code Predicate}.
* The function call {@code self(predicate)} is equivalent to {@code SELF.where(predicate)}.
* For example, {@code self(isElement())} returns a Step that selects the supplied node provided
* it is an element
*
* @param filter the predicate to be applied
* @return a Step that filters the nodes found on the parent axis using a supplied {@code Predicate}.
*/
public static Step self(Predicate super XdmNode> filter) {
return self().where(filter);
}
/**
* Obtain a {@code Step} that returns text nodes found on the child axis.
* The function call {@code text()} is equivalent to {@code child().where(isText())}.
* For example, {@code self(isElement())} returns a Step that selects the supplied node provided
* it is an element
* @return a Step that returns the text nodes found on the child axis.
*/
public static Step text() {
return child().where(isText());
}
/**
* Construct a path as a composite {@link Step} from a sequence of steps composed together
* @param steps the constituent steps in the path
* @return a composite step
*/
@SafeVarargs
public static Step extends XdmNode> path(Step extends XdmNode>... steps) {
return pathFromList(Arrays.asList(steps));
}
private static Step extends XdmNode> pathFromList(List> steps) {
if (steps.isEmpty()) {
return nothing();
} else if (steps.size() == 1) {
return steps.get(0);
} else {
return steps.get(0).then(pathFromList(steps.subList(1, steps.size())));
}
}
/**
* Construct a simple path consisting solely of simple child, attribute, descendant, root,
* and parent steps. For example, {@code path("div3", "head", "@style")} selects
* the same nodes as the XPath 2.0 expression {@code child::*:div3/child::*:head/attribute::*:style}.
* @param steps a sequence of strings. Each string must be one of the following:
*
* - A plain NCName (for example "item") selects child nodes by matching
* local-name (the namespace is ignored)
* - An NCName preceded by "@" (for example, "@code"), selects attribute nodes
* by matching local-name (again, ignoring any namespace)
* - The string "*" selects all child elements, regardless of name
* - The string "/" selects the root node of the tree, provided it
* is a document node
* - The string ".." selects the parent node
* - The string "//" selects all descendant-or-self nodes (note,
* this does not involve finding the root of the tree: it corresponds to a binary
* '//' operator in XPath, not to an initial '//')
*
.
* For more complex paths, see {@link #path(Step...)}
* @return a composite Step representing this sequence of steps
* @throws IllegalArgumentException if any of the strings is invalid according
* to these rules.
*/
public static Step extends XdmNode> path(String... steps) {
List> pathSteps = new ArrayList<>();
for (String step : steps) {
if (step.equals("/")) {
pathSteps.add(root().where(isDocument()));
} else if (step.equals("..")) {
pathSteps.add(parent());
} else if (step.equals("*")) {
pathSteps.add(child(isElement()));
} else if (step.equals("//")) {
pathSteps.add(descendantOrSelf());
} else if (step.startsWith("@")) {
String name = step.substring(1);
if (!NameChecker.isValidNCName(name)) {
throw new IllegalArgumentException("Invalid attribute name " + name);
}
pathSteps.add(attribute(name));
} else {
if (!NameChecker.isValidNCName(step)) {
throw new IllegalArgumentException("Invalid element name " + step);
}
pathSteps.add(child(step));
}
}
return pathFromList(pathSteps);
}
/**
* Obtain a Step whose effect is to tokenize the supplied item on whitespace
* boundaries, returning a sequence of strings as {@code XdmAtomicValue} instances.
*
* Note: the tokenize step, when applied to a string with leading and trailing whitespace,
* has the effect of removing this whitespace. In addition to its primary role, the function
* can therefore be useful for trimming the content of a single string.
*
* Usage example: {@code child().where(some(attribute("id").then(tokenize())).eq("a123"))}
* selects child elements that have an attribute named "id" whose value contains the token
* "a123".
*
* @return a Step whose effect is to take a supplied item and split its string
* value into a sequence of xs:string instances
*/
public static Step tokenize() {
return new Step() {
@Override
public Stream apply(XdmItem item) {
AtomicIterator iter = new Whitespace.Tokenizer(item.getUnderlyingValue().getUnicodeStringValue());
return XdmSequenceIterator.ofAtomicValues(iter).stream();
}
};
}
/**
* Obtain a Step whose effect is to interpret the supplied item as an xs:ID value
* and return the nodes (in a given document) that have that string as their ID.
* @param doc the root node (document node) of the document within which the ID
* value should be sought
* @return a Step whose effect is to take a supplied item and split its string
* value into a sequence of xs:string instances
*/
public static Step id(XdmNode doc) {
return new Step() {
@Override
public Stream apply(XdmItem item) {
if (doc.getNodeKind() != XdmNodeKind.DOCUMENT) {
throw new IllegalArgumentException("id() - argument is not a document node");
}
NodeInfo target = doc.getUnderlyingNode().getTreeInfo().selectID(item.getStringValue(), true);
return target==null
? Stream.empty()
: Stream.of((XdmNode)XdmNode.wrap(target));
}
};
}
}
// Copyright (c) 2018-2023 Saxonica Limited