2005-03-03 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslTransformProcessor.cs
1 //
2 // XslTransformProcessor.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //      
8 // (C) 2003 Ben Maurer
9 // (C) 2003 Atsushi Enomoto
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.IO;
35 using System.Collections;
36 using System.Text;
37 using System.Xml;
38 using System.Xml.XPath;
39 using System.Xml.Xsl;
40 using Mono.Xml.Xsl.Operations;
41 using Mono.Xml.XPath;
42
43 using QName = System.Xml.XmlQualifiedName;
44
45 namespace Mono.Xml.Xsl {
46         internal class XslTransformProcessor {
47                 CompiledStylesheet compiledStyle;
48                 
49                 XslStylesheet style;
50                 
51                 Stack currentTemplateStack = new Stack ();
52                 
53                 XPathNavigator root;
54                 XsltArgumentList args;
55                 XmlResolver resolver;
56                 bool outputStylesheetXmlns;
57                 string currentOutputUri;
58                 
59                 internal readonly XsltCompiledContext XPathContext;
60
61                 // Store the values of global params
62                 internal Hashtable globalVariableTable = new Hashtable ();
63                 
64                 public XslTransformProcessor (CompiledStylesheet style)
65                 {
66                         this.XPathContext = new XsltCompiledContext (this);
67                         this.compiledStyle = style;
68                         this.style = style.Style;
69                 }
70
71                 public void Process (XPathNavigator root, Outputter outputtter, XsltArgumentList args, XmlResolver resolver)
72                 {
73                         this.args = args;
74                         this.root = root;
75                         this.resolver = resolver != null ? resolver : new XmlUrlResolver ();
76                         this.outputStylesheetXmlns = true;
77                         this.currentOutputUri = String.Empty;
78
79                         // This is also done after the transformation, just for reuse after exception.
80                         foreach (XslKey key in style.Keys.Values)
81                                 key.ClearKeyTable ();
82
83                         XPathExpression exp = root.Compile (".");
84                         PushNodeset (root.Select (exp, this.XPathContext));
85                         
86                         // have to evaluate the params first, as Global vars may
87                         // be dependant on them
88                         if (args != null)
89                         {
90                                 foreach (XslGlobalVariable v in CompiledStyle.Variables.Values)
91                                 {
92                                         if (v is XslGlobalParam)
93                                         {
94                                                 object p = args.GetParam(v.Name.Name, v.Name.Namespace);
95                                                 if (p != null)
96                                                         ((XslGlobalParam)v).Override (this, p);
97
98                                                 v.Evaluate (this);
99                                         }
100                                 }
101                         }
102
103                         foreach (XslGlobalVariable v in CompiledStyle.Variables.Values) {
104                                 if (args == null || !(v is XslGlobalParam)) {
105                                         v.Evaluate (this);
106                                 }
107                         }
108                         
109                         PopNodeset ();
110                         
111                         this.PushOutput (outputtter);
112                         this.ApplyTemplates (root.Select (exp, this.XPathContext), QName.Empty, null);
113                         this.PopOutput ();
114
115                         foreach (XslKey key in style.Keys.Values)
116                                 key.ClearKeyTable ();
117                 }
118                 
119                 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
120                 public XsltArgumentList Arguments {get{return args;}}
121                 
122                 public MSXslScriptManager ScriptManager {
123                         get { return compiledStyle.ScriptManager; }
124                 }
125
126                 #region Document Resolution
127                 public XmlResolver Resolver {get{return resolver;}}
128                 
129                 Hashtable docCache;
130                 
131                 public XPathNavigator GetDocument (Uri uri)
132                 {
133                         XPathNavigator result;
134                         
135                         if (docCache != null) {
136                                 result = docCache [uri] as XPathNavigator;
137                                 if (result != null)
138                                         return result.Clone();
139                         } else {
140                                 docCache = new Hashtable();
141                         }
142
143                         XmlReader rdr = null;
144                         try {
145                                 rdr = new XmlTextReader (uri.ToString(), (Stream) resolver.GetEntity (uri, null, null), root.NameTable);
146                                 XmlValidatingReader xvr = new XmlValidatingReader (rdr);
147                                 xvr.ValidationType = ValidationType.None;
148                                 result = new XPathDocument (xvr, XmlSpace.Preserve).CreateNavigator ();
149                         } finally {
150                                 if (rdr != null)
151                                         rdr.Close ();
152                         }
153                         docCache [uri] = result.Clone ();
154                         return result;
155                 }
156                 
157                 #endregion
158                 
159                 #region Output
160                 Stack outputStack = new Stack ();
161                 
162                 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
163                 
164                 public void PushOutput (Outputter newOutput)
165                 {
166                         this.outputStack.Push (newOutput);
167                 }
168                 
169                 public Outputter PopOutput ()
170                 {
171                         Outputter ret = (Outputter)this.outputStack.Pop ();
172                         ret.Done ();
173                         return ret;
174                 }
175                 
176                 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
177
178                 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
179
180                 public string CurrentOutputUri { get { return currentOutputUri; } }
181
182                 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
183                 #endregion
184                 
185                 #region AVT StringBuilder
186                 StringBuilder avtSB;
187         
188                 #if DEBUG
189                 bool avtSBlock = false;
190                 #endif
191         
192                 public StringBuilder GetAvtStringBuilder ()
193                 {
194                         #if DEBUG
195                                 if (avtSBlock)
196                                         throw new XsltException ("String Builder was locked", null);
197                                 avtSBlock = true;
198                         #endif
199                         
200                         if (avtSB == null)
201                                 avtSB = new StringBuilder ();
202                         
203                         return avtSB;
204                 }
205                 
206                 public string ReleaseAvtStringBuilder ()
207                 {
208                         #if DEBUG
209                                 if (!avtSBlock)
210                                         throw new XsltException ("you never locked the string builder", null);
211                                 avtSBlock = false;
212                         #endif
213                         
214                         string ret = avtSB.ToString ();
215                         avtSB.Length = 0;
216                         return ret;
217                 }
218                 #endregion
219                 
220                 #region Templates -- Apply/Call
221                 Stack paramPassingCache = new Stack ();
222                 
223                 Hashtable GetParams (ArrayList withParams)
224                 {
225                         if (withParams == null) return null;
226                         Hashtable ret;
227                         
228                         if (paramPassingCache.Count != 0) {
229                                 ret = (Hashtable)paramPassingCache.Pop ();
230                                 ret.Clear ();
231                         } else
232                                 ret = new Hashtable ();
233                         
234                         int len = withParams.Count;
235                         for (int i = 0; i < len; i++) {
236                                 XslVariableInformation param = (XslVariableInformation)withParams [i];
237                                 ret.Add (param.Name, param.Evaluate (this));
238                         }
239                         return ret;
240                 }
241                 
242                 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
243                 {
244
245                         Hashtable passedParams = GetParams (withParams);
246                         
247                         PushNodeset (nodes);
248                         while (NodesetMoveNext ()) {
249                                 XslTemplate t = FindTemplate (CurrentNode, mode);
250                                 currentTemplateStack.Push (t);
251                                 t.Evaluate (this, passedParams);
252                                 currentTemplateStack.Pop ();
253                         }
254                         PopNodeset ();
255                         
256                         if (passedParams != null) paramPassingCache.Push (passedParams);
257                 }
258                 
259                 public void CallTemplate (QName name, ArrayList withParams)
260                 {
261                         Hashtable passedParams = GetParams (withParams);
262                         
263                         XslTemplate t = FindTemplate (name);
264                         currentTemplateStack.Push (null);
265                         t.Evaluate (this, passedParams);
266                         currentTemplateStack.Pop ();
267                         
268                         if (passedParams != null) paramPassingCache.Push (passedParams);
269                 }
270                 
271                 public void ApplyImports ()
272                 {       
273
274                         XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
275                         if (currentTemplate == null)
276                                 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
277                         XslTemplate t;
278                         
279                         for (int i = currentTemplate.Parent.Imports.Count - 1; i >= 0; i--) {
280                                 XslStylesheet s = (XslStylesheet)currentTemplate.Parent.Imports [i];
281                                 t = s.Templates.FindMatch (CurrentNode, currentTemplate.Mode, this);
282                                 if (t != null) {                                        
283                                         currentTemplateStack.Push (t);
284                                         t.Evaluate (this);
285                                         currentTemplateStack.Pop ();
286                                         return;
287                                 }
288                         }
289                         
290                         switch (CurrentNode.NodeType) {
291                         case XPathNodeType.Root:
292                         case XPathNodeType.Element:
293                                 if (currentTemplate.Mode == QName.Empty)
294                                         t = XslDefaultNodeTemplate.Instance;
295                                 else
296                                         t = new XslDefaultNodeTemplate(currentTemplate.Mode);
297                         
298                                 break;
299                         case XPathNodeType.Attribute:
300                         case XPathNodeType.SignificantWhitespace:
301                         case XPathNodeType.Text:
302                         case XPathNodeType.Whitespace:
303                                 t = XslDefaultTextTemplate.Instance;
304                                 break;
305                         
306                         case XPathNodeType.Comment:
307                         case XPathNodeType.ProcessingInstruction:
308                                 t = XslEmptyTemplate.Instance;
309                                 break;
310                         
311                         default:
312                                 t = XslEmptyTemplate.Instance;
313                                 break;
314                         }
315                         currentTemplateStack.Push (t);
316                         t.Evaluate (this);
317                         currentTemplateStack.Pop ();
318                 }
319
320                 internal void TryElementNamespacesOutput (Hashtable nsDecls, ArrayList excludedPrefixes)
321                 {
322                         TryElementNamespacesOutput (nsDecls, excludedPrefixes, null);
323                 }
324
325                 internal void TryElementNamespacesOutput (Hashtable nsDecls, ArrayList excludedPrefixes, string localPrefixInCopy)
326                 {
327                         if (nsDecls == null)
328                                 return;
329
330                         foreach (DictionaryEntry cur in nsDecls) {
331                                 string name = (string)cur.Key;
332                                 string value = (string)cur.Value;
333
334                                 // See XSLT 1.0 errata E25
335                                 if (localPrefixInCopy == name)
336                                         continue;
337                                 if (localPrefixInCopy != null &&
338                                         name.Length == 0 &&
339                                         XPathContext.ElementNamespace.Length == 0)
340                                         continue;
341
342                                 // exclude-result-prefixes, see the spec 7.1.1
343                                 bool skip = false;
344                                 if (style.ExcludeResultPrefixes != null) {
345                                         foreach (XmlQualifiedName exc in style.ExcludeResultPrefixes) {
346                                                 if (exc.Namespace == value) {
347                                                         skip = true;
348                                                         continue;
349                                                 }
350                                         }
351                                 }
352                                 if (skip)
353                                         continue;
354
355                                 if (style.NamespaceAliases [name] != null)
356                                         continue;
357
358                                 switch (value) {//FIXME: compare names by reference
359                                 case "http://www.w3.org/1999/XSL/Transform":
360 //                                      if ("xsl" == name)
361                                                 continue;
362 //                                      else
363 //                                              goto default;
364                                 case XmlNamespaceManager.XmlnsXml:
365                                         if (XmlNamespaceManager.PrefixXml == name)
366                                                 continue;
367                                         else
368                                                 goto default;
369                                 case XmlNamespaceManager.XmlnsXmlns:
370                                         if (XmlNamespaceManager.PrefixXmlns == name)
371                                                 continue;
372                                         else
373                                                 goto default;
374                                 default:
375                                         if (excludedPrefixes == null || !excludedPrefixes.Contains (name))
376                                                 Out.WriteNamespaceDecl (name, value);
377                                         break;
378                                 }
379                         }
380                 }
381
382                 XslTemplate FindTemplate (XPathNavigator node, QName mode)
383                 {
384                         XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
385                         
386                         if (ret != null) return ret;
387
388                         switch (node.NodeType) {
389                         case XPathNodeType.Root:
390                         case XPathNodeType.Element:
391                                 if (mode == QName.Empty)
392                                         return XslDefaultNodeTemplate.Instance;
393                                 else
394                                         return new XslDefaultNodeTemplate(mode);
395                         
396                         case XPathNodeType.Attribute:
397                         case XPathNodeType.SignificantWhitespace:
398                         case XPathNodeType.Text:
399                         case XPathNodeType.Whitespace:
400                                 return XslDefaultTextTemplate.Instance;
401                         
402                         case XPathNodeType.Comment:
403                         case XPathNodeType.ProcessingInstruction:
404                                 return XslEmptyTemplate.Instance;
405                         
406                         default:
407                                 return XslEmptyTemplate.Instance;
408                         }
409                 }
410                 
411                 XslTemplate FindTemplate (QName name)
412                 {
413                         XslTemplate ret = style.Templates.FindTemplate (name);
414                         if (ret != null) return ret;
415                                 
416                         throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
417                 }
418                 
419                 #endregion
420
421                 public void PushForEachContext ()
422                 {
423                         currentTemplateStack.Push (null);
424                 }
425                 
426                 public void PopForEachContext ()
427                 {
428                         currentTemplateStack.Pop ();
429                 }
430                 
431
432                 #region Nodeset Context
433                 ArrayList nodesetStack = new ArrayList ();
434                 
435                 public XPathNodeIterator CurrentNodeset {
436                         get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
437                 }
438                 
439                 public XPathNavigator CurrentNode {
440                         get {
441                                 XPathNavigator nav = CurrentNodeset.Current;
442                                 if (nav != null)
443                                         return nav;
444                                 // Inside for-each context, CurrentNodeset.Current may be null
445                                 for (int i = nodesetStack.Count - 2; i >= 0; i--) {
446                                         nav = ((XPathNodeIterator) nodesetStack [i]).Current;
447                                         if (nav != null)
448                                                 return nav;
449                                 }
450                                 return null;
451                         }
452                 }
453                 
454                 public bool NodesetMoveNext ()
455                 {
456                         return CurrentNodeset.MoveNext ();
457                 }
458                 
459                 public void PushNodeset (XPathNodeIterator itr)
460                 {
461                         nodesetStack.Add (itr.Clone ());
462                 }
463                 
464                 public void PopNodeset ()
465                 {
466                         nodesetStack.RemoveAt (nodesetStack.Count - 1);
467                 }
468                 #endregion
469                 
470                 #region Evaluate
471                 
472                 public bool Matches (Pattern p, XPathNavigator n)
473                 {
474                         return CompiledStyle.ExpressionStore.PatternMatches (p, this, n);
475                 }
476                 
477                 public object Evaluate (XPathExpression expr)
478                 {
479                         expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
480                         expr.SetContext (XPathContext);
481
482                         XPathNodeIterator itr = CurrentNodeset;
483                         return itr.Current.Evaluate (expr, itr, XPathContext);
484                 }
485                 
486                 public string EvaluateString (XPathExpression expr)
487                 {
488                         expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
489                         expr.SetContext (XPathContext);
490                         
491                         XPathNodeIterator itr = CurrentNodeset;
492                         return itr.Current.EvaluateString (expr, itr, XPathContext);
493                 }
494                                 
495                 public bool EvaluateBoolean (XPathExpression expr)
496                 {
497                         expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
498                         expr.SetContext (XPathContext);
499                         
500                         XPathNodeIterator itr = CurrentNodeset;
501                         return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
502                 }
503                 
504                 public double EvaluateNumber (XPathExpression expr)
505                 {
506                         expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
507                         expr.SetContext (XPathContext);
508                         
509                         XPathNodeIterator itr = CurrentNodeset;
510                         return itr.Current.EvaluateNumber (expr, itr, XPathContext);
511                 }
512                 
513                 public XPathNodeIterator Select (XPathExpression expr)
514                 {
515                         expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
516                         expr.SetContext (XPathContext);
517                         return CurrentNodeset.Current.Select (expr, XPathContext);
518                 }
519                 
520                 #endregion
521                 
522                 public XslAttributeSet ResolveAttributeSet (QName name)
523                 {
524                         return CompiledStyle.ResolveAttributeSet (name);
525                 }
526                 
527                 #region Variable Stack
528                 Stack variableStack = new Stack ();
529                 object [] currentStack;
530                 public int StackItemCount {
531                         get {
532                                 if (currentStack == null)
533                                       return 0;
534                                 for (int i = 0; i < currentStack.Length; i++)
535                                         if (currentStack [i] == null)
536                                                 return i;
537                                 return currentStack.Length;
538                         }
539                 }
540
541                 public object GetStackItem (int slot)
542                 {
543                         return currentStack [slot];
544                 }
545                 
546                 public void SetStackItem (int slot, object o)
547                 {
548                         currentStack [slot] = o;
549                 }
550                 
551                 public void PushStack (int stackSize)
552                 {
553                         variableStack.Push (currentStack);
554                         currentStack = new object [stackSize];
555                 }
556                 
557                 public void PopStack ()
558                 {
559                         currentStack = (object[])variableStack.Pop();
560                 }
561                 
562                 #endregion
563                 
564                 #region Free/Busy
565                 Hashtable busyTable = new Hashtable ();
566                 static object busyObject = new object ();
567                 
568                 public void SetBusy (object o)
569                 {
570                         busyTable [o] = busyObject;
571                 }
572                 
573                 public void SetFree (object o)
574                 {
575                         busyTable.Remove (o);
576                 }
577                 
578                 public bool IsBusy (object o)
579                 {
580                         return busyTable [o] == busyObject;
581                 }
582                 #endregion
583
584                 public bool PushElementState (string prefix, string name, string ns, bool preserveWhitespace)
585                 {
586                         bool b = IsCData (name, ns);
587                         XPathContext.PushScope ();
588                         Out.InsideCDataSection = XPathContext.IsCData = b;
589                         XPathContext.WhitespaceHandling = preserveWhitespace;
590                         XPathContext.ElementPrefix = prefix;
591                         XPathContext.ElementNamespace = ns;
592                         return b;
593                 }
594
595                 bool IsCData (string name, string ns)
596                 {
597                         for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
598                                 XmlQualifiedName qname = Output.CDataSectionElements [i];
599                                 if (qname.Name == name && qname.Namespace == ns) {
600                                         return true;
601                                 }
602                         }
603                         return false;
604                 }
605
606                 public void PopCDataState (bool isCData)
607                 {
608                         XPathContext.PopScope ();
609                         Out.InsideCDataSection = XPathContext.IsCData;
610                 }
611
612                 public bool PreserveWhitespace ()
613                 {
614                         return XPathContext.PreserveWhitespace (CurrentNode);
615                 }
616         }
617 }