client.net.sf.saxon.ce.dom.HTMLWriter.java Source code

Java tutorial

Introduction

Here is the source code for client.net.sf.saxon.ce.dom.HTMLWriter.java

Source

package client.net.sf.saxon.ce.dom;

import java.util.logging.Logger;

import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.Controller;
import client.net.sf.saxon.ce.Controller.APIcommand;
import client.net.sf.saxon.ce.event.PipelineConfiguration;
import client.net.sf.saxon.ce.event.Receiver;
import client.net.sf.saxon.ce.lib.NamespaceConstant;
import client.net.sf.saxon.ce.om.NamePool;
import client.net.sf.saxon.ce.om.NamespaceBinding;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.value.Whitespace;

import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Text;

/**
  * DOMWriter is a Receiver that attaches the result tree to a specified Node in the HTML DOM Document
  */

public class HTMLWriter implements Receiver {

    private PipelineConfiguration pipe;
    private NamePool namePool;
    private Node currentNode;
    private Document document;
    private Node nextSibling;
    private int level = 0;
    private String systemId;
    private Node containerNode;
    private static Logger logger = Logger.getLogger("XSLT20Processor");

    /**
     * Native Javascript method to create a namespaced element. Not available in GWT because
     * it's not supported in IE. But needed for SVG/mathML support
     * @param ns the namespace URI
     * @param name the local name
     * @return the constructed element or null if method not available
     */

    private native Element createElementNS(Document doc, final String ns, final String name) /*-{
                                                                                             if (doc.createElementNS) {
                                                                                             return doc.createElementNS(ns, name);
                                                                                             }
                                                                                             return null;
                                                                                             }-*/;

    private native Node createProcessingInstruction(Document doc, final String target, final String data) /*-{
                                                                                                          return doc.createProcessingInstruction(target, data);
                                                                                                          }-*/;

    private native Node createComment(Document doc, final String data) /*-{
                                                                       return doc.createComment(data);
                                                                       }-*/;

    private static native boolean attNSSupported(Document doc) /*-{
                                                               return (typeof doc.createNode == "function" || typeof doc.createAttributeNS == "function");
                                                               }-*/;

    public static void setAttribute(Document doc, Element element, String name, String URI, String value,
            WriteMode wMode) {
        // fix for IE issue with colspan etc #1570

        name = tableAttributeFix(name, wMode);

        if (attNSSupported(doc)) {
            setAttributeJs(doc, element, name, URI, value);
        } else {
            String prefix = name.substring(0, name.indexOf(":"));
            String x = "xmlns";
            String nsDeclaraction = (prefix.length() == 0) ? x : x + ":" + prefix;
            if (!element.hasAttribute(nsDeclaraction)) {
                addNamespace(element, prefix, URI);
            }
            element.setAttribute(name, value);
            setAttributeProps(element, name, value);
        }
    }

    public static String tableAttributeFix(String name, WriteMode wMode) {
        if (wMode != WriteMode.XML && Configuration.getIeVersion() > 0 && name.length() > 5) {
            if (name.equals("rowspan")) {
                name = "rowSpan";
            } else if (name.equals("colspan")) {
                name = "colSpan";
            } else if (name.equals("cellpadding")) {
                name = "cellPadding";
            } else if (name.equals("cellppacing")) {
                name = "cellSpacing";
            }
        }
        return name;
    }

    //  This throws an exception in IE7 that must be handled, in IE, you can only create a namespace-qualified
    //  attribute using the createNode method of the DOMDocument.
    /**
     *  Creates an attribute with a namespace.
     *  This throws an exception in IE7 that (it seems) must be handled by the caller,
     *  in IE, you can only create a namespace-qualified attribute using the createNode method
     *  of the DOMDocument.
     * 
     */
    public static native void setAttributeJs(Document doc, final Node element, final String name, final String URI,
            final String value) /*-{
                                var att;
                                if (doc.createNode) {
                                att = doc.createNode(2, name, URI);
                                att.value = value;
                                } else {
                                att = doc.createAttributeNS(URI, name);
                                att.nodeValue = value;
                                } 
                                element.setAttributeNode(att);         
                                }-*/;

    /**
    * Set the pipelineConfiguration
    */

    public void setPipelineConfiguration(PipelineConfiguration pipe) {
        this.pipe = pipe;
        namePool = pipe.getConfiguration().getNamePool();
    }

