536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1036 lines
47 KiB
C#
1036 lines
47 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="XsltInput.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">Microsoft</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
//#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<VarPar> 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<VarPar>(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<string, string> 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<string, string> 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,
|
|
// <xsl:stylesheet unknown="foo" version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
|
|
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; // "<![CDATA["
|
|
case XmlNodeType.ProcessingInstruction:
|
|
return 2; // "<?"
|
|
case XmlNodeType.Comment:
|
|
return 4; // "<!--"
|
|
case XmlNodeType.EndElement:
|
|
return 2; // "</"
|
|
case XmlNodeType.EntityReference:
|
|
return 1; // "&"
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public ISourceLineInfo BuildLineInfo() {
|
|
return new SourceLineInfo(Uri, Start, End);
|
|
}
|
|
|
|
public ISourceLineInfo BuildNameLineInfo() {
|
|
if (readerLineInfo == null) {
|
|
return BuildLineInfo();
|
|
}
|
|
|
|
// LocalName is checked against null since it is used to calculate QualifiedName used in turn to
|
|
// calculate end position.
|
|
// LocalName (and other cached properties) can be null only if nothing has been read from the reader.
|
|
// This happens for instance when a reader which has already been closed or a reader positioned
|
|
// on the very last node of the document is passed to the ctor.
|
|
if(LocalName == null) {
|
|
// Fill up the current record to set all the properties used below.
|
|
FillupRecord(ref records[currentRecord]);
|
|
}
|
|
|
|
Location start = Start;
|
|
int line = start.Line;
|
|
int pos = start.Pos + PositionAdjustment(NodeType);
|
|
return new SourceLineInfo(Uri, new Location(line, pos), new Location(line, pos + QualifiedName.Length));
|
|
}
|
|
|
|
public ISourceLineInfo BuildReaderLineInfo() {
|
|
Location loc;
|
|
|
|
if (readerLineInfo != null)
|
|
loc = new Location(readerLineInfo.LineNumber, readerLineInfo.LinePosition);
|
|
else
|
|
loc = new Location(0, 0);
|
|
|
|
return new SourceLineInfo(reader.BaseURI, loc, loc);
|
|
}
|
|
|
|
// Resolve prefix, return null and report an error if not found
|
|
public string LookupXmlNamespace(string prefix) {
|
|
Debug.Assert(prefix != null);
|
|
string nsUri = scopeManager.LookupNamespace(prefix);
|
|
if (nsUri != null) {
|
|
Debug.Assert(Ref.Equal(atoms.NameTable.Get(nsUri), nsUri), "Namespaces must be atomized");
|
|
return nsUri;
|
|
}
|
|
if (prefix.Length == 0) {
|
|
return string.Empty;
|
|
}
|
|
ReportError(/*[XT0280]*/Res.Xslt_InvalidPrefix, prefix);
|
|
return null;
|
|
}
|
|
|
|
// ---------------------- Error Handling ----------------------
|
|
|
|
public void ReportError(string res, params string[] args) {
|
|
compiler.ReportError(BuildNameLineInfo(), res, args);
|
|
}
|
|
|
|
public void ReportErrorFC(string res, params string[] args) {
|
|
if (!ForwardCompatibility) {
|
|
compiler.ReportError(BuildNameLineInfo(), res, args);
|
|
}
|
|
}
|
|
|
|
public void ReportWarning(string res, params string[] args) {
|
|
compiler.ReportWarning(BuildNameLineInfo(), res, args);
|
|
}
|
|
|
|
private void ReportNYI(string arg) {
|
|
ReportErrorFC(Res.Xslt_NotYetImplemented, arg);
|
|
}
|
|
|
|
// -------------------------------- ContextInfo ------------------------------------
|
|
|
|
internal class ContextInfo {
|
|
public NsDecl nsList;
|
|
public ISourceLineInfo lineInfo; // Line info for whole start tag
|
|
public ISourceLineInfo elemNameLi; // Line info for element name
|
|
public ISourceLineInfo endTagLi; // Line info for end tag or '/>'
|
|
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); } }
|
|
}
|
|
}
|
|
}
|