Package dk.brics.xact

Source Code of dk.brics.xact.XML

package dk.brics.xact;

import dk.brics.misc.Origin;
import dk.brics.relaxng.converter.ParseException;
import dk.brics.xact.operations.*;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* XML template content node.
* <p/>
* This class contains the main functionality of the XACT runtime system.
* See also the general documentation for the package {@link dk.brics.xact}.
*/
abstract public class XML extends Node implements ToXMLable {

    private static final ConcurrentHashMap<String, String> global_namespaces;

    private static final ThreadLocal<ConcurrentHashMap<String, String>> thread_namespaces;

    private static ThreadLocal<XHTMLMode> threadXHTMLMode  = new ThreadLocal<XHTMLMode>();

    private static XHTMLMode globalXHTMLMode = XHTMLMode.XHTML1_Transitional;

    public enum XHTMLMode {
        XHTML1_Transitional("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +
    "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n"),
        XHTML1_Strict("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " +
                "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"),
        XHTML1_FrameSet("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" " +
                "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">\r\n"),
        XHTML1_1("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +
    "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n"),
        XMTML5("<!DOCTYPE html>\r\n");

        private String doctype;
        private String namespace = "http://www.w3.org/1999/xhtml";

        private XHTMLMode(String doctype){
            this.doctype = doctype;
        }

        public String getDoctype() {
            return doctype;
        }

        public String getNamespace() {
            return namespace;
        }
    }

    public static XHTMLMode getXHTMLDoctype() {
        XHTMLMode xhtmlMode = threadXHTMLMode.get();
        if (xhtmlMode != null) {
            return xhtmlMode;
        }
        return globalXHTMLMode;
    }

    public static void setThreadXHTMLMode(XHTMLMode threadXHTMLMode) {
        XML.threadXHTMLMode.set(threadXHTMLMode);
    }

    public static void setGlobalXHTMLMode(XHTMLMode globalXHTMLMode) {
        XML.globalXHTMLMode = globalXHTMLMode;
    }

    static {
        global_namespaces = new ConcurrentHashMap<String, String>();
        thread_namespaces = new ThreadLocal<ConcurrentHashMap<String, String>>();
    }

    /**
     * Constructs a new template node.
     */
    protected XML(Origin origin) {
        super(origin);
    }

    /**
     * Normalizes the given node if it is an operation node.
     * Also merges adjacent text nodes and non-last empty text nodes.
     * Do never inspect the type of a node before invoking this method.
     */
    static TempNode normalize(XML x) {
        XML res = x;
        if (x != null) {
            synchronized (x) {
                res = follow(x);
                boolean operation_sibling = false;
                for (XML z = res; z != null; z = follow(z.getRealNextSibling())) {
                    if (z instanceof OperationNode) {
                        operation_sibling = true;
                        break;
                    } else if (!(z instanceof Text)) {
                        break;
                    }
                }
                if (operation_sibling) {
                    res = Normalizer.normalize(res); // make sure we normalize the subtree only once
                    x.forward(res);
                }
                StringBuilder b = new StringBuilder();
                XML y;
                int c;
                for (c = 0, y = res; y != null && y.isText(); y = y.getRealNextSibling(), c++) {
                    b.append(y.asText().getString());
                }
                if (c > 1) {
                    if (b.length() > 0 || y == null) {
                        res = new Text(b.toString(), y, res.getOrigin());
                    } else {
                        res = y;
                    }
                    x.forward(res);
                }
            }
        }
        return (TempNode) res;
    }

    @Override
    public final void visitBy(NodeVisitor v) {
        normalize(this).visitNormalizedBy(v);
    }

    @Override
    public final <R, A> R visitBy(NodeVisitorParameterized<R, A> v, A arg) {
        return normalize(this).visitNormalizedBy(v, arg);
    }

    /**
     * Returns the global namespace map (from prefix to URI).
     * The map is modifiable. The empty prefix string represents the default namespace.
     *
     * @see #getThreadNamespaceMap()
     */
    public static Map<String, String> getNamespaceMap() {
        return global_namespaces;
    }

