b50961a437882e2c3558e48ccc87df732ab1a5a5
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Xslt / XsltInput.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XsltInput.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 //#define XSLT2
9
10 using System.Diagnostics;
11 using System.Text;
12 using System.Xml.XPath;
13 using System.Collections.Generic;
14
15 namespace System.Xml.Xsl.Xslt {
16     using Res = System.Xml.Utils.Res;
17     using StringConcat = System.Xml.Xsl.Runtime.StringConcat;
18     //         a) Forward only, one pass.
19     //         b) You should call MoveToFirstChildren on nonempty element node. (or may be skip)
20
21     internal class XsltInput : IErrorHelper {
22     #if DEBUG
23         const int InitRecordsSize = 1;
24     #else
25         const int InitRecordsSize = 1 + 21;
26     #endif
27
28         private XmlReader           reader;
29         private IXmlLineInfo        readerLineInfo;
30         private bool                topLevelReader;
31         private CompilerScopeManager<VarPar> scopeManager;
32         private KeywordsTable       atoms;
33         private Compiler            compiler;
34         private bool                reatomize;
35
36         // Cached properties. MoveTo* functions set them.
37         private XmlNodeType         nodeType;
38         private Record[]            records = new Record[InitRecordsSize];
39         private int                 currentRecord;
40         private bool                isEmptyElement;
41         private int                 lastTextNode;
42         private int                 numAttributes;
43         private ContextInfo         ctxInfo;
44         private bool                attributesRead;
45
46         public XsltInput(XmlReader reader, Compiler compiler, KeywordsTable atoms) {
47             Debug.Assert(reader != null);
48             Debug.Assert(atoms != null);
49             EnsureExpandEntities(reader);
50             IXmlLineInfo xmlLineInfo = reader as IXmlLineInfo;
51
52             this.atoms          = atoms;
53             this.reader         = reader;
54             this.reatomize      = reader.NameTable != atoms.NameTable;
55             this.readerLineInfo = (xmlLineInfo != null && xmlLineInfo.HasLineInfo()) ? xmlLineInfo : null;
56             this.topLevelReader = reader.ReadState == ReadState.Initial;
57             this.scopeManager   = new CompilerScopeManager<VarPar>(atoms);
58             this.compiler       = compiler;
59             this.nodeType       = XmlNodeType.Document;
60         }
61
62         // Cached properties
63         public XmlNodeType   NodeType       { get { return nodeType == XmlNodeType.Element && 0 < currentRecord ? XmlNodeType.Attribute : nodeType; } }
64         public string        LocalName      { get { return records[currentRecord].localName      ;} }
65         public string        NamespaceUri   { get { return records[currentRecord].nsUri          ;} }
66         public string        Prefix         { get { return records[currentRecord].prefix         ;} }
67         public string        Value          { get { return records[currentRecord].value          ;} }
68         public string        BaseUri        { get { return records[currentRecord].baseUri        ;} }
69         public string        QualifiedName  { get { return records[currentRecord].QualifiedName  ;} }
70         public bool          IsEmptyElement { get { return isEmptyElement; } }
71
72         public string        Uri            { get { return records[currentRecord].baseUri        ; } }
73         public Location      Start          { get { return records[currentRecord].start          ; } }
74         public Location      End            { get { return records[currentRecord].end            ; } }
75
76         private static void EnsureExpandEntities(XmlReader reader) {
77             XmlTextReader tr = reader as XmlTextReader;
78             if (tr != null && tr.EntityHandling != EntityHandling.ExpandEntities) {
79                 Debug.Assert(tr.Settings == null, "XmlReader created with XmlReader.Create should always expand entities.");
80                 tr.EntityHandling = EntityHandling.ExpandEntities;
81             }
82         }
83
84         private void ExtendRecordBuffer(int position) {
85             if (records.Length <= position) {
86                 int newSize = records.Length * 2;
87                 if (newSize <= position) {
88                     newSize = position + 1;
89                 }
90                 Record[] tmp = new Record[newSize];
91                 Array.Copy(records, tmp, records.Length);
92                 records = tmp;
93             }
94         }
95
96         public bool FindStylesheetElement() {
97             if (! topLevelReader) {
98                 if (reader.ReadState != ReadState.Interactive) {
99                     return false;
100                 }
101             }
102
103             // The stylesheet may be an embedded stylesheet. If this is the case the reader will be in Interactive state and should be 
104             // positioned on xsl:stylesheet element (or any preceding whitespace) but there also can be namespaces defined on one 
105             // of the ancestor nodes. These namespace definitions have to be copied to the xsl:stylesheet element scope. Otherwise it 
106             // will not be possible to resolve them later and loading the stylesheet will end up with throwing an exception. 
107             IDictionary<string, string> namespacesInScope = null;
108             if (reader.ReadState == ReadState.Interactive) {
109                 // This may be an embedded stylesheet - store namespaces in scope
110                 IXmlNamespaceResolver nsResolver = reader as IXmlNamespaceResolver;
111                 if (nsResolver != null) {
112                     namespacesInScope = nsResolver.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
113                 }
114             }
115            
116             while (MoveToNextSibling() && nodeType == XmlNodeType.Whitespace) ;
117
118             // An Element node was reached. Potentially this is xsl:stylesheet instruction. 
119             if (nodeType == XmlNodeType.Element) {
120                 // If namespacesInScope is not null then the stylesheet being read is an embedded stylesheet that can have namespaces 
121                 // defined outside of xsl:stylesheet instruction. In this case the namespace definitions collected above have to be added
122                 // to the element scope.
123                 if (namespacesInScope != null) {
124                     foreach (KeyValuePair<string, string> prefixNamespacePair in namespacesInScope) {
125                         // The namespace could be redefined on the element we just read. If this is the case scopeManager already has
126                         // namespace definition for this prefix and the old definition must not be added to the scope. 
127                         if (scopeManager.LookupNamespace(prefixNamespacePair.Key) == null) {
128                             string nsAtomizedValue = atoms.NameTable.Add(prefixNamespacePair.Value);
129                             scopeManager.AddNsDeclaration(prefixNamespacePair.Key, nsAtomizedValue);
130                             ctxInfo.AddNamespace(prefixNamespacePair.Key, nsAtomizedValue);
131                         }
132                     }
133                 }
134
135                 // return true to indicate that we reached XmlNodeType.Element node - potentially xsl:stylesheet element.
136                 return true;
137             }
138
139             // return false to indicate that we did not reach XmlNodeType.Element node so it is not a valid stylesheet.
140             return false;
141         }
142
143         public void Finish() {
144             scopeManager.CheckEmpty();
145
146             if (topLevelReader) {
147                 while (reader.ReadState == ReadState.Interactive) {
148                     reader.Skip();
149                 }
150             }
151         }
152
153         private void FillupRecord(ref Record rec) {
154             rec.localName       = reader.LocalName;
155             rec.nsUri           = reader.NamespaceURI;
156             rec.prefix          = reader.Prefix;
157             rec.value           = reader.Value;
158             rec.baseUri         = reader.BaseURI;
159
160             if (reatomize) {
161                 rec.localName = atoms.NameTable.Add(rec.localName);
162                 rec.nsUri     = atoms.NameTable.Add(rec.nsUri    );
163                 rec.prefix    = atoms.NameTable.Add(rec.prefix   );
164             }
165
166             if (readerLineInfo != null) {
167                 rec.start       = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - PositionAdjustment(reader.NodeType));
168             }
169         }
170
171         private void SetRecordEnd(ref Record rec) {
172             if (readerLineInfo != null) {
173                 rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - PositionAdjustment(reader.NodeType));
174                 if (reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.start)) {
175                     rec.end = new Location(rec.start.Line, int.MaxValue);
176                 }
177             }
178         }
179
180         private void FillupTextRecord(ref Record rec) {
181             Debug.Assert(
182                 reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace ||
183                 reader.NodeType == XmlNodeType.Text       || reader.NodeType == XmlNodeType.CDATA
184             );
185             rec.localName       = string.Empty;
186             rec.nsUri           = string.Empty;
187             rec.prefix          = string.Empty;
188             rec.value           = reader.Value;
189             rec.baseUri         = reader.BaseURI;
190
191             if (readerLineInfo != null) {
192                 bool isCDATA = (reader.NodeType == XmlNodeType.CDATA);
193                 int line = readerLineInfo.LineNumber;
194                 int pos  = readerLineInfo.LinePosition;
195                 rec.start = new Location(line, pos - (isCDATA ? 9 : 0));
196                 char prevChar = ' ';
197                 foreach (char ch in rec.value) {
198                     switch (ch) {
199                     case '\n':
200                         if (prevChar != '\r') {
201                             goto case '\r';
202                         }
203                         break;
204                     case '\r':
205                         line ++;
206                         pos = 1;
207                         break;
208                     default :
209                         pos ++;
210                         break;
211                     }
212                     prevChar = ch;
213                 }
214                 rec.end = new Location(line, pos + (isCDATA ? 3 : 0));
215             }
216         }
217
218         private void FillupCharacterEntityRecord(ref Record rec) {
219             Debug.Assert(reader.NodeType == XmlNodeType.EntityReference);
220             string local = reader.LocalName;
221             Debug.Assert(local[0] == '#' || local == "lt" || local == "gt" || local == "quot" || local == "apos");
222             rec.localName       = string.Empty;
223             rec.nsUri           = string.Empty;
224             rec.prefix          = string.Empty;
225             rec.baseUri         = reader.BaseURI;
226
227             if (readerLineInfo != null) {
228                 rec.start = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - 1);
229             }
230             reader.ResolveEntity();
231             reader.Read();
232             Debug.Assert(reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace);
233             rec.value = reader.Value;
234             reader.Read();
235             Debug.Assert(reader.NodeType == XmlNodeType.EndEntity);
236             if (readerLineInfo != null) {
237                 int line = readerLineInfo.LineNumber;
238                 int pos = readerLineInfo.LinePosition;
239                 rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + 1);
240             }
241         }
242
243         StringConcat strConcat = new StringConcat();
244
245         // returns false if attribute is actualy namespace
246         private bool ReadAttribute(ref Record rec) {
247             Debug.Assert(reader.NodeType == XmlNodeType.Attribute, "reader.NodeType == XmlNodeType.Attribute");
248             FillupRecord(ref rec);
249             if (Ref.Equal(rec.prefix, atoms.Xmlns)) {                                      // xmlns:foo="NS_FOO"
250                 string atomizedValue = atoms.NameTable.Add(reader.Value);
251                 if (!Ref.Equal(rec.localName, atoms.Xml)) {
252                     scopeManager.AddNsDeclaration(rec.localName, atomizedValue);
253                     ctxInfo.AddNamespace(rec.localName, atomizedValue);
254                 }
255                 return false;
256             } else if (rec.prefix.Length == 0 && Ref.Equal(rec.localName, atoms.Xmlns)) {  // xmlns="NS_FOO"
257                 string atomizedValue = atoms.NameTable.Add(reader.Value);
258                 scopeManager.AddNsDeclaration(string.Empty, atomizedValue);
259                 ctxInfo.AddNamespace(string.Empty, atomizedValue);
260                 return false;
261             }
262             /* Read Attribute Value */ {
263                 if (!reader.ReadAttributeValue()) {
264                     // XmlTextReader never returns false from first call to ReadAttributeValue()
265                     rec.value = string.Empty;
266                     SetRecordEnd(ref rec);
267                     return true;
268                 }
269                 if (readerLineInfo != null) {
270                     int correction = (reader.NodeType == XmlNodeType.EntityReference) ? -2 : -1;
271                     rec.valueStart = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + correction);
272                     if (reader.BaseURI != rec.baseUri || rec.valueStart.LessOrEqual(rec.start)) {
273                         int nameLength = ((rec.prefix.Length != 0) ? rec.prefix.Length + 1 : 0) + rec.localName.Length;
274                         rec.end = new Location(rec.start.Line, rec.start.Pos + nameLength + 1);
275                     }
276                 }
277                 string lastText = string.Empty;
278                 strConcat.Clear();
279                 do {
280                     switch (reader.NodeType) {
281                     case XmlNodeType.EntityReference:
282                         reader.ResolveEntity();
283                         break;
284                     case XmlNodeType.EndEntity:
285                         break;
286                     default:
287                         Debug.Assert(reader.NodeType == XmlNodeType.Text, "Unexpected node type inside attribute value");
288                         lastText = reader.Value;
289                         strConcat.Concat(lastText);
290                         break;
291                     }
292                 } while (reader.ReadAttributeValue());
293                 rec.value = strConcat.GetResult();
294                 if (readerLineInfo != null) {
295                     Debug.Assert(reader.NodeType != XmlNodeType.EntityReference);
296                     int correction = ((reader.NodeType == XmlNodeType.EndEntity) ? 1 : lastText.Length) + 1;
297                     rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + correction);
298                     if (reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.valueStart)) {
299                         rec.end = new Location(rec.start.Line, int.MaxValue);
300                     }
301                 }
302             }
303             return true;
304         }
305
306         // --------------------
307
308         public bool MoveToFirstChild() {
309             Debug.Assert(nodeType == XmlNodeType.Element, "To call MoveToFirstChild() XsltI---- should be positioned on an Element.");
310             if (IsEmptyElement) {
311                 return false;
312             }
313             return ReadNextSibling();
314         }
315
316         public bool MoveToNextSibling() {
317             Debug.Assert(nodeType != XmlNodeType.Element || IsEmptyElement, "On non-empty elements we should call MoveToFirstChild()");
318             if (nodeType == XmlNodeType.Element || nodeType == XmlNodeType.EndElement) {
319                 scopeManager.ExitScope();
320             }
321             return ReadNextSibling();
322         }
323
324         public void SkipNode() {
325             if (nodeType == XmlNodeType.Element && MoveToFirstChild()) {
326                 do {
327                     SkipNode();
328                 } while (MoveToNextSibling());
329             }
330         }
331
332         private int ReadTextNodes() {
333             bool textPreserveWS = reader.XmlSpace == XmlSpace.Preserve;
334             bool textIsWhite = true;
335             int curTextNode = 0;
336             do {
337                 switch (reader.NodeType) {
338                 case XmlNodeType.Text:
339                     // XLinq reports WS nodes as Text so we need to analyze them here
340                 case XmlNodeType.CDATA:
341                     if (textIsWhite && ! XmlCharType.Instance.IsOnlyWhitespace(reader.Value)) {
342                         textIsWhite = false;
343                     }
344                     goto case XmlNodeType.SignificantWhitespace;
345                 case XmlNodeType.Whitespace:
346                 case XmlNodeType.SignificantWhitespace:
347                     ExtendRecordBuffer(curTextNode);
348                     FillupTextRecord(ref records[curTextNode]);
349                     reader.Read();
350                     curTextNode++;
351                     break;
352                 case XmlNodeType.EntityReference:
353                     string local = reader.LocalName;
354                     if (local.Length > 0 && (
355                         local[0] == '#' ||
356                         local == "lt" || local == "gt" || local == "quot" || local == "apos"
357                     )) {
358                         // Special treatment for character and built-in entities
359                         ExtendRecordBuffer(curTextNode);
360                         FillupCharacterEntityRecord(ref records[curTextNode]);
361                         if (textIsWhite && !XmlCharType.Instance.IsOnlyWhitespace(records[curTextNode].value)) {
362                             textIsWhite = false;
363                         }
364                         curTextNode++;
365                     } else {
366                         reader.ResolveEntity();
367                         reader.Read();
368                     }
369                     break;
370                 case XmlNodeType.EndEntity:
371                     reader.Read();
372                     break;
373                 default:
374                     this.nodeType = (
375                         ! textIsWhite  ? XmlNodeType.Text :
376                         textPreserveWS ? XmlNodeType.SignificantWhitespace :
377                         /*default:    */ XmlNodeType.Whitespace
378                     );
379                     return curTextNode;
380                 }
381             } while (true);
382         }
383
384         private bool ReadNextSibling() {
385             if (currentRecord < lastTextNode) {
386                 Debug.Assert(nodeType == XmlNodeType.Text || nodeType == XmlNodeType.Whitespace || nodeType == XmlNodeType.SignificantWhitespace);
387                 currentRecord++;
388                 if (currentRecord == lastTextNode) {
389                     lastTextNode = 0;  // we are done with text nodes. Reset this counter
390                 }
391                 return true;
392             }
393             currentRecord = 0;
394             while (! reader.EOF) {
395                 switch (reader.NodeType) {
396                 case XmlNodeType.Text:
397                 case XmlNodeType.CDATA:
398                 case XmlNodeType.Whitespace:
399                 case XmlNodeType.SignificantWhitespace:
400                 case XmlNodeType.EntityReference:
401                     int numTextNodes = ReadTextNodes();
402                     if (numTextNodes == 0) {
403                         // Most likely this was Entity that starts from non-text node
404                         continue;
405                     }
406                     lastTextNode = numTextNodes - 1;
407                     return true;
408                 case XmlNodeType.Element:
409                     scopeManager.EnterScope();
410                     numAttributes = ReadElement();
411                     return true;
412                 case XmlNodeType.EndElement:
413                     nodeType = XmlNodeType.EndElement;
414                     isEmptyElement = false;
415                     FillupRecord(ref records[0]);
416                     reader.Read();
417                     SetRecordEnd(ref records[0]);
418                     return false;
419                 default:
420                     reader.Read();
421                     break;
422                 }
423             }
424             return false;
425         }
426
427         private int ReadElement() {
428             Debug.Assert(reader.NodeType == XmlNodeType.Element);
429
430             attributesRead = false;
431             FillupRecord(ref records[0]);
432             nodeType = XmlNodeType.Element;
433             isEmptyElement = reader.IsEmptyElement;
434             ctxInfo = new ContextInfo(this);
435
436             int record = 1;
437             if (reader.MoveToFirstAttribute()) {
438                 do {
439                     ExtendRecordBuffer(record);
440                     if (ReadAttribute(ref records[record])) {
441                         record++;
442                     }
443                 } while (reader.MoveToNextAttribute());
444                 reader.MoveToElement();
445             }
446             reader.Read();
447             SetRecordEnd(ref records[0]);
448             ctxInfo.lineInfo = BuildLineInfo();
449             attributes = null;
450             return record - 1;
451         }
452
453         public void MoveToElement() {
454             Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToElement() we should be positioned on Element or Attribute");
455             currentRecord = 0;
456         }
457
458         private bool MoveToAttributeBase(int attNum) {
459             Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute");
460             if (0 < attNum && attNum <= numAttributes) {
461                 currentRecord = attNum;
462                 return true;
463             } else {
464                 currentRecord = 0;
465                 return false;
466             }
467         }
468
469         public bool MoveToLiteralAttribute(int attNum) {
470             Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute");
471             if (0 < attNum && attNum <= numAttributes) {
472                 currentRecord = attNum;
473                 return true;
474             } else {
475                 currentRecord = 0;
476                 return false;
477             }
478         }
479
480         public bool MoveToXsltAttribute(int attNum, string attName) {
481             Debug.Assert(attributes != null && attributes[attNum].name == attName, "Attribute numbering error.");
482             this.currentRecord = xsltAttributeNumber[attNum];
483             return this.currentRecord != 0;
484         }
485
486         public bool IsRequiredAttribute(int attNum) {
487             return (attributes[attNum].flags & (compiler.Version == 2 ? XsltLoader.V2Req : XsltLoader.V1Req)) != 0;
488         }
489
490         public bool AttributeExists(int attNum, string attName) {
491             Debug.Assert(attributes != null && attributes[attNum].name == attName, "Attribute numbering error.");
492             return xsltAttributeNumber[attNum] != 0;
493         }
494
495         public struct DelayedQName {
496             string prefix   ;
497             string localName;
498             public DelayedQName(ref Record rec) {
499                 this.prefix    = rec.prefix;
500                 this.localName = rec.localName;
501             }
502             public static implicit operator string(DelayedQName qn) {
503                 return qn.prefix.Length == 0 ? qn.localName : (qn.prefix + ':' + qn.localName);
504             }
505         }
506
507         public DelayedQName ElementName {
508             get {
509                 Debug.Assert(nodeType == XmlNodeType.Element || nodeType == XmlNodeType.EndElement, "Input is positioned on element or attribute");
510                 return new DelayedQName(ref records[0]);
511             }
512         }
513
514         // -------------------- Keywords testing --------------------
515
516         public bool IsNs(string ns)             { return Ref.Equal(ns, NamespaceUri); }
517         public bool IsKeyword(string kwd)       { return Ref.Equal(kwd, LocalName);  }
518         public bool IsXsltNamespace()           { return IsNs(atoms.UriXsl); }
519         public bool IsNullNamespace()           { return IsNs(string.Empty); }
520         public bool IsXsltAttribute(string kwd) { return IsKeyword(kwd) && IsNullNamespace(); }
521         public bool IsXsltKeyword(  string kwd) { return IsKeyword(kwd) && IsXsltNamespace(); }
522
523         // -------------------- Scope Management --------------------
524         // See private class InputScopeManager bellow.
525         // InputScopeManager handles some flags and values with respect of scope level where they as defined.
526         // To parse XSLT style sheet we need the folloing values:
527         //  BackwardCompatibility -- this flag is set when compiler.version==2 && xsl:version<2.
528         //  ForwardCompatibility  -- this flag is set when compiler.version==2 && xsl:version>1 or compiler.version==1 && xsl:version!=1
529         //  CanHaveApplyImports  -- we allow xsl:apply-templates instruction to apear in any template with match!=null, but not inside xsl:for-each
530         //                          so it can't be inside global variable and has initial value = false
531         //  ExtentionNamespace   -- is defined by extension-element-prefixes attribute on LRE or xsl:stylesheet
532
533         public bool CanHaveApplyImports {
534             get { return scopeManager.CanHaveApplyImports;  }
535             set { scopeManager.CanHaveApplyImports = value; }
536         }
537
538         public bool IsExtensionNamespace(string uri) {
539             Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
540             return scopeManager.IsExNamespace(uri);
541         }
542
543         public bool ForwardCompatibility {
544             get {
545                 Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
546                 return scopeManager.ForwardCompatibility;
547             }
548         }
549
550         public bool BackwardCompatibility {
551             get {
552                 Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
553                 return scopeManager.BackwardCompatibility;
554             }
555         }
556
557         public XslVersion XslVersion {
558             get { return scopeManager.ForwardCompatibility ? XslVersion.ForwardsCompatible : XslVersion.Current; }
559         }
560
561         private void SetVersion(int attVersion) {
562             MoveToLiteralAttribute(attVersion);
563             Debug.Assert(IsKeyword(atoms.Version));
564             double version = XPathConvert.StringToDouble(Value);
565             if (double.IsNaN(version)) {
566                 ReportError(/*[XT0110]*/Res.Xslt_InvalidAttrValue, atoms.Version, Value);
567 #if XSLT2
568                 version = 2.0;
569 #else
570                 version = 1.0;
571 #endif
572             }
573             SetVersion(version);
574         }
575         private void SetVersion(double version) {
576             if (compiler.Version == 0) {
577 #if XSLT2
578                 compiler.Version = version < 2.0 ? 1 : 2;
579 #else
580                 compiler.Version = 1;
581 #endif
582             }
583
584             if (compiler.Version == 1) {
585                 scopeManager.BackwardCompatibility = false;
586                 scopeManager.ForwardCompatibility = (version != 1.0);
587             } else {
588                 scopeManager.BackwardCompatibility = version < 2;
589                 scopeManager.ForwardCompatibility = 2 < version;
590             }
591         }
592
593         // --------------- GetAtributes(...) -------------------------
594         // All Xslt Instructions allows fixed set of attributes in null-ns, no in XSLT-ns and any in other ns.
595         // In ForwardCompatibility mode we should ignore any of this problems.
596         // We not use these functions for parseing LiteralResultElement and xsl:stylesheet
597
598         public struct XsltAttribute {
599             public string name;
600             public int    flags;
601             public XsltAttribute(string name, int flags) {
602                 this.name  = name;
603                 this.flags = flags;
604             }
605         }
606
607         private XsltAttribute[] attributes = null;
608         // Mapping of attribute names as they ordered in 'attributes' array
609         // to there's numbers in actual stylesheet as they ordered in 'records' array
610         private int[] xsltAttributeNumber = new int[21];
611
612         static private XsltAttribute[] noAttributes = new XsltAttribute[]{};
613         public ContextInfo GetAttributes() {
614             return GetAttributes(noAttributes);
615         }
616
617         public ContextInfo GetAttributes(XsltAttribute[] attributes) {
618             Debug.Assert(NodeType == XmlNodeType.Element);
619             Debug.Assert(attributes.Length <= xsltAttributeNumber.Length);
620             this.attributes = attributes;
621             // temp hack to fix value? = new AttValue(records[values[?]].value);
622             records[0].value = null;
623
624             // Standard Attributes:
625             int attExtension = 0;
626             int attExclude   = 0;
627             int attNamespace = 0;
628             int attCollation = 0;
629             int attUseWhen   = 0;
630
631             bool isXslOutput = IsXsltNamespace() && IsKeyword(atoms.Output);
632             bool SS = IsXsltNamespace() && (IsKeyword(atoms.Stylesheet) || IsKeyword(atoms.Transform));
633             bool V2 = compiler.Version == 2;
634
635             for (int i = 0; i < attributes.Length; i++) {
636                 xsltAttributeNumber[i] = 0;
637             }
638
639             compiler.EnterForwardsCompatible();
640             if (SS || V2 && !isXslOutput) {
641                 for (int i = 1; MoveToAttributeBase(i); i++) {
642                     if (IsNullNamespace() && IsKeyword(atoms.Version)) {
643                         SetVersion(i);
644                         break;
645                     }
646                 }
647             }
648             if (compiler.Version == 0) {
649                 Debug.Assert(SS, "First we parse xsl:stylesheet element");
650 #if XSLT2
651                 SetVersion(2.0);
652 #else
653                 SetVersion(1.0);
654 #endif
655             }
656             V2 = compiler.Version == 2;
657             int OptOrReq = V2 ? XsltLoader.V2Opt | XsltLoader.V2Req : XsltLoader.V1Opt | XsltLoader.V1Req;
658
659             for (int attNum = 1; MoveToAttributeBase(attNum); attNum++) {
660                 if (IsNullNamespace()) {
661                     string localName = LocalName;
662                     int kwd;
663                     for (kwd = 0; kwd < attributes.Length; kwd++) {
664                         if (Ref.Equal(localName, attributes[kwd].name) && (attributes[kwd].flags & OptOrReq) != 0) {
665                             xsltAttributeNumber[kwd] = attNum;
666                             break;
667                         }
668                     }
669
670                     if (kwd == attributes.Length) {
671                         if (Ref.Equal(localName, atoms.ExcludeResultPrefixes   ) && (SS || V2)) {attExclude   = attNum; } else
672                         if (Ref.Equal(localName, atoms.ExtensionElementPrefixes) && (SS || V2)) {attExtension = attNum; } else
673                         if (Ref.Equal(localName, atoms.XPathDefaultNamespace   ) && (      V2)) {attNamespace = attNum; } else
674                         if (Ref.Equal(localName, atoms.DefaultCollation        ) && (      V2)) {attCollation = attNum; } else
675                         if (Ref.Equal(localName, atoms.UseWhen                 ) && (      V2)) {attUseWhen   = attNum; } else {
676                             ReportError(/*[XT0090]*/Res.Xslt_InvalidAttribute, QualifiedName, records[0].QualifiedName);
677                         }
678                     }
679                 } else if (IsXsltNamespace()) {
680                     ReportError(/*[XT0090]*/Res.Xslt_InvalidAttribute, QualifiedName, records[0].QualifiedName);
681                 } else {
682                     // Ignore the attribute.
683                     // An element from the XSLT namespace may have any attribute not from the XSLT namespace,
684                     // provided that the expanded-name of the attribute has a non-null namespace URI.
685                     // For example, it may be 'xml:space'.
686                 }
687             }
688
689             attributesRead = true;
690
691             // Ignore invalid attributes if forwards-compatible behavior is enabled. Note that invalid
692             // attributes may encounter before ForwardCompatibility flag is set to true. For example,
693             // <xsl:stylesheet unknown="foo" version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
694             compiler.ExitForwardsCompatible(ForwardCompatibility);
695
696             InsertExNamespaces(attExtension, ctxInfo, /*extensions:*/ true );
697             InsertExNamespaces(attExclude  , ctxInfo, /*extensions:*/ false);
698             SetXPathDefaultNamespace(attNamespace);
699             SetDefaultCollation(attCollation);
700             if (attUseWhen != 0) {
701                 ReportNYI(atoms.UseWhen);
702             }
703
704             MoveToElement();
705             // Report missing mandatory attributes
706             for (int i = 0; i < attributes.Length; i ++) {
707                 if (xsltAttributeNumber[i] == 0) {
708                     int flags = attributes[i].flags;
709                     if (
710                         compiler.Version == 2 && (flags & XsltLoader.V2Req) != 0 ||
711                         compiler.Version == 1 && (flags & XsltLoader.V1Req) != 0 && (!ForwardCompatibility || (flags & XsltLoader.V2Req) != 0)
712                     ) {
713                         ReportError(/*[XT_001]*/Res.Xslt_MissingAttribute, attributes[i].name);
714                     }
715                 }
716             }
717
718             return ctxInfo;
719         }
720
721         public ContextInfo GetLiteralAttributes(bool asStylesheet) {
722             Debug.Assert(NodeType == XmlNodeType.Element);
723
724             // Standard Attributes:
725             int attVersion   = 0;
726             int attExtension = 0;
727             int attExclude   = 0;
728             int attNamespace = 0;
729             int attCollation = 0;
730             int attUseWhen   = 0;
731
732             for (int i = 1; MoveToLiteralAttribute(i); i++) {
733                 if (IsXsltNamespace()) {
734                     string localName = LocalName;
735                     if (Ref.Equal(localName, atoms.Version                 )) {attVersion   = i; } else
736                     if (Ref.Equal(localName, atoms.ExtensionElementPrefixes)) {attExtension = i; } else
737                     if (Ref.Equal(localName, atoms.ExcludeResultPrefixes   )) {attExclude   = i; } else
738                     if (Ref.Equal(localName, atoms.XPathDefaultNamespace   )) {attNamespace = i; } else
739                     if (Ref.Equal(localName, atoms.DefaultCollation        )) {attCollation = i; } else
740                     if (Ref.Equal(localName, atoms.UseWhen                 )) {attUseWhen   = i; }
741                 }
742             }
743
744             attributesRead = true;
745             this.MoveToElement();
746
747             if (attVersion != 0) {
748                 // Enable forwards-compatible behavior if version attribute is not "1.0"
749                 SetVersion(attVersion);
750             } else {
751                 if (asStylesheet) {
752                     ReportError(Ref.Equal(NamespaceUri, atoms.UriWdXsl) && Ref.Equal(LocalName, atoms.Stylesheet) ?
753                         /*[XT_025]*/Res.Xslt_WdXslNamespace : /*[XT0150]*/Res.Xslt_WrongStylesheetElement
754                     );
755 #if XSLT2
756                     SetVersion(2.0);
757 #else
758                     SetVersion(1.0);
759 #endif
760                 }
761             }
762
763             // Parse xsl:extension-element-prefixes attribute (now that forwards-compatible mode is known)
764             InsertExNamespaces(attExtension, ctxInfo, /*extensions:*/true);
765
766             if (! IsExtensionNamespace(records[0].nsUri)) {
767                 // Parse other attributes (now that it's known this is a literal result element)
768                 if (compiler.Version == 2) {
769                     SetXPathDefaultNamespace(attNamespace);
770                     SetDefaultCollation(attCollation);
771                     if (attUseWhen != 0) {
772                         ReportNYI(atoms.UseWhen);
773                     }
774                 }
775
776                 InsertExNamespaces(attExclude, ctxInfo, /*extensions:*/false);
777             }
778
779             return ctxInfo;
780         }
781
782         // Get just the 'version' attribute of an unknown XSLT instruction. All other attributes
783         // are ignored since we do not want to report an error on each of them.
784         public void GetVersionAttribute() {
785             Debug.Assert(NodeType == XmlNodeType.Element && IsXsltNamespace());
786             bool V2 = compiler.Version == 2;
787
788             if (V2) {
789                 for (int i = 1; MoveToAttributeBase(i); i++) {
790                     if (IsNullNamespace() && IsKeyword(atoms.Version)) {
791                         SetVersion(i);
792                         break;
793                     }
794                 }
795             }
796             attributesRead = true;
797         }
798
799         private void InsertExNamespaces(int attExPrefixes, ContextInfo ctxInfo, bool extensions) {
800             // List of Extension namespaces are maintaned by XsltInput's ScopeManager and is used by IsExtensionNamespace() in XsltLoader.LoadLiteralResultElement()
801             // Both Extension and Exclusion namespaces will not be coppied by LiteralResultElement. Logic of copping namespaces are in QilGenerator.CompileLiteralElement().
802             // At this time we will have different scope manager and need preserve all required information from load time to compile time.
803             // Each XslNode contains list of NsDecls (nsList) wich stores prefix+namespaces pairs for each namespace decls as well as exclusion namespaces.
804             // In addition it also contains Exclusion namespace. They are represented as (null+namespace). Special case is Exlusion "#all" represented as (null+null).
805             //and Exclusion namespace
806             if (MoveToLiteralAttribute(attExPrefixes)) {
807                 Debug.Assert(extensions ? IsKeyword(atoms.ExtensionElementPrefixes) : IsKeyword(atoms.ExcludeResultPrefixes));
808                 string value = Value;
809                 if (value.Length != 0) {
810                     if (!extensions && compiler.Version != 1 && value == "#all") {
811                         ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, /*nsUri:*/null);    // null, null means Exlusion #all
812                     } else {
813                         compiler.EnterForwardsCompatible();
814                         string[] list = XmlConvert.SplitString(value);
815                         for (int idx = 0; idx < list.Length; idx++) {
816                             if (list[idx] == "#default") {
817                                 list[idx] = this.LookupXmlNamespace(string.Empty);
818                                 if (list[idx].Length == 0 && compiler.Version != 1 && !BackwardCompatibility) {
819                                     ReportError(/*[XTSE0809]*/Res.Xslt_ExcludeDefault);
820                                 }
821                             } else {
822                                 list[idx] = this.LookupXmlNamespace(list[idx]);
823                             }
824                         }
825                         if (!compiler.ExitForwardsCompatible(this.ForwardCompatibility)) {
826                             // There were errors in the list, ignore the whole list
827                             return;
828                         }
829
830                         for (int idx = 0; idx < list.Length; idx++) {
831                             if (list[idx] != null) {
832                                 ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, list[idx]); // null means that this Exlusion NS
833                                 if (extensions) {
834                                     this.scopeManager.AddExNamespace(list[idx]);                         // At Load time we need to know Extencion namespaces to ignore such literal elements.
835                                 }
836                             }
837                         }
838                     }
839                 }
840             }
841         }
842
843         private void SetXPathDefaultNamespace(int attNamespace) {
844             if (MoveToLiteralAttribute(attNamespace)) {
845                 Debug.Assert(IsKeyword(atoms.XPathDefaultNamespace));
846                 if (Value.Length != 0) {
847                     ReportNYI(atoms.XPathDefaultNamespace);
848                 }
849             }
850         }
851
852         private void SetDefaultCollation(int attCollation) {
853             if (MoveToLiteralAttribute(attCollation)) {
854                 Debug.Assert(IsKeyword(atoms.DefaultCollation));
855                 string[] list = XmlConvert.SplitString(Value);
856                 int col;
857                 for (col = 0; col < list.Length; col++) {
858                     if (System.Xml.Xsl.Runtime.XmlCollation.Create(list[col], /*throw:*/false) != null) {
859                         break;
860                     }
861                 }
862                 if (col == list.Length) {
863                     ReportErrorFC(/*[XTSE0125]*/Res.Xslt_CollationSyntax);
864                 } else {
865                     if (list[col] != XmlReservedNs.NsCollCodePoint) {
866                         ReportNYI(atoms.DefaultCollation);
867                     }
868                 }
869             }
870         }
871
872         // ----------------------- ISourceLineInfo -----------------------
873
874         private static int PositionAdjustment(XmlNodeType nt) {
875             switch (nt) {
876             case XmlNodeType.Element:
877                 return 1;   // "<"
878             case XmlNodeType.CDATA:
879                 return 9;   // "<![CDATA["
880             case XmlNodeType.ProcessingInstruction:
881                 return 2;   // "<?"
882             case XmlNodeType.Comment:
883                 return 4;   // "<!--"
884             case XmlNodeType.EndElement:
885                 return 2;   // "</"
886             case XmlNodeType.EntityReference:
887                 return 1;   // "&"
888             default:
889                 return 0;
890             }
891         }
892
893         public ISourceLineInfo BuildLineInfo() {
894             return new SourceLineInfo(Uri, Start, End);
895         }
896
897         public ISourceLineInfo BuildNameLineInfo() {
898             if (readerLineInfo == null) {
899                 return BuildLineInfo();
900             }
901
902             // LocalName is checked against null since it is used to calculate QualifiedName used in turn to 
903             // calculate end position. 
904             // LocalName (and other cached properties) can be null only if nothing has been read from the reader. 
905             // This happens for instance when a reader which has already been closed or a reader positioned
906             // on the very last node of the document is passed to the ctor. 
907             if(LocalName == null) {
908                 // Fill up the current record to set all the properties used below.
909                 FillupRecord(ref records[currentRecord]);
910             }
911
912             Location start = Start;
913             int line = start.Line;
914             int pos  = start.Pos + PositionAdjustment(NodeType);
915             return new SourceLineInfo(Uri, new Location(line, pos), new Location(line, pos + QualifiedName.Length));
916         }
917
918         public ISourceLineInfo BuildReaderLineInfo() {
919             Location loc;
920
921             if (readerLineInfo != null)
922                 loc = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition);
923             else
924                 loc = new Location(0, 0);
925
926             return new SourceLineInfo(reader.BaseURI, loc, loc);
927         }
928
929         // Resolve prefix, return null and report an error if not found
930         public string LookupXmlNamespace(string prefix) {
931             Debug.Assert(prefix != null);
932             string nsUri = scopeManager.LookupNamespace(prefix);
933             if (nsUri != null) {
934                 Debug.Assert(Ref.Equal(atoms.NameTable.Get(nsUri), nsUri), "Namespaces must be atomized");
935                 return nsUri;
936             }
937             if (prefix.Length == 0) {
938                 return string.Empty;
939             }
940             ReportError(/*[XT0280]*/Res.Xslt_InvalidPrefix, prefix);
941             return null;
942         }
943
944         // ---------------------- Error Handling ----------------------
945
946         public void ReportError(string res, params string[] args) {
947             compiler.ReportError(BuildNameLineInfo(), res, args);
948         }
949
950         public void ReportErrorFC(string res, params string[] args) {
951             if (!ForwardCompatibility) {
952                 compiler.ReportError(BuildNameLineInfo(), res, args);
953             }
954         }
955
956         public void ReportWarning(string res, params string[] args) {
957             compiler.ReportWarning(BuildNameLineInfo(), res, args);
958         }
959
960         private void ReportNYI(string arg) {
961             ReportErrorFC(Res.Xslt_NotYetImplemented, arg);
962         }
963
964         // -------------------------------- ContextInfo ------------------------------------
965
966         internal class ContextInfo {
967             public NsDecl           nsList;
968             public ISourceLineInfo  lineInfo;       // Line info for whole start tag
969             public ISourceLineInfo  elemNameLi;     // Line info for element name
970             public ISourceLineInfo  endTagLi;       // Line info for end tag or '/>'
971             private int             elemNameLength;
972
973             // Create ContextInfo based on existing line info (used during AST rewriting)
974             internal ContextInfo(ISourceLineInfo lineinfo) {
975                 this.elemNameLi = lineinfo;
976                 this.endTagLi = lineinfo;
977                 this.lineInfo = lineinfo;
978             }
979
980             public ContextInfo(XsltInput input) {
981                 elemNameLength = input.QualifiedName.Length;
982             }
983
984             public void AddNamespace(string prefix, string nsUri) {
985                 nsList = new NsDecl(nsList, prefix, nsUri);
986             }
987
988             public void SaveExtendedLineInfo(XsltInput input) {
989                 if (lineInfo.Start.Line == 0) {
990                     elemNameLi = endTagLi = null;
991                     return;
992                 }
993
994                 elemNameLi = new SourceLineInfo(
995                     lineInfo.Uri,
996                     lineInfo.Start.Line, lineInfo.Start.Pos + 1,  // "<"
997                     lineInfo.Start.Line, lineInfo.Start.Pos + 1 + elemNameLength
998                 );
999
1000                 if (!input.IsEmptyElement) {
1001                     Debug.Assert(input.NodeType == XmlNodeType.EndElement);
1002                     endTagLi = input.BuildLineInfo();
1003                 } else {
1004                     Debug.Assert(input.NodeType == XmlNodeType.Element || input.NodeType == XmlNodeType.Attribute);
1005                     endTagLi = new EmptyElementEndTag(lineInfo);
1006                 }
1007             }
1008
1009             // We need this wrapper class because elementTagLi is not yet calculated
1010             internal class EmptyElementEndTag : ISourceLineInfo {
1011                 private ISourceLineInfo elementTagLi;
1012
1013                 public EmptyElementEndTag(ISourceLineInfo elementTagLi) {
1014                     this.elementTagLi = elementTagLi;
1015                 }
1016
1017                 public string Uri       { get { return elementTagLi.Uri;        } }
1018                 public bool IsNoSource  { get { return elementTagLi.IsNoSource; } }
1019                 public Location Start   { get { return new Location(elementTagLi.End.Line, elementTagLi.End.Pos - 2); } }
1020                 public Location End     { get { return elementTagLi.End ; } }
1021             }
1022         }
1023         internal struct Record {
1024             public string       localName ;
1025             public string       nsUri     ;
1026             public string       prefix    ;
1027             public string       value     ;
1028             public string       baseUri   ;
1029             public Location     start     ;
1030             public Location     valueStart;
1031             public Location     end       ;
1032             public string       QualifiedName  { get { return prefix.Length == 0 ? localName : string.Concat(prefix, ":", localName); } }
1033         }
1034     }
1035 }