|
//------------------------------------------------------------------------------
// <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]);
}
}
}
}
}
}
|