    /**
     * Returns the thread local namespace map (from prefix to URI).
     * The map is modifiable. The empty prefix string represents the default namespace.
     * Overrides the global namespace map for this thread.
     *
     * @see #getNamespaceMap()
     */
    public static Map<String, String> getThreadNamespaceMap() {
        ConcurrentHashMap<String, String> map = thread_namespaces.get();
        if (map == null) {
            map = new ConcurrentHashMap<String, String>();
            thread_namespaces.set(map);
        }
        return map;
    }

    /**
     * Loads an XML schema.
     * Supported schema languages: DTD, XML Schema, and Restricted RELAX NG.
     */
    public synchronized static void loadXMLSchema(URL url) {
        try {
            XMLValidator.loadXMLSchema(url);
        } catch (ParseException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Loads an XML schema.
     * Supported schema languages: DTD, XML Schema, and Restricted RELAX NG.
     */
    public synchronized static void loadXMLSchema(String url) {
        try {
            loadXMLSchema(new URL(url));
        } catch (MalformedURLException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Returns the next sibling node, or null if none.
     */
    public XML getNextSibling() {
        XML x = follow(this);
        if (x != null) {
            x = x.getNextSibling();
        }
        return x;
    }

    /**
     * Returns the next sibling, without normalizing.
     * Overridden in subclasses that can have a next sibling.
     */
    XML getRealNextSibling() {
        return null;
    }

    /**
     * Returns this object.
     */
    public XML toXML() {
        return this;
    }

    /**
     * Converts the given value to an XML template using {@link ToXMLable#toXML()} or {@link #toString()}.
     */
    public static XML toXML(Object v) {
        XML x;
        if (v instanceof ToXMLable) {
            x = ((ToXMLable) v).toXML();
        } else {
            x = new Text(v.toString());
        }
        return x;
    }

    /**
     * Evaluates the given value using {@link ToXMLable#toXML()} or {@link #toString()}
     * and returns an object of type <code>XML</code> or a <code>String</code>.
     *
     * @throws NullPointerException if v is null
     */
    private static Object extract(Object v) {
        if (v == null) {
            throw new NullPointerException("attempt to plug null value");
        }
        Object r;
        if (v instanceof XML || v instanceof String) {
            r = v;
        } else if (v instanceof ToXMLable) {
            r = ((ToXMLable) v).toXML();
        } else {
            r = v.toString();
        }
        return r;
    }

    /**
     * Constructs an XML template from its string representation.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     * Results are cached in memory.
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     * @see #parseDocument(String)
     * @see #parseTemplate(String, Origin)
     */
    public static XML parseTemplate(String template) {
        return parseTemplate(template, null);
    }

    /**
     * Constructs an XML template from its string representation.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     * Results are cached in memory (using the template string as key).
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     * @see #parseDocument(String)
     * @see #parseTemplate(InputStream, Origin)
     */
    public static XML parseTemplate(String template, Origin origin) {
        XML x = TemplateCache.get(template);
        if (x == null) {
            try {
                x = XMLParser.parse(new ByteArrayInputStream(template.getBytes("UTF-8")), "UTF-8", true, null, origin);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new XMLException(e);
            }
            TemplateCache.put(template, x);
        }
        return x;
    }

    /**
     * Constructs an XML template from its string representation from an input stream.
     * Assumes UTF-8 encoding.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     * @see #parseDocument(String)
     */
    public static XML parseTemplate(InputStream in, Origin origin) {
        try {
            return XMLParser.parse(in, "UTF-8", true, null, origin);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Constructs an XML template from its string representation from an input stream.
     * Assumes UTF-8 encoding.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     * @see #parseDocument(String)
     */
    public static XML parseTemplate(InputStream in) {
        return parseTemplate(in, null);
    }


    /**
     * Constructs an XML template from its string representation located in a resource.
     * Assumes UTF-8 encoding.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     * Uses <code>XML.class.getResourceAsStream</code> to load the string.
     * Results are cached in memory.
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     *                              or the resource is not found
     * @see #parseDocument(String)
     */
    public static XML parseTemplateResource(String name) {
        return parseTemplateResource(XML.class, name);
    }

    /**
     * Constructs an XML template from its string representation located in a resource.
     * Assumes UTF-8 encoding.
     * The program analysis requires the string to be constant.
     * The global and thread local namespace declarations are used, and gaps are recognized.
     * Uses <code>base.getResourceAsStream</code> to load the string.
     * Results are cached in memory.
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML template
     *                              or the resource is not found
     * @see #parseDocument(String)
     */
    public static XML parseTemplateResource(Class<?> base, String name) {
        String key = base.getName() + "#" + name;
        XML x = TemplateCache.get(key);
        if (x == null) {
            try {
                InputStream in = base.getResourceAsStream(name);
                if (in == null) {
                    throw new XMLTemplateException("Resource not found: " + name + " from " + base.toString());
                }
                x = XMLParser.parse(in, "UTF-8", true, null, new Origin(name, 0, 0));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new XMLException(e);
            }
            TemplateCache.put(key, x);
        }
        return x;

    }

    /**
     * Constructs an XML document from its string representation.
     * Assumes UTF-8 encoding.
     * (The global and thread local namespace declarations are <i>not</i> considered, and gaps are <i>not</i> recognized.)
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML document
     * @see #parseDocument(InputStream)
     */
    public static XML parseDocument(String doc) {
        try {
            return parseDocument(new ByteArrayInputStream(doc.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Constructs an XML document from its string representation from a URL.
     * Assumes UTF-8 encoding.
     * (The global and thread local namespace declarations are <i>not</i> considered, and gaps are <i>not</i> recognized.)
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML document
     * @see #parseDocument(InputStream, String, Origin)
     * @see #parseDocument(String)
     * @see #toDocument(OutputStream, String)
     */
    public static XML parseDocument(URL url) {
        try {
            return parseDocument(url.openStream());
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Constructs an XML document from its string representation from an input stream.
     * Assumes UTF-8 encoding.
     * (The global and thread local namespace declarations are <i>not</i> considered, and gaps are <i>not</i> recognized.)
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML document
     * @see #parseDocument(InputStream, String, Origin)
     * @see #parseDocument(String)
     * @see #toDocument(OutputStream, String)
     */
    public static XML parseDocument(InputStream in) {
        return parseDocument(in, "UTF-8", null);
    }

    /**
     * Constructs an XML document from its string representation from an input stream using the given encoding.
     * (The global and thread local namespace declarations are <i>not</i> considered, and gaps are <i>not</i> recognized.)
     *
     * @throws XMLTemplateException if the string is not a syntactically wellformed XML document
     * @see #parseDocument(InputStream)
     * @see #parseDocument(String)
     * @see #toDocument(OutputStream, String)
     */
    public static XML parseDocument(InputStream in, String encoding, Origin origin) {
        try {
            return XMLParser.parse(in, encoding, false, null, origin);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Converts this XML template to its string representation with all gaps removed.
     * Uses UTF-8 encoding.
     * The output contains an XML declaration.
     *
     * @see #toTemplate()
     * @see #toDocument(String)
     */
    public String toDocument() {
        try {
            return toDocument("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Converts this XML template to its string representation with all gaps removed, using the given encoding.
     * The output contains an XML declaration.
     *
     * @throws XMLWellformednessException   if the result is not a wellformed XML document
     * @throws UnsupportedEncodingException if the encoding is not supported
     * @see #toDocument()
     * @see #toTemplate(String)
     */
    public String toDocument(String encoding) throws UnsupportedEncodingException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        toDocument(out, encoding);
        return out.toString(encoding);
    }

    /**
     * Converts this XML template to its string representation with all gaps removed, using the given encoding.
     * The output contains an XML declaration.
     *
     * @throws XMLWellformednessException if the result is not a wellformed XML document
     * @see #toDocument()
     * @see #toTemplate(XMLIndentation)
     */
    public String toDocument(XMLIndentation indentation) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            XMLPrinter.print(follow(this), out, "UTF-8", false, true, indentation);
            return out.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Converts this template to its string representation.
     * Uses UTF-8 encoding.
     * Unlike {@link #toString()}, gaps are preserved in the output.
     *
     * @see #toString()
     * @see #toTemplate(String)
     */
    public String toTemplate() {
        try {
            return toTemplate("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Converts this template to its string representation, using the specified
     * indentation strategy.
     * Uses UTF-8 encoding.
     * Unlike {@link #toDocument()}, gaps are preserved in the output.
     *
     * @see #toString()
     * @see #toTemplate(String)
     */
    public String toTemplate(XMLIndentation indentation) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            XMLPrinter.print(follow(this), out, "UTF-8", true, false, indentation);
            return out.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Converts this template to its string representation, using the given encoding.
     * Unlike {@link #toDocument()}, gaps are preserved in the output.
     *
     * @see #toDocument(String)
     * @see #toTemplate()
     */
    public String toTemplate(String encoding) throws UnsupportedEncodingException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        XMLPrinter.print(follow(this), out, encoding, true, false);
        return out.toString(encoding);
    }

    /**
     * Converts this XML template to its string representation with all gaps removed
     * and prints the result to an output stream.
     * The output contains an XML declaration.
     *
     * @throws XMLWellformednessException   if the result is not a wellformed XML document
     * @throws UnsupportedEncodingException if the encoding is not supported
     * @see #parseDocument(InputStream)
     */
    public void toDocument(OutputStream out, String encoding) throws UnsupportedEncodingException {
        XMLPrinter.print(follow(this), out, encoding, false, true);
    }

    /**
     * Computes the number of bytes written if invoking {@link #toDocument(OutputStream, String)}.
     *
     * @throws XMLWellformednessException   if the result is not a wellformed XML document
     * @throws UnsupportedEncodingException if the encoding is not supported
     */
    public int byteLength(String encoding) throws UnsupportedEncodingException {
        LengthOutputStream s = new LengthOutputStream();
        toDocument(s, encoding);
        return s.getLength();
    }

    /**
     * Constructs a new XML template by plugging the given value into each template gap of the
     * given name in this template.
     *
     * @throws NullPointerException if the value is null
     * @see #plugList(String, Iterable)
     * @see #plugWrap(String, Iterable)
     * @see #close
     */
    public final XML plug(String gap, Object v) {
        return new PlugSingleNode(follow(this), gap, extract(v));
    }

    /**
     * Constructs a new XML template by plugging the given list of values into the list of template gaps of the
     * given name in this template. If too many values are provided, the remaining ones are ignored.
     * If too few are provided, the remaining gaps are removed.
     *
     * @throws NullPointerException if one of the values is null
     * @see #plug(String, Object)
     * @see #plugWrap(String, Iterable)
     * @see #close
     */
    public final XML plugList(String gap, Iterable<?> vs) {
        ArrayList<Object> values = new ArrayList<Object>();
        for (Object v : vs) {
            values.add(extract(v));
        }
        return new PlugListNode(follow(this), gap, values);
    }

    /**
     * Constructs a template by wrapping each value from the given list into the gaps of the
     * given name in this template and concatenating the results.
     *
     * @throws NullPointerException if one of the values is null
     * @see #plug(String, Object)
     * @see #plugList(String, Iterable)
     * @see #close
     */
    public final XML plugWrap(String gap, Iterable<?> vs) {
        ArrayList<Object> values = new ArrayList<Object>();
        for (Object v : vs) {
            values.add(plug(gap, v));
        }
        return concat(values);
    }

    /**
     * Removes all gaps in this template.
     *
     * @see #plug(String, Object)
     * @see #plugList(String, Iterable)
     * @see #plugWrap(String, Iterable)
     */
    public final XML close() {
        return new CloseNode(follow(this));
    }

    /**
     * Returns the nodes specified by the given XPath expression.
     * The initial context node is this node. The document root node
     * is an implicit node that has this node and its siblings as content.
     * The nodes are returned in document order.
     *
     * @see #getElements(String)
     * @see #getElement(String)
     * @see #getString(String)
     * @see #has(String)
     */
    public final NodeList<Node> get(String xpath) {
        return new NodeList<Node>(XMLNavigator.selectNodes(follow(this), xpath, true).getNodes());
    }

    /**
     * Returns the elements specified by the given XPath expression.
     * The elements are returned in document order.
     *
     * @see #get(String)
     * @see #getElement(String)
     * @see #getStrings(String)
     * @see #getString(String)
     * @see #has(String)
     */
    public final NodeList<Element> getElements(String xpath) {
        return new NodeList<Element>(XMLNavigator.selectElements(follow(this), xpath, true).getElements());
    }

    /**
     * Returns the first element specified by the given XPath expression.
     *
     * @see #get(String)
     * @see #getFirstElement()
     */
    public final Element getElement(String xpath) {
        Node n = XMLNavigator.selectSingleNode(follow(this), xpath, true).getNode();
        if (!(n instanceof Element)) {
            throw new XMLXPathException("node is not an element");
        }
        return (Element) n;
    }

    /**
     * Returns the element with the given ID.
     *
     * @see #getElement(String)
     */
    public final Element getElementByID(String id) {
        return XMLNavigator.selectElementByID(follow(this), id, true);
    }

    /**
     * Returns the value of the attribute of the given name in the first element in this template, or null if not present.
     *
     * @see #get(String)
     * @see #getAttribute(String, String)
     */
    public String getAttribute(String localname) {
        return getAttribute(null, localname);
    }

    /**
     * Returns the value of the attribute of the given name in the first element in this template, or null if not present.
     *
     * @see #get(String)
     * @see #getAttribute(String)
     */
    public String getAttribute(String namespace, String localname) {
        return getFirstElement().getAttribute(namespace, localname);
    }

    /**
     * Returns the text or attribute values specified by the given XPath expression.
     * Each string value is determined per the <code>string(..)</code> core function as defined in the XPath 1.0 specification.
     * The strings are returned in document order. The returned list is unmodifiable.
     *
     * @see #get(String)
     * @see #getElements(String)
     * @see #getString(String)
     */
    public List<String> getStrings(String xpath) {
        return XMLNavigator.selectStrings(follow(this), xpath);
    }

    /**
     * Returns the first element in this template.
     *
     * @see #getElement(String)
     */
    public Element getFirstElement() {
        Element res = null;
        for (XML x = normalize(this); x != null; x = x.getNextSibling()) {
            if (x instanceof Element) {
                res = (Element) x;
                break;
            }
        }
        if (res == null) {
            throw new XMLXPathException("no element");
        }
        return res.copy((XML) null);
    }

    /**
     * Returns the text specified by the given XPath expression.
     * If multiple nodes are selected, only the first one is considered.
     * The value is determined per the <code>string(..)</code> core function as defined in the XPath 1.0 specification.
     *
     * @see #getString()
     * @see #get(String)
     * @see #getElement(String)
     */
    public String getString(String xpath) {
        return XMLNavigator.stringValueOf(follow(this), xpath);
    }

    /**
     * Returns the string value of the first element or text node in this template.
     *
     * @see #getString(String)
     */
    public String getString() {
        Node n = getFirstElementOrText();
        if (n.isElement()) {
            return XMLPrinter.getElementStringValue(n.asElement());
        } else {
            return n.asText().getString();
        }
    }

    /**
     * Returns the boolean specified by the given XPath expression.
     * If multiple nodes are selected, only the first one is considered.
     * The value is determined per the <code>boolean(..)</code> core function as defined in the XPath 1.0 specification.
     *
     * @see #get(String)
     */
    public boolean has(String xpath) {
        return XMLNavigator.booleanValueOf(follow(this), xpath);
    }

    /**
     * Returns the number specified by the given XPath expression.
     * If multiple nodes are selected, only the first one is considered.
     * The value is determined per the <code>number(..)</code> core function as defined in the XPath 1.0 specification.
     *
     * @see #get(String)
     * @see #getNumber()
     */
    public Number getNumber(String xpath) {
        return XMLNavigator.numberValueOf(follow(this), xpath);
    }

    /**
     * Returns the number value of the first element or text node in this template.
     *
     * @see #get(String)
     * @see #getNumber(String)
     * @see #getString()
     */
    public Number getNumber() {
        try {
            return Double.parseDouble(getString().trim());
        } catch (NumberFormatException e) {
            return Double.NaN;
        }
    }

    /**
     * Checks that this template is valid according to the given schema type.
     *
     * @return this XML template
     * @throws XMLValidationException if the template is invalid
     * @see #analyze(String)
     */
    public XML validate(String type) throws XMLValidationException {
        XMLValidator.validate(follow(this), type, null);
        return this;
    }

    /**
     * Instruction for the program analyzer; no operation at runtime.
     * At analysis-time, the static analyzer will check validity of
     * this template relative to the given schema type.
     *
     * @return this XML template
     * @see #validate(String)
     */
    public XML analyze(String type) {
        return this;
    }

    /**
     * Converts all nodes selected by the given XPath expression into gaps of the given name.
     *
     * @see #gapify(String, String, String)
     */
    public XML gapify(String xpath, String gap) {
        return gapify(xpath, gap, null);
    }

    /**
     * Converts all nodes selected by the given XPath expression into gaps of the given name and schema type.
     *
     * @see #gapify(String, String)
     */
    public XML gapify(String xpath, String gap, String type) {
        XML t = follow(this);
        XMLNavigator.NodeListResult res = XMLNavigator.selectNodes(t, xpath, false);
        for (Node n : res.getNodes()) {
            if (n.isTemplate()) {
                TempNode x = n.asTemplate();
                n.forward(new TemplateGap(gap, type, x.getRealNextSibling(), n.getOrigin()));
            } else if (n.isAttribute()) {
                Attribute a = n.asAttribute();
                n.forward(new AttributeGap(a.getNamespace(), a.getLocalName(),
                                           gap, type, a.getNextAttr(), a.getOrigin()));
            }
        }
        return follow(res.getInitial());
    }

    /**
     * Removes the nodes selected by the given XPath expression.
     *
     * @see #gapify(String, String)
     */
    public XML remove(String xpath) {
        XML t = follow(this);
        XMLNavigator.NodeListResult res = XMLNavigator.selectNodes(t, xpath, false);
        for (Node n : res.getNodes()) {
            if (n.isTemplate()) {
                TempNode x = n.asTemplate();
                if (x.getRealNextSibling() != null) {
                    n.forward(x.getRealNextSibling());
                } else {
                    n.forward(Text.EMPTY);
                }
            } else if (n.isAttribute()) {
                Attribute a = n.asAttribute();
                n.forward(a.getNextAttr());
            }
        }
        return follow(res.getInitial());
    }

    /**
     * Sets an attribute at each selected element node.
     *
     * @see #set(AttrNode)
     */
    public XML set(String xpath, AttrNode a) {
        return set(XMLNavigator.selectElements(follow(this), xpath, false), a);
    }

    /**
     * Sets an attribute at this element.
     *
     * @throws ClassCastException if this is not an element node
     * @see #set(String, AttrNode)
     */
    public XML set(AttrNode a) {
        return set(getElementCopied(), a);
    }

    /**
     * Sets a namespace declaration at this element.
     *
     * @throws ClassCastException if this is not an element node
     */
    public XML set(NamespaceDecl d) {
        return set(getElementCopied(), d);
    }

    /**
     * Concatenates the given list of XML templates or strings into one template.
     *
     * @see #concat(Object...)
     */
    public static XML concat(Iterable<?> vs) {
        XML res;
        Iterator<?> i = vs.iterator();
        if (!i.hasNext()) {
            res = Text.EMPTY;
        } else {
            res = toXML(i.next());
            while (i.hasNext()) {
                res = new ConcatNode(res, toXML(i.next()), null);
            }
        }
        return res;
    }

    /**
     * Concatenates the given list of XML templates or strings.
     *
     * @see #concat(Iterable)
     */
    public static XML concat(Object... xs) {
        XML res;
        if (xs.length == 0) {
            res = Text.EMPTY;
        } else {
            res = toXML(xs[0]);
            for (int i = 1; i < xs.length; i++) {
                res = new ConcatNode(res, toXML(xs[i]), null);
            }
        }
        return res;
    }

    /**
     * Concatenates the given XML templates.
     */
    static XML concatXML(XML x1, XML x2) {
        if (x1 == null) {
            return x2;
        } else if (x2 == null) {
            return x1;
        } else {
            return new ConcatNode(x1, x2, null);
        }
    }

    /**
     * Inserts the given value in front of this template.
     *
     * @see #insertBefore(String, Object)
     * @see #append(Object)
     */
    public XML prepend(Object v) {
        return new ConcatNode(toXML(v), follow(this), null);
    }

    /**
     * Inserts the given value after this template.
     *
     * @see #insertAfter(String, Object)
     * @see #prepend(Object)
     */
    public XML append(Object v) {
        return new ConcatNode(follow(this), toXML(v), null);
    }

    /**
     * Inserts the given value before each selected content node.
     *
     * @see #insertAfter(String, Object)
     * @see #prepend(Object)
     * @see #prependContent(String, Object)
     */
    public XML insertBefore(String xpath, Object v) {
        XML t = follow(this);
        XMLNavigator.NodeListResult res = XMLNavigator.selectNodes(t, xpath, false);
        for (Node n : res.getNodes()) {
            if (n.isTemplate()) {
                TempNode x = n.asTemplate();
                n.forward(concatXML(toXML(v), x.copy(x.getRealNextSibling())));
            }
        }
        return follow(res.getInitial());
    }

    /**
     * Inserts the given value after each selected content node.
     *
     * @see #insertBefore(String, Object)
     * @see #append(Object)
     * @see #appendContent(String, Object)
     */
    public XML insertAfter(String xpath, Object v) {
        XML t = follow(this);
        XMLNavigator.NodeListResult res = XMLNavigator.selectNodes(t, xpath, false);
        for (Node n : res.getNodes()) {
            if (n.isTemplate()) {
                TempNode x = n.asTemplate();
                n.forward(x.copy(concatXML(toXML(v), x.getRealNextSibling())));
            }
        }
        return follow(res.getInitial());
    }

    /**
     * Inserts the given value before the content of each selected element node.
     *
     * @see #prependContent(Object)
     * @see #appendContent(String, Object)
     */
    public XML prependContent(String xpath, Object v) {
        return prependContent(XMLNavigator.selectElements(follow(this), xpath, false), v);
    }

    /**
     * Inserts the given value before the content of this element.
     *
     * @throws ClassCastException if this is not an element node
     * @see #prependContent(String, Object)
     * @see #appendContent(Object)
     */
    public XML prependContent(Object v) {
        return prependContent(getElementCopied(), v);
    }

    /**
     * Inserts the given value after the content of each selected element node.
     *
     * @see #appendContent(Object)
     * @see #prependContent(String, Object)
     */
    public XML appendContent(String xpath, Object v) {
        return appendContent(XMLNavigator.selectElements(follow(this), xpath, false), v);
    }

    /**
     * Inserts the given value after the content of this element.
     *
     * @throws ClassCastException if this is not an element node
     * @see #appendContent(String, Object)
     * @see #prependContent(Object)
     */
    public XML appendContent(Object v) {
        return appendContent(getElementCopied(), v);
    }

    /**
     * Replaces each selected content node by the given value.
     *
     * @see #appendContent(Object)
     * @see #prependContent(String, Object)
     * @see #setContent(String, Object)
     * @see #set(String, AttrNode)
     */
    public XML set(String xpath, Object v) {
        return set(XMLNavigator.selectNodes(follow(this), xpath, false), v);
    }

    // TODO: setElementByID

    /**
     * Sets the content of each selected element node to the given value.
     *
     * @see #appendContent(Object)
     * @see #prependContent(String, Object)
     * @see #set(String, Object)
     */
    public XML setContent(String xpath, Object v) {
        return setContent(XMLNavigator.selectElements(follow(this), xpath, false), v);
    }

    // TODO: setContentOfElementByID

    /**
     * Sets the content of this element to the given value.
     *
     * @throws ClassCastException if this is not an element node
     * @see #appendContent(String, Object)
     * @see #prependContent(Object)
     */
    public XML setContent(Object v) {
        return setContent(getElementCopied(), v);
    }

    /**
     * Returns the current element node as a navigator result.
     *
     * @throws ClassCastException if this is not an element node
     */
    private XMLNavigator.ElementListResult getElementCopied() {
        Element e = asElement();
        e = e.copy();
        List<Element> selected = new ArrayList<Element>();
        selected.add(e);
        return new XMLNavigator.ElementListResult(e, selected);
    }

    private Node getFirstElementOrText() {
        for (XML x = this; x != null; x = x.getNextSibling()) {
            if (x.isElement() || x.isText()) {
                return x;
            }
        }
        throw new XMLXPathException("no element or text");
    }

    private XML set(XMLNavigator.ElementListResult res, AttrNode a) {
        for (Element e : res.getElements()) {
            e.forward(e.copy(a.copy(removeAttrIfFound(a, e.getFirstAttr()))));
        }
        return follow(res.getInitial());
    }

    private AttrNode removeAttrIfFound(AttrNode a, AttrNode first) {
        boolean found = false;
        for (AttrNode f = first; f != null; f = f.getNextAttr()) {
            if (sameAttrName(a, f)) {
                found = true;
                break;
            }
        }
        if (!found) {
            return first;
        } else {
            return removeAttr(a, first);
        }
    }

    private AttrNode removeAttr(AttrNode a, AttrNode first) {
        if (sameAttrName(a, first)) {
            return first.getNextAttr();
        } else {
            return first.copy(removeAttr(a, first.getNextAttr()));
        }
    }

    private boolean sameAttrName(AttrNode a, AttrNode b) {
        return ((a.getNamespace() == null && b.getNamespace() == null)
                || (a.getNamespace() != null && b.getNamespace() != null && a.getNamespace().equals(b.getNamespace())))
               && (a.getLocalName().equals(b.getLocalName()));
    }

    private XML set(XMLNavigator.ElementListResult res, NamespaceDecl d) {
        for (Element e : res.getElements()) {
            e.forward(e.copy(d.copy(e.getFirstNamespaceDecl())));
        }
        return follow(res.getInitial());
    }

    private XML prependContent(XMLNavigator.ElementListResult res, Object v) {
        for (Element e : res.getElements()) {
            e.forward(e.copy(concatXML(toXML(v), e.getRealFirstChild()), e.getRealNextSibling()));
        }
        return follow(res.getInitial());
    }

    private XML appendContent(XMLNavigator.ElementListResult res, Object v) {
        for (Element e : res.getElements()) {
            e.forward(e.copy(concatXML(e.getRealFirstChild(), toXML(v)), e.getRealNextSibling()));
        }
        return follow(res.getInitial());
    }

    private XML set(XMLNavigator.NodeListResult res, Object v) {
        for (Node n : res.getNodes()) {
            if (n.isTemplate()) {
                TempNode t = n.asTemplate();
                n.forward(concatXML(toXML(v), t.getRealNextSibling()));
            }
        }
        return follow(res.getInitial());
    }

    private XML setContent(XMLNavigator.ElementListResult res, Object v) {
        for (Element e : res.getElements()) {
            e.forward(e.copy(toXML(v), e.getRealNextSibling()));
        }
        return follow(res.getInitial());
    }

    // TODO: add 'group' operation (like XACT 2.0)
    // TODO: getID, setID, setContentOfID ? (desugar to id('...')...)
}
TOP

Related Classes of dk.brics.xact.XML

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.