blob: 36a15f3bfb361d5fa78689e33cca9d7d285e587e [file] [log] [blame]
/*
* Copyright (C) 2005 Frerich Raabe <raabe@kde.org>
* Copyright (C) 2006-2025 Apple Inc. All rights reserved.
* Copyright (C) 2019 Google 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 "config.h"
#include "XPathFunctions.h"
#include "Attribute.h"
#include "ElementInlines.h"
#include "ProcessingInstruction.h"
#include "TreeScope.h"
#include "XMLNames.h"
#include "XPathUtil.h"
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RobinHoodHashMap.h>
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore::XPath {
WTF_MAKE_TZONE_ALLOCATED_IMPL(Function);
static inline bool isWhitespace(char16_t c)
{
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
}
#define DEFINE_FUNCTION_CREATOR(Suffix) static std::unique_ptr<Function> createFunction##Suffix() { return makeUnique<Fun##Suffix>(); }
class Interval {
public:
static const int Inf = -1;
Interval();
Interval(int value);
Interval(int min, int max);
bool contains(int value) const;
private:
int m_min;
int m_max;
};
class FunLast final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLast);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
public:
FunLast() { setIsContextSizeSensitive(true); }
};
class FunPosition final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunPosition);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
public:
FunPosition() { setIsContextPositionSensitive(true); }
};
class FunCount final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunCount);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
};
class FunId final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunId);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::NodeSet; }
};
class FunLocalName final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLocalName);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
public:
FunLocalName() { setIsContextNodeSensitive(true); } // local-name() with no arguments uses context node.
};
class FunNamespaceURI final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNamespaceURI);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
public:
FunNamespaceURI() { setIsContextNodeSensitive(true); } // namespace-uri() with no arguments uses context node.
};
class FunName final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunName);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
public:
FunName() { setIsContextNodeSensitive(true); } // name() with no arguments uses context node.
};
class FunString final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunString);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
public:
FunString() { setIsContextNodeSensitive(true); } // string() with no arguments uses context node.
};
class FunConcat final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunConcat);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
};
class FunStartsWith final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunStartsWith);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunContains final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunContains);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunSubstringBefore final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstringBefore);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
};
class FunSubstringAfter final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstringAfter);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
};
class FunSubstring final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstring);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
};
class FunStringLength final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunStringLength);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
public:
FunStringLength() { setIsContextNodeSensitive(true); } // string-length() with no arguments uses context node.
};
class FunNormalizeSpace final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNormalizeSpace);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
public:
FunNormalizeSpace() { setIsContextNodeSensitive(true); } // normalize-space() with no arguments uses context node.
};
class FunTranslate final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunTranslate);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::String; }
};
class FunBoolean final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunBoolean);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunNot : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNot);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunTrue final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunTrue);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunFalse final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunFalse);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
};
class FunLang final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLang);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Boolean; }
public:
FunLang() { setIsContextNodeSensitive(true); } // lang() always works on context node.
};
class FunNumber final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNumber);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
public:
FunNumber() { setIsContextNodeSensitive(true); } // number() with no arguments uses context node.
};
class FunSum final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSum);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
};
class FunFloor final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunFloor);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
};
class FunCeiling final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunCeiling);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
};
class FunRound final : public Function {
WTF_MAKE_TZONE_ALLOCATED_INLINE(FunRound);
Value evaluate() const override;
Value::Type resultType() const override { return Value::Type::Number; }
public:
static double round(double);
};
DEFINE_FUNCTION_CREATOR(Last)
DEFINE_FUNCTION_CREATOR(Position)
DEFINE_FUNCTION_CREATOR(Count)
DEFINE_FUNCTION_CREATOR(Id)
DEFINE_FUNCTION_CREATOR(LocalName)
DEFINE_FUNCTION_CREATOR(NamespaceURI)
DEFINE_FUNCTION_CREATOR(Name)
DEFINE_FUNCTION_CREATOR(String)
DEFINE_FUNCTION_CREATOR(Concat)
DEFINE_FUNCTION_CREATOR(StartsWith)
DEFINE_FUNCTION_CREATOR(Contains)
DEFINE_FUNCTION_CREATOR(SubstringBefore)
DEFINE_FUNCTION_CREATOR(SubstringAfter)
DEFINE_FUNCTION_CREATOR(Substring)
DEFINE_FUNCTION_CREATOR(StringLength)
DEFINE_FUNCTION_CREATOR(NormalizeSpace)
DEFINE_FUNCTION_CREATOR(Translate)
DEFINE_FUNCTION_CREATOR(Boolean)
DEFINE_FUNCTION_CREATOR(Not)
DEFINE_FUNCTION_CREATOR(True)
DEFINE_FUNCTION_CREATOR(False)
DEFINE_FUNCTION_CREATOR(Lang)
DEFINE_FUNCTION_CREATOR(Number)
DEFINE_FUNCTION_CREATOR(Sum)
DEFINE_FUNCTION_CREATOR(Floor)
DEFINE_FUNCTION_CREATOR(Ceiling)
DEFINE_FUNCTION_CREATOR(Round)
#undef DEFINE_FUNCTION_CREATOR
inline Interval::Interval()
: m_min(Inf), m_max(Inf)
{
}
inline Interval::Interval(int value)
: m_min(value), m_max(value)
{
}
inline Interval::Interval(int min, int max)
: m_min(min), m_max(max)
{
}
inline bool Interval::contains(int value) const
{
if (m_min == Inf && m_max == Inf)
return true;
if (m_min == Inf)
return value <= m_max;
if (m_max == Inf)
return value >= m_min;
return value >= m_min && value <= m_max;
}
void Function::setArguments(const String& name, Vector<std::unique_ptr<Expression>> arguments)
{
ASSERT(!subexpressionCount());
// Functions that use the context node as an implicit argument are context node sensitive when they
// have no arguments, but when explicit arguments are added, they are no longer context node sensitive.
// As of this writing, the only exception to this is the "lang" function.
if (name != "lang"_s && !arguments.isEmpty())
setIsContextNodeSensitive(false);
setSubexpressions(WTF::move(arguments));
}
Value FunLast::evaluate() const
{
return Expression::evaluationContext().size;
}
Value FunPosition::evaluate() const
{
return Expression::evaluationContext().position;
}
Value FunId::evaluate() const
{
Value a = argument(0).evaluate();
StringBuilder idList; // A whitespace-separated list of IDs
if (!a.isNodeSet())
idList.append(a.toString());
else {
for (auto& node : a.toNodeSet()) {
idList.append(stringValue(node.get()));
idList.append(' ');
}
}
Ref contextScope = evaluationContext().node->treeScope();
NodeSet result;
HashSet<Ref<Node>> resultSet;
unsigned startPos = 0;
unsigned length = idList.length();
while (true) {
while (startPos < length && isWhitespace(idList[startPos]))
++startPos;
if (startPos == length)
break;
size_t endPos = startPos;
while (endPos < length && !isWhitespace(idList[endPos]))
++endPos;
// If there are several nodes with the same id, id() should return the first one.
// In WebKit, getElementById behaves so, too, although its behavior in this case is formally undefined.
RefPtr node = contextScope->getElementById(StringView(idList).substring(startPos, endPos - startPos));
if (node && resultSet.add(*node).isNewEntry)
result.append(node.releaseNonNull());
startPos = endPos;
}
result.markSorted(false);
return Value(WTF::move(result));
}
static inline String expandedNameLocalPart(Node& node)
{
if (auto* pi = dynamicDowncast<ProcessingInstruction>(node))
return pi->target();
return node.localName().string();
}
static inline String expandedName(Node& node)
{
auto& prefix = node.prefix();
return prefix.isEmpty() ? expandedNameLocalPart(node) : makeString(prefix, ':', expandedNameLocalPart(node));
}
Value FunLocalName::evaluate() const
{
if (argumentCount() > 0) {
Value a = argument(0).evaluate();
if (!a.isNodeSet())
return emptyString();
RefPtr node = a.toNodeSet().firstNode();
return node ? expandedNameLocalPart(*node) : emptyString();
}
return expandedNameLocalPart(*evaluationContext().protectedNode());
}
Value FunNamespaceURI::evaluate() const
{
if (argumentCount() > 0) {
Value a = argument(0).evaluate();
if (!a.isNodeSet())
return emptyString();
RefPtr node = a.toNodeSet().firstNode();
return node ? node->namespaceURI().string() : emptyString();
}
return evaluationContext().protectedNode()->namespaceURI().string();
}
Value FunName::evaluate() const
{
if (argumentCount() > 0) {
Value a = argument(0).evaluate();
if (!a.isNodeSet())
return emptyString();
RefPtr node = a.toNodeSet().firstNode();
return node ? expandedName(*node) : emptyString();
}
return expandedName(*evaluationContext().protectedNode());
}
Value FunCount::evaluate() const
{
Value a = argument(0).evaluate();
return double(a.toNodeSet().size());
}
Value FunString::evaluate() const
{
if (!argumentCount())
return Value(NodeSet(*Expression::evaluationContext().node)).toString();
return argument(0).evaluate().toString();
}
Value FunConcat::evaluate() const
{
StringBuilder result;
result.reserveCapacity(1024);
for (unsigned i = 0, count = argumentCount(); i < count; ++i) {
EvaluationContext clonedContext(Expression::evaluationContext());
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
result.append(argument(i).evaluate().toString());
}
return result.toString();
}
Value FunStartsWith::evaluate() const
{
auto clonedContext = Expression::evaluationContext();
String s1 = argument(0).evaluate().toString();
String s2;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
s2 = argument(1).evaluate().toString();
}
if (s2.isEmpty())
return true;
return s1.startsWith(s2);
}
Value FunContains::evaluate() const
{
auto clonedContext = Expression::evaluationContext();
String s1 = argument(0).evaluate().toString();
String s2;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
s2 = argument(1).evaluate().toString();
}
if (s2.isEmpty())
return true;
return s1.contains(s2) != 0;
}
Value FunSubstringBefore::evaluate() const
{
auto clonedContext = Expression::evaluationContext();
String s1 = argument(0).evaluate().toString();
String s2;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
s2 = argument(1).evaluate().toString();
}
if (s2.isEmpty())
return emptyString();
size_t i = s1.find(s2);
if (i == notFound)
return emptyString();
return s1.left(i);
}
Value FunSubstringAfter::evaluate() const
{
auto clonedContext = Expression::evaluationContext();
String s1 = argument(0).evaluate().toString();
String s2;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
s2 = argument(1).evaluate().toString();
}
size_t i = s1.find(s2);
if (i == notFound)
return emptyString();
return s1.substring(i + s2.length());
}
// Computes the 1-based start and end (exclusive) string indices for
// substring. This is all the positions [1, maxLen (inclusive)] where
// start <= position < start + len
static std::pair<unsigned, unsigned> computeSubstringStartEnd(double start, double len, double maxLen)
{
ASSERT(std::isfinite(maxLen));
const double end = start + len;
if (std::isnan(start) || std::isnan(end))
return std::make_pair(1, 1);
// Neither start nor end are NaN, but may still be +/- Inf
const double clampedStart = std::clamp<double>(start, 1, maxLen + 1);
const double clampedEnd = std::clamp<double>(end, clampedStart, maxLen + 1);
return std::make_pair(static_cast<unsigned>(clampedStart), static_cast<unsigned>(clampedEnd));
}
// substring(string, number pos, number? len)
//
// Characters in string are indexed from 1. Numbers are doubles and
// substring is specified to work with IEEE-754 infinity, NaN, and
// XPath's bespoke rounding function, round.
//
// <https://www.w3.org/TR/xpath/#function-substring>
Value FunSubstring::evaluate() const
{
EvaluationContext clonedContext1(Expression::evaluationContext());
EvaluationContext clonedContext2(Expression::evaluationContext());
EvaluationContext clonedContext3(Expression::evaluationContext());
String sourceString;
double pos;
double len;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext1);
sourceString = argument(0).evaluate().toString();
}
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext2);
pos = FunRound::round(argument(1).evaluate().toNumber());
}
if (argumentCount() == 3) {
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext3);
len = FunRound::round(argument(2).evaluate().toNumber());
} else {
len = std::numeric_limits<double>::infinity();
}
const auto bounds = computeSubstringStartEnd(pos, len, sourceString.length());
if (bounds.second <= bounds.first)
return emptyString();
return sourceString.substring(bounds.first - 1, bounds.second - bounds.first);
}
Value FunStringLength::evaluate() const
{
if (!argumentCount())
return Value(NodeSet(*Expression::evaluationContext().node)).toString().length();
return argument(0).evaluate().toString().length();
}
Value FunNormalizeSpace::evaluate() const
{
// https://www.w3.org/TR/1999/REC-xpath-19991116/#function-normalize-space
if (!argumentCount()) {
String s = Value(NodeSet(*Expression::evaluationContext().node)).toString();
return s.simplifyWhiteSpace(isASCIIWhitespaceWithoutFF<char16_t>);
}
String s = argument(0).evaluate().toString();
return s.simplifyWhiteSpace(isASCIIWhitespaceWithoutFF<char16_t>);
}
Value FunTranslate::evaluate() const
{
EvaluationContext clonedContext1(Expression::evaluationContext());
EvaluationContext clonedContext2(Expression::evaluationContext());
String s1 = argument(0).evaluate().toString();
String s2, s3;
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext1);
s2 = argument(1).evaluate().toString();
}
{
SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext2);
s3 = argument(2).evaluate().toString();
}
StringBuilder result;
for (unsigned i1 = 0; i1 < s1.length(); ++i1) {
char16_t ch = s1[i1];
size_t i2 = s2.find(ch);
if (i2 == notFound)
result.append(ch);
else if (i2 < s3.length())
result.append(s3[i2]);
}
return result.toString();
}
Value FunBoolean::evaluate() const
{
return argument(0).evaluate().toBoolean();
}
Value FunNot::evaluate() const
{
return !argument(0).evaluate().toBoolean();
}
Value FunTrue::evaluate() const
{
return true;
}
Value FunLang::evaluate() const
{
String lang = argument(0).evaluate().toString();
const Attribute* languageAttribute = nullptr;
RefPtr node = evaluationContext().node.get();
while (node) {
if (RefPtr element = dynamicDowncast<Element>(*node)) {
if (element->hasAttributes())
languageAttribute = element->findAttributeByName(XMLNames::langAttr);
}
if (languageAttribute)
break;
node = node->parentNode();
}
if (!languageAttribute)
return false;
String langValue = languageAttribute->value();
while (true) {
if (equalIgnoringASCIICase(langValue, lang))
return true;
// Remove suffixes one by one.
size_t index = langValue.reverseFind('-');
if (index == notFound)
break;
langValue = langValue.left(index);
}
return false;
}
Value FunFalse::evaluate() const
{
return false;
}
Value FunNumber::evaluate() const
{
if (!argumentCount())
return Value(NodeSet(*Expression::evaluationContext().node)).toNumber();
return argument(0).evaluate().toNumber();
}
Value FunSum::evaluate() const
{
Value a = argument(0).evaluate();
if (!a.isNodeSet())
return 0.0;
double sum = 0.0;
const NodeSet& nodes = a.toNodeSet();
// To be really compliant, we should sort the node-set, as floating point addition is not associative.
// However, this is unlikely to ever become a practical issue, and sorting is slow.
for (auto& node : nodes)
sum += Value(stringValue(node.get())).toNumber();
return sum;
}
Value FunFloor::evaluate() const
{
return floor(argument(0).evaluate().toNumber());
}
Value FunCeiling::evaluate() const
{
return ceil(argument(0).evaluate().toNumber());
}
double FunRound::round(double val)
{
if (std::isfinite(val)) {
if (std::signbit(val) && val >= -0.5)
val *= 0; // negative zero
else
val = floor(val + 0.5);
}
return val;
}
Value FunRound::evaluate() const
{
return round(argument(0).evaluate().toNumber());
}
struct FunctionMapValue {
std::unique_ptr<Function> (*creationFunction)();
Interval argumentCountInterval;
};
static MemoryCompactLookupOnlyRobinHoodHashMap<String, FunctionMapValue> createFunctionMap()
{
struct FunctionMapping {
ASCIILiteral name;
FunctionMapValue function;
};
static const auto functions = std::to_array<FunctionMapping>({
{ "boolean"_s, { createFunctionBoolean, 1 } },
{ "ceiling"_s, { createFunctionCeiling, 1 } },
{ "concat"_s, { createFunctionConcat, Interval(2, Interval::Inf) } },
{ "contains"_s, { createFunctionContains, 2 } },
{ "count"_s, { createFunctionCount, 1 } },
{ "false"_s, { createFunctionFalse, 0 } },
{ "floor"_s, { createFunctionFloor, 1 } },
{ "id"_s, { createFunctionId, 1 } },
{ "lang"_s, { createFunctionLang, 1 } },
{ "last"_s, { createFunctionLast, 0 } },
{ "local-name"_s, { createFunctionLocalName, Interval(0, 1) } },
{ "name"_s, { createFunctionName, Interval(0, 1) } },
{ "namespace-uri"_s, { createFunctionNamespaceURI, Interval(0, 1) } },
{ "normalize-space"_s, { createFunctionNormalizeSpace, Interval(0, 1) } },
{ "not"_s, { createFunctionNot, 1 } },
{ "number"_s, { createFunctionNumber, Interval(0, 1) } },
{ "position"_s, { createFunctionPosition, 0 } },
{ "round"_s, { createFunctionRound, 1 } },
{ "starts-with"_s, { createFunctionStartsWith, 2 } },
{ "string"_s, { createFunctionString, Interval(0, 1) } },
{ "string-length"_s, { createFunctionStringLength, Interval(0, 1) } },
{ "substring"_s, { createFunctionSubstring, Interval(2, 3) } },
{ "substring-after"_s, { createFunctionSubstringAfter, 2 } },
{ "substring-before"_s, { createFunctionSubstringBefore, 2 } },
{ "sum"_s, { createFunctionSum, 1 } },
{ "translate"_s, { createFunctionTranslate, 3 } },
{ "true"_s, { createFunctionTrue, 0 } },
});
MemoryCompactLookupOnlyRobinHoodHashMap<String, FunctionMapValue> map;
for (auto& function : functions)
map.add(function.name, function.function);
return map;
}
std::unique_ptr<Function> Function::create(const String& name, unsigned numArguments)
{
static NeverDestroyed functionMap = createFunctionMap();
auto it = functionMap.get().find(name);
if (it == functionMap.get().end())
return nullptr;
if (!it->value.argumentCountInterval.contains(numArguments))
return nullptr;
return it->value.creationFunction();
}
std::unique_ptr<Function> Function::create(const String& name)
{
return create(name, 0);
}
std::unique_ptr<Function> Function::create(const String& name, Vector<std::unique_ptr<Expression>> arguments)
{
auto function = create(name, arguments.size());
if (function)
function->setArguments(name, WTF::move(arguments));
return function;
}
} // namespace WebCore::XPath