1 //------------------------------------------------------------------------------
2 // <copyright file="XsltInput.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
10 using System.Diagnostics;
12 using System.Xml.XPath;
13 using System.Collections.Generic;
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)
21 internal class XsltInput : IErrorHelper {
23 const int InitRecordsSize = 1;
25 const int InitRecordsSize = 1 + 21;
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;
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;
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;
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;
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; } }
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 ; } }
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;
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;
90 Record[] tmp = new Record[newSize];
91 Array.Copy(records, tmp, records.Length);
96 public bool FindStylesheetElement() {
97 if (! topLevelReader) {
98 if (reader.ReadState != ReadState.Interactive) {
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);
116 while (MoveToNextSibling() && nodeType == XmlNodeType.Whitespace) ;
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);
135 // return true to indicate that we reached XmlNodeType.Element node - potentially xsl:stylesheet element.
139 // return false to indicate that we did not reach XmlNodeType.Element node so it is not a valid stylesheet.
143 public void Finish() {
144 scopeManager.CheckEmpty();
146 if (topLevelReader) {
147 while (reader.ReadState == ReadState.Interactive) {
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;
161 rec.localName = atoms.NameTable.Add(rec.localName);
162 rec.nsUri = atoms.NameTable.Add(rec.nsUri );
163 rec.prefix = atoms.NameTable.Add(rec.prefix );
166 if (readerLineInfo != null) {
167 rec.start = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - PositionAdjustment(reader.NodeType));
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);
180 private void FillupTextRecord(ref Record rec) {
182 reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace ||
183 reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA
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;
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));
197 foreach (char ch in rec.value) {
200 if (prevChar != '\r') {
214 rec.end = new Location(line, pos + (isCDATA ? 3 : 0));
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;
227 if (readerLineInfo != null) {
228 rec.start = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - 1);
230 reader.ResolveEntity();
232 Debug.Assert(reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace);
233 rec.value = reader.Value;
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);
243 StringConcat strConcat = new StringConcat();
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);
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);
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);
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);
277 string lastText = string.Empty;
280 switch (reader.NodeType) {
281 case XmlNodeType.EntityReference:
282 reader.ResolveEntity();
284 case XmlNodeType.EndEntity:
287 Debug.Assert(reader.NodeType == XmlNodeType.Text, "Unexpected node type inside attribute value");
288 lastText = reader.Value;
289 strConcat.Concat(lastText);
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);
306 // --------------------
308 public bool MoveToFirstChild() {
309 Debug.Assert(nodeType == XmlNodeType.Element, "To call MoveToFirstChild() XsltI---- should be positioned on an Element.");
310 if (IsEmptyElement) {
313 return ReadNextSibling();
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();
321 return ReadNextSibling();
324 public void SkipNode() {
325 if (nodeType == XmlNodeType.Element && MoveToFirstChild()) {
328 } while (MoveToNextSibling());
332 private int ReadTextNodes() {
333 bool textPreserveWS = reader.XmlSpace == XmlSpace.Preserve;
334 bool textIsWhite = true;
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)) {
344 goto case XmlNodeType.SignificantWhitespace;
345 case XmlNodeType.Whitespace:
346 case XmlNodeType.SignificantWhitespace:
347 ExtendRecordBuffer(curTextNode);
348 FillupTextRecord(ref records[curTextNode]);
352 case XmlNodeType.EntityReference:
353 string local = reader.LocalName;
354 if (local.Length > 0 && (
356 local == "lt" || local == "gt" || local == "quot" || local == "apos"
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)) {
366 reader.ResolveEntity();
370 case XmlNodeType.EndEntity:
375 ! textIsWhite ? XmlNodeType.Text :
376 textPreserveWS ? XmlNodeType.SignificantWhitespace :
377 /*default: */ XmlNodeType.Whitespace
384 private bool ReadNextSibling() {
385 if (currentRecord < lastTextNode) {
386 Debug.Assert(nodeType == XmlNodeType.Text || nodeType == XmlNodeType.Whitespace || nodeType == XmlNodeType.SignificantWhitespace);
388 if (currentRecord == lastTextNode) {
389 lastTextNode = 0; // we are done with text nodes. Reset this counter
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
406 lastTextNode = numTextNodes - 1;
408 case XmlNodeType.Element:
409 scopeManager.EnterScope();
410 numAttributes = ReadElement();
412 case XmlNodeType.EndElement:
413 nodeType = XmlNodeType.EndElement;
414 isEmptyElement = false;
415 FillupRecord(ref records[0]);
417 SetRecordEnd(ref records[0]);
427 private int ReadElement() {
428 Debug.Assert(reader.NodeType == XmlNodeType.Element);
430 attributesRead = false;
431 FillupRecord(ref records[0]);
432 nodeType = XmlNodeType.Element;
433 isEmptyElement = reader.IsEmptyElement;
434 ctxInfo = new ContextInfo(this);
437 if (reader.MoveToFirstAttribute()) {
439 ExtendRecordBuffer(record);
440 if (ReadAttribute(ref records[record])) {
443 } while (reader.MoveToNextAttribute());
444 reader.MoveToElement();
447 SetRecordEnd(ref records[0]);
448 ctxInfo.lineInfo = BuildLineInfo();
453 public void MoveToElement() {
454 Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToElement() we should be positioned on Element or Attribute");
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;
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;
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;
486 public bool IsRequiredAttribute(int attNum) {
487 return (attributes[attNum].flags & (compiler.Version == 2 ? XsltLoader.V2Req : XsltLoader.V1Req)) != 0;
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;
495 public struct DelayedQName {
498 public DelayedQName(ref Record rec) {
499 this.prefix = rec.prefix;
500 this.localName = rec.localName;
502 public static implicit operator string(DelayedQName qn) {
503 return qn.prefix.Length == 0 ? qn.localName : (qn.prefix + ':' + qn.localName);
507 public DelayedQName ElementName {
509 Debug.Assert(nodeType == XmlNodeType.Element || nodeType == XmlNodeType.EndElement, "Input is positioned on element or attribute");
510 return new DelayedQName(ref records[0]);
514 // -------------------- Keywords testing --------------------
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(); }
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
533 public bool CanHaveApplyImports {
534 get { return scopeManager.CanHaveApplyImports; }
535 set { scopeManager.CanHaveApplyImports = value; }
538 public bool IsExtensionNamespace(string uri) {
539 Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
540 return scopeManager.IsExNamespace(uri);
543 public bool ForwardCompatibility {
545 Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
546 return scopeManager.ForwardCompatibility;
550 public bool BackwardCompatibility {
552 Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes");
553 return scopeManager.BackwardCompatibility;
557 public XslVersion XslVersion {
558 get { return scopeManager.ForwardCompatibility ? XslVersion.ForwardsCompatible : XslVersion.Current; }
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);
575 private void SetVersion(double version) {
576 if (compiler.Version == 0) {
578 compiler.Version = version < 2.0 ? 1 : 2;
580 compiler.Version = 1;
584 if (compiler.Version == 1) {
585 scopeManager.BackwardCompatibility = false;
586 scopeManager.ForwardCompatibility = (version != 1.0);
588 scopeManager.BackwardCompatibility = version < 2;
589 scopeManager.ForwardCompatibility = 2 < version;
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
598 public struct XsltAttribute {
601 public XsltAttribute(string name, int flags) {
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];
612 static private XsltAttribute[] noAttributes = new XsltAttribute[]{};
613 public ContextInfo GetAttributes() {
614 return GetAttributes(noAttributes);
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;
624 // Standard Attributes:
625 int attExtension = 0;
627 int attNamespace = 0;
628 int attCollation = 0;
631 bool isXslOutput = IsXsltNamespace() && IsKeyword(atoms.Output);
632 bool SS = IsXsltNamespace() && (IsKeyword(atoms.Stylesheet) || IsKeyword(atoms.Transform));
633 bool V2 = compiler.Version == 2;
635 for (int i = 0; i < attributes.Length; i++) {
636 xsltAttributeNumber[i] = 0;
639 compiler.EnterForwardsCompatible();
640 if (SS || V2 && !isXslOutput) {
641 for (int i = 1; MoveToAttributeBase(i); i++) {
642 if (IsNullNamespace() && IsKeyword(atoms.Version)) {
648 if (compiler.Version == 0) {
649 Debug.Assert(SS, "First we parse xsl:stylesheet element");
656 V2 = compiler.Version == 2;
657 int OptOrReq = V2 ? XsltLoader.V2Opt | XsltLoader.V2Req : XsltLoader.V1Opt | XsltLoader.V1Req;
659 for (int attNum = 1; MoveToAttributeBase(attNum); attNum++) {
660 if (IsNullNamespace()) {
661 string localName = LocalName;
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;
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);
679 } else if (IsXsltNamespace()) {
680 ReportError(/*[XT0090]*/Res.Xslt_InvalidAttribute, QualifiedName, records[0].QualifiedName);
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'.
689 attributesRead = true;
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);
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);
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;
710 compiler.Version == 2 && (flags & XsltLoader.V2Req) != 0 ||
711 compiler.Version == 1 && (flags & XsltLoader.V1Req) != 0 && (!ForwardCompatibility || (flags & XsltLoader.V2Req) != 0)
713 ReportError(/*[XT_001]*/Res.Xslt_MissingAttribute, attributes[i].name);
721 public ContextInfo GetLiteralAttributes(bool asStylesheet) {
722 Debug.Assert(NodeType == XmlNodeType.Element);
724 // Standard Attributes:
726 int attExtension = 0;
728 int attNamespace = 0;
729 int attCollation = 0;
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; }
744 attributesRead = true;
745 this.MoveToElement();
747 if (attVersion != 0) {
748 // Enable forwards-compatible behavior if version attribute is not "1.0"
749 SetVersion(attVersion);
752 ReportError(Ref.Equal(NamespaceUri, atoms.UriWdXsl) && Ref.Equal(LocalName, atoms.Stylesheet) ?
753 /*[XT_025]*/Res.Xslt_WdXslNamespace : /*[XT0150]*/Res.Xslt_WrongStylesheetElement
763 // Parse xsl:extension-element-prefixes attribute (now that forwards-compatible mode is known)
764 InsertExNamespaces(attExtension, ctxInfo, /*extensions:*/true);
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);
776 InsertExNamespaces(attExclude, ctxInfo, /*extensions:*/false);
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;
789 for (int i = 1; MoveToAttributeBase(i); i++) {
790 if (IsNullNamespace() && IsKeyword(atoms.Version)) {
796 attributesRead = true;
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
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);
822 list[idx] = this.LookupXmlNamespace(list[idx]);
825 if (!compiler.ExitForwardsCompatible(this.ForwardCompatibility)) {
826 // There were errors in the list, ignore the whole list
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
834 this.scopeManager.AddExNamespace(list[idx]); // At Load time we need to know Extencion namespaces to ignore such literal elements.
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);
852 private void SetDefaultCollation(int attCollation) {
853 if (MoveToLiteralAttribute(attCollation)) {
854 Debug.Assert(IsKeyword(atoms.DefaultCollation));
855 string[] list = XmlConvert.SplitString(Value);
857 for (col = 0; col < list.Length; col++) {
858 if (System.Xml.Xsl.Runtime.XmlCollation.Create(list[col], /*throw:*/false) != null) {
862 if (col == list.Length) {
863 ReportErrorFC(/*[XTSE0125]*/Res.Xslt_CollationSyntax);
865 if (list[col] != XmlReservedNs.NsCollCodePoint) {
866 ReportNYI(atoms.DefaultCollation);
872 // ----------------------- ISourceLineInfo -----------------------
874 private static int PositionAdjustment(XmlNodeType nt) {
876 case XmlNodeType.Element:
878 case XmlNodeType.CDATA:
879 return 9; // "<![CDATA["
880 case XmlNodeType.ProcessingInstruction:
882 case XmlNodeType.Comment:
884 case XmlNodeType.EndElement:
886 case XmlNodeType.EntityReference:
893 public ISourceLineInfo BuildLineInfo() {
894 return new SourceLineInfo(Uri, Start, End);
897 public ISourceLineInfo BuildNameLineInfo() {
898 if (readerLineInfo == null) {
899 return BuildLineInfo();
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]);
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));
918 public ISourceLineInfo BuildReaderLineInfo() {
921 if (readerLineInfo != null)
922 loc = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition);
924 loc = new Location(0, 0);
926 return new SourceLineInfo(reader.BaseURI, loc, loc);
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);
934 Debug.Assert(Ref.Equal(atoms.NameTable.Get(nsUri), nsUri), "Namespaces must be atomized");
937 if (prefix.Length == 0) {
940 ReportError(/*[XT0280]*/Res.Xslt_InvalidPrefix, prefix);
944 // ---------------------- Error Handling ----------------------
946 public void ReportError(string res, params string[] args) {
947 compiler.ReportError(BuildNameLineInfo(), res, args);
950 public void ReportErrorFC(string res, params string[] args) {
951 if (!ForwardCompatibility) {
952 compiler.ReportError(BuildNameLineInfo(), res, args);
956 public void ReportWarning(string res, params string[] args) {
957 compiler.ReportWarning(BuildNameLineInfo(), res, args);
960 private void ReportNYI(string arg) {
961 ReportErrorFC(Res.Xslt_NotYetImplemented, arg);
964 // -------------------------------- ContextInfo ------------------------------------
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;
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;
980 public ContextInfo(XsltInput input) {
981 elemNameLength = input.QualifiedName.Length;
984 public void AddNamespace(string prefix, string nsUri) {
985 nsList = new NsDecl(nsList, prefix, nsUri);
988 public void SaveExtendedLineInfo(XsltInput input) {
989 if (lineInfo.Start.Line == 0) {
990 elemNameLi = endTagLi = null;
994 elemNameLi = new SourceLineInfo(
996 lineInfo.Start.Line, lineInfo.Start.Pos + 1, // "<"
997 lineInfo.Start.Line, lineInfo.Start.Pos + 1 + elemNameLength
1000 if (!input.IsEmptyElement) {
1001 Debug.Assert(input.NodeType == XmlNodeType.EndElement);
1002 endTagLi = input.BuildLineInfo();
1004 Debug.Assert(input.NodeType == XmlNodeType.Element || input.NodeType == XmlNodeType.Attribute);
1005 endTagLi = new EmptyElementEndTag(lineInfo);
1009 // We need this wrapper class because elementTagLi is not yet calculated
1010 internal class EmptyElementEndTag : ISourceLineInfo {
1011 private ISourceLineInfo elementTagLi;
1013 public EmptyElementEndTag(ISourceLineInfo elementTagLi) {
1014 this.elementTagLi = elementTagLi;
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 ; } }
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); } }