blob: 7afd269c53d0997f6a4d4006cbff59e44e9f46cb [file] [log] [blame]
/*
* Copyright (C) 2005 Frerich Raabe <raabe@kde.org>
* Copyright (C) 2006, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/xml/XPathStep.h"
#include "core/dom/Attr.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/NodeTraversal.h"
#include "core/xml/XPathParser.h"
#include "core/xml/XPathUtil.h"
#include "core/xmlns_names.h"
namespace blink {
namespace XPath {
Step::Step(Axis axis, const NodeTest& node_test)
: axis_(axis), node_test_(new NodeTest(node_test)) {}
Step::Step(Axis axis,
const NodeTest& node_test,
HeapVector<Member<Predicate>>& predicates)
: axis_(axis), node_test_(new NodeTest(node_test)) {
predicates_.swap(predicates);
}
Step::~Step() = default;
void Step::Trace(blink::Visitor* visitor) {
visitor->Trace(node_test_);
visitor->Trace(predicates_);
ParseNode::Trace(visitor);
}
void Step::Optimize() {
// Evaluate predicates as part of node test if possible to avoid building
// unnecessary NodeSets.
// E.g., there is no need to build a set of all "foo" nodes to evaluate
// "foo[@bar]", we can check the predicate while enumerating.
// This optimization can be applied to predicates that are not context node
// list sensitive, or to first predicate that is only context position
// sensitive, e.g. foo[position() mod 2 = 0].
HeapVector<Member<Predicate>> remaining_predicates;
for (const auto& predicate : predicates_) {
if ((!predicate->IsContextPositionSensitive() ||
GetNodeTest().MergedPredicates().IsEmpty()) &&
!predicate->IsContextSizeSensitive() &&
remaining_predicates.IsEmpty()) {
GetNodeTest().MergedPredicates().push_back(predicate);
} else {
remaining_predicates.push_back(predicate);
}
}
swap(remaining_predicates, predicates_);
}
bool OptimizeStepPair(Step* first, Step* second) {
if (first->axis_ == Step::kDescendantOrSelfAxis &&
first->GetNodeTest().GetKind() == Step::NodeTest::kAnyNodeTest &&
!first->predicates_.size() &&
!first->GetNodeTest().MergedPredicates().size()) {
DCHECK(first->GetNodeTest().Data().IsEmpty());
DCHECK(first->GetNodeTest().NamespaceURI().IsEmpty());
// Optimize the common case of "//" AKA
// /descendant-or-self::node()/child::NodeTest to /descendant::NodeTest.
if (second->axis_ == Step::kChildAxis &&
second->PredicatesAreContextListInsensitive()) {
first->axis_ = Step::kDescendantAxis;
first->GetNodeTest() = Step::NodeTest(
second->GetNodeTest().GetKind(), second->GetNodeTest().Data(),
second->GetNodeTest().NamespaceURI());
swap(second->GetNodeTest().MergedPredicates(),
first->GetNodeTest().MergedPredicates());
swap(second->predicates_, first->predicates_);
first->Optimize();
return true;
}
}
return false;
}
bool Step::PredicatesAreContextListInsensitive() const {
for (const auto& predicate : predicates_) {
if (predicate->IsContextPositionSensitive() ||
predicate->IsContextSizeSensitive())
return false;
}
for (const auto& predicate : GetNodeTest().MergedPredicates()) {
if (predicate->IsContextPositionSensitive() ||
predicate->IsContextSizeSensitive())
return false;
}
return true;
}
void Step::Evaluate(EvaluationContext& evaluation_context,
Node* context,
NodeSet& nodes) const {
evaluation_context.position = 0;
NodesInAxis(evaluation_context, context, nodes);
// Check predicates that couldn't be merged into node test.
for (const auto& predicate : predicates_) {
NodeSet* new_nodes = NodeSet::Create();
if (!nodes.IsSorted())
new_nodes->MarkSorted(false);
for (unsigned j = 0; j < nodes.size(); j++) {
Node* node = nodes[j];
evaluation_context.node = node;
evaluation_context.size = nodes.size();
evaluation_context.position = j + 1;
if (predicate->Evaluate(evaluation_context))
new_nodes->Append(node);
}
nodes.Swap(*new_nodes);
}
}
#if DCHECK_IS_ON()
static inline Node::NodeType PrimaryNodeType(Step::Axis axis) {
switch (axis) {
case Step::kAttributeAxis:
return Node::kAttributeNode;
default:
return Node::kElementNode;
}
}
#endif
// Evaluate NodeTest without considering merged predicates.
static inline bool NodeMatchesBasicTest(Node* node,
Step::Axis axis,
const Step::NodeTest& node_test) {
switch (node_test.GetKind()) {
case Step::NodeTest::kTextNodeTest: {
Node::NodeType type = node->getNodeType();
return type == Node::kTextNode || type == Node::kCdataSectionNode;
}
case Step::NodeTest::kCommentNodeTest:
return node->getNodeType() == Node::kCommentNode;
case Step::NodeTest::kProcessingInstructionNodeTest: {
const AtomicString& name = node_test.Data();
return node->getNodeType() == Node::kProcessingInstructionNode &&
(name.IsEmpty() || node->nodeName() == name);
}
case Step::NodeTest::kAnyNodeTest:
return true;
case Step::NodeTest::kNameTest: {
const AtomicString& name = node_test.Data();
const AtomicString& namespace_uri = node_test.NamespaceURI();
if (axis == Step::kAttributeAxis) {
Attr* attr = ToAttr(node);
// In XPath land, namespace nodes are not accessible on the
// attribute axis.
if (attr->namespaceURI() == XMLNSNames::xmlnsNamespaceURI)
return false;
if (name == g_star_atom)
return namespace_uri.IsEmpty() ||
attr->namespaceURI() == namespace_uri;
return attr->localName() == name &&
attr->namespaceURI() == namespace_uri;
}
// Node test on the namespace axis is not implemented yet, the caller
// has a check for it.
DCHECK_NE(Step::kNamespaceAxis, axis);
// For other axes, the principal node type is element.
#if DCHECK_IS_ON()
DCHECK_EQ(Node::kElementNode, PrimaryNodeType(axis));
#endif
if (!node->IsElementNode())
return false;
Element& element = ToElement(*node);
if (name == g_star_atom)
return namespace_uri.IsEmpty() ||
namespace_uri == element.namespaceURI();
if (element.GetDocument().IsHTMLDocument()) {
if (element.IsHTMLElement()) {
// Paths without namespaces should match HTML elements in HTML
// documents despite those having an XHTML namespace. Names are
// compared case-insensitively.
return DeprecatedEqualIgnoringCase(element.localName(), name) &&
(namespace_uri.IsNull() ||
namespace_uri == element.namespaceURI());
}
// An expression without any prefix shouldn't match no-namespace
// nodes (because HTML5 says so).
return element.HasLocalName(name) &&
namespace_uri == element.namespaceURI() &&
!namespace_uri.IsNull();
}
return element.HasLocalName(name) &&
namespace_uri == element.namespaceURI();
}
}
NOTREACHED();
return false;
}
static inline bool NodeMatches(EvaluationContext& evaluation_context,
Node* node,
Step::Axis axis,
const Step::NodeTest& node_test) {
if (!NodeMatchesBasicTest(node, axis, node_test))
return false;
// Only the first merged predicate may depend on position.
++evaluation_context.position;
for (const auto& predicate : node_test.MergedPredicates()) {
evaluation_context.node = node;
// No need to set context size - we only get here when evaluating
// predicates that do not depend on it.
if (!predicate->Evaluate(evaluation_context))
return false;
}
return true;
}
// Result nodes are ordered in axis order. Node test (including merged
// predicates) is applied.
void Step::NodesInAxis(EvaluationContext& evaluation_context,
Node* context,
NodeSet& nodes) const {
DCHECK(nodes.IsEmpty());
switch (axis_) {
case kChildAxis:
// In XPath model, attribute nodes do not have children.
if (context->IsAttributeNode())
return;
for (Node* n = context->firstChild(); n; n = n->nextSibling()) {
if (NodeMatches(evaluation_context, n, kChildAxis, GetNodeTest()))
nodes.Append(n);
}
return;
case kDescendantAxis:
// In XPath model, attribute nodes do not have children.
if (context->IsAttributeNode())
return;
for (Node& n : NodeTraversal::DescendantsOf(*context)) {
if (NodeMatches(evaluation_context, &n, kDescendantAxis, GetNodeTest()))
nodes.Append(&n);
}
return;
case kParentAxis:
if (context->IsAttributeNode()) {
Element* n = ToAttr(context)->ownerElement();
if (NodeMatches(evaluation_context, n, kParentAxis, GetNodeTest()))
nodes.Append(n);
} else {
ContainerNode* n = context->parentNode();
if (n && NodeMatches(evaluation_context, n, kParentAxis, GetNodeTest()))
nodes.Append(n);
}
return;
case kAncestorAxis: {
Node* n = context;
if (context->IsAttributeNode()) {
n = ToAttr(context)->ownerElement();
if (NodeMatches(evaluation_context, n, kAncestorAxis, GetNodeTest()))
nodes.Append(n);
}
for (n = n->parentNode(); n; n = n->parentNode()) {
if (NodeMatches(evaluation_context, n, kAncestorAxis, GetNodeTest()))
nodes.Append(n);
}
nodes.MarkSorted(false);
return;
}
case kFollowingSiblingAxis:
if (context->getNodeType() == Node::kAttributeNode)
return;
for (Node* n = context->nextSibling(); n; n = n->nextSibling()) {
if (NodeMatches(evaluation_context, n, kFollowingSiblingAxis,
GetNodeTest()))
nodes.Append(n);
}
return;
case kPrecedingSiblingAxis:
if (context->getNodeType() == Node::kAttributeNode)
return;
for (Node* n = context->previousSibling(); n; n = n->previousSibling()) {
if (NodeMatches(evaluation_context, n, kPrecedingSiblingAxis,
GetNodeTest()))
nodes.Append(n);
}
nodes.MarkSorted(false);
return;
case kFollowingAxis:
if (context->IsAttributeNode()) {
for (Node& p :
NodeTraversal::StartsAfter(*ToAttr(context)->ownerElement())) {
if (NodeMatches(evaluation_context, &p, kFollowingAxis,
GetNodeTest()))
nodes.Append(&p);
}
} else {
for (Node* p = context; !IsRootDomNode(p); p = p->parentNode()) {
for (Node* n = p->nextSibling(); n; n = n->nextSibling()) {
if (NodeMatches(evaluation_context, n, kFollowingAxis,
GetNodeTest()))
nodes.Append(n);
for (Node& c : NodeTraversal::DescendantsOf(*n)) {
if (NodeMatches(evaluation_context, &c, kFollowingAxis,
GetNodeTest()))
nodes.Append(&c);
}
}
}
}
return;
case kPrecedingAxis: {
if (context->IsAttributeNode())
context = ToAttr(context)->ownerElement();
Node* n = context;
while (ContainerNode* parent = n->parentNode()) {
for (n = NodeTraversal::Previous(*n); n != parent;
n = NodeTraversal::Previous(*n)) {
if (NodeMatches(evaluation_context, n, kPrecedingAxis, GetNodeTest()))
nodes.Append(n);
}
n = parent;
}
nodes.MarkSorted(false);
return;
}
case kAttributeAxis: {
if (!context->IsElementNode())
return;
Element* context_element = ToElement(context);
// Avoid lazily creating attribute nodes for attributes that we do not
// need anyway.
if (GetNodeTest().GetKind() == NodeTest::kNameTest &&
GetNodeTest().Data() != g_star_atom) {
Attr* attr = context_element->getAttributeNodeNS(
GetNodeTest().NamespaceURI(), GetNodeTest().Data());
// In XPath land, namespace nodes are not accessible on the attribute
// axis.
if (attr && attr->namespaceURI() != XMLNSNames::xmlnsNamespaceURI) {
// Still need to check merged predicates.
if (NodeMatches(evaluation_context, attr, kAttributeAxis,
GetNodeTest()))
nodes.Append(attr);
}
return;
}
AttributeCollection attributes = context_element->Attributes();
for (auto& attribute : attributes) {
Attr* attr = context_element->EnsureAttr(attribute.GetName());
if (NodeMatches(evaluation_context, attr, kAttributeAxis,
GetNodeTest()))
nodes.Append(attr);
}
return;
}
case kNamespaceAxis:
// XPath namespace nodes are not implemented.
return;
case kSelfAxis:
if (NodeMatches(evaluation_context, context, kSelfAxis, GetNodeTest()))
nodes.Append(context);
return;
case kDescendantOrSelfAxis:
if (NodeMatches(evaluation_context, context, kDescendantOrSelfAxis,
GetNodeTest()))
nodes.Append(context);
// In XPath model, attribute nodes do not have children.
if (context->IsAttributeNode())
return;
for (Node& n : NodeTraversal::DescendantsOf(*context)) {
if (NodeMatches(evaluation_context, &n, kDescendantOrSelfAxis,
GetNodeTest()))
nodes.Append(&n);
}
return;
case kAncestorOrSelfAxis: {
if (NodeMatches(evaluation_context, context, kAncestorOrSelfAxis,
GetNodeTest()))
nodes.Append(context);
Node* n = context;
if (context->IsAttributeNode()) {
n = ToAttr(context)->ownerElement();
if (NodeMatches(evaluation_context, n, kAncestorOrSelfAxis,
GetNodeTest()))
nodes.Append(n);
}
for (n = n->parentNode(); n; n = n->parentNode()) {
if (NodeMatches(evaluation_context, n, kAncestorOrSelfAxis,
GetNodeTest()))
nodes.Append(n);
}
nodes.MarkSorted(false);
return;
}
}
NOTREACHED();
}
} // namespace XPath
} // namespace blink