536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
914 lines
46 KiB
C#
914 lines
46 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="XPathBuilder.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">Microsoft</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Xml.Schema;
|
|
using System.Xml.XPath;
|
|
using System.Xml.Xsl.Qil;
|
|
|
|
//#define StopMaskOptimisation
|
|
|
|
namespace System.Xml.Xsl.XPath {
|
|
using FunctionInfo = XPathBuilder.FunctionInfo<XPathBuilder.FuncId>;
|
|
using Res = System.Xml.Utils.Res;
|
|
using T = XmlQueryTypeFactory;
|
|
|
|
internal class XPathBuilder : IXPathBuilder<QilNode>, IXPathEnvironment {
|
|
private XPathQilFactory f;
|
|
private 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 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) {
|
|
this.environment = environment;
|
|
this.f = this.environment.Factory;
|
|
this.fixupCurrent = f.Unknown(T.NodeNotRtf);
|
|
this.fixupPosition = f.Unknown(T.DoubleX);
|
|
this.fixupLast = f.Unknown(T.DoubleX);
|
|
this.fixupVisitor = new FixupVisitor(f, fixupCurrent, fixupPosition, fixupLast);
|
|
}
|
|
|
|
public virtual void StartBuild() {
|
|
Debug.Assert(! inTheBuild, "XPathBuilder is busy!");
|
|
inTheBuild = true;
|
|
numFixupCurrent = numFixupPosition = numFixupLast = 0;
|
|
}
|
|
|
|
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:*/this.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);
|
|
switch (OperatorGroup[(int)op]) {
|
|
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, right);
|
|
case XPathOperatorGroup.Union : return UnionOperator (op, left, right);
|
|
default:
|
|
Debug.Fail(op + " is not a valid XPathOperator");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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)
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
QilNode CompareNodeSetAndValue(XPathOperator op, QilNode nodeset, QilNode val, XmlTypeCode compType) {
|
|
f.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
|
|
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
|
|
);
|
|
}
|
|
|
|
QilNode CompareNodeSetAndNodeSet(XPathOperator op, QilNode left, QilNode right, XmlTypeCode compType) {
|
|
f.CheckNodeSet(left);
|
|
f.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)))));
|
|
}
|
|
|
|
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 (f.IsAnyType(left) || f.IsAnyType(right)) {
|
|
return f.InvokeEqualityOperator(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);
|
|
}
|
|
}
|
|
|
|
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 (f.IsAnyType(left) || f.IsAnyType(right)) {
|
|
return f.InvokeRelationalOperator(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);
|
|
}
|
|
}
|
|
|
|
QilNode NegateOperator(XPathOperator op, QilNode left, QilNode right) {
|
|
Debug.Assert(op == XPathOperator.UnaryMinus);
|
|
Debug.Assert(right == null);
|
|
return f.Negate(f.ConvertToNumber(left));
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
// ToDo: drop this logic or move it to QilPatternFactory.Union()
|
|
((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) XPathNodeType2QilXmlNodeKind[(int) nodeType] & (int) XPathAxisMask[(int) xpathAxis]
|
|
);
|
|
}
|
|
|
|
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>
|
|
|
|
// ToDo: remove this code when IlGen bug will be fixed.
|
|
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
|
|
static XmlNodeKindFlags[] 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
|
|
};
|
|
|
|
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 becasue 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 : this.environment.ResolvePrefix(prefix);
|
|
return BuildAxis(xpathAxis, nodeType, nsUri, name);
|
|
}
|
|
|
|
// "left/right"
|
|
public virtual QilNode JoinStep(QilNode left, QilNode right) {
|
|
f.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 actuly 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 expresion 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 (!f.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
|
|
// ToDo: 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
|
|
// ToDo: 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 this.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 this.environment.ResolveFunction(prefix, name, args, (IFocus)this);
|
|
}
|
|
|
|
QilNode LocalNameOfFirstNode(QilNode arg) {
|
|
f.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)));
|
|
}
|
|
}
|
|
|
|
QilNode NamespaceOfFirstNode(QilNode arg) {
|
|
f.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)));
|
|
}
|
|
}
|
|
|
|
QilNode NameOf(QilNode arg) {
|
|
f.CheckNodeNotRtf(arg);
|
|
// ToDo: NameOf QIL node returns QName, so we cannot use it here.
|
|
// 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));
|
|
}
|
|
}
|
|
|
|
QilNode NameOfFirstNode(QilNode arg) {
|
|
f.CheckNodeSet(arg);
|
|
if (arg.XmlType.IsSingleton) {
|
|
return NameOf(arg);
|
|
} else {
|
|
QilIterator i;
|
|
return f.StrConcat(f.Loop(i = f.FirstNode(arg), NameOf(i)));
|
|
}
|
|
}
|
|
|
|
QilNode Sum(QilNode arg) {
|
|
f.CheckNodeSet(arg);
|
|
QilIterator i;
|
|
return f.Sum(f.Sequence(f.Double(0d), f.Loop(i = f.For(arg), f.ConvertToNumber(i))));
|
|
}
|
|
|
|
enum XPathOperatorGroup {
|
|
Unknown ,
|
|
Logical ,
|
|
Equality ,
|
|
Relational,
|
|
Arithmetic,
|
|
Negate ,
|
|
Union ,
|
|
}
|
|
|
|
static XPathOperatorGroup[] 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 ,
|
|
};
|
|
|
|
static QilNodeType[] 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
|
|
static XmlNodeKindFlags[] 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 class FixupVisitor : QilReplaceVisitor {
|
|
new QilPatternFactory f;
|
|
QilNode fixupCurrent, fixupPosition, fixupLast; // fixup nodes we are replacing
|
|
QilIterator current;
|
|
QilNode last; // expressions we are using to replace fixupNodes
|
|
bool justCount; // Don't change tree, just count
|
|
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;
|
|
this.fixupCurrent = fixupCurrent;
|
|
this.fixupPosition = fixupPosition;
|
|
this.fixupLast = fixupLast ;
|
|
}
|
|
|
|
public QilNode Fixup(QilNode inExpr, QilIterator current, QilNode last) {
|
|
QilDepthChecker.Check(inExpr);
|
|
this.current = current ;
|
|
this.last = last ;
|
|
Debug.Assert(current != null);
|
|
this.justCount = false;
|
|
this.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);
|
|
this.justCount = false;
|
|
this.current = null;
|
|
this.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) {
|
|
this.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 (this.environment != null) {
|
|
unknown = this.environment.GetCurrent();
|
|
} else if (this.current != null) {
|
|
unknown = this.current;
|
|
}
|
|
}
|
|
} else if (unknown == fixupPosition) {
|
|
numPosition ++;
|
|
if (! justCount) {
|
|
if (this.environment != null) {
|
|
unknown = this.environment.GetPosition();
|
|
} else if (this.current != null) {
|
|
// position can be in predicate only and in predicate current olways an iterator
|
|
unknown = f.XsltConvert(f.PositionOf((QilIterator)this.current), T.DoubleX);
|
|
}
|
|
}
|
|
} else if (unknown == fixupLast) {
|
|
numLast ++;
|
|
if (! justCount) {
|
|
if (this.environment != null) {
|
|
unknown = this.environment.GetLast();
|
|
} else if (this.current != null) {
|
|
Debug.Assert(this.last != null);
|
|
unknown = this.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 posible 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 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 = Res.XPath_NArgsExpected;
|
|
} else {
|
|
if (maxArgs == minArgs + 1) {
|
|
resId = Res.XPath_NOrMArgsExpected;
|
|
} else if (numArgs < minArgs) {
|
|
resId = Res.XPath_AtLeastNArgsExpected;
|
|
} else {
|
|
// This case is impossible for standard XPath/XSLT functions
|
|
Debug.Assert(numArgs > maxArgs);
|
|
resId = Res.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 {
|
|
for (int i = 0; i < args.Count; i++) {
|
|
if (argTypes[i] == XmlTypeCode.Node && f.CannotBeNodeSet(args[i])) {
|
|
throw new XPathCompileException(Res.XPath_NodeSetArgumentExpected, name, (i + 1).ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
args[i] = f.ConvertToType(argTypes[i], args[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|