File: XPathPatternBuilder.cs

package info (click to toggle)
mono 6.14.1%2Bds2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,282,732 kB
  • sloc: cs: 11,182,461; xml: 2,850,281; ansic: 699,123; cpp: 122,919; perl: 58,604; javascript: 30,841; asm: 21,845; makefile: 19,602; sh: 10,973; python: 4,772; pascal: 925; sql: 859; sed: 16; php: 1
file content (403 lines) | stat: -rw-r--r-- 19,298 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//------------------------------------------------------------------------------
// <copyright file="XPathPatternBuilder.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Xml.Xsl.Qil;
using System.Xml.Xsl.XPath;

namespace System.Xml.Xsl.Xslt {
    using T = XmlQueryTypeFactory;

    internal class XPathPatternBuilder : XPathPatternParser.IPatternBuilder {
        private XPathPredicateEnvironment predicateEnvironment;
        private XPathBuilder              predicateBuilder;
        private bool                      inTheBuild;
        private XPathQilFactory           f;
        private QilNode                   fixupNode;
        private IXPathEnvironment         environment;

        public XPathPatternBuilder(IXPathEnvironment environment) {
            Debug.Assert(environment != null);
            this.environment = environment;
            this.f           = environment.Factory;
            this.predicateEnvironment = new XPathPredicateEnvironment(environment);
            this.predicateBuilder     = new XPathBuilder(predicateEnvironment);

            this.fixupNode = f.Unknown(T.NodeNotRtfS);
        }

        public QilNode FixupNode {
            get { return fixupNode; }
        }

        public virtual void StartBuild() {
            Debug.Assert(! inTheBuild, "XPathBuilder is buisy!");
            inTheBuild = true;
            return;
        }

        [Conditional("DEBUG")]
        public void AssertFilter(QilLoop filter) {
            Debug.Assert(filter.NodeType == QilNodeType.Filter, "XPathPatternBuilder expected to generate list of Filters on top level");
            Debug.Assert(filter.Variable.XmlType.IsSubtypeOf(T.NodeNotRtf));
            Debug.Assert(filter.Variable.Binding.NodeType == QilNodeType.Unknown);  // fixupNode
            Debug.Assert(filter.Body.XmlType.IsSubtypeOf(T.Boolean));
        }

        private void FixupFilterBinding(QilLoop filter, QilNode newBinding) {
            AssertFilter(filter);
            filter.Variable.Binding = newBinding;
        }

        public virtual QilNode EndBuild(QilNode result) {
            Debug.Assert(inTheBuild, "StartBuild() wasn't called");
            if (result == null) {
                // Special door to clean builder state in exception handlers
            }

            // All these variables will be positive for "false() and (. = position() + last())"
            // since QilPatternFactory eliminates the right operand of 'and'
            Debug.Assert(predicateEnvironment.numFixupCurrent >= 0, "Context fixup error");
            Debug.Assert(predicateEnvironment.numFixupPosition >= 0, "Context fixup error");
            Debug.Assert(predicateEnvironment.numFixupLast >= 0, "Context fixup error");
            inTheBuild = false;
            return result;
        }

        public QilNode Operator(XPathOperator op, QilNode left, QilNode right) {
            Debug.Assert(op == XPathOperator.Union);
            Debug.Assert(left  != null);
            Debug.Assert(right != null);
            // It is important to not create nested lists here
            Debug.Assert(right.NodeType == QilNodeType.Filter, "LocationPathPattern must be compiled into a filter");
            if (left.NodeType == QilNodeType.Sequence) {
                ((QilList)left).Add(right);
                return left;
            } else {
                Debug.Assert(left.NodeType == QilNodeType.Filter, "LocationPathPattern must be compiled into a filter");
                return f.Sequence(left, right);
            }
        }

