    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath.ri.model.jdom;
    019
    020import java.util.List;
    021import java.util.Locale;
    022
    023import org.apache.commons.jxpath.JXPathAbstractFactoryException;
    024import org.apache.commons.jxpath.JXPathContext;
    025import org.apache.commons.jxpath.JXPathException;
    026import org.apache.commons.jxpath.ri.Compiler;
    027import org.apache.commons.jxpath.ri.NamespaceResolver;
    028import org.apache.commons.jxpath.ri.QName;
    029import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
    030import org.apache.commons.jxpath.ri.compiler.NodeTest;
    031import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
    032import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
    033import org.apache.commons.jxpath.ri.model.NodeIterator;
    034import org.apache.commons.jxpath.ri.model.NodePointer;
    035import org.apache.commons.jxpath.util.TypeUtils;
    036import org.jdom.Attribute;
    037import org.jdom.CDATA;
    038import org.jdom.Comment;
    039import org.jdom.Document;
    040import org.jdom.Element;
    041import org.jdom.Namespace;
    042import org.jdom.ProcessingInstruction;
    043import org.jdom.Text;
    044
    045/**
    046 * A Pointer that points to a DOM node.
    047 */
    048public class JDOMNodePointer extends NodePointer {
    049
    050    private static final long serialVersionUID = -6346532297491082651L;
    051    /** XML ns uri */
    052    public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
    053    /** XMLNS ns uri */
    054    public static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
    055
    056    /**
    057     * Tests whether two strings are == or .equals()
    058     *
    059     * @param s1 string 1
    060     * @param s2 string 2
    061     * @return true if equal
    062     */
    063    private static boolean equalStrings(String s1, String s2) {
    064        if (s1 == s2) {
    065            return true;
    066        }
    067        s1 = s1 == null ? "" : s1.trim();
    068        s2 = s2 == null ? "" : s2.trim();
    069        return s1.equals(s2);
    070    }
    071
    072    /**
    073     * Find the nearest occurrence of the specified attribute on the specified and enclosing elements.
    074     *
    075     * @param n        current node
    076     * @param attrName attribute name
    077     * @param ns       Namespace
    078     * @return attribute value
    079     */
    080    protected static String findEnclosingAttribute(Object n, final String attrName, final Namespace ns) {
    081        while (n != null) {
    082            if (n instanceof Element) {
    083                final Element e = (Element) n;
    084                final String attr = e.getAttributeValue(attrName, ns);
    085                if (attr != null && !attr.isEmpty()) {
    086                    return attr;
    087                }
    088            }
    089            n = nodeParent(n);
    090        }
    091        return null;
    092    }
    093
    094    /**
    095     * Gets the local name of the specified node.
    096     *
    097     * @param node to check
    098     * @return String local name
    099     */
    100    public static String getLocalName(final Object node) {
    101        if (node instanceof Element) {
    102            return ((Element) node).getName();
    103        }
    104        if (node instanceof Attribute) {
    105            return ((Attribute) node).getName();
    106        }
    107        return null;
    108    }
    109
    110    /**
    111     * Gets the ns uri of the specified node.
    112     *
    113     * @param node Node to check
    114     * @return String
    115     */
    116    private static String getNamespaceURI(final Object node) {
    117        if (node instanceof Element) {
    118            final Element element = (Element) node;
    119            String ns = element.getNamespaceURI();
    120            if ("".equals(ns)) {
    121                ns = null;
    122            }
    123            return ns;
    124        }
    125        return null;
    126    }
    127
    128    /**
    129     * Gets the prefix from a given node.
    130     *
    131     * @param node to check
    132     * @return String
    133     */
    134    public static String getPrefix(final Object node) {
    135        if (node instanceof Element) {
    136            final String prefix = ((Element) node).getNamespacePrefix();
    137            return prefix == null || prefix.isEmpty() ? null : prefix;
    138        }
    139        if (node instanceof Attribute) {
    140            final String prefix = ((Attribute) node).getNamespacePrefix();
    141            return prefix == null || prefix.isEmpty() ? null : prefix;
    142        }
    143        return null;
    144    }
    145
    146    /**
    147     * Gets the parent of the specified node.
    148     *
    149     * @param node to check
    150     * @return parent Element
    151     */
    152    private static Element nodeParent(final Object node) {
    153        if (node instanceof Element) {
    154            final Object parent = ((Element) node).getParent();
    155            return parent instanceof Element ? (Element) parent : null;
    156        }
    157        if (node instanceof Text) {
    158            return (Element) ((Text) node).getParent();
    159        }
    160        if (node instanceof CDATA) {
    161            return (Element) ((CDATA) node).getParent();
    162        }
    163        if (node instanceof ProcessingInstruction) {
    164            return (Element) ((ProcessingInstruction) node).getParent();
    165        }
    166        if (node instanceof Comment) {
    167            return (Element) ((Comment) node).getParent();
    168        }
    169        return null;
    170    }
    171
    172    /**
    173     * Execute test against node on behalf of pointer.
    174     *
    175     * @param pointer Pointer
    176     * @param node    to test
    177     * @param test    to execute
    178     * @return true if node passes test
    179     */
    180    public static boolean testNode(final NodePointer pointer, final Object node, final NodeTest test) {
    181        if (test == null) {
    182            return true;
    183        }
    184        if (test instanceof NodeNameTest) {
    185            if (!(node instanceof Element)) {
    186                return false;
    187            }
    188            final NodeNameTest nodeNameTest = (NodeNameTest) test;
    189            final QName testName = nodeNameTest.getNodeName();
    190            final String namespaceURI = nodeNameTest.getNamespaceURI();
    191            final boolean wildcard = nodeNameTest.isWildcard();
    192            final String testPrefix = testName.getPrefix();
    193            if (wildcard && testPrefix == null) {
    194                return true;
    195            }
    196            if (wildcard || testName.getName().equals(getLocalName(node))) {
    197                final String nodeNS = getNamespaceURI(node);
    198                return equalStrings(namespaceURI, nodeNS) || nodeNS == null && equalStrings(testPrefix, getPrefix(node));
    199            }
    200            return false;
    201        }
    202        if (test instanceof NodeTypeTest) {
    203            switch (((NodeTypeTest) test).getNodeType()) {
    204            case Compiler.NODE_TYPE_NODE:
    205                return true;
    206            case Compiler.NODE_TYPE_TEXT:
    207                return node instanceof Text || node instanceof CDATA;
    208            case Compiler.NODE_TYPE_COMMENT:
    209                return node instanceof Comment;
    210            case Compiler.NODE_TYPE_PI:
    211                return node instanceof ProcessingInstruction;
    212            default:
    213                return false;
    214            }
    215        }
    216        if (test instanceof ProcessingInstructionTest && node instanceof ProcessingInstruction) {
    217            final String testPI = ((ProcessingInstructionTest) test).getTarget();
    218            final String nodePI = ((ProcessingInstruction) node).getTarget();
    219            return testPI.equals(nodePI);
    220        }
    221        return false;
    222    }
    223
    224    /** Node, like a bean. */
    225    private final Object node;
    226
    227    /** Optional ID, may be null. */
    228    private final String id;
    229
    230    /** Local namespace resolver. */
    231    private NamespaceResolver localNamespaceResolver;
    232
    233    /**
    234     * Constructs a new JDOMNodePointer.
    235     *
    236     * @param parent NodePointer
    237     * @param node   pointed
    238     */
    239    public JDOMNodePointer(final NodePointer parent, final Object node) {
    240        super(parent);
    241        this.node = node;
    242        this.id = null;
    243    }
    244
    245    /**
    246     * Constructs a new JDOMNodePointer.
    247     *
    248     * @param node   node, like a bean.
    249     * @param locale Locale
    250     */
    251    public JDOMNodePointer(final Object node, final Locale locale) {
    252        super(null, locale);
    253        this.node = node;
    254        this.id = null;
    255    }
    256
    257    /**
    258     * Constructs a new JDOMNodePointer.
    259     *
    260     * @param node   pointed
    261     * @param locale Locale
    262     * @param id     String id
    263     */
    264    public JDOMNodePointer(final Object node, final Locale locale, final String id) {
    265        super(null, locale);
    266        this.node = node;
    267        this.id = id;
    268    }
    269
    270    /**
    271     * Add the specified content to this element.
    272     *
    273     * @param content List
    274     */
    275    private void addContent(final List content) {
    276        final Element element = (Element) node;
    277        final int count = content.size();
    278        for (int i = 0; i < count; i++) {
    279            Object child = content.get(i);
    280            if (child instanceof Element) {
    281                child = ((Element) child).clone();
    282                element.addContent((Element) child);
    283            } else if (child instanceof Text) {
    284                child = ((Text) child).clone();
    285                element.addContent((Text) child);
    286            } else if (node instanceof CDATA) {
    287                child = ((CDATA) child).clone();
    288                element.addContent((CDATA) child);
    289            } else if (node instanceof ProcessingInstruction) {
    290                child = ((ProcessingInstruction) child).clone();
    291                element.addContent((ProcessingInstruction) child);
    292            } else if (node instanceof Comment) {
    293                child = ((Comment) child).clone();
    294                element.addContent((Comment) child);
    295            }
    296        }
    297    }
    298
    299    @Override
    300    public String asPath() {
    301        if (id != null) {
    302            return "id('" + escape(id) + "')";
    303        }
    304        final StringBuilder buffer = new StringBuilder();
    305        if (parent != null) {
    306            buffer.append(parent.asPath());
    307        }
    308        if (node instanceof Element) {
    309            // If the parent pointer is not a JDOMNodePointer, it is
    310            // the parent's responsibility to produce the node test part
    311            // of the path
    312            if (parent instanceof JDOMNodePointer) {
    313                if (buffer.length() == 0 || buffer.charAt(buffer.length() - 1) != '/') {
    314                    buffer.append('/');
    315                }
    316                final String nsURI = getNamespaceURI();
    317                final String ln = getLocalName(node);
    318                if (nsURI == null) {
    319                    buffer.append(ln);
    320                    buffer.append('[');
    321                    buffer.append(getRelativePositionByQName()).append(']');
    322                } else {
    323                    final String prefix = getNamespaceResolver().getPrefix(nsURI);
    324                    if (prefix != null) {
    325                        buffer.append(prefix);
    326                        buffer.append(':');
    327                        buffer.append(ln);
    328                        buffer.append('[');
    329                        buffer.append(getRelativePositionByQName());
    330                    } else {
    331                        buffer.append("node()");
    332                        buffer.append('[');
    333                        buffer.append(getRelativePositionOfElement());
    334                    }
    335                    buffer.append(']');
    336                }
    337            }
    338        } else if (node instanceof Text || node instanceof CDATA) {
    339            buffer.append("/text()");
    340            buffer.append('[').append(getRelativePositionOfTextNode()).append(']');
    341        } else if (node instanceof ProcessingInstruction) {
    342            buffer.append("/processing-instruction(\'").append(((ProcessingInstruction) node).getTarget()).append("')");
    343            buffer.append('[').append(getRelativePositionOfPI()).append(']');
    344        }
    345        return buffer.toString();
    346    }
    347
    348    @Override
    349    public NodeIterator attributeIterator(final QName qName) {
    350        return new JDOMAttributeIterator(this, qName);
    351    }
    352
    353    @Override
    354    public NodeIterator childIterator(final NodeTest test, final boolean reverse, final NodePointer startWith) {
    355        return new JDOMNodeIterator(this, test, reverse, startWith);
    356    }
    357
    358    @Override
    359    public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) {
    360        final Object node1 = pointer1.getBaseValue();
    361        final Object node2 = pointer2.getBaseValue();
    362        if (node1 == node2) {
    363            return 0;
    364        }
    365        if (node1 instanceof Attribute && !(node2 instanceof Attribute)) {
    366            return -1;
    367        }
    368        if (!(node1 instanceof Attribute) && node2 instanceof Attribute) {
    369            return 1;
    370        }
    371        if (node1 instanceof Attribute && node2 instanceof Attribute) {
    372            final List list = ((Element) getNode()).getAttributes();
    373            final int length = list.size();
    374            for (int i = 0; i < length; i++) {
    375                final Object n = list.get(i);
    376                if (n == node1) {
    377                    return -1;
    378                }
    379                if (n == node2) {
    380                    return 1;
    381                }
    382            }
    383            return 0; // Should not happen
    384        }
    385        if (!(node instanceof Element)) {
    386            throw new IllegalStateException("JXPath internal error: compareChildNodes called for " + node);
    387        }
    388        final List children = ((Element) node).getContent();
    389        final int length = children.size();
    390        for (int i = 0; i < length; i++) {
    391            final Object n = children.get(i);
    392            if (n == node1) {
    393                return -1;
    394            }
    395            if (n == node2) {
    396                return 1;
    397            }
    398        }
    399        return 0;
    400    }
    401
    402    @Override
    403    public NodePointer createAttribute(final JXPathContext context, final QName qName) {
    404        if (!(node instanceof Element)) {
    405            return super.createAttribute(context, qName);
    406        }
    407        final Element element = (Element) node;
    408        final String prefix = qName.getPrefix();
    409        if (prefix != null) {
    410            final String namespaceUri = getNamespaceResolver().getNamespaceURI(prefix);
    411            if (namespaceUri == null) {
    412                throw new JXPathException("Unknown namespace prefix: " + prefix);
    413            }
    414            final Namespace ns = Namespace.getNamespace(prefix, namespaceUri);
    415            final Attribute attr = element.getAttribute(qName.getName(), ns);
    416            if (attr == null) {
    417                element.setAttribute(qName.getName(), "", ns);
    418            }
    419        } else {
    420            final Attribute attr = element.getAttribute(qName.getName());
    421            if (attr == null) {
    422                element.setAttribute(qName.getName(), "");
    423            }
    424        }
    425        final NodeIterator it = attributeIterator(qName);
    426        it.setPosition(1);
    427        return it.getNodePointer();
    428    }
    429
    430    @Override
    431    public NodePointer createChild(final JXPathContext context, final QName qName, int index) {
    432        if (index == WHOLE_COLLECTION) {
    433            index = 0;
    434        }
    435        final boolean success = getAbstractFactory(context).createObject(context, this, node, qName.toString(), index);
    436        if (success) {
    437            NodeTest nodeTest;
    438            final String prefix = qName.getPrefix();
    439            final String namespaceURI = prefix == null ? null : context.getNamespaceURI(prefix);
    440            nodeTest = new NodeNameTest(qName, namespaceURI);
    441            final NodeIterator it = childIterator(nodeTest, false, null);
    442            if (it != null && it.setPosition(index + 1)) {
    443                return it.getNodePointer();
    444            }
    445        }
    446        throw new JXPathAbstractFactoryException("Factory could not create a child node for path: " + asPath() + "/" + qName + "[" + (index + 1) + "]");
    447    }
    448
    449    @Override
    450    public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
    451        final NodePointer ptr = createChild(context, qName, index);
    452        ptr.setValue(value);
    453        return ptr;
    454    }
    455
    456    @Override
    457    public boolean equals(final Object object) {
    458        if (object == this) {
    459            return true;
    460        }
    461        if (!(object instanceof JDOMNodePointer)) {
    462            return false;
    463        }
    464        final JDOMNodePointer other = (JDOMNodePointer) object;
    465        return node == other.node;
    466    }
    467
    468    @Override
    469    public Object getBaseValue() {
    470        return node;
    471    }
    472
    473    @Override
    474    public Object getImmediateNode() {
    475        return node;
    476    }
    477
    478    /**
    479     * Gets the language of this element.
    480     *
    481     * @return String language
    482     */
    483    protected String getLanguage() {
    484        return findEnclosingAttribute(node, "lang", Namespace.XML_NAMESPACE);
    485    }
    486
    487    @Override
    488    public int getLength() {
    489        return 1;
    490    }
    491
    492    @Override
    493    public QName getName() {
    494        String ns = null;
    495        String ln = null;
    496        if (node instanceof Element) {
    497            ns = ((Element) node).getNamespacePrefix();
    498            if (ns != null && ns.isEmpty()) {
    499                ns = null;
    500            }
    501            ln = ((Element) node).getName();
    502        } else if (node instanceof ProcessingInstruction) {
    503            ln = ((ProcessingInstruction) node).getTarget();
    504        }
    505        return new QName(ns, ln);
    506    }
    507
    508    @Override
    509    public synchronized NamespaceResolver getNamespaceResolver() {
    510        if (localNamespaceResolver == null) {
    511            localNamespaceResolver = new NamespaceResolver(super.getNamespaceResolver());
    512            localNamespaceResolver.setNamespaceContextPointer(this);
    513        }
    514        return localNamespaceResolver;
    515    }
    516
    517    @Override
    518    public String getNamespaceURI() {
    519        return getNamespaceURI(node);
    520    }
    521
    522    @Override
    523    public String getNamespaceURI(final String prefix) {
    524        if (prefix.equals("xml")) {
    525            return Namespace.XML_NAMESPACE.getURI();
    526        }
    527        Element element = null;
    528        if (node instanceof Document) {
    529            element = ((Document) node).getRootElement();
    530        }
    531        if (node instanceof Element) {
    532            element = (Element) node;
    533        }
    534        if (element == null) {
    535            return null;
    536        }
    537        final Namespace ns = element.getNamespace(prefix);
    538        return ns == null ? null : ns.getURI();
    539    }
    540
    541    /**
    542     * Gets relative position of this among like-named siblings.
    543     *
    544     * @return 1..n
    545     */
    546    private int getRelativePositionByQName() {
    547        if (node instanceof Element) {
    548            final Object parent = ((Element) node).getParent();
    549            if (!(parent instanceof Element)) {
    550                return 1;
    551            }
    552            final List children = ((Element) parent).getContent();
    553            int count = 0;
    554            for (final Object child : children) {
    555                if (child instanceof Element && matchesQName((Element) child)) {
    556                    count++;
    557                }
    558                if (child == node) {
    559                    break;
    560                }
    561            }
    562            return count;
    563        }
    564        return 1;
    565    }
    566
    567    /**
    568     * Gets relative position of this among all siblings.
    569     *
    570     * @return 1..n
    571     */
    572    private int getRelativePositionOfElement() {
    573        final Object parent = ((Element) node).getParent();
    574        if (parent == null) {
    575            return 1;
    576        }
    577        List children;
    578        if (parent instanceof Element) {
    579            children = ((Element) parent).getContent();
    580        } else {
    581            children = ((Document) parent).getContent();
    582        }
    583        int count = 0;
    584        for (final Object child : children) {
    585            if (child instanceof Element) {
    586                count++;
    587            }
    588            if (child == node) {
    589                break;
    590            }
    591        }
    592        return count;
    593    }
    594
    595    /**
    596     * Gets the relative position of this among same-target processing instruction siblings.
    597     *
    598     * @return 1..n
    599     */
    600    private int getRelativePositionOfPI() {
    601        final String target = ((ProcessingInstruction) node).getTarget();
    602        final Element parent = (Element) ((ProcessingInstruction) node).getParent();
    603        if (parent == null) {
    604            return 1;
    605        }
    606        final List children = parent.getContent();
    607        int count = 0;
    608        for (final Object child : children) {
    609            if (child instanceof ProcessingInstruction && (target == null || target.equals(((ProcessingInstruction) child).getTarget()))) {
    610                count++;
    611            }
    612            if (child == node) {
    613                break;
    614            }
    615        }
    616        return count;
    617    }
    618
    619    /**
    620     * Gets the relative position of this among sibling text nodes.
    621     *
    622     * @return 1..n
    623     */
    624    private int getRelativePositionOfTextNode() {
    625        Element parent;
    626        if (node instanceof Text) {
    627            parent = (Element) ((Text) node).getParent();
    628        } else {
    629            parent = (Element) ((CDATA) node).getParent();
    630        }
    631        if (parent == null) {
    632            return 1;
    633        }
    634        final List children = parent.getContent();
    635        int count = 0;
    636        for (final Object child : children) {
    637            if (child instanceof Text || child instanceof CDATA) {
    638                count++;
    639            }
    640            if (child == node) {
    641                break;
    642            }
    643        }
    644        return count;
    645    }
    646
    647    @Override
    648    public Object getValue() {
    649        if (node instanceof Element) {
    650            final StringBuilder buf = new StringBuilder();
    651            for (final NodeIterator children = childIterator(null, false, null); children.setPosition(children.getPosition() + 1);) {
    652                final NodePointer ptr = children.getNodePointer();
    653                if (ptr.getImmediateNode() instanceof Element || ptr.getImmediateNode() instanceof Text) {
    654                    buf.append(ptr.getValue());
    655                }
    656            }
    657            return buf.toString();
    658        }
    659        if (node instanceof Comment) {
    660            String text = ((Comment) node).getText();
    661            if (text != null) {
    662                text = text.trim();
    663            }
    664            return text;
    665        }
    666        String result = null;
    667        if (node instanceof Text) {
    668            result = ((Text) node).getText();
    669        }
    670        if (node instanceof ProcessingInstruction) {
    671            result = ((ProcessingInstruction) node).getData();
    672        }
    673        final boolean trim = !"preserve".equals(findEnclosingAttribute(node, "space", Namespace.XML_NAMESPACE));
    674        return result != null && trim ? result.trim() : result;
    675    }
    676
    677    @Override
    678    public int hashCode() {
    679        return node.hashCode();
    680    }
    681
    682    @Override
    683    public boolean isCollection() {
    684        return false;
    685    }
    686
    687    /**
    688     * Returns true if the xml:lang attribute for the current node or its parent has the specified prefix <em>lang</em>. If no node has this prefix, calls
    689     * {@code super.isLanguage(lang)}.
    690     *
    691     * @param lang to compare
    692     * @return true if this element uses the specified language.
    693     */
    694    @Override
    695    public boolean isLanguage(final String lang) {
    696        final String current = getLanguage();
    697        return current == null ? super.isLanguage(lang) : current.toUpperCase(Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
    698    }
    699
    700    @Override
    701    public boolean isLeaf() {
    702        if (node instanceof Element) {
    703            return ((Element) node).getContent().isEmpty();
    704        }
    705        if (node instanceof Document) {
    706            return ((Document) node).getContent().isEmpty();
    707        }
    708        return true;
    709    }
    710
    711    private boolean matchesQName(final Element element) {
    712        if (getNamespaceURI() != null) {
    713            final String ns = getNamespaceURI(element);
    714            if (ns == null || !ns.equals(getNamespaceURI())) {
    715                return false;
    716            }
    717        }
    718        return element.getName().equals(((Element) node).getName());
    719    }
    720
    721    @Override
    722    public NodeIterator namespaceIterator() {
    723        return new JDOMNamespaceIterator(this);
    724    }
    725
    726    @Override
    727    public NodePointer namespacePointer(final String prefix) {
    728        return new JDOMNamespacePointer(this, prefix);
    729    }
    730
    731    @Override
    732    public void remove() {
    733        final Element parent = nodeParent(node);
    734        if (parent == null) {
    735            throw new JXPathException("Cannot remove root JDOM node");
    736        }
    737        parent.getContent().remove(node);
    738    }
    739
    740    @Override
    741    public void setValue(final Object value) {
    742        if (node instanceof Text) {
    743            final String string = (String) TypeUtils.convert(value, String.class);
    744            if (string != null && !string.isEmpty()) {
    745                ((Text) node).setText(string);
    746            } else {
    747                nodeParent(node).removeContent((Text) node);
    748            }
    749        } else {
    750            final Element element = (Element) node;
    751            element.getContent().clear();
    752            if (value instanceof Element) {
    753                final Element valueElement = (Element) value;
    754                addContent(valueElement.getContent());
    755            } else if (value instanceof Document) {
    756                final Document valueDocument = (Document) value;
    757                addContent(valueDocument.getContent());
    758            } else if (value instanceof Text || value instanceof CDATA) {
    759                final String string = ((Text) value).getText();
    760                element.addContent(new Text(string));
    761            } else if (value instanceof ProcessingInstruction) {
    762                final ProcessingInstruction pi = (ProcessingInstruction) ((ProcessingInstruction) value).clone();
    763                element.addContent(pi);
    764            } else if (value instanceof Comment) {
    765                final Comment comment = (Comment) ((Comment) value).clone();
    766                element.addContent(comment);
    767            } else {
    768                final String string = (String) TypeUtils.convert(value, String.class);
    769                if (string != null && !string.isEmpty()) {
    770                    element.addContent(new Text(string));
    771                }
    772            }
    773        }
    774    }
    775
    776    @Override
    777    public boolean testNode(final NodeTest test) {
    778        return testNode(this, node, test);
    779    }
    780}