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.BrowserVersionFeatures.JS_DOMPARSER_EMPTY_STRING_IS_ERROR;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOMPARSER_EXCEPTION_ON_ERROR;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOMPARSER_PARSERERROR_ON_ERROR;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_XML_GET_ELEMENTS_BY_TAG_NAME_LOCAL;
21  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_XML_GET_ELEMENT_BY_ID__ANY_ELEMENT;
22  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
23  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
24  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
25  
26  import java.io.IOException;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.w3c.dom.CDATASection;
32  import org.w3c.dom.ProcessingInstruction;
33  
34  import com.gargoylesoftware.htmlunit.StringWebResponse;
35  import com.gargoylesoftware.htmlunit.WebRequest;
36  import com.gargoylesoftware.htmlunit.WebResponse;
37  import com.gargoylesoftware.htmlunit.WebWindow;
38  import com.gargoylesoftware.htmlunit.html.DomAttr;
39  import com.gargoylesoftware.htmlunit.html.DomElement;
40  import com.gargoylesoftware.htmlunit.html.DomNode;
41  import com.gargoylesoftware.htmlunit.html.HtmlElement;
42  import com.gargoylesoftware.htmlunit.html.HtmlPage;
43  import com.gargoylesoftware.htmlunit.javascript.HtmlUnitScriptable;
44  import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
45  import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
46  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
47  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
48  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
49  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
50  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
51  import com.gargoylesoftware.htmlunit.javascript.host.Element;
52  import com.gargoylesoftware.htmlunit.javascript.host.dom.Attr;
53  import com.gargoylesoftware.htmlunit.javascript.host.dom.DOMException;
54  import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
55  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
56  import com.gargoylesoftware.htmlunit.svg.SvgElement;
57  import com.gargoylesoftware.htmlunit.xml.XmlPage;
58  
59  import net.sourceforge.htmlunit.corejs.javascript.Context;
60  
61  /**
62   * A JavaScript object for {@code XMLDocument}.
63   *
64   * @author Ahmed Ashour
65   * @author Marc Guillemot
66   * @author Sudhan Moghe
67   * @author Ronald Brill
68   * @author Chuck Dumont
69   * @author Frank Danek
70   */
71  @JsxClass
72  public class XMLDocument extends Document {
73  
74      private static final Log LOG = LogFactory.getLog(XMLDocument.class);
75  
76      private boolean async_ = true;
77  
78      /**
79       * Creates a new instance.
80       */
81      @JsxConstructor({CHROME, FF, EDGE})
82      public XMLDocument() {
83          this(null);
84      }
85  
86      /**
87       * Creates a new instance, with associated XmlPage.
88       * @param enclosingWindow the window
89       */
90      public XMLDocument(final WebWindow enclosingWindow) {
91          if (enclosingWindow != null) {
92              try {
93                  final XmlPage page = new XmlPage((WebResponse) null, enclosingWindow);
94                  setDomNode(page);
95              }
96              catch (final IOException e) {
97                  throw Context.reportRuntimeError("IOException: " + e);
98              }
99          }
100     }
101 
102     /**
103      * Sets the {@code async} attribute.
104      * @param async Whether or not to send the request to the server asynchronously
105      */
106     @JsxSetter(FF)
107     public void setAsync(final boolean async) {
108         async_ = async;
109     }
110 
111     /**
112      * Returns Whether or not to send the request to the server asynchronously.
113      * @return the {@code async} attribute
114      */
115     @JsxGetter(FF)
116     public boolean isAsync() {
117         return async_;
118     }
119 
120     /**
121      * Loads an XML document from the specified location.
122      *
123      * @param xmlSource a string containing a URL that specifies the location of the XML file
124      * @return true if the load succeeded; false if the load failed
125      */
126     @JsxFunction(FF)
127     public boolean load(final String xmlSource) {
128         if (async_) {
129             if (LOG.isDebugEnabled()) {
130                 LOG.debug("XMLDocument.load(): 'async' is true, currently treated as false.");
131             }
132         }
133         try {
134             final HtmlPage htmlPage = (HtmlPage) getWindow().getWebWindow().getEnclosedPage();
135             final WebRequest request = new WebRequest(htmlPage.getFullyQualifiedUrl(xmlSource));
136             final WebResponse webResponse = getWindow().getWebWindow().getWebClient().loadWebResponse(request);
137             final XmlPage page = new XmlPage(webResponse, getWindow().getWebWindow(), false);
138             setDomNode(page);
139             return true;
140         }
141         catch (final IOException e) {
142             if (LOG.isDebugEnabled()) {
143                 LOG.debug("Error parsing XML from '" + xmlSource + "'", e);
144             }
145             return false;
146         }
147     }
148 
149     /**
150      * Loads an XML document using the supplied string.
151      *
152      * @param strXML A string containing the XML string to load into this XML document object
153      *        This string can contain an entire XML document or a well-formed fragment.
154      * @return true if the load succeeded; false if the load failed
155      */
156     public boolean loadXML(final String strXML) {
157         final WebWindow webWindow = getWindow().getWebWindow();
158         try {
159             if (StringUtils.isEmpty(strXML) && getBrowserVersion().hasFeature(JS_DOMPARSER_EMPTY_STRING_IS_ERROR)) {
160                 throw new IOException("Error parsing XML '" + strXML + "'");
161             }
162 
163             final WebResponse webResponse = new StringWebResponse(strXML, webWindow.getEnclosedPage().getUrl());
164 
165             final XmlPage page = new XmlPage(webResponse, webWindow, false);
166             setDomNode(page);
167             return true;
168         }
169         catch (final IOException e) {
170             if (LOG.isDebugEnabled()) {
171                 LOG.debug("Error parsing XML\n" + strXML, e);
172             }
173 
174             if (getBrowserVersion().hasFeature(JS_DOMPARSER_EXCEPTION_ON_ERROR)) {
175                 throw asJavaScriptException(
176                         new DOMException("Syntax Error",
177                             DOMException.SYNTAX_ERR));
178             }
179             if (getBrowserVersion().hasFeature(JS_DOMPARSER_PARSERERROR_ON_ERROR)) {
180                 try {
181                     final XmlPage page = createParserErrorXmlPage("Syntax Error", webWindow);
182                     setDomNode(page);
183                 }
184                 catch (final IOException eI) {
185                     LOG.error("Could not handle ParserError", e);
186                 }
187             }
188 
189             return false;
190         }
191     }
192 
193     private static XmlPage createParserErrorXmlPage(final String message, final WebWindow webWindow)
194             throws IOException {
195         final String xml = "<parsererror xmlns=\"http://www.mozilla.org/newlayout/xml/parsererror.xml\">\n"
196             + message + "\n"
197             + "<sourcetext></sourcetext>\n"
198             + "</parsererror>";
199 
200         final WebResponse webResponse = new StringWebResponse(xml, webWindow.getEnclosedPage().getUrl());
201 
202         return new XmlPage(webResponse, webWindow, false);
203     }
204 
205     /**
206      * {@inheritDoc}
207      */
208     @Override
209     public SimpleScriptable makeScriptableFor(final DomNode domNode) {
210         final SimpleScriptable scriptable;
211 
212         // TODO: cleanup, getScriptObject() should be used!!!
213         if (domNode instanceof DomElement && !(domNode instanceof HtmlElement)) {
214             if (domNode instanceof SvgElement) {
215                 final Class<? extends HtmlUnitScriptable> javaScriptClass
216                     = ((JavaScriptEngine) getWindow().getWebWindow().getWebClient()
217                         .getJavaScriptEngine()).getJavaScriptClass(domNode.getClass());
218                 try {
219                     scriptable = (SimpleScriptable) javaScriptClass.newInstance();
220                 }
221                 catch (final Exception e) {
222                     throw Context.throwAsScriptRuntimeEx(e);
223                 }
224             }
225             else {
226                 scriptable = new Element();
227             }
228         }
229         else if (domNode instanceof DomAttr) {
230             scriptable = new Attr();
231         }
232         else {
233             return super.makeScriptableFor(domNode);
234         }
235 
236         scriptable.setPrototype(getPrototype(scriptable.getClass()));
237         scriptable.setParentScope(getParentScope());
238         scriptable.setDomNode(domNode);
239         return scriptable;
240     }
241 
242     /**
243      * {@inheritDoc}
244      */
245     @Override
246     protected void initParentScope(final DomNode domNode, final SimpleScriptable scriptable) {
247         scriptable.setParentScope(getParentScope());
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
254     @JsxFunction
255     public HTMLCollection getElementsByTagName(final String tagName) {
256         final DomNode firstChild = getDomNodeOrDie().getFirstChild();
257         if (firstChild == null) {
258             return HTMLCollection.emptyCollection(getWindow().getDomNodeOrDie());
259         }
260 
261         final HTMLCollection collection = new HTMLCollection(getDomNodeOrDie(), false) {
262             @Override
263             protected boolean isMatching(final DomNode node) {
264                 final String nodeName;
265                 if (getBrowserVersion().hasFeature(JS_XML_GET_ELEMENTS_BY_TAG_NAME_LOCAL)) {
266                     nodeName = node.getLocalName();
267                 }
268                 else {
269                     nodeName = node.getNodeName();
270                 }
271 
272                 return nodeName.equals(tagName);
273             }
274         };
275 
276         return collection;
277     }
278 
279     /**
280      * Returns the element with the specified ID, as long as it is an HTML element; {@code null} otherwise.
281      * @param id the ID to search for
282      * @return the element with the specified ID, as long as it is an HTML element; {@code null} otherwise
283      */
284     @JsxFunction
285     public Object getElementById(final String id) {
286         final DomNode domNode = getDomNodeOrDie();
287         final Object domElement = domNode.getFirstByXPath("//*[@id = \"" + id + "\"]");
288         if (domElement != null) {
289             if (!(domNode instanceof XmlPage) || domElement instanceof HtmlElement
290                     || getBrowserVersion().hasFeature(JS_XML_GET_ELEMENT_BY_ID__ANY_ELEMENT)) {
291                 return ((DomElement) domElement).getScriptableObject();
292             }
293             if (LOG.isDebugEnabled()) {
294                 LOG.debug("getElementById(" + id + "): no HTML DOM node found with this ID");
295             }
296         }
297         return null;
298     }
299 
300     /**
301      * Creates a new ProcessingInstruction.
302      * @param target the target
303      * @param data the data
304      * @return the new ProcessingInstruction
305      */
306     @JsxFunction
307     public Object createProcessingInstruction(final String target, final String data) {
308         final ProcessingInstruction node = getPage().createProcessingInstruction(target, data);
309         return getScriptableFor(node);
310     }
311 
312     /**
313      * Creates a new createCDATASection.
314      * @param data the data
315      * @return the new CDATASection
316      */
317     @JsxFunction
318     public Object createCDATASection(final String data) {
319         final CDATASection node = getPage().createCDATASection(data);
320         return getScriptableFor(node);
321     }
322 }