2 // XslTransformProcessor.cs
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
35 using System.Collections;
38 using System.Xml.XPath;
40 using Mono.Xml.Xsl.Operations;
43 using QName = System.Xml.XmlQualifiedName;
45 namespace Mono.Xml.Xsl {
46 internal class XslTransformProcessor {
47 CompiledStylesheet compiledStyle;
51 Stack currentTemplateStack = new Stack ();
54 XsltArgumentList args;
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)
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)
83 XPathExpression exp = root.Compile (".");
84 PushNodeset (root.Select (exp, this.XPathContext));
86 // have to evaluate the params first, as Global vars may
87 // be dependant on them
90 foreach (XslGlobalVariable v in CompiledStyle.Variables.Values)
92 if (v is XslGlobalParam)
94 object p = args.GetParam(v.Name.Name, v.Name.Namespace);
96 ((XslGlobalParam)v).Override (this, p);
103 foreach (XslGlobalVariable v in CompiledStyle.Variables.Values) {
104 if (args == null || !(v is XslGlobalParam)) {
111 this.PushOutput (outputtter);
112 this.ApplyTemplates (root.Select (exp, this.XPathContext), QName.Empty, null);
115 foreach (XslKey key in style.Keys.Values)
116 key.ClearKeyTable ();
119 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
120 public XsltArgumentList Arguments {get{return args;}}
122 public MSXslScriptManager ScriptManager {
123 get { return compiledStyle.ScriptManager; }
126 #region Document Resolution
127 public XmlResolver Resolver {get{return resolver;}}
131 public XPathNavigator GetDocument (Uri uri)
133 XPathNavigator result;
135 if (docCache != null) {
136 result = docCache [uri] as XPathNavigator;
138 return result.Clone();
140 docCache = new Hashtable();
143 XmlReader rdr = null;
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 ();
153 docCache [uri] = result.Clone ();
160 Stack outputStack = new Stack ();
162 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
164 public void PushOutput (Outputter newOutput)
166 this.outputStack.Push (newOutput);
169 public Outputter PopOutput ()
171 Outputter ret = (Outputter)this.outputStack.Pop ();
176 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
178 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
180 public string CurrentOutputUri { get { return currentOutputUri; } }
182 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
185 #region AVT StringBuilder
189 bool avtSBlock = false;
192 public StringBuilder GetAvtStringBuilder ()
196 throw new XsltException ("String Builder was locked", null);
201 avtSB = new StringBuilder ();
206 public string ReleaseAvtStringBuilder ()
210 throw new XsltException ("you never locked the string builder", null);
214 string ret = avtSB.ToString ();
220 #region Templates -- Apply/Call
221 Stack paramPassingCache = new Stack ();
223 Hashtable GetParams (ArrayList withParams)
225 if (withParams == null) return null;
228 if (paramPassingCache.Count != 0) {
229 ret = (Hashtable)paramPassingCache.Pop ();
232 ret = new Hashtable ();
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));
242 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
245 Hashtable passedParams = GetParams (withParams);
248 while (NodesetMoveNext ()) {
249 XslTemplate t = FindTemplate (CurrentNode, mode);
250 currentTemplateStack.Push (t);
251 t.Evaluate (this, passedParams);
252 currentTemplateStack.Pop ();
256 if (passedParams != null) paramPassingCache.Push (passedParams);
259 public void CallTemplate (QName name, ArrayList withParams)
261 Hashtable passedParams = GetParams (withParams);
263 XslTemplate t = FindTemplate (name);
264 currentTemplateStack.Push (null);
265 t.Evaluate (this, passedParams);
266 currentTemplateStack.Pop ();
268 if (passedParams != null) paramPassingCache.Push (passedParams);
271 public void ApplyImports ()
274 XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
275 if (currentTemplate == null)
276 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
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);
283 currentTemplateStack.Push (t);
285 currentTemplateStack.Pop ();
290 switch (CurrentNode.NodeType) {
291 case XPathNodeType.Root:
292 case XPathNodeType.Element:
293 if (currentTemplate.Mode == QName.Empty)
294 t = XslDefaultNodeTemplate.Instance;
296 t = new XslDefaultNodeTemplate(currentTemplate.Mode);
299 case XPathNodeType.Attribute:
300 case XPathNodeType.SignificantWhitespace:
301 case XPathNodeType.Text:
302 case XPathNodeType.Whitespace:
303 t = XslDefaultTextTemplate.Instance;
306 case XPathNodeType.Comment:
307 case XPathNodeType.ProcessingInstruction:
308 t = XslEmptyTemplate.Instance;
312 t = XslEmptyTemplate.Instance;
315 currentTemplateStack.Push (t);
317 currentTemplateStack.Pop ();
320 internal void TryElementNamespacesOutput (Hashtable nsDecls, ArrayList excludedPrefixes)
322 TryElementNamespacesOutput (nsDecls, excludedPrefixes, null);
325 internal void TryElementNamespacesOutput (Hashtable nsDecls, ArrayList excludedPrefixes, string localPrefixInCopy)
330 foreach (DictionaryEntry cur in nsDecls) {
331 string name = (string)cur.Key;
332 string value = (string)cur.Value;
334 // See XSLT 1.0 errata E25
335 if (localPrefixInCopy == name)
337 if (localPrefixInCopy != null &&
339 XPathContext.ElementNamespace.Length == 0)
342 // exclude-result-prefixes, see the spec 7.1.1
344 if (style.ExcludeResultPrefixes != null) {
345 foreach (XmlQualifiedName exc in style.ExcludeResultPrefixes) {
346 if (exc.Namespace == value) {
355 if (style.NamespaceAliases [name] != null)
358 switch (value) {//FIXME: compare names by reference
359 case "http://www.w3.org/1999/XSL/Transform":
360 // if ("xsl" == name)
364 case XmlNamespaceManager.XmlnsXml:
365 if (XmlNamespaceManager.PrefixXml == name)
369 case XmlNamespaceManager.XmlnsXmlns:
370 if (XmlNamespaceManager.PrefixXmlns == name)
375 if (excludedPrefixes == null || !excludedPrefixes.Contains (name))
376 Out.WriteNamespaceDecl (name, value);
382 XslTemplate FindTemplate (XPathNavigator node, QName mode)
384 XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
386 if (ret != null) return ret;
388 switch (node.NodeType) {
389 case XPathNodeType.Root:
390 case XPathNodeType.Element:
391 if (mode == QName.Empty)
392 return XslDefaultNodeTemplate.Instance;
394 return new XslDefaultNodeTemplate(mode);
396 case XPathNodeType.Attribute:
397 case XPathNodeType.SignificantWhitespace:
398 case XPathNodeType.Text:
399 case XPathNodeType.Whitespace:
400 return XslDefaultTextTemplate.Instance;
402 case XPathNodeType.Comment:
403 case XPathNodeType.ProcessingInstruction:
404 return XslEmptyTemplate.Instance;
407 return XslEmptyTemplate.Instance;
411 XslTemplate FindTemplate (QName name)
413 XslTemplate ret = style.Templates.FindTemplate (name);
414 if (ret != null) return ret;
416 throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
421 public void PushForEachContext ()
423 currentTemplateStack.Push (null);
426 public void PopForEachContext ()
428 currentTemplateStack.Pop ();
432 #region Nodeset Context
433 ArrayList nodesetStack = new ArrayList ();
435 public XPathNodeIterator CurrentNodeset {
436 get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
439 public XPathNavigator CurrentNode {
441 XPathNavigator nav = CurrentNodeset.Current;
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;
454 public bool NodesetMoveNext ()
456 return CurrentNodeset.MoveNext ();
459 public void PushNodeset (XPathNodeIterator itr)
461 nodesetStack.Add (itr.Clone ());
464 public void PopNodeset ()
466 nodesetStack.RemoveAt (nodesetStack.Count - 1);
472 public bool Matches (Pattern p, XPathNavigator n)
474 return CompiledStyle.ExpressionStore.PatternMatches (p, this, n);
477 public object Evaluate (XPathExpression expr)
479 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
480 expr.SetContext (XPathContext);
482 XPathNodeIterator itr = CurrentNodeset;
483 return itr.Current.Evaluate (expr, itr, XPathContext);
486 public string EvaluateString (XPathExpression expr)
488 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
489 expr.SetContext (XPathContext);
491 XPathNodeIterator itr = CurrentNodeset;
492 return itr.Current.EvaluateString (expr, itr, XPathContext);
495 public bool EvaluateBoolean (XPathExpression expr)
497 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
498 expr.SetContext (XPathContext);
500 XPathNodeIterator itr = CurrentNodeset;
501 return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
504 public double EvaluateNumber (XPathExpression expr)
506 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
507 expr.SetContext (XPathContext);
509 XPathNodeIterator itr = CurrentNodeset;
510 return itr.Current.EvaluateNumber (expr, itr, XPathContext);
513 public XPathNodeIterator Select (XPathExpression expr)
515 expr = CompiledStyle.ExpressionStore.PrepForExecution (expr, this);
516 expr.SetContext (XPathContext);
517 return CurrentNodeset.Current.Select (expr, XPathContext);
522 public XslAttributeSet ResolveAttributeSet (QName name)
524 return CompiledStyle.ResolveAttributeSet (name);
527 #region Variable Stack
528 Stack variableStack = new Stack ();
529 object [] currentStack;
530 public int StackItemCount {
532 if (currentStack == null)
534 for (int i = 0; i < currentStack.Length; i++)
535 if (currentStack [i] == null)
537 return currentStack.Length;
541 public object GetStackItem (int slot)
543 return currentStack [slot];
546 public void SetStackItem (int slot, object o)
548 currentStack [slot] = o;
551 public void PushStack (int stackSize)
553 variableStack.Push (currentStack);
554 currentStack = new object [stackSize];
557 public void PopStack ()
559 currentStack = (object[])variableStack.Pop();
565 Hashtable busyTable = new Hashtable ();
566 static object busyObject = new object ();
568 public void SetBusy (object o)
570 busyTable [o] = busyObject;
573 public void SetFree (object o)
575 busyTable.Remove (o);
578 public bool IsBusy (object o)
580 return busyTable [o] == busyObject;
584 public bool PushElementState (string prefix, string name, string ns, bool preserveWhitespace)
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;
595 bool IsCData (string name, string ns)
597 for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
598 XmlQualifiedName qname = Output.CDataSectionElements [i];
599 if (qname.Name == name && qname.Namespace == ns) {
606 public void PopCDataState (bool isCData)
608 XPathContext.PopScope ();
609 Out.InsideCDataSection = XPathContext.IsCData;
612 public bool PreserveWhitespace ()
614 return XPathContext.PreserveWhitespace (CurrentNode);