        private static QilLoop BuildAxisFilter(QilPatternFactory f, QilIterator itr, XPathAxis xpathAxis, XPathNodeType nodeType, string name, string nsUri) {
            QilNode nameTest = (
                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 intersection = XPathBuilder.AxisTypeMask(itr.XmlType.NodeKinds, nodeType, xpathAxis);

            QilNode typeTest = (
                intersection == 0                     ? f.False() :  // input & required doesn't intersect
                intersection == itr.XmlType.NodeKinds ? f.True()  :  // input is subset of required
                /*else*/                                f.IsType(itr, T.NodeChoice(intersection))
            );

            QilLoop filter = f.BaseFactory.Filter(itr, f.And(typeTest, nameTest));
            filter.XmlType = T.PrimeProduct(T.NodeChoice(intersection), filter.XmlType.Cardinality);

            return filter;
        }

        public QilNode Axis(XPathAxis xpathAxis, XPathNodeType nodeType, string prefix, string name) {
            Debug.Assert(
                xpathAxis == XPathAxis.Child ||
                xpathAxis == XPathAxis.Attribute ||
                xpathAxis == XPathAxis.DescendantOrSelf ||
                xpathAxis == XPathAxis.Root
            );
            QilLoop result;
            double  priority;
            switch (xpathAxis) {
            case XPathAxis.DescendantOrSelf :
                Debug.Assert(nodeType == XPathNodeType.All && prefix == null && name == null, " // is the only d-o-s axes that we can have in pattern");
                return f.Nop(this.fixupNode); // We using Nop as a flag that DescendantOrSelf exis was used between steps.
            case XPathAxis.Root :
                QilIterator i;
                result = f.BaseFactory.Filter(i = f.For(fixupNode), f.IsType(i, T.Document));
                priority = 0.5;
                break;
            default :
                string   nsUri = prefix == null ? null : this.environment.ResolvePrefix(prefix);
                result = BuildAxisFilter(f, f.For(fixupNode), xpathAxis, nodeType, name, nsUri);
                switch (nodeType) {
                case XPathNodeType.Element :
                case XPathNodeType.Attribute :
                    if (name != null) {
                        priority = 0;
                    } else {
                        if (prefix != null) {
                            priority = -0.25;
                        } else {
                            priority = -0.5;
                        }
                    }
                    break;
                case XPathNodeType.ProcessingInstruction :
                    priority = name != null ? 0 : -0.5;
                    break;
                default:
                    priority = -0.5;
                    break;
                }
                break;
            }

            SetPriority(result, priority);
            SetLastParent(result, result);
            return result;
        }

        // a/b/c -> self::c[parent::b[parent::a]]
        // a/b//c -> self::c[ancestor::b[parent::a]]
        // a/b -> self::b[parent::a]
        //  -> JoinStep(Axis('a'), Axis('b'))
        //   -> Filter('b' & Parent(Filter('a')))
        // a//b
        //  -> JoinStep(Axis('a'), JoingStep(Axis(DescendantOrSelf), Axis('b')))
        //   -> JoinStep(Filter('a'), JoingStep(Nop(null), Filter('b')))
        //    -> JoinStep(Filter('a'), Nop(Filter('b')))
        //     -> Filter('b' & Ancestor(Filter('a')))
        public QilNode JoinStep(QilNode left, QilNode right) {
            Debug.Assert(left  != null);
            Debug.Assert(right != null);
            if (left.NodeType == QilNodeType.Nop) {
                QilUnary nop = (QilUnary)left;
                Debug.Assert(nop.Child == this.fixupNode);
                nop.Child = right;  // We use Nop as a flag that DescendantOrSelf axis was used between steps.
                return nop;
            }
            Debug.Assert(GetLastParent(left) == left, "Left is always single axis and never the step");
            Debug.Assert(left.NodeType == QilNodeType.Filter);
            CleanAnnotation(left);
            QilLoop parentFilter = (QilLoop) left;
            bool ancestor = false; {
                if (right.NodeType == QilNodeType.Nop) {
                    ancestor = true;
                    QilUnary nop = (QilUnary)right;
                    Debug.Assert(nop.Child != null);
                    right = nop.Child;
                }
            }
            Debug.Assert(right.NodeType == QilNodeType.Filter);
            QilLoop lastParent = GetLastParent(right);
            FixupFilterBinding(parentFilter, ancestor ? f.Ancestor(lastParent.Variable) : f.Parent(lastParent.Variable));
            lastParent.Body = f.And(lastParent.Body, f.Not(f.IsEmpty(parentFilter)));
            SetPriority(right, 0.5);
            SetLastParent(right, parentFilter);
            return right;
        }

        QilNode IXPathBuilder<QilNode>.Predicate(QilNode node, QilNode condition, bool isReverseStep) {
            Debug.Assert(false, "Should not call to this function.");
            return null;
        }

        //The structure of result is a Filter, variable is current node, body is the match condition.
        //Previous predicate build logic in XPathPatternBuilder is match from right to left, which have 2^n complexiy when have lots of position predicates. TFS #368771
        //Now change the logic to: If predicates contains position/last predicates, given the current node, filter out all the nodes that match the predicates,
        //and then check if current node is in the result set.
        public QilNode BuildPredicates(QilNode nodeset, List<QilNode> predicates) {
            //convert predicates to boolean type
            List<QilNode> convertedPredicates = new List<QilNode>(predicates.Count);
            foreach (var predicate in predicates) {
                convertedPredicates.Add(XPathBuilder.PredicateToBoolean(predicate, f, predicateEnvironment));
            }

            QilLoop nodeFilter = (QilLoop)nodeset;
            QilIterator current = nodeFilter.Variable;
            
            //If no last() and position() in predicates, use nodeFilter.Variable to fixup current 
            //because all the predicates only based on the input variable, no matter what other predicates are.           
            if (predicateEnvironment.numFixupLast == 0 && predicateEnvironment.numFixupPosition == 0) {
                foreach (var predicate in convertedPredicates) {
                    nodeFilter.Body = f.And(nodeFilter.Body, predicate);
                }
                nodeFilter.Body = predicateEnvironment.fixupVisitor.Fixup(nodeFilter.Body, current, null);
            }
            //If any preidcate contains last() or position() node, then the current node is based on previous predicates,
            //for instance, a[...][2] is match second node after filter 'a[...]' instead of second 'a'.
            else {
                //filter out the siblings
                QilIterator parentIter = f.For(f.Parent(current));
                QilNode sibling = f.Content(parentIter);
                //generate filter based on input filter
                QilLoop siblingFilter = (QilLoop)nodeset.DeepClone(f.BaseFactory);
                siblingFilter.Variable.Binding = sibling;
                siblingFilter = (QilLoop)f.Loop(parentIter, siblingFilter);

                //build predicates from left to right to get all the matching nodes 
                QilNode matchingSet = siblingFilter;
                foreach (var predicate in convertedPredicates) {
                    matchingSet = XPathBuilder.BuildOnePredicate(matchingSet, predicate, /*isReverseStep*/false, 
                                                                    f, predicateEnvironment.fixupVisitor,
                                                                    ref predicateEnvironment.numFixupCurrent, ref predicateEnvironment.numFixupPosition, ref predicateEnvironment.numFixupLast);
                }

                //check if the matching nodes contains the current node
                QilIterator matchNodeIter = f.For(matchingSet);
                QilNode filterCurrent = f.Filter(matchNodeIter, f.Is(matchNodeIter, current));
                nodeFilter.Body = f.Not(f.IsEmpty(filterCurrent));
                //for passing type check, explict say the result is target type
                nodeFilter.Body = f.And(f.IsType(current, nodeFilter.XmlType), nodeFilter.Body);
            }

            SetPriority(nodeset, 0.5);
            return nodeset;
        }

        public QilNode Function(string prefix, string name, IList<QilNode> args) {
            Debug.Assert(prefix.Length == 0);
            QilIterator i = f.For(fixupNode);
            QilNode     matches;

            if (name == "id") {
                Debug.Assert(
                    args.Count == 1 && args[0].NodeType == QilNodeType.LiteralString,
                    "Function id() must have one literal string argument"
                );
                matches = f.Id(i, args[0]);
            } else {
                Debug.Assert(name == "key", "Unexpected function");
                Debug.Assert(
                    args.Count == 2 &&
                    args[0].NodeType == QilNodeType.LiteralString && args[1].NodeType == QilNodeType.LiteralString,
                    "Function key() must have two literal string arguments"
                );
                matches = environment.ResolveFunction(prefix, name, args, new XsltFunctionFocus(i));
            }

            QilIterator j;
            QilLoop result = f.BaseFactory.Filter(i, f.Not(f.IsEmpty(f.Filter(j = f.For(matches), f.Is(j, i)))));
            SetPriority(result, 0.5);
            SetLastParent(result, result);
            return result;
        }

        public QilNode String(string value)                     { return f.String(value); }     // As argument of id() or key() function
        public QilNode Number(double value)                     { return UnexpectedToken("Literal number"); }
        public QilNode Variable(string prefix, string name)     { return UnexpectedToken("Variable"); }

        private QilNode UnexpectedToken(string tokenName) {
            string prompt = string.Format(CultureInfo.InvariantCulture, "Internal Error: {0} is not allowed in XSLT pattern outside of predicate.", tokenName);
            Debug.Assert(false, prompt);
            throw new Exception(prompt);
        }

        // -------------------------------------- Priority / Parent ---------------------------------------

        private class Annotation {
            public double   Priority;
            public QilLoop  Parent;
        }

        public static void SetPriority(QilNode node, double priority) {
            Annotation ann = (Annotation)node.Annotation ?? new Annotation();
            ann.Priority = priority;
            node.Annotation = ann;
        }

        public static double GetPriority(QilNode node) {
            return ((Annotation)node.Annotation).Priority;
        }

        private static void SetLastParent(QilNode node, QilLoop parent) {
            Debug.Assert(parent.NodeType == QilNodeType.Filter);
            Annotation ann = (Annotation)node.Annotation ?? new Annotation();
            ann.Parent = parent;
            node.Annotation = ann;
        }

        private static QilLoop GetLastParent(QilNode node) {
            return ((Annotation)node.Annotation).Parent;
        }

        public static void CleanAnnotation(QilNode node) {
            node.Annotation = null;
        }

        // -------------------------------------- GetPredicateBuilder() ---------------------------------------

        public IXPathBuilder<QilNode> GetPredicateBuilder(QilNode ctx) {
            QilLoop context = (QilLoop) ctx;
            Debug.Assert(context != null, "Predicate always has step so it can't have context == null");
            Debug.Assert(context.Variable.NodeType == QilNodeType.For, "It shouldn't be Let, becaus predicates in PatternBuilder don't produce cached tuples.");
            return predicateBuilder;
        }

        private class XPathPredicateEnvironment : IXPathEnvironment {
            private readonly IXPathEnvironment   baseEnvironment;
            private readonly XPathQilFactory f;
            public readonly XPathBuilder.FixupVisitor fixupVisitor;
            private readonly QilNode fixupCurrent, fixupPosition, fixupLast;

            // Number of unresolved fixup nodes
            public int numFixupCurrent, numFixupPosition, numFixupLast;

            public XPathPredicateEnvironment(IXPathEnvironment baseEnvironment) {
                this.baseEnvironment = baseEnvironment;
                this.f = baseEnvironment.Factory;
                this.fixupCurrent = f.Unknown(T.NodeNotRtf);
                this.fixupPosition = f.Unknown(T.DoubleX);
                this.fixupLast = f.Unknown(T.DoubleX);
                this.fixupVisitor = new XPathBuilder.FixupVisitor(f, fixupCurrent, fixupPosition, fixupLast);
            }

            /*  ----------------------------------------------------------------------------
                IXPathEnvironment interface
            */
            public XPathQilFactory Factory         { get { return f; } }

            public QilNode ResolveVariable(string prefix, string name)  {
                return baseEnvironment.ResolveVariable(prefix, name);
            }
            public QilNode ResolveFunction(string prefix, string name, IList<QilNode> args, IFocus env) {
                return baseEnvironment.ResolveFunction(prefix, name, args, env);
            }
            public string  ResolvePrefix(string prefix) {
                return baseEnvironment.ResolvePrefix(prefix);
            }

            public QilNode GetCurrent() { numFixupCurrent++; return fixupCurrent; }
            public QilNode GetPosition() { numFixupPosition++; return fixupPosition; }
            public QilNode GetLast() { numFixupLast++; return fixupLast; }
        }

        private class XsltFunctionFocus : IFocus {
            private QilIterator current;

            public XsltFunctionFocus(QilIterator current) {
                Debug.Assert(current != null);
                this.current = current;
            }

            /*  ----------------------------------------------------------------------------
                IFocus interface
            */
            public QilNode GetCurrent()  {
                return current;
            }

            public QilNode GetPosition() {
                Debug.Fail("GetPosition() must not be called");
                return null;
            }

            public QilNode GetLast() {
                Debug.Fail("GetLast() must not be called");
                return null;
            }
        }
    }
}