1 //------------------------------------------------------------------------------
2 // <copyright file="Compiler.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System
.CodeDom
.Compiler
;
9 using System
.Collections
.Generic
;
10 using System
.Collections
.ObjectModel
;
11 using System
.Diagnostics
;
12 using System
.Xml
.XPath
;
13 using System
.Xml
.Xsl
.Qil
;
14 using System
.Xml
.Xsl
.XPath
;
15 using System
.Runtime
.Versioning
;
17 namespace System
.Xml
.Xsl
.Xslt
{
18 using Res
= System
.Xml
.Utils
.Res
;
19 using TypeFactory
= XmlQueryTypeFactory
;
21 using XmlILTrace
= System
.Xml
.Xsl
.IlGen
.XmlILTrace
;
24 internal enum XslVersion
{
26 ForwardsCompatible
= 1,
30 // RootLevel is underdeveloped consept currently. I plane to move here more collections from Compiler.
31 // Compiler is like a stylesheet in some sense. it has a lot of properties of stylesheet. Instead of
32 // inhereting from Styleseet (or StylesheetLevel) I desided to agregate special subclass of StylesheetLevel.
33 // One more reason to for this design is to normolize apply-templates and apply-imports to one concept:
34 // apply-templates is apply-imports(compiler.Root).
35 // For now I don't create new files for these new classes to simplify integrations WebData <-> WebData_xsl
36 internal class RootLevel
: StylesheetLevel
{
37 public RootLevel(Stylesheet principal
) {
38 base.Imports
= new Stylesheet
[] { principal }
;
42 internal class Compiler
{
43 public XsltSettings Settings
;
45 public string ScriptAssemblyPath
;
46 public int Version
; // 0 - Auto; 1 - XSLT 1.0; 2 - XSLT 2.0
47 public string inputTypeAnnotations
; // null - "unspecified"; "preserve"; "strip"
49 public CompilerResults CompilerResults
; // Results of the compilation
50 public int CurrentPrecedence
= 0; // Decreases by 1 with each import
51 public XslNode StartApplyTemplates
;
52 public RootLevel Root
;
53 public Scripts Scripts
;
54 public Output Output
= new Output();
55 public List
<VarPar
> ExternalPars
= new List
<VarPar
>();
56 public List
<VarPar
> GlobalVars
= new List
<VarPar
>();
57 public List
<WhitespaceRule
> WhitespaceRules
= new List
<WhitespaceRule
>();
58 public DecimalFormats DecimalFormats
= new DecimalFormats();
59 public Keys Keys
= new Keys();
60 public List
<ProtoTemplate
> AllTemplates
= new List
<ProtoTemplate
>();
62 public Dictionary
<QilName
, VarPar
> AllGlobalVarPars
= new Dictionary
<QilName
, VarPar
>();
63 public Dictionary
<QilName
, Template
> NamedTemplates
= new Dictionary
<QilName
, Template
>();
64 public Dictionary
<QilName
, AttributeSet
> AttributeSets
= new Dictionary
<QilName
, AttributeSet
>();
65 public Dictionary
<string, NsAlias
> NsAliases
= new Dictionary
<string, NsAlias
>();
67 private Dictionary
<string, int> moduleOrder
= new Dictionary
<string,int>();
69 public Compiler(XsltSettings settings
, bool debug
, string scriptAssemblyPath
) {
70 Debug
.Assert(CompilerResults
== null, "Compiler cannot be reused");
72 // Keep all intermediate files if tracing is enabled
73 TempFileCollection tempFiles
= settings
.TempFiles
?? new TempFileCollection();
76 if (XmlILTrace
.IsEnabled
) {
77 tempFiles
.KeepFiles
= true;
82 IsDebug
= settings
.IncludeDebugInformation
| debug
;
83 ScriptAssemblyPath
= scriptAssemblyPath
;
85 CompilerResults
= new CompilerResults(tempFiles
);
86 Scripts
= new Scripts(this);
89 [ResourceConsumption(ResourceScope
.Machine
)]
90 [ResourceExposure(ResourceScope
.Machine
)]
91 public CompilerResults
Compile(object stylesheet
, XmlResolver xmlResolver
, out QilExpression qil
) {
92 Debug
.Assert(stylesheet
!= null);
93 Debug
.Assert(Root
== null, "Compiler cannot be reused");
95 new XsltLoader().Load(this, stylesheet
, xmlResolver
);
96 qil
= QilGenerator
.CompileStylesheet(this);
98 return CompilerResults
;
101 public Stylesheet
CreateStylesheet() {
102 Stylesheet sheet
= new Stylesheet(this, CurrentPrecedence
);
103 if (CurrentPrecedence
-- == 0) {
104 Root
= new RootLevel(sheet
);
109 public void AddModule(string baseUri
) {
110 if (!moduleOrder
.ContainsKey(baseUri
)) {
111 moduleOrder
[baseUri
] = moduleOrder
.Count
;
115 public void ApplyNsAliases(ref string prefix
, ref string nsUri
) {
117 if (NsAliases
.TryGetValue(nsUri
, out alias)) {
118 nsUri
= alias.ResultNsUri
;
119 prefix
= alias.ResultPrefix
;
123 // Returns true in case of redefinition
124 public bool SetNsAlias(string ssheetNsUri
, string resultNsUri
, string resultPrefix
, int importPrecedence
) {
126 if (NsAliases
.TryGetValue(ssheetNsUri
, out oldNsAlias
)) {
127 // Namespace alias for this stylesheet namespace URI has already been defined
128 Debug
.Assert(importPrecedence
<= oldNsAlias
.ImportPrecedence
, "Stylesheets must be processed in the order of decreasing import precedence");
129 if (importPrecedence
< oldNsAlias
.ImportPrecedence
|| resultNsUri
== oldNsAlias
.ResultNsUri
) {
130 // Either the identical definition or lower precedence - ignore it
133 // Recover by choosing the declaration that occurs later in the stylesheet
135 NsAliases
[ssheetNsUri
] = new NsAlias(resultNsUri
, resultPrefix
, importPrecedence
);
136 return oldNsAlias
!= null;
139 private void MergeWhitespaceRules(Stylesheet sheet
) {
140 for (int idx
= 0; idx
<= 2; idx
++) {
141 sheet
.WhitespaceRules
[idx
].Reverse();
142 this.WhitespaceRules
.AddRange(sheet
.WhitespaceRules
[idx
]);
144 sheet
.WhitespaceRules
= null;
147 private void MergeAttributeSets(Stylesheet sheet
) {
148 foreach (QilName attSetName
in sheet
.AttributeSets
.Keys
) {
150 if (!this.AttributeSets
.TryGetValue(attSetName
, out attSet
)) {
151 this.AttributeSets
[attSetName
] = sheet
.AttributeSets
[attSetName
];
153 // Lower import precedence - insert before all previous definitions
154 attSet
.MergeContent(sheet
.AttributeSets
[attSetName
]);
157 sheet
.AttributeSets
= null;
160 private void MergeGlobalVarPars(Stylesheet sheet
) {
161 foreach (VarPar
var in sheet
.GlobalVarPars
) {
162 Debug
.Assert(var.NodeType
== XslNodeType
.Variable
|| var.NodeType
== XslNodeType
.Param
);
163 if (!AllGlobalVarPars
.ContainsKey(var.Name
)) {
164 if (var.NodeType
== XslNodeType
.Variable
) {
167 ExternalPars
.Add(var);
169 AllGlobalVarPars
[var.Name
] = var;
172 sheet
.GlobalVarPars
= null;
175 public void MergeWithStylesheet(Stylesheet sheet
) {
176 MergeWhitespaceRules(sheet
);
177 MergeAttributeSets(sheet
);
178 MergeGlobalVarPars(sheet
);
181 public static string ConstructQName(string prefix
, string localName
) {
182 if (prefix
.Length
== 0) {
185 return prefix
+ ':' + localName
;
189 public bool ParseQName(string qname
, out string prefix
, out string localName
, IErrorHelper errorHelper
) {
190 Debug
.Assert(qname
!= null);
192 ValidateNames
.ParseQNameThrow(qname
, out prefix
, out localName
);
195 catch (XmlException e
) {
196 errorHelper
.ReportError(/*[XT_042]*/e
.Message
, null);
197 prefix
= PhantomNCName
;
198 localName
= PhantomNCName
;
203 public bool ParseNameTest(string nameTest
, out string prefix
, out string localName
, IErrorHelper errorHelper
) {
204 Debug
.Assert(nameTest
!= null);
206 ValidateNames
.ParseNameTestThrow(nameTest
, out prefix
, out localName
);
209 catch (XmlException e
) {
210 errorHelper
.ReportError(/*[XT_043]*/e
.Message
, null);
211 prefix
= PhantomNCName
;
212 localName
= PhantomNCName
;
217 public void ValidatePiName(string name
, IErrorHelper errorHelper
) {
218 Debug
.Assert(name
!= null);
220 ValidateNames
.ValidateNameThrow(
221 /*prefix:*/string.Empty
, /*localName:*/name
, /*ns:*/string.Empty
,
222 XPathNodeType
.ProcessingInstruction
, ValidateNames
.Flags
.AllExceptPrefixMapping
225 catch (XmlException e
) {
226 errorHelper
.ReportError(/*[XT_044]*/e
.Message
, null);
230 public readonly string PhantomNCName
= "error";
231 private int phantomNsCounter
= 0;
233 public string CreatePhantomNamespace() {
234 // Prepend invalid XmlChar to ensure this name would not clash with any namespace name in the stylesheet
235 return "\0namespace" + phantomNsCounter
++;
238 public bool IsPhantomNamespace(string namespaceName
) {
239 return namespaceName
.Length
> 0 && namespaceName
[0] == '\0';
242 public bool IsPhantomName(QilName qname
) {
243 string nsUri
= qname
.NamespaceUri
;
244 return nsUri
.Length
> 0 && nsUri
[0] == '\0';
247 // -------------------------------- Error Handling --------------------------------
249 private int ErrorCount
{
251 return CompilerResults
.Errors
.Count
;
254 Debug
.Assert(value <= ErrorCount
);
255 for (int idx
= ErrorCount
- 1; idx
>= value; idx
--) {
256 CompilerResults
.Errors
.RemoveAt(idx
);
261 private int savedErrorCount
= -1;
263 public void EnterForwardsCompatible() {
264 Debug
.Assert(savedErrorCount
== -1, "Nested EnterForwardsCompatible calls");
265 savedErrorCount
= ErrorCount
;
268 // Returns true if no errors were suppressed
269 public bool ExitForwardsCompatible(bool fwdCompat
) {
270 Debug
.Assert(savedErrorCount
!= -1, "ExitForwardsCompatible without EnterForwardsCompatible");
271 if (fwdCompat
&& ErrorCount
> savedErrorCount
) {
272 ErrorCount
= savedErrorCount
;
273 Debug
.Assert((savedErrorCount
= -1) < 0);
276 Debug
.Assert((savedErrorCount
= -1) < 0);
280 public CompilerError
CreateError(ISourceLineInfo lineInfo
, string res
, params string[] args
) {
281 AddModule(lineInfo
.Uri
);
282 return new CompilerError(
283 lineInfo
.Uri
, lineInfo
.Start
.Line
, lineInfo
.Start
.Pos
, /*errorNumber:*/string.Empty
,
284 /*errorText:*/XslTransformException
.CreateMessage(res
, args
)
288 public void ReportError(ISourceLineInfo lineInfo
, string res
, params string[] args
) {
289 CompilerError error
= CreateError(lineInfo
, res
, args
);
290 CompilerResults
.Errors
.Add(error
);
293 public void ReportWarning(ISourceLineInfo lineInfo
, string res
, params string[] args
) {
294 int warningLevel
= 1;
295 if (0 <= Settings
.WarningLevel
&& Settings
.WarningLevel
< warningLevel
) {
299 CompilerError error
= CreateError(lineInfo
, res
, args
);
300 if (Settings
.TreatWarningsAsErrors
) {
301 error
.ErrorText
= XslTransformException
.CreateMessage(Res
.Xslt_WarningAsError
, error
.ErrorText
);
302 CompilerResults
.Errors
.Add(error
);
304 error
.IsWarning
= true;
305 CompilerResults
.Errors
.Add(error
);
309 private void SortErrors() {
310 CompilerErrorCollection errorColl
= this.CompilerResults
.Errors
;
311 if (errorColl
.Count
> 1) {
312 CompilerError
[] errors
= new CompilerError
[errorColl
.Count
];
313 errorColl
.CopyTo(errors
, 0);
314 Array
.Sort
<CompilerError
>(errors
, new CompilerErrorComparer(this.moduleOrder
));
316 errorColl
.AddRange(errors
);
320 private class CompilerErrorComparer
: IComparer
<CompilerError
> {
321 Dictionary
<string, int> moduleOrder
;
323 public CompilerErrorComparer(Dictionary
<string, int> moduleOrder
) {
324 this.moduleOrder
= moduleOrder
;
327 public int Compare(CompilerError x
, CompilerError y
) {
328 if ((object)x
== (object)y
)
337 int result
= moduleOrder
[x
.FileName
].CompareTo(moduleOrder
[y
.FileName
]);
341 result
= x
.Line
.CompareTo(y
.Line
);
345 result
= x
.Column
.CompareTo(y
.Column
);
349 result
= x
.IsWarning
.CompareTo(y
.IsWarning
);
353 result
= string.CompareOrdinal(x
.ErrorNumber
, y
.ErrorNumber
);
357 return string.CompareOrdinal(x
.ErrorText
, y
.ErrorText
);
362 internal class Output
{
363 public XmlWriterSettings Settings
;
364 public string Version
;
365 public string Encoding
;
366 public XmlQualifiedName Method
;
368 // All the xsl:output elements occurring in a stylesheet are merged into a single effective xsl:output element.
369 // We store the import precedence of each attribute value to catch redefinitions with the same import precedence.
370 public const int NeverDeclaredPrec
= int.MinValue
;
371 public int MethodPrec
= NeverDeclaredPrec
;
372 public int VersionPrec
= NeverDeclaredPrec
;
373 public int EncodingPrec
= NeverDeclaredPrec
;
374 public int OmitXmlDeclarationPrec
= NeverDeclaredPrec
;
375 public int StandalonePrec
= NeverDeclaredPrec
;
376 public int DocTypePublicPrec
= NeverDeclaredPrec
;
377 public int DocTypeSystemPrec
= NeverDeclaredPrec
;
378 public int IndentPrec
= NeverDeclaredPrec
;
379 public int MediaTypePrec
= NeverDeclaredPrec
;
382 Settings
= new XmlWriterSettings();
383 Settings
.OutputMethod
= XmlOutputMethod
.AutoDetect
;
384 Settings
.AutoXmlDeclaration
= true;
385 Settings
.ConformanceLevel
= ConformanceLevel
.Auto
;
386 Settings
.MergeCDataSections
= true;
390 internal class DecimalFormats
: KeyedCollection
<XmlQualifiedName
, DecimalFormatDecl
> {
391 protected override XmlQualifiedName
GetKeyForItem(DecimalFormatDecl format
) {
396 internal class DecimalFormatDecl
{
397 public readonly XmlQualifiedName Name
;
398 public readonly string InfinitySymbol
;
399 public readonly string NanSymbol
;
400 public readonly char[] Characters
;
402 public static DecimalFormatDecl Default
= new DecimalFormatDecl(new XmlQualifiedName(), "Infinity", "NaN", ".,%\u20300#;-");
404 public DecimalFormatDecl(XmlQualifiedName name
, string infinitySymbol
, string nanSymbol
, string characters
) {
405 Debug
.Assert(characters
.Length
== 8);
407 this.InfinitySymbol
= infinitySymbol
;
408 this.NanSymbol
= nanSymbol
;
409 this.Characters
= characters
.ToCharArray();
413 internal class NsAlias
{
414 public readonly string ResultNsUri
;
415 public readonly string ResultPrefix
;
416 public readonly int ImportPrecedence
;
418 public NsAlias(string resultNsUri
, string resultPrefix
, int importPrecedence
) {
419 this.ResultNsUri
= resultNsUri
;
420 this.ResultPrefix
= resultPrefix
;
421 this.ImportPrecedence
= importPrecedence
;