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.
34 using System.Collections;
35 using System.Security.Policy;
37 using System.Xml.XPath;
41 using Mono.Xml.Xsl.Operations;
44 using QName = System.Xml.XmlQualifiedName;
46 namespace Mono.Xml.Xsl
48 internal class CompiledStylesheet {
50 Hashtable globalVariables;
52 ExpressionStore exprStore;
53 XmlNamespaceManager nsMgr;
56 Hashtable decimalFormats;
57 MSXslScriptManager msScripts;
59 public CompiledStylesheet (XslStylesheet style, Hashtable globalVariables, Hashtable attrSets, ExpressionStore exprStore, XmlNamespaceManager nsMgr, Hashtable keys, Hashtable outputs, Hashtable decimalFormats,
60 MSXslScriptManager msScripts)
63 this.globalVariables = globalVariables;
64 this.attrSets = attrSets;
65 this.exprStore = exprStore;
68 this.outputs = outputs;
69 this.decimalFormats = decimalFormats;
70 this.msScripts = msScripts;
72 public Hashtable Variables {get{return globalVariables;}}
73 public XslStylesheet Style { get { return style; }}
74 public ExpressionStore ExpressionStore {get{return exprStore;}}
75 public XmlNamespaceManager NamespaceManager {get{return nsMgr;}}
76 public Hashtable Keys {get { return keys;}}
77 public Hashtable Outputs { get { return outputs; }}
79 public MSXslScriptManager ScriptManager {
80 get { return msScripts; }
84 public XslDecimalFormat LookupDecimalFormat (QName name)
86 XslDecimalFormat ret = decimalFormats [name] as XslDecimalFormat;
87 if (ret == null && name == QName.Empty)
88 return XslDecimalFormat.Default;
92 public XslGeneralVariable ResolveVariable (QName name)
94 return (XslGeneralVariable)globalVariables [name];
97 public XslKey ResolveKey (QName name)
99 return (XslKey) keys [name];
102 public XslAttributeSet ResolveAttributeSet (QName name)
104 return (XslAttributeSet)attrSets [name];
108 internal class Compiler : IStaticXsltContext {
109 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
111 ArrayList inputStack = new ArrayList ();
112 XPathNavigator currentInput;
114 Stack styleStack = new Stack ();
115 XslStylesheet currentStyle;
117 Hashtable keys = new Hashtable ();
118 Hashtable globalVariables = new Hashtable ();
119 Hashtable attrSets = new Hashtable ();
121 ExpressionStore exprStore = new ExpressionStore ();
122 XmlNamespaceManager nsMgr = new XmlNamespaceManager (new NameTable ());
127 XslStylesheet rootStyle;
128 Hashtable outputs = new Hashtable ();
129 bool keyCompilationMode;
130 string stylesheetVersion;
132 public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence)
134 this.xpathParser = new XPathParser (this);
135 this.patternParser = new XsltPatternParser (this);
138 this.res = new XmlUrlResolver ();
139 this.evidence = evidence;
141 // reject empty document.
142 if (nav.NodeType == XPathNodeType.Root && !nav.MoveToFirstChild ())
143 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, nav);
144 while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext();
146 stylesheetVersion = nav.GetAttribute ("version",
147 (nav.NamespaceURI != XsltNamespace) ?
148 XsltNamespace : String.Empty);
149 outputs [""] = new XslOutput ("", stylesheetVersion);
151 PushInputDocument (nav);
152 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml))
155 nsMgr.AddNamespace (nav.LocalName, nav.Value);
156 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
160 rootStyle = new XslStylesheet ();
161 rootStyle.Compile (this);
162 } catch (XsltCompileException) {
164 } catch (Exception x) {
165 throw new XsltCompileException ("XSLT compile error. " + x.Message, x, Input);
168 return new CompiledStylesheet (rootStyle, globalVariables, attrSets, exprStore, nsMgr, keys, outputs, decimalFormats, msScripts);
171 MSXslScriptManager msScripts = new MSXslScriptManager ();
172 public MSXslScriptManager ScriptManager {
173 get { return msScripts; }
176 public bool KeyCompilationMode {
177 get { return keyCompilationMode; }
178 set { keyCompilationMode = value; }
181 internal Evidence Evidence {
182 get { return evidence; }
186 public XPathNavigator Input {
187 get { return currentInput; }
190 public XslStylesheet CurrentStylesheet {
191 get { return currentStyle; }
194 public void PushStylesheet (XslStylesheet style)
196 if (currentStyle != null) styleStack.Push (currentStyle);
197 currentStyle = style;
200 public void PopStylesheet ()
202 if (styleStack.Count == 0)
205 currentStyle = (XslStylesheet)styleStack.Pop ();
208 public void PushInputDocument (string url)
210 // todo: detect recursion
211 Uri baseUriObj = (Input.BaseURI == String.Empty) ? null : new Uri (Input.BaseURI);
212 Uri absUri = res.ResolveUri (baseUriObj, url);
213 string absUriString = absUri != null ? absUri.ToString () : String.Empty;
214 using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream)))
217 throw new XsltCompileException ("Can not access URI " + absUri.ToString (), null, Input);
218 XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUriString, s, nsMgr.NameTable));
219 vr.ValidationType = ValidationType.None;
220 XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator ();
222 n.MoveToFirstChild ();
224 if (n.NodeType == XPathNodeType.Element)
226 } while (n.MoveToNext ());
227 PushInputDocument (n);
231 public void PushInputDocument (XPathNavigator nav)
233 // Inclusion nest check
234 IXmlLineInfo li = currentInput as IXmlLineInfo;
235 bool hasLineInfo = (li != null && !li.HasLineInfo ());
236 for (int i = 0; i < inputStack.Count; i++) {
237 XPathNavigator cur = (XPathNavigator) inputStack [i];
238 if (cur.BaseURI == nav.BaseURI) {
239 throw new XsltCompileException (null,
240 currentInput.BaseURI,
241 hasLineInfo ? li.LineNumber : 0,
242 hasLineInfo ? li.LinePosition : 0);
245 if (currentInput != null)
246 inputStack.Add (currentInput);
250 public void PopInputDocument ()
252 int last = inputStack.Count - 1;
253 currentInput = (XPathNavigator) inputStack [last];
254 inputStack.RemoveAt (last);
257 public QName ParseQNameAttribute (string localName)
259 return ParseQNameAttribute (localName, String.Empty);
261 public QName ParseQNameAttribute (string localName, string ns)
263 return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input);
266 public QName [] ParseQNameListAttribute (string localName)
268 return ParseQNameListAttribute (localName, String.Empty);
271 public QName [] ParseQNameListAttribute (string localName, string ns)
273 string s = GetAttribute (localName, ns);
274 if (s == null) return null;
276 string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'});
278 for (int i=0; i<names.Length; i++)
279 if (names[i].Length != 0)
282 QName [] ret = new QName [count];
284 for (int i = 0, j=0; i < names.Length; i++)
285 if (names[i].Length != 0)
286 ret [j++] = XslNameUtil.FromString (names [i], Input);
291 public bool ParseYesNoAttribute (string localName, bool defaultVal)
293 return ParseYesNoAttribute (localName, String.Empty, defaultVal);
296 public bool ParseYesNoAttribute (string localName, string ns, bool defaultVal)
298 string s = GetAttribute (localName, ns);
301 case null: return defaultVal;
302 case "yes": return true;
303 case "no": return false;
305 throw new XsltCompileException ("Invalid value for " + localName, null, Input);
309 public string GetAttribute (string localName)
311 return GetAttribute (localName, String.Empty);
314 public string GetAttribute (string localName, string ns)
316 if (!Input.MoveToAttribute (localName, ns))
319 string ret = Input.Value;
320 Input.MoveToParent ();
323 public XslAvt ParseAvtAttribute (string localName)
325 return ParseAvtAttribute (localName, String.Empty);
327 public XslAvt ParseAvtAttribute (string localName, string ns)
329 return ParseAvt (GetAttribute (localName, ns));
332 public void AssertAttribute (string localName)
334 AssertAttribute (localName, "");
336 public void AssertAttribute (string localName, string ns)
338 if (Input.GetAttribute (localName, ns) == null)
339 throw new XsltCompileException ("Was expecting the " + localName + " attribute", null, Input);
342 public XslAvt ParseAvt (string s)
344 if (s == null) return null;
345 return new XslAvt (s, this);
351 public Pattern CompilePattern (string pattern, XPathNavigator loc)
353 if (pattern == null || pattern == "") return null;
354 Pattern p = Pattern.Compile (pattern, this);
356 throw new XsltCompileException (String.Format ("Invalid pattern '{0}'", pattern), null, loc);
357 exprStore.AddPattern (p, this);
362 internal XPathParser xpathParser;
363 internal XsltPatternParser patternParser;
364 internal CompiledExpression CompileExpression (string expression)
366 return CompileExpression (expression, false);
369 internal CompiledExpression CompileExpression (string expression, bool isKey)
371 if (expression == null || expression == "") return null;
373 Expression expr = xpathParser.Compile (expression);
375 expr = new ExprKeyContainer (expr);
376 CompiledExpression e = new CompiledExpression (expression, expr);
378 exprStore.AddExpression (e, this);
383 public XslOperation CompileTemplateContent ()
385 return CompileTemplateContent (XPathNodeType.All, false);
388 public XslOperation CompileTemplateContent (XPathNodeType parentType)
390 return CompileTemplateContent (parentType, false);
393 public XslOperation CompileTemplateContent (XPathNodeType parentType, bool xslForEach)
395 return new XslTemplateContent (this, parentType, xslForEach);
399 public void AddGlobalVariable (XslGlobalVariable var)
401 globalVariables [var.Name] = var;
404 public void AddKey (XslKey key)
406 keys [key.Name] = key;
409 public void AddAttributeSet (XslAttributeSet set)
411 XslAttributeSet existing = attrSets [set.Name] as XslAttributeSet;
412 // The latter set will have higher priority
413 if (existing != null) {
414 existing.Merge (set);
415 attrSets [set.Name] = existing;
418 attrSets [set.Name] = set;
421 VariableScope curVarScope;
423 public void PushScope ()
425 curVarScope = new VariableScope (curVarScope);
428 public VariableScope PopScope ()
430 curVarScope.giveHighTideToParent ();
431 VariableScope cur = curVarScope;
432 curVarScope = curVarScope.Parent;
436 public int AddVariable (XslLocalVariable v)
438 if (curVarScope == null)
439 throw new XsltCompileException ("Not initialized variable", null, Input);
441 return curVarScope.AddVariable (v);
444 public void AddSort (XPathExpression e, Sort s)
446 exprStore.AddSort (e, s);
448 public VariableScope CurrentVariableScope { get { return curVarScope; }}
451 #region Scope (version, {excluded, extension} namespaces)
452 [MonoTODO ("This will work, but is *very* slow")]
453 public bool IsExtensionNamespace (string nsUri)
455 if (nsUri == XsltNamespace) return true;
457 XPathNavigator nav = Input.Clone ();
458 XPathNavigator nsScope = nav.Clone ();
460 bool isXslt = nav.NamespaceURI == XsltNamespace;
461 nsScope.MoveTo (nav);
462 if (nav.MoveToFirstAttribute ()) {
464 if (nav.LocalName == "extension-element-prefixes" &&
465 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
468 foreach (string ns in nav.Value.Split (' '))
469 if (nsScope.GetNamespace (ns == "#default" ? "" : ns) == nsUri)
472 } while (nav.MoveToNextAttribute ());
475 } while (nav.MoveToParent ());
480 public Hashtable GetNamespacesToCopy ()
482 Hashtable ret = new Hashtable ();
484 XPathNavigator nav = Input.Clone ();
485 XPathNavigator nsScope = nav.Clone ();
487 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml)) {
489 if (nav.Value != XsltNamespace && !ret.Contains (nav.Name))
490 ret.Add (nav.Name, nav.Value);
491 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
496 bool isXslt = nav.NamespaceURI == XsltNamespace;
497 nsScope.MoveTo (nav);
499 if (nav.MoveToFirstAttribute ()) {
501 if ((nav.LocalName == "extension-element-prefixes" || nav.LocalName == "exclude-result-prefixes") &&
502 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
504 foreach (string ns in nav.Value.Split (' ')) {
505 string realNs = ns == "#default" ? "" : ns;
507 if ((string)ret [realNs] == nsScope.GetNamespace (realNs))
511 } while (nav.MoveToNextAttribute ());
514 } while (nav.MoveToParent ());
520 #region Decimal Format
521 Hashtable decimalFormats = new Hashtable ();
523 public void CompileDecimalFormat ()
525 QName nm = ParseQNameAttribute ("name");
527 if (nm.Name != String.Empty)
528 XmlConvert.VerifyNCName (nm.Name);
529 } catch (XmlException ex) {
530 throw new XsltCompileException ("Invalid qualified name", ex, Input);
532 XslDecimalFormat df = new XslDecimalFormat (this);
534 if (decimalFormats.Contains (nm))
535 ((XslDecimalFormat)decimalFormats [nm]).CheckSameAs (df);
537 decimalFormats [nm] = df;
540 #region Static XSLT context
541 Expression IStaticXsltContext.TryGetVariable (string nm)
543 if (curVarScope == null)
546 XslLocalVariable var = curVarScope.ResolveStatic (XslNameUtil.FromString (nm, Input));
551 return new XPathVariableBinding (var);
554 Expression IStaticXsltContext.TryGetFunction (QName name, FunctionArguments args)
556 string ns = LookupNamespace (name.Namespace);
557 if (ns == XslStylesheet.MSXsltNamespace && name.Name == "node-set")
558 return new MSXslNodeSet (args);
564 case "current": return new XsltCurrent (args);
565 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args);
566 case "element-available": return new XsltElementAvailable (args, this);
567 case "system-property": return new XsltSystemProperty (args, this);
568 case "function-available": return new XsltFunctionAvailable (args, this);
569 case "generate-id": return new XsltGenerateId (args);
570 case "format-number": return new XsltFormatNumber (args, this);
572 if (KeyCompilationMode)
573 throw new XsltCompileException ("Cannot use key() function inside key definition", null, this.Input);
574 return new XsltKey (args, this);
575 case "document": return new XsltDocument (args, this);
581 QName IStaticXsltContext.LookupQName (string s)
583 return XslNameUtil.FromString (s, Input);
586 public string LookupNamespace (string prefix)
588 if (prefix == "" || prefix == null)
591 XPathNavigator n = Input;
592 if (Input.NodeType == XPathNodeType.Attribute) {
597 return n.GetNamespace (prefix);
600 public void CompileOutput ()
602 XPathNavigator n = Input;
603 string uri = n.GetAttribute ("href", "");
604 XslOutput output = outputs [uri] as XslOutput;
605 if (output == null) {
606 output = new XslOutput (uri, stylesheetVersion);
607 outputs.Add (uri, output);
613 internal class VariableScope {
614 ArrayList variableNames;
616 VariableScope parent;
618 int highTide = 0; // this will be the size of the stack frame
620 internal void giveHighTideToParent ()
623 parent.highTide = System.Math.Max (VariableHighTide, parent.VariableHighTide);
626 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
628 public VariableScope (VariableScope parent)
630 this.parent = parent;
632 this.nextSlot = parent.nextSlot;
635 public VariableScope Parent { get { return parent; }}
637 public int AddVariable (XslLocalVariable v)
639 if (variables == null) {
640 variableNames = new ArrayList ();
641 variables = new Hashtable ();
643 variables [v.Name] = v;
644 int idx = variableNames.IndexOf (v.Name);
647 variableNames.Add (v.Name);
651 public XslLocalVariable ResolveStatic (QName name)
653 for (VariableScope s = this; s != null; s = s.Parent) {
654 if (s.variables == null) continue;
655 XslLocalVariable v = s.variables [name] as XslLocalVariable;
656 if (v != null) return v;
661 public XslLocalVariable Resolve (XslTransformProcessor p, QName name)
663 for (VariableScope s = this; s != null; s = s.Parent) {
664 if (s.variables == null) continue;
665 XslLocalVariable v = s.variables [name] as XslLocalVariable;
666 if (v != null && v.IsEvaluated (p))
674 internal class Sort {
676 XmlDataType dataType;
678 XmlCaseOrder caseOrder;
680 XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt;
681 XPathExpression expr;
683 public Sort (Compiler c)
685 expr = c.CompileExpression (c.GetAttribute ("select"));
687 expr = c.CompileExpression ("string(.)");
689 langAvt = c.ParseAvtAttribute ("lang");
690 dataTypeAvt = c.ParseAvtAttribute ("data-type");
691 orderAvt = c.ParseAvtAttribute ("order");
692 caseOrderAvt = c.ParseAvtAttribute ("case-order");
694 // Precalc whatever we can
695 lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt));
696 dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt));
697 order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt));
698 caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt));
702 string ParseLang (string value)
707 XmlDataType ParseDataType (string value)
712 return XmlDataType.Number;
716 return XmlDataType.Text;
720 XmlSortOrder ParseOrder (string value)
725 return XmlSortOrder.Descending;
729 return XmlSortOrder.Ascending;
733 XmlCaseOrder ParseCaseOrder (string value)
738 return XmlCaseOrder.UpperFirst;
740 return XmlCaseOrder.LowerFirst;
743 return XmlCaseOrder.None;
748 public void AddToExpr (XPathExpression e, XslTransformProcessor p)
752 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
753 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
754 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
755 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
760 internal class ExpressionStore {
761 Hashtable exprToSorts;
763 public void AddExpression (XPathExpression e, Compiler c)
767 public void AddPattern (Pattern p, Compiler c)
771 public void AddSort (XPathExpression e, Sort s)
773 if (exprToSorts == null)
774 exprToSorts = new Hashtable ();
776 if (exprToSorts.Contains (e))
777 ((ArrayList)exprToSorts [e]).Add (s);
779 ArrayList a = new ArrayList ();
785 public XPathExpression PrepForExecution (XPathExpression e, XslTransformProcessor p)
787 if (exprToSorts != null && exprToSorts.Contains (e))
789 XPathExpression expr = e.Clone ();
790 foreach (Sort s in (ArrayList)exprToSorts [e])
791 s.AddToExpr (expr,p);
797 public bool PatternMatches (Pattern p, XslTransformProcessor proc, XPathNavigator n)
799 return p.Matches (n, proc.XPathContext);
803 internal class XslNameUtil
805 public static QName [] FromListString (string names, XPathNavigator current)
807 string [] nameArray = names.Split (XmlChar.WhitespaceChars);
809 for (int i = 0; i < nameArray.Length; i++)
810 if (nameArray [i] != String.Empty)
813 XmlQualifiedName [] qnames = new XmlQualifiedName [idx];
816 for (int i = 0; i < nameArray.Length; i++)
817 if (nameArray [i] != String.Empty)
818 qnames [idx++] = FromString (nameArray [i], current, true);
823 public static QName FromString (string name, XPathNavigator current)
825 return FromString (name, current, false);
828 public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns)
830 if (current.NodeType == XPathNodeType.Attribute)
831 (current = current.Clone ()).MoveToParent ();
833 int colon = name.IndexOf (':');
835 return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon)));
837 return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : "");
839 throw new ArgumentException ("Invalid name: " + name);
842 public static QName FromString (string name, Hashtable nsDecls)
844 int colon = name.IndexOf (':');
846 return new QName (name.Substring (colon + 1), nsDecls [name.Substring (0, colon)] as string);
848 return new QName (name,
849 nsDecls.ContainsKey (String.Empty) ?
850 (string) nsDecls [String.Empty] : String.Empty);
852 throw new ArgumentException ("Invalid name: " + name);
855 public static QName FromString (string name, IStaticXsltContext ctx)
857 int colon = name.IndexOf (':');
859 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon)));
861 // Default namespace is not used for unprefixed names.
862 return new QName (name, "");
864 throw new ArgumentException ("Invalid name: " + name);
867 public static QName FromString (string name, XmlNamespaceManager ctx)
869 int colon = name.IndexOf (':');
871 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon), false));
873 // Default namespace is not used for unprefixed names.
874 return new QName (name, "");
876 throw new ArgumentException ("Invalid name: " + name);
879 public static string LocalNameOf (string name)
881 int colon = name.IndexOf (':');
883 return name.Substring (colon + 1);
887 throw new ArgumentException ("Invalid name: " + name);