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 XsltDebuggerWrapper debugger;
49 CompiledStylesheet compiledStyle;
53 Stack currentTemplateStack = new Stack ();
56 XsltArgumentList args;
58 // bool outputStylesheetXmlns;
59 string currentOutputUri;
61 internal readonly XsltCompiledContext XPathContext;
63 // Store the values of global params
64 internal Hashtable globalVariableTable = new Hashtable ();
66 public XslTransformProcessor (CompiledStylesheet style, object debugger)
68 this.XPathContext = new XsltCompiledContext (this);
69 this.compiledStyle = style;
70 this.style = style.Style;
72 this.debugger = new XsltDebuggerWrapper (debugger);
75 public void Process (XPathNavigator root, Outputter outputtter, XsltArgumentList args, XmlResolver resolver)
79 this.resolver = resolver != null ? resolver : new XmlUrlResolver ();
80 // this.outputStylesheetXmlns = true;
81 this.currentOutputUri = String.Empty;
83 PushNodeset (new SelfIterator (root, this.XPathContext));
84 CurrentNodeset.MoveNext ();
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 (new SelfIterator (root, this.XPathContext), QName.Empty, null);
116 public XsltDebuggerWrapper Debugger {
117 get { return debugger; }
120 public CompiledStylesheet CompiledStyle { get { return compiledStyle; }}
121 public XsltArgumentList Arguments {get{return args;}}
123 public XPathNavigator Root { get { return root; } }
125 public MSXslScriptManager ScriptManager {
126 get { return compiledStyle.ScriptManager; }
129 #region Document Resolution
130 public XmlResolver Resolver {get{return resolver;}}
134 public XPathNavigator GetDocument (Uri uri)
136 XPathNavigator result;
138 if (docCache != null) {
139 result = docCache [uri] as XPathNavigator;
141 return result.Clone();
143 docCache = new Hashtable();
146 XmlReader rdr = null;
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 ();
156 docCache [uri] = result.Clone ();
163 Stack outputStack = new Stack ();
165 public Outputter Out { get { return (Outputter)outputStack.Peek(); }}
167 public void PushOutput (Outputter newOutput)
169 this.outputStack.Push (newOutput);
172 public Outputter PopOutput ()
174 Outputter ret = (Outputter)this.outputStack.Pop ();
179 public Hashtable Outputs { get { return compiledStyle.Outputs; }}
181 public XslOutput Output { get { return Outputs [currentOutputUri] as XslOutput; } }
183 public string CurrentOutputUri { get { return currentOutputUri; } }
185 public bool InsideCDataElement { get { return this.XPathContext.IsCData; } }
188 #region AVT StringBuilder
192 bool avtSBlock = false;
195 public StringBuilder GetAvtStringBuilder ()
199 throw new XsltException ("String Builder was locked", null);
204 avtSB = new StringBuilder ();
209 public string ReleaseAvtStringBuilder ()
213 throw new XsltException ("you never locked the string builder", null);
217 string ret = avtSB.ToString ();
223 #region Templates -- Apply/Call
224 Stack paramPassingCache = new Stack ();
226 Hashtable GetParams (ArrayList withParams)
228 if (withParams == null) return null;
231 if (paramPassingCache.Count != 0) {
232 ret = (Hashtable)paramPassingCache.Pop ();
235 ret = new Hashtable ();
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));
245 public void ApplyTemplates (XPathNodeIterator nodes, QName mode, ArrayList withParams)
248 Hashtable passedParams = GetParams (withParams);
250 while (NodesetMoveNext (nodes)) {
252 XslTemplate t = FindTemplate (CurrentNode, mode);
253 currentTemplateStack.Push (t);
254 t.Evaluate (this, passedParams);
255 currentTemplateStack.Pop ();
259 if (passedParams != null) paramPassingCache.Push (passedParams);
262 public void CallTemplate (QName name, ArrayList withParams)
264 Hashtable passedParams = GetParams (withParams);
266 XslTemplate t = FindTemplate (name);
267 currentTemplateStack.Push (null);
268 t.Evaluate (this, passedParams);
269 currentTemplateStack.Pop ();
271 if (passedParams != null) paramPassingCache.Push (passedParams);
274 public void ApplyImports ()
277 XslTemplate currentTemplate = (XslTemplate)currentTemplateStack.Peek();
278 if (currentTemplate == null)
279 throw new XsltException ("Invalid context for apply-imports", null, CurrentNode);
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);
286 currentTemplateStack.Push (t);
288 currentTemplateStack.Pop ();
293 switch (CurrentNode.NodeType) {
294 case XPathNodeType.Root:
295 case XPathNodeType.Element:
296 if (currentTemplate.Mode == QName.Empty)
297 t = XslDefaultNodeTemplate.Instance;
299 t = new XslDefaultNodeTemplate(currentTemplate.Mode);
302 case XPathNodeType.Attribute:
303 case XPathNodeType.SignificantWhitespace:
304 case XPathNodeType.Text:
305 case XPathNodeType.Whitespace:
306 t = XslDefaultTextTemplate.Instance;
309 case XPathNodeType.Comment:
310 case XPathNodeType.ProcessingInstruction:
311 t = XslEmptyTemplate.Instance;
315 t = XslEmptyTemplate.Instance;
318 currentTemplateStack.Push (t);
320 currentTemplateStack.Pop ();
323 // Outputs Literal namespace nodes described in spec 7.7.1
324 internal void OutputLiteralNamespaceUriNodes (Hashtable nsDecls, ArrayList excludedPrefixes, string localPrefixInCopy)
329 foreach (DictionaryEntry cur in nsDecls) {
330 string name = (string)cur.Key;
331 string value = (string)cur.Value;
333 // See XSLT 1.0 errata E25
334 if (localPrefixInCopy == name)
336 if (localPrefixInCopy != null &&
338 XPathContext.ElementNamespace.Length == 0)
341 // exclude-result-prefixes, see the spec 7.1.1
343 if (style.ExcludeResultPrefixes != null) {
344 foreach (XmlQualifiedName exc in style.ExcludeResultPrefixes) {
345 if (exc.Namespace == value) {
354 if (style.NamespaceAliases [name] != null)
357 switch (value) {//FIXME: compare names by reference
358 case "http://www.w3.org/1999/XSL/Transform":
359 // if ("xsl" == name)
363 case XmlNamespaceManager.XmlnsXml:
364 if (XmlNamespaceManager.PrefixXml == name)
368 case XmlNamespaceManager.XmlnsXmlns:
369 if (XmlNamespaceManager.PrefixXmlns == name)
374 if (excludedPrefixes == null || !excludedPrefixes.Contains (name))
375 Out.WriteNamespaceDecl (name, value);
381 XslTemplate FindTemplate (XPathNavigator node, QName mode)
383 XslTemplate ret = style.Templates.FindMatch (CurrentNode, mode, this);
385 if (ret != null) return ret;
387 switch (node.NodeType) {
388 case XPathNodeType.Root:
389 case XPathNodeType.Element:
390 if (mode == QName.Empty)
391 return XslDefaultNodeTemplate.Instance;
393 return new XslDefaultNodeTemplate(mode);
395 case XPathNodeType.Attribute:
396 case XPathNodeType.SignificantWhitespace:
397 case XPathNodeType.Text:
398 case XPathNodeType.Whitespace:
399 return XslDefaultTextTemplate.Instance;
401 case XPathNodeType.Comment:
402 case XPathNodeType.ProcessingInstruction:
403 return XslEmptyTemplate.Instance;
406 return XslEmptyTemplate.Instance;
410 XslTemplate FindTemplate (QName name)
412 XslTemplate ret = style.Templates.FindTemplate (name);
413 if (ret != null) return ret;
415 throw new XsltException ("Could not resolve named template " + name, null, CurrentNode);
420 public void PushForEachContext ()
422 currentTemplateStack.Push (null);
425 public void PopForEachContext ()
427 currentTemplateStack.Pop ();
431 #region Nodeset Context
432 ArrayList nodesetStack = new ArrayList ();
434 public XPathNodeIterator CurrentNodeset {
435 get { return (XPathNodeIterator) nodesetStack [nodesetStack.Count - 1]; }
438 public XPathNavigator CurrentNode {
440 XPathNavigator nav = CurrentNodeset.Current;
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;
453 public bool NodesetMoveNext ()
455 return NodesetMoveNext (CurrentNodeset);
458 public bool NodesetMoveNext (XPathNodeIterator iter)
460 if (!iter.MoveNext ())
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);
476 public void PushNodeset (XPathNodeIterator itr)
478 BaseIterator bi = itr as BaseIterator;
479 bi = bi != null ? bi : new WrapperIterator (itr, null);
480 bi.NamespaceManager = XPathContext;
481 nodesetStack.Add (bi);
484 public void PopNodeset ()
486 nodesetStack.RemoveAt (nodesetStack.Count - 1);
492 public bool Matches (Pattern p, XPathNavigator n)
494 return p.Matches (n, this.XPathContext);
497 public object Evaluate (XPathExpression expr)
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);
507 public string EvaluateString (XPathExpression expr)
509 XPathNodeIterator itr = CurrentNodeset;
511 return itr.Current.EvaluateString (expr, itr, XPathContext);
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);
521 public bool EvaluateBoolean (XPathExpression expr)
523 XPathNodeIterator itr = CurrentNodeset;
525 return itr.Current.EvaluateBoolean (expr, itr, XPathContext);
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);
535 public double EvaluateNumber (XPathExpression expr)
537 XPathNodeIterator itr = CurrentNodeset;
539 return itr.Current.EvaluateNumber (expr, itr, XPathContext);
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);
549 public XPathNodeIterator Select (XPathExpression expr)
552 return CurrentNodeset.Current.Select (expr, XPathContext);
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);
564 public XslAttributeSet ResolveAttributeSet (QName name)
566 return CompiledStyle.ResolveAttributeSet (name);
569 #region Variable Stack
570 Stack variableStack = new Stack ();
571 object [] currentStack;
572 public int StackItemCount {
574 if (currentStack == null)
576 for (int i = 0; i < currentStack.Length; i++)
577 if (currentStack [i] == null)
579 return currentStack.Length;
583 public object GetStackItem (int slot)
585 return currentStack [slot];
588 public void SetStackItem (int slot, object o)
590 currentStack [slot] = o;
593 public void PushStack (int stackSize)
595 variableStack.Push (currentStack);
596 currentStack = new object [stackSize];
599 public void PopStack ()
601 currentStack = (object[])variableStack.Pop();
607 Hashtable busyTable = new Hashtable ();
608 static object busyObject = new object ();
610 public void SetBusy (object o)
612 busyTable [o] = busyObject;
615 public void SetFree (object o)
617 busyTable.Remove (o);
620 public bool IsBusy (object o)
622 return busyTable [o] == busyObject;
626 public bool PushElementState (string prefix, string name, string ns, bool preserveWhitespace)
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;
637 bool IsCData (string name, string ns)
639 for (int i = 0; i < Output.CDataSectionElements.Length; i++) {
640 XmlQualifiedName qname = Output.CDataSectionElements [i];
641 if (qname.Name == name && qname.Namespace == ns) {
648 public void PopCDataState (bool isCData)
650 XPathContext.PopScope ();
651 Out.InsideCDataSection = XPathContext.IsCData;
654 public bool PreserveOutputWhitespace {
655 get { return XPathContext.Whitespace; }