1 //------------------------------------------------------------------------------
2 // <copyright file="XPathBuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.Xml.Schema;
12 using System.Xml.XPath;
13 using System.Xml.Xsl.Qil;
15 //#define StopMaskOptimisation
17 namespace System.Xml.Xsl.XPath {
18 using FunctionInfo = XPathBuilder.FunctionInfo<XPathBuilder.FuncId>;
19 using Res = System.Xml.Utils.Res;
20 using T = XmlQueryTypeFactory;
22 internal class XPathBuilder : IXPathBuilder<QilNode>, IXPathEnvironment {
23 private XPathQilFactory f;
24 private IXPathEnvironment environment;
25 private bool inTheBuild;
27 // Singleton nodes used as fixup markers
28 protected QilNode fixupCurrent, fixupPosition, fixupLast;
30 // Number of unresolved fixup nodes
31 protected int numFixupCurrent, numFixupPosition, numFixupLast;
32 private FixupVisitor fixupVisitor;
34 /* ----------------------------------------------------------------------------
35 IXPathEnvironment interface
37 QilNode IFocus.GetCurrent() { return GetCurrentNode (); }
38 QilNode IFocus.GetPosition() { return GetCurrentPosition(); }
39 QilNode IFocus.GetLast() { return GetLastPosition (); }
41 XPathQilFactory IXPathEnvironment.Factory { get { return f; } }
43 QilNode IXPathEnvironment.ResolveVariable(string prefix, string name) {
44 return Variable(prefix, name);
46 QilNode IXPathEnvironment.ResolveFunction(string prefix, string name, IList<QilNode> args, IFocus env) {
47 Debug.Fail("Must not be called");
50 string IXPathEnvironment.ResolvePrefix(string prefix) {
51 return environment.ResolvePrefix(prefix);
53 // ----------------------------------------------------------------------------
55 public XPathBuilder(IXPathEnvironment environment) {
56 this.environment = environment;
57 this.f = this.environment.Factory;
58 this.fixupCurrent = f.Unknown(T.NodeNotRtf);
59 this.fixupPosition = f.Unknown(T.DoubleX);
60 this.fixupLast = f.Unknown(T.DoubleX);
61 this.fixupVisitor = new FixupVisitor(f, fixupCurrent, fixupPosition, fixupLast);
64 public virtual void StartBuild() {
65 Debug.Assert(! inTheBuild, "XPathBuilder is busy!");
67 numFixupCurrent = numFixupPosition = numFixupLast = 0;
70 public virtual QilNode EndBuild(QilNode result) {
71 if (result == null) { // special door to clean builder state in exception handlers
75 Debug.Assert(inTheBuild, "StartBuild() wasn't called");
76 if (result.XmlType.MaybeMany && result.XmlType.IsNode && result.XmlType.IsNotRtf) {
77 result = f.DocOrderDistinct(result);
79 result = fixupVisitor.Fixup(result, /*environment:*/this.environment);
80 numFixupCurrent -= fixupVisitor.numCurrent ;
81 numFixupPosition -= fixupVisitor.numPosition;
82 numFixupLast -= fixupVisitor.numLast ;
84 // All these variables will be positive for "false() and (. = position() + last())"
85 // since QilPatternFactory eliminates the right operand of 'and'
86 Debug.Assert(numFixupCurrent >= 0, "Context fixup error");
87 Debug.Assert(numFixupPosition >= 0, "Context fixup error");
88 Debug.Assert(numFixupLast >= 0, "Context fixup error");
93 private QilNode GetCurrentNode () { numFixupCurrent ++; return fixupCurrent ; }
94 private QilNode GetCurrentPosition() { numFixupPosition ++; return fixupPosition; }
95 private QilNode GetLastPosition () { numFixupLast ++; return fixupLast ; }
97 public virtual QilNode String(string value) {
98 return f.String(value);
101 public virtual QilNode Number(double value) {
102 return f.Double(value);
105 public virtual QilNode Operator(XPathOperator op, QilNode left, QilNode right) {
106 Debug.Assert(op != XPathOperator.Unknown);
107 switch (OperatorGroup[(int)op]) {
108 case XPathOperatorGroup.Logical : return LogicalOperator (op, left, right);
109 case XPathOperatorGroup.Equality : return EqualityOperator (op, left, right);
110 case XPathOperatorGroup.Relational : return RelationalOperator(op, left, right);
111 case XPathOperatorGroup.Arithmetic : return ArithmeticOperator(op, left, right);
112 case XPathOperatorGroup.Negate : return NegateOperator (op, left, right);
113 case XPathOperatorGroup.Union : return UnionOperator (op, left, right);
115 Debug.Fail(op + " is not a valid XPathOperator");
120 QilNode LogicalOperator(XPathOperator op, QilNode left, QilNode right) {
121 Debug.Assert(op == XPathOperator.Or || op == XPathOperator.And);
122 left = f.ConvertToBoolean(left );
123 right = f.ConvertToBoolean(right);
125 op == XPathOperator.Or ? f.Or (left, right) :
126 /*default*/ f.And(left, right)
130 QilNode CompareValues(XPathOperator op, QilNode left, QilNode right, XmlTypeCode compType) {
131 Debug.Assert(compType == XmlTypeCode.Boolean || compType == XmlTypeCode.Double || compType == XmlTypeCode.String);
132 Debug.Assert(compType == XmlTypeCode.Boolean || left.XmlType.IsSingleton && right.XmlType.IsSingleton, "Both comparison operands must be singletons");
133 left = f.ConvertToType(compType, left );
134 right = f.ConvertToType(compType, right);
137 case XPathOperator.Eq : return f.Eq(left, right);
138 case XPathOperator.Ne : return f.Ne(left, right);
139 case XPathOperator.Lt : return f.Lt(left, right);
140 case XPathOperator.Le : return f.Le(left, right);
141 case XPathOperator.Gt : return f.Gt(left, right);
142 case XPathOperator.Ge : return f.Ge(left, right);
144 Debug.Fail("Wrong operator type");
149 QilNode CompareNodeSetAndValue(XPathOperator op, QilNode nodeset, QilNode val, XmlTypeCode compType) {
150 f.CheckNodeSet(nodeset);
151 Debug.Assert(val.XmlType.IsSingleton);
152 Debug.Assert(compType == XmlTypeCode.Boolean || compType == XmlTypeCode.Double || compType == XmlTypeCode.String, "I don't know what to do with RTF here");
153 if (compType == XmlTypeCode.Boolean || nodeset.XmlType.IsSingleton) {
154 return CompareValues(op, nodeset, val, compType);
156 QilIterator it = f.For(nodeset);
157 return f.Not(f.IsEmpty(f.Filter(it, CompareValues(op, f.XPathNodeValue(it), val, compType))));
161 // Inverts relational operator in order to swap operands of the comparison
162 static XPathOperator InvertOp(XPathOperator op) {
164 op == XPathOperator.Lt ? XPathOperator.Gt : // '<' --> '>'
165 op == XPathOperator.Le ? XPathOperator.Ge : // '<=' --> '>='
166 op == XPathOperator.Gt ? XPathOperator.Lt : // '>' --> '<'
167 op == XPathOperator.Ge ? XPathOperator.Le : // '>=' --> '<='
172 QilNode CompareNodeSetAndNodeSet(XPathOperator op, QilNode left, QilNode right, XmlTypeCode compType) {
173 f.CheckNodeSet(left);
174 f.CheckNodeSet(right);
175 if (right.XmlType.IsSingleton) {
176 return CompareNodeSetAndValue(op, /*nodeset:*/left, /*value:*/right, compType);
178 if (left.XmlType.IsSingleton) {
180 return CompareNodeSetAndValue(op, /*nodeset:*/right, /*value:*/left, compType);
182 QilIterator leftEnd = f.For(left );
183 QilIterator rightEnd = f.For(right);
184 return f.Not(f.IsEmpty(f.Loop(leftEnd, f.Filter(rightEnd, CompareValues(op, f.XPathNodeValue(leftEnd), f.XPathNodeValue(rightEnd), compType)))));
187 QilNode EqualityOperator(XPathOperator op, QilNode left, QilNode right) {
188 Debug.Assert(op == XPathOperator.Eq || op == XPathOperator.Ne);
189 XmlQueryType leftType = left.XmlType;
190 XmlQueryType rightType = right.XmlType;
192 if (f.IsAnyType(left) || f.IsAnyType(right)) {
193 return f.InvokeEqualityOperator(QilOperator[(int)op], left, right);
194 } else if (leftType.IsNode && rightType.IsNode) {
195 return CompareNodeSetAndNodeSet(op, left, right, XmlTypeCode.String);
196 } else if (leftType.IsNode) {
197 return CompareNodeSetAndValue(op, /*nodeset:*/left, /*val:*/right, rightType.TypeCode);
198 } else if (rightType.IsNode) {
199 return CompareNodeSetAndValue(op, /*nodeset:*/right, /*val:*/left, leftType.TypeCode);
201 XmlTypeCode compType = (
202 leftType.TypeCode == XmlTypeCode.Boolean || rightType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean :
203 leftType.TypeCode == XmlTypeCode.Double || rightType.TypeCode == XmlTypeCode.Double ? XmlTypeCode.Double :
204 /*default:*/ XmlTypeCode.String
206 return CompareValues(op, left, right, compType);
210 QilNode RelationalOperator(XPathOperator op, QilNode left, QilNode right) {
211 Debug.Assert(op == XPathOperator.Lt || op == XPathOperator.Le || op == XPathOperator.Gt || op == XPathOperator.Ge);
212 XmlQueryType leftType = left.XmlType;
213 XmlQueryType rightType = right.XmlType;
215 if (f.IsAnyType(left) || f.IsAnyType(right)) {
216 return f.InvokeRelationalOperator(QilOperator[(int)op], left, right);
217 } else if (leftType.IsNode && rightType.IsNode) {
218 return CompareNodeSetAndNodeSet(op, left, right, XmlTypeCode.Double);
219 } else if (leftType.IsNode) {
220 XmlTypeCode compType = rightType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean : XmlTypeCode.Double;
221 return CompareNodeSetAndValue(op, /*nodeset:*/left, /*val:*/right, compType);
222 } else if (rightType.IsNode) {
223 XmlTypeCode compType = leftType.TypeCode == XmlTypeCode.Boolean ? XmlTypeCode.Boolean : XmlTypeCode.Double;
225 return CompareNodeSetAndValue(op, /*nodeset:*/right, /*val:*/left, compType);
227 return CompareValues(op, left, right, XmlTypeCode.Double);
231 QilNode NegateOperator(XPathOperator op, QilNode left, QilNode right) {
232 Debug.Assert(op == XPathOperator.UnaryMinus);
233 Debug.Assert(right == null);
234 return f.Negate(f.ConvertToNumber(left));
237 QilNode ArithmeticOperator(XPathOperator op, QilNode left, QilNode right) {
238 left = f.ConvertToNumber(left );
239 right = f.ConvertToNumber(right);
241 case XPathOperator.Plus : return f.Add( left, right);
242 case XPathOperator.Minus : return f.Subtract(left, right);
243 case XPathOperator.Multiply : return f.Multiply(left, right);
244 case XPathOperator.Divide : return f.Divide( left, right);
245 case XPathOperator.Modulo : return f.Modulo( left, right);
247 Debug.Fail("Wrong operator type");
252 QilNode UnionOperator(XPathOperator op, QilNode left, QilNode right) {
253 Debug.Assert(op == XPathOperator.Union);
255 return f.EnsureNodeSet(right);
257 left = f.EnsureNodeSet(left );
258 right = f.EnsureNodeSet(right);
259 if (left.NodeType == QilNodeType.Sequence) {
260 // ToDo: drop this logic or move it to QilPatternFactory.Union()
261 ((QilList)left).Add(right);
264 return f.Union(left, right);
268 // also called by XPathPatternBuilder
269 public static XmlNodeKindFlags AxisTypeMask(XmlNodeKindFlags inputTypeMask, XPathNodeType nodeType, XPathAxis xpathAxis) {
270 return (XmlNodeKindFlags) (
271 (int) inputTypeMask &
272 (int) XPathNodeType2QilXmlNodeKind[(int) nodeType] & (int) XPathAxisMask[(int) xpathAxis]
276 QilNode BuildAxisFilter(QilNode qilAxis, XPathAxis xpathAxis, XPathNodeType nodeType, string name, string nsUri) {
277 XmlNodeKindFlags original = qilAxis.XmlType.NodeKinds;
278 XmlNodeKindFlags required = AxisTypeMask(original, nodeType, xpathAxis);
284 } else if (required == original) {
286 qilAxis = f.Filter(itr = f.For(qilAxis), f.IsType(itr, T.NodeChoice(required)));
287 qilAxis.XmlType = T.PrimeProduct(T.NodeChoice(required), qilAxis.XmlType.Cardinality);
290 // Without code bellow IlGeneragion gives stack overflow exception for the following passage.
291 //<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
292 // <xsl:template match="/">
293 // <xsl:value-of select="descendant::author/@id | comment()" />
297 // ToDo: remove this code when IlGen bug will be fixed.
298 if (qilAxis.NodeType == QilNodeType.Filter) {
299 QilLoop filter = (QilLoop) qilAxis;
300 filter.Body = f.And(filter.Body,
301 name != null && nsUri != null ? f.Eq(f.NameOf(itr), f.QName(name, nsUri)) : // ns:bar || bar
302 nsUri != null ? f.Eq(f.NamespaceUriOf(itr), f.String(nsUri)) : // ns:*
303 name != null ? f.Eq(f.LocalNameOf(itr), f.String(name)) : // *:foo
304 /*name == nsUri == null*/ f.True() // *
310 return f.Filter(itr = f.For(qilAxis),
311 name != null && nsUri != null ? f.Eq(f.NameOf(itr), f.QName(name, nsUri)) : // ns:bar || bar
312 nsUri != null ? f.Eq(f.NamespaceUriOf(itr), f.String(nsUri)) : // ns:*
313 name != null ? f.Eq(f.LocalNameOf(itr), f.String(name)) : // *:foo
314 /*name == nsUri == null*/ f.True() // *
318 // XmlNodeKindFlags from XPathNodeType
319 static XmlNodeKindFlags[] XPathNodeType2QilXmlNodeKind = {
320 /*Root */ XmlNodeKindFlags.Document,
321 /*Element */ XmlNodeKindFlags.Element,
322 /*Attribute */ XmlNodeKindFlags.Attribute,
323 /*Namespace */ XmlNodeKindFlags.Namespace,
324 /*Text */ XmlNodeKindFlags.Text,
325 /*SignificantWhitespace*/ XmlNodeKindFlags.Text,
326 /*Whitespace */ XmlNodeKindFlags.Text,
327 /*ProcessingInstruction*/ XmlNodeKindFlags.PI,
328 /*Comment */ XmlNodeKindFlags.Comment,
329 /*All */ XmlNodeKindFlags.Any
332 QilNode BuildAxis(XPathAxis xpathAxis, XPathNodeType nodeType, string nsUri, string name) {
333 QilNode currentNode = GetCurrentNode();
337 case XPathAxis.Ancestor : qilAxis = f.Ancestor (currentNode); break;
338 case XPathAxis.AncestorOrSelf : qilAxis = f.AncestorOrSelf (currentNode); break;
339 case XPathAxis.Attribute : qilAxis = f.Content (currentNode); break;
340 case XPathAxis.Child : qilAxis = f.Content (currentNode); break;
341 case XPathAxis.Descendant : qilAxis = f.Descendant (currentNode); break;
342 case XPathAxis.DescendantOrSelf : qilAxis = f.DescendantOrSelf (currentNode); break;
343 case XPathAxis.Following : qilAxis = f.XPathFollowing (currentNode); break;
344 case XPathAxis.FollowingSibling : qilAxis = f.FollowingSibling (currentNode); break;
345 case XPathAxis.Namespace : qilAxis = f.XPathNamespace (currentNode); break;
346 case XPathAxis.Parent : qilAxis = f.Parent (currentNode); break;
347 case XPathAxis.Preceding : qilAxis = f.XPathPreceding (currentNode); break;
348 case XPathAxis.PrecedingSibling : qilAxis = f.PrecedingSibling (currentNode); break;
349 case XPathAxis.Self : qilAxis = (currentNode); break;
350 // Can be done using BuildAxisFilter() but f.Root() sets wrong XmlNodeKindFlags
351 case XPathAxis.Root : return f.Root (currentNode);
354 Debug.Fail("Invalid EnumValue 'XPathAxis'");
358 QilNode result = BuildAxisFilter(qilAxis, xpathAxis, nodeType, name, nsUri);
360 xpathAxis == XPathAxis.Ancestor || xpathAxis == XPathAxis.Preceding ||
361 xpathAxis == XPathAxis.AncestorOrSelf || xpathAxis == XPathAxis.PrecedingSibling
363 result = f.BaseFactory.DocOrderDistinct(result);
364 // To make grouping operator NOP we should always return path expressions in DOD.
365 // I can't use Pattern factory here becasue Predicate() depends on fact that DOD() is
366 // outmost node in reverse steps
371 public virtual QilNode Axis(XPathAxis xpathAxis, XPathNodeType nodeType, string prefix, string name) {
372 string nsUri = prefix == null ? null : this.environment.ResolvePrefix(prefix);
373 return BuildAxis(xpathAxis, nodeType, nsUri, name);
377 public virtual QilNode JoinStep(QilNode left, QilNode right) {
378 f.CheckNodeSet(right);
379 QilIterator leftIt = f.For(f.EnsureNodeSet(left));
380 // in XPath 1.0 step is always nodetest and as a result it can't contain last().
381 right = fixupVisitor.Fixup(right, /*current:*/leftIt, /*last:*/null);
382 numFixupCurrent -= fixupVisitor.numCurrent ;
383 numFixupPosition -= fixupVisitor.numPosition;
384 numFixupLast -= fixupVisitor.numLast ;
385 return f.DocOrderDistinct(f.Loop(leftIt, right));
388 // "nodeset[predicate]"
389 // XPath spec $3.3 (para 5)
390 public virtual QilNode Predicate(QilNode nodeset, QilNode predicate, bool isReverseStep) {
392 Debug.Assert(nodeset.NodeType == QilNodeType.DocOrderDistinct,
393 "ReverseAxe in Qil is actuly reverse and we compile them here in builder by wrapping to DocOrderDistinct()"
395 // The trick here is that we unwarp it back, compile as regular predicate and wrap again.
396 // this way this wat we hold invariant that path expresion are always DOD and make predicates on reverse axe
397 // work as specified in XPath 2.0 FS: http://www.w3.org/TR/xquery-semantics/#id-axis-steps
398 nodeset = ((QilUnary)nodeset).Child;
401 predicate = PredicateToBoolean(predicate, f, this);
403 return BuildOnePredicate(nodeset, predicate, isReverseStep, f, fixupVisitor, ref numFixupCurrent, ref numFixupPosition, ref numFixupLast);
406 //also used by XPathPatternBuilder
407 public static QilNode PredicateToBoolean(QilNode predicate, XPathQilFactory f, IXPathEnvironment env) {
408 // Prepocess predicate: if (predicate is number) then predicate := (position() == predicate)
409 if (!f.IsAnyType(predicate)) {
410 if (predicate.XmlType.TypeCode == XmlTypeCode.Double) {
411 predicate = f.Eq(env.GetPosition(), predicate);
413 predicate = f.ConvertToBoolean(predicate);
417 predicate = f.Loop(i = f.Let(predicate),
418 f.Conditional(f.IsType(i, T.Double),
419 f.Eq(env.GetPosition(), f.TypeAssert(i, T.DoubleX)),
420 f.ConvertToBoolean(i)
427 //also used by XPathPatternBuilder
428 public static QilNode BuildOnePredicate(QilNode nodeset, QilNode predicate, bool isReverseStep,
429 XPathQilFactory f, FixupVisitor fixupVisitor,
430 ref int numFixupCurrent, ref int numFixupPosition, ref int numFixupLast) {
431 nodeset = f.EnsureNodeSet(nodeset);
433 // Mirgeing nodeset and predicate:
434 // 1. Predicate contains 0 last() :
438 // ToDo: Currently we are keepeing old output to minimize diff.
439 // 2. Predicate contains 1 last()
440 // let $cach := nodeset return
442 // where predicate(length($cach))
444 // ToDo: This is a little optimisation we can do or don't do.
445 // 3. Predicate contains 2+ last()
446 // let $cash := nodeset return
447 // let $size := length($cash) return
449 // where predicate($size)
453 if (numFixupLast != 0 && fixupVisitor.CountUnfixedLast(predicate) != 0) {
454 // this subtree has unfixed last() nodes
455 QilIterator cash = f.Let(nodeset);
456 QilIterator size = f.Let(f.XsltConvert(f.Length(cash), T.DoubleX));
457 QilIterator it = f.For(cash);
458 predicate = fixupVisitor.Fixup(predicate, /*current:*/it, /*last:*/size);
459 numFixupCurrent -= fixupVisitor.numCurrent;
460 numFixupPosition -= fixupVisitor.numPosition;
461 numFixupLast -= fixupVisitor.numLast;
462 result = f.Loop(cash, f.Loop(size, f.Filter(it, predicate)));
464 QilIterator it = f.For(nodeset);
465 predicate = fixupVisitor.Fixup(predicate, /*current:*/it, /*last:*/null);
466 numFixupCurrent -= fixupVisitor.numCurrent;
467 numFixupPosition -= fixupVisitor.numPosition;
468 numFixupLast -= fixupVisitor.numLast;
469 result = f.Filter(it, predicate);
472 result = f.DocOrderDistinct(result);
477 public virtual QilNode Variable(string prefix, string name) {
478 return this.environment.ResolveVariable(prefix, name);
481 public virtual QilNode Function(string prefix, string name, IList<QilNode> args) {
482 Debug.Assert(!args.IsReadOnly, "Writable collection expected");
483 if (prefix.Length == 0) {
485 if (FunctionTable.TryGetValue(name, out func)) {
486 func.CastArguments(args, name, f);
489 case FuncId.Not : return f.Not(args[0]);
490 case FuncId.Last : return GetLastPosition();
491 case FuncId.Position : return GetCurrentPosition();
492 case FuncId.Count : return f.XsltConvert(f.Length(f.DocOrderDistinct(args[0])), T.DoubleX);
493 case FuncId.LocalName : return args.Count == 0 ? f.LocalNameOf(GetCurrentNode()) : LocalNameOfFirstNode(args[0]);
494 case FuncId.NamespaceUri : return args.Count == 0 ? f.NamespaceUriOf(GetCurrentNode()) : NamespaceOfFirstNode(args[0]);
495 case FuncId.Name : return args.Count == 0 ? NameOf(GetCurrentNode()) : NameOfFirstNode(args[0]);
496 case FuncId.String : return args.Count == 0 ? f.XPathNodeValue(GetCurrentNode()) : f.ConvertToString(args[0]);
497 case FuncId.Number : return args.Count == 0 ? f.XsltConvert(f.XPathNodeValue(GetCurrentNode()), T.DoubleX) : f.ConvertToNumber(args[0]);
498 case FuncId.Boolean : return f.ConvertToBoolean(args[0]);
499 case FuncId.True : return f.True();
500 case FuncId.False : return f.False();
501 case FuncId.Id : return f.DocOrderDistinct(f.Id(GetCurrentNode(), args[0]));
502 case FuncId.Concat : return f.StrConcat(args);
503 case FuncId.StartsWith : return f.InvokeStartsWith(args[0], args[1]);
504 case FuncId.Contains : return f.InvokeContains(args[0], args[1]);
505 case FuncId.SubstringBefore : return f.InvokeSubstringBefore(args[0], args[1]);
506 case FuncId.SubstringAfter : return f.InvokeSubstringAfter(args[0], args[1]);
507 case FuncId.Substring :
508 return args.Count == 2 ? f.InvokeSubstring(args[0], args[1]) : f.InvokeSubstring(args[0], args[1], args[2]);
509 case FuncId.StringLength :
510 return f.XsltConvert(f.StrLength(args.Count == 0 ? f.XPathNodeValue(GetCurrentNode()) : args[0]), T.DoubleX);
511 case FuncId.Normalize :
512 return f.InvokeNormalizeSpace(args.Count == 0 ? f.XPathNodeValue(GetCurrentNode()) : args[0]);
513 case FuncId.Translate : return f.InvokeTranslate(args[0], args[1], args[2]);
514 case FuncId.Lang : return f.InvokeLang(args[0], GetCurrentNode());
515 case FuncId.Sum : return Sum(f.DocOrderDistinct(args[0]));
516 case FuncId.Floor : return f.InvokeFloor(args[0]);
517 case FuncId.Ceiling : return f.InvokeCeiling(args[0]);
518 case FuncId.Round : return f.InvokeRound(args[0]);
520 Debug.Fail(func.id + " is present in the function table, but absent from the switch");
526 return this.environment.ResolveFunction(prefix, name, args, (IFocus)this);
529 QilNode LocalNameOfFirstNode(QilNode arg) {
531 if (arg.XmlType.IsSingleton) {
532 return f.LocalNameOf(arg);
535 return f.StrConcat(f.Loop(i = f.FirstNode(arg), f.LocalNameOf(i)));
539 QilNode NamespaceOfFirstNode(QilNode arg) {
541 if (arg.XmlType.IsSingleton) {
542 return f.NamespaceUriOf(arg);
545 return f.StrConcat(f.Loop(i = f.FirstNode(arg), f.NamespaceUriOf(i)));
549 QilNode NameOf(QilNode arg) {
550 f.CheckNodeNotRtf(arg);
551 // ToDo: NameOf QIL node returns QName, so we cannot use it here.
552 // We may want to introduce a new QIL node that returns a string.
553 if (arg is QilIterator) {
555 return f.Loop(p = f.Let(f.PrefixOf(arg)), f.Loop(ln = f.Let(f.LocalNameOf(arg)),
556 f.Conditional(f.Eq(f.StrLength(p), f.Int32(0)), ln, f.StrConcat(p, f.String(":"), ln)
559 QilIterator let = f.Let(arg);
560 return f.Loop(let, /*recursion:*/NameOf(let));
564 QilNode NameOfFirstNode(QilNode arg) {
566 if (arg.XmlType.IsSingleton) {
570 return f.StrConcat(f.Loop(i = f.FirstNode(arg), NameOf(i)));
574 QilNode Sum(QilNode arg) {
577 return f.Sum(f.Sequence(f.Double(0d), f.Loop(i = f.For(arg), f.ConvertToNumber(i))));
580 enum XPathOperatorGroup {
590 static XPathOperatorGroup[] OperatorGroup = {
591 /*Unknown */ XPathOperatorGroup.Unknown ,
592 /*Or */ XPathOperatorGroup.Logical ,
593 /*And */ XPathOperatorGroup.Logical ,
594 /*Eq */ XPathOperatorGroup.Equality ,
595 /*Ne */ XPathOperatorGroup.Equality ,
596 /*Lt */ XPathOperatorGroup.Relational,
597 /*Le */ XPathOperatorGroup.Relational,
598 /*Gt */ XPathOperatorGroup.Relational,
599 /*Ge */ XPathOperatorGroup.Relational,
600 /*Plus */ XPathOperatorGroup.Arithmetic,
601 /*Minus */ XPathOperatorGroup.Arithmetic,
602 /*Multiply */ XPathOperatorGroup.Arithmetic,
603 /*Divide */ XPathOperatorGroup.Arithmetic,
604 /*Modulo */ XPathOperatorGroup.Arithmetic,
605 /*UnaryMinus*/ XPathOperatorGroup.Negate ,
606 /*Union */ XPathOperatorGroup.Union ,
609 static QilNodeType[] QilOperator = {
610 /*Unknown */ QilNodeType.Unknown ,
611 /*Or */ QilNodeType.Or ,
612 /*And */ QilNodeType.And ,
613 /*Eq */ QilNodeType.Eq ,
614 /*Ne */ QilNodeType.Ne ,
615 /*Lt */ QilNodeType.Lt ,
616 /*Le */ QilNodeType.Le ,
617 /*Gt */ QilNodeType.Gt ,
618 /*Ge */ QilNodeType.Ge ,
619 /*Plus */ QilNodeType.Add ,
620 /*Minus */ QilNodeType.Subtract,
621 /*Multiply */ QilNodeType.Multiply,
622 /*Divide */ QilNodeType.Divide ,
623 /*Modulo */ QilNodeType.Modulo ,
624 /*UnaryMinus */ QilNodeType.Negate ,
625 /*Union */ QilNodeType.Sequence,
628 // XmlNodeType(s) of nodes by XPathAxis
629 static XmlNodeKindFlags[] XPathAxisMask = {
630 /*Unknown */ XmlNodeKindFlags.None,
631 /*Ancestor */ XmlNodeKindFlags.Element | XmlNodeKindFlags.Document,
632 /*AncestorOrSelf */ XmlNodeKindFlags.Any,
633 /*Attribute */ XmlNodeKindFlags.Attribute,
634 /*Child */ XmlNodeKindFlags.Content,
635 /*Descendant */ XmlNodeKindFlags.Content,
636 /*DescendantOrSelf*/ XmlNodeKindFlags.Any,
637 /*Following */ XmlNodeKindFlags.Content,
638 /*FollowingSibling*/ XmlNodeKindFlags.Content,
639 /*Namespace */ XmlNodeKindFlags.Namespace,
640 /*Parent */ XmlNodeKindFlags.Element | XmlNodeKindFlags.Document,
641 /*Preceding */ XmlNodeKindFlags.Content,
642 /*PrecedingSibling*/ XmlNodeKindFlags.Content,
643 /*Self */ XmlNodeKindFlags.Any,
644 /*Root */ XmlNodeKindFlags.Document,
647 // ----------------------------------------------------------------
648 internal enum FuncId {
678 public static readonly XmlTypeCode[] argAny = {XmlTypeCode.Item};
679 public static readonly XmlTypeCode[] argNodeSet = {XmlTypeCode.Node};
680 public static readonly XmlTypeCode[] argBoolean = {XmlTypeCode.Boolean};
681 public static readonly XmlTypeCode[] argDouble = {XmlTypeCode.Double};
682 public static readonly XmlTypeCode[] argString = {XmlTypeCode.String};
683 public static readonly XmlTypeCode[] argString2 = {XmlTypeCode.String, XmlTypeCode.String};
684 public static readonly XmlTypeCode[] argString3 = {XmlTypeCode.String, XmlTypeCode.String, XmlTypeCode.String};
685 public static readonly XmlTypeCode[] argFnSubstr = {XmlTypeCode.String, XmlTypeCode.Double, XmlTypeCode.Double};
687 public static Dictionary<string, FunctionInfo> FunctionTable = CreateFunctionTable();
688 private static Dictionary<string, FunctionInfo> CreateFunctionTable() {
689 Dictionary<string, FunctionInfo> table = new Dictionary<string, FunctionInfo>(36);
690 table.Add("last" , new FunctionInfo(FuncId.Last , 0, 0, null));
691 table.Add("position" , new FunctionInfo(FuncId.Position , 0, 0, null));
692 table.Add("name" , new FunctionInfo(FuncId.Name , 0, 1, argNodeSet));
693 table.Add("namespace-uri" , new FunctionInfo(FuncId.NamespaceUri , 0, 1, argNodeSet));
694 table.Add("local-name" , new FunctionInfo(FuncId.LocalName , 0, 1, argNodeSet));
695 table.Add("count" , new FunctionInfo(FuncId.Count , 1, 1, argNodeSet));
696 table.Add("id" , new FunctionInfo(FuncId.Id , 1, 1, argAny));
697 table.Add("string" , new FunctionInfo(FuncId.String , 0, 1, argAny));
698 table.Add("concat" , new FunctionInfo(FuncId.Concat , 2, FunctionInfo.Infinity, null));
699 table.Add("starts-with" , new FunctionInfo(FuncId.StartsWith , 2, 2, argString2));
700 table.Add("contains" , new FunctionInfo(FuncId.Contains , 2, 2, argString2));
701 table.Add("substring-before" , new FunctionInfo(FuncId.SubstringBefore, 2, 2, argString2));
702 table.Add("substring-after" , new FunctionInfo(FuncId.SubstringAfter , 2, 2, argString2));
703 table.Add("substring" , new FunctionInfo(FuncId.Substring , 2, 3, argFnSubstr));
704 table.Add("string-length" , new FunctionInfo(FuncId.StringLength , 0, 1, argString));
705 table.Add("normalize-space" , new FunctionInfo(FuncId.Normalize , 0, 1, argString));
706 table.Add("translate" , new FunctionInfo(FuncId.Translate , 3, 3, argString3));
707 table.Add("boolean" , new FunctionInfo(FuncId.Boolean , 1, 1, argAny));
708 table.Add("not" , new FunctionInfo(FuncId.Not , 1, 1, argBoolean));
709 table.Add("true" , new FunctionInfo(FuncId.True , 0, 0, null));
710 table.Add("false" , new FunctionInfo(FuncId.False , 0, 0, null));
711 table.Add("lang" , new FunctionInfo(FuncId.Lang , 1, 1, argString));
712 table.Add("number" , new FunctionInfo(FuncId.Number , 0, 1, argAny));
713 table.Add("sum" , new FunctionInfo(FuncId.Sum , 1, 1, argNodeSet));
714 table.Add("floor" , new FunctionInfo(FuncId.Floor , 1, 1, argDouble));
715 table.Add("ceiling" , new FunctionInfo(FuncId.Ceiling , 1, 1, argDouble));
716 table.Add("round" , new FunctionInfo(FuncId.Round , 1, 1, argDouble));
720 public static bool IsFunctionAvailable(string localName, string nsUri) {
721 if (nsUri.Length != 0) {
724 return FunctionTable.ContainsKey(localName);
727 internal class FixupVisitor : QilReplaceVisitor {
728 new QilPatternFactory f;
729 QilNode fixupCurrent, fixupPosition, fixupLast; // fixup nodes we are replacing
731 QilNode last; // expressions we are using to replace fixupNodes
732 bool justCount; // Don't change tree, just count
733 IXPathEnvironment environment; // temp solution
734 public int numCurrent, numPosition, numLast; // here we are counting all replacements we have made
736 public FixupVisitor(QilPatternFactory f, QilNode fixupCurrent, QilNode fixupPosition, QilNode fixupLast) : base(f.BaseFactory) {
738 this.fixupCurrent = fixupCurrent;
739 this.fixupPosition = fixupPosition;
740 this.fixupLast = fixupLast ;
743 public QilNode Fixup(QilNode inExpr, QilIterator current, QilNode last) {
744 QilDepthChecker.Check(inExpr);
745 this.current = current ;
747 Debug.Assert(current != null);
748 this.justCount = false;
749 this.environment = null;
750 numCurrent = numPosition = numLast = 0;
751 inExpr = VisitAssumeReference(inExpr);
752 #if StopMaskOptimisation
753 SetStopVisitMark(inExpr, /*stop*/true);
758 public QilNode Fixup(QilNode inExpr, IXPathEnvironment environment) {
759 Debug.Assert(environment != null);
760 QilDepthChecker.Check(inExpr);
761 this.justCount = false;
763 this.environment = environment;
764 numCurrent = numPosition = numLast = 0;
765 inExpr = VisitAssumeReference(inExpr);
766 #if StopMaskOptimisation
768 //SetStopVisitMark(inExpr, /*stop*/true);
773 public int CountUnfixedLast(QilNode inExpr) {
774 this.justCount = true;
775 numCurrent = numPosition = numLast = 0;
776 VisitAssumeReference(inExpr);
780 protected override QilNode VisitUnknown(QilNode unknown) {
781 Debug.Assert(unknown.NodeType == QilNodeType.Unknown);
782 if (unknown == fixupCurrent) {
785 if (this.environment != null) {
786 unknown = this.environment.GetCurrent();
787 } else if (this.current != null) {
788 unknown = this.current;
791 } else if (unknown == fixupPosition) {
794 if (this.environment != null) {
795 unknown = this.environment.GetPosition();
796 } else if (this.current != null) {
797 // position can be in predicate only and in predicate current olways an iterator
798 unknown = f.XsltConvert(f.PositionOf((QilIterator)this.current), T.DoubleX);
801 } else if (unknown == fixupLast) {
804 if (this.environment != null) {
805 unknown = this.environment.GetLast();
806 } else if (this.current != null) {
807 Debug.Assert(this.last != null);
812 Debug.Assert(unknown != null);
816 #if StopMaskOptimisation
817 // This optimisation marks subtrees that was fixed already and prevents FixupVisitor from
818 // visiting them again. The logic is brokken, because when unfixed tree is added inside fixed one
819 // it never fixed anymore.
820 // This happens in all cortasian productions now.
821 // Excample "a/b=c". 'c' is added inside 'b'
823 // I belive some optimisation is posible and would be nice to have.
824 // We may change the way we generating cortasian product.
826 protected override QilNode Visit(QilNode n) {
827 if (GetStopVisitMark(n)) {
829 // This subtree was fixed already. No need to go inside it.
831 SetStopVisitMark(n, /*stop*/false); // We clean this annotation
835 return base.Visit(n);
838 void SetStopVisitMark(QilNode n, bool stop) {
839 if (n.Type != QilNodeType.For && n.Type != QilNodeType.Let) {
840 XsltAnnotation.Write(n)[0] = (stop ? /*any object*/fixupCurrent : null);
842 // we shouldn't alter annotation of "reference" nodes (Iterators, Functions, ...)
845 bool GetStopVisitMark(QilNode n) {
846 return XsltAnnotation.Write(n)[0] != null;
851 internal class FunctionInfo<T> {
855 public XmlTypeCode[] argTypes;
857 public const int Infinity = int.MaxValue;
859 public FunctionInfo(T id, int minArgs, int maxArgs, XmlTypeCode[] argTypes) {
860 Debug.Assert(maxArgs == 0 || maxArgs == Infinity || argTypes != null && argTypes.Length == maxArgs);
862 this.minArgs = minArgs;
863 this.maxArgs = maxArgs;
864 this.argTypes = argTypes;
867 public static void CheckArity(int minArgs, int maxArgs, string name, int numArgs) {
868 if (minArgs <= numArgs && numArgs <= maxArgs) {
873 // [0, 0], [1, 1], [2, 2], [3, 3]
874 // [0, 1], [1, 2], [2, 3], [2, +inf]
877 if (minArgs == maxArgs) {
878 resId = Res.XPath_NArgsExpected;
880 if (maxArgs == minArgs + 1) {
881 resId = Res.XPath_NOrMArgsExpected;
882 } else if (numArgs < minArgs) {
883 resId = Res.XPath_AtLeastNArgsExpected;
885 // This case is impossible for standard XPath/XSLT functions
886 Debug.Assert(numArgs > maxArgs);
887 resId = Res.XPath_AtMostMArgsExpected;
890 throw new XPathCompileException(resId, name, minArgs.ToString(CultureInfo.InvariantCulture), maxArgs.ToString(CultureInfo.InvariantCulture));
893 public void CastArguments(IList<QilNode> args, string name, XPathQilFactory f) {
894 CheckArity(this.minArgs, this.maxArgs, name, args.Count);
896 // Convert arguments to the appropriate types
897 if (maxArgs == Infinity) {
898 // Special case for concat() function
899 for (int i = 0; i < args.Count; i++) {
900 args[i] = f.ConvertToType(XmlTypeCode.String, args[i]);
903 for (int i = 0; i < args.Count; i++) {
904 if (argTypes[i] == XmlTypeCode.Node && f.CannotBeNodeSet(args[i])) {
905 throw new XPathCompileException(Res.XPath_NodeSetArgumentExpected, name, (i + 1).ToString(CultureInfo.InvariantCulture));
907 args[i] = f.ConvertToType(argTypes[i], args[i]);