TARGET_JVM: refer nunit from mono tree
[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                         XPathExpression exp = root.Compile (".");
80                         PushNodeset (root.Select (exp, this.XPathContext));
81                         
82                         // have to evaluate the params first, as Global vars may
83                         // be dependant on them
84                         if (args != null)
85                         {
86                                 foreach (XslGlobalVariable v in CompiledStyle.Variables.Values)
87                                 {
88                                         if (v is XslGlobalParam)
89                                         {
90                                                 object p = args.GetParam(v.Name.Name, v.Name.Namespace);
91                                                 if (p != null)
92                                                         ((XslGlobalParam)v).Override (this, p);
93
94                                                 v.Evaluate (this);
95                                         }
96                                 }
97                         }
98
99                         foreach (XslGlobalVariable v in CompiledStyle.Variables.Values) {
100                                 if (args == null || !(v is XslGlobalParam)) {
101                                         v.Evaluate (this);
102                                 }
103                         }
104                         
105                         PopNodeset ();
106                         
107                         this.PushOutput (outputtter);
108                         this.ApplyTemplates (root.Select (exp, this.XPathContext), QName.Empty, null);
109                         this.PopOutput ();
110                 }
111                 
112                 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
113                 public XsltArgumentList Arguments {get{return args;}}
114                 
115                 public MSXslScriptManager ScriptManager {
116                         get { return compiledStyle.ScriptManager; }
117                 }
118
119                 #region Document Resolution
120                 public XmlResolver Resolver {get{return resolver;}}
121                 
122                 Hashtable docCache;
123                 
124                 public XPathNavigator GetDocument (Uri uri)
125                 {
126                         XPathNavigator result;
127                         
128                         if (docCache != null) {
129                                 result = docCache [uri] as XPathNavigator;
130                                 if (result != null)
131                                         return result.Clone();
132                         } else {
133                                 docCache = new Hashtable();
134                         }
135
136                         XmlReader rdr = null;
137                         try {
138                                 rdr = new XmlTextReader (uri.ToString(), (Stream) resolver.GetEntity (uri, null, null), root.NameTable);
139                                 XmlValidatingReader xvr = new XmlValidatingReader (rdr);
140                                 xvr.ValidationType = ValidationType.None;
141                                 result = new XPathDocument (xvr, XmlSpace.Preserve).CreateNavigator ();
142                         } finally {
143                                 if (rdr != null)
144                                         rdr.Close ();
145                         }
146                         docCache [uri] = result.Clone ();
147                         return result;
148                 }
149                 
150                 #endregion
151                 
152                 #region Output
153                 Stack outputStack = new Stack ();
154                 
155                 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
156                 
157                 public void PushOutput (Outputter newOutput)
158                 {
159                         this.outputStack.Push (newOutput);
160                 }
161                 
162                 public Outputter PopOutput ()
163                 {
164                         Outputter ret = (Outputter)this.outputStack.Pop ();
165                         ret.Done ();
166                         return ret;
167                 }
168                 
169                 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
170
171                 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
172
173                 public string CurrentOutputUri { get { return currentOutputUri; } }
174
175                 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
176                 #endregion
177                 
178                 #region AVT StringBuilder
179                 StringBuilder avtSB;
180         
181                 #if DEBUG
182                 bool avtSBlock = false;
183                 #endif
184         
185                 public StringBuilder GetAvtStringBuilder ()
186                 {
187                         #if DEBUG
188                                 if (avtSBlock)
189                                         throw new XsltException ("String Builder was locked", null);
190                                 avtSBlock = true;
191                         #endif
192                         
193                         if (avtSB == null)
194                                 avtSB = new StringBuilder ();
195                         
196                         return avtSB;
197                 }
198                 
199                 public string ReleaseAvtStringBuilder ()
200                 {
201                         #if DEBUG
202                                 if (!avtSBlock)
203                                         throw new XsltException ("you never locked the string builder", null);
204                                 avtSBlock = false;
205                         #endif
206                         
207                         string ret = avtSB.ToString ();
208                         avtSB.Length = 0;
209                         return ret;
210                 }
211                 #endregion
212                 
213                 #region Templates -- Apply/Call
214                 Stack paramPassingCache = new Stack ();
215                 
216                 Hashtable GetParams (ArrayList withParams)
217                 {
218                         if (withParams == null) return null;
219                         Hashtable ret;
220                         
221                         if (paramPassingCache.Count != 0) {
222                                 ret = (Hashtable)paramPassingCache.Pop ();
223                                 ret.Clear ();
224                         } else
225                                 ret = new Hashtable ();
226                         
227                         int len = withParams.Count;
228                         for (int i = 0; i < len; i++) {
229                                 XslVariableInformation param = (XslVariableInformation)withParams [i];
230                                 ret.Add (param.Name, param.Evaluate (this));
231                         }
232                         return ret;
233                 }
234                 
235                 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
236                 {
237
238                         Hashtable passedParams = GetParams (withParams);
239                         
240                         PushNodeset (nodes);
241                         while (NodesetMoveNext ()) {
242                                 XslTemplate t = FindTemplate (CurrentNode, mode);
243                                 currentTemplateStack.Push (t);
244                                 t.Evaluate (this, passedParams);
245                                 currentTemplateStack.Pop ();
246                         }
247                         PopNodeset ();
248                         
249                         if (passedParams != null) paramPassingCache.Push (passedParams);
250                 }
251                 
252                 public void CallTemplate (QName name, ArrayList withParams)
253                 {
254                         Hashtable passedParams = GetParams (withParams);
255                         
256                         XslTemplate t = FindTemplate (name);
257                         currentTemplateStack.Push (null);
258                         t.Evaluate (this, passedParams);
259                         currentTemplateStack.Pop ();
260                         
261                         if (passedParams != null) paramPassingCache.Push (passedParams);
262                 }
263                 
264                 public void ApplyImports ()
265                 {       
266
267                         XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
268                         if (currentTemplate == null)
269                                 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
270                         XslTemplate t;
271                         
272                         for (int i = currentTemplate.Parent.Imports.Count - 1; i >= 0; i--) {
273                                 XslStylesheet s = (XslStylesheet)currentTemplate.Parent.Imports [i];
274                                 t = s.Templates.FindMatch (CurrentNode, currentTemplate.Mode, this);
275                                 if (t != null) {                                        
276                                         currentTemplateStack.Push (t);
277                                         t.Evaluate (this);
278                                         currentTemplateStack.Pop ();
279                                         return;
280                                 }
281                         }
282                         
283                         switch (CurrentNode.NodeType) {
284                         case XPathNodeType.Root:
285                         case XPathNodeType.Element:
286                                 if (currentTemplate.Mode == QName.Empty)
287                                         t = XslDefaultNodeTemplate.Instance;
288                                 else
289                                         t = new XslDefaultNodeTemplate(currentTemplate.Mode);
290                         
291                                 break;
292                         case XPathNodeType.Attribute:
293                         case XPathNodeType.SignificantWhitespace:
294                         case XPathNodeType.Text:
295                         case XPathNodeType.Whitespace:
296                                 t = XslDefaultTextTemplate.Instance;
297                                 break;
298                         
299                         case XPathNodeType.Comment:
300                         case XPathNodeType.ProcessingInstruction:
301                                 t = XslEmptyTemplate.Instance;
302                                 break;
303                         
304                         default:
305                                 t = XslEmptyTemplate.Instance;
306                                 break;
307                         }
308                         currentTemplateStack.Push (t);
309                         t.Evaluate (this);
310                         currentTemplateStack.Pop ();
311                 }
312
313                 // Outputs Literal namespace nodes described in spec 7.7.1
314                 internal void OutputLiteralNamespaceUriNodes (Hashtable nsDecls, ArrayList excludedPrefixes, string localPrefixInCopy)
315                 {
316                         if (nsDecls == null)
317                                 return;
318
319                         foreach (DictionaryEntry cur in nsDecls) {
320                                 string name = (string)cur.Key;
321                                 string value = (string)cur.Value;
322
323                                 // See XSLT 1.0 errata E25
324                                 if (localPrefixInCopy == name)
325                                         continue;
326                                 if (localPrefixInCopy != null &&
327                                         name.Length == 0 &&
328                                         XPathContext.ElementNamespace.Length == 0)
329                                         continue;
330
331                                 // exclude-result-prefixes, see the spec 7.1.1
332                                 bool skip = false;
333                                 if (style.ExcludeResultPrefixes != null) {
334                                         foreach (XmlQualifiedName exc in style.ExcludeResultPrefixes) {
335                                                 if (exc.Namespace == value) {
336                                                         skip = true;
337                                                         continue;
338                                                 }
339                                         }
340                                 }
341                                 if (skip)
342                                         continue;
343
344                                 if (style.NamespaceAliases [name] != null)
345                                         continue;
346
347                                 switch (value) {//FIXME: compare names by reference
348                                 case "http://www.w3.org/1999/XSL/Transform":
349 //                                      if ("xsl" == name)
350                                                 continue;
351 //                                      else
352 //                                              goto default;
353                                 case XmlNamespaceManager.XmlnsXml:
354                                         if (XmlNamespaceManager.PrefixXml == name)
355                                                 continue;
356                                         else
357                                                 goto default;
358                                 case XmlNamespaceManager.XmlnsXmlns:
359                                         if (XmlNamespaceManager.PrefixXmlns == name)
360                                                 continue;
361                                         else
362                                                 goto default;
363                                 default:
364                                         if (excludedPrefixes == null || !excludedPrefixes.Contains (name))
365                                                 Out.WriteNamespaceDecl (name, value);
366                                         break;
367                                 }
368                         }
369                 }
370
371                 XslTemplate FindTemplate (XPathNavigator node, QName mode)
372                 {
373                         XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
374                         
375                         if (ret != null) return ret;
376
377                         switch (node.NodeType) {
378                         case XPathNodeType.Root:
379                         case XPathNodeType.Element:
380                                 if (mode == QName.Empty)
381                                         return XslDefaultNodeTemplate.Instance;
382                                 else
383                                         return new XslDefaultNodeTemplate(mode);
384                         
385                         case XPathNodeType.Attribute:
386                         case XPathNodeType.SignificantWhitespace:
387                         case XPathNodeType.Text:
388                         case XPathNodeType.Whitespace:
389                                 return XslDefaultTextTemplate.Instance;
390                         
391                         case XPathNodeType.Comment:
392                         case XPathNodeType.ProcessingInstruction:
393                                 return XslEmptyTemplate.Instance;
394                         
395                         default:
396                                 return XslEmptyTemplate.Instance;
397                         }
398                 }
399                 
400                 XslTemplate FindTemplate (QName name)
401                 {
402                         XslTemplate ret = style.Templates.FindTemplate (name);
403                         if (ret != null) return ret;
404                                 
405                         throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
406                 }
407                 
408                 #endregion
409
410                 public void PushForEachContext ()
411                 {
412                         currentTemplateStack.Push (null);
413                 }
414                 
415                 public void PopForEachContext ()
416                 {
417                         currentTemplateStack.Pop ();
418                 }
419                 
420
421                 #region Nodeset Context
422                 ArrayList nodesetStack = new ArrayList ();
423                 
424                 public XPathNodeIterator CurrentNodeset {
425                         get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
426                 }
427                 
428                 public XPathNavigator CurrentNode {
429                         get {
430                                 XPathNavigator nav = CurrentNodeset.Current;
431                                 if (nav != null)
432                                         return nav;
433                                 // Inside for-each context, CurrentNodeset.Current may be null
434                                 for (int i = nodesetStack.Count - 2; i >= 0; i--) {
435                                         nav = ((XPathNodeIterator) nodesetStack [i]).Current;
436                                         if (nav != null)
437                                                 return nav;
438                                 }
439                                 return null;
440                         }
441                 }
442                 
443                 public bool NodesetMoveNext ()
444                 {
445                         return CurrentNodeset.MoveNext ();
446                 }
447                 
448                 public void PushNodeset (XPathNodeIterator itr)
449                 {
450                         nodesetStack.Add (itr);
451                 }
452                 
453                 public void PopNodeset ()
454                 {
455                         nodesetStack.RemoveAt (nodesetStack.Count - 1);
456                 }
457                 #endregion
458                 
459                 #region Evaluate
460                 
461                 public bool Matches (Pattern p, XPathNavigator n)
462                 {
463                         return p.Matches (n, this.XPathContext);
464                 }
465                 
466                 public object Evaluate (XPathExpression expr)
467                 {
468                         XPathNodeIterator itr = CurrentNodeset;
469                         return itr.Current.Evaluate (expr, itr, XPathContext);
470                 }
471                 
472                 public string EvaluateString (XPathExpression expr)
473                 {
474                         XPathNodeIterator itr = CurrentNodeset;
475                         return itr.Current.EvaluateString (expr, itr, XPathContext);
476                 }
477                                 
478                 public bool EvaluateBoolean (XPathExpression expr)
479                 {
480                         XPathNodeIterator itr = CurrentNodeset;
481                         return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
482                 }
483                 
484                 public double EvaluateNumber (XPathExpression expr)
485                 {
486                         XPathNodeIterator itr = CurrentNodeset;
487                         return itr.Current.EvaluateNumber (expr, itr, XPathContext);
488                 }
489                 
490                 public XPathNodeIterator Select (XPathExpression expr)
491                 {
492                         return CurrentNodeset.Current.Select (expr, XPathContext);
493                 }
494                 
495                 #endregion
496                 
497                 public XslAttributeSet ResolveAttributeSet (QName name)
498                 {
499                         return CompiledStyle.ResolveAttributeSet (name);
500                 }
501                 
502                 #region Variable Stack
503                 Stack variableStack = new Stack ();
504                 object [] currentStack;
505                 public int StackItemCount {
506                         get {
507                                 if (currentStack == null)
508                                       return 0;
509                                 for (int i = 0; i < currentStack.Length; i++)
510                                         if (currentStack [i] == null)
511                                                 return i;
512                                 return currentStack.Length;
513                         }
514                 }
515
516                 public object GetStackItem (int slot)
517                 {
518                         return currentStack [slot];
519                 }
520                 
521                 public void SetStackItem (int slot, object o)
522                 {
523                         currentStack [slot] = o;
524                 }
525                 
526                 public void PushStack (int stackSize)
527                 {
528                         variableStack.Push (currentStack);
529                         currentStack = new object [stackSize];
530                 }
531                 
532                 public void PopStack ()
533                 {
534                         currentStack = (object[])variableStack.Pop();
535                 }
536                 
537                 #endregion
538                 
539                 #region Free/Busy
540                 Hashtable busyTable = new Hashtable ();
541                 static object busyObject = new object ();
542                 
543                 public void SetBusy (object o)
544                 {
545                         busyTable [o] = busyObject;
546                 }
547                 
548                 public void SetFree (object o)
549                 {
550                         busyTable.Remove (o);
551                 }
552                 
553                 public bool IsBusy (object o)
554                 {
555                         return busyTable [o] == busyObject;
556                 }
557                 #endregion
558
559                 public bool PushElementState (string prefix, string name, string ns, bool preserveWhitespace)
560                 {
561                         bool b = IsCData (name, ns);
562                         XPathContext.PushScope ();
563                         Out.InsideCDataSection = XPathContext.IsCData = b;
564                         XPathContext.WhitespaceHandling = true;//preserveWhitespace;
565                         XPathContext.ElementPrefix = prefix;
566                         XPathContext.ElementNamespace = ns;
567                         return b;
568                 }
569
570                 bool IsCData (string name, string ns)
571                 {
572                         for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
573                                 XmlQualifiedName qname = Output.CDataSectionElements [i];
574                                 if (qname.Name == name && qname.Namespace == ns) {
575                                         return true;
576                                 }
577                         }
578                         return false;
579                 }
580
581                 public void PopCDataState (bool isCData)
582                 {
583                         XPathContext.PopScope ();
584                         Out.InsideCDataSection = XPathContext.IsCData;
585                 }
586
587                 public bool PreserveWhitespace ()
588                 {
589 //                      return XPathContext.PreserveWhitespace (CurrentNode);
590                         return XPathContext.Whitespace;
591                 }
592         }
593 }