Welcome to mirror list, hosted at ThFree Co, Russian Federation.

MatcherBuilder.cs « Xslt « Xsl « Xml « System « System.Data.SqlXml « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 22715a24743777b137fa4c2035eace3bc87617bf (plain)
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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
//------------------------------------------------------------------------------
// <copyright file="MatcherBuilder.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.Xml.Xsl.Qil;
using System.Xml.Xsl.XPath;

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

    #region Comments
    /*  The MatcherBuilder class implements xsl:apply-templates/imports logic, grouping patterns
     *  first by node type, then by node name of their last StepPattern. For example, suppose that
     *  we are given the following patterns, listed in order of decreasing generalized priority
     *  (3-tuple (import precedence, priority, order number in the stylesheet)):
     *
     *                          Generalized
     *      Pattern             Priority
     *      -------------------------------
     *      pattern7/foo        7
     *      pattern6/bar        6
     *      pattern5/*          5
     *      pattern4/node()     4
     *      pattern3/foo        3
     *      pattern2/bar        2
     *      pattern1/*          1
     *      pattern0/node()     0
     *      -------------------------------
     *
     *  The following code will be generated to find a first match amongst them ($it denotes a test
     *  node, and =~ denotes the match operation):
     *
     *  (: First check patterns which match only one fixed node type. :)
     *  (: Switch on the node type of the test node.                  :)
     *  let $pt :=
     *      typeswitch($it)
     *      case element() return
     *          (: First check patterns which match only one fixed node name. :)
     *          (: Switch on the node name of the test node.                  :)
     *          let $pe :=
     *              typeswitch($it)
     *              (: One case for every unique element name occurred in patterns :)
     *              case element(foo) return
     *                  if ($it =~ pattern7/foo) then 7 else
     *                  if ($it =~ pattern3/foo) then 3 else
     *                  -1                 (: -1 is used as "no match found" value :)
     *              case element(bar) return
     *                  if ($it =~ pattern6/bar) then 6 else
     *                  if ($it =~ pattern2/bar) then 2 else
     *                  -1
     *              default return
     *                  -1
     *
     *          (: Now check patterns which may match multiple node names, taking :)
     *          (: into account the priority of the previously found match        :)
     *          return
     *              if ($pe > 5)           then $pe else
     *              if ($it =~ pattern5/*) then   5 else
     *              if ($pe > 1)           then $pe else
     *              if ($it =~ pattern1/*) then   1 else
     *              if ($pe > -1)          then $pe else
     *              -1
     *
     *      (: In the general case check all other node types ocurred in patterns :)
     *      (: case attribute()...              :)
     *      (: case text()...                   :)
     *      (: case document-node()...          :)
     *      (: case comment()...                :)
     *      (: case processing-instruction()... :)
     *
     *      default return
     *          -1
     *
     *  (: Now check patterns which may match multiple node types, taking :)
     *  (: into account the priority of the previously found match        :)
     *  return
     *      if ($pt > 4)         then $pt else
     *      if (pattern4/node()) then   4 else
     *      if ($pt > 0)         then $pt else
     *      if (pattern0/node()) then   0 else
     *      if ($pt > -1)        then $pt else
     *      -1
     */
    #endregion

    internal class TemplateMatch {
        public readonly static TemplateMatchComparer Comparer = new TemplateMatchComparer();

        private Template          template;
        private double            priority;
        private XmlNodeKindFlags  nodeKind;
        private QilName           qname;
        private QilIterator       iterator;
        private QilNode           condition;    // null means f.True()

        public XmlNodeKindFlags NodeKind {
            get { return nodeKind; }
        }

        public QilName QName {
            get { return qname; }
        }

        public QilIterator Iterator {
            get { return iterator; }
        }

        public QilNode Condition {
            get { return condition; }
        }

        public QilFunction TemplateFunction {
            get { return template.Function; }
        }

        public TemplateMatch(Template template, QilLoop filter) {
            this.template   = template;
            this.priority   = double.IsNaN(template.Priority) ? XPathPatternBuilder.GetPriority(filter) : template.Priority;
            this.iterator   = filter.Variable;
            this.condition  = filter.Body;

            XPathPatternBuilder.CleanAnnotation(filter);
            NipOffTypeNameCheck();

            Debug.Assert(
                qname == null ||
                nodeKind == XmlNodeKindFlags.Element || nodeKind == XmlNodeKindFlags.Attribute || nodeKind == XmlNodeKindFlags.PI,
                "qname may be not null only for element, attribute, or PI patterns"
            );
        }

        /*  NOTE: This code depends on the form of Qil expressions generated by XPathPatternBuilder.
         *  More specifically, it recognizes the following two patterns:
         *
         *  A) /, *, @*, text(), comment(), processing-instruction():
         *      (And* $x:(IsType RefTo LiteralType))
         *
         *  B) foo, @ns:foo, processing-instruction('foo'):
         *      (And* $x:(And (IsType RefTo LiteralType) (Eq (NameOf RefTo) LiteralQName)))
         *
         *  where all RefTo refer to 'it', and LiteralType has exactly one NodeKind bit set.
         *
         *  If one of patterns recognized, we nip $x off of the nested And sequence:
         *      (And* (And2 (And1 $x:* $y:*) $z:*))  =>  (And* (And2 $y:* $z:*))
         */
        private void NipOffTypeNameCheck() {
            QilBinary[] leftPath  = new QilBinary[4];   // Circular buffer for last 4 And nodes
            int         idx       = -1;                 // Index of last element in leftPath
            QilNode     node      = condition;          // Walker through left path of the tree

            nodeKind = XmlNodeKindFlags.None;
            qname    = null;

            while (node.NodeType == QilNodeType.And) {
                node = (leftPath[++idx & 3] = (QilBinary)node).Left;
            }

            // Recognizing (IsType RefTo LiteralType)
            if (!(node.NodeType == QilNodeType.IsType)) {
                return;
            }

            QilBinary isType = (QilBinary)node;
            if (!(isType.Left == iterator && isType.Right.NodeType == QilNodeType.LiteralType)) {
                return;
            }

            XmlNodeKindFlags nodeKinds = isType.Right.XmlType.NodeKinds;
            if (!Bits.ExactlyOne((uint)nodeKinds)) {
                return;
            }

            // Recognized pattern A, check for B
            QilNode x = isType;
            nodeKind = nodeKinds;
            QilBinary lastAnd = leftPath[idx & 3];

            if (lastAnd != null && lastAnd.Right.NodeType == QilNodeType.Eq) {
                QilBinary eq = (QilBinary)lastAnd.Right;

                // Recognizing (Eq (NameOf RefTo) LiteralQName)
                if (eq.Left.NodeType == QilNodeType.NameOf &&
                    ((QilUnary)eq.Left).Child == iterator && eq.Right.NodeType == QilNodeType.LiteralQName
                ) {
                    // Recognized pattern B
                    x = lastAnd;
                    qname = (QilName)((QilLiteral)eq.Right).Value;
                    idx--;
                }
            }

            // Nip $x off the condition
            QilBinary and1 = leftPath[idx & 3];
            QilBinary and2 = leftPath[--idx & 3];

            if (and2 != null) {
                and2.Left = and1.Right;
            } else if (and1 != null) {
                condition = and1.Right;
            } else {
                condition = null;
            }
        }

        internal class TemplateMatchComparer : IComparer<TemplateMatch> {
            // TemplateMatch x is "greater" than TemplateMatch y iff
            // * x's priority is greater than y's priority, or
            // * x's priority is equal to y's priority, and x occurs later in the stylesheet than y.
            // Order of TemplateMatch'es from the same xsl:template/@match attribute does not matter.

            public int Compare(TemplateMatch x, TemplateMatch y) {
                Debug.Assert(!double.IsNaN(x.priority));
                Debug.Assert(!double.IsNaN(y.priority));
                return (
                    x.priority > y.priority ?  1 :
                    x.priority < y.priority ? -1 :
                    x.template.OrderNumber - y.template.OrderNumber
                );
            }
        }
    }

    internal struct Pattern {
        public readonly TemplateMatch Match;

        // Generalized priority of 'match' for the xsl:apply-templates/imports currently being processed
        public readonly int Priority;

        public Pattern(TemplateMatch match, int priority) {
            this.Match    = match;
            this.Priority = priority;
        }
    }

    internal class PatternBag {
        public Dictionary<QilName, List<Pattern>> FixedNamePatterns = new Dictionary<QilName, List<Pattern>>();
        public List<QilName> FixedNamePatternsNames = new List<QilName>();  // Needed only to guarantee a stable order
        public List<Pattern> NonFixedNamePatterns   = new List<Pattern>();

        public void Clear() {
            FixedNamePatterns.Clear();
            FixedNamePatternsNames.Clear();
            NonFixedNamePatterns.Clear();
        }

        public void Add(Pattern pattern) {
            QilName qname = pattern.Match.QName;
            List<Pattern> list;

            if (qname == null) {
                list = NonFixedNamePatterns;
            } else {
                if (!FixedNamePatterns.TryGetValue(qname, out list)) {
                    FixedNamePatternsNames.Add(qname);
                    list = FixedNamePatterns[qname] = new List<Pattern>();
                }
            }
            list.Add(pattern);
        }
    }

    internal class MatcherBuilder {
        private XPathQilFactory     f;
        private ReferenceReplacer   refReplacer;
        private InvokeGenerator     invkGen;

        private const int           NoMatch = -1;

        public MatcherBuilder(XPathQilFactory f, ReferenceReplacer refReplacer, InvokeGenerator invkGen) {
            this.f           = f;
            this.refReplacer = refReplacer;
            this.invkGen     = invkGen;
        }

        private int priority = -1;

        private PatternBag    elementPatterns       = new PatternBag();
        private PatternBag    attributePatterns     = new PatternBag();
        private List<Pattern> textPatterns          = new List<Pattern>();
        private List<Pattern> documentPatterns      = new List<Pattern>();
        private List<Pattern> commentPatterns       = new List<Pattern>();
        private PatternBag    piPatterns            = new PatternBag();
        private List<Pattern> heterogenousPatterns  = new List<Pattern>();

        private List<List<TemplateMatch>> allMatches = new List<List<TemplateMatch>>();

        private void Clear() {
            priority = -1;

            elementPatterns.Clear();
            attributePatterns.Clear();
            textPatterns.Clear();
            documentPatterns.Clear();
            commentPatterns.Clear();
            piPatterns.Clear();
            heterogenousPatterns.Clear();

            allMatches.Clear();
        }

        private void AddPatterns(List<TemplateMatch> matches) {
            // Process templates in the straight order, since their order will be reverted in the result tree
            foreach (TemplateMatch match in matches) {
                Pattern pattern = new Pattern(match, ++priority);

                switch (match.NodeKind) {
                case XmlNodeKindFlags.Element   : elementPatterns     .Add(pattern); break;
                case XmlNodeKindFlags.Attribute : attributePatterns   .Add(pattern); break;
                case XmlNodeKindFlags.Text      : textPatterns        .Add(pattern); break;
                case XmlNodeKindFlags.Document  : documentPatterns    .Add(pattern); break;
                case XmlNodeKindFlags.Comment   : commentPatterns     .Add(pattern); break;
                case XmlNodeKindFlags.PI        : piPatterns          .Add(pattern); break;
                default                         : heterogenousPatterns.Add(pattern); break;
                }
            }
        }

        private void CollectPatternsInternal(Stylesheet sheet, QilName mode) {
            // Process imported stylesheets in the straight order, since their order will be reverted in the result tree
            foreach (Stylesheet import in sheet.Imports) {
                CollectPatternsInternal(import, mode);
            }

            List<TemplateMatch> matchesForMode;
            if (sheet.TemplateMatches.TryGetValue(mode, out matchesForMode)) {
                AddPatterns(matchesForMode);
                allMatches.Add(matchesForMode);
            }
        }

        public void CollectPatterns(StylesheetLevel sheet, QilName mode) {
            Clear();
            foreach (Stylesheet import in sheet.Imports) {
                CollectPatternsInternal(import, mode);
            }
        }

        private QilNode MatchPattern(QilIterator it, TemplateMatch match) {
            QilNode cond = match.Condition;
            if (cond == null) {
                return f.True();
            } else {
                // We have to clone, because the same pattern may be used
                // in many different xsl:apply-templates/imports functions
                cond = cond.DeepClone(f.BaseFactory);
                return refReplacer.Replace(cond, match.Iterator, it);
            }
        }

        private QilNode MatchPatterns(QilIterator it, List<Pattern> patternList) {
            Debug.Assert(patternList.Count > 0);
            QilNode result = f.Int32(NoMatch);

            foreach (Pattern pattern in patternList) {
                // if ($it =~ pattern.Match) then pattern.Priority else...
                result = f.Conditional(MatchPattern(it, pattern.Match), f.Int32(pattern.Priority), result);
            }

            return result;
        }

        private QilNode MatchPatterns(QilIterator it, XmlQueryType xt, List<Pattern> patternList, QilNode otherwise) {
            if (patternList.Count == 0) {
                return otherwise;
            }
            return f.Conditional(f.IsType(it, xt), MatchPatterns(it, patternList), otherwise);
        }

        private bool IsNoMatch(QilNode matcher) {
            if (matcher.NodeType == QilNodeType.LiteralInt32) {
                Debug.Assert((int)(QilLiteral)matcher == NoMatch);
                return true;
            }
            return false;
        }

        private QilNode MatchPatternsWhosePriorityGreater(QilIterator it, List<Pattern> patternList, QilNode matcher) {
            if (patternList.Count == 0) {
                return matcher;
            }
            if (IsNoMatch(matcher)) {
                return MatchPatterns(it, patternList);
            }

            QilIterator stopPriority = f.Let(matcher);
            QilNode result = f.Int32(NoMatch);
            int lastPriority = NoMatch;

            foreach (Pattern pattern in patternList) {
                // if (stopPriority > pattern.Priority) then stopPriority     else
                // if ($it =~ pattern.Match)            then pattern.Priority else...

                // First 'if' is generated lazily since it is not needed if priorities are consecutive numbers
                Debug.Assert(pattern.Priority > lastPriority);
                if (pattern.Priority > lastPriority + 1) {
                    result = f.Conditional(f.Gt(stopPriority, f.Int32(lastPriority)), stopPriority, result);
                }

                result = f.Conditional(MatchPattern(it, pattern.Match), f.Int32(pattern.Priority), result);
                lastPriority = pattern.Priority;
            }

            // If the last pattern has the highest priority, the check can be eliminated
            if (lastPriority != this.priority) {
                result = f.Conditional(f.Gt(stopPriority, f.Int32(lastPriority)), stopPriority, result);
            }

            return f.Loop(stopPriority, result);
        }

        private QilNode MatchPatterns(QilIterator it, XmlQueryType xt, PatternBag patternBag, QilNode otherwise) {
            if (patternBag.FixedNamePatternsNames.Count == 0) {
                return MatchPatterns(it, xt, patternBag.NonFixedNamePatterns, otherwise);
            }

            QilNode matcher = f.Int32(NoMatch);

            foreach (QilName qname in patternBag.FixedNamePatternsNames) {
                matcher = f.Conditional(f.Eq(f.NameOf(it), qname.ShallowClone(f.BaseFactory)),
                    MatchPatterns(it, patternBag.FixedNamePatterns[qname]),
                    matcher
                );
            }

            matcher = MatchPatternsWhosePriorityGreater(it, patternBag.NonFixedNamePatterns, matcher);
            return f.Conditional(f.IsType(it, xt), matcher, otherwise);
        }

#if !DISABLE_MATCH_OPTIMIZATION
        public QilNode BuildMatcher(QilIterator it, IList<XslNode> actualArgs, QilNode otherwise) {
            QilNode matcher = f.Int32(NoMatch);

            matcher = MatchPatterns(it, T.PI       , piPatterns       , matcher);
            matcher = MatchPatterns(it, T.Comment  , commentPatterns  , matcher);
            matcher = MatchPatterns(it, T.Document , documentPatterns , matcher);
            matcher = MatchPatterns(it, T.Text     , textPatterns     , matcher);
            matcher = MatchPatterns(it, T.Attribute, attributePatterns, matcher);
            matcher = MatchPatterns(it, T.Element  , elementPatterns  , matcher);

            matcher = MatchPatternsWhosePriorityGreater(it, heterogenousPatterns, matcher);

            if (IsNoMatch(matcher)) {
                return otherwise;
            }

#if !DISABLE_SWITCH
            QilNode[] branches = new QilNode[this.priority + 2];
            int priority = -1;

            foreach (List<TemplateMatch> list in allMatches) {
                foreach (TemplateMatch match in list) {
                    branches[++priority] = invkGen.GenerateInvoke(match.TemplateFunction, actualArgs);
                }
            }

            branches[++priority] = otherwise;
            Debug.Assert(priority == branches.Length - 1);
            return f.Choice(matcher, f.BranchList(branches));
#else
            QilIterator p = f.Let(matcher);
            QilNode result = otherwise;
            int priority = 0;

            foreach (List<TemplateMatch> list in allMatches) {
                foreach (TemplateMatch match in list) {
                    result = f.Conditional(f.Eq(p, f.Int32(priority++)),
                        invkGen.GenerateInvoke(match.TemplateFunction, actualArgs),
                        result
                    );
                }
            }

            return f.Loop(p, result);
#endif
        }
#else
        public QilNode BuildMatcher(QilIterator it, IList<XslNode> actualArgs, QilNode otherwise) {
            QilNode result = otherwise;

            foreach (List<TemplateMatch> list in allMatches) {
                foreach (TemplateMatch match in list) {
                    XmlNodeKindFlags nodeKind = match.NodeKind;
                    QilName qname = match.QName;
                    QilNode cond = match.Condition;

                    if (cond != null) {
                        // We have to clone, because the same pattern may be used
                        // in many different xsl:apply-templates/imports functions
                        cond = cond.DeepClone(f.BaseFactory);
                        cond = refReplacer.Replace(cond, match.Iterator, it);
                    }

                    if (nodeKind != 0) {
                        XmlQueryType nodeType;
                        switch (nodeKind) {
                        case XmlNodeKindFlags.Element   : nodeType = T.Element  ;  break;
                        case XmlNodeKindFlags.Attribute : nodeType = T.Attribute;  break;
                        case XmlNodeKindFlags.Text      : nodeType = T.Text     ;  break;
                        case XmlNodeKindFlags.Document  : nodeType = T.Document ;  break;
                        case XmlNodeKindFlags.Comment   : nodeType = T.Comment  ;  break;
                        case XmlNodeKindFlags.PI        : nodeType = T.PI       ;  break;
                        default                         : nodeType = null       ;  break;
                        }

                        Debug.Assert(nodeType != null, "Unexpected nodeKind: " + nodeKind);
                        QilNode typeNameCheck = f.IsType(it, nodeType);

                        if (qname != null) {
                            typeNameCheck = f.And(typeNameCheck, f.Eq(f.NameOf(it), qname.ShallowClone(f.BaseFactory)));
                        }

                        cond = (cond == null) ? typeNameCheck : f.And(typeNameCheck, cond);
                    }

                    result = f.Conditional(cond,
                        invkGen.GenerateInvoke(match.TemplateFunction, actualArgs),
                        result
                    );
                }
            }
            return result;
        }
#endif
    }
}