1//------------------------------------------------------------------------------
2// <copyright file="NavigatorInput.cs" company="Microsoft">
3//     Copyright (c) Microsoft Corporation.  All rights reserved.
4// </copyright>
5//------------------------------------------------------------------------------
6using System;
7using System.Text;
8using System.Collections;
9using System.Diagnostics;
10using System.Xml;
11using System.Xml.XPath;
12using ISourceLineInfo = MS.Internal.Xml.ISourceLineInfo;
13
14namespace Microsoft.Xml.Query.Xslt {
15
16    // This class is designed to help XSLT parser with reading stylesheet. We should be able to load SS from
17    // XmlReader, URL, XPathNavigator. The simples way to load from URL and XmlReader is to load xml to XPathDocument and
18    // use navigator over XPathDocument. To avoid intermediate storage XsltInput can work directly from reader and limits
19    // navigation over SS nodes to XmlReader like MoveTo* functions.
20    // document("") function in the case of XslInput(XmlReader) will read SS again. To be able to "reread" SS
21    // we should be garanteed that Xml.Reader.BaseUri != "".
22
23    // Simplest way to implement two ways if XslInput is to make it abstract class and put implementation in its subclasses.
24    // In purpose to not have two additional classes, two additional files and bunch of virtual functions we put both
25    // implementation into base XslImput class. (navigator == null) means that reader is used.
26
27    // To aline XPathNavigator and XmlReader we
28    //     1. Restrict all navigation to pure reader ability:
29    //         a) Forward only, one pass.
30    //         b) You should call MoveToFirstChildren on nonempty element node. (or may be skip)
31    //         c) You should call MoveToParent when no more children is available.
32    //     2. Extend XmlReader to XPath data model.
33    //         a) merge all text, WS and CDATA nodes.
34    //         b) make distinction between NS and other attributes
35    //         c) keep track of all namespaces available for each node
36    //     3. Real problem in XmlReader part we have with MoveToParent(). Navigator will be positioned on real parent while
37    //        Reader can't do this. It will be positioned on EndElement or anythig after it. To allign behavior here
38    //        we are prohibinting to assess node properties (NodeType, LocalName, Value) after MoveToParent().
39    //     3.1. The special part of this problem for XmlReader is <A></A>. MoveToFirstChild() sould be called. It it should
40    //          return false (to line with Navigator) Reader stays on </A>
41    //     4. We don't need value of Element nodes. To lineup with XmlReader set it to string.Empty.
42    //     5. XPathDocument ignores whitespaces ot the root level. To acomplish this with reader we use it's Depth property.
43    //     6. Namespaces are in reverce order in XPathDocument. We are not adressing this problem.
44
45    internal class XsltInput : ISourceLineInfo {
46        private InputScopeManager   scopeManager;
47        private XsltInput           next;
48        private Keywords            atoms;
49
50        private XPathNavigator      navigator;
51        private XmlValidatingReader reader;
52        private string              text;           // Special state for reader base Input. when it != null reader stays after all
53        private bool                textIsWhite;    //    merged text nodes. textIsWhite true alny if all partes was WS or SWS
54        private bool                shouldCloseReader;
55        private int                 depth;
56
57        // Cahced properties. MoveTo* functions set them.
58        private XPathNodeType       nodeType;
59        private string              localName;
60        private string              namespaceUri;
61        private string              value;
62
63        public XsltInput(XPathNavigator navigator) {
64            Init(navigator);
65        }
66
67        public XsltInput(XmlReader reader) {
68            if (reader.BaseURI == null || reader.BaseURI == "") {
69                Init((new System.Xml.XPath.XPathDocument(reader, XmlSpace.Preserve)).CreateNavigator());
70            }
71            else {
72                Init(reader);
73            }
74        }
75
76        public XsltInput(string uri) {
77            CompareInputs(uri);
78            Init(new XmlTextReader(uri));
79    //      Debug.Assert(uri == this.reader.BaseURI);
80        }
81        private void Init(XPathNavigator navigator) {
82            if (navigator == null) throw new ArgumentException(nameof(navigator));
83            this.navigator = navigator;
84            this.scopeManager = new InputScopeManager(navigator.NameTable);
85            this.atoms        = new Keywords(navigator.NameTable);
86            if (this.navigator.NodeType == XPathNodeType.Root) {
87                this.navigator.MoveToFirstChild();
88                // ??? Do we need to check result.
89            }
90            StepOnNodeNav();
91        }
92
93        private void Init(XmlReader reader) {
94            if (reader == null) throw new ArgumentException(nameof(reader));
95            this.reader = EnsureExpandEntities(reader);
96            this.scopeManager = new InputScopeManager(reader.NameTable);
97            this.atoms        = new Keywords(reader.NameTable);
98            this.ReadNextSiblig();
99            StepOnNodeRdr();
100        }
101
102        private XmlValidatingReader EnsureExpandEntities(XmlReader reader) {
103            XmlValidatingReader vr = reader as XmlValidatingReader;
104            if (vr == null || vr.EntityHandling != EntityHandling.ExpandEntities) {
105                vr = new XmlValidatingReader( reader );
106                vr.EntityHandling = EntityHandling.ExpandEntities;
107                vr.ValidationType = ValidationType.None;
108                this.shouldCloseReader = true;
109            }
110            return vr;
111        }
112
113        public void Close() {
114            if (shouldCloseReader) {
115                Debug.Assert(this.reader != null);
116                this.reader.Close();
117                this.reader = null;
118            }
119        }
120
121        private void SetCachedPropertiesFromNavigator() {
122            Debug.Assert(this.navigator != null);
123            nodeType     = this.navigator.NodeType;
124            localName    = this.navigator.LocalName;
125            namespaceUri = this.navigator.NamespaceURI;
126            value        = (nodeType != XPathNodeType.Element) ? this.navigator.Value : string.Empty;
127        }
128
129        private void SetCachedPropertiesFromReader() {
130            Debug.Assert(this.reader != null);
131            nodeType     = ConvertNodeType(this.reader.NodeType);
132            localName    = this.reader.LocalName;
133            namespaceUri = this.reader.NamespaceURI;
134            value        = this.reader.Value;
135            if (nodeType == XPathNodeType.Attribute && IsNamespace(this.reader)) {
136                nodeType = XPathNodeType.Namespace;
137                namespaceUri = string.Empty;
138                if (localName == "xmlns") {
139                    localName = string.Empty;
140                }
141            }
142        }
143
144        public XsltInput Next {
145            get { return this.next; }
146            set { this.next = value; }
147        }
148
149        public Keywords Atoms { get { return this.atoms; } }
150
151        public XPathNavigator Navigator { get { return this.navigator; } }
152
153        // Propertes we have cached
154        public XPathNodeType NodeType     { get { return this.nodeType;     } }
155        public string        Value        { get { return this.value;        } }
156        public string        LocalName    { get { return this.localName;    } }
157        public string        NamespaceUri { get { return this.namespaceUri; } }
158
159        public int           Depth        { get { return this.depth;        } }
160
161        // Prefix, IsEmptyElement and BaseURI are not cached
162        public  string Prefix {
163            get {
164                Debug.Assert(! IsTextType(this.NodeType));
165                if (navigator != null) {
166                    return this.navigator.Prefix;
167                }
168                else {
169                    return this.reader.Prefix;
170                }
171            }
172        }
173
174        public bool IsEmptyElement {
175            get {
176                Debug.Assert(! IsTextType(this.NodeType));
177                if (navigator != null) {
178                    return this.navigator.IsEmptyElement;
179                }
180                else {
181                    return this.reader.IsEmptyElement;
182                }
183            }
184        }
185
186        public string BaseUri {
187            get {
188                Debug.Assert(! IsTextType(this.NodeType));
189                if (navigator != null) {
190                    return this.navigator.BaseURI;
191                }
192                else {
193                    return this.reader.BaseURI;
194                }
195            }
196        }
197
198        public string LookupXmlNamespace(string prefix) {
199            Debug.Assert(prefix != null);
200            string nsUri = (navigator != null
201                ? this.navigator.GetNamespace(prefix)
202                : this.reader.LookupNamespace(prefix)
203            );
204            if (nsUri != null) {
205                return nsUri;
206            }
207            if (prefix.Length == 0) {
208                return string.Empty;
209            }
210            throw new XmlException("SR.Xslt_InvalidPrefix, prefix");
211        }
212
213        static bool IsNamespace(XmlReader reader) {
214            Debug.Assert(reader.NodeType == XmlNodeType.Attribute);
215            return reader.Prefix == "xmlns" || reader.Prefix.Length == 0 && reader.LocalName == "xmlns";
216        }
217
218        static bool NextAttribute(XmlReader reader) {
219            do  {
220                if (! reader.MoveToNextAttribute()) {
221                    return false;
222                }
223            }while (IsNamespace(reader));
224            return true;
225        }
226
227        static bool NextNamespace(XmlReader reader) {
228            do  {
229                if (! reader.MoveToNextAttribute()) {
230                    return false;
231                }
232            }while (! IsNamespace(reader));
233            return true;
234        }
235
236        public bool MoveToFirstAttribute() {
237            Debug.Assert(this.NodeType == XPathNodeType.Element);
238            if (navigator != null) {
239                if (this.navigator.MoveToFirstAttribute()) {
240                    SetCachedPropertiesFromNavigator();
241                    return true;
242                }
243            }
244            else {
245                if (NextAttribute(this.reader)) {
246                    SetCachedPropertiesFromReader();
247                    return true;
248                }
249                else {
250                    // Attributes and namespaces are mixed in reader. It's posible that we are position on NS now.
251                    // To fixup this problem:
252                    this.reader.MoveToElement();
253                }
254            }
255            return false;
256        }
257
258        public bool MoveToNextAttribute() {
259            Debug.Assert(this.NodeType == XPathNodeType.Attribute);
260            if (navigator != null) {
261                if (this.navigator.MoveToNextAttribute()) {
262                    SetCachedPropertiesFromNavigator();
263                    return true;
264                }
265            }
266            else {
267                if (NextAttribute(this.reader)) {
268                    SetCachedPropertiesFromReader();
269                    return true;
270                }
271            }
272            return false;
273        }
274
275        public bool MoveToFirstNamespace() {
276            Debug.Assert(this.NodeType == XPathNodeType.Element);
277            if (navigator != null) {
278                if (this.navigator.MoveToFirstNamespace(XPathNamespaceScope.Local)) {
279                    SetCachedPropertiesFromNavigator();
280                    return true;
281                }
282            }
283            else {
284                if (NextNamespace(this.reader)) {
285                    SetCachedPropertiesFromReader();
286                    return true;
287                }
288                else {
289                    // Attributes and namespaces are mixed in reader. It's posible that we are position on Attribute now.
290                    // To fixup this problem:
291                    this.reader.MoveToElement();
292                }
293            }
294            return false;
295        }
296
297        public bool MoveToNextNamespace() {
298            Debug.Assert(this.NodeType == XPathNodeType.Namespace);
299            if (navigator != null) {
300                if (this.navigator.MoveToNextNamespace(XPathNamespaceScope.Local)) {
301                    SetCachedPropertiesFromNavigator();
302                    return true;
303                }
304            }
305            else {
306                if (NextNamespace(this.reader)) {
307                    SetCachedPropertiesFromReader();
308                    return true;
309                }
310            }
311            return false;
312        }
313
314        public void MoveToElement() {
315            Debug.Assert(this.NodeType == XPathNodeType.Attribute || this.NodeType == XPathNodeType.Namespace, "We should be on an attribute or on a namespace to call MoveToElement");
316            if (navigator != null) {
317                this.navigator.MoveToParent();
318                SetCachedPropertiesFromNavigator();
319            }
320            else {
321                this.reader.MoveToElement();
322                SetCachedPropertiesFromReader();
323            }
324        }
325
326        // Debulg subsystem
327        enum Moves {
328            Next, Child, Parent
329        };
330
331        Moves lastMove   = Moves.Child;
332        bool  lastResult = true;
333
334        [System.Diagnostics.Conditional("DEBUG")]
335        private void SetLastMove(Moves lastMove, bool lastResult) {
336            this.lastMove   = lastMove;
337            this.lastResult = lastResult;
338        }
339        // --------------------
340
341        private void StepOnNodeNav() {
342            SetCachedPropertiesFromNavigator();
343            if (this.NodeType == XPathNodeType.Element) {
344                this.scopeManager.PushScope();
345            }
346        }
347
348        private void StepOnNodeRdr() {
349            if (this.text == null) {
350                SetCachedPropertiesFromReader();
351            }
352            else {
353                this.value        = this.text;
354                this.localName    = string.Empty;
355                this.namespaceUri = string.Empty;
356                this.nodeType = (
357                    (! this.textIsWhite                  ) ? XPathNodeType.Text :
358                    (reader.XmlSpace == XmlSpace.Preserve) ? XPathNodeType.SignificantWhitespace :
359                    /*default:                          */   XPathNodeType.Whitespace
360                );
361            }
362            if (this.NodeType == XPathNodeType.Element) {
363                this.scopeManager.PushScope();
364            }
365        }
366
367        private void StepOffNode () {
368            if (this.NodeType == XPathNodeType.Element) {
369                this.scopeManager.PopScope();
370            }
371        }
372
373        private bool MoveToFirstChildAny() {
374            bool result;
375            if (navigator != null) {
376                result = this.navigator.MoveToFirstChild();
377                if (result) {
378                    StepOnNodeNav();
379                    depth ++;
380                }
381            }
382            else {
383                if (this.reader.IsEmptyElement) {
384                    result = false;
385                }
386                else {
387                    result = this.ReadNextSiblig();
388                    Debug.Assert(result, "For non-empty element ReadNextSiblig() can't fail");
389                    StepOnNodeRdr();
390                    depth ++;
391                }
392            }
393            return result;
394        }
395
396        public bool MoveToFirstChild() {
397            Debug.Assert(this.lastResult && this.lastMove != Moves.Parent, "We should sucessfuly move MoveToNextSibling() after MoveToParent()");
398            Debug.Assert(this.NodeType == XPathNodeType.Element);
399            bool result = MoveToFirstChildAny();
400            if (result) {
401                // Pass commants and PIs
402                if(this.NodeType == XPathNodeType.Comment || this.NodeType == XPathNodeType.ProcessingInstruction) {
403                    result = this.MoveToNextSibling();
404                }
405                if (! result) {
406                    this.MoveToParent();
407                }
408            }
409            SetLastMove(Moves.Child, result);
410            return result;
411        }
412
413        private bool MoveToNextSiblingAny() {
414            bool result;
415            StepOffNode();
416            if (navigator != null) {
417                result = this.navigator.MoveToNext();
418                if (result) {
419                    StepOnNodeNav();
420                }
421            }
422            else {
423                result = this.ReadNextSiblig();
424                if (result) {
425                    StepOnNodeRdr();
426                }
427            }
428            return result;
429        }
430
431        public bool MoveToNextSibling() {
432#if DEBUG
433            if (this.lastMove == Moves.Next) {
434                Debug.Assert(this.lastResult, "we can't move next if we already failed doing this");
435                Debug.Assert(this.NodeType != XPathNodeType.Element, "We should call MoveToFirstChild() on all elements. See SkipElement().");
436            }
437#endif
438            bool result;
439            do {
440                result = this.MoveToNextSiblingAny();
441            }while (result && (this.NodeType == XPathNodeType.Comment || this.NodeType == XPathNodeType.ProcessingInstruction));
442            SetLastMove(Moves.Next, result);
443            return result;
444        }
445
446        public  bool MoveToParent() {
447            Debug.Assert(this.lastMove == Moves.Next && ! this.lastResult, "we can't move parent before read all children");
448            bool result;
449            // We shouldn't call StepOffNode() here because we already left last node.
450            if (navigator != null) {
451                result = this.navigator.MoveToParent();
452            }
453            else {
454                result = true; // this not exectely true if we are on root.
455            }
456            depth --;
457            SetLastMove(Moves.Parent, result);
458            return result;
459        }
460
461        public void SkipNode() {
462            if (this.NodeType == XPathNodeType.Element && this.MoveToFirstChild()) {
463                do {
464                    this.SkipNode();
465                } while(this.MoveToNextSibling());
466                this.MoveToParent();
467            }
468        }
469
470        private bool ReadNextSiblig() {
471            if (this.text != null) {
472                this.text = null;
473                return this.reader.NodeType != XmlNodeType.EndElement;
474            }
475            XmlSpace space = reader.XmlSpace;
476            this.textIsWhite = true;
477            do {
478                if (! reader.Read()) {
479                    return this.text != null;
480                }
481                switch (reader.NodeType) {
482                case XmlNodeType.Text:
483                    this.textIsWhite = false;
484                    goto case XmlNodeType.SignificantWhitespace;
485                case XmlNodeType.CDATA:
486                    if (this.textIsWhite && ! IsWhitespace(reader.Value)) {
487                        this.textIsWhite = false;
488                    }
489                    goto case XmlNodeType.SignificantWhitespace;
490                case XmlNodeType.Whitespace:
491                case XmlNodeType.SignificantWhitespace:
492                    if (this.text == null) {
493                        if (this.reader.Depth == 0 && this.textIsWhite) {
494                            continue; // We should ignore whitespace nodes on root level as XPathDocument does this.
495                        }
496                        this.text = reader.Value;
497                    }
498                    else {
499                        this.text += reader.Value;
500                    }
501                    break;
502                case XmlNodeType.EntityReference :
503                    Debug.Assert(false, "Entety references whould be resolved for us");
504                    break;
505                case XmlNodeType.DocumentType:
506                case XmlNodeType.XmlDeclaration:
507                    break;
508                default:
509                    if (this.text != null) {
510                        return true;
511                    }
512                    return this.reader.NodeType != XmlNodeType.EndElement;
513                }
514            }while (true);
515        }
516
517        private static bool IsWhitespace(string text) {
518            for (int i = text.Length - 1; 0 <= i; i --) {
519                if (! XmlCharType.IsWhiteSpace(text[i])) {
520                    return false;
521                }
522            }
523            return true;
524        }
525
526        private static XPathNodeType ConvertNodeType(XmlNodeType type) {
527            switch(type) {
528            case XmlNodeType.Element               : return XPathNodeType.Element              ;
529            case XmlNodeType.EndElement            : return XPathNodeType.Element              ;
530            case XmlNodeType.Attribute             : return XPathNodeType.Attribute            ;
531            case XmlNodeType.ProcessingInstruction : return XPathNodeType.ProcessingInstruction;
532            case XmlNodeType.Comment               : return XPathNodeType.Comment              ;
533            case XmlNodeType.Text                  : return XPathNodeType.Text                 ;
534    //            case XmlNodeType.Document              : return XPathNodeType.Root                 ;
535            default :
536                Debug.Assert(false, "we should't be on this node");
537                // We can't be on Text WS, SWS, CDATA -- because we reading them all
538                // We can't be on DocumentFragment, Notation, EntityReference, Entity, DocumentType, DocumentFragment, Notation,
539                //                EndElement, EndEntity, XmlDeclaration - we don't know what to do with them on XPath
540                //                XsltInput shoud pass them
541                return XPathNodeType.All;
542            }
543        }
544        private static bool IsTextType(XPathNodeType nodeType) {
545            return (
546                nodeType == XPathNodeType.Text ||
547                nodeType == XPathNodeType.Whitespace ||
548                nodeType == XPathNodeType.SignificantWhitespace
549            );
550        }
551
552        // -------------------- Keywords testing --------------------
553
554        public bool IsNs(string ns)               { return Ref.Equal(ns, this.NamespaceUri); }
555        public bool IsKeyword(string kwd)         { return Ref.Equal(kwd, this.LocalName);  }
556        public bool IsXsltNamespace()             { return IsNs(atoms.UrnXsl); }
557        public bool IsNullNamespace()             { return IsNs(string.Empty); }
558        public bool IsXsltInstruction(string kwd) { return IsKeyword(kwd) && IsXsltNamespace(); }
559        public bool IsXsltAttribute(string kwd)   { return IsKeyword(kwd) && IsNullNamespace(); }
560        public bool IsWithParam()                 { return IsXsltInstruction(this.Atoms.WithParam); }
561        public bool IsSort()                      { return IsXsltInstruction(this.Atoms.Sort     ); }
562
563
564        // -------------------- Scope Management --------------------
565        // See private class InputScopeManager bellow.
566        // InputScopeManager handles some flags and values with respect of scope level where they as defined.
567        // To parse XSLT style sheet we need the folloing values:
568        //  ForwardCompatibility -- this flag is set when xsl:version!='1.0'.
569        //  CanHaveApplyImports  -- we allow xsl:apply-templates instruction to apear in any template with match!=null, but not inside xsl:for-each
570        //                          so it can't be inside global variable and has initial value = false
571        //  ExtentionNamespace   -- is defined by extension-element-prefixes attribute on LRE or xsl:stylesheet
572
573        public bool CanHaveApplyImports {
574            get { return this.scopeManager.CanHaveApplyImports; }
575            set { this.scopeManager.CanHaveApplyImports = value;}
576        }
577
578        public void AddExtensionNamespace(string uri) { this.scopeManager.AddExtensionNamespace(uri); }
579        public bool IsExtensionNamespace( string uri) { return this.scopeManager.IsExtensionNamespace(uri); }
580
581        public bool ForwardCompatibility {
582            get { return this.scopeManager.ForwardCompatibility; }
583        }
584
585        public void SetVersion(string ver) {
586            // BugBug! -- version shouldn't be NaN !
587            if (XmlConvert.ToDouble(ver) < 1) { // should be XmlConvert.ToXPathDouble
588                throw new XmlException("InvalidAttrValueKeywords ");
589            }
590            this.scopeManager.ForwardCompatibility = (ver != "1.0");
591        }
592
593        [System.Diagnostics.Conditional("DEBUG")]
594        private void CompareInputs(string uri) {
595            XsltInput navInput = new XsltInput((new System.Xml.XPath.XPathDocument(uri, XmlSpace.Preserve)).CreateNavigator());
596            XsltInput rdrInput = new XsltInput(new XmlTextReader(uri));
597            // Test all nodes on the root level. It can be comments, PIs and one element node.
598            bool navMove, rdrMove;
599            do {
600                CompareNodes(navInput, rdrInput);
601                navMove = navInput.MoveToNextSibling();
602                rdrMove = rdrInput.MoveToNextSibling();
603                Debug.Assert(navMove == rdrMove);
604            }while(navMove);
605        }
606
607        [System.Diagnostics.Conditional("DEBUG")]
608        private void CompareNodes(XsltInput navInput, XsltInput rdrInput) {
609            XPathNodeType navNodeType       = navInput.NodeType    ;
610            XPathNodeType rdrNodeType       = rdrInput.NodeType    ;
611            string        navValue          = navInput.Value       ;
612            string        rdrValue          = rdrInput.Value       ;
613            string        navLocalName      = navInput.LocalName   ;
614            string        rdrLocalName      = rdrInput.LocalName   ;
615            string        navNamespaceUri   = navInput.NamespaceUri;
616            string        rdrNamespaceUri   = rdrInput.NamespaceUri;
617            int           navDepth          = navInput.Depth       ;
618            int           rdrDepth          = rdrInput.Depth       ;
619            Debug.Assert(navNodeType     == rdrNodeType    );
620            Debug.Assert(navLocalName    == rdrLocalName   );
621            Debug.Assert(navNamespaceUri == rdrNamespaceUri);
622            Debug.Assert(navValue        == rdrValue       );
623            Debug.Assert(navDepth        == rdrDepth       );
624            if (IsTextType(navNodeType)) {
625                return;   // Text is syntetic node for reader breanch. Nothing else is available
626            }
627            string        navPrefix         = navInput.Prefix        ;
628            string        rdrPrefix         = rdrInput.Prefix        ;
629            string        navBaseUri        = navInput.BaseUri       ;
630            string        rdrBaseUri        = rdrInput.BaseUri       ;
631            bool          navIsEmptyElement = navInput.IsEmptyElement;
632            bool          rdrIsEmptyElement = rdrInput.IsEmptyElement;
633            Debug.Assert(navIsEmptyElement == rdrIsEmptyElement);
634            Debug.Assert(navPrefix         == rdrPrefix        );
635            Debug.Assert(navBaseUri        == rdrBaseUri       );
636            if (navInput.NodeType == XPathNodeType.Element) {
637                bool navMove, rdrMove;
638                /* Compare Namespases (local)*/ {
639                    // reader has oposit order of namespaces that navigator.
640                    rdrMove = rdrInput.MoveToFirstNamespace();
641                    navMove = navInput.MoveToFirstNamespace();
642                    Debug.Assert(navMove == rdrMove);
643                    if (navMove) {
644                        ArrayList names = new ArrayList();
645                        ArrayList nss   = new ArrayList();
646                        do {
647                            names.Add(rdrInput.LocalName);
648                            nss  .Add(rdrInput.Value    );
649                            rdrMove = rdrInput.MoveToNextNamespace();
650                        }while(rdrMove);
651                        int count;
652                        for (count = names.Count - 1; 0 <= count; -- count) {
653                            Debug.Assert(navMove);
654                            string name = (string) names[count];
655                            string ns   = (string) nss  [count];
656                            Debug.Assert(name == navInput.LocalName);
657                            Debug.Assert(ns   == navInput.Value    );
658                            navMove = navInput.MoveToNextNamespace();
659                        }
660                        Debug.Assert(-1 == count, "all namespaces from reader was counted");
661                        Debug.Assert(! navMove, "all namespaces from navigator was passed");
662                        navInput.MoveToElement();
663                        rdrInput.MoveToElement();
664                    }
665                }
666                /* Compare attributes */ {
667                    navMove = navInput.MoveToFirstAttribute();
668                    rdrMove = rdrInput.MoveToFirstAttribute();
669                    Debug.Assert(navMove == rdrMove);
670                    if (navMove) {
671                        do {
672                            CompareNodes(navInput, rdrInput);
673                            navMove = navInput.MoveToNextAttribute();
674                            rdrMove = rdrInput.MoveToNextAttribute();
675                            Debug.Assert(navMove == rdrMove);
676                        }while(navMove);
677                        navInput.MoveToElement();
678                        rdrInput.MoveToElement();
679                    }
680                }
681                /* Compare Children */ {
682                    navMove = navInput.MoveToFirstChild();
683                    rdrMove = rdrInput.MoveToFirstChild();
684                    Debug.Assert(navMove == rdrMove);
685                    if (navMove) {
686                        do {
687                            CompareNodes(navInput, rdrInput);
688                            navMove = navInput.MoveToNextSibling();
689                            rdrMove = rdrInput.MoveToNextSibling();
690                            Debug.Assert(navMove == rdrMove);
691                        }while(navMove);
692                        navInput.MoveToParent();
693                        rdrInput.MoveToParent();
694                    }
695                }
696            }
697        }
698
699        // --------------- GetAttributes(...) -------------------------
700        // All Xslt Instructions allows fixed set of attributes in null-ns, no in XSLT-ns and any in other ns.
701        // In ForwardCompatibility mode we should ignore any of this problems.
702        // We not use these functions for parseing LiteralResultElement and xsl:stylesheet
703
704        private string[] names  = new string[11];
705        private string[] values = new string[11];
706
707        public ContextInfo GetAttributes() {
708            return GetAttributes(0, 0, names, values);
709        }
710
711        public ContextInfo GetAttributes(int required, string name, out string value) {
712            names[0] = name;
713            ContextInfo ctxInfo = GetAttributes(required, 1, names, values);
714            value = values[0];
715            return ctxInfo;
716        }
717
718        public ContextInfo GetAttributes(int required, string name0, out string value0, string name1, out string value1) {
719            names[0] = name0;
720            names[1] = name1;
721            ContextInfo ctxInfo = GetAttributes(required, 2, names, values);
722            value0 = values[0];
723            value1 = values[1];
724            return ctxInfo;
725        }
726
727        public ContextInfo GetAttributes(int required,
728            string name0, out string value0,
729            string name1, out string value1,
730            string name2, out string value2
731        ) {
732            names[0] = name0;
733            names[1] = name1;
734            names[2] = name2;
735            ContextInfo ctxInfo = GetAttributes(required, 3, names, values);
736            value0 = values[0];
737            value1 = values[1];
738            value2 = values[2];
739            return ctxInfo;
740        }
741
742        public ContextInfo GetAttributes(int required,
743            string name0, out string value0,
744            string name1, out string value1,
745            string name2, out string value2,
746            string name3, out string value3
747        ) {
748            names[0] = name0;
749            names[1] = name1;
750            names[2] = name2;
751            names[3] = name3;
752            ContextInfo ctxInfo = GetAttributes(required, 4, names, values);
753            value0 = values[0];
754            value1 = values[1];
755            value2 = values[2];
756            value3 = values[3];
757            return ctxInfo;
758        }
759
760        public ContextInfo GetAttributes(int required,
761            string name0, out string value0,
762            string name1, out string value1,
763            string name2, out string value2,
764            string name3, out string value3,
765            string name4, out string value4
766        ) {
767            names[0] = name0;
768            names[1] = name1;
769            names[2] = name2;
770            names[3] = name3;
771            names[4] = name4;
772            ContextInfo ctxInfo = GetAttributes(required, 4, names, values);
773            value0 = values[0];
774            value1 = values[1];
775            value2 = values[2];
776            value3 = values[3];
777            value4 = values[4];
778            return ctxInfo;
779        }
780
781        public ContextInfo GetAttributes(int required,
782            string name0, out string value0,
783            string name1, out string value1,
784            string name2, out string value2,
785            string name3, out string value3,
786            string name4, out string value4,
787            string name5, out string value5,
788            string name6, out string value6,
789            string name7, out string value7,
790            string name8, out string value8
791        ) {
792            names[0] = name0;
793            names[1] = name1;
794            names[2] = name2;
795            names[3] = name3;
796            names[4] = name4;
797            names[5] = name5;
798            names[6] = name6;
799            names[7] = name7;
800            names[8] = name8;
801            ContextInfo ctxInfo = GetAttributes(required, 9, names, values);
802            value0 = values[0];
803            value1 = values[1];
804            value2 = values[2];
805            value3 = values[3];
806            value4 = values[4];
807            value5 = values[5];
808            value6 = values[6];
809            value7 = values[7];
810            value8 = values[8];
811            return ctxInfo;
812        }
813
814        public ContextInfo GetAttributes(int required,
815            string name0, out string value0,
816            string name1, out string value1,
817            string name2, out string value2,
818            string name3, out string value3,
819            string name4, out string value4,
820            string name5, out string value5,
821            string name6, out string value6,
822            string name7, out string value7,
823            string name8, out string value8,
824            string name9, out string value9
825        ) {
826            names[0] = name0;
827            names[1] = name1;
828            names[2] = name2;
829            names[3] = name3;
830            names[4] = name4;
831            names[5] = name5;
832            names[6] = name6;
833            names[7] = name7;
834            names[8] = name8;
835            names[9] = name9;
836            ContextInfo ctxInfo = GetAttributes(required, 10, names, values);
837            value0 = values[0];
838            value1 = values[1];
839            value2 = values[2];
840            value3 = values[3];
841            value4 = values[4];
842            value5 = values[5];
843            value6 = values[6];
844            value7 = values[7];
845            value8 = values[8];
846            value9 = values[9];
847            return ctxInfo;
848        }
849
850        public ContextInfo GetAttributes(int required,
851            string name0, out string value0,
852            string name1, out string value1,
853            string name2, out string value2,
854            string name3, out string value3,
855            string name4, out string value4,
856            string name5, out string value5,
857            string name6, out string value6,
858            string name7, out string value7,
859            string name8, out string value8,
860            string name9, out string value9,
861            string name10, out string value10
862        ) {
863            names[0] = name0;
864            names[1] = name1;
865            names[2] = name2;
866            names[3] = name3;
867            names[4] = name4;
868            names[5] = name5;
869            names[6] = name6;
870            names[7] = name7;
871            names[8] = name8;
872            names[9] = name9;
873            names[10] = name10;
874            ContextInfo ctxInfo = GetAttributes(required, 11, names, values);
875            value0 = values[0];
876            value1 = values[1];
877            value2 = values[2];
878            value3 = values[3];
879            value4 = values[4];
880            value5 = values[5];
881            value6 = values[6];
882            value7 = values[7];
883            value8 = values[8];
884            value9 = values[9];
885            value10 = values[10];
886            return ctxInfo;
887        }
888
889        private ContextInfo GetAttributes(int required, int number, string[] names, string[] values) {
890            for(int i = 0; i < number; i ++) {
891                values[i] = null;
892            }
893            string unknownAttribute = null;
894            ContextInfo ctxInfo = new ContextInfo(this);
895            if (this.MoveToFirstAttribute()) {
896                do {
897                    ctxInfo.AddAttribute(this);
898                    bool found = false;
899                    for(int i = 0; i < number; i ++) {
900                        if (this.IsXsltAttribute(names[i])) {
901                            found = true;
902                            values[i] = this.Value;
903                            if (Ref.Equal(names[i], Atoms.Version)) {
904                                this.SetVersion(this.Value);
905                            }
906                            break;
907                        }
908                    }
909                    if (! found && (this.IsNullNamespace() || this.IsXsltNamespace())) {
910                        unknownAttribute = this.LocalName;
911                    }
912                } while(this.MoveToNextAttribute());
913                this.MoveToElement();
914                if (unknownAttribute != null && ! this.scopeManager.ForwardCompatibility) {
915                    throw new XmlException("Element has an attribute that XSLT 1.0 does not allow the element to have");
916                }
917            }
918            ctxInfo.Finish(this);
919            for (int i = 0; i < required; i ++) {
920                if (values[i] == null) {
921                    throw new XmlException("SR.Xslt_MissingAttribute names[i]");
922                }
923            }
924            return ctxInfo;
925        }
926
927        public ISourceLineInfo BuildLineInfo() {
928            if (this.FileName == null) {
929                return null;
930            }
931            return new SourceLineInfo((ISourceLineInfo) this);
932        }
933
934        public NsDecl BuildNamespaceList() {
935            // Build namespace list of current node:
936            NsDecl last = null;
937            if (this.MoveToFirstNamespace()) {
938                do {
939                    last = new NsDecl(last, this.LocalName, this.Value);
940                }while(this.MoveToNextNamespace());
941                this.MoveToElement();
942            }
943            return last;
944        }
945
946        // ----------------------- ISourceLineInfo -----------------------
947        public string FileName  { get { return this.reader != null ? this.reader.BaseURI : null; } }
948        public int    StartLine { get { return this.reader != null ? ((IXmlLineInfo)this.reader).LineNumber   : -1; } }
949        public int    StartPos  { get { return this.reader != null ? ((IXmlLineInfo)this.reader).LinePosition : -1; } }
950        public int    EndLine   { get { return this.StartLine ; } }
951        public int    EndPos    { get { return this.StartPos +  this.GetNameSize(); } }
952
953        private int GetNameSize() {
954            int nameSize = this.LocalName.Length;
955            string prefix = this.Prefix;
956            if (prefix.Length != 0) {
957                return prefix.Length + 1 + nameSize; // + ':'
958            }
959            return nameSize;
960        }
961
962        private class InputScopeManager {
963            private enum ScopeFlags {
964                ForwardCompatibility = 0x1,
965                CanHaveApplyImports = 0x2,
966                NsExtension = 0x4,
967
968                InheretedFlags = ForwardCompatibility | CanHaveApplyImports,
969            }
970            private struct ScopeReord {
971                public int        scopeCount;
972                public ScopeFlags scopeFlags;
973                public string     nsUri;
974            }
975
976            XmlNameTable nameTable;
977            ScopeReord[] records = new ScopeReord[32];
978            int lastRecord = 0;
979            int lastScopes = 0;  // This is cash of records[lastRecord].scopeCount field;
980                                // most often we will have PushScope()/PopScope pare over the same record.
981                                // It has sence to avoid adresing this field through array access.
982
983            public InputScopeManager(XmlNameTable nameTable) {
984                this.nameTable = nameTable;
985                records[0].scopeFlags = 0;
986                PushScope();
987            }
988
989            public void PushScope() {
990                lastScopes ++;
991            }
992
993            public void PopScope() {
994                if (0 < lastScopes) {
995                    lastScopes --;
996                }
997                else {
998                    while(records[-- lastRecord].scopeCount == 0) ;
999                    lastScopes = records[lastRecord].scopeCount;
1000                    lastScopes --;
1001                }
1002            }
1003
1004            private void AddRecord() {
1005                records[lastRecord].scopeCount = lastScopes;
1006                lastRecord ++;
1007                if (lastRecord == records.Length) {
1008                    ScopeReord[] newRecords = new ScopeReord[lastRecord * 2];
1009                    Array.Copy(records, 0, newRecords, 0, lastRecord);
1010                    records = newRecords;
1011                }
1012                lastScopes = 0;
1013            }
1014
1015            private void SetFlag(bool value, ScopeFlags flag) {
1016                Debug.Assert(flag == ScopeFlags.ForwardCompatibility || flag == ScopeFlags.CanHaveApplyImports);
1017                ScopeFlags lastFlags  =  records[lastRecord].scopeFlags;
1018                //         lastScopes == records[lastRecord].scopeCount; // we know this because we are cashing it.
1019                bool canReuseLastRecord = lastScopes == 0;                             // last record is from last scope
1020                if (! canReuseLastRecord) {
1021                    AddRecord();
1022                    lastFlags = lastFlags & ScopeFlags.InheretedFlags;
1023                }
1024                records[lastRecord].scopeFlags = value ? (lastFlags | flag) : (lastFlags & ~flag);
1025            }
1026
1027            public bool ForwardCompatibility {
1028                get { return (records[lastRecord].scopeFlags & ScopeFlags.ForwardCompatibility) != 0; }
1029                set { SetFlag(value, ScopeFlags.ForwardCompatibility) ;}
1030            }
1031
1032            public bool CanHaveApplyImports {
1033                get { return (records[lastRecord].scopeFlags & ScopeFlags.CanHaveApplyImports) != 0; }
1034                set { SetFlag(value, ScopeFlags.CanHaveApplyImports) ;}
1035            }
1036
1037            public void AddExtensionNamespace(string uri) {
1038                Debug.Assert(uri != null);
1039
1040                uri = nameTable.Add(uri);
1041
1042                ScopeFlags lastFlags  =  records[lastRecord].scopeFlags;
1043                //         lastScopes == records[lastRecord].scopeCount; // we know this because we are cashing it.
1044                bool canReuseLastRecord = (
1045                    lastScopes == 0 &&                                   // last record is from last scope
1046                    (lastFlags & ScopeFlags.NsExtension) == 0            // only flag fields are used in this record
1047                );
1048                if (! canReuseLastRecord) {
1049                    AddRecord();
1050                    lastFlags = lastFlags & ScopeFlags.InheretedFlags;
1051                }
1052                records[lastRecord].nsUri = uri;
1053                records[lastRecord].scopeFlags = lastFlags | ScopeFlags.NsExtension;
1054            }
1055
1056            public bool IsExtensionNamespace(string nsUri) {
1057                for (int record = this.lastRecord; 0 <= record; record --) {
1058                    if (
1059                        (records[record].scopeFlags & ScopeFlags.NsExtension) != 0 &&
1060                        (records[record].nsUri == nsUri)
1061                    ) {
1062                        return true;
1063                    }
1064                }
1065                return false;
1066            }
1067        }
1068
1069        // -------------------------------- ContextInfo ------------------------------------
1070
1071#if ! NO_Qil_COMMENTS
1072        StringBuilder dbgCommentBuilder = new StringBuilder();
1073#endif
1074
1075        public struct ContextInfo {
1076            public NsDecl          nsList;
1077            public string          dbgComment;
1078            public ISourceLineInfo lineInfo  ;
1079
1080            public ContextInfo(XsltInput input) {
1081                this.nsList     = input.BuildNamespaceList();
1082                this.lineInfo   = input.BuildLineInfo();
1083#if ! NO_Qil_COMMENTS
1084                this.dbgComment = null;
1085                Debug.Assert(input.dbgCommentBuilder.Length == 0);
1086                input.dbgCommentBuilder.Append("<");
1087                if (input.Prefix.Length != 0) {
1088                    input.dbgCommentBuilder.Append(input.Prefix);
1089                    input.dbgCommentBuilder.Append(":");
1090                }
1091                input.dbgCommentBuilder.Append(input.LocalName);
1092#endif
1093            }
1094
1095            public void AddAttribute(XsltInput input) {
1096#if ! NO_Qil_COMMENTS
1097                input.dbgCommentBuilder.Append(" ");
1098                if (input.Prefix.Length != 0) {
1099                    input.dbgCommentBuilder.Append(input.Prefix);
1100                    input.dbgCommentBuilder.Append(":");
1101                }
1102                input.dbgCommentBuilder.Append(input.LocalName);
1103                input.dbgCommentBuilder.Append("=\"");
1104                input.dbgCommentBuilder.Append(input.Value);
1105                input.dbgCommentBuilder.Append("\"");
1106#endif
1107            }
1108
1109            public void Finish(XsltInput input) {
1110#if ! NO_Qil_COMMENTS
1111                input.dbgCommentBuilder.Append(input.IsEmptyElement ? "/>" : ">...");
1112                this.dbgComment = input.dbgCommentBuilder.ToString();
1113                input.dbgCommentBuilder.Length = 0;
1114#endif
1115            }
1116        }
1117    }
1118}
1119