View Javadoc
1   /*
2    * Copyright (c) 2002-2018 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 WebWindow ww = getWindow().getWebWindow();
135             final HtmlPage htmlPage = (HtmlPage) ww.getEnclosedPage();
136             final WebRequest request = new WebRequest(htmlPage.getFullyQualifiedUrl(xmlSource));
137             final WebResponse webResponse = ww.getWebClient().loadWebResponse(request);
138             final XmlPage page = new XmlPage(webResponse, ww, false);
139             setDomNode(page);
140             return true;
141         }
142         catch (final IOException e) {
143             if (LOG.isDebugEnabled()) {
144                 LOG.debug("Error parsing XML from '" + xmlSource + "'", e);
145             }
146             return false;
147         }
148     }
149 
150     /**
151      * Loads an XML document using the supplied string.
152      *
153      * @param strXML A string containing the XML string to load into this XML document object
154      *        This string can contain an entire XML document or a well-formed fragment.
155      * @return true if the load succeeded; false if the load failed
156      */
157     public boolean loadXML(final String strXML) {
158         final WebWindow webWindow = getWindow().getWebWindow();
159         try {
160             if (StringUtils.isEmpty(strXML) && getBrowserVersion().hasFeature(JS_DOMPARSER_EMPTY_STRING_IS_ERROR)) {
161                 throw new IOException("Error parsing XML '" + strXML + "'");
162             }
163 
164             final WebResponse webResponse = new StringWebResponse(strXML, webWindow.getEnclosedPage().getUrl());
165 
166             final XmlPage page = new XmlPage(webResponse, webWindow, false);
167             setDomNode(page);
168             return true;
169         }
170         catch (final IOException e) {
171             if (LOG.isDebugEnabled()) {
172                 LOG.debug("Error parsing XML\n" + strXML, e);
173             }
174 
175             if (getBrowserVersion().hasFeature(JS_DOMPARSER_EXCEPTION_ON_ERROR)) {
176                 throw asJavaScriptException(
177                         new DOMException("Syntax Error",
178                             DOMException.SYNTAX_ERR));
179             }
180             if (getBrowserVersion().hasFeature(JS_DOMPARSER_PARSERERROR_ON_ERROR)) {
181                 try {
182                     final XmlPage page = createParserErrorXmlPage("Syntax Error", webWindow);
183                     setDomNode(page);
184                 }
185                 catch (final IOException eI) {
186                     LOG.error("Could not handle ParserError", e);
187                 }
188             }
189 
190             return false;
191         }
192     }
193 
194     private static XmlPage createParserErrorXmlPage(final String message, final WebWindow webWindow)
195             throws IOException {
196         final String xml = "<parsererror xmlns=\"http://www.mozilla.org/newlayout/xml/parsererror.xml\">\n"
197             + message + "\n"
198             + "<sourcetext></sourcetext>\n"
199             + "</parsererror>";
200 
201         final WebResponse webResponse = new StringWebResponse(xml, webWindow.getEnclosedPage().getUrl());
202 
203         return new XmlPage(webResponse, webWindow, false);
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public SimpleScriptable makeScriptableFor(final DomNode domNode) {
211         final SimpleScriptable scriptable;
212 
213         // TODO: cleanup, getScriptObject() should be used!!!
214         if (domNode instanceof DomElement && !(domNode instanceof HtmlElement)) {
215             if (domNode instanceof SvgElement) {
216                 final Class<? extends HtmlUnitScriptable> javaScriptClass
217                     = ((JavaScriptEngine) getWindow().getWebWindow().getWebClient()
218                         .getJavaScriptEngine()).getJavaScriptClass(domNode.getClass());
219                 try {
220                     scriptable = (SimpleScriptable) javaScriptClass.newInstance();
221                 }
222                 catch (final Exception e) {
223                     throw Context.throwAsScriptRuntimeEx(e);
224                 }
225             }
226             else {
227                 scriptable = new Element();
228             }
229         }
230         else if (domNode instanceof DomAttr) {
231             scriptable = new Attr();
232         }
233         else {
234             return super.makeScriptableFor(domNode);
235         }
236 
237         scriptable.setPrototype(getPrototype(scriptable.getClass()));
238         scriptable.setParentScope(getParentScope());
239         scriptable.setDomNode(domNode);
240         return scriptable;
241     }
242 
243     /**
244      * {@inheritDoc}
245      */
246     @Override
247     protected void initParentScope(final DomNode domNode, final SimpleScriptable scriptable) {
248         scriptable.setParentScope(getParentScope());
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     @JsxFunction
256     public HTMLCollection getElementsByTagName(final String tagName) {
257         final DomNode firstChild = getDomNodeOrDie().getFirstChild();
258         if (firstChild == null) {
259             return HTMLCollection.emptyCollection(getWindow().getDomNodeOrDie());
260         }
261 
262         final HTMLCollection collection = new HTMLCollection(getDomNodeOrDie(), false) {
263             @Override
264             protected boolean isMatching(final DomNode node) {
265                 final String nodeName;
266                 if (getBrowserVersion().hasFeature(JS_XML_GET_ELEMENTS_BY_TAG_NAME_LOCAL)) {
267                     nodeName = node.getLocalName();
268                 }
269                 else {
270                     nodeName = node.getNodeName();
271                 }
272 
273                 return nodeName.equals(tagName);
274             }
275         };
276 
277         return collection;
278     }
279 
280     /**
281      * Returns the element with the specified ID, as long as it is an HTML element; {@code null} otherwise.
282      * @param id the ID to search for
283      * @return the element with the specified ID, as long as it is an HTML element; {@code null} otherwise
284      */
285     @JsxFunction
286     public Object getElementById(final String id) {
287         final DomNode domNode = getDomNodeOrDie();
288         final Object domElement = domNode.getFirstByXPath("//*[@id = \"" + id + "\"]");
289         if (domElement != null) {
290             if (!(domNode instanceof XmlPage) || domElement instanceof HtmlElement
291                     || getBrowserVersion().hasFeature(JS_XML_GET_ELEMENT_BY_ID__ANY_ELEMENT)) {
292                 return ((DomElement) domElement).getScriptableObject();
293             }
294             if (LOG.isDebugEnabled()) {
295                 LOG.debug("getElementById(" + id + "): no HTML DOM node found with this ID");
296             }
297         }
298         return null;
299     }
300 
301     /**
302      * Creates a new ProcessingInstruction.
303      * @param target the target
304      * @param data the data
305      * @return the new ProcessingInstruction
306      */
307     @JsxFunction
308     public Object createProcessingInstruction(final String target, final String data) {
309         final ProcessingInstruction node = getPage().createProcessingInstruction(target, data);
310         return getScriptableFor(node);
311     }
312 
313     /**
314      * Creates a new createCDATASection.
315      * @param data the data
316      * @return the new CDATASection
317      */
318     @JsxFunction
319     public Object createCDATASection(final String data) {
320         final CDATASection node = getPage().createCDATASection(data);
321         return getScriptableFor(node);
322     }
323 }