Ignore SynchronizationAttribute on MT
[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                 XsltDebuggerWrapper debugger;
48                 
49                 CompiledStylesheet compiledStyle;
50                 
51                 XslStylesheet style;
52                 
53                 Stack currentTemplateStack = new Stack ();
54                 
55                 XPathNavigator root;
56                 XsltArgumentList args;
57                 XmlResolver resolver;
58 //              bool outputStylesheetXmlns;
59                 string currentOutputUri;
60                 
61                 internal readonly XsltCompiledContext XPathContext;
62
63                 // Store the values of global params
64                 internal Hashtable globalVariableTable = new Hashtable ();
65                 
66                 public XslTransformProcessor (CompiledStylesheet style, object debugger)
67                 {
68                         this.XPathContext = new XsltCompiledContext (this);
69                         this.compiledStyle = style;
70                         this.style = style.Style;
71                         if (debugger != null)
72                                 this.debugger = new XsltDebuggerWrapper (debugger);
73                 }
74
75                 public void Process (XPathNavigator root, Outputter outputtter, XsltArgumentList args, XmlResolver resolver)
76                 {
77                         this.args = args;
78                         this.root = root;
79                         this.resolver = resolver != null ? resolver : new XmlUrlResolver ();
80 //                      this.outputStylesheetXmlns = true;
81                         this.currentOutputUri = String.Empty;
82
83                         PushNodeset (new SelfIterator (root, this.XPathContext));
84 CurrentNodeset.MoveNext ();
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 (new SelfIterator (root, this.XPathContext), QName.Empty, null);
113                         this.PopOutput ();
114                 }
115
116                 public XsltDebuggerWrapper Debugger {
117                         get { return debugger; }
118                 }
119
120                 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
121                 public XsltArgumentList Arguments {get{return args;}}
122
123                 public XPathNavigator Root { get { return root; } }
124
125                 public MSXslScriptManager ScriptManager {
126                         get { return compiledStyle.ScriptManager; }
127                 }
128
129                 #region Document Resolution
130                 public XmlResolver Resolver {get{return resolver;}}
131                 
132                 Hashtable docCache;
133                 
134                 public XPathNavigator GetDocument (Uri uri)
135                 {
136                         XPathNavigator result;
137                         
138                         if (docCache != null) {
139                                 result = docCache [uri] as XPathNavigator;
140                                 if (result != null)
141                                         return result.Clone();
142                         } else {
143                                 docCache = new Hashtable();
144                         }
145
146                         XmlReader rdr = null;
147                         try {
148                                 rdr = new XmlTextReader (uri.ToString(), (Stream) resolver.GetEntity (uri, null, null), root.NameTable);
149                                 XmlValidatingReader xvr = new XmlValidatingReader (rdr);
150                                 xvr.ValidationType = ValidationType.None;
151                                 result = new XPathDocument (xvr, XmlSpace.Preserve).CreateNavigator ();
152                         } finally {
153                                 if (rdr != null)
154                                         rdr.Close ();
155                         }
156                         docCache [uri] = result.Clone ();
157                         return result;
158                 }
159                 
160                 #endregion
161                 
162                 #region Output
163                 Stack outputStack = new Stack ();
164                 
165                 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
166                 
167                 public void PushOutput (Outputter newOutput)
168                 {
169                         this.outputStack.Push (newOutput);
170                 }
171                 
172                 public Outputter PopOutput ()
173                 {
174                         Outputter ret = (Outputter)this.outputStack.Pop ();
175                         ret.Done ();
176                         return ret;
177                 }
178                 
179                 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
180
181                 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
182
183                 public string CurrentOutputUri { get { return currentOutputUri; } }
184
185                 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
186                 #endregion
187                 
188                 #region AVT StringBuilder
189                 StringBuilder avtSB;
190         
191                 #if DEBUG
192                 bool avtSBlock = false;
193                 #endif
194         
195                 public StringBuilder GetAvtStringBuilder ()
196                 {
197                         #if DEBUG
198                                 if (avtSBlock)
199                                         throw new XsltException ("String Builder was locked", null);
200                                 avtSBlock = true;
201                         #endif
202                         
203                         if (avtSB == null)
204                                 avtSB = new StringBuilder ();
205                         
206                         return avtSB;
207                 }
208                 
209                 public string ReleaseAvtStringBuilder ()
210                 {
211                         #if DEBUG
212                                 if (!avtSBlock)
213                                         throw new XsltException ("you never locked the string builder", null);
214                                 avtSBlock = false;
215                         #endif
216                         
217                         string ret = avtSB.ToString ();
218                         avtSB.Length = 0;
219                         return ret;
220                 }
221                 #endregion
222                 
223                 #region Templates -- Apply/Call
224                 Stack paramPassingCache = new Stack ();
225                 
226                 Hashtable GetParams (ArrayList withParams)
227                 {
228                         if (withParams == null) return null;
229                         Hashtable ret;
230                         
231                         if (paramPassingCache.Count != 0) {
232                                 ret = (Hashtable)paramPassingCache.Pop ();
233                                 ret.Clear ();
234                         } else
235                                 ret = new Hashtable ();
236                         
237                         int len = withParams.Count;
238                         for (int i = 0; i < len; i++) {
239                                 XslVariableInformation param = (XslVariableInformation)withParams [i];
240                                 ret.Add (param.Name, param.Evaluate (this));
241                         }
242                         return ret;
243                 }
244                 
245                 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
246                 {
247
248                         Hashtable passedParams = GetParams (withParams);
249                         
250                         while (NodesetMoveNext (nodes)) {
251                                 PushNodeset (nodes);
252                                 XslTemplate t = FindTemplate (CurrentNode, mode);
253                                 currentTemplateStack.Push (t);
254                                 t.Evaluate (this, passedParams);
255                                 currentTemplateStack.Pop ();
256                                 PopNodeset ();
257                         }
258                         
259                         if (passedParams != null) paramPassingCache.Push (passedParams);
260                 }
261                 
262                 public void CallTemplate (QName name, ArrayList withParams)
263                 {
264                         Hashtable passedParams = GetParams (withParams);
265                         
266                         XslTemplate t = FindTemplate (name);
267                         currentTemplateStack.Push (null);
268                         t.Evaluate (this, passedParams);
269                         currentTemplateStack.Pop ();
270                         
271                         if (passedParams != null) paramPassingCache.Push (passedParams);
272                 }
273                 
274                 public void ApplyImports ()
275                 {       
276
277                         XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
278                         if (currentTemplate == null)
279                                 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
280                         XslTemplate t;
281                         
282                         for (int i = currentTemplate.Parent.Imports.Count - 1; i >= 0; i--) {
283                                 XslStylesheet s = (XslStylesheet)currentTemplate.Parent.Imports [i];
284                                 t = s.Templates.FindMatch (CurrentNode, currentTemplate.Mode, this);
285                                 if (t != null) {                                        
286                                         currentTemplateStack.Push (t);
287                                         t.Evaluate (this);
288                                         currentTemplateStack.Pop ();
289                                         return;
290                                 }
291                         }
292                         
293                         switch (CurrentNode.NodeType) {
294                         case XPathNodeType.Root:
295                         case XPathNodeType.Element:
296                                 if (currentTemplate.Mode == QName.Empty)
297                                         t = XslDefaultNodeTemplate.Instance;
298                                 else
299                                         t = new XslDefaultNodeTemplate(currentTemplate.Mode);
300                         
301                                 break;
302                         case XPathNodeType.Attribute:
303                         case XPathNodeType.SignificantWhitespace:
304                         case XPathNodeType.Text:
305                         case XPathNodeType.Whitespace:
306                                 t = XslDefaultTextTemplate.Instance;
307                                 break;
308                         
309                         case XPathNodeType.Comment:
310                         case XPathNodeType.ProcessingInstruction:
311                                 t = XslEmptyTemplate.Instance;
312                                 break;
313                         
314                         default:
315                                 t = XslEmptyTemplate.Instance;
316                                 break;
317                         }
318                         currentTemplateStack.Push (t);
319                         t.Evaluate (this);
320                         currentTemplateStack.Pop ();
321                 }
322
323                 // Outputs Literal namespace nodes described in spec 7.7.1
324                 internal void OutputLiteralNamespaceUriNodes (Hashtable nsDecls, ArrayList excludedPrefixes, string localPrefixInCopy)
325                 {
326                         if (nsDecls == null)
327                                 return;
328
329                         foreach (DictionaryEntry cur in nsDecls) {
330                                 string name = (string)cur.Key;
331                                 string value = (string)cur.Value;
332
333                                 // See XSLT 1.0 errata E25
334                                 if (localPrefixInCopy == name)
335                                         continue;
336                                 if (localPrefixInCopy != null &&
337                                         name.Length == 0 &&
338                                         XPathContext.ElementNamespace.Length == 0)
339                                         continue;
340
341                                 // exclude-result-prefixes, see the spec 7.1.1
342                                 bool skip = false;
343                                 if (style.ExcludeResultPrefixes != null) {
344                                         foreach (XmlQualifiedName exc in style.ExcludeResultPrefixes) {
345                                                 if (exc.Namespace == value) {
346                                                         skip = true;
347                                                         continue;
348                                                 }
349                                         }
350                                 }
351                                 if (skip)
352                                         continue;
353
354                                 if (style.NamespaceAliases [name] != null)
355                                         continue;
356
357                                 switch (value) {//FIXME: compare names by reference
358                                 case "http://www.w3.org/1999/XSL/Transform":
359 //                                      if ("xsl" == name)
360                                                 continue;
361 //                                      else
362 //                                              goto default;
363                                 case XmlNamespaceManager.XmlnsXml:
364                                         if (XmlNamespaceManager.PrefixXml == name)
365                                                 continue;
366                                         else
367                                                 goto default;
368                                 case XmlNamespaceManager.XmlnsXmlns:
369                                         if (XmlNamespaceManager.PrefixXmlns == name)
370                                                 continue;
371                                         else
372                                                 goto default;
373                                 default:
374                                         if (excludedPrefixes == null || !excludedPrefixes.Contains (name))
375                                                 Out.WriteNamespaceDecl (name, value);
376                                         break;
377                                 }
378                         }
379                 }
380
381                 XslTemplate FindTemplate (XPathNavigator node, QName mode)
382                 {
383                         XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
384                         
385                         if (ret != null) return ret;
386
387                         switch (node.NodeType) {
388                         case XPathNodeType.Root:
389                         case XPathNodeType.Element:
390                                 if (mode == QName.Empty)
391                                         return XslDefaultNodeTemplate.Instance;
392                                 else
393                                         return new XslDefaultNodeTemplate(mode);
394                         
395                         case XPathNodeType.Attribute:
396                         case XPathNodeType.SignificantWhitespace:
397                         case XPathNodeType.Text:
398                         case XPathNodeType.Whitespace:
399                                 return XslDefaultTextTemplate.Instance;
400                         
401                         case XPathNodeType.Comment:
402                         case XPathNodeType.ProcessingInstruction:
403                                 return XslEmptyTemplate.Instance;
404                         
405                         default:
406                                 return XslEmptyTemplate.Instance;
407                         }
408                 }
409                 
410                 XslTemplate FindTemplate (QName name)
411                 {
412                         XslTemplate ret = style.Templates.FindTemplate (name);
413                         if (ret != null) return ret;
414                                 
415                         throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
416                 }
417                 
418                 #endregion
419
420                 public void PushForEachContext ()
421                 {
422                         currentTemplateStack.Push (null);
423                 }
424                 
425                 public void PopForEachContext ()
426                 {
427                         currentTemplateStack.Pop ();
428                 }
429                 
430
431                 #region Nodeset Context
432                 ArrayList nodesetStack = new ArrayList ();
433                 
434                 public XPathNodeIterator CurrentNodeset {
435                         get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
436                 }
437                 
438                 public XPathNavigator CurrentNode {
439                         get {
440                                 XPathNavigator nav = CurrentNodeset.Current;
441                                 if (nav != null)
442                                         return nav;
443                                 // Inside for-each context, CurrentNodeset.Current may be null
444                                 for (int i = nodesetStack.Count - 2; i >= 0; i--) {
445                                         nav = ((XPathNodeIterator) nodesetStack [i]).Current;
446                                         if (nav != null)
447                                                 return nav;
448                                 }
449                                 return null;
450                         }
451                 }
452                 
453                 public bool NodesetMoveNext ()
454                 {
455                         return NodesetMoveNext (CurrentNodeset);
456                 }
457
458                 public bool NodesetMoveNext (XPathNodeIterator iter)
459                 {
460                         if (!iter.MoveNext ())
461                                 return false;
462                         // FIXME: this check should not be required.
463                         // Since removal of this check causes some regressions,
464                         // there should be some wrong assumption on our
465                         // BaseIterator usage. Actually BaseIterator should
466                         // not do whitespace check and every PreserveWhitespace
467                         // evaluation in XslTransform should be done at
468                         // different level. One possible solution is to wrap
469                         // the input XmlReader by a new XmlReader that takes
470                         // whitespace stripping into consideration.
471                         if (iter.Current.NodeType == XPathNodeType.Whitespace && !XPathContext.PreserveWhitespace (iter.Current))
472                                 return NodesetMoveNext (iter);
473                         return true;
474                 }
475
476                 public void PushNodeset (XPathNodeIterator itr)
477                 {
478                         BaseIterator bi = itr as BaseIterator;
479                         bi = bi != null ? bi : new WrapperIterator (itr, null);
480                         bi.NamespaceManager = XPathContext;
481                         nodesetStack.Add (bi);
482                 }
483                 
484                 public void PopNodeset ()
485                 {
486                         nodesetStack.RemoveAt (nodesetStack.Count - 1);
487                 }
488                 #endregion
489                 
490                 #region Evaluate
491                 
492                 public bool Matches (Pattern p, XPathNavigator n)
493                 {
494                         return p.Matches (n, this.XPathContext);
495                 }
496                 
497                 public object Evaluate (XPathExpression expr)
498                 {
499                         XPathNodeIterator itr = CurrentNodeset;
500                         BaseIterator bi = (BaseIterator) itr;
501                         CompiledExpression cexpr = (CompiledExpression) expr;
502                         if (bi.NamespaceManager == null)
503                                 bi.NamespaceManager = cexpr.NamespaceManager;
504                         return cexpr.Evaluate (bi);
505                 }
506                 
507                 public string EvaluateString (XPathExpression expr)
508                 {
509                         XPathNodeIterator itr = CurrentNodeset;
510 #if true
511                         return itr.Current.EvaluateString (expr, itr, XPathContext);
512 #else
513                         BaseIterator bi = (BaseIterator) itr;
514                         CompiledExpression cexpr = (CompiledExpression) expr;
515                         if (bi.NamespaceManager == null)
516                                 bi.NamespaceManager = cexpr.NamespaceManager;
517                         return cexpr.EvaluateString (bi);
518 #endif
519                 }
520                                 
521                 public bool EvaluateBoolean (XPathExpression expr)
522                 {
523                         XPathNodeIterator itr = CurrentNodeset;
524 #if true
525                         return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
526 #else
527                         BaseIterator bi = (BaseIterator) itr;
528                         CompiledExpression cexpr = (CompiledExpression) expr;
529                         if (bi.NamespaceManager == null)
530                                 bi.NamespaceManager = cexpr.NamespaceManager;
531                         return cexpr.EvaluateBoolean (bi);
532 #endif
533                 }
534                 
535                 public double EvaluateNumber (XPathExpression expr)
536                 {
537                         XPathNodeIterator itr = CurrentNodeset;
538 #if true
539                         return itr.Current.EvaluateNumber (expr, itr, XPathContext);
540 #else
541                         BaseIterator bi = (BaseIterator) itr;
542                         CompiledExpression cexpr = (CompiledExpression) expr;
543                         if (bi.NamespaceManager == null)
544                                 bi.NamespaceManager = cexpr.NamespaceManager;
545                         return cexpr.EvaluateNumber (bi);
546 #endif
547                 }
548                 
549                 public XPathNodeIterator Select (XPathExpression expr)
550                 {
551 #if true
552                         return CurrentNodeset.Current.Select (expr, XPathContext);
553 #else
554                         BaseIterator bi = (BaseIterator) CurrentNodeset;
555                         CompiledExpression cexpr = (CompiledExpression) expr;
556                         if (bi.NamespaceManager == null)
557                                 bi.NamespaceManager = cexpr.NamespaceManager;
558                         return cexpr.EvaluateNodeSet (bi);
559 #endif
560                 }
561                 
562                 #endregion
563                 
564                 public XslAttributeSet ResolveAttributeSet (QName name)
565                 {
566                         return CompiledStyle.ResolveAttributeSet (name);
567                 }
568                 
569                 #region Variable Stack
570                 Stack variableStack = new Stack ();
571                 object [] currentStack;
572                 public int StackItemCount {
573                         get {
574                                 if (currentStack == null)
575                                       return 0;
576                                 for (int i = 0; i < currentStack.Length; i++)
577                                         if (currentStack [i] == null)
578                                                 return i;
579                                 return currentStack.Length;
580                         }
581                 }
582
583                 public object GetStackItem (int slot)
584                 {
585                         return currentStack [slot];
586                 }
587                 
588                 public void SetStackItem (int slot, object o)
589                 {
590                         currentStack [slot] = o;
591                 }
592                 
593                 public void PushStack (int stackSize)
594                 {
595                         variableStack.Push (currentStack);
596                         currentStack = new object [stackSize];
597                 }
598                 
599                 public void PopStack ()
600                 {
601                         currentStack = (object[])variableStack.Pop();
602                 }
603                 
604                 #endregion
605                 
606                 #region Free/Busy
607                 Hashtable busyTable = new Hashtable ();
608                 static object busyObject = new object ();
609                 
610                 public void SetBusy (object o)
611                 {
612                         busyTable [o] = busyObject;
613                 }
614                 
615                 public void SetFree (object o)
616                 {
617                         busyTable.Remove (o);
618                 }
619                 
620                 public bool IsBusy (object o)
621                 {
622                         return busyTable [o] == busyObject;
623                 }
624                 #endregion
625
626                 public bool PushElementState (string prefix, string name, string ns, bool preserveWhitespace)
627                 {
628                         bool b = IsCData (name, ns);
629                         XPathContext.PushScope ();
630                         Out.InsideCDataSection = XPathContext.IsCData = b;
631                         XPathContext.WhitespaceHandling = true;//preserveWhitespace;
632                         XPathContext.ElementPrefix = prefix;
633                         XPathContext.ElementNamespace = ns;
634                         return b;
635                 }
636
637                 bool IsCData (string name, string ns)
638                 {
639                         for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
640                                 XmlQualifiedName qname = Output.CDataSectionElements [i];
641                                 if (qname.Name == name && qname.Namespace == ns) {
642                                         return true;
643                                 }
644                         }
645                         return false;
646                 }
647
648                 public void PopCDataState (bool isCData)
649                 {
650                         XPathContext.PopScope ();
651                         Out.InsideCDataSection = XPathContext.IsCData;
652                 }
653
654                 public bool PreserveOutputWhitespace {
655                         get { return XPathContext.Whitespace; }
656                 }
657         }
658 }