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.activex.javascript.msxml;
16  
17  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
18  
19  import java.io.StringWriter;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import javax.xml.parsers.DocumentBuilderFactory;
24  import javax.xml.transform.Result;
25  import javax.xml.transform.Source;
26  import javax.xml.transform.Transformer;
27  import javax.xml.transform.TransformerFactory;
28  import javax.xml.transform.dom.DOMResult;
29  import javax.xml.transform.dom.DOMSource;
30  import javax.xml.transform.stream.StreamResult;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.w3c.dom.NodeList;
34  
35  import com.gargoylesoftware.htmlunit.SgmlPage;
36  import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
37  import com.gargoylesoftware.htmlunit.html.DomNode;
38  import com.gargoylesoftware.htmlunit.html.DomText;
39  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
40  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
41  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
42  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
43  import com.gargoylesoftware.htmlunit.javascript.host.dom.Node;
44  import com.gargoylesoftware.htmlunit.xml.XmlUtil;
45  
46  import net.sourceforge.htmlunit.corejs.javascript.Context;
47  
48  /**
49   * A JavaScript object for MSXML's (ActiveX) XSLProcessor.<br>
50   * Used for transformations with compiled style sheets.
51   * @see <a href="http://msdn.microsoft.com/en-us/library/ms762799.aspx">MSDN documentation</a>
52   *
53   * @author Ahmed Ashour
54   * @author Frank Danek
55   */
56  @JsxClass(IE)
57  public class XSLProcessor extends MSXMLScriptable {
58  
59      private XMLDOMNode style_;
60      private XMLDOMNode input_;
61      private Object output_;
62      private Map<String, Object> parameters_ = new HashMap<>();
63  
64      /**
65       * Specifies which XML input tree to transform.
66       * @param input the input tree
67       */
68      @JsxSetter
69      public void setInput(final XMLDOMNode input) {
70          input_ = input;
71      }
72  
73      /**
74       * Returns which XML input tree to transform.
75       * @return which XML input tree to transform
76       */
77      @JsxGetter
78      public XMLDOMNode getInput() {
79          return input_;
80      }
81  
82      /**
83       * Sets the object to which to write the output of the transformation.
84       * @param output the object to which to write the output of the transformation
85       */
86      @JsxSetter
87      public void setOutput(final Object output) {
88          output_ = output;
89      }
90  
91      /**
92       * Gets a custom output to write the result of the transformation.
93       * @return the output of the transformation
94       */
95      @JsxGetter
96      public Object getOutput() {
97          return output_;
98      }
99  
100     /**
101      * Adds parameters into an XSL Transformations (XSLT) style sheet.
102      *
103      * @param baseName the name that will be used inside the style sheet to identify the parameter context
104      * @param parameter the parameter value
105      *        To remove a parameter previously added to the processor, provide a value of Empty or Null instead.
106      * @param namespaceURI an optional namespace
107      */
108     @JsxFunction
109     public void addParameter(final String baseName, final Object parameter, final Object namespaceURI) {
110         final String nsString;
111         if (namespaceURI instanceof String) {
112             nsString = (String) namespaceURI;
113         }
114         else {
115             nsString = null;
116         }
117         parameters_.put(getQualifiedName(nsString, baseName), parameter);
118     }
119 
120     private static String getQualifiedName(final String namespaceURI, final String localName) {
121         final String qualifiedName;
122         if (namespaceURI != null && !namespaceURI.isEmpty() && !"null".equals(namespaceURI)) {
123             qualifiedName = '{' + namespaceURI + '}' + localName;
124         }
125         else {
126             qualifiedName = localName;
127         }
128         return qualifiedName;
129     }
130 
131     /**
132      * Starts the transformation process or resumes a previously failed transformation.
133      */
134     @JsxFunction
135     public void transform() {
136         final XMLDOMNode input = input_;
137         final SgmlPage page = input.getDomNodeOrDie().getPage();
138 
139         if (output_ == null || !(output_ instanceof XMLDOMNode)) {
140             final DomDocumentFragment fragment = page.createDocumentFragment();
141             final XMLDOMDocumentFragment node = new XMLDOMDocumentFragment();
142             node.setParentScope(getParentScope());
143             node.setPrototype(getPrototype(node.getClass()));
144             node.setDomNode(fragment);
145             output_ = fragment.getScriptableObject();
146         }
147 
148         transform(input_, ((XMLDOMNode) output_).getDomNodeOrDie());
149         final XMLSerializer serializer = new XMLSerializer(false);
150         final StringBuilder output = new StringBuilder();
151         for (final DomNode child : ((XMLDOMNode) output_).getDomNodeOrDie().getChildren()) {
152             if (child instanceof DomText) {
153                 //IE: XmlPage ignores all empty text nodes (if 'xml:space' is 'default')
154                 //Maybe this should be changed for 'xml:space' = preserve
155                 //See XMLDocumentTest.testLoadXML_XMLSpaceAttribute()
156                 if (StringUtils.isNotBlank(((DomText) child).getData())) {
157                     output.append(((DomText) child).getData());
158                 }
159             }
160             else {
161                 //remove trailing "\r\n"
162                 final String serializedString =
163                     serializer.serializeToString((XMLDOMNode) child.getScriptableObject());
164                 output.append(serializedString, 0, serializedString.length() - 2);
165             }
166         }
167         output_ = output.toString();
168     }
169 
170     /**
171      * @return {@link XMLDOMNode} or {@link String}
172      */
173     private Object transform(final XMLDOMNode source) {
174         try {
175             Source xmlSource = new DOMSource(source.getDomNodeOrDie());
176             final Source xsltSource = new DOMSource(style_.getDomNodeOrDie());
177 
178             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
179             final org.w3c.dom.Document containerDocument = factory.newDocumentBuilder().newDocument();
180             final org.w3c.dom.Element containerElement = containerDocument.createElement("container");
181             containerDocument.appendChild(containerElement);
182 
183             final DOMResult result = new DOMResult(containerElement);
184 
185             final Transformer transformer = TransformerFactory.newInstance().newTransformer(xsltSource);
186             for (final Map.Entry<String, Object> entry : parameters_.entrySet()) {
187                 transformer.setParameter(entry.getKey(), entry.getValue());
188             }
189             transformer.transform(xmlSource, result);
190 
191             final org.w3c.dom.Node transformedNode = result.getNode();
192             if (transformedNode.getFirstChild().getNodeType() == Node.ELEMENT_NODE) {
193                 return transformedNode;
194             }
195             //output is not DOM (text)
196             xmlSource = new DOMSource(source.getDomNodeOrDie());
197             final StringWriter writer = new StringWriter();
198             final Result streamResult = new StreamResult(writer);
199             transformer.transform(xmlSource, streamResult);
200             return writer.toString();
201         }
202         catch (final Exception e) {
203             throw Context.reportRuntimeError("Exception: " + e);
204         }
205     }
206 
207     private void transform(final XMLDOMNode source, final DomNode parent) {
208         final Object result = transform(source);
209         if (result instanceof org.w3c.dom.Node) {
210             final SgmlPage parentPage = parent.getPage();
211             final NodeList children = ((org.w3c.dom.Node) result).getChildNodes();
212             for (int i = 0; i < children.getLength(); i++) {
213                 XmlUtil.appendChild(parentPage, parent, children.item(i), false);
214             }
215         }
216         else {
217             final DomText text = new DomText(parent.getPage(), (String) result);
218             parent.appendChild(text);
219         }
220     }
221 
222     /**
223      * Imports the specified stylesheet into this XSLTProcessor for transformations. The specified node
224      * may be either a document node or an element node. If it is a document node, then the document can
225      * contain either a XSLT stylesheet or a LRE stylesheet. If it is an element node, it must be the
226      * xsl:stylesheet (or xsl:transform) element of an XSLT stylesheet.
227      *
228      * @param style the root-node of an XSLT stylesheet (may be a document node or an element node)
229      */
230     public void importStylesheet(final XMLDOMNode style) {
231         style_ = style;
232     }
233 }