**** Merged from MCS ****
[mono-project.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslTransformProcessor.cs
blob6108c82d4617ea5cec7b53b76db1246e160f8d5d
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
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.
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;
43 using QName = System.Xml.XmlQualifiedName;
45 namespace Mono.Xml.Xsl {
46 internal class XslTransformProcessor {
47 CompiledStylesheet compiledStyle;
49 XslStylesheet style;
51 Stack currentTemplateStack = new Stack ();
53 XPathNavigator root;
54 XsltArgumentList args;
55 XmlResolver resolver;
56 bool outputStylesheetXmlns;
57 string currentOutputUri;
59 internal readonly XsltCompiledContext XPathContext;
61 // Store the values of global params
62 internal Hashtable globalVariableTable = new Hashtable ();
64 public XslTransformProcessor (CompiledStylesheet style)
66 this.XPathContext = new XsltCompiledContext (this);
67 this.compiledStyle = style;
68 this.style = style.Style;
71 public void Process (XPathNavigator root, Outputter outputtter, XsltArgumentList args, XmlResolver resolver)
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;
79 // This is also done after the transformation, just for reuse after exception.
80 foreach (XslKey key in style.Keys.Values)
81 key.ClearKeyTable ();
83 XPathExpression exp = root.Compile (".");
84 PushNodeset (root.Select (exp, this.XPathContext));
86 foreach (XslGlobalVariable v in CompiledStyle.Variables.Values) {
87 if (args != null && v is XslGlobalParam) {
88 object p = args.GetParam(v.Name.Name, v.Name.Namespace);
89 if (p != null)
90 ((XslGlobalParam)v).Override (this, p);
91 else
92 v.Evaluate (this);
94 v.Evaluate (this);
97 PopNodeset ();
99 this.PushOutput (outputtter);
100 this.ApplyTemplates (root.Select (exp, this.XPathContext), QName.Empty, null);
101 this.PopOutput ();
103 foreach (XslKey key in style.Keys.Values)
104 key.ClearKeyTable ();
107 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
108 public XsltArgumentList Arguments {get{return args;}}
110 public MSXslScriptManager ScriptManager {
111 get { return compiledStyle.ScriptManager; }
114 #region Document Resolution
115 public XmlResolver Resolver {get{return resolver;}}
117 Hashtable docCache;
119 public XPathNavigator GetDocument (Uri uri)
121 XPathNavigator result;
123 if (docCache != null) {
124 result = docCache [uri] as XPathNavigator;
125 if (result != null)
126 return result.Clone();
127 } else {
128 docCache = new Hashtable();
131 XmlReader rdr = null;
132 try {
133 rdr = new XmlTextReader (uri.ToString(), (Stream) resolver.GetEntity (uri, null, null), root.NameTable);
134 XmlValidatingReader xvr = new XmlValidatingReader (rdr);
135 xvr.ValidationType = ValidationType.None;
136 result = new XPathDocument (xvr, XmlSpace.Preserve).CreateNavigator ();
137 } finally {
138 if (rdr != null)
139 rdr.Close ();
141 docCache [uri] = result.Clone ();
142 return result;
145 #endregion
147 #region Output
148 Stack outputStack = new Stack ();
150 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
152 public void PushOutput (Outputter newOutput)
154 this.outputStack.Push (newOutput);
157 public Outputter PopOutput ()
159 Outputter ret = (Outputter)this.outputStack.Pop ();
160 ret.Done ();
161 return ret;
164 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
166 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
168 public string CurrentOutputUri { get { return currentOutputUri; } }
170 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
171 #endregion
173 #region AVT StringBuilder
174 StringBuilder avtSB;
176 #if DEBUG
177 bool avtSBlock = false;
178 #endif
180 public StringBuilder GetAvtStringBuilder ()
182 #if DEBUG
183 if (avtSBlock)
184 throw new XsltException ("String Builder was locked", null);
185 avtSBlock = true;
186 #endif
188 if (avtSB == null)
189 avtSB = new StringBuilder ();
191 return avtSB;
194 public string ReleaseAvtStringBuilder ()
196 #if DEBUG
197 if (!avtSBlock)
198 throw new XsltException ("you never locked the string builder", null);
199 avtSBlock = false;
200 #endif
202 string ret = avtSB.ToString ();
203 avtSB.Length = 0;
204 return ret;
206 #endregion
208 #region Templates -- Apply/Call
209 Stack paramPassingCache = new Stack ();
211 Hashtable GetParams (ArrayList withParams)
213 if (withParams == null) return null;
214 Hashtable ret;
216 if (paramPassingCache.Count != 0) {
217 ret = (Hashtable)paramPassingCache.Pop ();
218 ret.Clear ();
219 } else
220 ret = new Hashtable ();
222 int len = withParams.Count;
223 for (int i = 0; i < len; i++) {
224 XslVariableInformation param = (XslVariableInformation)withParams [i];
225 ret.Add (param.Name, param.Evaluate (this));
227 return ret;
230 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
233 Hashtable passedParams = GetParams (withParams);
235 PushNodeset (nodes);
236 while (NodesetMoveNext ()) {
237 XslTemplate t = FindTemplate (CurrentNode, mode);
238 currentTemplateStack.Push (t);
239 t.Evaluate (this, passedParams);
240 currentTemplateStack.Pop ();
242 PopNodeset ();
244 if (passedParams != null) paramPassingCache.Push (passedParams);
247 public void CallTemplate (QName name, ArrayList withParams)
249 Hashtable passedParams = GetParams (withParams);
251 XslTemplate t = FindTemplate (name);
252 currentTemplateStack.Push (null);
253 t.Evaluate (this, passedParams);
254 currentTemplateStack.Pop ();
256 if (passedParams != null) paramPassingCache.Push (passedParams);
259 public void ApplyImports ()
262 XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
263 if (currentTemplate == null)
264 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
265 XslTemplate t;
267 for (int i = currentTemplate.Parent.Imports.Count - 1; i >= 0; i--) {
268 XslStylesheet s = (XslStylesheet)currentTemplate.Parent.Imports [i];
269 t = s.Templates.FindMatch (CurrentNode, currentTemplate.Mode, this);
270 if (t != null) {
271 currentTemplateStack.Push (t);
272 t.Evaluate (this);
273 currentTemplateStack.Pop ();
274 return;
278 switch (CurrentNode.NodeType) {
279 case XPathNodeType.Root:
280 case XPathNodeType.Element:
281 if (currentTemplate.Mode == QName.Empty)
282 t = XslDefaultNodeTemplate.Instance;
283 else
284 t = new XslDefaultNodeTemplate(currentTemplate.Mode);
286 break;
287 case XPathNodeType.Attribute:
288 case XPathNodeType.SignificantWhitespace:
289 case XPathNodeType.Text:
290 case XPathNodeType.Whitespace:
291 t = XslDefaultTextTemplate.Instance;
292 break;
294 case XPathNodeType.Comment:
295 case XPathNodeType.ProcessingInstruction:
296 t = XslEmptyTemplate.Instance;
297 break;
299 default:
300 t = XslEmptyTemplate.Instance;
301 break;
303 currentTemplateStack.Push (t);
304 t.Evaluate (this);
305 currentTemplateStack.Pop ();
308 internal void TryStylesheetNamespaceOutput (ArrayList excluded)
310 if (outputStylesheetXmlns) {
311 foreach (XmlQualifiedName qname in this.style.StylesheetNamespaces) {
312 string prefix = style.PrefixInEffect (qname.Name, excluded);
313 if (prefix == null)
314 continue;
315 else if (prefix == qname.Name)
316 Out.WriteNamespaceDecl (
317 prefix == "#default" ? String.Empty : prefix,
318 qname.Namespace);
319 else
320 Out.WriteNamespaceDecl (prefix, style.StyleDocument.GetNamespace (prefix));
322 outputStylesheetXmlns = false;
326 XslTemplate FindTemplate (XPathNavigator node, QName mode)
328 XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
330 if (ret != null) return ret;
332 switch (node.NodeType) {
333 case XPathNodeType.Root:
334 case XPathNodeType.Element:
335 if (mode == QName.Empty)
336 return XslDefaultNodeTemplate.Instance;
337 else
338 return new XslDefaultNodeTemplate(mode);
340 case XPathNodeType.Attribute:
341 case XPathNodeType.SignificantWhitespace:
342 case XPathNodeType.Text:
343 case XPathNodeType.Whitespace:
344 return XslDefaultTextTemplate.Instance;
346 case XPathNodeType.Comment:
347 case XPathNodeType.ProcessingInstruction:
348 return XslEmptyTemplate.Instance;
350 default:
351 return XslEmptyTemplate.Instance;
355 XslTemplate FindTemplate (QName name)
357 XslTemplate ret = style.Templates.FindTemplate (name);
358 if (ret != null) return ret;
360 throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
363 #endregion
365 public void PushForEachContext ()
367 currentTemplateStack.Push (null);
370 public void PopForEachContext ()
372 currentTemplateStack.Pop ();
376 #region Nodeset Context
377 ArrayList nodesetStack = new ArrayList ();
379 public XPathNodeIterator CurrentNodeset {
380 get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
383 public XPathNavigator CurrentNode {
384 get {
385 XPathNavigator nav = CurrentNodeset.Current;
386 if (nav != null)
387 return nav;
388 // Inside for-each context, CurrentNodeset.Current may be null
389 for (int i = nodesetStack.Count - 2; i >= 0; i--) {
390 nav = ((XPathNodeIterator) nodesetStack [i]).Current;
391 if (nav != null)
392 return nav;
394 return null;
398 public bool NodesetMoveNext ()
400 return CurrentNodeset.MoveNext ();
403 public void PushNodeset (XPathNodeIterator itr)
405 nodesetStack.Add (itr.Clone ());
408 public void PopNodeset ()
410 nodesetStack.RemoveAt (nodesetStack.Count - 1);
412 #endregion
414 #region Evaluate
416 public bool Matches (Pattern p, XPathNavigator n)
418 return CompiledStyle.ExpressionStore.PatternMatches (p, this, n);
421 public object Evaluate (XPathExpression expr)
423 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
424 expr.SetContext (XPathContext);
426 XPathNodeIterator itr = CurrentNodeset;
427 return itr.Current.Evaluate (expr, itr, XPathContext);
430 public string EvaluateString (XPathExpression expr)
432 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
433 expr.SetContext (XPathContext);
435 XPathNodeIterator itr = CurrentNodeset;
436 return itr.Current.EvaluateString (expr, itr, XPathContext);
439 public bool EvaluateBoolean (XPathExpression expr)
441 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
442 expr.SetContext (XPathContext);
444 XPathNodeIterator itr = CurrentNodeset;
445 return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
448 public double EvaluateNumber (XPathExpression expr)
450 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
451 expr.SetContext (XPathContext);
453 XPathNodeIterator itr = CurrentNodeset;
454 return itr.Current.EvaluateNumber (expr, itr, XPathContext);
457 public XPathNodeIterator Select (XPathExpression expr)
459 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
460 expr.SetContext (XPathContext);
461 return CurrentNodeset.Current.Select (expr, XPathContext);
464 #endregion
466 public XslAttributeSet ResolveAttributeSet (QName name)
468 return CompiledStyle.ResolveAttributeSet (name);
471 #region Variable Stack
472 Stack variableStack = new Stack ();
473 object [] currentStack;
474 public int StackItemCount {
475 get {
476 if (currentStack == null)
477 return 0;
478 for (int i = 0; i < currentStack.Length; i++)
479 if (currentStack [i] == null)
480 return i;
481 return currentStack.Length;
485 public object GetStackItem (int slot)
487 return currentStack [slot];
490 public void SetStackItem (int slot, object o)
492 currentStack [slot] = o;
495 public void PushStack (int stackSize)
497 variableStack.Push (currentStack);
498 currentStack = new object [stackSize];
501 public void PopStack ()
503 currentStack = (object[])variableStack.Pop();
506 #endregion
508 #region Free/Busy
509 Hashtable busyTable = new Hashtable ();
510 static object busyObject = new object ();
512 public void SetBusy (object o)
514 busyTable [o] = busyObject;
517 public void SetFree (object o)
519 busyTable.Remove (o);
522 public bool IsBusy (object o)
524 return busyTable [o] == busyObject;
526 #endregion
528 public bool PushElementState (string name, string ns, bool preserveWhitespace)
530 bool b = IsCData (name, ns);
531 XPathContext.PushScope ();
532 Out.InsideCDataSection = XPathContext.IsCData = b;
533 XPathContext.WhitespaceHandling = preserveWhitespace;
534 return b;
537 bool IsCData (string name, string ns)
539 for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
540 XmlQualifiedName qname = Output.CDataSectionElements [i];
541 if (qname.Name == name && qname.Namespace == ns) {
542 return true;
545 return false;
548 public void PopCDataState (bool isCData)
550 XPathContext.PopScope ();
551 Out.InsideCDataSection = XPathContext.IsCData;
554 public bool PreserveWhitespace ()
556 return XPathContext.PreserveWhitespace (CurrentNode);