All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.sapling.SaplingElement Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.sapling;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.event.ReceiverOption;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.lib.ParseOptions;
import net.sf.saxon.ma.trie.ImmutableHashTrieMap;
import net.sf.saxon.ma.trie.ImmutableList;
import net.sf.saxon.ma.trie.ImmutableMap;
import net.sf.saxon.ma.trie.TrieKVP;
import net.sf.saxon.om.*;
import net.sf.saxon.s9api.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.Untyped;

import java.util.Objects;

/**
 * An element node on a sapling tree. The node has the following properties:
 *
 * 
    *
  • A name. This is a QName, potentially containing a prefix, URI, and local-name.
  • * *
  • An ordered list of children. The children may be elements, text nodes, comments, or processing instructions.
  • * *
  • A set of attributes. An attribute has a name (which is a QName), and a string value; attribute names must * be distinct. The prefix and URI of an attribute must either both be empty or both non-empty. Attributes in this * model are not nodes, they are simply properties of an element node. Attributes for an element are unordered.
  • * *
  • A set of namespace bindings (prefix/URI pairs, where the prefix may be empty, and the URI may be empty * if and only if the prefix is empty). The namespace bindings implicitly include the prefix/URI combination * for the element name, and the prefix/URI combinations for all attributes. They may also include additional * namespace bindings. As far as the sapling tree is concerned, namespace bindings are not inherited from parent * nodes; each element must contain all the namespace bindings that it needs. When a sapling tree is converted * to a full tree, however, namespace bindings on a parent node will propagate to its children.
  • *
* *

Like all sapling nodes, a {@code SaplingElement} is immutable. All operations such as adding children * or attributes deliver a new element node. A sapling node generally exists only transiently during tree * construction; to make use of the constructed tree, it will usually be converted to a regular tree when * construction is complete, using {@link #toXdmNode(Processor)} or {@link #toNodeInfo(Configuration)}.

*/ public class SaplingElement extends SaplingNode { private final StructuredQName nodeName; private ImmutableList reversedChildren; private ImmutableMap attributes = emptyAttributes(); private NamespaceMap namespaces = NamespaceMap.emptyMap(); @CSharpReplaceBody(code="return System.Collections.Immutable.ImmutableDictionary.Empty;") private static ImmutableMap emptyAttributes() { return ImmutableHashTrieMap.empty(); } /** * Create an empty element, in no namespace * @param name the name of the element. This should take the form of an NCName, but the * current implementation does not check this. */ public SaplingElement(String name) { Objects.requireNonNull(name); reversedChildren = emptyNodeList(); nodeName = StructuredQName.fromEQName((name)); } /** * Create an empty element, with a name supplied as a QName. * * @param name the name of the element, as a QName. If the prefix of the QName is non-empty, * then the URI part must also be non-empty. * @throws IllegalArgumentException if the name contains a prefix but no URI */ public SaplingElement(QName name) { Objects.requireNonNull(name); reversedChildren = emptyNodeList(); nodeName = name.getStructuredQName(); if (!nodeName.getPrefix().isEmpty() && nodeName.getURI().isEmpty()) { throw new IllegalArgumentException("No namespace URI for prefixed element name: " + name); } namespaces = NamespaceMap.of(nodeName.getPrefix(), nodeName.getURI()); } private SaplingElement(StructuredQName name) { this.nodeName = name; reversedChildren = emptyNodeList(); } @Override public int getNodeKind() { return Type.ELEMENT; } private SaplingElement copy() { SaplingElement e2 = new SaplingElement(nodeName); e2.reversedChildren = reversedChildren; e2.attributes = attributes; e2.namespaces = namespaces; return e2; } /** * Add a number of child nodes to a document node, returning a new document node * with additional children beyond those already present. The target document is not modified, * neither are the added children. *

Note: because adding a child always creates a new parent element, it is impossible * to create cycles by adding a node to itself, directly or indirectly.

* @param children The nodes to be added as children. * The supplied nodes are added in order after any existing children. * * @return the new parent element node * @throws IllegalArgumentException if any of the nodes supplied as an argument is * a document node. */ public SaplingElement withChild(SaplingNode... children) { SaplingElement e2 = copy(); for (SaplingNode child : children) { switch (child.getNodeKind()) { case Type.DOCUMENT: throw new IllegalArgumentException("Cannot add document node as a child of an element node"); case Type.ELEMENT: case Type.TEXT: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: e2.reversedChildren = e2.reversedChildren.prepend(child); break; } } return e2; } /** * Add a text node as a child to this element, returning a new sapling element node. * This is a convenience method: e.withText("value") is equavalent to * e.withChild(text("value")) * @param value the string value of a new text node, which will be added as a child * to this element after any existing children * @return a new element node, identical to this element but with the new text node as * an additional child */ public SaplingElement withText(String value) { return withChild(new SaplingText(value)); } private SaplingElement withAttribute(StructuredQName name, String value) { SaplingElement e2 = copy(); e2.attributes = e2.attributes.put(name, value); return e2; } /** * Add or replace an attribute of the element node, returning a new element node with a modified * set of attributes * @param name the name of the attribute to be added or replaced; this represents the local part of * a no-namespace QName. The name must be in the form of an NCName, but the current implementation * does not check this. * @param value the (new) value of the attribute * @return a new sapling element node, identical to the target node except for the added or replaced attribute */ public SaplingElement withAttr(String name, String value) { return withAttribute(new StructuredQName("", "", name), value); } /** * Add or replace an attribute of the element node, returning a new element node with a modified * set of attributes * * @param name the name of the attribute to be added or replaced, as a QName * @param value the (new) value of the attribute * @return a new sapling element node, identical to the target node except for the added or replaced attribute * @throws IllegalArgumentException if the prefix of the attribute name is empty and the URI is not, or * if the URI is empty and the prefix is not * @throws IllegalStateException if the prefix/uri binding is incompatible with the existing prefix/uri * bindings on the element */ public SaplingElement withAttr(QName name, String value) { StructuredQName attName = name.getStructuredQName(); if (attName.getPrefix().isEmpty() && !attName.getURI().isEmpty()) { throw new IllegalArgumentException("An attribute whose name is in a namespace must have a prefix"); } withNamespace(attName.getPrefix(), attName.getURI()); return withAttribute(attName, value); } /** * Add a namespace binding for a prefix/URI pair. Namespace bindings are added automatically for * prefixes used in element and attribute names; they only need to be added explicitly if the binding * is additional to those in element and attribute names. A namespace binding for the XML namespace * is implicitly present on every element. * @param prefix the namespace prefix. This must either be a zero length string, or it must take the * form of an NCName, but this constraint is not currently enforced. * @param uri the namespace URI. If this is the empty string, then (a) if the prefix is empty, the * namespace binding is ignored; (b) otherwise, an exception is raised (namespace undeclarations * are not permitted). * @return a new element node, identical to the original except for the additional namespace binding * @throws IllegalArgumentException if the URI is empty and the prefix is not * @throws IllegalStateException if the element already has a namespace binding for this prefix, with * a different URI. */ public SaplingElement withNamespace(String prefix, String uri) { if (uri.isEmpty()) { if (prefix.isEmpty()) { return this; } else { throw new IllegalArgumentException("Cannot bind non-empty prefix to empty URI"); } } String existingURI = namespaces.getURI(prefix); if (existingURI != null) { if (existingURI.equals(uri)) { return this; } else { throw new IllegalStateException("Inconsistent namespace bindings for prefix '" + prefix + "'"); } } SaplingElement e2 = copy(); e2.namespaces = namespaces.put(prefix, uri); return e2; } @Override public void deliver(Receiver receiver, ParseOptions options) throws XPathException { final Configuration config = receiver.getPipelineConfiguration().getConfiguration(); final NamePool namePool = config.getNamePool(); NamespaceMap ns = namespaces; if (!nodeName.getURI().isEmpty()) { ns = ns.put(nodeName.getPrefix(), nodeName.getURI()); } AttributeMap atts = EmptyAttributeMap.getInstance(); for (TrieKVP attribute : attributes) { StructuredQName qName = attribute.getKey(); atts = atts.put(new AttributeInfo(new FingerprintedQName(qName, namePool), BuiltInAtomicType.UNTYPED_ATOMIC, attribute.getValue(), Loc.NONE, ReceiverOption.NONE)); if (!qName.getURI().isEmpty()) { ns = ns.put(qName.getPrefix(), qName.getURI()); } } receiver.startElement(new FingerprintedQName(nodeName, namePool), Untyped.getInstance(), atts, ns, Loc.NONE, ReceiverOption.NONE); ImmutableList children = reversedChildren.reverse(); for (SaplingNode node : children) { node.deliver(receiver, null); } receiver.endElement(); } /** * Convert the sapling element to a regular element node, returning the {@link NodeInfo} object * representing the parentless element node at the root of the resulting tree * * @param config the Saxon Configuration * @return the parentless element node at the root of the constructed tree. The implementation model for the * tree will be the default tree model of the Configuration * @throws XPathException if construction fails; this could happen if constraints have been violated */ public NodeInfo toNodeInfo(Configuration config) throws XPathException { PipelineConfiguration pipe = config.makePipelineConfiguration(); TreeModel treeModel = config.getParseOptions().getModel(); Builder builder = treeModel.makeBuilder(pipe); builder.open(); deliver(builder, null); builder.close(); return builder.getCurrentRoot(); } /** * Convert the sapling element to a regular element node, returning the {@link XdmNode} object * representing the parentless element node at the root of the resulting tree * * @param processor the s9api Processor object that is to own the resulting tree * @return the element node at the root of the constructed tree. The implementation model for the * tree will be the default tree model of the Processor's Configuration * @throws SaxonApiException if construction fails; this could happen if constraints have been violated */ public XdmNode toXdmNode(Processor processor) throws SaxonApiException { try { return (XdmNode) XdmValue.wrap(toNodeInfo(processor.getUnderlyingConfiguration())); } catch (XPathException e) { throw new SaxonApiException(e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy