View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.javascript.host.xml;
16  
17  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
18  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
19  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
20  
21  import java.io.StringWriter;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import javax.xml.parsers.DocumentBuilderFactory;
26  import javax.xml.transform.Result;
27  import javax.xml.transform.Source;
28  import javax.xml.transform.Transformer;
29  import javax.xml.transform.TransformerFactory;
30  import javax.xml.transform.dom.DOMResult;
31  import javax.xml.transform.dom.DOMSource;
32  import javax.xml.transform.stream.StreamResult;
33  
34  import org.w3c.dom.NodeList;
35  
36  import com.gargoylesoftware.htmlunit.SgmlPage;
37  import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
38  import com.gargoylesoftware.htmlunit.html.DomNode;
39  import com.gargoylesoftware.htmlunit.html.DomText;
40  import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
41  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
42  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
43  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
44  import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
45  import com.gargoylesoftware.htmlunit.javascript.host.dom.DocumentFragment;
46  import com.gargoylesoftware.htmlunit.javascript.host.dom.Node;
47  import com.gargoylesoftware.htmlunit.xml.XmlPage;
48  import com.gargoylesoftware.htmlunit.xml.XmlUtil;
49  
50  import net.sourceforge.htmlunit.corejs.javascript.Context;
51  
52  /**
53   * A JavaScript object for {@code XSLTProcessor}.
54   *
55   * @author Ahmed Ashour
56   * @author Ronald Brill
57   */
58  @JsxClass({CHROME, FF, EDGE})
59  public class XSLTProcessor extends SimpleScriptable {
60  
61      private Node style_;
62      private Map<String, Object> parameters_ = new HashMap<>();
63  
64      /**
65       * Default constructor.
66       */
67      @JsxConstructor
68      public XSLTProcessor() {
69      }
70  
71      /**
72       * Imports the specified stylesheet into this XSLTProcessor for transformations. The specified node
73       * may be either a document node or an element node. If it is a document node, then the document can
74       * contain either a XSLT stylesheet or a LRE stylesheet. If it is an element node, it must be the
75       * xsl:stylesheet (or xsl:transform) element of an XSLT stylesheet.
76       *
77       * @param style the root-node of an XSLT stylesheet (may be a document node or an element node)
78       */
79      @JsxFunction
80      public void importStylesheet(final Node style) {
81          style_ = style;
82      }
83  
84      /**
85       * Transforms the node source applying the stylesheet given by the importStylesheet() function.
86       * The owner document of the output node owns the returned document fragment.
87       *
88       * @param source the node to be transformed
89       * @return the result of the transformation
90       */
91      @JsxFunction
92      public XMLDocument transformToDocument(final Node source) {
93          final XMLDocument doc = new XMLDocument();
94          doc.setPrototype(getPrototype(doc.getClass()));
95          doc.setParentScope(getParentScope());
96  
97          final Object transformResult = transform(source);
98          final org.w3c.dom.Node node;
99          if (transformResult instanceof org.w3c.dom.Node) {
100             final org.w3c.dom.Node transformedDoc = (org.w3c.dom.Node) transformResult;
101             node = transformedDoc.getFirstChild();
102         }
103         else {
104             node = null;
105         }
106         final XmlPage page = new XmlPage(node, getWindow().getWebWindow());
107         doc.setDomNode(page);
108         return doc;
109     }
110 
111     /**
112      * @return {@link Node} or {@link String}
113      */
114     private Object transform(final Node source) {
115         try {
116             Source xmlSource = new DOMSource(source.getDomNodeOrDie());
117             final Source xsltSource = new DOMSource(style_.getDomNodeOrDie());
118 
119             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
120             final org.w3c.dom.Document containerDocument =
121                 factory.newDocumentBuilder().newDocument();
122             final org.w3c.dom.Element containerElement = containerDocument.createElement("container");
123             containerDocument.appendChild(containerElement);
124 
125             final DOMResult result = new DOMResult(containerElement);
126 
127             final Transformer transformer = TransformerFactory.newInstance().newTransformer(xsltSource);
128             for (final Map.Entry<String, Object> entry : parameters_.entrySet()) {
129                 transformer.setParameter(entry.getKey(), entry.getValue());
130             }
131             transformer.transform(xmlSource, result);
132 
133             final org.w3c.dom.Node transformedNode = result.getNode();
134             if (transformedNode.getFirstChild().getNodeType() == Node.ELEMENT_NODE) {
135                 return transformedNode;
136             }
137             //output is not DOM (text)
138             xmlSource = new DOMSource(source.getDomNodeOrDie());
139             final StringWriter writer = new StringWriter();
140             final Result streamResult = new StreamResult(writer);
141             transformer.transform(xmlSource, streamResult);
142             return writer.toString();
143         }
144         catch (final Exception e) {
145             throw Context.reportRuntimeError("Exception: " + e);
146         }
147     }
148 
149     /**
150      * Transforms the node source applying the stylesheet given by the importStylesheet() function.
151      * The owner document of the output node owns the returned document fragment.
152      * @param source the node to be transformed
153      * @param output This document is used to generate the output
154      * @return the result of the transformation
155      */
156     @JsxFunction
157     public DocumentFragment transformToFragment(final Node source, final Object output) {
158         final SgmlPage page = (SgmlPage) ((Document) output).getDomNodeOrDie();
159 
160         final DomDocumentFragment fragment = page.createDocumentFragment();
161         final DocumentFragment rv = new DocumentFragment();
162         rv.setPrototype(getPrototype(rv.getClass()));
163         rv.setParentScope(getParentScope());
164         rv.setDomNode(fragment);
165 
166         transform(source, fragment);
167         return rv;
168     }
169 
170     private void transform(final Node source, final DomNode parent) {
171         final Object result = transform(source);
172         if (result instanceof org.w3c.dom.Node) {
173             final SgmlPage parentPage = parent.getPage();
174             final NodeList children = ((org.w3c.dom.Node) result).getChildNodes();
175             for (int i = 0; i < children.getLength(); i++) {
176                 XmlUtil.appendChild(parentPage, parent, children.item(i), true);
177             }
178         }
179         else {
180             final DomText text = new DomText(parent.getPage(), (String) result);
181             parent.appendChild(text);
182         }
183     }
184 
185     /**
186      * Sets a parameter to be used in subsequent transformations with this nsIXSLTProcessor.
187      * If the parameter doesn't exist in the stylesheet the parameter will be ignored.
188      * @param namespaceURI the namespaceURI of the XSLT parameter
189      * @param localName the local name of the XSLT parameter
190      * @param value the new value of the XSLT parameter
191      */
192     @JsxFunction
193     public void setParameter(final String namespaceURI, final String localName, final Object value) {
194         parameters_.put(getQualifiedName(namespaceURI, localName), value);
195     }
196 
197     /**
198      * Gets a parameter if previously set by setParameter. Returns null otherwise.
199      * @param namespaceURI the namespaceURI of the XSLT parameter
200      * @param localName the local name of the XSLT parameter
201      * @return the value of the XSLT parameter
202      */
203     @JsxFunction
204     public Object getParameter(final String namespaceURI, final String localName) {
205         return parameters_.get(getQualifiedName(namespaceURI, localName));
206     }
207 
208     private static String getQualifiedName(final String namespaceURI, final String localName) {
209         final String qualifiedName;
210         if (namespaceURI != null && !namespaceURI.isEmpty() && !"null".equals(namespaceURI)) {
211             qualifiedName = '{' + namespaceURI + '}' + localName;
212         }
213         else {
214             qualifiedName = localName;
215         }
216         return qualifiedName;
217     }
218 }