0001: /*
0002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * 1. Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: * 2. Redistributions in binary form must reproduce the above copyright notice,
0010: * this list of conditions and the following disclaimer in the documentation
0011: * and/or other materials provided with the distribution.
0012: * 3. The end-user documentation included with the redistribution, if any, must
0013: * include the following acknowledgment:
0014: *
0015: * "This product includes software developed by Gargoyle Software Inc.
0016: * (http://www.GargoyleSoftware.com/)."
0017: *
0018: * Alternately, this acknowledgment may appear in the software itself, if
0019: * and wherever such third-party acknowledgments normally appear.
0020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
0021: * products derived from this software without prior written permission.
0022: * For written permission, please contact info@GargoyleSoftware.com.
0023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
0024: * "HtmlUnit" appear in their name, without prior written permission of
0025: * Gargoyle Software Inc.
0026: *
0027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
0028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
0029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
0030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
0031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
0033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0037: */
0038: package com.gargoylesoftware.htmlunit.javascript.host;
0039:
0040: import java.io.IOException;
0041: import java.net.URL;
0042: import java.util.Arrays;
0043: import java.util.Collections;
0044: import java.util.Date;
0045: import java.util.HashMap;
0046: import java.util.List;
0047: import java.util.Map;
0048: import java.util.StringTokenizer;
0049:
0050: import org.apache.commons.httpclient.Cookie;
0051: import org.apache.commons.httpclient.HttpState;
0052: import org.apache.commons.httpclient.cookie.CookiePolicy;
0053: import org.apache.commons.httpclient.cookie.CookieSpec;
0054: import org.apache.commons.httpclient.util.DateParseException;
0055: import org.apache.commons.httpclient.util.DateUtil;
0056: import org.apache.commons.lang.StringUtils;
0057: import org.jaxen.JaxenException;
0058: import org.jaxen.XPathFunctionContext;
0059: import org.mozilla.javascript.Context;
0060: import org.mozilla.javascript.Function;
0061: import org.mozilla.javascript.Scriptable;
0062: import org.mozilla.javascript.UniqueTag;
0063: import org.w3c.dom.DOMException;
0064:
0065: import com.gargoylesoftware.htmlunit.BrowserVersion;
0066: import com.gargoylesoftware.htmlunit.ElementNotFoundException;
0067: import com.gargoylesoftware.htmlunit.SgmlPage;
0068: import com.gargoylesoftware.htmlunit.StringWebResponse;
0069: import com.gargoylesoftware.htmlunit.WebClient;
0070: import com.gargoylesoftware.htmlunit.WebResponse;
0071: import com.gargoylesoftware.htmlunit.WebWindow;
0072: import com.gargoylesoftware.htmlunit.html.DomDocumentFragment;
0073: import com.gargoylesoftware.htmlunit.html.DomNode;
0074: import com.gargoylesoftware.htmlunit.html.DomText;
0075: import com.gargoylesoftware.htmlunit.html.HTMLParser;
0076: import com.gargoylesoftware.htmlunit.html.HtmlDivision;
0077: import com.gargoylesoftware.htmlunit.html.HtmlElement;
0078: import com.gargoylesoftware.htmlunit.html.HtmlForm;
0079: import com.gargoylesoftware.htmlunit.html.HtmlImage;
0080: import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame;
0081: import com.gargoylesoftware.htmlunit.html.HtmlPage;
0082: import com.gargoylesoftware.htmlunit.html.HtmlScript;
0083: import com.gargoylesoftware.htmlunit.html.xpath.FunctionContextWrapper;
0084: import com.gargoylesoftware.htmlunit.html.xpath.HtmlUnitXPath;
0085: import com.gargoylesoftware.htmlunit.html.xpath.LowerCaseFunction;
0086: import com.gargoylesoftware.htmlunit.javascript.HTMLCollection;
0087: import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
0088: import com.gargoylesoftware.htmlunit.xml.XmlPage;
0089:
0090: /**
0091: * A JavaScript object for a Document.
0092: *
0093: * @version $Revision: 2156 $
0094: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
0095: * @author David K. Taylor
0096: * @author <a href="mailto:chen_jun@users.sourceforge.net">Chen Jun</a>
0097: * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
0098: * @author Chris Erskine
0099: * @author Marc Guillemot
0100: * @author Daniel Gredler
0101: * @author Michael Ottati
0102: * @author <a href="mailto:george@murnock.com">George Murnock</a>
0103: * @author Ahmed Ashour
0104: * @author Rob Di Marco
0105: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/obj_document.asp">
0106: * MSDN documentation</a>
0107: * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-7068919">
0108: * W3C Dom Level 1</a>
0109: */
0110: public class Document extends Node {
0111:
0112: private static final long serialVersionUID = -7646789903352066465L;
0113:
0114: /**
0115: * Map<String, Class> which maps strings a caller may use when calling into
0116: * {@link #jsxFunction_createEvent(String)} to the associated event class. To support a new
0117: * event creation type, the event type and associated class need to be added into this map in
0118: * the static initializer. The map is unmodifiable. Any class that is a value in this map MUST
0119: * have a no-arg constructor.
0120: */
0121: private static final Map SUPPORTED_EVENT_TYPE_MAP;
0122:
0123: private HTMLCollection all_; // has to be a member to have equality (==) working
0124: private HTMLCollection forms_; // has to be a member to have equality (==) working
0125: private HTMLCollection links_; // has to be a member to have equality (==) working
0126: private HTMLCollection images_; // has to be a member to have equality (==) working
0127: private HTMLCollection scripts_; // has to be a member to have equality (==) working
0128: private HTMLCollection anchors_; // has to be a member to have equality (==) working
0129: private StyleSheetList styleSheets_; // has to be a member to have equality (==) working
0130:
0131: /** The buffer that will be used for calls to document.write() */
0132: private final StringBuffer writeBuffer_ = new StringBuffer();
0133: private boolean writeInCurrentDocument_ = true;
0134: private String domain_;
0135: private Window window_;
0136:
0137: private final FunctionContextWrapper functionContext_;
0138: private DOMImplementation implementation_;
0139:
0140: /** Initializes the supported event type map. */
0141: static {
0142: final Map eventMap = new HashMap();
0143: eventMap.put("Event", Event.class);
0144: eventMap.put("Events", Event.class);
0145: eventMap.put("HTMLEvents", Event.class);
0146: eventMap.put("UIEvent", UIEvent.class);
0147: eventMap.put("UIEvents", UIEvent.class);
0148: eventMap.put("MouseEvent", MouseEvent.class);
0149: eventMap.put("MouseEvents", MouseEvent.class);
0150: SUPPORTED_EVENT_TYPE_MAP = Collections
0151: .unmodifiableMap(eventMap);
0152: }
0153:
0154: /**
0155: * Create an instance. Javascript objects must have a default constructor.
0156: */
0157: public Document() {
0158: // add custom functions to custom context
0159: functionContext_ = new FunctionContextWrapper(
0160: XPathFunctionContext.getInstance());
0161: functionContext_.registerFunction("lower-case",
0162: new LowerCaseFunction());
0163: }
0164:
0165: /**
0166: * Javascript constructor. This must be declared in every javascript file because
0167: * the rhino engine won't walk up the hierarchy looking for constructors.
0168: */
0169: public void jsConstructor() {
0170: }
0171:
0172: /**
0173: * Define the Window JavaScript object that encloses this Document object.
0174: *
0175: * @param window The Window JavaScript object that encloses this document.
0176: */
0177: void setWindow(final Window window) {
0178: window_ = window;
0179: }
0180:
0181: /**
0182: * Return the html page that this document is modeling..
0183: * @return The page.
0184: */
0185: public HtmlPage getHtmlPage() {
0186: return (HtmlPage) getDomNodeOrDie();
0187: }
0188:
0189: /**
0190: * Return the html page that this document is modeling or null if the
0191: * page is empty.
0192: * @return The page.
0193: */
0194: public HtmlPage getHtmlPageOrNull() {
0195: return (HtmlPage) getDomNodeOrNull();
0196: }
0197:
0198: /**
0199: * Return the value of the javascript attribute "forms".
0200: * @return The value of this attribute.
0201: */
0202: public Object jsxGet_forms() {
0203: if (forms_ == null) {
0204: forms_ = new HTMLCollection(this );
0205: try {
0206: forms_.init(getDomNodeOrDie(), new HtmlUnitXPath(
0207: "//form"));
0208: } catch (final JaxenException e) {
0209: throw Context
0210: .reportRuntimeError("Failed to initialize collection document.forms: "
0211: + e.getMessage());
0212: }
0213: }
0214: return forms_;
0215: }
0216:
0217: /**
0218: * Return the value of the javascript attribute "links". Refer also to the
0219: * <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/collections/links.asp"
0220: * MSDN documentation</a>
0221: * @return The value of this attribute.
0222: */
0223: public Object jsxGet_links() {
0224: if (links_ == null) {
0225: links_ = new HTMLCollection(this );
0226: try {
0227: links_.init(getDomNodeOrDie(), new HtmlUnitXPath(
0228: "//a[@href] | //area[@href]"));
0229: } catch (final JaxenException e) {
0230: throw Context
0231: .reportRuntimeError("Failed to initialize collection document.links: "
0232: + e.getMessage());
0233: }
0234: }
0235: return links_;
0236: }
0237:
0238: /**
0239: * Return the value of the javascript attribute "anchors".
0240: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/collections/anchors.asp">
0241: * MSDN documentation</a>
0242: * @see <a href="http://www.mozilla.org/docs/dom/domref/dom_doc_ref4.html#1024543">
0243: * Gecko DOM reference</a>
0244: * @return The value of this attribute.
0245: */
0246: public Object jsxGet_anchors() {
0247: if (anchors_ == null) {
0248: anchors_ = new HTMLCollection(this );
0249: try {
0250: final String xpath;
0251: if (getWindow().getWebWindow().getWebClient()
0252: .getBrowserVersion().isIE()) {
0253: xpath = "//a[@name or @id]";
0254: } else {
0255: xpath = "//a[@name]";
0256: }
0257:
0258: anchors_.init(getDomNodeOrDie(), new HtmlUnitXPath(
0259: xpath));
0260: } catch (final JaxenException e) {
0261: throw Context
0262: .reportRuntimeError("Failed to initialize collection document.anchors: "
0263: + e.getMessage());
0264: }
0265: }
0266: return anchors_;
0267: }
0268:
0269: /**
0270: * javascript function "write" may accept a variable number of args.
0271: * It's not documented by W3C, Mozilla or MSDN but works with Mozilla and IE.
0272: * @param context The javascript context
0273: * @param thisObj The scriptable
0274: * @param args The arguments passed into the method.
0275: * @param function The function.
0276: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/write.asp">
0277: * MSDN documentation</a>
0278: */
0279: public static void jsxFunction_write(final Context context,
0280: final Scriptable this Obj, final Object[] args,
0281: final Function function) {
0282:
0283: final Document this AsDocument = getDocument(this Obj);
0284: this AsDocument.write(concatArgsAsString(args));
0285: }
0286:
0287: /**
0288: * Converts the arguments to strings and concatenate them.
0289: * @param args the javascript arguments
0290: * @return the string concatenation
0291: */
0292: private static String concatArgsAsString(final Object[] args) {
0293: final StringBuffer buffer = new StringBuffer();
0294: for (int i = 0; i < args.length; i++) {
0295: buffer.append(Context.toString(args[i]));
0296: }
0297: return buffer.toString();
0298: }
0299:
0300: /**
0301: * javascript function "writeln" may accept a variable number of args.
0302: * It's not documented by W3C, Mozilla or MSDN but works with Mozilla and IE.
0303: * @param context The javascript context
0304: * @param thisObj The scriptable
0305: * @param args The arguments passed into the method.
0306: * @param function The function.
0307: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/writeln.asp">
0308: * MSDN documentation</a>
0309: */
0310: public static void jsxFunction_writeln(final Context context,
0311: final Scriptable this Obj, final Object[] args,
0312: final Function function) {
0313:
0314: final Document this AsDocument = getDocument(this Obj);
0315: this AsDocument.write(concatArgsAsString(args) + "\n");
0316: }
0317:
0318: /**
0319: * Gets the document instance.
0320: * @param document maybe the prototype when function is used without "this"
0321: * @return the current document
0322: */
0323: private static Document getDocument(final Scriptable this Obj) {
0324: // if function is used "detached", then thisObj is the top scope (ie Window), not the real object
0325: // cf unit test DocumentTest#testDocumentWrite_AssignedToVar
0326: if (this Obj instanceof Document) {
0327: return (Document) this Obj;
0328: } else {
0329: final Window window = getWindow(this Obj);
0330: final BrowserVersion browser = window.getWebWindow()
0331: .getWebClient().getBrowserVersion();
0332: if (browser.isIE()) {
0333: return window.jsxGet_document();
0334: } else {
0335: throw Context
0336: .reportRuntimeError("Function can't be used detached from document");
0337: }
0338: }
0339: }
0340:
0341: /**
0342: * javascript function "write".
0343: * @param content the content to write
0344: */
0345: protected void write(final String content) {
0346: getLog().debug("write: " + content);
0347:
0348: writeBuffer_.append(content);
0349:
0350: if (!writeInCurrentDocument_) {
0351: getLog().debug("written content added to buffer");
0352: } else {
0353: // open() hasn't been called
0354: final String bufferedContent = writeBuffer_.toString();
0355: if (canAlreadyBeParsed(bufferedContent)) {
0356: writeBuffer_.setLength(0);
0357: getLog().debug(
0358: "parsing buffered content: " + bufferedContent);
0359:
0360: final HtmlPage page = (HtmlPage) getDomNodeOrDie();
0361: // get the node at which end the parsed content should be added
0362: HtmlElement current = getLastHtmlElement(page
0363: .getDocumentHtmlElement());
0364: getLog().debug("current: " + current);
0365:
0366: // quick and dirty workaround as long as IFRAME JS object aren't an HTMLElement
0367: if (current instanceof HtmlInlineFrame) {
0368: current = (HtmlElement) current.getParentDomNode();
0369: }
0370: ((HTMLElement) getJavaScriptNode(current))
0371: .jsxFunction_insertAdjacentHTML(
0372: HTMLElement.POSITION_BEFORE_END,
0373: bufferedContent);
0374: } else {
0375: getLog().debug(
0376: "write: not enough content to parsed it now");
0377: }
0378: }
0379: }
0380:
0381: /**
0382: * Indicates if the content is a well formed html snippet that can already be parsed to be added
0383: * to the dom
0384: * @param content the html snippet
0385: * @return <code>false</code> if it not well formed
0386: */
0387: private boolean canAlreadyBeParsed(final String content) {
0388: // all <script> must have their </script> because the parser doesn't close automatically this tag
0389: // All tags must be complete, that is from '<' to '>'.
0390: final int tagOutside = 0;
0391: final int tagSart = 1;
0392: final int tagInName = 2;
0393: final int tagInside = 3;
0394: int tagState = tagOutside;
0395: int tagNameBeginIndex = 0;
0396: int scriptTagCount = 0;
0397: boolean tagIsOpen = true;
0398: for (int index = 0; index < content.length(); index++) {
0399: final char currentChar = content.charAt(index);
0400: switch (tagState) {
0401: case tagOutside:
0402: if (currentChar == '<') {
0403: tagState = tagSart;
0404: tagIsOpen = true;
0405: }
0406: break;
0407: case tagSart:
0408: if (currentChar == '/') {
0409: tagIsOpen = false;
0410: tagNameBeginIndex = index + 1;
0411: } else {
0412: tagNameBeginIndex = index;
0413: }
0414: tagState = tagInName;
0415: break;
0416: case tagInName:
0417: if (!Character.isLetter(currentChar)) {
0418: final String tagName = content.substring(
0419: tagNameBeginIndex, index);
0420: if (tagName.equalsIgnoreCase("script")) {
0421: if (tagIsOpen) {
0422: scriptTagCount++;
0423: } else if (scriptTagCount > 0) {
0424: // Ignore extra close tags for now. Let the
0425: // parser deal with them.
0426: scriptTagCount--;
0427: }
0428: }
0429: if (currentChar == '>') {
0430: tagState = tagOutside;
0431: } else {
0432: tagState = tagInside;
0433: }
0434: }
0435: break;
0436: case tagInside:
0437: if (currentChar == '>') {
0438: tagState = tagOutside;
0439: }
0440: break;
0441: default:
0442: // nothing
0443: }
0444: }
0445: if (scriptTagCount > 0 || tagState != tagOutside) {
0446: return false;
0447: }
0448:
0449: return true;
0450: }
0451:
0452: /**
0453: * Gets the node that is the last one when exploring following nodes, depth-first.
0454: * @param node the node to search
0455: * @return the searched node
0456: */
0457: HtmlElement getLastHtmlElement(final HtmlElement node) {
0458: final DomNode lastChild = node.getLastDomChild();
0459: if (lastChild == null || !(lastChild instanceof HtmlElement)
0460: || lastChild instanceof HtmlScript) {
0461: return node;
0462: }
0463:
0464: return getLastHtmlElement((HtmlElement) lastChild);
0465: }
0466:
0467: /**
0468: * Returns the cookie attribute.
0469: * @return The cookie attribute
0470: */
0471: public String jsxGet_cookie() {
0472:
0473: final HtmlPage page = getHtmlPage();
0474: final HttpState state = page.getWebClient().getWebConnection()
0475: .getState();
0476: final URL url = page.getWebResponse().getUrl();
0477: final boolean secure = "https".equals(url.getProtocol());
0478: final int port;
0479: if (url.getPort() != -1) {
0480: port = url.getPort();
0481: } else {
0482: port = url.getDefaultPort();
0483: }
0484:
0485: final CookieSpec spec = CookiePolicy
0486: .getCookieSpec(WebClient.HTMLUNIT_COOKIE_POLICY);
0487: final Cookie[] cookies = spec.match(url.getHost(), port, url
0488: .getPath(), secure, state.getCookies());
0489: if (cookies == null) {
0490: return "";
0491: }
0492:
0493: final StringBuffer buffer = new StringBuffer();
0494:
0495: for (int i = 0; i < cookies.length; i++) {
0496: if (i != 0) {
0497: buffer.append(";");
0498: }
0499: buffer.append(cookies[i].getName());
0500: buffer.append("=");
0501: buffer.append(cookies[i].getValue());
0502: }
0503: return buffer.toString();
0504: }
0505:
0506: /**
0507: * Adds a cookie.
0508: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/cookie.asp">
0509: * MSDN documentation</a>
0510: * @param newCookie in the format "name=value[;expires=date][;domain=domainname][;path=path][;secure]
0511: */
0512: public void jsxSet_cookie(final String newCookie) {
0513: final HttpState state = getHtmlPage().getWebClient()
0514: .getWebConnection().getState();
0515:
0516: final Cookie cookie = buildCookie(newCookie, getHtmlPage()
0517: .getWebResponse().getUrl());
0518: state.addCookie(cookie);
0519: getLog().info("Added cookie: " + cookie);
0520: }
0521:
0522: /**
0523: * Builds a cookie object from the string representation allowed in JS
0524: * @param newCookie in the format "name=value[;expires=date][;domain=domainname][;path=path][;secure]
0525: * @param currentURL the url of the current page
0526: * @return the cookie
0527: */
0528: static Cookie buildCookie(final String newCookie,
0529: final URL currentURL) {
0530: final StringTokenizer st = new StringTokenizer(newCookie, ";");
0531: final String nameValue = st.nextToken();
0532:
0533: final String name = StringUtils.substringBefore(nameValue, "=")
0534: .trim();
0535: final String value = StringUtils.substringAfter(nameValue, "=")
0536: .trim();
0537:
0538: final Map attributes = new HashMap();
0539: // default values
0540: attributes.put("domain", currentURL.getHost());
0541: // default value "" as it seems that org.apache.commons.httpclient.cookie.CookieSpec
0542: // doesn't like null as path
0543: attributes.put("path", "");
0544:
0545: while (st.hasMoreTokens()) {
0546: final String token = st.nextToken();
0547: final int indexEqual = token.indexOf("=");
0548: if (indexEqual > -1) {
0549: attributes.put(token.substring(0, indexEqual)
0550: .toLowerCase().trim(), token.substring(
0551: indexEqual + 1).trim());
0552: } else {
0553: attributes
0554: .put(token.toLowerCase().trim(), Boolean.TRUE);
0555: }
0556: }
0557:
0558: // Try to parse the <expires> value as a date if specified
0559: Date expires = null;
0560: final String date = (String) attributes.get("expires");
0561: if (date != null) {
0562: try {
0563: expires = DateUtil.parseDate(date);
0564: } catch (final DateParseException e) {
0565: // nothing
0566: }
0567: }
0568:
0569: final String domain = (String) attributes.get("domain");
0570: final String path = (String) attributes.get("path");
0571: final boolean secure = (attributes.get("secure") != null);
0572: final Cookie cookie = new Cookie(domain, name, value, path,
0573: expires, secure);
0574:
0575: return cookie;
0576: }
0577:
0578: /**
0579: * Return the value of the "location" property.
0580: * @return The value of the "location" property
0581: */
0582: public Location jsxGet_location() {
0583: return window_.jsxGet_location();
0584: }
0585:
0586: /**
0587: * Sets the value of the "location" property. The location's default property is "href",
0588: * so setting "document.location='http://www.sf.net'" is equivalent to setting
0589: * "document.location.href='http://www.sf.net'".
0590: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/objects/obj_location.asp">
0591: * MSDN documentation</a>
0592: * @param location the location to navigate to
0593: * @throws IOException when location loading fails
0594: */
0595: public void jsxSet_location(final String location)
0596: throws IOException {
0597: window_.jsxSet_location(location);
0598: }
0599:
0600: /**
0601: * Return the value of the "images" property.
0602: * @return The value of the "images" property
0603: */
0604: public Object jsxGet_images() {
0605: if (images_ == null) {
0606: images_ = new HTMLCollection(this );
0607: try {
0608: images_.init(getDomNodeOrDie(), new HtmlUnitXPath(
0609: "//img"));
0610: } catch (final JaxenException e) {
0611: throw Context
0612: .reportRuntimeError("Failed to initialize collection document.images: "
0613: + e.getMessage());
0614: }
0615: }
0616: return images_;
0617: }
0618:
0619: /**
0620: * Return the value of the "referrer" property.
0621: * @return The value of the "referrer" property
0622: */
0623: public String jsxGet_referrer() {
0624: final String referrer = getHtmlPage().getWebResponse()
0625: .getResponseHeaderValue("referrer");
0626: if (referrer == null) {
0627: return "";
0628: } else {
0629: return referrer;
0630: }
0631: }
0632:
0633: /**
0634: * Return the value of the "URL" property.
0635: * @return The value of the "URL" property
0636: */
0637: public String jsxGet_URL() {
0638: return getHtmlPage().getWebResponse().getUrl().toExternalForm();
0639: }
0640:
0641: /**
0642: * Return the value of the "all" property.
0643: * @return The value of the "all" property
0644: */
0645: public HTMLCollection jsxGet_all() {
0646: if (all_ == null) {
0647: all_ = new HTMLCollection(this );
0648: try {
0649: all_.init(getDomNodeOrDie(), new HtmlUnitXPath("//*"));
0650: } catch (final JaxenException e) {
0651: throw Context
0652: .reportRuntimeError("Failed to initialize collection document.all: "
0653: + e.getMessage());
0654: }
0655: }
0656: return all_;
0657: }
0658:
0659: /**
0660: * javascript function "open".
0661: * @param context The javascript context
0662: * @param scriptable The scriptable
0663: * @param args The arguments passed into the method.
0664: * @param function The function.
0665: * @return Nothing
0666: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/open_1.asp">
0667: * MSDN documentation</a>
0668: */
0669: public static Object jsxFunction_open(final Context context,
0670: final Scriptable scriptable, final Object[] args,
0671: final Function function) {
0672:
0673: final Document document = (Document) scriptable;
0674: if (!document.writeInCurrentDocument_) {
0675: document.getLog().warn(
0676: "open() called when document is already open.");
0677: }
0678: document.writeInCurrentDocument_ = false;
0679: return null;
0680: }
0681:
0682: /**
0683: * javascript function "close".
0684: * @throws IOException If an IO problem occurs.
0685: */
0686: public void jsxFunction_close() throws IOException {
0687:
0688: if (writeInCurrentDocument_) {
0689: getLog().warn("close() called when document is not open.");
0690: } else {
0691: final WebResponse webResponse = new StringWebResponse(
0692: writeBuffer_.toString());
0693: final HtmlPage page = getDomNodeOrDie().getPage();
0694: final WebClient webClient = page.getWebClient();
0695: final WebWindow window = page.getEnclosingWindow();
0696:
0697: webClient.loadWebResponseInto(webResponse, window);
0698:
0699: writeInCurrentDocument_ = true;
0700: writeBuffer_.setLength(0);
0701: }
0702: }
0703:
0704: /**
0705: * Get the JavaScript property "documentElement" for the document.
0706: * @return The root node for the document.
0707: */
0708: public SimpleScriptable jsxGet_documentElement() {
0709: return getScriptableFor(((HtmlPage) getDomNodeOrDie())
0710: .getDocumentHtmlElement());
0711: }
0712:
0713: /**
0714: * Get the window in which this document is contained.
0715: * @return the window
0716: */
0717: public Object jsxGet_defaultView() {
0718: return getWindow();
0719: }
0720:
0721: /**
0722: * Get the window in which this document is contained.
0723: * @return the window
0724: */
0725: public Object jsxGet_parentWindow() {
0726: return getWindow();
0727: }
0728:
0729: /**
0730: * Create a new HTML element with the given tag name.
0731: *
0732: * @param tagName The tag name
0733: * @return the new HTML element, or NOT_FOUND if the tag is not supported.
0734: */
0735: public Object jsxFunction_createElement(String tagName) {
0736: Object result = NOT_FOUND;
0737: try {
0738: final SgmlPage page = (SgmlPage) getDomNodeOrDie()
0739: .getNativePage();
0740: final BrowserVersion browserVersion = page.getWebClient()
0741: .getBrowserVersion();
0742:
0743: //IE can handle HTML
0744: if (tagName.startsWith("<") && browserVersion.isIE()) {
0745: try {
0746: final HtmlElement proxyNode = new HtmlDivision(
0747: null, HtmlDivision.TAG_NAME,
0748: getDomNodeOrDie().getPage(), null);
0749: HTMLParser.parseFragment(proxyNode, tagName);
0750: final DomNode resultNode = proxyNode
0751: .getFirstDomChild();
0752: resultNode.removeAllChildren();
0753: result = resultNode.getScriptObject();
0754: } catch (final Exception e) {
0755: getLog()
0756: .error(
0757: "Unexpected exception occurred while parsing html snippet",
0758: e);
0759: throw Context
0760: .reportRuntimeError("Unexpected exception occurred while parsing html snippet: "
0761: + e.getMessage());
0762: }
0763: } else {
0764: //Firefox handles only simple '<tagName>'
0765: if (tagName.startsWith("<") && tagName.endsWith(">")
0766: && browserVersion.isNetscape()) {
0767: tagName = tagName
0768: .substring(1, tagName.length() - 1);
0769: if (!tagName.matches("\\w+")) {
0770: getLog()
0771: .error(
0772: "Unexpected exception occurred while parsing html snippet");
0773: throw Context
0774: .reportRuntimeError("Unexpected exception occurred while parsing html snippet: "
0775: + tagName);
0776: }
0777: }
0778:
0779: final DomNode element;
0780: if (page instanceof HtmlPage) {
0781: element = ((HtmlPage) page)
0782: .createHtmlElement(tagName);
0783: } else {
0784: element = ((XmlPage) page)
0785: .createXmlElement(tagName);
0786: }
0787: final Object jsElement = getScriptableFor(element);
0788:
0789: if (jsElement == NOT_FOUND) {
0790: getLog()
0791: .debug(
0792: "createElement("
0793: + tagName
0794: + ") cannot return a result as there isn't a javascript object for the element "
0795: + element.getClass()
0796: .getName());
0797: } else {
0798: result = jsElement;
0799: }
0800: }
0801: } catch (final ElementNotFoundException e) {
0802: // Just fall through - result is already set to NOT_FOUND
0803: }
0804: return result;
0805: }
0806:
0807: /**
0808: * Create a new HTML element with the given tag name, and name
0809: *
0810: * @param namespaceURI the URI that identifies an XML namespace.
0811: * @param qualifiedName The qualified name of the element type to instantiate
0812: * @return the new HTML element, or NOT_FOUND if the tag is not supported.
0813: */
0814: public Object jsxFunction_createElementNS(
0815: final String namespaceURI, final String qualifiedName) {
0816: Object result = NOT_FOUND;
0817: try {
0818: final HtmlElement htmlElement = getDomNodeOrDie().getPage()
0819: .createHtmlElementNS(namespaceURI, qualifiedName);
0820: final Object jsElement = getScriptableFor(htmlElement);
0821:
0822: if (jsElement == NOT_FOUND) {
0823: getLog()
0824: .debug(
0825: "createElementNS("
0826: + namespaceURI
0827: + ','
0828: + qualifiedName
0829: + ") cannot return a result as there isn't a javascript object for the html element "
0830: + htmlElement.getClass()
0831: .getName());
0832: } else {
0833: result = jsElement;
0834: }
0835: } catch (final ElementNotFoundException e) {
0836: // Just fall through - result is already set to NOT_FOUND
0837: }
0838: return result;
0839: }
0840:
0841: /**
0842: * Create a new DocumentFragment
0843: * @return a newly created DocumentFragment.
0844: */
0845: public Object jsxFunction_createDocumentFragment() {
0846: final DomDocumentFragment fragment = ((SgmlPage) getDomNodeOrDie()
0847: .getNativePage()).createDomDocumentFragment();
0848: final DocumentFragment node = new DocumentFragment();
0849: node.setParentScope(getParentScope());
0850: node.setPrototype(getPrototype(node.getClass()));
0851: node.setDomNode(fragment);
0852: return getScriptableFor(fragment);
0853: }
0854:
0855: /**
0856: * Creates a new HTML attribute with the specified name.
0857: *
0858: * @param attributeName the name of the attribute to create
0859: * @return an attribute with the specified name.
0860: */
0861: public Attribute jsxFunction_createAttribute(
0862: final String attributeName) {
0863: final Attribute att = new Attribute();
0864: att.setPrototype(getPrototype(Attribute.class));
0865: att.setParentScope(getWindow());
0866: att.init(attributeName, null);
0867: return att;
0868: }
0869:
0870: /**
0871: * Creates a new Stylesheet.
0872: * Current implementation just creates an empty {@link Stylesheet} object.
0873: * @param url the stylesheet url
0874: * @param index where to insert the sheet in the collection
0875: * @return the newly created stylesheet
0876: */
0877: public Stylesheet jsxFunction_createStyleSheet(final String url,
0878: final int index) {
0879: // minimal implementation
0880: final Stylesheet stylesheet = new Stylesheet();
0881: stylesheet.setPrototype(getPrototype(Stylesheet.class));
0882: stylesheet.setParentScope(getWindow());
0883:
0884: return stylesheet;
0885: }
0886:
0887: /**
0888: * Create a new DOM text node with the given data.
0889: *
0890: * @param newData The string value for the text node.
0891: * @return the new text node or NOT_FOUND if there is an error.
0892: */
0893: public Object jsxFunction_createTextNode(final String newData) {
0894: Object result = NOT_FOUND;
0895: try {
0896: final DomNode domNode = new DomText(getDomNodeOrDie()
0897: .getNativePage(), newData);
0898: final Object jsElement = getScriptableFor(domNode);
0899:
0900: if (jsElement == NOT_FOUND) {
0901: getLog()
0902: .debug(
0903: "createTextNode("
0904: + newData
0905: + ") cannot return a result as there isn't a javascript object for the DOM node "
0906: + domNode.getClass().getName());
0907: } else {
0908: result = jsElement;
0909: }
0910: } catch (final ElementNotFoundException e) {
0911: // Just fall through - result is already set to NOT_FOUND
0912: }
0913: return result;
0914: }
0915:
0916: /**
0917: * Returns the {@link BoxObject} for the specific element.
0918: *
0919: * @param element target for BoxObject.
0920: * @return the BoxObject
0921: */
0922: public BoxObject jsxFunction_getBoxObjectFor(
0923: final HTMLElement element) {
0924: return element.getBoxObject();
0925: }
0926:
0927: /**
0928: * Return the element with the specified id or null if that element could
0929: * not be found
0930: * @param id The ID to search for
0931: * @return the element or null
0932: */
0933: public Object jsxFunction_getElementById(final String id) {
0934: Object result = null;
0935: try {
0936: final HtmlElement htmlElement = ((HtmlPage) getDomNodeOrDie())
0937: .getDocumentHtmlElement().getHtmlElementById(id);
0938: final Object jsElement = getScriptableFor(htmlElement);
0939:
0940: if (jsElement == NOT_FOUND) {
0941: getLog()
0942: .debug(
0943: "getElementById("
0944: + id
0945: + ") cannot return a result as there isn't a javascript object for the html element "
0946: + htmlElement.getClass()
0947: .getName());
0948: } else {
0949: result = jsElement;
0950: }
0951: } catch (final ElementNotFoundException e) {
0952: // Just fall through - result is already set to null
0953:
0954: final BrowserVersion browser = getHtmlPage().getWebClient()
0955: .getBrowserVersion();
0956: if (browser.isIE()) {
0957: final HTMLCollection elements = (HTMLCollection) jsxFunction_getElementsByName(id);
0958: result = elements.get(0, elements);
0959: if (result instanceof UniqueTag) {
0960: return null;
0961: }
0962: getLog()
0963: .warn(
0964: "getElementById("
0965: + id
0966: + ") did a getElementByName for Internet Explorer");
0967: return result;
0968: }
0969: getLog().debug(
0970: "getElementById(" + id
0971: + "): no DOM node found with this id");
0972: }
0973: return result;
0974: }
0975:
0976: /**
0977: * Returns all the descendant elements with the specified tag name.
0978: * @param tagName the name to search for
0979: * @return all the descendant elements with the specified tag name
0980: */
0981: public Object jsxFunction_getElementsByTagName(final String tagName) {
0982: final HTMLCollection collection = new HTMLCollection(this );
0983: try {
0984: final String xpath = "//" + tagName.toLowerCase();
0985: collection
0986: .init(getDomNodeOrDie(), new HtmlUnitXPath(xpath));
0987: } catch (final JaxenException e) {
0988: final String msg = "Error initializing collection getElementsByTagName("
0989: + tagName + "): ";
0990: throw Context.reportRuntimeError(msg + e.getMessage());
0991: }
0992: return collection;
0993: }
0994:
0995: /**
0996: * Returns all HTML elements that have a "name" attribute with the given value.
0997: *
0998: * Refer to <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-71555259">
0999: * The DOM spec</a> for details.
1000: *
1001: * @param elementName - value of the "name" attribute to look for
1002: * @return NodeList of elements
1003: */
1004: public Object jsxFunction_getElementsByName(final String elementName) {
1005: final HTMLCollection collection = new HTMLCollection(this );
1006: final String exp = "//*[@name='" + elementName + "']";
1007: try {
1008: final HtmlUnitXPath xpath = new HtmlUnitXPath(exp);
1009: collection.init(getDomNodeOrDie(), xpath);
1010: } catch (final JaxenException e) {
1011: throw Context
1012: .reportRuntimeError("Failed to initialize collection document.getElementsByName: "
1013: + e.getMessage());
1014: }
1015: return collection;
1016: }
1017:
1018: /**
1019: * Calls to <tt>document.XYZ</tt> should first look at elements named <tt>XYZ</tt> before
1020: * using standard functions.
1021: *
1022: * {@inheritDoc}
1023: */
1024: protected Object getWithPreemption(final String name) {
1025: final HtmlPage page = (HtmlPage) getDomNodeOrNull();
1026: if (page == null) {
1027: return NOT_FOUND;
1028: }
1029: // Try to satisfy this request using a map-backed operation before punting and using XPath.
1030: // XPath operations are very expensive, and this method gets invoked quite a bit.
1031: // This little shortcut shaves ~35% off the build time (3 min -> 2 min, as of 8/10/2007).
1032: final List elements = page.getHtmlElementsByName(name);
1033: if (elements.isEmpty()) {
1034: return NOT_FOUND;
1035: }
1036: if (elements.size() == 1) {
1037: final HtmlElement element = (HtmlElement) elements.get(0);
1038: final String tagName = element.getTagName();
1039: if (HtmlImage.TAG_NAME.equals(tagName)
1040: || HtmlForm.TAG_NAME.equals(tagName)) {
1041: return getScriptableFor(element);
1042: } else {
1043: return NOT_FOUND;
1044: }
1045: }
1046: // The shortcut wasn't enough, which means we probably need to perform the XPath operation anyway.
1047: // Note that the XPath expression below HAS TO MATCH the tag name checks performed in the shortcut above.
1048: // TODO: Behavior for iframe seems to differ between IE and Moz.
1049: final HTMLCollection collection = new HTMLCollection(this );
1050: final String xpath = "//*[(@name = '" + name
1051: + "' and (name() = 'img' or name() = 'form'))]";
1052: try {
1053: collection.init(page, new HtmlUnitXPath(xpath));
1054: } catch (final JaxenException e) {
1055: final String msg = "Failed to initialize collection (using xpath "
1056: + xpath + "): " + e.getMessage();
1057: throw Context.reportRuntimeError(msg);
1058: }
1059: final int size = collection.jsxGet_length();
1060: if (size == 1) {
1061: return collection.get(0, collection);
1062: } else if (size > 1) {
1063: return collection;
1064: }
1065: return NOT_FOUND;
1066: }
1067:
1068: /**
1069: * Returns this document's <tt>body</tt> element.
1070: * @return this document's <tt>body</tt> element
1071: */
1072: public Object jsxGet_body() {
1073: final List tagNames = Arrays.asList(new String[] { "body",
1074: "frameset" });
1075: final List list = getHtmlPage().getDocumentHtmlElement()
1076: .getHtmlElementsByTagNames(tagNames);
1077: if (list.isEmpty()) {
1078: return NOT_FOUND;
1079: } else {
1080: final DomNode bodyElement = (DomNode) list.get(0);
1081: return getScriptableFor(bodyElement);
1082: }
1083: }
1084:
1085: /**
1086: * Returns this document's title.
1087: * @return this document's title
1088: */
1089: public String jsxGet_title() {
1090: return getHtmlPage().getTitleText();
1091: }
1092:
1093: /**
1094: * Sets this document's title.
1095: * @param title the new title
1096: */
1097: public void jsxSet_title(final String title) {
1098: getHtmlPage().setTitleText(title);
1099: }
1100:
1101: /**
1102: * Returns the ready state of the document. This is an IE-only property.
1103: * @return The ready state of the document.
1104: * @see DomNode#READY_STATE_UNINITIALIZED
1105: * @see DomNode#READY_STATE_LOADING
1106: * @see DomNode#READY_STATE_LOADED
1107: * @see DomNode#READY_STATE_INTERACTIVE
1108: * @see DomNode#READY_STATE_COMPLETE
1109: */
1110: public String jsxGet_readyState() {
1111: final DomNode node = getDomNodeOrDie();
1112: if (node instanceof HtmlPage) {
1113: return ((HtmlPage) node).getDocumentHtmlElement()
1114: .getReadyState();
1115: } else {
1116: return node.getReadyState();
1117: }
1118: }
1119:
1120: /**
1121: * The domain name of the server that served the document,
1122: * or null if the server cannot be identified by a domain name.
1123: * @return domain name
1124: * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-2250147">
1125: * W3C documentation</a>
1126: */
1127: public String jsxGet_domain() {
1128: if (domain_ == null) {
1129: domain_ = getHtmlPage().getWebResponse().getUrl().getHost();
1130: final BrowserVersion browser = getHtmlPage().getWebClient()
1131: .getBrowserVersion();
1132: if (browser.isNetscape()) {
1133: domain_ = domain_.toLowerCase();
1134: }
1135: }
1136:
1137: return domain_;
1138: }
1139:
1140: /**
1141: * Set the the domain of this document.
1142: *
1143: * Domains can only be set to suffixes of the existing domain
1144: * with the exception of setting the domain to itself.
1145: * <p>
1146: * The domain will be set according to the following rules:
1147: * <ol>
1148: * <li>If the newDomain.equalsIgnoreCase(currentDomain) the method returns with no error.</li>
1149: * <li>If the browser version is netscape, the newDomain is downshifted.</li>
1150: * <li>The change will take place if and only if the suffixes of the
1151: * current domain and the new domain match AND there are at least
1152: * two domain qualifiers e.g. the following transformations are legal
1153: * d1.d2.d3.gargoylesoftware.com may be transformed to itself or:
1154: * d2.d3.gargoylesoftware.com
1155: * d3.gargoylesoftware.com
1156: * gargoylesoftware.com
1157: *
1158: * transformation to: com
1159: * will fail
1160: * </li>
1161: * </ol>
1162: * </p>
1163: * TODO This code could be modified to understand country domain suffixes.
1164: * The domain www.bbc.co.uk should be trimmable only down to bbc.co.uk
1165: * trimming to co.uk should not be possible.
1166: * @param newDomain the new domain to set
1167: */
1168: public void jsxSet_domain(final String newDomain) {
1169: final String currentDomain = jsxGet_domain();
1170: if (currentDomain.equalsIgnoreCase(newDomain)) {
1171: return;
1172: }
1173:
1174: if (newDomain.indexOf(".") == -1
1175: || !currentDomain.toLowerCase().endsWith(
1176: "." + newDomain.toLowerCase())) {
1177: throw Context
1178: .reportRuntimeError("Illegal domain value, can not set domain from: \""
1179: + currentDomain
1180: + "\" to: \""
1181: + newDomain
1182: + "\"");
1183: }
1184:
1185: // Netscape down shifts the case of the domain
1186: if (getHtmlPage().getWebClient().getBrowserVersion()
1187: .isNetscape()) {
1188: domain_ = newDomain.toLowerCase();
1189: } else {
1190: domain_ = newDomain;
1191: }
1192: }
1193:
1194: /**
1195: * Return the value of the javascript attribute "scripts".
1196: * @return The value of this attribute.
1197: */
1198: public Object jsxGet_scripts() {
1199: if (scripts_ == null) {
1200: scripts_ = new HTMLCollection(this );
1201: try {
1202: scripts_.init(getDomNodeOrDie(), new HtmlUnitXPath(
1203: "//script"));
1204: } catch (final JaxenException e) {
1205: throw Context
1206: .reportRuntimeError("Failed to initialize collection document.scripts: "
1207: + e.getMessage());
1208: }
1209: }
1210: return scripts_;
1211: }
1212:
1213: /**
1214: * Return the value of the frames property.
1215: * @see <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/collections/frames.asp">
1216: * MSDN documentation</a>
1217: * @return The live collection of frames
1218: */
1219: public Object jsxGet_frames() {
1220: return getWindow().jsxGet_frames();
1221: }
1222:
1223: /**
1224: * Returns the implementation object of the current document.
1225: * @return implementation-specific object.
1226: */
1227: public DOMImplementation jsxGet_implementation() {
1228: if (implementation_ == null) {
1229: implementation_ = new DOMImplementation();
1230: implementation_.setParentScope(getWindow());
1231: implementation_.setPrototype(getPrototype(implementation_
1232: .getClass()));
1233: }
1234: return implementation_;
1235: }
1236:
1237: /**
1238: * Retrieves a collection of styleSheet objects representing the style sheets that correspond
1239: * to each instance of a Link or {@link Style} object in the document.
1240: *
1241: * @return styleSheet collection.
1242: */
1243: public Object jsxGet_styleSheets() {
1244: if (styleSheets_ == null) {
1245: styleSheets_ = new StyleSheetList(this );
1246: }
1247: return styleSheets_;
1248: }
1249:
1250: /**
1251: * Implementation of the {@link DocumentEvent} interface's
1252: * {@link org.w3c.dom.events.DocumentEvent#createEvent(String)} method. The method creates an
1253: * event of the specified type.
1254: *
1255: * @link http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-DocumentEvent
1256: * @param eventType The event type to create.
1257: * @return The associated event object for that type. The event object will NOT have had its
1258: * initialization method called. It is up to the caller of the method to initialize the
1259: * event.
1260: * @throws DOMException Thrown if the event type is not supported. The DOMException will have a
1261: * type of DOMException.NOT_SUPPORTED_ERR
1262: */
1263: public Event jsxFunction_createEvent(final String eventType)
1264: throws DOMException {
1265: final Class clazz = (Class) SUPPORTED_EVENT_TYPE_MAP
1266: .get(eventType);
1267: if (clazz == null) {
1268: throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1269: "Event Type is not supported: " + eventType);
1270: }
1271: try {
1272: final Event event = (Event) clazz.newInstance();
1273: event.setEventType(eventType);
1274: event.setParentScope(getWindow());
1275: event.setPrototype(getPrototype(clazz));
1276: return event;
1277: } catch (final InstantiationException e) {
1278: throw Context
1279: .reportRuntimeError("Failed to instantiate event: class ='"
1280: + clazz.getName()
1281: + "' for event type of '"
1282: + eventType + "': " + e.getMessage());
1283: } catch (final IllegalAccessException e) {
1284: throw Context
1285: .reportRuntimeError("Failed to instantiate event: class ='"
1286: + clazz.getName()
1287: + "' for event type of '"
1288: + eventType + "': " + e.getMessage());
1289: }
1290: }
1291:
1292: /**
1293: * Implementation of the <tt>createEventObject</tt> method supported by Internet Explorer.
1294: *
1295: * @link http://msdn2.microsoft.com/en-us/library/ms536390.aspx
1296: * @return An instance of the event object. The event object will NOT have its
1297: * member variables initialized. It is up to the caller of the method to initialize
1298: * the properties of the event.
1299: */
1300: public Event jsxFunction_createEventObject() {
1301: final Event event = new Event();
1302: event.setParentScope(getWindow());
1303: event.setPrototype(getPrototype(event.getClass()));
1304: return event;
1305: }
1306:
1307: /**
1308: * Returns the element for the specified x coordinate and the specified y coordinate.
1309: * The current implementation returns the <body> element.
1310: *
1311: * @param x Specifies the X-offset, in pixels.
1312: * @param y Specifies the Y-offset, in pixels.
1313: *
1314: * @return the element for the specified x coordinate and the specified y coordinate.
1315: */
1316: public Object jsxFunction_elementFromPoint(final int x, final int y) {
1317: return jsxGet_body();
1318: }
1319:
1320: /**
1321: * Create a new range.
1322: * @return the range.
1323: * @see <a href="http://www.xulplanet.com/references/objref/HTMLDocument.html#method_createRange">
1324: * XUL Planet</a>
1325: */
1326: public Object jsxFunction_createRange() {
1327: final Range r = new Range();
1328: r.setParentScope(getWindow());
1329: r.setPrototype(getPrototype(Range.class));
1330: return r;
1331: }
1332:
1333: /**
1334: * Adapts any DOM node to resolve namespaces so that an XPath expression
1335: * can be easily evaluated relative to the context of the node where it appeared within the document.
1336: * @param nodeResolver The node to be used as a context for namespace resolution.
1337: * @return XPathNSResolver which resolves namespaces with respect to the definitions in scope for a specified node.
1338: */
1339: public XPathNSResolver jsxFunction_createNSResolver(
1340: final Node nodeResolver) {
1341: final XPathNSResolver resolver = new XPathNSResolver();
1342: resolver.setElement(nodeResolver);
1343: resolver.setParentScope(getWindow());
1344: resolver.setPrototype(getPrototype(resolver.getClass()));
1345: return resolver;
1346: }
1347:
1348: /**
1349: * Evaluates an XPath expression string and returns a result of the specified type if possible.
1350: * @param expression The XPath expression string to be parsed and evaluated.
1351: * @param contextNode The context node for the evaluation of this XPath expression.
1352: * @param resolver The resolver permits translation of all prefixes, including the xml namespace prefix,
1353: * within the XPath expression into appropriate namespace URIs.
1354: * @param type If a specific type is specified, then the result will be returned as the corresponding type.
1355: * @param result The result object which may be reused and returned by this method.
1356: * @return The result of the evaluation of the XPath expression.
1357: */
1358: public XPathResult jsxFunction_evaluate(final String expression,
1359: final Node contextNode, final Object resolver,
1360: final int type, final Object result) {
1361: XPathResult xPathResult = (XPathResult) result;
1362:
1363: try {
1364: if (xPathResult == null) {
1365: xPathResult = new XPathResult();
1366: xPathResult.setPrototype(getPrototype(xPathResult
1367: .getClass()));
1368: }
1369: xPathResult.init(contextNode.getDomNodeOrDie().getByXPath(
1370: expression), type);
1371: } catch (final JaxenException e) {
1372: throw Context.reportRuntimeError("Error using exression: "
1373: + expression);
1374: }
1375: return xPathResult;
1376: }
1377: }
|