//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft //------------------------------------------------------------------------------ //#define XSLT2 using System.Diagnostics; using System.Text; using System.Xml.XPath; using System.Collections.Generic; namespace System.Xml.Xsl.Xslt { using Res = System.Xml.Utils.Res; using StringConcat = System.Xml.Xsl.Runtime.StringConcat; // a) Forward only, one pass. // b) You should call MoveToFirstChildren on nonempty element node. (or may be skip) internal class XsltInput : IErrorHelper { #if DEBUG const int InitRecordsSize = 1; #else const int InitRecordsSize = 1 + 21; #endif private XmlReader reader; private IXmlLineInfo readerLineInfo; private bool topLevelReader; private CompilerScopeManager scopeManager; private KeywordsTable atoms; private Compiler compiler; private bool reatomize; // Cached properties. MoveTo* functions set them. private XmlNodeType nodeType; private Record[] records = new Record[InitRecordsSize]; private int currentRecord; private bool isEmptyElement; private int lastTextNode; private int numAttributes; private ContextInfo ctxInfo; private bool attributesRead; public XsltInput(XmlReader reader, Compiler compiler, KeywordsTable atoms) { Debug.Assert(reader != null); Debug.Assert(atoms != null); EnsureExpandEntities(reader); IXmlLineInfo xmlLineInfo = reader as IXmlLineInfo; this.atoms = atoms; this.reader = reader; this.reatomize = reader.NameTable != atoms.NameTable; this.readerLineInfo = (xmlLineInfo != null && xmlLineInfo.HasLineInfo()) ? xmlLineInfo : null; this.topLevelReader = reader.ReadState == ReadState.Initial; this.scopeManager = new CompilerScopeManager(atoms); this.compiler = compiler; this.nodeType = XmlNodeType.Document; } // Cached properties public XmlNodeType NodeType { get { return nodeType == XmlNodeType.Element && 0 < currentRecord ? XmlNodeType.Attribute : nodeType; } } public string LocalName { get { return records[currentRecord].localName ;} } public string NamespaceUri { get { return records[currentRecord].nsUri ;} } public string Prefix { get { return records[currentRecord].prefix ;} } public string Value { get { return records[currentRecord].value ;} } public string BaseUri { get { return records[currentRecord].baseUri ;} } public string QualifiedName { get { return records[currentRecord].QualifiedName ;} } public bool IsEmptyElement { get { return isEmptyElement; } } public string Uri { get { return records[currentRecord].baseUri ; } } public Location Start { get { return records[currentRecord].start ; } } public Location End { get { return records[currentRecord].end ; } } private static void EnsureExpandEntities(XmlReader reader) { XmlTextReader tr = reader as XmlTextReader; if (tr != null && tr.EntityHandling != EntityHandling.ExpandEntities) { Debug.Assert(tr.Settings == null, "XmlReader created with XmlReader.Create should always expand entities."); tr.EntityHandling = EntityHandling.ExpandEntities; } } private void ExtendRecordBuffer(int position) { if (records.Length <= position) { int newSize = records.Length * 2; if (newSize <= position) { newSize = position + 1; } Record[] tmp = new Record[newSize]; Array.Copy(records, tmp, records.Length); records = tmp; } } public bool FindStylesheetElement() { if (! topLevelReader) { if (reader.ReadState != ReadState.Interactive) { return false; } } // The stylesheet may be an embedded stylesheet. If this is the case the reader will be in Interactive state and should be // positioned on xsl:stylesheet element (or any preceding whitespace) but there also can be namespaces defined on one // of the ancestor nodes. These namespace definitions have to be copied to the xsl:stylesheet element scope. Otherwise it // will not be possible to resolve them later and loading the stylesheet will end up with throwing an exception. IDictionary namespacesInScope = null; if (reader.ReadState == ReadState.Interactive) { // This may be an embedded stylesheet - store namespaces in scope IXmlNamespaceResolver nsResolver = reader as IXmlNamespaceResolver; if (nsResolver != null) { namespacesInScope = nsResolver.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml); } } while (MoveToNextSibling() && nodeType == XmlNodeType.Whitespace) ; // An Element node was reached. Potentially this is xsl:stylesheet instruction. if (nodeType == XmlNodeType.Element) { // If namespacesInScope is not null then the stylesheet being read is an embedded stylesheet that can have namespaces // defined outside of xsl:stylesheet instruction. In this case the namespace definitions collected above have to be added // to the element scope. if (namespacesInScope != null) { foreach (KeyValuePair prefixNamespacePair in namespacesInScope) { // The namespace could be redefined on the element we just read. If this is the case scopeManager already has // namespace definition for this prefix and the old definition must not be added to the scope. if (scopeManager.LookupNamespace(prefixNamespacePair.Key) == null) { string nsAtomizedValue = atoms.NameTable.Add(prefixNamespacePair.Value); scopeManager.AddNsDeclaration(prefixNamespacePair.Key, nsAtomizedValue); ctxInfo.AddNamespace(prefixNamespacePair.Key, nsAtomizedValue); } } } // return true to indicate that we reached XmlNodeType.Element node - potentially xsl:stylesheet element. return true; } // return false to indicate that we did not reach XmlNodeType.Element node so it is not a valid stylesheet. return false; } public void Finish() { scopeManager.CheckEmpty(); if (topLevelReader) { while (reader.ReadState == ReadState.Interactive) { reader.Skip(); } } } private void FillupRecord(ref Record rec) { rec.localName = reader.LocalName; rec.nsUri = reader.NamespaceURI; rec.prefix = reader.Prefix; rec.value = reader.Value; rec.baseUri = reader.BaseURI; if (reatomize) { rec.localName = atoms.NameTable.Add(rec.localName); rec.nsUri = atoms.NameTable.Add(rec.nsUri ); rec.prefix = atoms.NameTable.Add(rec.prefix ); } if (readerLineInfo != null) { rec.start = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - PositionAdjustment(reader.NodeType)); } } private void SetRecordEnd(ref Record rec) { if (readerLineInfo != null) { rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - PositionAdjustment(reader.NodeType)); if (reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.start)) { rec.end = new Location(rec.start.Line, int.MaxValue); } } } private void FillupTextRecord(ref Record rec) { Debug.Assert( reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace || reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA ); rec.localName = string.Empty; rec.nsUri = string.Empty; rec.prefix = string.Empty; rec.value = reader.Value; rec.baseUri = reader.BaseURI; if (readerLineInfo != null) { bool isCDATA = (reader.NodeType == XmlNodeType.CDATA); int line = readerLineInfo.LineNumber; int pos = readerLineInfo.LinePosition; rec.start = new Location(line, pos - (isCDATA ? 9 : 0)); char prevChar = ' '; foreach (char ch in rec.value) { switch (ch) { case '\n': if (prevChar != '\r') { goto case '\r'; } break; case '\r': line ++; pos = 1; break; default : pos ++; break; } prevChar = ch; } rec.end = new Location(line, pos + (isCDATA ? 3 : 0)); } } private void FillupCharacterEntityRecord(ref Record rec) { Debug.Assert(reader.NodeType == XmlNodeType.EntityReference); string local = reader.LocalName; Debug.Assert(local[0] == '#' || local == "lt" || local == "gt" || local == "quot" || local == "apos"); rec.localName = string.Empty; rec.nsUri = string.Empty; rec.prefix = string.Empty; rec.baseUri = reader.BaseURI; if (readerLineInfo != null) { rec.start = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition - 1); } reader.ResolveEntity(); reader.Read(); Debug.Assert(reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace); rec.value = reader.Value; reader.Read(); Debug.Assert(reader.NodeType == XmlNodeType.EndEntity); if (readerLineInfo != null) { int line = readerLineInfo.LineNumber; int pos = readerLineInfo.LinePosition; rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + 1); } } StringConcat strConcat = new StringConcat(); // returns false if attribute is actualy namespace private bool ReadAttribute(ref Record rec) { Debug.Assert(reader.NodeType == XmlNodeType.Attribute, "reader.NodeType == XmlNodeType.Attribute"); FillupRecord(ref rec); if (Ref.Equal(rec.prefix, atoms.Xmlns)) { // xmlns:foo="NS_FOO" string atomizedValue = atoms.NameTable.Add(reader.Value); if (!Ref.Equal(rec.localName, atoms.Xml)) { scopeManager.AddNsDeclaration(rec.localName, atomizedValue); ctxInfo.AddNamespace(rec.localName, atomizedValue); } return false; } else if (rec.prefix.Length == 0 && Ref.Equal(rec.localName, atoms.Xmlns)) { // xmlns="NS_FOO" string atomizedValue = atoms.NameTable.Add(reader.Value); scopeManager.AddNsDeclaration(string.Empty, atomizedValue); ctxInfo.AddNamespace(string.Empty, atomizedValue); return false; } /* Read Attribute Value */ { if (!reader.ReadAttributeValue()) { // XmlTextReader never returns false from first call to ReadAttributeValue() rec.value = string.Empty; SetRecordEnd(ref rec); return true; } if (readerLineInfo != null) { int correction = (reader.NodeType == XmlNodeType.EntityReference) ? -2 : -1; rec.valueStart = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + correction); if (reader.BaseURI != rec.baseUri || rec.valueStart.LessOrEqual(rec.start)) { int nameLength = ((rec.prefix.Length != 0) ? rec.prefix.Length + 1 : 0) + rec.localName.Length; rec.end = new Location(rec.start.Line, rec.start.Pos + nameLength + 1); } } string lastText = string.Empty; strConcat.Clear(); do { switch (reader.NodeType) { case XmlNodeType.EntityReference: reader.ResolveEntity(); break; case XmlNodeType.EndEntity: break; default: Debug.Assert(reader.NodeType == XmlNodeType.Text, "Unexpected node type inside attribute value"); lastText = reader.Value; strConcat.Concat(lastText); break; } } while (reader.ReadAttributeValue()); rec.value = strConcat.GetResult(); if (readerLineInfo != null) { Debug.Assert(reader.NodeType != XmlNodeType.EntityReference); int correction = ((reader.NodeType == XmlNodeType.EndEntity) ? 1 : lastText.Length) + 1; rec.end = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition + correction); if (reader.BaseURI != rec.baseUri || rec.end.LessOrEqual(rec.valueStart)) { rec.end = new Location(rec.start.Line, int.MaxValue); } } } return true; } // -------------------- public bool MoveToFirstChild() { Debug.Assert(nodeType == XmlNodeType.Element, "To call MoveToFirstChild() XsltI---- should be positioned on an Element."); if (IsEmptyElement) { return false; } return ReadNextSibling(); } public bool MoveToNextSibling() { Debug.Assert(nodeType != XmlNodeType.Element || IsEmptyElement, "On non-empty elements we should call MoveToFirstChild()"); if (nodeType == XmlNodeType.Element || nodeType == XmlNodeType.EndElement) { scopeManager.ExitScope(); } return ReadNextSibling(); } public void SkipNode() { if (nodeType == XmlNodeType.Element && MoveToFirstChild()) { do { SkipNode(); } while (MoveToNextSibling()); } } private int ReadTextNodes() { bool textPreserveWS = reader.XmlSpace == XmlSpace.Preserve; bool textIsWhite = true; int curTextNode = 0; do { switch (reader.NodeType) { case XmlNodeType.Text: // XLinq reports WS nodes as Text so we need to analyze them here case XmlNodeType.CDATA: if (textIsWhite && ! XmlCharType.Instance.IsOnlyWhitespace(reader.Value)) { textIsWhite = false; } goto case XmlNodeType.SignificantWhitespace; case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: ExtendRecordBuffer(curTextNode); FillupTextRecord(ref records[curTextNode]); reader.Read(); curTextNode++; break; case XmlNodeType.EntityReference: string local = reader.LocalName; if (local.Length > 0 && ( local[0] == '#' || local == "lt" || local == "gt" || local == "quot" || local == "apos" )) { // Special treatment for character and built-in entities ExtendRecordBuffer(curTextNode); FillupCharacterEntityRecord(ref records[curTextNode]); if (textIsWhite && !XmlCharType.Instance.IsOnlyWhitespace(records[curTextNode].value)) { textIsWhite = false; } curTextNode++; } else { reader.ResolveEntity(); reader.Read(); } break; case XmlNodeType.EndEntity: reader.Read(); break; default: this.nodeType = ( ! textIsWhite ? XmlNodeType.Text : textPreserveWS ? XmlNodeType.SignificantWhitespace : /*default: */ XmlNodeType.Whitespace ); return curTextNode; } } while (true); } private bool ReadNextSibling() { if (currentRecord < lastTextNode) { Debug.Assert(nodeType == XmlNodeType.Text || nodeType == XmlNodeType.Whitespace || nodeType == XmlNodeType.SignificantWhitespace); currentRecord++; if (currentRecord == lastTextNode) { lastTextNode = 0; // we are done with text nodes. Reset this counter } return true; } currentRecord = 0; while (! reader.EOF) { switch (reader.NodeType) { case XmlNodeType.Text: case XmlNodeType.CDATA: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: case XmlNodeType.EntityReference: int numTextNodes = ReadTextNodes(); if (numTextNodes == 0) { // Most likely this was Entity that starts from non-text node continue; } lastTextNode = numTextNodes - 1; return true; case XmlNodeType.Element: scopeManager.EnterScope(); numAttributes = ReadElement(); return true; case XmlNodeType.EndElement: nodeType = XmlNodeType.EndElement; isEmptyElement = false; FillupRecord(ref records[0]); reader.Read(); SetRecordEnd(ref records[0]); return false; default: reader.Read(); break; } } return false; } private int ReadElement() { Debug.Assert(reader.NodeType == XmlNodeType.Element); attributesRead = false; FillupRecord(ref records[0]); nodeType = XmlNodeType.Element; isEmptyElement = reader.IsEmptyElement; ctxInfo = new ContextInfo(this); int record = 1; if (reader.MoveToFirstAttribute()) { do { ExtendRecordBuffer(record); if (ReadAttribute(ref records[record])) { record++; } } while (reader.MoveToNextAttribute()); reader.MoveToElement(); } reader.Read(); SetRecordEnd(ref records[0]); ctxInfo.lineInfo = BuildLineInfo(); attributes = null; return record - 1; } public void MoveToElement() { Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToElement() we should be positioned on Element or Attribute"); currentRecord = 0; } private bool MoveToAttributeBase(int attNum) { Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute"); if (0 < attNum && attNum <= numAttributes) { currentRecord = attNum; return true; } else { currentRecord = 0; return false; } } public bool MoveToLiteralAttribute(int attNum) { Debug.Assert(nodeType == XmlNodeType.Element, "For MoveToLiteralAttribute() we should be positioned on Element or Attribute"); if (0 < attNum && attNum <= numAttributes) { currentRecord = attNum; return true; } else { currentRecord = 0; return false; } } public bool MoveToXsltAttribute(int attNum, string attName) { Debug.Assert(attributes != null && attributes[attNum].name == attName, "Attribute numbering error."); this.currentRecord = xsltAttributeNumber[attNum]; return this.currentRecord != 0; } public bool IsRequiredAttribute(int attNum) { return (attributes[attNum].flags & (compiler.Version == 2 ? XsltLoader.V2Req : XsltLoader.V1Req)) != 0; } public bool AttributeExists(int attNum, string attName) { Debug.Assert(attributes != null && attributes[attNum].name == attName, "Attribute numbering error."); return xsltAttributeNumber[attNum] != 0; } public struct DelayedQName { string prefix ; string localName; public DelayedQName(ref Record rec) { this.prefix = rec.prefix; this.localName = rec.localName; } public static implicit operator string(DelayedQName qn) { return qn.prefix.Length == 0 ? qn.localName : (qn.prefix + ':' + qn.localName); } } public DelayedQName ElementName { get { Debug.Assert(nodeType == XmlNodeType.Element || nodeType == XmlNodeType.EndElement, "Input is positioned on element or attribute"); return new DelayedQName(ref records[0]); } } // -------------------- Keywords testing -------------------- public bool IsNs(string ns) { return Ref.Equal(ns, NamespaceUri); } public bool IsKeyword(string kwd) { return Ref.Equal(kwd, LocalName); } public bool IsXsltNamespace() { return IsNs(atoms.UriXsl); } public bool IsNullNamespace() { return IsNs(string.Empty); } public bool IsXsltAttribute(string kwd) { return IsKeyword(kwd) && IsNullNamespace(); } public bool IsXsltKeyword( string kwd) { return IsKeyword(kwd) && IsXsltNamespace(); } // -------------------- Scope Management -------------------- // See private class InputScopeManager bellow. // InputScopeManager handles some flags and values with respect of scope level where they as defined. // To parse XSLT style sheet we need the folloing values: // BackwardCompatibility -- this flag is set when compiler.version==2 && xsl:version<2. // ForwardCompatibility -- this flag is set when compiler.version==2 && xsl:version>1 or compiler.version==1 && xsl:version!=1 // CanHaveApplyImports -- we allow xsl:apply-templates instruction to apear in any template with match!=null, but not inside xsl:for-each // so it can't be inside global variable and has initial value = false // ExtentionNamespace -- is defined by extension-element-prefixes attribute on LRE or xsl:stylesheet public bool CanHaveApplyImports { get { return scopeManager.CanHaveApplyImports; } set { scopeManager.CanHaveApplyImports = value; } } public bool IsExtensionNamespace(string uri) { Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes"); return scopeManager.IsExNamespace(uri); } public bool ForwardCompatibility { get { Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes"); return scopeManager.ForwardCompatibility; } } public bool BackwardCompatibility { get { Debug.Assert(nodeType != XmlNodeType.Element || attributesRead, "Should first read attributes"); return scopeManager.BackwardCompatibility; } } public XslVersion XslVersion { get { return scopeManager.ForwardCompatibility ? XslVersion.ForwardsCompatible : XslVersion.Current; } } private void SetVersion(int attVersion) { MoveToLiteralAttribute(attVersion); Debug.Assert(IsKeyword(atoms.Version)); double version = XPathConvert.StringToDouble(Value); if (double.IsNaN(version)) { ReportError(/*[XT0110]*/Res.Xslt_InvalidAttrValue, atoms.Version, Value); #if XSLT2 version = 2.0; #else version = 1.0; #endif } SetVersion(version); } private void SetVersion(double version) { if (compiler.Version == 0) { #if XSLT2 compiler.Version = version < 2.0 ? 1 : 2; #else compiler.Version = 1; #endif } if (compiler.Version == 1) { scopeManager.BackwardCompatibility = false; scopeManager.ForwardCompatibility = (version != 1.0); } else { scopeManager.BackwardCompatibility = version < 2; scopeManager.ForwardCompatibility = 2 < version; } } // --------------- GetAtributes(...) ------------------------- // All Xslt Instructions allows fixed set of attributes in null-ns, no in XSLT-ns and any in other ns. // In ForwardCompatibility mode we should ignore any of this problems. // We not use these functions for parseing LiteralResultElement and xsl:stylesheet public struct XsltAttribute { public string name; public int flags; public XsltAttribute(string name, int flags) { this.name = name; this.flags = flags; } } private XsltAttribute[] attributes = null; // Mapping of attribute names as they ordered in 'attributes' array // to there's numbers in actual stylesheet as they ordered in 'records' array private int[] xsltAttributeNumber = new int[21]; static private XsltAttribute[] noAttributes = new XsltAttribute[]{}; public ContextInfo GetAttributes() { return GetAttributes(noAttributes); } public ContextInfo GetAttributes(XsltAttribute[] attributes) { Debug.Assert(NodeType == XmlNodeType.Element); Debug.Assert(attributes.Length <= xsltAttributeNumber.Length); this.attributes = attributes; // temp hack to fix value? = new AttValue(records[values[?]].value); records[0].value = null; // Standard Attributes: int attExtension = 0; int attExclude = 0; int attNamespace = 0; int attCollation = 0; int attUseWhen = 0; bool isXslOutput = IsXsltNamespace() && IsKeyword(atoms.Output); bool SS = IsXsltNamespace() && (IsKeyword(atoms.Stylesheet) || IsKeyword(atoms.Transform)); bool V2 = compiler.Version == 2; for (int i = 0; i < attributes.Length; i++) { xsltAttributeNumber[i] = 0; } compiler.EnterForwardsCompatible(); if (SS || V2 && !isXslOutput) { for (int i = 1; MoveToAttributeBase(i); i++) { if (IsNullNamespace() && IsKeyword(atoms.Version)) { SetVersion(i); break; } } } if (compiler.Version == 0) { Debug.Assert(SS, "First we parse xsl:stylesheet element"); #if XSLT2 SetVersion(2.0); #else SetVersion(1.0); #endif } V2 = compiler.Version == 2; int OptOrReq = V2 ? XsltLoader.V2Opt | XsltLoader.V2Req : XsltLoader.V1Opt | XsltLoader.V1Req; for (int attNum = 1; MoveToAttributeBase(attNum); attNum++) { if (IsNullNamespace()) { string localName = LocalName; int kwd; for (kwd = 0; kwd < attributes.Length; kwd++) { if (Ref.Equal(localName, attributes[kwd].name) && (attributes[kwd].flags & OptOrReq) != 0) { xsltAttributeNumber[kwd] = attNum; break; } } if (kwd == attributes.Length) { if (Ref.Equal(localName, atoms.ExcludeResultPrefixes ) && (SS || V2)) {attExclude = attNum; } else if (Ref.Equal(localName, atoms.ExtensionElementPrefixes) && (SS || V2)) {attExtension = attNum; } else if (Ref.Equal(localName, atoms.XPathDefaultNamespace ) && ( V2)) {attNamespace = attNum; } else if (Ref.Equal(localName, atoms.DefaultCollation ) && ( V2)) {attCollation = attNum; } else if (Ref.Equal(localName, atoms.UseWhen ) && ( V2)) {attUseWhen = attNum; } else { ReportError(/*[XT0090]*/Res.Xslt_InvalidAttribute, QualifiedName, records[0].QualifiedName); } } } else if (IsXsltNamespace()) { ReportError(/*[XT0090]*/Res.Xslt_InvalidAttribute, QualifiedName, records[0].QualifiedName); } else { // Ignore the attribute. // An element from the XSLT namespace may have any attribute not from the XSLT namespace, // provided that the expanded-name of the attribute has a non-null namespace URI. // For example, it may be 'xml:space'. } } attributesRead = true; // Ignore invalid attributes if forwards-compatible behavior is enabled. Note that invalid // attributes may encounter before ForwardCompatibility flag is set to true. For example, // compiler.ExitForwardsCompatible(ForwardCompatibility); InsertExNamespaces(attExtension, ctxInfo, /*extensions:*/ true ); InsertExNamespaces(attExclude , ctxInfo, /*extensions:*/ false); SetXPathDefaultNamespace(attNamespace); SetDefaultCollation(attCollation); if (attUseWhen != 0) { ReportNYI(atoms.UseWhen); } MoveToElement(); // Report missing mandatory attributes for (int i = 0; i < attributes.Length; i ++) { if (xsltAttributeNumber[i] == 0) { int flags = attributes[i].flags; if ( compiler.Version == 2 && (flags & XsltLoader.V2Req) != 0 || compiler.Version == 1 && (flags & XsltLoader.V1Req) != 0 && (!ForwardCompatibility || (flags & XsltLoader.V2Req) != 0) ) { ReportError(/*[XT_001]*/Res.Xslt_MissingAttribute, attributes[i].name); } } } return ctxInfo; } public ContextInfo GetLiteralAttributes(bool asStylesheet) { Debug.Assert(NodeType == XmlNodeType.Element); // Standard Attributes: int attVersion = 0; int attExtension = 0; int attExclude = 0; int attNamespace = 0; int attCollation = 0; int attUseWhen = 0; for (int i = 1; MoveToLiteralAttribute(i); i++) { if (IsXsltNamespace()) { string localName = LocalName; if (Ref.Equal(localName, atoms.Version )) {attVersion = i; } else if (Ref.Equal(localName, atoms.ExtensionElementPrefixes)) {attExtension = i; } else if (Ref.Equal(localName, atoms.ExcludeResultPrefixes )) {attExclude = i; } else if (Ref.Equal(localName, atoms.XPathDefaultNamespace )) {attNamespace = i; } else if (Ref.Equal(localName, atoms.DefaultCollation )) {attCollation = i; } else if (Ref.Equal(localName, atoms.UseWhen )) {attUseWhen = i; } } } attributesRead = true; this.MoveToElement(); if (attVersion != 0) { // Enable forwards-compatible behavior if version attribute is not "1.0" SetVersion(attVersion); } else { if (asStylesheet) { ReportError(Ref.Equal(NamespaceUri, atoms.UriWdXsl) && Ref.Equal(LocalName, atoms.Stylesheet) ? /*[XT_025]*/Res.Xslt_WdXslNamespace : /*[XT0150]*/Res.Xslt_WrongStylesheetElement ); #if XSLT2 SetVersion(2.0); #else SetVersion(1.0); #endif } } // Parse xsl:extension-element-prefixes attribute (now that forwards-compatible mode is known) InsertExNamespaces(attExtension, ctxInfo, /*extensions:*/true); if (! IsExtensionNamespace(records[0].nsUri)) { // Parse other attributes (now that it's known this is a literal result element) if (compiler.Version == 2) { SetXPathDefaultNamespace(attNamespace); SetDefaultCollation(attCollation); if (attUseWhen != 0) { ReportNYI(atoms.UseWhen); } } InsertExNamespaces(attExclude, ctxInfo, /*extensions:*/false); } return ctxInfo; } // Get just the 'version' attribute of an unknown XSLT instruction. All other attributes // are ignored since we do not want to report an error on each of them. public void GetVersionAttribute() { Debug.Assert(NodeType == XmlNodeType.Element && IsXsltNamespace()); bool V2 = compiler.Version == 2; if (V2) { for (int i = 1; MoveToAttributeBase(i); i++) { if (IsNullNamespace() && IsKeyword(atoms.Version)) { SetVersion(i); break; } } } attributesRead = true; } private void InsertExNamespaces(int attExPrefixes, ContextInfo ctxInfo, bool extensions) { // List of Extension namespaces are maintaned by XsltInput's ScopeManager and is used by IsExtensionNamespace() in XsltLoader.LoadLiteralResultElement() // Both Extension and Exclusion namespaces will not be coppied by LiteralResultElement. Logic of copping namespaces are in QilGenerator.CompileLiteralElement(). // At this time we will have different scope manager and need preserve all required information from load time to compile time. // Each XslNode contains list of NsDecls (nsList) wich stores prefix+namespaces pairs for each namespace decls as well as exclusion namespaces. // In addition it also contains Exclusion namespace. They are represented as (null+namespace). Special case is Exlusion "#all" represented as (null+null). //and Exclusion namespace if (MoveToLiteralAttribute(attExPrefixes)) { Debug.Assert(extensions ? IsKeyword(atoms.ExtensionElementPrefixes) : IsKeyword(atoms.ExcludeResultPrefixes)); string value = Value; if (value.Length != 0) { if (!extensions && compiler.Version != 1 && value == "#all") { ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, /*nsUri:*/null); // null, null means Exlusion #all } else { compiler.EnterForwardsCompatible(); string[] list = XmlConvert.SplitString(value); for (int idx = 0; idx < list.Length; idx++) { if (list[idx] == "#default") { list[idx] = this.LookupXmlNamespace(string.Empty); if (list[idx].Length == 0 && compiler.Version != 1 && !BackwardCompatibility) { ReportError(/*[XTSE0809]*/Res.Xslt_ExcludeDefault); } } else { list[idx] = this.LookupXmlNamespace(list[idx]); } } if (!compiler.ExitForwardsCompatible(this.ForwardCompatibility)) { // There were errors in the list, ignore the whole list return; } for (int idx = 0; idx < list.Length; idx++) { if (list[idx] != null) { ctxInfo.nsList = new NsDecl(ctxInfo.nsList, /*prefix:*/null, list[idx]); // null means that this Exlusion NS if (extensions) { this.scopeManager.AddExNamespace(list[idx]); // At Load time we need to know Extencion namespaces to ignore such literal elements. } } } } } } } private void SetXPathDefaultNamespace(int attNamespace) { if (MoveToLiteralAttribute(attNamespace)) { Debug.Assert(IsKeyword(atoms.XPathDefaultNamespace)); if (Value.Length != 0) { ReportNYI(atoms.XPathDefaultNamespace); } } } private void SetDefaultCollation(int attCollation) { if (MoveToLiteralAttribute(attCollation)) { Debug.Assert(IsKeyword(atoms.DefaultCollation)); string[] list = XmlConvert.SplitString(Value); int col; for (col = 0; col < list.Length; col++) { if (System.Xml.Xsl.Runtime.XmlCollation.Create(list[col], /*throw:*/false) != null) { break; } } if (col == list.Length) { ReportErrorFC(/*[XTSE0125]*/Res.Xslt_CollationSyntax); } else { if (list[col] != XmlReservedNs.NsCollCodePoint) { ReportNYI(atoms.DefaultCollation); } } } } // ----------------------- ISourceLineInfo ----------------------- private static int PositionAdjustment(XmlNodeType nt) { switch (nt) { case XmlNodeType.Element: return 1; // "<" case XmlNodeType.CDATA: return 9; // "' private int elemNameLength; // Create ContextInfo based on existing line info (used during AST rewriting) internal ContextInfo(ISourceLineInfo lineinfo) { this.elemNameLi = lineinfo; this.endTagLi = lineinfo; this.lineInfo = lineinfo; } public ContextInfo(XsltInput input) { elemNameLength = input.QualifiedName.Length; } public void AddNamespace(string prefix, string nsUri) { nsList = new NsDecl(nsList, prefix, nsUri); } public void SaveExtendedLineInfo(XsltInput input) { if (lineInfo.Start.Line == 0) { elemNameLi = endTagLi = null; return; } elemNameLi = new SourceLineInfo( lineInfo.Uri, lineInfo.Start.Line, lineInfo.Start.Pos + 1, // "<" lineInfo.Start.Line, lineInfo.Start.Pos + 1 + elemNameLength ); if (!input.IsEmptyElement) { Debug.Assert(input.NodeType == XmlNodeType.EndElement); endTagLi = input.BuildLineInfo(); } else { Debug.Assert(input.NodeType == XmlNodeType.Element || input.NodeType == XmlNodeType.Attribute); endTagLi = new EmptyElementEndTag(lineInfo); } } // We need this wrapper class because elementTagLi is not yet calculated internal class EmptyElementEndTag : ISourceLineInfo { private ISourceLineInfo elementTagLi; public EmptyElementEndTag(ISourceLineInfo elementTagLi) { this.elementTagLi = elementTagLi; } public string Uri { get { return elementTagLi.Uri; } } public bool IsNoSource { get { return elementTagLi.IsNoSource; } } public Location Start { get { return new Location(elementTagLi.End.Line, elementTagLi.End.Pos - 2); } } public Location End { get { return elementTagLi.End ; } } } } internal struct Record { public string localName ; public string nsUri ; public string prefix ; public string value ; public string baseUri ; public Location start ; public Location valueStart; public Location end ; public string QualifiedName { get { return prefix.Length == 0 ? localName : string.Concat(prefix, ":", localName); } } } } }