    /**
    * Get the pipeline configuration used for this document
    */

    public PipelineConfiguration getPipelineConfiguration() {
        return pipe;
    }

    /**
     * Set the System ID of the destination tree
     */

    public void setSystemId(String systemId) {
        this.systemId = systemId;
    }

    /**
     * Get the system identifier that was set with setSystemId.
     *
     * @return The system identifier that was set with setSystemId,
     *         or null if setSystemId was not called.
     */
    public String getSystemId() {
        return systemId;
    }

    /**
    * Start of the document.
    */

    public void open() {
    }

    /**
    * End of the document.
    */

    public void close() {
    }

    /**
     * Start of a document node.
    */

    public void startDocument() throws XPathException {
        // not required - setNode is called instead:
        //document = XMLDOM.createDocument();
    }

    /**
     * Notify the end of a document node
     */

    public void endDocument() throws XPathException {
    }

    /**
    * Start of an element.
    */

    public void startElement(int nameCode, int properties) throws XPathException {
        String localName = namePool.getLocalName(nameCode);
        String prefix = namePool.getPrefix(nameCode);
        String uri = namePool.getURI(nameCode);

        // TODO: For XML Writer it should write prefixes in a
        // way compliant with the XSLT2.0 specification - using xsl:output attributes
        Element element = null;
        if (uri != null && !uri.isEmpty()) {
            if (mode == WriteMode.XML && !prefix.equals("")) {
                element = createElementNS(document, uri, prefix + ":" + localName);

            } else {
                // no svg specific prefix now used, for compliance with HTML5
                element = createElementNS(document, uri, localName);
            }
        }

        // if there's no namespace - or no namespace support
        if (element == null) {
            element = document.createElement(localName);
        }
        // special case for html element: write to the document node
        Controller controller = pipe.getController();
        if (controller != null && controller.getApiCommand() == APIcommand.UPDATE_HTML
                && (localName.equals("html") || localName.equals("head") || localName.equals("body"))) {
            if (localName.equals("html")) {
                element = (Element) document.getFirstChild();
            } else {
                element = (Element) document.getElementsByTagName(localName.toUpperCase()).getItem(0);
                NodeList<Node> nodes = element.getChildNodes();
                for (int n = 0; n < nodes.getLength(); n++) {
                    Node node = nodes.getItem(n);
                    node.removeFromParent();
                }
            }
            currentNode = element;
            level++;
            return;
        }
        if (nextSibling != null && level == 0) {
            currentNode.insertBefore(element, nextSibling);
        } else {
            try {
                currentNode.appendChild(element);
            } catch (JavaScriptException err) {
                if (uri.equals(NamespaceConstant.IXSL)) {
                    XPathException xpe = new XPathException(
                            "Error on adding IXSL element to the DOM, the IXSL namespace should be added to the 'extension-element-prefixes' list.");
                    throw (xpe);
                } else {
                    throw (new XPathException(err.getMessage()));
                }
            } catch (Exception exc) {
                XPathException xpe = new XPathException(
                        "Error on startElement in HTMLWriter for element '" + localName + "': " + exc.getMessage());
                throw (xpe);
            }
        }
        currentNode = element;
        level++;
    }

    private static void addNamespace(Element element, String prefix, String uri) {
        String attName = (prefix.isEmpty() ? "xmlns" : "xmlns:" + prefix);
        element.setAttribute(attName, uri);
    }

    public void namespace(NamespaceBinding nsBinding, int properties) throws XPathException {
        if (mode == WriteMode.XML) {
            String prefix = nsBinding.getPrefix();
            String uri = nsBinding.getURI();
            Element element = (Element) currentNode;
            if (!(uri.equals(NamespaceConstant.XML))) {
                addNamespace(element, prefix, uri);
            }
        }

    }

    public void attribute(int nameCode, CharSequence value) throws XPathException {
        String localName = namePool.getLocalName(nameCode);
        String uri = namePool.getURI(nameCode);
        String val = value.toString();
        Element element = (Element) currentNode;

        // must be HTML write mode
        if (mode != WriteMode.XML && NamespaceConstant.HTML_PROP.equals(uri)) {
            element.setPropertyString(localName, val);
        } else if (mode != WriteMode.XML && NamespaceConstant.HTML_STYLE_PROP.equals(uri)) {
            // if localName starts with '_-' then remove the underscore e.g _-webkit-transition
            if (localName.length() > 1 && localName.charAt(0) == '_' && localName.charAt(1) == '-') {
                localName = localName.substring(1);
            }
            localName = HTMLWriter.getCamelCaseName(localName);
            element.getStyle().setProperty(localName, val);
        } else if (uri != null && !uri.isEmpty()) {
            String fullname = namePool.getDisplayName(nameCode);
            setAttribute(document, element, fullname, uri, val, mode);
        } else {
            localName = tableAttributeFix(localName, mode);
            element.setAttribute(localName, val);
            setAttributeProps(element, localName, val);
        }
    }

