e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
523 lines
18 KiB
C#
523 lines
18 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="XslAst.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Xml.Xsl.Qil;
|
|
|
|
namespace System.Xml.Xsl.Xslt {
|
|
using ContextInfo = XsltInput.ContextInfo;
|
|
using XPathQilFactory = System.Xml.Xsl.XPath.XPathQilFactory;
|
|
|
|
// Set of classes that represent XSLT AST
|
|
|
|
// XSLT AST is a tree of nodes that represent content of xsl template.
|
|
// All nodes are subclasses of QilNode. This was done to keep NodeCtor and Text ctors
|
|
// the sames nodes thay will be in resulting QilExpression tree.
|
|
// So we have: ElementCtor, AttributeCtor, QilTextCtor, CommentCtor, PICtor, NamespaceDecl, List.
|
|
// Plus couple subclasses of XslNode that represent different xslt instructions
|
|
// including artifitial: Sort, ExNamespaceDecl, UseAttributeSets
|
|
|
|
internal enum XslNodeType {
|
|
Unknown = 0,
|
|
ApplyImports,
|
|
ApplyTemplates,
|
|
Attribute,
|
|
AttributeSet,
|
|
CallTemplate,
|
|
Choose,
|
|
Comment,
|
|
Copy,
|
|
CopyOf,
|
|
Element,
|
|
Error,
|
|
ForEach,
|
|
If,
|
|
Key,
|
|
List,
|
|
LiteralAttribute,
|
|
LiteralElement,
|
|
Message,
|
|
Nop,
|
|
Number,
|
|
Otherwise,
|
|
Param,
|
|
PI,
|
|
Sort,
|
|
Template,
|
|
Text,
|
|
UseAttributeSet,
|
|
ValueOf,
|
|
ValueOfDoe,
|
|
Variable,
|
|
WithParam,
|
|
}
|
|
|
|
internal class NsDecl {
|
|
public readonly NsDecl Prev;
|
|
public readonly string Prefix; // Empty string denotes the default namespace, null - extension or excluded namespace
|
|
public readonly string NsUri; // null means "#all" -- all namespace defined above this one are excluded.
|
|
|
|
public NsDecl(NsDecl prev, string prefix, string nsUri) {
|
|
Debug.Assert(nsUri != null || Prefix == null);
|
|
this.Prev = prev;
|
|
this.Prefix = prefix;
|
|
this.NsUri = nsUri;
|
|
}
|
|
}
|
|
|
|
internal class XslNode {
|
|
public readonly XslNodeType NodeType;
|
|
public ISourceLineInfo SourceLine;
|
|
public NsDecl Namespaces;
|
|
public readonly QilName Name; // name or mode
|
|
public readonly object Arg; // select or test or terminate or stylesheet;-)
|
|
public readonly XslVersion XslVersion;
|
|
public XslFlags Flags;
|
|
private List<XslNode> content;
|
|
|
|
public XslNode(XslNodeType nodeType, QilName name, object arg, XslVersion xslVer) {
|
|
this.NodeType = nodeType;
|
|
this.Name = name;
|
|
this.Arg = arg;
|
|
this.XslVersion = xslVer;
|
|
}
|
|
|
|
public XslNode(XslNodeType nodeType) {
|
|
this.NodeType = nodeType;
|
|
this.XslVersion = XslVersion.Current;
|
|
}
|
|
|
|
public string Select { get { return (string)Arg; } }
|
|
public bool ForwardsCompatible { get { return XslVersion == XslVersion.ForwardsCompatible; } }
|
|
|
|
// -------------------------------- Content Management --------------------------------
|
|
|
|
private static readonly IList<XslNode> EmptyList = new List<XslNode>().AsReadOnly();
|
|
|
|
public IList<XslNode> Content {
|
|
get { return content ?? EmptyList; }
|
|
}
|
|
|
|
public void SetContent(List<XslNode> content) {
|
|
this.content = content;
|
|
}
|
|
|
|
public void AddContent(XslNode node) {
|
|
Debug.Assert(node != null);
|
|
if (content == null) {
|
|
content = new List<XslNode>();
|
|
}
|
|
content.Add(node);
|
|
}
|
|
|
|
public void InsertContent(IEnumerable<XslNode> collection) {
|
|
if (content == null) {
|
|
content = new List<XslNode>(collection);
|
|
} else {
|
|
content.InsertRange(0, collection);
|
|
}
|
|
}
|
|
|
|
internal string TraceName {
|
|
get {
|
|
#if DEBUG
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
string nodeTypeName;
|
|
switch (NodeType) {
|
|
case XslNodeType.AttributeSet : nodeTypeName = "attribute-set"; break;
|
|
case XslNodeType.Template : nodeTypeName = "template"; break;
|
|
case XslNodeType.Param : nodeTypeName = "param"; break;
|
|
case XslNodeType.Variable : nodeTypeName = "variable"; break;
|
|
case XslNodeType.WithParam : nodeTypeName = "with-param"; break;
|
|
default : nodeTypeName = NodeType.ToString(); break;
|
|
}
|
|
sb.Append(nodeTypeName);
|
|
if (Name != null) {
|
|
sb.Append(' ');
|
|
sb.Append(Name.QualifiedName);
|
|
}
|
|
ISourceLineInfo lineInfo = SourceLine;
|
|
if (lineInfo == null && NodeType == XslNodeType.AttributeSet) {
|
|
lineInfo = Content[0].SourceLine;
|
|
Debug.Assert(lineInfo != null);
|
|
}
|
|
if (lineInfo != null) {
|
|
string fileName = SourceLineInfo.GetFileName(lineInfo.Uri);
|
|
int idx = fileName.LastIndexOf(System.IO.Path.DirectorySeparatorChar) + 1;
|
|
sb.Append(" (");
|
|
sb.Append(fileName, idx, fileName.Length - idx);
|
|
sb.Append(':');
|
|
sb.Append(lineInfo.Start.Line);
|
|
sb.Append(')');
|
|
}
|
|
return sb.ToString();
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
internal abstract class ProtoTemplate : XslNode {
|
|
public QilFunction Function; // Compiled body
|
|
|
|
public ProtoTemplate(XslNodeType nt, QilName name, XslVersion xslVer) : base(nt, name, null, xslVer) {}
|
|
public abstract string GetDebugName();
|
|
}
|
|
|
|
internal enum CycleCheck {
|
|
NotStarted = 0,
|
|
Processing = 1,
|
|
Completed = 2,
|
|
}
|
|
|
|
internal class AttributeSet : ProtoTemplate {
|
|
public CycleCheck CycleCheck; // Used to detect circular references
|
|
|
|
public AttributeSet(QilName name, XslVersion xslVer) : base(XslNodeType.AttributeSet, name, xslVer) {}
|
|
|
|
public override string GetDebugName() {
|
|
StringBuilder dbgName = new StringBuilder();
|
|
dbgName.Append("<xsl:attribute-set name=\"");
|
|
dbgName.Append(Name.QualifiedName);
|
|
dbgName.Append("\">");
|
|
return dbgName.ToString();
|
|
}
|
|
|
|
public new void AddContent(XslNode node) {
|
|
Debug.Assert(node != null && node.NodeType == XslNodeType.List);
|
|
base.AddContent(node);
|
|
}
|
|
|
|
public void MergeContent(AttributeSet other) {
|
|
InsertContent(other.Content);
|
|
}
|
|
}
|
|
|
|
internal class Template : ProtoTemplate {
|
|
public readonly string Match;
|
|
public readonly QilName Mode;
|
|
public readonly double Priority;
|
|
public int ImportPrecedence;
|
|
public int OrderNumber;
|
|
|
|
public Template(QilName name, string match, QilName mode, double priority, XslVersion xslVer)
|
|
: base(XslNodeType.Template, name, xslVer)
|
|
{
|
|
this.Match = match;
|
|
this.Mode = mode;
|
|
this.Priority = priority;
|
|
}
|
|
|
|
public override string GetDebugName() {
|
|
StringBuilder dbgName = new StringBuilder();
|
|
dbgName.Append("<xsl:template");
|
|
if (Match != null) {
|
|
dbgName.Append(" match=\"");
|
|
dbgName.Append(Match);
|
|
dbgName.Append('"');
|
|
}
|
|
if (Name != null) {
|
|
dbgName.Append(" name=\"");
|
|
dbgName.Append(Name.QualifiedName);
|
|
dbgName.Append('"');
|
|
}
|
|
if (!double.IsNaN(Priority)) {
|
|
dbgName.Append(" priority=\"");
|
|
dbgName.Append(Priority.ToString(CultureInfo.InvariantCulture));
|
|
dbgName.Append('"');
|
|
}
|
|
if (Mode.LocalName.Length != 0) {
|
|
dbgName.Append(" mode=\"");
|
|
dbgName.Append(Mode.QualifiedName);
|
|
dbgName.Append('"');
|
|
}
|
|
dbgName.Append('>');
|
|
return dbgName.ToString();
|
|
}
|
|
}
|
|
|
|
internal class VarPar : XslNode {
|
|
public XslFlags DefValueFlags;
|
|
public QilNode Value; // Contains value for WithParams and global VarPars
|
|
|
|
public VarPar(XslNodeType nt, QilName name, string select, XslVersion xslVer) : base(nt, name, select, xslVer) {}
|
|
}
|
|
|
|
internal class Sort : XslNode {
|
|
public readonly string Lang;
|
|
public readonly string DataType;
|
|
public readonly string Order;
|
|
public readonly string CaseOrder;
|
|
|
|
public Sort(string select, string lang, string dataType, string order, string caseOrder, XslVersion xslVer)
|
|
: base(XslNodeType.Sort, null, select, xslVer)
|
|
{
|
|
this.Lang = lang;
|
|
this.DataType = dataType;
|
|
this.Order = order;
|
|
this.CaseOrder = caseOrder;
|
|
}
|
|
}
|
|
|
|
internal class Keys : KeyedCollection<QilName, List<Key>> {
|
|
protected override QilName GetKeyForItem(List<Key> list) {
|
|
Debug.Assert(list != null && list.Count > 0);
|
|
return list[0].Name;
|
|
}
|
|
}
|
|
|
|
internal class Key : XslNode {
|
|
public readonly string Match;
|
|
public readonly string Use;
|
|
public QilFunction Function;
|
|
|
|
public Key(QilName name, string match, string use, XslVersion xslVer)
|
|
: base(XslNodeType.Key, name, null, xslVer)
|
|
{
|
|
// match and use can be null in case of incorrect stylesheet
|
|
Debug.Assert(name != null);
|
|
this.Match = match;
|
|
this.Use = use;
|
|
}
|
|
|
|
public string GetDebugName() {
|
|
StringBuilder dbgName = new StringBuilder();
|
|
dbgName.Append("<xsl:key name=\"");
|
|
dbgName.Append(Name.QualifiedName);
|
|
dbgName.Append('"');
|
|
|
|
if (Match != null) {
|
|
dbgName.Append(" match=\"");
|
|
dbgName.Append(Match);
|
|
dbgName.Append('"');
|
|
}
|
|
if (Use != null) {
|
|
dbgName.Append(" use=\"");
|
|
dbgName.Append(Use);
|
|
dbgName.Append('"');
|
|
}
|
|
dbgName.Append('>');
|
|
return dbgName.ToString();
|
|
}
|
|
}
|
|
|
|
internal enum NumberLevel {
|
|
Single,
|
|
Multiple,
|
|
Any,
|
|
}
|
|
|
|
internal class Number : XslNode {
|
|
public readonly NumberLevel Level;
|
|
public readonly string Count;
|
|
public readonly string From;
|
|
public readonly string Value;
|
|
public readonly string Format;
|
|
public readonly string Lang;
|
|
public readonly string LetterValue;
|
|
public readonly string GroupingSeparator;
|
|
public readonly string GroupingSize;
|
|
|
|
public Number(NumberLevel level, string count, string from, string value,
|
|
string format, string lang, string letterValue, string groupingSeparator, string groupingSize,
|
|
XslVersion xslVer) : base(XslNodeType.Number, null, null, xslVer)
|
|
{
|
|
this.Level = level;
|
|
this.Count = count;
|
|
this.From = from;
|
|
this.Value = value;
|
|
this.Format = format;
|
|
this.Lang = lang;
|
|
this.LetterValue = letterValue;
|
|
this.GroupingSeparator = groupingSeparator;
|
|
this.GroupingSize = groupingSize;
|
|
}
|
|
}
|
|
|
|
internal class NodeCtor : XslNode {
|
|
public readonly string NameAvt;
|
|
public readonly string NsAvt;
|
|
|
|
public NodeCtor(XslNodeType nt, string nameAvt, string nsAvt, XslVersion xslVer)
|
|
: base(nt, null, null, xslVer)
|
|
{
|
|
this.NameAvt = nameAvt;
|
|
this.NsAvt = nsAvt;
|
|
}
|
|
}
|
|
|
|
internal class Text : XslNode {
|
|
public readonly SerializationHints Hints;
|
|
|
|
public Text(string data, SerializationHints hints, XslVersion xslVer)
|
|
: base(XslNodeType.Text, null, data, xslVer)
|
|
{
|
|
this.Hints = hints;
|
|
}
|
|
}
|
|
|
|
internal class XslNodeEx : XslNode {
|
|
public readonly ISourceLineInfo ElemNameLi;
|
|
public readonly ISourceLineInfo EndTagLi;
|
|
|
|
public XslNodeEx(XslNodeType t, QilName name, object arg, ContextInfo ctxInfo, XslVersion xslVer)
|
|
: base(t, name, arg, xslVer)
|
|
{
|
|
ElemNameLi = ctxInfo.elemNameLi;
|
|
EndTagLi = ctxInfo.endTagLi;
|
|
}
|
|
|
|
public XslNodeEx(XslNodeType t, QilName name, object arg, XslVersion xslVer) : base(t, name, arg, xslVer) {
|
|
}
|
|
}
|
|
|
|
internal static class AstFactory {
|
|
public static XslNode XslNode(XslNodeType nodeType, QilName name, string arg, XslVersion xslVer) {
|
|
return new XslNode(nodeType, name, arg, xslVer);
|
|
}
|
|
|
|
public static XslNode ApplyImports(QilName mode, Stylesheet sheet, XslVersion xslVer) {
|
|
return new XslNode(XslNodeType.ApplyImports, mode, sheet, xslVer);
|
|
}
|
|
|
|
public static XslNodeEx ApplyTemplates(QilName mode, string select, ContextInfo ctxInfo, XslVersion xslVer) {
|
|
return new XslNodeEx(XslNodeType.ApplyTemplates, mode, select, ctxInfo, xslVer);
|
|
}
|
|
|
|
// Special node for start apply-templates
|
|
public static XslNodeEx ApplyTemplates(QilName mode) {
|
|
return new XslNodeEx(XslNodeType.ApplyTemplates, mode, /*select:*/null, XslVersion.Current);
|
|
}
|
|
|
|
public static NodeCtor Attribute(string nameAvt, string nsAvt, XslVersion xslVer) {
|
|
return new NodeCtor(XslNodeType.Attribute, nameAvt, nsAvt, xslVer);
|
|
}
|
|
|
|
public static AttributeSet AttributeSet(QilName name) {
|
|
return new AttributeSet(name, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNodeEx CallTemplate(QilName name, ContextInfo ctxInfo) {
|
|
return new XslNodeEx(XslNodeType.CallTemplate, name, null, ctxInfo, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNode Choose() {
|
|
return new XslNode(XslNodeType.Choose);
|
|
}
|
|
|
|
public static XslNode Comment() {
|
|
return new XslNode(XslNodeType.Comment);
|
|
}
|
|
|
|
public static XslNode Copy() {
|
|
return new XslNode(XslNodeType.Copy);
|
|
}
|
|
|
|
public static XslNode CopyOf(string select, XslVersion xslVer) {
|
|
return new XslNode(XslNodeType.CopyOf, null, select, xslVer);
|
|
}
|
|
|
|
public static NodeCtor Element(string nameAvt, string nsAvt, XslVersion xslVer) {
|
|
return new NodeCtor(XslNodeType.Element, nameAvt, nsAvt, xslVer);
|
|
}
|
|
|
|
public static XslNode Error(string message) {
|
|
return new XslNode(XslNodeType.Error, null, message, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNodeEx ForEach(string select, ContextInfo ctxInfo, XslVersion xslVer) {
|
|
return new XslNodeEx(XslNodeType.ForEach, null, select, ctxInfo, xslVer);
|
|
}
|
|
|
|
public static XslNode If(string test, XslVersion xslVer) {
|
|
return new XslNode(XslNodeType.If, null, test, xslVer);
|
|
}
|
|
|
|
public static Key Key(QilName name, string match, string use, XslVersion xslVer) {
|
|
return new Key(name, match, use, xslVer);
|
|
}
|
|
|
|
public static XslNode List() {
|
|
return new XslNode(XslNodeType.List);
|
|
}
|
|
|
|
public static XslNode LiteralAttribute(QilName name, string value, XslVersion xslVer) {
|
|
return new XslNode(XslNodeType.LiteralAttribute, name, value, xslVer);
|
|
}
|
|
|
|
public static XslNode LiteralElement(QilName name) {
|
|
return new XslNode(XslNodeType.LiteralElement, name, null, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNode Message(bool term) {
|
|
return new XslNode(XslNodeType.Message, null, term, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNode Nop() {
|
|
return new XslNode(XslNodeType.Nop);
|
|
}
|
|
|
|
public static Number Number(NumberLevel level, string count, string from, string value,
|
|
string format, string lang, string letterValue, string groupingSeparator, string groupingSize,
|
|
XslVersion xslVer)
|
|
{
|
|
return new Number(level, count, from, value, format, lang, letterValue, groupingSeparator, groupingSize, xslVer);
|
|
}
|
|
|
|
public static XslNode Otherwise() {
|
|
return new XslNode(XslNodeType.Otherwise);
|
|
}
|
|
|
|
public static XslNode PI(string name, XslVersion xslVer) {
|
|
return new XslNode(XslNodeType.PI, null, name, xslVer);
|
|
}
|
|
|
|
public static Sort Sort(string select, string lang, string dataType, string order, string caseOrder, XslVersion xslVer) {
|
|
return new Sort(select, lang, dataType, order, caseOrder, xslVer);
|
|
}
|
|
|
|
public static Template Template(QilName name, string match, QilName mode, double priority, XslVersion xslVer) {
|
|
return new Template(name, match, mode, priority, xslVer);
|
|
}
|
|
|
|
public static XslNode Text(string data) {
|
|
return new Text(data, SerializationHints.None, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNode Text(string data, SerializationHints hints) {
|
|
return new Text(data, hints, XslVersion.Current);
|
|
}
|
|
|
|
public static XslNode UseAttributeSet(QilName name) {
|
|
return new XslNode(XslNodeType.UseAttributeSet, name, null, XslVersion.Current);
|
|
}
|
|
|
|
public static VarPar VarPar(XslNodeType nt, QilName name, string select, XslVersion xslVer) {
|
|
return new VarPar(nt, name, select, xslVer);
|
|
}
|
|
|
|
public static VarPar WithParam(QilName name) {
|
|
return VarPar(XslNodeType.WithParam, name, /*select*/null, XslVersion.Current);
|
|
}
|
|
|
|
private static QilFactory f = new QilFactory();
|
|
|
|
public static QilName QName(string local, string uri, string prefix) {
|
|
return f.LiteralQName(local, uri, prefix);
|
|
}
|
|
|
|
public static QilName QName(string local) {
|
|
return f.LiteralQName(local);
|
|
}
|
|
}
|
|
}
|