5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
9 // (C) 2003 Atsushi Enomoto
14 using System.Collections;
15 using System.Collections.Specialized;
16 using System.Security.Policy;
18 using System.Xml.Schema;
19 using System.Xml.XPath;
23 using Mono.Xml.Xsl.Operations;
26 using QName = System.Xml.XmlQualifiedName;
28 namespace Mono.Xml.Xsl
30 public class CompiledStylesheet {
32 Hashtable globalVariables;
34 ExpressionStore exprStore;
35 XmlNamespaceManager nsMgr;
38 Hashtable decimalFormats;
39 MSXslScriptManager msScripts;
41 public CompiledStylesheet (XslStylesheet style, Hashtable globalVariables, Hashtable attrSets, ExpressionStore exprStore, XmlNamespaceManager nsMgr, Hashtable keys, Hashtable outputs, Hashtable decimalFormats,
42 MSXslScriptManager msScripts)
45 this.globalVariables = globalVariables;
46 this.attrSets = attrSets;
47 this.exprStore = exprStore;
50 this.outputs = outputs;
51 this.decimalFormats = decimalFormats;
52 this.msScripts = msScripts;
54 public Hashtable Variables {get{return globalVariables;}}
55 public XslStylesheet Style { get { return style; }}
56 public ExpressionStore ExpressionStore {get{return exprStore;}}
57 public XmlNamespaceManager NamespaceManager {get{return nsMgr;}}
58 public Hashtable Keys {get { return keys;}}
59 public Hashtable Outputs { get { return outputs; }}
61 public MSXslScriptManager ScriptManager {
62 get { return msScripts; }
66 public XslDecimalFormat LookupDecimalFormat (QName name)
68 XslDecimalFormat ret = decimalFormats [name] as XslDecimalFormat;
69 if (ret == null && name == QName.Empty)
70 return XslDecimalFormat.Default;
74 public XslGeneralVariable ResolveVariable (QName name)
76 return (XslGeneralVariable)globalVariables [name];
79 public XslAttributeSet ResolveAttributeSet (QName name)
81 return (XslAttributeSet)attrSets [name];
85 public class Compiler : IStaticXsltContext {
86 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
88 ArrayList inputStack = new ArrayList ();
89 XPathNavigator currentInput;
91 Stack styleStack = new Stack ();
92 XslStylesheet currentStyle;
94 Hashtable globalVariables = new Hashtable ();
95 Hashtable attrSets = new Hashtable ();
97 ExpressionStore exprStore = new ExpressionStore ();
98 XmlNamespaceManager nsMgr = new XmlNamespaceManager (new NameTable ());
103 XslStylesheet rootStyle;
104 Hashtable outputs = new Hashtable ();
105 bool keyCompilationMode;
107 public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence)
109 this.parser = new XPathParser (this);
112 this.res = new XmlUrlResolver ();
113 this.evidence = evidence;
115 if (!nav.MoveToFirstChild ())
116 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element.", null, nav);
118 outputs [""] = new XslOutput ("");
120 while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext();
122 PushInputDocument (nav);
123 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml))
126 nsMgr.AddNamespace (nav.LocalName, nav.Value);
127 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
130 this.rootStyle = new XslStylesheet (this);
132 return new CompiledStylesheet (rootStyle, globalVariables, attrSets, exprStore, nsMgr, rootStyle.Keys, outputs, decimalFormats, msScripts);
135 MSXslScriptManager msScripts = new MSXslScriptManager ();
136 public MSXslScriptManager ScriptManager {
137 get { return msScripts; }
140 public bool KeyCompilationMode {
141 get { return keyCompilationMode; }
142 set { keyCompilationMode = value; }
145 internal Evidence Evidence {
146 get { return evidence; }
150 public XPathNavigator Input {
151 get { return currentInput; }
154 public XslStylesheet CurrentStylesheet {
155 get { return currentStyle; }
158 public void PushStylesheet (XslStylesheet style)
160 if (currentStyle != null) styleStack.Push (currentStyle);
161 currentStyle = style;
164 public void PopStylesheet ()
166 if (styleStack.Count == 0)
169 currentStyle = (XslStylesheet)styleStack.Pop ();
172 public void PushInputDocument (string url)
174 // todo: detect recursion
175 Uri absUri = res.ResolveUri (new Uri (Input.BaseURI), url);
176 using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream)))
179 XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUri.ToString (), s));
180 vr.ValidationType = ValidationType.None;
181 XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator ();
183 n.MoveToFirstChild ();
185 if (n.NodeType == XPathNodeType.Element)
187 } while (n.MoveToNext ());
188 PushInputDocument (n);
192 private void PushInputDocument (XPathNavigator nav)
194 for (int i = 0; i < inputStack.Count; i++) {
195 XPathNavigator cur = (XPathNavigator) inputStack [i];
196 if (cur.BaseURI == nav.BaseURI) {
197 IXmlLineInfo li = currentInput as IXmlLineInfo;
198 throw new XsltCompileException (null,
199 currentInput.BaseURI,
200 li != null ? li.LineNumber : 0,
201 li != null ? li.LinePosition : 0);
204 if (currentInput != null)
205 inputStack.Add (currentInput);
209 public void PopInputDocument ()
211 int last = inputStack.Count - 1;
212 currentInput = (XPathNavigator) inputStack [last];
213 inputStack.RemoveAt (last);
216 public QName ParseQNameAttribute (string localName)
218 return ParseQNameAttribute (localName, String.Empty);
220 public QName ParseQNameAttribute (string localName, string ns)
222 return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input);
225 public QName [] ParseQNameListAttribute (string localName)
227 return ParseQNameListAttribute (localName, String.Empty);
230 public QName [] ParseQNameListAttribute (string localName, string ns)
232 string s = GetAttribute (localName, ns);
233 if (s == null) return null;
235 string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'});
236 QName [] ret = new QName [names.Length];
238 for (int i = 0; i < names.Length; i++)
239 ret [i] = XslNameUtil.FromString (names [i], Input);
244 public bool ParseYesNoAttribute (string localName, bool defaultVal)
246 return ParseYesNoAttribute (localName, String.Empty, defaultVal);
249 public bool ParseYesNoAttribute (string localName, string ns, bool defaultVal)
251 string s = GetAttribute (localName, ns);
254 case null: return defaultVal;
255 case "yes": return true;
256 case "no": return false;
258 throw new XsltCompileException ("invalid value for " + localName, null, Input);
262 public string GetAttribute (string localName)
264 return GetAttribute (localName, String.Empty);
267 public string GetAttribute (string localName, string ns)
269 if (!Input.MoveToAttribute (localName, ns))
272 string ret = Input.Value;
273 Input.MoveToParent ();
276 public XslAvt ParseAvtAttribute (string localName)
278 return ParseAvtAttribute (localName, String.Empty);
280 public XslAvt ParseAvtAttribute (string localName, string ns)
282 return ParseAvt (GetAttribute (localName, ns));
285 public void AssertAttribute (string localName)
287 AssertAttribute (localName, "");
289 public void AssertAttribute (string localName, string ns)
291 if (Input.GetAttribute (localName, ns) == null)
292 throw new XsltCompileException ("Was expecting the " + localName + " attribute.", null, Input);
295 public XslAvt ParseAvt (string s)
297 if (s == null) return null;
298 return new XslAvt (s, this);
304 public Pattern CompilePattern (string pattern)
306 if (pattern == null || pattern == "") return null;
307 Pattern p = Pattern.Compile (pattern, this);
309 exprStore.AddPattern (p, this);
314 internal XPathParser parser;
315 internal CompiledExpression CompileExpression (string expression)
317 return CompileExpression (expression, false);
320 internal CompiledExpression CompileExpression (string expression, bool isKey)
322 if (expression == null || expression == "") return null;
324 Expression expr = parser.Compile (expression);
326 expr = new ExprKeyContainer (expr);
327 CompiledExpression e = new CompiledExpression (expr);
329 exprStore.AddExpression (e, this);
334 public XslOperation CompileTemplateContent ()
336 return CompileTemplateContent (XPathNodeType.All);
339 public XslOperation CompileTemplateContent (XPathNodeType parentType)
341 return new XslTemplateContent (this, parentType);
345 public void AddGlobalVariable (XslGlobalVariable var)
347 globalVariables [var.Name] = var;
350 public void AddAttributeSet (XslAttributeSet set)
352 XslAttributeSet existing = attrSets [set.Name] as XslAttributeSet;
353 // The latter set will have higher priority
354 if (existing != null) {
355 existing.Merge (set);
356 attrSets [set.Name] = existing;
359 attrSets [set.Name] = set;
362 VariableScope curVarScope;
364 public void PushScope ()
366 curVarScope = new VariableScope (curVarScope);
369 public VariableScope PopScope ()
371 curVarScope.giveHighTideToParent ();
372 VariableScope cur = curVarScope;
373 curVarScope = curVarScope.Parent;
377 public int AddVariable (XslLocalVariable v)
379 if (curVarScope == null)
380 throw new XsltCompileException ("Not initialized variable", null, Input);
382 return curVarScope.AddVariable (v);
385 public void AddSort (XPathExpression e, Sort s)
387 exprStore.AddSort (e, s);
389 public VariableScope CurrentVariableScope { get { return curVarScope; }}
392 #region Scope (version, {excluded, extension} namespaces)
393 [MonoTODO ("This will work, but is *very* slow")]
394 public bool IsExtensionNamespace (string nsUri)
396 if (nsUri == XsltNamespace) return true;
398 XPathNavigator nav = Input.Clone ();
399 XPathNavigator nsScope = nav.Clone ();
401 bool isXslt = nav.NamespaceURI == XsltNamespace;
402 nsScope.MoveTo (nav);
403 if (nav.MoveToFirstAttribute ()) {
405 if (nav.LocalName == "extension-element-prefixes" &&
406 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
409 foreach (string ns in nav.Value.Split (' '))
410 if (nsScope.GetNamespace (ns == "#default" ? "" : ns) == nsUri)
413 } while (nav.MoveToNextAttribute ());
416 } while (nav.MoveToParent ());
421 public Hashtable GetNamespacesToCopy ()
423 Hashtable ret = new Hashtable ();
425 XPathNavigator nav = Input.Clone ();
426 XPathNavigator nsScope = nav.Clone ();
428 if (nav.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
430 if (nav.Value != XsltNamespace && !ret.Contains (nav.Name))
431 ret.Add (nav.Name, nav.Value);
432 } while (nav.MoveToNextNamespace (XPathNamespaceScope.Local));
437 bool isXslt = nav.NamespaceURI == XsltNamespace;
438 nsScope.MoveTo (nav);
440 if (nav.MoveToFirstAttribute()) {
442 if ((nav.LocalName == "extension-element-prefixes" || nav.LocalName == "exclude-result-prefixes") &&
443 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
445 foreach (string ns in nav.Value.Split (' ')) {
446 string realNs = ns == "#default" ? "" : ns;
448 if ((string)ret [realNs] == nsScope.GetNamespace (realNs))
452 } while (nav.MoveToNextAttribute ());
455 } while (nav.MoveToParent ());
461 #region Decimal Format
462 Hashtable decimalFormats = new Hashtable ();
464 public void CompileDecimalFormat ()
466 QName nm = ParseQNameAttribute ("name");
468 if (nm.Name != String.Empty)
469 XmlConvert.VerifyNCName (nm.Name);
470 } catch (XmlException ex) {
471 throw new XsltCompileException ("Invalid qualified name.", ex, Input);
473 XslDecimalFormat df = new XslDecimalFormat (this);
475 if (decimalFormats.Contains (nm))
476 ((XslDecimalFormat)decimalFormats [nm]).CheckSameAs (df);
478 decimalFormats [nm] = df;
481 #region Static XSLT context
482 Expression IStaticXsltContext.TryGetVariable (string nm)
484 if (curVarScope == null)
487 XslLocalVariable var = curVarScope.ResolveStatic (XslNameUtil.FromString (nm, Input));
492 return new XPathVariableBinding (var);
495 Expression IStaticXsltContext.TryGetFunction (QName name, FunctionArguments args)
497 if (name.Namespace != null && name.Namespace != "")
501 case "current": return new XsltCurrent (args);
502 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args);
503 case "element-available": return new XsltElementAvailable (args, this);
504 case "system-property": return new XsltSystemProperty (args, this);
505 case "function-available": return new XsltFunctionAvailable (args, this);
506 case "generate-id": return new XsltGenerateId (args);
507 case "format-number": return new XsltFormatNumber (args, this);
509 if (KeyCompilationMode)
510 throw new XsltCompileException ("Cannot use key() function inside key definition.", null, this.Input);
511 return new XsltKey (args, this);
512 case "document": return new XsltDocument (args, this);
518 QName IStaticXsltContext.LookupQName (string s)
520 return XslNameUtil.FromString (s, Input);
523 XmlNamespaceManager IStaticXsltContext.GetNsm ()
525 return new XPathNavigatorNsm (Input);
528 public XmlNamespaceManager GetNsm ()
530 return new XPathNavigatorNsm (Input);
533 public void CompileOutput ()
535 XPathNavigator n = Input;
536 string uri = n.GetAttribute ("href", "");
537 XslOutput output = outputs [uri] as XslOutput;
538 if (output == null) {
539 output = new XslOutput (uri);
540 outputs.Add (uri, output);
546 public class VariableScope {
548 VariableScope parent;
550 int highTide = 0; // this will be the size of the stack frame
552 internal void giveHighTideToParent ()
555 parent.highTide = System.Math.Max (VariableHighTide, parent.VariableHighTide);
558 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
560 public VariableScope (VariableScope parent)
562 this.parent = parent;
564 this.nextSlot = parent.nextSlot;
567 public VariableScope Parent { get { return parent; }}
569 public int AddVariable (XslLocalVariable v)
571 if (variables == null)
572 variables = new Hashtable ();
574 variables [v.Name] = v;
578 public XslLocalVariable ResolveStatic (QName name)
580 for (VariableScope s = this; s != null; s = s.Parent) {
581 if (s.variables == null) continue;
582 XslLocalVariable v = s.variables [name] as XslLocalVariable;
583 if (v != null) return v;
588 public XslLocalVariable Resolve (XslTransformProcessor p, QName name)
590 for (VariableScope s = this; s != null; s = s.Parent) {
591 if (s.variables == null) continue;
592 XslLocalVariable v = s.variables [name] as XslLocalVariable;
593 if (v != null && v.IsEvaluated (p))
603 XmlDataType dataType;
605 XmlCaseOrder caseOrder;
607 XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt;
608 XPathExpression expr;
610 public Sort (Compiler c)
612 expr = c.CompileExpression (c.GetAttribute ("select"));
614 expr = c.CompileExpression ("string(.)");
616 langAvt = c.ParseAvtAttribute ("lang");
617 dataTypeAvt = c.ParseAvtAttribute ("data-type");
618 orderAvt = c.ParseAvtAttribute ("order");
619 caseOrderAvt = c.ParseAvtAttribute ("case-order");
621 // Precalc whatever we can
622 lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt));
623 dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt));
624 order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt));
625 caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt));
629 string ParseLang (string value)
634 XmlDataType ParseDataType (string value)
639 return XmlDataType.Number;
643 return XmlDataType.Text;
647 XmlSortOrder ParseOrder (string value)
652 return XmlSortOrder.Descending;
656 return XmlSortOrder.Ascending;
660 XmlCaseOrder ParseCaseOrder (string value)
665 return XmlCaseOrder.UpperFirst;
667 return XmlCaseOrder.LowerFirst;
670 return XmlCaseOrder.None;
675 public void AddToExpr (XPathExpression e, XslTransformProcessor p)
679 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
680 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
681 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
682 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
687 public class ExpressionStore {
688 Hashtable exprToSorts;
690 public void AddExpression (XPathExpression e, Compiler c)
694 public void AddPattern (Pattern p, Compiler c)
698 public void AddSort (XPathExpression e, Sort s)
700 if (exprToSorts == null)
701 exprToSorts = new Hashtable ();
703 if (exprToSorts.Contains (e))
704 ((ArrayList)exprToSorts [e]).Add (s);
706 ArrayList a = new ArrayList ();
712 public XPathExpression PrepForExecution (XPathExpression e, XslTransformProcessor p)
714 if (exprToSorts != null && exprToSorts.Contains (e))
716 XPathExpression expr = e.Clone ();
717 foreach (Sort s in (ArrayList)exprToSorts [e])
718 s.AddToExpr (expr,p);
724 public bool PatternMatches (Pattern p, XslTransformProcessor proc, XPathNavigator n)
726 return p.Matches (n, proc.XPathContext);
730 internal class XslNameUtil
732 public static QName [] FromListString (string names, XPathNavigator current)
734 string [] nameArray = names.Split (XmlChar.WhitespaceChars);
736 for (int i = 0; i < nameArray.Length; i++)
737 if (nameArray [i] != String.Empty)
740 XmlQualifiedName [] qnames = new XmlQualifiedName [idx];
743 for (int i = 0; i < nameArray.Length; i++)
744 if (nameArray [i] != String.Empty)
745 qnames [idx++] = FromString (nameArray [i], current, true);
750 public static QName FromString (string name, XPathNavigator current)
752 return FromString (name, current, false);
755 public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns)
757 if (current.NodeType == XPathNodeType.Attribute)
758 (current = current.Clone ()).MoveToParent ();
760 int colon = name.IndexOf (':');
762 return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon)));
764 return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : "");
766 throw new ArgumentException ("Invalid name: " + name);
769 public static QName FromString (string name, XmlNamespaceManager ctx)
771 int colon = name.IndexOf (':');
773 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon)));
775 // Default namespace is not used for unprefixed names.
776 return new QName (name, "");
778 throw new ArgumentException ("Invalid name: " + name);
781 public static string LocalNameOf (string name)
783 int colon = name.IndexOf (':');
785 return name.Substring (colon + 1);
789 throw new ArgumentException ("Invalid name: " + name);
793 internal class XPathNavigatorNsm : XmlNamespaceManager {
794 XPathNavigator nsScope;
796 public XPathNavigatorNsm (XPathNavigator n) : base (n.NameTable) {
797 nsScope = n.Clone ();
798 if (nsScope.NodeType == XPathNodeType.Attribute)
799 nsScope.MoveToParent ();
802 public override string DefaultNamespace { get { return String.Empty; }}
804 public override string LookupNamespace (string prefix)
806 if (prefix == "" || prefix == null)
809 return nsScope.GetNamespace (prefix);