/* * Copyright (c) 2002-2018 Gargoyle Software Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gargoylesoftware.htmlunit.javascript.host.dom; import com.gargoylesoftware.htmlunit.html.DomAttr; import com.gargoylesoftware.htmlunit.html.DomNode; import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter; import net.sourceforge.htmlunit.corejs.javascript.Context; import java.util.List; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF; /** * A JavaScript object for {@code XPathResult}. * * @author Ahmed Ashour * @author Chuck Dumont * @author Ronald Brill */ @JsxClass({CHROME, FF, EDGE}) public class XPathResult extends SimpleScriptable { /** * This code does not represent a specific type. * An evaluation of an XPath expression will never produce this type. If this type is requested, * then the evaluation returns whatever type naturally results from evaluation of the expression. */ @JsxConstant public static final int ANY_TYPE = 0; /** * The result is a number. */ @JsxConstant public static final int NUMBER_TYPE = 1; /** * The result is a string. */ @JsxConstant public static final int STRING_TYPE = 2; /** * The result is a boolean. */ @JsxConstant public static final int BOOLEAN_TYPE = 3; /** * The result is a node set that will be accessed iteratively, which may not produce nodes in a particular order. * This is the default type returned if the result is a node set and {@link #ANY_TYPE} is requested. */ @JsxConstant public static final int UNORDERED_NODE_ITERATOR_TYPE = 4; /** * The result is a node set that will be accessed iteratively, which will produce document-ordered nodes. */ @JsxConstant public static final int ORDERED_NODE_ITERATOR_TYPE = 5; /** * The result is a node set that will be accessed as a snapshot list of nodes * that may not be in a particular order. */ @JsxConstant public static final int UNORDERED_NODE_SNAPSHOT_TYPE = 6; /** * The result is a node set that will be accessed as a snapshot list of nodes * that will be in original document order. */ @JsxConstant public static final int ORDERED_NODE_SNAPSHOT_TYPE = 7; /** * The result is a node set and will be accessed as a single node, which may be null if the node set is empty. * If there is more than one node in the actual result, * the single node returned might not be the first in document order. */ @JsxConstant public static final int ANY_UNORDERED_NODE_TYPE = 8; /** * The result is a node set and will be accessed as a single node, which may be null if the node set is empty. * If there are more than one node in the actual result, * the single node returned will be the first in document order. */ @JsxConstant public static final int FIRST_ORDERED_NODE_TYPE = 9; private List<?> result_; private int resultType_; /** * The index of the next result. */ private int iteratorIndex_; /** * Creates an instance. */ @JsxConstructor public XPathResult() { } /** * @param result the evaluation result * @param type If a specific type is specified, then the result will be returned as the corresponding type */ void init(final List<?> result, final int type) { result_ = result; resultType_ = -1; if (result_.size() == 1) { final Object o = result_.get(0); if (o instanceof Number) { resultType_ = NUMBER_TYPE; } else if (o instanceof String) { resultType_ = STRING_TYPE; } else if (o instanceof Boolean) { resultType_ = BOOLEAN_TYPE; } } if (resultType_ == -1) { if (type != ANY_TYPE) { resultType_ = type; } else { resultType_ = UNORDERED_NODE_ITERATOR_TYPE; } } iteratorIndex_ = 0; } /** * The code representing the type of this result, as defined by the type constants. * @return the code representing the type of this result */ @JsxGetter public int getResultType() { return resultType_; } /** * The number of nodes in the result snapshot. * @return the number of nodes in the result snapshot */ @JsxGetter public int getSnapshotLength() { if (resultType_ != UNORDERED_NODE_SNAPSHOT_TYPE && resultType_ != ORDERED_NODE_SNAPSHOT_TYPE) { throw Context.reportRuntimeError("Cannot get snapshotLength for type: " + resultType_); } return result_.size(); } /** * The value of this single node result, which may be null. * @return the value of this single node result, which may be null */ @JsxGetter public Node getSingleNodeValue() { if (resultType_ != ANY_UNORDERED_NODE_TYPE && resultType_ != FIRST_ORDERED_NODE_TYPE) { throw Context.reportRuntimeError("Cannot get singleNodeValue for type: " + resultType_); } if (!result_.isEmpty()) { return (Node) ((DomNode) result_.get(0)).getScriptableObject(); } return null; } /** * Iterates and returns the next node from the node set or {@code null} if there are no more nodes. * @return the next node */ @JsxFunction public Node iterateNext() { if (resultType_ != UNORDERED_NODE_ITERATOR_TYPE && resultType_ != ORDERED_NODE_ITERATOR_TYPE) { throw Context.reportRuntimeError("Cannot get iterateNext for type: " + resultType_); } if (iteratorIndex_ < result_.size()) { return (Node) ((DomNode) result_.get(iteratorIndex_++)).getScriptableObject(); } return null; } /** * Returns the index<sup>th</sup> item in the snapshot collection. * If index is greater than or equal to the number of nodes in the list, this method returns null. * @param index Index into the snapshot collection * @return the node at the index<sup>th</sup> position in the NodeList, or null if that is not a valid index */ @JsxFunction public Node snapshotItem(final int index) { if (resultType_ != UNORDERED_NODE_SNAPSHOT_TYPE && resultType_ != ORDERED_NODE_SNAPSHOT_TYPE) { throw Context.reportRuntimeError("Cannot get snapshotLength for type: " + resultType_); } if (index >= 0 && index < result_.size()) { return (Node) ((DomNode) result_.get(index)).getScriptableObject(); } return null; } /** * Returns the value of this number result. * @return the value of this number result */ @JsxGetter public double getNumberValue() { if (resultType_ != NUMBER_TYPE) { throw Context.reportRuntimeError("Cannot get numberValue for type: " + resultType_); } final String asString = asString(); Double answer; try { answer = Double.parseDouble(asString); } catch (final NumberFormatException e) { answer = Double.NaN; } return answer; } /** * Returns the value of this boolean result. * @return the value of this boolean result */ @JsxGetter public boolean isBooleanValue() { if (resultType_ != BOOLEAN_TYPE) { throw Context.reportRuntimeError("Cannot get booleanValue for type: " + resultType_); } return result_.size() > 0; } /** * Returns the value of this string result. * @return the value of this string result */ @JsxGetter public String getStringValue() { if (resultType_ != STRING_TYPE) { throw Context.reportRuntimeError("Cannot get stringValue for type: " + resultType_); } return asString(); } private String asString() { final Object resultObj = result_.get(0); if (resultObj instanceof DomAttr) { return ((DomAttr) resultObj).getValue(); } if (resultObj instanceof DomNode) { return ((DomNode) resultObj).asText(); } return resultObj.toString(); } }