|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Xsl.Qil;
using FunctionInfo = System.Xml.Xsl.XPath.XPathBuilder.FunctionInfo<System.Xml.Xsl.XPath.XPathBuilder.FuncId>;
using T = System.Xml.Xsl.XmlQueryTypeFactory;
//#define StopMaskOptimisation
namespace System.Xml.Xsl.XPath
{
internal class XPathBuilder : IXPathBuilder<QilNode>, IXPathEnvironment
{
private readonly XPathQilFactory _f;
private readonly IXPathEnvironment _environment;
private bool _inTheBuild;
// Singleton nodes used as fixup markers
protected QilNode fixupCurrent, fixupPosition, fixupLast;
// Number of unresolved fixup nodes
protected int numFixupCurrent, numFixupPosition, numFixupLast;
private readonly FixupVisitor _fixupVisitor;
/* ----------------------------------------------------------------------------
IXPathEnvironment interface
*/
QilNode IFocus.GetCurrent() { return GetCurrentNode(); }
QilNode IFocus.GetPosition() { return GetCurrentPosition(); }
QilNode IFocus.GetLast() { return GetLastPosition(); }
XPathQilFactory IXPathEnvironment.Factory { get { return _f; } }
QilNode IXPathEnvironment.ResolveVariable(string prefix, string name)
{
return Variable(prefix, name);
}
QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList<QilNode> args, IFocus env)
{
Debug.Fail("Must not be called");
return null;
}
string IXPathEnvironment.ResolvePrefix(string prefix)
{
return _environment.ResolvePrefix(prefix);
}
// ----------------------------------------------------------------------------
public XPathBuilder(IXPathEnvironment environment)
{
_environment = environment;
_f = _environment.Factory;
this.fixupCurrent = _f.Unknown(T.NodeNotRtf);
this.fixupPosition = _f.Unknown(T.DoubleX);
this.fixupLast = _f.Unknown(T.DoubleX);
_fixupVisitor = new FixupVisitor(_f, fixupCurrent, fixupPosition, fixupLast);
}
public virtual void StartBuild()
{
Debug.Assert(!_inTheBuild, "XPathBuilder is busy!");
_inTheBuild = true;
numFixupCurrent = numFixupPosition = numFixupLast = 0;
}
[return: NotNullIfNotNull(nameof(result))]
public virtual QilNode? EndBuild(QilNode? result)
{
if (result == null)
{ // special door to clean builder state in exception handlers
_inTheBuild = false;
return result;
}
Debug.Assert(_inTheBuild, "StartBuild() wasn't called");
if (result.XmlType!.MaybeMany && result.XmlType.IsNode && result.XmlType.IsNotRtf)
{
result = _f.DocOrderDistinct(result);
}
result = _fixupVisitor.Fixup(result, /*environment:*/_environment);
numFixupCurrent -= _fixupVisitor.numCurrent;
numFixupPosition -= _fixupVisitor.numPosition;
numFixupLast -= _fixupVisitor.numLast;
// All these variables will be positive for "false() and (. = position() + last())"
// since QilPatternFactory eliminates the right operand of 'and'
Debug.Assert(numFixupCurrent >= 0, "Context fixup error");
Debug.Assert(numFixupPosition >= 0, "Context fixup error");
Debug.Assert(numFixupLast >= 0, "Context fixup error");
_inTheBuild = false;
return result;
}
private QilNode GetCurrentNode() { numFixupCurrent++; return fixupCurrent; }
private QilNode GetCurrentPosition() { numFixupPosition++; return fixupPosition; }
private QilNode GetLastPosition() { numFixupLast++; return fixupLast; }
public virtual QilNode String(string value)
{
return _f.String(value);
}
public virtual QilNode Number(double value)
{
return _f.Double(value);
}
public virtual QilNode Operator(XPathOperator op, QilNode? left, QilNode? right)
{
Debug.Assert(op != XPathOperator.Unknown);
XPathOperatorGroup opGroup = s_operatorGroup[(int)op];
Debug.Assert((opGroup != XPathOperatorGroup.Negate && right != null) || (opGroup == XPathOperatorGroup.Negate && right == null));
switch (opGroup)
{
case XPathOperatorGroup.Logical: return LogicalOperator(op, left!, right!);
case XPathOperatorGroup.Equality: return EqualityOperator(op, left!, right!);
case XPathOperatorGroup.Relational: return RelationalOperator(op, left!, right!);
case XPathOperatorGroup.Arithmetic: return ArithmeticOperator(op, left!, right!);
case XPathOperatorGroup.Negate: return NegateOperator(op, left!);
case XPathOperatorGroup.Union: return UnionOperator(op, left, right!);
default:
Debug.Fail($"{op} is not a valid XPathOperator");
return null;
}
}
private QilNode LogicalOperator(XPathOperator op, QilNode left, QilNode right)
{
Debug.Assert(op == XPathOperator.Or || op == XPathOperator.And);
left = _f.ConvertToBoolean(left);
right = _f.ConvertToBoolean(right);
return (
op == XPathOperator.Or ? _f.Or(left, right) :
/*default*/ _f.And(left, right)
);
}
private QilNode CompareValues(XPathOperator op, QilNode left, QilNode right, XmlTypeCode compType)
{
Debug.Assert(compType == XmlTypeCode.Boolean || compType == XmlTypeCode.Double || compType == XmlTypeCode.String);
Debug.Assert(compType == XmlTypeCode.Boolean || left.XmlType!.IsSingleton && right.XmlType!.IsSingleton, "Both comparison operands must be singletons");
left = _f.ConvertToType(compType, left);
right = _f.ConvertToType(compType, right);
switch (op)
{
case XPathOperator.Eq: return _f.Eq(left, right);
case XPathOperator.Ne: return _f.Ne(left, right);
case XPathOperator.Lt: return _f.Lt(left, right);
case XPathOperator.Le: return _f.Le(left, right);
case XPathOperator.Gt: return _f.Gt(left, right);
case XPathOperator.Ge: return _f.Ge(left, right);
default:
Debug.Fail("Wrong operator type");
return null;
}
}
private QilNode CompareNodeSetAndValue(XPathOperator op, QilNode nodeset, QilNode val, XmlTypeCode compType)
{
XPathQilFactory.CheckNodeSet(nodeset);
Debug.Assert(val.XmlType!.IsSingleton);
Debug.Assert(compType == XmlTypeCode.Boolean || compType == XmlTypeCode.Double || compType == XmlTypeCode.String, "I don't know what to do with RTF here");
if (compType == XmlTypeCode.Boolean || nodeset.XmlType!.IsSingleton)
{
return CompareValues(op, nodeset, val, compType);
}
else
{
QilIterator it = _f.For(nodeset);
return _f.Not(_f.IsEmpty(_f.Filter(it, CompareValues(op, _f.XPathNodeValue(it), val, compType))));
}
}
// Inverts relational operator in order to swap operands of the comparison
private static XPathOperator InvertOp(XPathOperator op)
{
return (
op == XPathOperator.Lt ? XPathOperator.Gt : // '<' --> '>'
op == XPathOperator.Le ? XPathOperator.Ge : // '<=' --> '>='
op == XPathOperator.Gt ? XPathOperator.Lt : // '>' --> '<'
op == XPathOperator.Ge ? XPathOperator.Le : // '>=' --> '<='
/*default:*/ op
);
}
private QilNode CompareNodeSetAndNodeSet(XPathOperator op, QilNode left, QilNode right, XmlTypeCode compType)
{
XPathQilFactory.CheckNodeSet(left);
XPathQilFactory.CheckNodeSet(right);
if (right.XmlType!.IsSingleton)
{
return CompareNodeSetAndValue(op, /*nodeset:*/left, /*value:*/right, compType);
}
if (left.XmlType!.IsSingleton)
{
op = InvertOp(op);
return CompareNodeSetAndValue(op, /*nodeset:*/right, /*value:*/left, compType);
}
QilIterator leftEnd = _f.For(left);
QilIterator rightEnd = _f.For(right);
return _f.Not(_f.IsEmpty(_f.Loop(leftEnd, _f.Filter(rightEnd, CompareValues(op, _f.XPathNodeValue(leftEnd), _f.XPathNodeValue(rightEnd), compType)))));
}
private QilNode EqualityOperator(XPathOperator op, QilNode left, QilNode right)
{
Debug.Assert(op == XPathOperator.Eq || op == XPathOperator.Ne);
XmlQueryType leftType = left.XmlType!;
XmlQueryType rightType = right.XmlType!;
if (XPathQilFactory.IsAnyType(left) || XPathQilFactory.IsAnyType(right))
{
return _f.InvokeEqualityOperator(s_qilOperator[(int)op], left, right);
}
else if (leftType.IsNode && rightType.IsNode)
{
return CompareNodeSetAndNodeSet(op, left, right, XmlTypeCode.String);
}
else if (leftType.IsNode)
{
return CompareNodeSetAndValue(op, /*nodeset:*/left, /*val:*/right, rightType.TypeCode);
}
else if (rightType.IsNode)
{
return CompareNodeSetAndValue(op, /*nodeset:*/right, /*val:*/left, leftType.TypeCode);
}
else
{
XmlTypeCode compType = (
leftType.TypeCode == XmlTypeCode.Boolean || rightType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean :
leftType.TypeCode == XmlTypeCode.Double || rightType.TypeCode == XmlTypeCode.Double ? XmlTypeCode.Double :
/*default:*/ XmlTypeCode.String
);
return CompareValues(op, left, right, compType);
}
}
private QilNode RelationalOperator(XPathOperator op, QilNode left, QilNode right)
{
Debug.Assert(op == XPathOperator.Lt || op == XPathOperator.Le || op == XPathOperator.Gt || op == XPathOperator.Ge);
XmlQueryType leftType = left.XmlType!;
XmlQueryType rightType = right.XmlType!;
if (XPathQilFactory.IsAnyType(left) || XPathQilFactory.IsAnyType(right))
{
return _f.InvokeRelationalOperator(s_qilOperator[(int)op], left, right);
}
else if (leftType.IsNode && rightType.IsNode)
{
return CompareNodeSetAndNodeSet(op, left, right, XmlTypeCode.Double);
}
else if (leftType.IsNode)
{
XmlTypeCode compType = rightType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean : XmlTypeCode.Double;
return CompareNodeSetAndValue(op, /*nodeset:*/left, /*val:*/right, compType);
}
else if (rightType.IsNode)
{
XmlTypeCode compType = leftType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean : XmlTypeCode.Double;
op = InvertOp(op);
return CompareNodeSetAndValue(op, /*nodeset:*/right, /*val:*/left, compType);
}
else
{
return CompareValues(op, left, right, XmlTypeCode.Double);
}
}
private QilNode NegateOperator(XPathOperator op, QilNode left)
{
Debug.Assert(op == XPathOperator.UnaryMinus);
return _f.Negate(_f.ConvertToNumber(left));
}
private QilNode ArithmeticOperator(XPathOperator op, QilNode left, QilNode right)
{
left = _f.ConvertToNumber(left);
right = _f.ConvertToNumber(right);
switch (op)
{
case XPathOperator.Plus: return _f.Add(left, right);
case XPathOperator.Minus: return _f.Subtract(left, right);
case XPathOperator.Multiply: return _f.Multiply(left, right);
case XPathOperator.Divide: return _f.Divide(left, right);
case XPathOperator.Modulo: return _f.Modulo(left, right);
default:
Debug.Fail("Wrong operator type");
return null;
}
}
private QilNode UnionOperator(XPathOperator op, QilNode? left, QilNode right)
{
Debug.Assert(op == XPathOperator.Union);
if (left == null)
{
return _f.EnsureNodeSet(right);
}
left = _f.EnsureNodeSet(left);
right = _f.EnsureNodeSet(right);
if (left.NodeType == QilNodeType.Sequence)
{
((QilList)left).Add(right);
return left;
}
else
{
return _f.Union(left, right);
}
}
// also called by XPathPatternBuilder
public static XmlNodeKindFlags AxisTypeMask(XmlNodeKindFlags inputTypeMask, XPathNodeType nodeType, XPathAxis xpathAxis)
{
return (XmlNodeKindFlags)(
(int)inputTypeMask &
(int)s_XPathNodeType2QilXmlNodeKind[(int)nodeType] & (int)s_XPathAxisMask[(int)xpathAxis]
);
}
private QilNode BuildAxisFilter(QilNode qilAxis, XPathAxis xpathAxis, XPathNodeType nodeType, string? name, string? nsUri)
{
XmlNodeKindFlags original = qilAxis.XmlType!.NodeKinds;
XmlNodeKindFlags required = AxisTypeMask(original, nodeType, xpathAxis);
QilIterator itr;
if (required == 0)
{
return _f.Sequence();
}
else if (required == original)
{
}
else
{
qilAxis = _f.Filter(itr = _f.For(qilAxis), _f.IsType(itr, T.NodeChoice(required)));
qilAxis.XmlType = T.PrimeProduct(T.NodeChoice(required), qilAxis.XmlType!.Cardinality);
// Without code bellow IlGeneragion gives stack overflow exception for the following passage.
//<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
// <xsl:template match="/">
// <xsl:value-of select="descendant::author/@id | comment()" />
// </xsl:template>
//</xsl:stylesheet>
if (qilAxis.NodeType == QilNodeType.Filter)
{
QilLoop filter = (QilLoop)qilAxis;
filter.Body = _f.And(filter.Body,
name != null && nsUri != null ? _f.Eq(_f.NameOf(itr), _f.QName(name, nsUri)) : // ns:bar || bar
nsUri != null ? _f.Eq(_f.NamespaceUriOf(itr), _f.String(nsUri)) : // ns:*
name != null ? _f.Eq(_f.LocalNameOf(itr), _f.String(name)) : // *:foo
/*name == nsUri == null*/ _f.True() // *
);
return filter;
}
}
return _f.Filter(itr = _f.For(qilAxis),
name != null && nsUri != null ? _f.Eq(_f.NameOf(itr), _f.QName(name, nsUri)) : // ns:bar || bar
nsUri != null ? _f.Eq(_f.NamespaceUriOf(itr), _f.String(nsUri)) : // ns:*
name != null ? _f.Eq(_f.LocalNameOf(itr), _f.String(name)) : // *:foo
/*name == nsUri == null*/ _f.True() // *
);
}
// XmlNodeKindFlags from XPathNodeType
private static readonly XmlNodeKindFlags[] s_XPathNodeType2QilXmlNodeKind = {
/*Root */ XmlNodeKindFlags.Document,
/*Element */ XmlNodeKindFlags.Element,
/*Attribute */ XmlNodeKindFlags.Attribute,
/*Namespace */ XmlNodeKindFlags.Namespace,
/*Text */ XmlNodeKindFlags.Text,
/*SignificantWhitespace*/ XmlNodeKindFlags.Text,
/*Whitespace */ XmlNodeKindFlags.Text,
/*ProcessingInstruction*/ XmlNodeKindFlags.PI,
/*Comment */ XmlNodeKindFlags.Comment,
/*All */ XmlNodeKindFlags.Any
};
private QilNode BuildAxis(XPathAxis xpathAxis, XPathNodeType nodeType, string? nsUri, string? name)
{
QilNode currentNode = GetCurrentNode();
QilNode qilAxis;
switch (xpathAxis)
{
case XPathAxis.Ancestor: qilAxis = _f.Ancestor(currentNode); break;
case XPathAxis.AncestorOrSelf: qilAxis = _f.AncestorOrSelf(currentNode); break;
case XPathAxis.Attribute: qilAxis = _f.Content(currentNode); break;
case XPathAxis.Child: qilAxis = _f.Content(currentNode); break;
case XPathAxis.Descendant: qilAxis = _f.Descendant(currentNode); break;
case XPathAxis.DescendantOrSelf: qilAxis = _f.DescendantOrSelf(currentNode); break;
case XPathAxis.Following: qilAxis = _f.XPathFollowing(currentNode); break;
case XPathAxis.FollowingSibling: qilAxis = _f.FollowingSibling(currentNode); break;
case XPathAxis.Namespace: qilAxis = _f.XPathNamespace(currentNode); break;
case XPathAxis.Parent: qilAxis = _f.Parent(currentNode); break;
case XPathAxis.Preceding: qilAxis = _f.XPathPreceding(currentNode); break;
case XPathAxis.PrecedingSibling: qilAxis = _f.PrecedingSibling(currentNode); break;
case XPathAxis.Self: qilAxis = (currentNode); break;
// Can be done using BuildAxisFilter() but f.Root() sets wrong XmlNodeKindFlags
case XPathAxis.Root: return _f.Root(currentNode);
default:
qilAxis = null!;
Debug.Fail("Invalid EnumValue 'XPathAxis'");
break;
}
QilNode result = BuildAxisFilter(qilAxis, xpathAxis, nodeType, name, nsUri);
if (
xpathAxis == XPathAxis.Ancestor || xpathAxis == XPathAxis.Preceding ||
xpathAxis == XPathAxis.AncestorOrSelf || xpathAxis == XPathAxis.PrecedingSibling
)
{
result = _f.BaseFactory.DocOrderDistinct(result);
// To make grouping operator NOP we should always return path expressions in DOD.
// I can't use Pattern factory here because Predicate() depends on fact that DOD() is
// outmost node in reverse steps
}
return result;
}
public virtual QilNode Axis(XPathAxis xpathAxis, XPathNodeType nodeType, string? prefix, string? name)
{
string? nsUri = prefix == null ? null : _environment.ResolvePrefix(prefix);
return BuildAxis(xpathAxis, nodeType, nsUri, name);
}
// "left/right"
public virtual QilNode JoinStep(QilNode left, QilNode right)
{
XPathQilFactory.CheckNodeSet(right);
QilIterator leftIt = _f.For(_f.EnsureNodeSet(left));
// in XPath 1.0 step is always nodetest and as a result it can't contain last().
right = _fixupVisitor.Fixup(right, /*current:*/leftIt, /*last:*/null);
numFixupCurrent -= _fixupVisitor.numCurrent;
numFixupPosition -= _fixupVisitor.numPosition;
numFixupLast -= _fixupVisitor.numLast;
return _f.DocOrderDistinct(_f.Loop(leftIt, right));
}
// "nodeset[predicate]"
// XPath spec $3.3 (para 5)
public virtual QilNode Predicate(QilNode nodeset, QilNode predicate, bool isReverseStep)
{
if (isReverseStep)
{
Debug.Assert(nodeset.NodeType == QilNodeType.DocOrderDistinct,
"ReverseAxe in Qil is actually reverse and we compile them here in builder by wrapping to DocOrderDistinct()"
);
// The trick here is that we unwarp it back, compile as regular predicate and wrap again.
// this way this wat we hold invariant that path expression are always DOD and make predicates on reverse axe
// work as specified in XPath 2.0 FS: http://www.w3.org/TR/xquery-semantics/#id-axis-steps
nodeset = ((QilUnary)nodeset).Child;
}
predicate = PredicateToBoolean(predicate, _f, this);
return BuildOnePredicate(nodeset, predicate, isReverseStep, _f, _fixupVisitor, ref numFixupCurrent, ref numFixupPosition, ref numFixupLast);
}
//also used by XPathPatternBuilder
public static QilNode PredicateToBoolean(QilNode predicate, XPathQilFactory f, IXPathEnvironment env)
{
// Prepocess predicate: if (predicate is number) then predicate := (position() == predicate)
if (!XPathQilFactory.IsAnyType(predicate))
{
if (predicate.XmlType!.TypeCode == XmlTypeCode.Double)
{
predicate = f.Eq(env.GetPosition(), predicate);
}
else
{
predicate = f.ConvertToBoolean(predicate);
}
}
else
{
QilIterator i;
predicate = f.Loop(i = f.Let(predicate),
f.Conditional(f.IsType(i, T.Double),
f.Eq(env.GetPosition(), f.TypeAssert(i, T.DoubleX)),
f.ConvertToBoolean(i)
)
);
}
return predicate;
}
//also used by XPathPatternBuilder
public static QilNode BuildOnePredicate(QilNode nodeset, QilNode predicate, bool isReverseStep,
XPathQilFactory f, FixupVisitor fixupVisitor,
ref int numFixupCurrent, ref int numFixupPosition, ref int numFixupLast)
{
nodeset = f.EnsureNodeSet(nodeset);
// Mirgeing nodeset and predicate:
// 1. Predicate contains 0 last() :
// for $i in nodeset
// where predicate
// return $i
// Note: Currently we are keepeing old output to minimize diff.
// 2. Predicate contains 1 last()
// let $cach := nodeset return
// for $i in $cach
// where predicate(length($cach))
// return $i
// Suggestion: This is a little optimisation we can do or don't do.
// 3. Predicate contains 2+ last()
// let $cash := nodeset return
// let $size := length($cash) return
// for $i in $cash
// where predicate($size)
// return $i
QilNode result;
if (numFixupLast != 0 && fixupVisitor.CountUnfixedLast(predicate) != 0)
{
// this subtree has unfixed last() nodes
QilIterator cash = f.Let(nodeset);
QilIterator size = f.Let(f.XsltConvert(f.Length(cash), T.DoubleX));
QilIterator it = f.For(cash);
predicate = fixupVisitor.Fixup(predicate, /*current:*/it, /*last:*/size);
numFixupCurrent -= fixupVisitor.numCurrent;
numFixupPosition -= fixupVisitor.numPosition;
numFixupLast -= fixupVisitor.numLast;
result = f.Loop(cash, f.Loop(size, f.Filter(it, predicate)));
}
else
{
QilIterator it = f.For(nodeset);
predicate = fixupVisitor.Fixup(predicate, /*current:*/it, /*last:*/null);
numFixupCurrent -= fixupVisitor.numCurrent;
numFixupPosition -= fixupVisitor.numPosition;
numFixupLast -= fixupVisitor.numLast;
result = f.Filter(it, predicate);
}
if (isReverseStep)
{
result = f.DocOrderDistinct(result);
}
return result;
}
public virtual QilNode Variable(string prefix, string name)
{
return _environment.ResolveVariable(prefix, name);
}
public virtual QilNode Function(string prefix, string name, IList<QilNode> args)
{
Debug.Assert(!args.IsReadOnly, "Writable collection expected");
if (prefix.Length == 0)
{
FunctionInfo? func;
if (FunctionTable.TryGetValue(name, out func))
{
func.CastArguments(args, name, _f);
switch (func.id)
{
case FuncId.Not: return _f.Not(args[0]);
case FuncId.Last: return GetLastPosition();
case FuncId.Position: return GetCurrentPosition();
case FuncId.Count: return _f.XsltConvert(_f.Length(_f.DocOrderDistinct(args[0])), T.DoubleX);
case FuncId.LocalName: return args.Count == 0 ? _f.LocalNameOf(GetCurrentNode()) : LocalNameOfFirstNode(args[0]);
case FuncId.NamespaceUri: return args.Count == 0 ? _f.NamespaceUriOf(GetCurrentNode()) : NamespaceOfFirstNode(args[0]);
case FuncId.Name: return args.Count == 0 ? NameOf(GetCurrentNode()) : NameOfFirstNode(args[0]);
case FuncId.String: return args.Count == 0 ? _f.XPathNodeValue(GetCurrentNode()) : _f.ConvertToString(args[0]);
case FuncId.Number: return args.Count == 0 ? _f.XsltConvert(_f.XPathNodeValue(GetCurrentNode()), T.DoubleX) : _f.ConvertToNumber(args[0]);
case FuncId.Boolean: return _f.ConvertToBoolean(args[0]);
case FuncId.True: return _f.True();
case FuncId.False: return _f.False();
case FuncId.Id: return _f.DocOrderDistinct(_f.Id(GetCurrentNode(), args[0]));
case FuncId.Concat: return _f.StrConcat(args);
case FuncId.StartsWith: return _f.InvokeStartsWith(args[0], args[1]);
case FuncId.Contains: return _f.InvokeContains(args[0], args[1]);
case FuncId.SubstringBefore: return _f.InvokeSubstringBefore(args[0], args[1]);
case FuncId.SubstringAfter: return _f.InvokeSubstringAfter(args[0], args[1]);
case FuncId.Substring:
return args.Count == 2 ? _f.InvokeSubstring(args[0], args[1]) : _f.InvokeSubstring(args[0], args[1], args[2]);
case FuncId.StringLength:
return _f.XsltConvert(_f.StrLength(args.Count == 0 ? _f.XPathNodeValue(GetCurrentNode()) : args[0]), T.DoubleX);
case FuncId.Normalize:
return _f.InvokeNormalizeSpace(args.Count == 0 ? _f.XPathNodeValue(GetCurrentNode()) : args[0]);
case FuncId.Translate: return _f.InvokeTranslate(args[0], args[1], args[2]);
case FuncId.Lang: return _f.InvokeLang(args[0], GetCurrentNode());
case FuncId.Sum: return Sum(_f.DocOrderDistinct(args[0]));
case FuncId.Floor: return _f.InvokeFloor(args[0]);
case FuncId.Ceiling: return _f.InvokeCeiling(args[0]);
case FuncId.Round: return _f.InvokeRound(args[0]);
default:
Debug.Fail($"{func.id} is present in the function table, but absent from the switch");
return null;
}
}
}
return _environment.ResolveFunction(prefix, name, args, (IFocus)this);
}
private QilNode LocalNameOfFirstNode(QilNode arg)
{
XPathQilFactory.CheckNodeSet(arg);
if (arg.XmlType!.IsSingleton)
{
return _f.LocalNameOf(arg);
}
else
{
QilIterator i;
return _f.StrConcat(_f.Loop(i = _f.FirstNode(arg), _f.LocalNameOf(i)));
}
}
private QilNode NamespaceOfFirstNode(QilNode arg)
{
XPathQilFactory.CheckNodeSet(arg);
if (arg.XmlType!.IsSingleton)
{
return _f.NamespaceUriOf(arg);
}
else
{
QilIterator i;
return _f.StrConcat(_f.Loop(i = _f.FirstNode(arg), _f.NamespaceUriOf(i)));
}
}
private QilNode NameOf(QilNode arg)
{
XPathQilFactory.CheckNodeNotRtf(arg);
// We may want to introduce a new QIL node that returns a string.
if (arg is QilIterator)
{
QilIterator p, ln;
return _f.Loop(p = _f.Let(_f.PrefixOf(arg)), _f.Loop(ln = _f.Let(_f.LocalNameOf(arg)),
_f.Conditional(_f.Eq(_f.StrLength(p), _f.Int32(0)), ln, _f.StrConcat(p, _f.String(":"), ln)
)));
}
else
{
QilIterator let = _f.Let(arg);
return _f.Loop(let, /*recursion:*/NameOf(let));
}
}
private QilNode NameOfFirstNode(QilNode arg)
{
XPathQilFactory.CheckNodeSet(arg);
if (arg.XmlType!.IsSingleton)
{
return NameOf(arg);
}
else
{
QilIterator i;
return _f.StrConcat(_f.Loop(i = _f.FirstNode(arg), NameOf(i)));
}
}
private QilNode Sum(QilNode arg)
{
XPathQilFactory.CheckNodeSet(arg);
QilIterator i;
return _f.Sum(_f.Sequence(_f.Double(0d), _f.Loop(i = _f.For(arg), _f.ConvertToNumber(i))));
}
private enum XPathOperatorGroup
{
Unknown,
Logical,
Equality,
Relational,
Arithmetic,
Negate,
Union,
}
private static readonly XPathOperatorGroup[] s_operatorGroup = {
/*Unknown */ XPathOperatorGroup.Unknown,
/*Or */ XPathOperatorGroup.Logical,
/*And */ XPathOperatorGroup.Logical,
/*Eq */ XPathOperatorGroup.Equality,
/*Ne */ XPathOperatorGroup.Equality,
/*Lt */ XPathOperatorGroup.Relational,
/*Le */ XPathOperatorGroup.Relational,
/*Gt */ XPathOperatorGroup.Relational,
/*Ge */ XPathOperatorGroup.Relational,
/*Plus */ XPathOperatorGroup.Arithmetic,
/*Minus */ XPathOperatorGroup.Arithmetic,
/*Multiply */ XPathOperatorGroup.Arithmetic,
/*Divide */ XPathOperatorGroup.Arithmetic,
/*Modulo */ XPathOperatorGroup.Arithmetic,
/*UnaryMinus*/ XPathOperatorGroup.Negate,
/*Union */ XPathOperatorGroup.Union,
};
private static readonly QilNodeType[] s_qilOperator = {
/*Unknown */ QilNodeType.Unknown,
/*Or */ QilNodeType.Or,
/*And */ QilNodeType.And,
/*Eq */ QilNodeType.Eq,
/*Ne */ QilNodeType.Ne,
/*Lt */ QilNodeType.Lt,
/*Le */ QilNodeType.Le,
/*Gt */ QilNodeType.Gt,
/*Ge */ QilNodeType.Ge,
/*Plus */ QilNodeType.Add,
/*Minus */ QilNodeType.Subtract,
/*Multiply */ QilNodeType.Multiply,
/*Divide */ QilNodeType.Divide,
/*Modulo */ QilNodeType.Modulo,
/*UnaryMinus */ QilNodeType.Negate,
/*Union */ QilNodeType.Sequence,
};
// XmlNodeType(s) of nodes by XPathAxis
private static readonly XmlNodeKindFlags[] s_XPathAxisMask = {
/*Unknown */ XmlNodeKindFlags.None,
/*Ancestor */ XmlNodeKindFlags.Element | XmlNodeKindFlags.Document,
/*AncestorOrSelf */ XmlNodeKindFlags.Any,
/*Attribute */ XmlNodeKindFlags.Attribute,
/*Child */ XmlNodeKindFlags.Content,
/*Descendant */ XmlNodeKindFlags.Content,
/*DescendantOrSelf*/ XmlNodeKindFlags.Any,
/*Following */ XmlNodeKindFlags.Content,
/*FollowingSibling*/ XmlNodeKindFlags.Content,
/*Namespace */ XmlNodeKindFlags.Namespace,
/*Parent */ XmlNodeKindFlags.Element | XmlNodeKindFlags.Document,
/*Preceding */ XmlNodeKindFlags.Content,
/*PrecedingSibling*/ XmlNodeKindFlags.Content,
/*Self */ XmlNodeKindFlags.Any,
/*Root */ XmlNodeKindFlags.Document,
};
// ----------------------------------------------------------------
internal enum FuncId
{
Last = 0,
Position,
Count,
LocalName,
NamespaceUri,
Name,
String,
Number,
Boolean,
True,
False,
Not,
Id,
Concat,
StartsWith,
Contains,
SubstringBefore,
SubstringAfter,
Substring,
StringLength,
Normalize,
Translate,
Lang,
Sum,
Floor,
Ceiling,
Round
};
public static readonly XmlTypeCode[] argAny = { XmlTypeCode.Item };
public static readonly XmlTypeCode[] argNodeSet = { XmlTypeCode.Node };
public static readonly XmlTypeCode[] argBoolean = { XmlTypeCode.Boolean };
public static readonly XmlTypeCode[] argDouble = { XmlTypeCode.Double };
public static readonly XmlTypeCode[] argString = { XmlTypeCode.String };
public static readonly XmlTypeCode[] argString2 = { XmlTypeCode.String, XmlTypeCode.String };
public static readonly XmlTypeCode[] argString3 = { XmlTypeCode.String, XmlTypeCode.String, XmlTypeCode.String };
public static readonly XmlTypeCode[] argFnSubstr = { XmlTypeCode.String, XmlTypeCode.Double, XmlTypeCode.Double };
public static Dictionary<string, FunctionInfo> FunctionTable = CreateFunctionTable();
private static Dictionary<string, FunctionInfo> CreateFunctionTable()
{
Dictionary<string, FunctionInfo> table = new Dictionary<string, FunctionInfo>(36);
table.Add("last", new FunctionInfo(FuncId.Last, 0, 0, null));
table.Add("position", new FunctionInfo(FuncId.Position, 0, 0, null));
table.Add("name", new FunctionInfo(FuncId.Name, 0, 1, argNodeSet));
table.Add("namespace-uri", new FunctionInfo(FuncId.NamespaceUri, 0, 1, argNodeSet));
table.Add("local-name", new FunctionInfo(FuncId.LocalName, 0, 1, argNodeSet));
table.Add("count", new FunctionInfo(FuncId.Count, 1, 1, argNodeSet));
table.Add("id", new FunctionInfo(FuncId.Id, 1, 1, argAny));
table.Add("string", new FunctionInfo(FuncId.String, 0, 1, argAny));
table.Add("concat", new FunctionInfo(FuncId.Concat, 2, FunctionInfo.Infinity, null));
table.Add("starts-with", new FunctionInfo(FuncId.StartsWith, 2, 2, argString2));
table.Add("contains", new FunctionInfo(FuncId.Contains, 2, 2, argString2));
table.Add("substring-before", new FunctionInfo(FuncId.SubstringBefore, 2, 2, argString2));
table.Add("substring-after", new FunctionInfo(FuncId.SubstringAfter, 2, 2, argString2));
table.Add("substring", new FunctionInfo(FuncId.Substring, 2, 3, argFnSubstr));
table.Add("string-length", new FunctionInfo(FuncId.StringLength, 0, 1, argString));
table.Add("normalize-space", new FunctionInfo(FuncId.Normalize, 0, 1, argString));
table.Add("translate", new FunctionInfo(FuncId.Translate, 3, 3, argString3));
table.Add("boolean", new FunctionInfo(FuncId.Boolean, 1, 1, argAny));
table.Add("not", new FunctionInfo(FuncId.Not, 1, 1, argBoolean));
table.Add("true", new FunctionInfo(FuncId.True, 0, 0, null));
table.Add("false", new FunctionInfo(FuncId.False, 0, 0, null));
table.Add("lang", new FunctionInfo(FuncId.Lang, 1, 1, argString));
table.Add("number", new FunctionInfo(FuncId.Number, 0, 1, argAny));
table.Add("sum", new FunctionInfo(FuncId.Sum, 1, 1, argNodeSet));
table.Add("floor", new FunctionInfo(FuncId.Floor, 1, 1, argDouble));
table.Add("ceiling", new FunctionInfo(FuncId.Ceiling, 1, 1, argDouble));
table.Add("round", new FunctionInfo(FuncId.Round, 1, 1, argDouble));
return table;
}
public static bool IsFunctionAvailable(string localName, string nsUri)
{
if (nsUri.Length != 0)
{
return false;
}
return FunctionTable.ContainsKey(localName);
}
internal sealed class FixupVisitor : QilReplaceVisitor
{
private new readonly QilPatternFactory f;
private readonly QilNode _fixupCurrent, _fixupPosition, _fixupLast; // fixup nodes we are replacing
private QilIterator? _current;
private QilNode? _last; // expressions we are using to replace fixupNodes
private bool _justCount; // Don't change tree, just count
private IXPathEnvironment? _environment; // temp solution
public int numCurrent, numPosition, numLast; // here we are counting all replacements we have made
public FixupVisitor(QilPatternFactory f, QilNode fixupCurrent, QilNode fixupPosition, QilNode fixupLast) : base(f.BaseFactory)
{
this.f = f;
_fixupCurrent = fixupCurrent;
_fixupPosition = fixupPosition;
_fixupLast = fixupLast;
}
public QilNode Fixup(QilNode inExpr, QilIterator current, QilNode? last)
{
QilDepthChecker.Check(inExpr);
_current = current;
_last = last;
Debug.Assert(current != null);
_justCount = false;
_environment = null;
numCurrent = numPosition = numLast = 0;
inExpr = VisitAssumeReference(inExpr);
#if StopMaskOptimisation
SetStopVisitMark(inExpr, /*stop*/true);
#endif
return inExpr;
}
public QilNode Fixup(QilNode inExpr, IXPathEnvironment environment)
{
Debug.Assert(environment != null);
QilDepthChecker.Check(inExpr);
_justCount = false;
_current = null;
_environment = environment;
numCurrent = numPosition = numLast = 0;
inExpr = VisitAssumeReference(inExpr);
#if StopMaskOptimisation
// Don't need
//SetStopVisitMark(inExpr, /*stop*/true);
#endif
return inExpr;
}
public int CountUnfixedLast(QilNode inExpr)
{
_justCount = true;
numCurrent = numPosition = numLast = 0;
VisitAssumeReference(inExpr);
return numLast;
}
protected override QilNode VisitUnknown(QilNode unknown)
{
Debug.Assert(unknown.NodeType == QilNodeType.Unknown);
if (unknown == _fixupCurrent)
{
numCurrent++;
if (!_justCount)
{
if (_environment != null)
{
unknown = _environment.GetCurrent()!;
}
else if (_current != null)
{
unknown = _current;
}
}
}
else if (unknown == _fixupPosition)
{
numPosition++;
if (!_justCount)
{
if (_environment != null)
{
unknown = _environment.GetPosition();
}
else if (_current != null)
{
// position can be in predicate only and in predicate current olways an iterator
unknown = f.XsltConvert(f.PositionOf((QilIterator)_current), T.DoubleX);
}
}
}
else if (unknown == _fixupLast)
{
numLast++;
if (!_justCount)
{
if (_environment != null)
{
unknown = _environment.GetLast();
}
else if (_current != null)
{
Debug.Assert(_last != null);
unknown = _last;
}
}
}
Debug.Assert(unknown != null);
return unknown;
}
#if StopMaskOptimisation
// This optimisation marks subtrees that was fixed already and prevents FixupVisitor from
// visiting them again. The logic is brokken, because when unfixed tree is added inside fixed one
// it never fixed anymore.
// This happens in all cortasian productions now.
// Excample "a/b=c". 'c' is added inside 'b'
// I belive some optimisation is possible and would be nice to have.
// We may change the way we generating cortasian product.
protected override QilNode Visit(QilNode n) {
if (GetStopVisitMark(n)) {
// Optimisation:
// This subtree was fixed already. No need to go inside it.
if (! justCount) {
SetStopVisitMark(n, /*stop*/false); // We clean this annotation
}
return n;
}
return base.Visit(n);
}
void SetStopVisitMark(QilNode n, bool stop) {
if (n.Type != QilNodeType.For && n.Type != QilNodeType.Let) {
XsltAnnotation.Write(n)[0] = (stop ? /*any object*/fixupCurrent : null);
} else {
// we shouldn't alter annotation of "reference" nodes (Iterators, Functions, ...)
}
}
bool GetStopVisitMark(QilNode n) {
return XsltAnnotation.Write(n)[0] != null;
}
#endif
}
internal sealed class FunctionInfo<T>
{
public T id;
public int minArgs;
public int maxArgs;
public XmlTypeCode[]? argTypes;
public const int Infinity = int.MaxValue;
public FunctionInfo(T id, int minArgs, int maxArgs, XmlTypeCode[]? argTypes)
{
Debug.Assert(maxArgs == 0 || maxArgs == Infinity || argTypes != null && argTypes.Length == maxArgs);
this.id = id;
this.minArgs = minArgs;
this.maxArgs = maxArgs;
this.argTypes = argTypes;
}
public static void CheckArity(int minArgs, int maxArgs, string name, int numArgs)
{
if (minArgs <= numArgs && numArgs <= maxArgs)
{
return;
}
// Possible cases:
// [0, 0], [1, 1], [2, 2], [3, 3]
// [0, 1], [1, 2], [2, 3], [2, +inf]
// [1, 3], [2, 4]
string resId;
if (minArgs == maxArgs)
{
resId = SR.XPath_NArgsExpected;
}
else
{
if (maxArgs == minArgs + 1)
{
resId = SR.XPath_NOrMArgsExpected;
}
else if (numArgs < minArgs)
{
resId = SR.XPath_AtLeastNArgsExpected;
}
else
{
// This case is impossible for standard XPath/XSLT functions
Debug.Assert(numArgs > maxArgs);
resId = SR.XPath_AtMostMArgsExpected;
}
}
throw new XPathCompileException(resId, name, minArgs.ToString(CultureInfo.InvariantCulture), maxArgs.ToString(CultureInfo.InvariantCulture));
}
public void CastArguments(IList<QilNode> args, string name, XPathQilFactory f)
{
CheckArity(this.minArgs, this.maxArgs, name, args.Count);
// Convert arguments to the appropriate types
if (maxArgs == Infinity)
{
// Special case for concat() function
for (int i = 0; i < args.Count; i++)
{
args[i] = f.ConvertToType(XmlTypeCode.String, args[i]);
}
}
else
{
Debug.Assert(args.Count == 0 || argTypes != null);
for (int i = 0; i < args.Count; i++)
{
if (argTypes![i] == XmlTypeCode.Node && XPathQilFactory.CannotBeNodeSet(args[i]))
{
throw new XPathCompileException(SR.XPath_NodeSetArgumentExpected, name, (i + 1).ToString(CultureInfo.InvariantCulture));
}
args[i] = f.ConvertToType(argTypes[i], args[i]);
}
}
}
}
}
}
|