|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Xml.XPath;
using System.Xml.Xsl.Qil;
namespace System.Xml.Xsl.Xslt
{
internal enum XslVersion
{
Version10 = 0,
ForwardsCompatible = 1,
Current = Version10,
}
// RootLevel is underdeveloped consept currently. I plane to move here more collections from Compiler.
// Compiler is like a stylesheet in some sense. it has a lot of properties of stylesheet. Instead of
// inhereting from Styleseet (or StylesheetLevel) I desided to aggregate special subclass of StylesheetLevel.
// One more reason to for this design is to normolize apply-templates and apply-imports to one concept:
// apply-templates is apply-imports(compiler.Root).
// For now I don't create new files for these new classes to simplify integrations WebData <-> WebData_xsl
internal sealed class RootLevel : StylesheetLevel
{
public RootLevel(Stylesheet principal)
{
base.Imports = new Stylesheet[] { principal };
}
}
internal sealed class Compiler
{
public XsltSettings Settings;
public bool IsDebug;
public string? ScriptAssemblyPath;
public int Version; // 0 - Auto; 1 - XSLT 1.0; 2 - XSLT 2.0
public string? inputTypeAnnotations; // null - "unspecified"; "preserve"; "strip"
public CompilerErrorCollection CompilerErrorColl; // Results of the compilation
public int CurrentPrecedence; // Decreases by 1 with each import
public XslNode? StartApplyTemplates;
public RootLevel? Root;
public Scripts Scripts;
public Output Output = new Output();
public List<VarPar> ExternalPars = new List<VarPar>();
public List<VarPar> GlobalVars = new List<VarPar>();
public List<WhitespaceRule> WhitespaceRules = new List<WhitespaceRule>();
public DecimalFormats DecimalFormats = new DecimalFormats();
public Keys Keys = new Keys();
public List<ProtoTemplate> AllTemplates = new List<ProtoTemplate>();
public Dictionary<QilName, VarPar> AllGlobalVarPars = new Dictionary<QilName, VarPar>();
public Dictionary<QilName, Template> NamedTemplates = new Dictionary<QilName, Template>();
public Dictionary<QilName, AttributeSet> AttributeSets = new Dictionary<QilName, AttributeSet>();
public Dictionary<string, NsAlias> NsAliases = new Dictionary<string, NsAlias>();
private readonly Dictionary<string, int> _moduleOrder = new Dictionary<string, int>();
public Compiler(XsltSettings settings, bool debug, string? scriptAssemblyPath)
{
Debug.Assert(CompilerErrorColl == null, "Compiler cannot be reused");
Settings = settings;
IsDebug = settings.IncludeDebugInformation | debug;
ScriptAssemblyPath = scriptAssemblyPath;
CompilerErrorColl = new CompilerErrorCollection();
Scripts = new Scripts(this);
}
public CompilerErrorCollection Compile(object stylesheet, XmlResolver? xmlResolver, XmlResolver? origResolver, out QilExpression qil)
{
Debug.Assert(stylesheet != null);
Debug.Assert(Root == null, "Compiler cannot be reused");
new XsltLoader().Load(this, stylesheet, xmlResolver, origResolver);
qil = QilGenerator.CompileStylesheet(this);
SortErrors();
return CompilerErrorColl;
}
public Stylesheet CreateStylesheet()
{
Stylesheet sheet = new Stylesheet(this, CurrentPrecedence);
if (CurrentPrecedence-- == 0)
{
Root = new RootLevel(sheet);
}
return sheet;
}
public void AddModule(string baseUri)
{
if (!_moduleOrder.ContainsKey(baseUri))
{
_moduleOrder[baseUri] = _moduleOrder.Count;
}
}
public void ApplyNsAliases(ref string? prefix, ref string nsUri)
{
NsAlias? alias;
if (NsAliases.TryGetValue(nsUri, out alias))
{
nsUri = alias.ResultNsUri;
prefix = alias.ResultPrefix;
}
}
// Returns true in case of redefinition
public bool SetNsAlias(string ssheetNsUri, string resultNsUri, string? resultPrefix, int importPrecedence)
{
NsAlias? oldNsAlias;
if (NsAliases.TryGetValue(ssheetNsUri, out oldNsAlias))
{
// Namespace alias for this stylesheet namespace URI has already been defined
Debug.Assert(importPrecedence <= oldNsAlias.ImportPrecedence, "Stylesheets must be processed in the order of decreasing import precedence");
if (importPrecedence < oldNsAlias.ImportPrecedence || resultNsUri == oldNsAlias.ResultNsUri)
{
// Either the identical definition or lower precedence - ignore it
return false;
}
// Recover by choosing the declaration that occurs later in the stylesheet
}
NsAliases[ssheetNsUri] = new NsAlias(resultNsUri, resultPrefix, importPrecedence);
return oldNsAlias != null;
}
private void MergeWhitespaceRules(Stylesheet sheet)
{
for (int idx = 0; idx <= 2; idx++)
{
sheet.WhitespaceRules![idx].Reverse();
this.WhitespaceRules.AddRange(sheet.WhitespaceRules[idx]);
}
sheet.WhitespaceRules = null;
}
private void MergeAttributeSets(Stylesheet sheet)
{
foreach (QilName attSetName in sheet.AttributeSets!.Keys)
{
AttributeSet? attSet;
if (!this.AttributeSets.TryGetValue(attSetName, out attSet))
{
this.AttributeSets[attSetName] = sheet.AttributeSets[attSetName];
}
else
{
// Lower import precedence - insert before all previous definitions
attSet.MergeContent(sheet.AttributeSets[attSetName]);
}
}
sheet.AttributeSets = null;
}
private void MergeGlobalVarPars(Stylesheet sheet)
{
foreach (VarPar var in sheet.GlobalVarPars!)
{
Debug.Assert(var.NodeType == XslNodeType.Variable || var.NodeType == XslNodeType.Param);
if (!AllGlobalVarPars.ContainsKey(var.Name!))
{
if (var.NodeType == XslNodeType.Variable)
{
GlobalVars.Add(var);
}
else
{
ExternalPars.Add(var);
}
AllGlobalVarPars[var.Name!] = var;
}
}
sheet.GlobalVarPars = null;
}
public void MergeWithStylesheet(Stylesheet sheet)
{
MergeWhitespaceRules(sheet);
MergeAttributeSets(sheet);
MergeGlobalVarPars(sheet);
}
public static string ConstructQName(string prefix, string localName)
{
if (prefix.Length == 0)
{
return localName;
}
else
{
return $"{prefix}:{localName}";
}
}
public bool ParseQName(string qname, out string prefix, out string localName, IErrorHelper errorHelper)
{
Debug.Assert(qname != null);
try
{
ValidateNames.ParseQNameThrow(qname, out prefix, out localName);
return true;
}
catch (XmlException e)
{
errorHelper.ReportError(/*[XT_042]*/e.Message, null);
prefix = PhantomNCName;
localName = PhantomNCName;
return false;
}
}
public bool ParseNameTest(string nameTest, out string? prefix, out string? localName, IErrorHelper errorHelper)
{
Debug.Assert(nameTest != null);
try
{
ValidateNames.ParseNameTestThrow(nameTest, out prefix, out localName);
return true;
}
catch (XmlException e)
{
errorHelper.ReportError(/*[XT_043]*/e.Message, null);
prefix = PhantomNCName;
localName = PhantomNCName;
return false;
}
}
public static void ValidatePiName(string name, IErrorHelper errorHelper)
{
Debug.Assert(name != null);
try
{
ValidateNames.ValidateNameThrow(
/*prefix:*/string.Empty, /*localName:*/name, /*ns:*/string.Empty,
XPathNodeType.ProcessingInstruction, ValidateNames.Flags.AllExceptPrefixMapping
);
}
catch (XmlException e)
{
errorHelper.ReportError(/*[XT_044]*/e.Message, null);
}
}
public readonly string PhantomNCName = "error";
private int _phantomNsCounter;
public string CreatePhantomNamespace()
{
// Prepend invalid XmlChar to ensure this name would not clash with any namespace name in the stylesheet
return $"\0namespace{_phantomNsCounter++}";
}
public static bool IsPhantomNamespace(string namespaceName)
{
return namespaceName.Length > 0 && namespaceName[0] == '\0';
}
public static bool IsPhantomName(QilName qname)
{
string nsUri = qname.NamespaceUri;
return nsUri.Length > 0 && nsUri[0] == '\0';
}
// -------------------------------- Error Handling --------------------------------
private int ErrorCount
{
get
{
return CompilerErrorColl.Count;
}
set
{
Debug.Assert(value <= ErrorCount);
for (int idx = ErrorCount - 1; idx >= value; idx--)
{
CompilerErrorColl.RemoveAt(idx);
}
}
}
private int _savedErrorCount = -1;
public void EnterForwardsCompatible()
{
Debug.Assert(_savedErrorCount == -1, "Nested EnterForwardsCompatible calls");
_savedErrorCount = ErrorCount;
}
// Returns true if no errors were suppressed
public bool ExitForwardsCompatible(bool fwdCompat)
{
Debug.Assert(_savedErrorCount != -1, "ExitForwardsCompatible without EnterForwardsCompatible");
if (fwdCompat && ErrorCount > _savedErrorCount)
{
ErrorCount = _savedErrorCount;
Debug.Assert((_savedErrorCount = -1) < 0);
return false;
}
Debug.Assert((_savedErrorCount = -1) < 0);
return true;
}
public CompilerError CreateError(ISourceLineInfo lineInfo, string res, params string?[]? args)
{
AddModule(lineInfo.Uri!);
return new CompilerError(
lineInfo.Uri!, lineInfo.Start.Line, lineInfo.Start.Pos, /*errorNumber:*/string.Empty,
/*errorText:*/XslTransformException.CreateMessage(res, args)
);
}
public void ReportError(ISourceLineInfo lineInfo, string res, params string?[]? args)
{
CompilerError error = CreateError(lineInfo, res, args);
CompilerErrorColl.Add(error);
}
public void ReportWarning(ISourceLineInfo lineInfo, string res, params string?[]? args)
{
int warningLevel = 1;
if (0 <= Settings.WarningLevel && Settings.WarningLevel < warningLevel)
{
// Ignore warning
return;
}
CompilerError error = CreateError(lineInfo, res, args);
if (Settings.TreatWarningsAsErrors)
{
error.ErrorText = XslTransformException.CreateMessage(SR.Xslt_WarningAsError, error.ErrorText);
CompilerErrorColl.Add(error);
}
else
{
error.IsWarning = true;
CompilerErrorColl.Add(error);
}
}
private void SortErrors()
{
CompilerErrorCollection errorColl = this.CompilerErrorColl;
if (errorColl.Count > 1)
{
CompilerError[] errors = new CompilerError[errorColl.Count];
errorColl.CopyTo(errors, 0);
Array.Sort<CompilerError>(errors, new CompilerErrorComparer(_moduleOrder));
errorColl.Clear();
errorColl.AddRange(errors);
}
}
private sealed class CompilerErrorComparer : IComparer<CompilerError>
{
private readonly Dictionary<string, int> _moduleOrder;
public CompilerErrorComparer(Dictionary<string, int> moduleOrder)
{
_moduleOrder = moduleOrder;
}
public int Compare(CompilerError? x, CompilerError? y)
{
if ((object?)x == (object?)y)
return 0;
if (x == null)
return -1;
if (y == null)
return 1;
int result = _moduleOrder[x.FileName].CompareTo(_moduleOrder[y.FileName]);
if (result != 0)
return result;
result = x.Line.CompareTo(y.Line);
if (result != 0)
return result;
result = x.Column.CompareTo(y.Column);
if (result != 0)
return result;
result = x.IsWarning.CompareTo(y.IsWarning);
if (result != 0)
return result;
result = string.CompareOrdinal(x.ErrorNumber, y.ErrorNumber);
if (result != 0)
return result;
return string.CompareOrdinal(x.ErrorText, y.ErrorText);
}
}
}
internal sealed class Output
{
public XmlWriterSettings Settings;
public string? Version;
public string? Encoding;
public XmlQualifiedName? Method;
// All the xsl:output elements occurring in a stylesheet are merged into a single effective xsl:output element.
// We store the import precedence of each attribute value to catch redefinitions with the same import precedence.
public const int NeverDeclaredPrec = int.MinValue;
public int MethodPrec = NeverDeclaredPrec;
public int VersionPrec = NeverDeclaredPrec;
public int EncodingPrec = NeverDeclaredPrec;
public int OmitXmlDeclarationPrec = NeverDeclaredPrec;
public int StandalonePrec = NeverDeclaredPrec;
public int DocTypePublicPrec = NeverDeclaredPrec;
public int DocTypeSystemPrec = NeverDeclaredPrec;
public int IndentPrec = NeverDeclaredPrec;
public int MediaTypePrec = NeverDeclaredPrec;
public Output()
{
Settings = new XmlWriterSettings();
Settings.OutputMethod = XmlOutputMethod.AutoDetect;
Settings.AutoXmlDeclaration = true;
Settings.ConformanceLevel = ConformanceLevel.Auto;
Settings.MergeCDataSections = true;
}
}
internal sealed class DecimalFormats : KeyedCollection<XmlQualifiedName, DecimalFormatDecl>
{
protected override XmlQualifiedName GetKeyForItem(DecimalFormatDecl format)
{
return format.Name;
}
}
internal sealed class DecimalFormatDecl
{
public readonly XmlQualifiedName Name;
public readonly string InfinitySymbol;
public readonly string NanSymbol;
public readonly char[] Characters;
public static readonly DecimalFormatDecl Default = new DecimalFormatDecl(new XmlQualifiedName(), "Infinity", "NaN", ".,%\u20300#;-");
public DecimalFormatDecl(XmlQualifiedName name, string infinitySymbol, string nanSymbol, string characters)
{
Debug.Assert(characters.Length == 8);
this.Name = name;
this.InfinitySymbol = infinitySymbol;
this.NanSymbol = nanSymbol;
this.Characters = characters.ToCharArray();
}
}
internal sealed class NsAlias
{
public readonly string ResultNsUri;
public readonly string? ResultPrefix;
public readonly int ImportPrecedence;
public NsAlias(string resultNsUri, string? resultPrefix, int importPrecedence)
{
this.ResultNsUri = resultNsUri;
this.ResultPrefix = resultPrefix;
this.ImportPrecedence = importPrecedence;
}
}
}
|