    /**
     * Method for backward compatibility with IE8 and previous where
     * properties and attributes were handled separately
     */
    public static void setAttributePropsOriginal(Element element, String localName, String val) {
        if (Configuration.getIeVersion() > 0 && Configuration.getIeVersion() < 9) {
            if (localName.length() == 5) {
                if (localName.equals("style")) {
                    // In IE, setting the style attribute dynamically has no effect on the individual style properties,
                    // and does not affect the rendition of the element. So we parse out the content of the attribute,
                    // and use it to set the individual properties.
                    if (hasStyle(element)) {
                        setStyleProperties(element, val);
                    }
                } else if (localName.equals("class")) {
                    setClass(element, val);
                } else if (localName.equals("title")) {
                    setTitle(element, val);
                    //               } else if (localName.equals("align") || localName.equals("width")) {
                    //                  setElementProperty(element, localName, val);
                } else {
                    setElementProperty(element, localName, val);
                }
            } else if (localName.length() == 2 && localName.equals("id")) {
                setId(element, val);
                //           } else if (localName.length() == 7 && localName.equals("colSpan") || localName.equals("rowSpan") ) {
                //              setElementProperty(element, localName, val);
            } else {
                setElementProperty(element, localName, val);
            }
        }
    }

    /**
     * following setElementProperty method call doesn't work consistently
     * because in IE some element are initially undefined?
     */
    public static void setAttributeProps(Element element, String localName, String val) {
        if (Configuration.getIeVersion() > 0 && Configuration.getIeVersion() < 9) {
            if (localName.equals("style")) {
                if (hasStyle(element)) {
                    setStyleProperties(element, val);
                }
            } else {
                localName = (localName == "class") ? "className" : localName;
                try {
                    setElementProperty(element, localName, val);
                } catch (Exception e) {
                    // some IE8 properties exist but appear to be read-only
                    logger.warning("Unable to set '" + localName + "' property for element.");
                }
            }
        }
    }

    private static native boolean hasStyle(Element element) /*-{
                                                            return (typeof element.style !== "undefined");
                                                            }-*/;

    private static native boolean setClass(Element element, String value) /*-{
                                                                          if (typeof element.className !== "undefined") {
                                                                          element.className = value;
                                                                          }
                                                                          }-*/;

    private static native boolean setId(Element element, String value) /*-{
                                                                       if (typeof element.id !== "undefined") {
                                                                       element.id = value;
                                                                       }
                                                                       }-*/;

    private static native boolean setTitle(Element element, String value) /*-{
                                                                          if (typeof element.title !== "undefined") {
                                                                          element.title = value;
                                                                          }
                                                                          }-*/;

    private static native void setElementProperty(Element element, String name, String value) /*-{
                                                                                              if (typeof element[name] !== "undefined") {
                                                                                              element[name] = value;
                                                                                              }
                                                                                              }-*/;

    /**
     * Parse the value of the style attribute and use it to set individual properties of the style object
     * @param element the element whose style properties are to be updated
     * @param styleAttribute the raw value of the style attribute
     * @throws XPathException
     */

    public static void setStyleProperties(Element element, String styleAttribute) {
        int semi = styleAttribute.indexOf(';');
        String first = (semi < 0 ? styleAttribute : styleAttribute.substring(0, semi));
        int colon = first.indexOf(':');
        if (colon > 0 && colon < first.length() - 1) {
            String prop = first.substring(0, colon).trim();
            // Turn the style name into camelCase
            prop = getCamelCaseName(prop);
            String value = first.substring(colon + 1).trim();
            try {
                element.getStyle().setProperty(prop, value);
            } // IE throws illegal argument exception if property name is
              // not valid - ignore exception for consistency
            catch (JavaScriptException jex) {
            }
        }
        if (semi > 0 && semi < styleAttribute.length() - 2) {
            setStyleProperties(element, styleAttribute.substring(semi + 1));
        }
    }

