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;
36 using System.Collections.Specialized;
37 using System.Security.Policy;
39 using System.Xml.Schema;
40 using System.Xml.XPath;
44 using Mono.Xml.Xsl.Operations;
47 using QName = System.Xml.XmlQualifiedName;
49 namespace Mono.Xml.Xsl
51 internal class CompiledStylesheet {
53 Hashtable globalVariables;
55 ExpressionStore exprStore;
56 XmlNamespaceManager nsMgr;
59 Hashtable decimalFormats;
60 MSXslScriptManager msScripts;
62 public CompiledStylesheet (XslStylesheet style, Hashtable globalVariables, Hashtable attrSets, ExpressionStore exprStore, XmlNamespaceManager nsMgr, Hashtable keys, Hashtable outputs, Hashtable decimalFormats,
63 MSXslScriptManager msScripts)
66 this.globalVariables = globalVariables;
67 this.attrSets = attrSets;
68 this.exprStore = exprStore;
71 this.outputs = outputs;
72 this.decimalFormats = decimalFormats;
73 this.msScripts = msScripts;
75 public Hashtable Variables {get{return globalVariables;}}
76 public XslStylesheet Style { get { return style; }}
77 public ExpressionStore ExpressionStore {get{return exprStore;}}
78 public XmlNamespaceManager NamespaceManager {get{return nsMgr;}}
79 public Hashtable Keys {get { return keys;}}
80 public Hashtable Outputs { get { return outputs; }}
82 public MSXslScriptManager ScriptManager {
83 get { return msScripts; }
87 public XslDecimalFormat LookupDecimalFormat (QName name)
89 XslDecimalFormat ret = decimalFormats [name] as XslDecimalFormat;
90 if (ret == null && name == QName.Empty)
91 return XslDecimalFormat.Default;
95 public XslGeneralVariable ResolveVariable (QName name)
97 return (XslGeneralVariable)globalVariables [name];
100 public XslAttributeSet ResolveAttributeSet (QName name)
102 return (XslAttributeSet)attrSets [name];
106 internal class Compiler : IStaticXsltContext {
107 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
109 ArrayList inputNSResolverStack = new ArrayList ();
110 XPathNavigatorNsm currentNsm;
112 Stack styleStack = new Stack ();
113 XslStylesheet currentStyle;
115 Hashtable globalVariables = new Hashtable ();
116 Hashtable attrSets = new Hashtable ();
118 ExpressionStore exprStore = new ExpressionStore ();
119 XmlNamespaceManager nsMgr = new XmlNamespaceManager (new NameTable ());
124 XslStylesheet rootStyle;
125 Hashtable outputs = new Hashtable ();
126 bool keyCompilationMode;
128 public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence)
130 this.parser = new XPathParser (this);
133 this.res = new XmlUrlResolver ();
134 this.evidence = evidence;
136 if (!nav.MoveToFirstChild ())
137 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element.", null, nav);
139 outputs [""] = new XslOutput ("");
141 while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext();
143 PushInputDocument (nav);
144 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml))
147 nsMgr.AddNamespace (nav.LocalName, nav.Value);
148 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
152 this.rootStyle = new XslStylesheet (this);
153 } catch (XsltCompileException) {
155 } catch (Exception x) {
156 throw new XsltCompileException ("XSLT compile error. " + x.Message, x, Input);
159 return new CompiledStylesheet (rootStyle, globalVariables, attrSets, exprStore, nsMgr, rootStyle.Keys, outputs, decimalFormats, msScripts);
162 MSXslScriptManager msScripts = new MSXslScriptManager ();
163 public MSXslScriptManager ScriptManager {
164 get { return msScripts; }
167 public bool KeyCompilationMode {
168 get { return keyCompilationMode; }
169 set { keyCompilationMode = value; }
172 internal Evidence Evidence {
173 get { return evidence; }
177 public XPathNavigator Input {
178 get { return currentNsm.Navigator; }
181 public XslStylesheet CurrentStylesheet {
182 get { return currentStyle; }
185 public void PushStylesheet (XslStylesheet style)
187 if (currentStyle != null) styleStack.Push (currentStyle);
188 currentStyle = style;
191 public void PopStylesheet ()
193 if (styleStack.Count == 0)
196 currentStyle = (XslStylesheet)styleStack.Pop ();
199 public void PushInputDocument (string url)
201 // todo: detect recursion
202 Uri baseUriObj = (Input.BaseURI == String.Empty) ? null : new Uri (Input.BaseURI);
203 Uri absUri = res.ResolveUri (baseUriObj, url);
204 string absUriString = absUri != null ? absUri.ToString () : String.Empty;
205 using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream)))
208 throw new XsltCompileException ("Can not access URI "+absUri.ToString (), null, Input);
209 XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUriString, s, nsMgr.NameTable));
210 vr.ValidationType = ValidationType.None;
211 XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator ();
213 n.MoveToFirstChild ();
215 if (n.NodeType == XPathNodeType.Element)
217 } while (n.MoveToNext ());
218 PushInputDocument (n);
222 private void PushInputDocument (XPathNavigator nav)
224 // Inclusion nest check
225 IXmlLineInfo li = currentNsm != null ?
226 currentNsm.Navigator as IXmlLineInfo : null;
227 bool hasLineInfo = (li != null && !li.HasLineInfo ());
228 for (int i = 0; i < inputNSResolverStack.Count; i++) {
229 XPathNavigator cur = ((XPathNavigatorNsm) inputNSResolverStack [i]).Navigator;
230 if (cur.BaseURI == nav.BaseURI) {
231 throw new XsltCompileException (null,
232 currentNsm.Navigator.BaseURI,
233 hasLineInfo ? li.LineNumber : 0,
234 hasLineInfo ? li.LinePosition : 0);
237 if (currentNsm != null)
238 inputNSResolverStack.Add (currentNsm);
239 currentNsm = new XPathNavigatorNsm (nav);
242 public void PopInputDocument ()
244 int last = inputNSResolverStack.Count - 1;
245 currentNsm = (XPathNavigatorNsm) inputNSResolverStack [last];
246 inputNSResolverStack.RemoveAt (last);
249 public QName ParseQNameAttribute (string localName)
251 return ParseQNameAttribute (localName, String.Empty);
253 public QName ParseQNameAttribute (string localName, string ns)
255 return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input);
258 public QName [] ParseQNameListAttribute (string localName)
260 return ParseQNameListAttribute (localName, String.Empty);
263 public QName [] ParseQNameListAttribute (string localName, string ns)
265 string s = GetAttribute (localName, ns);
266 if (s == null) return null;
268 string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'});
270 for (int i=0; i<names.Length; i++)
271 if (names[i].Length != 0)
274 QName [] ret = new QName [count];
276 for (int i = 0, j=0; i < names.Length; i++)
277 if (names[i].Length != 0)
278 ret [j++] = XslNameUtil.FromString (names [i], Input);
283 public bool ParseYesNoAttribute (string localName, bool defaultVal)
285 return ParseYesNoAttribute (localName, String.Empty, defaultVal);
288 public bool ParseYesNoAttribute (string localName, string ns, bool defaultVal)
290 string s = GetAttribute (localName, ns);
293 case null: return defaultVal;
294 case "yes": return true;
295 case "no": return false;
297 throw new XsltCompileException ("invalid value for " + localName, null, Input);
301 public string GetAttribute (string localName)
303 return GetAttribute (localName, String.Empty);
306 public string GetAttribute (string localName, string ns)
308 if (!Input.MoveToAttribute (localName, ns))
311 string ret = Input.Value;
312 Input.MoveToParent ();
315 public XslAvt ParseAvtAttribute (string localName)
317 return ParseAvtAttribute (localName, String.Empty);
319 public XslAvt ParseAvtAttribute (string localName, string ns)
321 return ParseAvt (GetAttribute (localName, ns));
324 public void AssertAttribute (string localName)
326 AssertAttribute (localName, "");
328 public void AssertAttribute (string localName, string ns)
330 if (Input.GetAttribute (localName, ns) == null)
331 throw new XsltCompileException ("Was expecting the " + localName + " attribute.", null, Input);
334 public XslAvt ParseAvt (string s)
336 if (s == null) return null;
337 return new XslAvt (s, this);
343 public Pattern CompilePattern (string pattern, XPathNavigator loc)
345 if (pattern == null || pattern == "") return null;
346 Pattern p = Pattern.Compile (pattern, this);
348 throw new XsltCompileException (String.Format ("Invalid pattern '{0}'.", pattern), null, loc);
349 exprStore.AddPattern (p, this);
354 internal XPathParser parser;
355 internal CompiledExpression CompileExpression (string expression)
357 return CompileExpression (expression, false);
360 internal CompiledExpression CompileExpression (string expression, bool isKey)
362 if (expression == null || expression == "") return null;
364 Expression expr = parser.Compile (expression);
366 expr = new ExprKeyContainer (expr);
367 CompiledExpression e = new CompiledExpression (expression, expr);
369 exprStore.AddExpression (e, this);
374 public XslOperation CompileTemplateContent ()
376 return CompileTemplateContent (XPathNodeType.All);
379 public XslOperation CompileTemplateContent (XPathNodeType parentType)
381 return new XslTemplateContent (this, parentType);
385 public void AddGlobalVariable (XslGlobalVariable var)
387 globalVariables [var.Name] = var;
390 public void AddAttributeSet (XslAttributeSet set)
392 XslAttributeSet existing = attrSets [set.Name] as XslAttributeSet;
393 // The latter set will have higher priority
394 if (existing != null) {
395 existing.Merge (set);
396 attrSets [set.Name] = existing;
399 attrSets [set.Name] = set;
402 VariableScope curVarScope;
404 public void PushScope ()
406 curVarScope = new VariableScope (curVarScope);
409 public VariableScope PopScope ()
411 curVarScope.giveHighTideToParent ();
412 VariableScope cur = curVarScope;
413 curVarScope = curVarScope.Parent;
417 public int AddVariable (XslLocalVariable v)
419 if (curVarScope == null)
420 throw new XsltCompileException ("Not initialized variable", null, Input);
422 return curVarScope.AddVariable (v);
425 public void AddSort (XPathExpression e, Sort s)
427 exprStore.AddSort (e, s);
429 public VariableScope CurrentVariableScope { get { return curVarScope; }}
432 #region Scope (version, {excluded, extension} namespaces)
433 [MonoTODO ("This will work, but is *very* slow")]
434 public bool IsExtensionNamespace (string nsUri)
436 if (nsUri == XsltNamespace) return true;
438 XPathNavigator nav = Input.Clone ();
439 XPathNavigator nsScope = nav.Clone ();
441 bool isXslt = nav.NamespaceURI == XsltNamespace;
442 nsScope.MoveTo (nav);
443 if (nav.MoveToFirstAttribute ()) {
445 if (nav.LocalName == "extension-element-prefixes" &&
446 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
449 foreach (string ns in nav.Value.Split (' '))
450 if (nsScope.GetNamespace (ns == "#default" ? "" : ns) == nsUri)
453 } while (nav.MoveToNextAttribute ());
456 } while (nav.MoveToParent ());
461 public Hashtable GetNamespacesToCopy ()
463 Hashtable ret = new Hashtable ();
465 XPathNavigator nav = Input.Clone ();
466 XPathNavigator nsScope = nav.Clone ();
468 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml)) {
470 if (nav.Value != XsltNamespace && !ret.Contains (nav.Name))
471 ret.Add (nav.Name, nav.Value);
472 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
477 bool isXslt = nav.NamespaceURI == XsltNamespace;
478 nsScope.MoveTo (nav);
480 if (nav.MoveToFirstAttribute ()) {
482 if ((nav.LocalName == "extension-element-prefixes" || nav.LocalName == "exclude-result-prefixes") &&
483 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
485 foreach (string ns in nav.Value.Split (' ')) {
486 string realNs = ns == "#default" ? "" : ns;
488 if ((string)ret [realNs] == nsScope.GetNamespace (realNs))
492 } while (nav.MoveToNextAttribute ());
495 } while (nav.MoveToParent ());
501 #region Decimal Format
502 Hashtable decimalFormats = new Hashtable ();
504 public void CompileDecimalFormat ()
506 QName nm = ParseQNameAttribute ("name");
508 if (nm.Name != String.Empty)
509 XmlConvert.VerifyNCName (nm.Name);
510 } catch (XmlException ex) {
511 throw new XsltCompileException ("Invalid qualified name.", ex, Input);
513 XslDecimalFormat df = new XslDecimalFormat (this);
515 if (decimalFormats.Contains (nm))
516 ((XslDecimalFormat)decimalFormats [nm]).CheckSameAs (df);
518 decimalFormats [nm] = df;
521 #region Static XSLT context
522 Expression IStaticXsltContext.TryGetVariable (string nm)
524 if (curVarScope == null)
527 XslLocalVariable var = curVarScope.ResolveStatic (XslNameUtil.FromString (nm, Input));
532 return new XPathVariableBinding (var);
535 Expression IStaticXsltContext.TryGetFunction (QName name, FunctionArguments args)
537 string ns = GetNsm ().LookupNamespace (name.Namespace, false);
538 if (ns == XslStylesheet.MSXsltNamespace && name.Name == "node-set")
539 return new MSXslNodeSet (args);
545 case "current": return new XsltCurrent (args);
546 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args);
547 case "element-available": return new XsltElementAvailable (args, this);
548 case "system-property": return new XsltSystemProperty (args, this);
549 case "function-available": return new XsltFunctionAvailable (args, this);
550 case "generate-id": return new XsltGenerateId (args);
551 case "format-number": return new XsltFormatNumber (args, this);
553 if (KeyCompilationMode)
554 throw new XsltCompileException ("Cannot use key() function inside key definition.", null, this.Input);
555 return new XsltKey (args, this);
556 case "document": return new XsltDocument (args, this);
562 QName IStaticXsltContext.LookupQName (string s)
564 return XslNameUtil.FromString (s, Input);
567 XmlNamespaceManager IStaticXsltContext.GetNsm ()
572 public XmlNamespaceManager GetNsm ()
577 public void CompileOutput ()
579 XPathNavigator n = Input;
580 string uri = n.GetAttribute ("href", "");
581 XslOutput output = outputs [uri] as XslOutput;
582 if (output == null) {
583 output = new XslOutput (uri);
584 outputs.Add (uri, output);
590 internal class VariableScope {
592 VariableScope parent;
594 int highTide = 0; // this will be the size of the stack frame
596 internal void giveHighTideToParent ()
599 parent.highTide = System.Math.Max (VariableHighTide, parent.VariableHighTide);
602 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
604 public VariableScope (VariableScope parent)
606 this.parent = parent;
608 this.nextSlot = parent.nextSlot;
611 public VariableScope Parent { get { return parent; }}
613 public int AddVariable (XslLocalVariable v)
615 if (variables == null)
616 variables = new Hashtable ();
618 variables [v.Name] = v;
622 public XslLocalVariable ResolveStatic (QName name)
624 for (VariableScope s = this; s != null; s = s.Parent) {
625 if (s.variables == null) continue;
626 XslLocalVariable v = s.variables [name] as XslLocalVariable;
627 if (v != null) return v;
632 public XslLocalVariable Resolve (XslTransformProcessor p, QName name)
634 for (VariableScope s = this; s != null; s = s.Parent) {
635 if (s.variables == null) continue;
636 XslLocalVariable v = s.variables [name] as XslLocalVariable;
637 if (v != null && v.IsEvaluated (p))
645 internal class Sort {
647 XmlDataType dataType;
649 XmlCaseOrder caseOrder;
651 XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt;
652 XPathExpression expr;
654 public Sort (Compiler c)
656 expr = c.CompileExpression (c.GetAttribute ("select"));
658 expr = c.CompileExpression ("string(.)");
660 langAvt = c.ParseAvtAttribute ("lang");
661 dataTypeAvt = c.ParseAvtAttribute ("data-type");
662 orderAvt = c.ParseAvtAttribute ("order");
663 caseOrderAvt = c.ParseAvtAttribute ("case-order");
665 // Precalc whatever we can
666 lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt));
667 dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt));
668 order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt));
669 caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt));
673 string ParseLang (string value)
678 XmlDataType ParseDataType (string value)
683 return XmlDataType.Number;
687 return XmlDataType.Text;
691 XmlSortOrder ParseOrder (string value)
696 return XmlSortOrder.Descending;
700 return XmlSortOrder.Ascending;
704 XmlCaseOrder ParseCaseOrder (string value)
709 return XmlCaseOrder.UpperFirst;
711 return XmlCaseOrder.LowerFirst;
714 return XmlCaseOrder.None;
719 public void AddToExpr (XPathExpression e, XslTransformProcessor p)
723 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
724 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
725 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
726 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
731 internal class ExpressionStore {
732 Hashtable exprToSorts;
734 public void AddExpression (XPathExpression e, Compiler c)
738 public void AddPattern (Pattern p, Compiler c)
742 public void AddSort (XPathExpression e, Sort s)
744 if (exprToSorts == null)
745 exprToSorts = new Hashtable ();
747 if (exprToSorts.Contains (e))
748 ((ArrayList)exprToSorts [e]).Add (s);
750 ArrayList a = new ArrayList ();
756 public XPathExpression PrepForExecution (XPathExpression e, XslTransformProcessor p)
758 if (exprToSorts != null && exprToSorts.Contains (e))
760 XPathExpression expr = e.Clone ();
761 foreach (Sort s in (ArrayList)exprToSorts [e])
762 s.AddToExpr (expr,p);
768 public bool PatternMatches (Pattern p, XslTransformProcessor proc, XPathNavigator n)
770 return p.Matches (n, proc.XPathContext);
774 internal class XslNameUtil
776 public static QName [] FromListString (string names, XPathNavigator current)
778 string [] nameArray = names.Split (XmlChar.WhitespaceChars);
780 for (int i = 0; i < nameArray.Length; i++)
781 if (nameArray [i] != String.Empty)
784 XmlQualifiedName [] qnames = new XmlQualifiedName [idx];
787 for (int i = 0; i < nameArray.Length; i++)
788 if (nameArray [i] != String.Empty)
789 qnames [idx++] = FromString (nameArray [i], current, true);
794 public static QName FromString (string name, XPathNavigator current)
796 return FromString (name, current, false);
799 public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns)
801 if (current.NodeType == XPathNodeType.Attribute)
802 (current = current.Clone ()).MoveToParent ();
804 int colon = name.IndexOf (':');
806 return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon)));
808 return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : "");
810 throw new ArgumentException ("Invalid name: " + name);
813 public static QName FromString (string name, Hashtable nsDecls)
815 int colon = name.IndexOf (':');
817 return new QName (name.Substring (colon + 1), nsDecls [name.Substring (0, colon)] as string);
819 // Default namespace is not used for unprefixed names.
820 return new QName (name, "");
822 throw new ArgumentException ("Invalid name: " + name);
825 public static QName FromString (string name, XmlNamespaceManager ctx)
827 int colon = name.IndexOf (':');
829 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon), false));
831 // Default namespace is not used for unprefixed names.
832 return new QName (name, "");
834 throw new ArgumentException ("Invalid name: " + name);
837 public static string LocalNameOf (string name)
839 int colon = name.IndexOf (':');
841 return name.Substring (colon + 1);
845 throw new ArgumentException ("Invalid name: " + name);
849 internal class XPathNavigatorNsm : XmlNamespaceManager {
850 XPathNavigator nsScope;
852 public XPathNavigatorNsm (XPathNavigator n) : base (n.NameTable) {
856 public XPathNavigator Navigator {
857 get { return nsScope; }
860 public override string DefaultNamespace { get { return String.Empty; }}
863 public override string LookupNamespace (string prefix, bool atomizedNames)
865 internal override string LookupNamespace (string prefix, bool atomizedNames)
868 if (prefix == "" || prefix == null)
871 XPathNavigator n = nsScope;
872 if (nsScope.NodeType == XPathNodeType.Attribute) {
873 n = nsScope.Clone ();
877 return n.GetNamespace (prefix);