455e8912aaf566f51b0d94a7759a6a0db33cf0ad
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Xslt / XslCompiledTransform.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XslCompiledTransform.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <spec>http://webdata/xml/specs/XslCompiledTransform.xml</spec>
7 //------------------------------------------------------------------------------
8
9 using System.CodeDom.Compiler;
10 using System.Diagnostics;
11 using System.Diagnostics.CodeAnalysis;
12 using System.IO;
13 using System.Reflection;
14 using System.Reflection.Emit;
15 using System.Security;
16 using System.Security.Permissions;
17 using System.Xml.XPath;
18 using System.Xml.Xsl.Qil;
19 using System.Xml.Xsl.Runtime;
20 using System.Xml.Xsl.Xslt;
21 using System.Runtime.Versioning;
22 using System.Xml.XmlConfiguration;
23
24 namespace System.Xml.Xsl {
25 #if ! HIDE_XSL
26
27     //----------------------------------------------------------------------------------------------------
28     //  Clarification on null values in this API:
29     //      stylesheet, stylesheetUri   - cannot be null
30     //      settings                    - if null, XsltSettings.Default will be used
31     //      stylesheetResolver          - if null, XmlNullResolver will be used for includes/imports.
32     //                                    However, if the principal stylesheet is given by its URI, that
33     //                                    URI will be resolved using XmlUrlResolver (for compatibility
34     //                                    with XslTransform and XmlReader).
35     //      typeBuilder                 - cannot be null
36     //      scriptAssemblyPath          - can be null only if scripts are disabled
37     //      compiledStylesheet          - cannot be null
38     //      executeMethod, queryData    - cannot be null
39     //      earlyBoundTypes             - null means no script types
40     //      documentResolver            - if null, XmlNullResolver will be used
41     //      input, inputUri             - cannot be null
42     //      arguments                   - null means no arguments
43     //      results, resultsFile        - cannot be null
44     //----------------------------------------------------------------------------------------------------
45
46     public sealed class XslCompiledTransform {
47         // Reader settings used when creating XmlReader from inputUri
48         private static readonly XmlReaderSettings ReaderSettings = null;
49
50         // Permission set that contains Reflection [MemberAccess] permissions
51         private static readonly PermissionSet MemberAccessPermissionSet;
52
53         // Version for GeneratedCodeAttribute
54         private const string Version = ThisAssembly.Version;
55
56         static XslCompiledTransform() {
57             MemberAccessPermissionSet = new PermissionSet(PermissionState.None);
58             MemberAccessPermissionSet.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.MemberAccess));
59             ReaderSettings = new XmlReaderSettings();
60         }
61
62         // Options of compilation
63         private bool                enableDebug     = false;
64
65         // Results of compilation
66         private CompilerResults     compilerResults = null;
67         private XmlWriterSettings   outputSettings  = null;
68         private QilExpression       qil             = null;
69
70         // Executable command for the compiled stylesheet
71         private XmlILCommand        command         = null;
72
73         public XslCompiledTransform() {}
74
75         public XslCompiledTransform(bool enableDebug) {
76             this.enableDebug = enableDebug;
77         }
78
79         /// <summary>
80         /// This function is called on every recompilation to discard all previous results
81         /// </summary>
82         private void Reset() {
83             this.compilerResults = null;
84             this.outputSettings  = null;
85             this.qil             = null;
86             this.command         = null;
87         }
88
89         internal CompilerErrorCollection Errors {
90             get { return this.compilerResults != null ? this.compilerResults.Errors : null; }
91         }
92
93         /// <summary>
94         /// Writer settings specified in the stylesheet
95         /// </summary>
96         public XmlWriterSettings OutputSettings {
97             get {
98                 return this.outputSettings; 
99             }
100         }
101
102         public TempFileCollection TemporaryFiles {
103             [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
104             get { return this.compilerResults != null ? this.compilerResults.TempFiles : null; }
105         }
106
107         //------------------------------------------------
108         // Load methods
109         //------------------------------------------------
110
111         // SxS: This method does not take any resource name and does not expose any resources to the caller.
112         // It's OK to suppress the SxS warning.
113         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
114         [ResourceExposure(ResourceScope.None)]
115         public void Load(XmlReader stylesheet) {
116             Reset();
117             LoadInternal(stylesheet, XsltSettings.Default, XsltConfigSection.CreateDefaultResolver());
118         }
119
120         // SxS: This method does not take any resource name and does not expose any resources to the caller.
121         // It's OK to suppress the SxS warning.
122         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
123         [ResourceExposure(ResourceScope.None)]
124         public void Load(XmlReader stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) {
125             Reset();
126             LoadInternal(stylesheet, settings, stylesheetResolver);
127         }
128
129         // SxS: This method does not take any resource name and does not expose any resources to the caller.
130         // It's OK to suppress the SxS warning.
131         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
132         [ResourceExposure(ResourceScope.None)]
133         public void Load(IXPathNavigable stylesheet) {
134             Reset();
135             LoadInternal(stylesheet, XsltSettings.Default, XsltConfigSection.CreateDefaultResolver());
136         }
137
138         // SxS: This method does not take any resource name and does not expose any resources to the caller.
139         // It's OK to suppress the SxS warning.
140         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
141         [ResourceExposure(ResourceScope.None)]
142         public void Load(IXPathNavigable stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) {
143             Reset();
144             LoadInternal(stylesheet, settings, stylesheetResolver);
145         }
146
147         [ResourceConsumption(ResourceScope.Machine)]
148         [ResourceExposure(ResourceScope.Machine)]
149         public void Load(string stylesheetUri) {
150             Reset();
151             if (stylesheetUri == null) {
152                 throw new ArgumentNullException("stylesheetUri");
153             }
154             LoadInternal(stylesheetUri, XsltSettings.Default, XsltConfigSection.CreateDefaultResolver());
155         }
156
157         [ResourceConsumption(ResourceScope.Machine)]
158         [ResourceExposure(ResourceScope.Machine)]
159         public void Load(string stylesheetUri, XsltSettings settings, XmlResolver stylesheetResolver) {
160             Reset();
161             if (stylesheetUri == null) {
162                 throw new ArgumentNullException("stylesheetUri");
163             }
164             LoadInternal(stylesheetUri, settings, stylesheetResolver);
165         }
166
167         [ResourceConsumption(ResourceScope.Machine)]
168         [ResourceExposure(ResourceScope.Machine)]
169         private CompilerResults LoadInternal(object stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) {
170             if (stylesheet == null) {
171                 throw new ArgumentNullException("stylesheet");
172             }
173             if (settings == null) {
174                 settings = XsltSettings.Default;
175             }
176             CompileXsltToQil(stylesheet, settings, stylesheetResolver);
177             CompilerError error = GetFirstError();
178             if (error != null) {
179                 throw new XslLoadException(error);
180             }
181             if (!settings.CheckOnly) {
182                 CompileQilToMsil(settings);
183             }
184             return this.compilerResults;
185         }
186
187         [ResourceConsumption(ResourceScope.Machine)]
188         [ResourceExposure(ResourceScope.Machine)]
189         private void CompileXsltToQil(object stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) {
190             this.compilerResults = new Compiler(settings, this.enableDebug, null).Compile(stylesheet, stylesheetResolver, out this.qil);
191         }
192
193         /// <summary>
194         /// Returns the first compiler error except warnings
195         /// </summary>
196         private CompilerError GetFirstError() {
197             foreach (CompilerError error in compilerResults.Errors) {
198                 if (!error.IsWarning) {
199                     return error;
200                 }
201             }
202             return null;
203         }
204
205         private void CompileQilToMsil(XsltSettings settings) {
206             this.command = new XmlILGenerator().Generate(this.qil, /*typeBuilder:*/null);
207             this.outputSettings = this.command.StaticData.DefaultWriterSettings;
208             this.qil = null;
209         }
210
211         //------------------------------------------------
212         // Compile stylesheet to a TypeBuilder
213         //------------------------------------------------
214
215         private static volatile ConstructorInfo GeneratedCodeCtor;
216
217         [ResourceConsumption(ResourceScope.Machine)]
218         [ResourceExposure(ResourceScope.Machine)]        
219         public static CompilerErrorCollection CompileToType(XmlReader stylesheet, XsltSettings settings, XmlResolver stylesheetResolver, bool debug, TypeBuilder typeBuilder, string scriptAssemblyPath) {
220             if (stylesheet == null)
221                 throw new ArgumentNullException("stylesheet");
222
223             if (typeBuilder == null)
224                 throw new ArgumentNullException("typeBuilder");
225
226             if (settings == null)
227                 settings = XsltSettings.Default;
228
229             if (settings.EnableScript && scriptAssemblyPath == null)
230                 throw new ArgumentNullException("scriptAssemblyPath");
231
232             if (scriptAssemblyPath != null)
233                 scriptAssemblyPath = Path.GetFullPath(scriptAssemblyPath);
234
235             QilExpression qil;
236             CompilerErrorCollection errors = new Compiler(settings, debug, scriptAssemblyPath).Compile(stylesheet, stylesheetResolver, out qil).Errors;
237
238             if (!errors.HasErrors) {
239                 // Mark the type with GeneratedCodeAttribute to identify its origin
240                 if (GeneratedCodeCtor == null)
241                     GeneratedCodeCtor = typeof(GeneratedCodeAttribute).GetConstructor(new Type[] { typeof(string), typeof(string) });
242
243                 typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(GeneratedCodeCtor,
244                     new object[] { typeof(XslCompiledTransform).FullName, Version }));
245
246                 new XmlILGenerator().Generate(qil, typeBuilder);
247             }
248
249             return errors;
250         }
251
252         //------------------------------------------------
253         // Load compiled stylesheet from a Type
254         //------------------------------------------------
255
256         public void Load(Type compiledStylesheet) {
257             Reset();
258             if (compiledStylesheet == null)
259                 throw new ArgumentNullException("compiledStylesheet");
260
261             object[] customAttrs = compiledStylesheet.GetCustomAttributes(typeof(GeneratedCodeAttribute), /*inherit:*/false);
262             GeneratedCodeAttribute generatedCodeAttr = customAttrs.Length > 0 ? (GeneratedCodeAttribute)customAttrs[0] : null;
263
264             // If GeneratedCodeAttribute is not there, it is not a compiled stylesheet class
265             if (generatedCodeAttr != null && generatedCodeAttr.Tool == typeof(XslCompiledTransform).FullName) {
266
267                 if(new Version(Version).CompareTo(new Version(generatedCodeAttr.Version)) < 0) {
268                     throw new ArgumentException(Res.GetString(Res.Xslt_IncompatibleCompiledStylesheetVersion, generatedCodeAttr.Version, Version), "compiledStylesheet");
269                 }
270
271                 FieldInfo fldData  = compiledStylesheet.GetField(XmlQueryStaticData.DataFieldName,  BindingFlags.Static | BindingFlags.NonPublic);
272                 FieldInfo fldTypes = compiledStylesheet.GetField(XmlQueryStaticData.TypesFieldName, BindingFlags.Static | BindingFlags.NonPublic);
273
274                 // If private fields are not there, it is not a compiled stylesheet class
275                 if (fldData != null && fldTypes != null) {
276                     if (System.Xml.XmlConfiguration.XsltConfigSection.EnableMemberAccessForXslCompiledTransform)
277                     {
278                         // Need MemberAccess reflection permission to access a private data field and create a delegate
279                         new ReflectionPermission(ReflectionPermissionFlag.MemberAccess).Assert();
280                     }
281
282                     // Retrieve query static data from the type
283                     byte[] queryData = fldData.GetValue(/*this:*/null) as byte[];
284
285                     if (queryData != null) {
286                         MethodInfo executeMethod = compiledStylesheet.GetMethod("Execute", BindingFlags.Static | BindingFlags.NonPublic);
287                         Type[] earlyBoundTypes = (Type[])fldTypes.GetValue(/*this:*/null);
288
289                         // Load the stylesheet
290                         Load(executeMethod, queryData, earlyBoundTypes);                        
291                         return;
292                     }
293                 }
294             }
295
296             // Throw an exception if the command was not loaded
297             if (this.command == null)
298                 throw new ArgumentException(Res.GetString(Res.Xslt_NotCompiledStylesheet, compiledStylesheet.FullName), "compiledStylesheet");
299         }
300
301         public void Load(MethodInfo executeMethod, byte[] queryData, Type[] earlyBoundTypes) {
302             Reset();
303
304             if (executeMethod == null)
305                 throw new ArgumentNullException("executeMethod");
306
307             if (queryData == null)
308                 throw new ArgumentNullException("queryData");
309
310             // earlyBoundTypes may be null
311
312             if (!System.Xml.XmlConfiguration.XsltConfigSection.EnableMemberAccessForXslCompiledTransform)
313             {
314                 // make sure we have permission to create the delegate if the type is not visible.
315                 // If the declaring type is null we cannot check for visibility.
316                 // NOTE: a DynamicMethod will always have a DeclaringType == null. DynamicMethods will do demand on their own if skipVisibility is true. 
317                 if (executeMethod.DeclaringType != null && !executeMethod.DeclaringType.IsVisible)
318                 {
319                     new ReflectionPermission(ReflectionPermissionFlag.MemberAccess).Demand();
320                 }
321             }
322
323             DynamicMethod dm = executeMethod as DynamicMethod;
324             Delegate delExec = (dm != null) ? dm.CreateDelegate(typeof(ExecuteDelegate)) : Delegate.CreateDelegate(typeof(ExecuteDelegate), executeMethod);
325             this.command = new XmlILCommand((ExecuteDelegate)delExec, new XmlQueryStaticData(queryData, earlyBoundTypes));
326             this.outputSettings = this.command.StaticData.DefaultWriterSettings;
327         }
328
329         //------------------------------------------------
330         // Transform methods which take an IXPathNavigable
331         //------------------------------------------------
332
333         public void Transform(IXPathNavigable input, XmlWriter results) {
334             CheckArguments(input, results);
335             Transform(input, (XsltArgumentList)null, results, XsltConfigSection.CreateDefaultResolver());
336         }
337
338         public void Transform(IXPathNavigable input, XsltArgumentList arguments, XmlWriter results) {
339             CheckArguments(input, results);
340             Transform(input, arguments, results, XsltConfigSection.CreateDefaultResolver());
341         }
342
343         public void Transform(IXPathNavigable input, XsltArgumentList arguments, TextWriter results) {
344             CheckArguments(input, results);
345             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
346                 Transform(input, arguments, writer, XsltConfigSection.CreateDefaultResolver());
347                 writer.Close();
348             }
349         }
350
351         public void Transform(IXPathNavigable input, XsltArgumentList arguments, Stream results) {
352             CheckArguments(input, results);
353             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
354                 Transform(input, arguments, writer, XsltConfigSection.CreateDefaultResolver());
355                 writer.Close();
356             }
357         }
358
359         //------------------------------------------------
360         // Transform methods which take an XmlReader
361         //------------------------------------------------
362
363         public void Transform(XmlReader input, XmlWriter results) {
364             CheckArguments(input, results);
365             Transform(input, (XsltArgumentList)null, results, XsltConfigSection.CreateDefaultResolver());
366         }
367
368         public void Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results) {
369             CheckArguments(input, results);
370             Transform(input, arguments, results, XsltConfigSection.CreateDefaultResolver());
371         }
372
373         public void Transform(XmlReader input, XsltArgumentList arguments, TextWriter results) {
374             CheckArguments(input, results);
375             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
376                 Transform(input, arguments, writer, XsltConfigSection.CreateDefaultResolver());
377                 writer.Close();
378             }
379         }
380
381         public void Transform(XmlReader input, XsltArgumentList arguments, Stream results) {
382             CheckArguments(input, results);
383             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
384                 Transform(input, arguments, writer, XsltConfigSection.CreateDefaultResolver());
385                 writer.Close();
386             }
387         }
388
389         //------------------------------------------------
390         // Transform methods which take a uri
391         // SxS Note: Annotations should propagate to the caller to have him either check that 
392         // the passed URIs are SxS safe or decide that they don't have to be SxS safe and 
393         // suppress the message. 
394         //------------------------------------------------
395
396         [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings")]
397         [ResourceConsumption(ResourceScope.Machine)]
398         [ResourceExposure(ResourceScope.Machine)]
399         public void Transform(string inputUri, XmlWriter results) {
400             CheckArguments(inputUri, results);
401             using (XmlReader reader = XmlReader.Create(inputUri, ReaderSettings)) {
402                 Transform(reader, (XsltArgumentList)null, results, XsltConfigSection.CreateDefaultResolver());
403             }
404         }
405
406         [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings")]
407         [ResourceConsumption(ResourceScope.Machine)]
408         [ResourceExposure(ResourceScope.Machine)]
409         public void Transform(string inputUri, XsltArgumentList arguments, XmlWriter results) {
410             CheckArguments(inputUri, results);
411             using (XmlReader reader = XmlReader.Create(inputUri, ReaderSettings)) {
412                 Transform(reader, arguments, results, XsltConfigSection.CreateDefaultResolver());
413             }
414         }
415
416         [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings")]
417         [ResourceConsumption(ResourceScope.Machine)]
418         [ResourceExposure(ResourceScope.Machine)]
419         public void Transform(string inputUri, XsltArgumentList arguments, TextWriter results) {
420             CheckArguments(inputUri, results);
421             using (XmlReader reader = XmlReader.Create(inputUri, ReaderSettings))
422             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
423                 Transform(reader, arguments, writer, XsltConfigSection.CreateDefaultResolver());
424                 writer.Close();
425             }
426         }
427
428         [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings")]
429         [ResourceConsumption(ResourceScope.Machine)]
430         [ResourceExposure(ResourceScope.Machine)]
431         public void Transform(string inputUri, XsltArgumentList arguments, Stream results) {
432             CheckArguments(inputUri, results);
433             using (XmlReader reader = XmlReader.Create(inputUri, ReaderSettings))
434             using (XmlWriter writer = XmlWriter.Create(results, OutputSettings)) {
435                 Transform(reader, arguments, writer, XsltConfigSection.CreateDefaultResolver());
436                 writer.Close();
437             }
438         }
439
440         [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings")]
441         [ResourceConsumption(ResourceScope.Machine)]
442         [ResourceExposure(ResourceScope.Machine)]
443         public void Transform(string inputUri, string resultsFile) {
444             if (inputUri == null)
445                 throw new ArgumentNullException("inputUri");
446
447             if (resultsFile == null)
448                 throw new ArgumentNullException("resultsFile");
449
450             // SQLBUDT 276415: Prevent wiping out the content of the input file if the output file is the same
451             using (XmlReader reader = XmlReader.Create(inputUri, ReaderSettings))
452             using (XmlWriter writer = XmlWriter.Create(resultsFile, OutputSettings)) {
453                 Transform(reader, (XsltArgumentList)null, writer, XsltConfigSection.CreateDefaultResolver());
454                 writer.Close();
455             }
456         }
457
458         //------------------------------------------------
459         // Main Transform overloads
460         //------------------------------------------------
461
462         // SxS: This method does not take any resource name and does not expose any resources to the caller.
463         // It's OK to suppress the SxS warning.
464         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
465         [ResourceExposure(ResourceScope.None)]
466         public void Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver) {
467             CheckArguments(input, results);
468             CheckCommand();
469             this.command.Execute((object)input, documentResolver, arguments, results);
470         }
471
472         // SxS: This method does not take any resource name and does not expose any resources to the caller.
473         // It's OK to suppress the SxS warning.
474         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
475         [ResourceExposure(ResourceScope.None)]
476         public void Transform(IXPathNavigable input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver) {
477             CheckArguments(input, results);
478             CheckCommand();
479             this.command.Execute((object)input.CreateNavigator(), documentResolver, arguments, results);
480         }
481
482         //------------------------------------------------
483         // Helper methods
484         //------------------------------------------------
485
486         private static void CheckArguments(object input, object results) {
487             if (input == null)
488                 throw new ArgumentNullException("input");
489
490             if (results == null)
491                 throw new ArgumentNullException("results");
492         }
493
494         private static void CheckArguments(string inputUri, object results) {
495             if (inputUri == null)
496                 throw new ArgumentNullException("inputUri");
497
498             if (results == null)
499                 throw new ArgumentNullException("results");
500         }
501
502         private void CheckCommand() {
503             if (this.command == null) {
504                 throw new InvalidOperationException(Res.GetString(Res.Xslt_NoStylesheetLoaded));
505             }
506         }
507
508         //------------------------------------------------
509         // Test suites entry points
510         //------------------------------------------------
511
512         [ResourceConsumption(ResourceScope.Machine)]
513         [ResourceExposure(ResourceScope.Machine)]
514         private QilExpression TestCompile(object stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) {
515             Reset();
516             CompileXsltToQil(stylesheet, settings, stylesheetResolver);
517             return qil;
518         }
519
520         private void TestGenerate(XsltSettings settings) {
521             Debug.Assert(qil != null, "You must compile to Qil first");
522             CompileQilToMsil(settings);
523         }
524
525         [ResourceConsumption(ResourceScope.Machine)]
526         [ResourceExposure(ResourceScope.Machine)]
527         private void Transform(string inputUri, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver) {
528             command.Execute(inputUri, documentResolver, arguments, results);
529         }
530
531         internal static void PrintQil(object qil, XmlWriter xw, bool printComments, bool printTypes, bool printLineInfo) {
532             QilExpression qilExpr = (QilExpression)qil;
533             QilXmlWriter.Options options = QilXmlWriter.Options.None;
534             QilValidationVisitor.Validate(qilExpr);
535             if (printComments) options |= QilXmlWriter.Options.Annotations;
536             if (printTypes) options |= QilXmlWriter.Options.TypeInfo;
537             if (printLineInfo) options |= QilXmlWriter.Options.LineInfo;
538             QilXmlWriter qw = new QilXmlWriter(xw, options);
539             qw.ToXml(qilExpr);
540             xw.Flush();
541         }
542     }
543 #endif // ! HIDE_XSL
544 }