    public static String getCamelCaseName(String prop) {
        while (prop.contains("-")) {
            int h = prop.indexOf('-');
            if (h > 0) { // preserve first char
                String p = prop.substring(0, h) + Character.toUpperCase(prop.charAt(h + 1));
                if (h + 2 < prop.length()) {
                    p += prop.substring(h + 2);
                }
                prop = p;
            }
        }
        return prop;
    }

    public void startContent() throws XPathException {
    }

    /**
    * End of an element.
    */

    public void endElement() throws XPathException {
        currentNode = currentNode.getParentNode();
        level--;
    }

    /**
    * Character data.
    */

    public void characters(CharSequence chars) throws XPathException {
        if (level == 0 && nextSibling == null && Whitespace.isWhite(chars)) {
            return; // no action for top-level whitespace
        }

        try {
            Text text = document.createTextNode(chars.toString());
            if (nextSibling != null && level == 0) {
                currentNode.insertBefore(text, nextSibling);
            } else {
                currentNode.appendChild(text);
            }
        } catch (Exception e) {
            String desc = (nextSibling != null && level == 0) ? "inserting" : "appending";
            throw (new XPathException("DOM error " + desc + " text node with value: '" + chars.toString()
                    + "' to node with name: " + currentNode.getNodeName()));
        }
    }

    /**
    * Handle a processing instruction.
    */

    public void processingInstruction(String target, CharSequence data) throws XPathException {
        // Processing instructions not in HTML DOM - but added for XML DOM
        // TODO: Note that Node is a GWT HTML DOM object so an exception may be raised
        // by appending the wrong node type!
        if (mode == WriteMode.XML) {
            JavaScriptObject pi = createProcessingInstruction(document, target, data.toString());
            addNode(pi, "processing-instruction");
        }
    }

    /**
    * Handle a comment.
    */

    public void comment(CharSequence chars) throws XPathException {
        // Added for XML compatibility
        if (mode == WriteMode.XML) {
            JavaScriptObject comment = createComment(document, chars.toString());
            addNode(comment, "comment");
        }
    }

    public void addNode(JavaScriptObject newNode, String nodeType) throws XPathException {
        try {
            if (nextSibling != null && level == 0) {
                insertBefore(nextSibling, newNode);
            } else {
                appendChild(currentNode, newNode);
            }
        } catch (Exception e) {
            String desc = (nextSibling != null && level == 0) ? "inserting" : "appending";
            throw (new XPathException("DOM error " + desc + " " + nodeType + " node to node with name: "
                    + currentNode.getNodeName()));
        }
    }

    public final native JavaScriptObject appendChild(JavaScriptObject parent, JavaScriptObject newChild) /*-{
                                                                                                         return parent.appendChild(newChild);
                                                                                                         }-*/;

    public final native JavaScriptObject insertBefore(JavaScriptObject targetNode, JavaScriptObject newNode) /*-{
                                                                                                             return targetNode.insertBefore(newNode);
                                                                                                             }-*/;

    /**
     * Set the attachment point for the new subtree
     * @param node the node to which the new subtree will be attached
    */

    public enum WriteMode {
        NONE, XML, HTML
    }

    private WriteMode mode = WriteMode.NONE;

    public void setNode(Node node) {
        if (node == null) {
            return;
        }
        currentNode = node;
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            document = (Document) node;
        } else {
            document = currentNode.getOwnerDocument();
        }
        if (mode == WriteMode.NONE) {
            Controller.APIcommand cmd = pipe.getController().getApiCommand();
            mode = (cmd == APIcommand.TRANSFORM_TO_DOCUMENT || cmd == APIcommand.TRANSFORM_TO_FRAGMENT)
                    ? WriteMode.XML
                    : WriteMode.HTML;
        }
    }

    public Node getNode() {
        // though this is changed by startElement it's reset by endElement and therefore,
        // when called after close, should always return the initial container node
        // - the document node or the documentFragment node.
        return currentNode;
    }

    /**
     * Set next sibling
     * @param nextSibling the node, which must be a child of the attachment point, before which the new subtree
     * will be created. If this is null the new subtree will be added after any existing children of the
     * attachment point.
     */

    public void setNextSibling(Node nextSibling) {
        this.nextSibling = nextSibling;
    }

}

// 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.