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 XmlNamespaceManager nsMgr;
55 Hashtable decimalFormats;
56 MSXslScriptManager msScripts;
58 public CompiledStylesheet (XslStylesheet style, Hashtable globalVariables, Hashtable attrSets, XmlNamespaceManager nsMgr, Hashtable keys, Hashtable outputs, Hashtable decimalFormats,
59 MSXslScriptManager msScripts)
62 this.globalVariables = globalVariables;
63 this.attrSets = attrSets;
66 this.outputs = outputs;
67 this.decimalFormats = decimalFormats;
68 this.msScripts = msScripts;
70 public Hashtable Variables {get{return globalVariables;}}
71 public XslStylesheet Style { get { return style; }}
72 public XmlNamespaceManager NamespaceManager {get{return nsMgr;}}
73 public Hashtable Keys {get { return keys;}}
74 public Hashtable Outputs { get { return outputs; }}
76 public MSXslScriptManager ScriptManager {
77 get { return msScripts; }
81 public XslDecimalFormat LookupDecimalFormat (QName name)
83 XslDecimalFormat ret = decimalFormats [name] as XslDecimalFormat;
84 if (ret == null && name == QName.Empty)
85 return XslDecimalFormat.Default;
89 public XslGeneralVariable ResolveVariable (QName name)
91 return (XslGeneralVariable)globalVariables [name];
94 public ArrayList ResolveKey (QName name)
96 return (ArrayList) keys [name];
99 public XslAttributeSet ResolveAttributeSet (QName name)
101 return (XslAttributeSet)attrSets [name];
105 internal class Compiler : IStaticXsltContext {
106 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
108 ArrayList inputStack = new ArrayList ();
109 XPathNavigator currentInput;
111 Stack styleStack = new Stack ();
112 XslStylesheet currentStyle;
114 Hashtable keys = new Hashtable ();
115 Hashtable globalVariables = new Hashtable ();
116 Hashtable attrSets = new Hashtable ();
118 XmlNamespaceManager nsMgr = new XmlNamespaceManager (new NameTable ());
123 XslStylesheet rootStyle;
124 Hashtable outputs = new Hashtable ();
125 bool keyCompilationMode;
126 string stylesheetVersion;
127 XsltDebuggerWrapper debugger;
128 bool strictMSXslNodeSet;
130 public Compiler (object debugger, bool strictMSXslNodeSet)
132 if (debugger != null)
133 this.debugger = new XsltDebuggerWrapper (debugger);
134 this.strictMSXslNodeSet = strictMSXslNodeSet;
137 public XsltDebuggerWrapper Debugger {
138 get { return debugger; }
141 public void CheckExtraAttributes (string element, params string [] validNames)
143 if (Input.MoveToFirstAttribute ()) {
145 if (Input.NamespaceURI.Length > 0)
148 foreach (string s in validNames)
149 if (Input.LocalName == s) {
154 throw new XsltCompileException (String.Format ("Invalid attribute '{0}' on element '{1}'", Input.LocalName, element), null, Input);
155 } while (Input.MoveToNextAttribute ());
156 Input.MoveToParent ();
160 public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence)
162 this.xpathParser = new XPathParser (this);
163 this.patternParser = new XsltPatternParser (this);
166 this.res = new XmlUrlResolver ();
167 this.evidence = evidence;
169 // reject empty document.
170 if (nav.NodeType == XPathNodeType.Root && !nav.MoveToFirstChild ())
171 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, nav);
172 while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext();
174 stylesheetVersion = nav.GetAttribute ("version",
175 (nav.NamespaceURI != XsltNamespace) ?
176 XsltNamespace : String.Empty);
177 outputs [""] = new XslOutput ("", stylesheetVersion);
179 PushInputDocument (nav);
180 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml))
183 nsMgr.AddNamespace (nav.LocalName, nav.Value);
184 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
188 rootStyle = new XslStylesheet ();
189 rootStyle.Compile (this);
190 } catch (XsltCompileException) {
192 } catch (Exception x) {
193 throw new XsltCompileException ("XSLT compile error. " + x.Message, x, Input);
196 return new CompiledStylesheet (rootStyle, globalVariables, attrSets, nsMgr, keys, outputs, decimalFormats, msScripts);
199 MSXslScriptManager msScripts = new MSXslScriptManager ();
200 public MSXslScriptManager ScriptManager {
201 get { return msScripts; }
204 public bool KeyCompilationMode {
205 get { return keyCompilationMode; }
206 set { keyCompilationMode = value; }
209 internal Evidence Evidence {
210 get { return evidence; }
214 public XPathNavigator Input {
215 get { return currentInput; }
218 public XslStylesheet CurrentStylesheet {
219 get { return currentStyle; }
222 public void PushStylesheet (XslStylesheet style)
224 if (currentStyle != null) styleStack.Push (currentStyle);
225 currentStyle = style;
228 public void PopStylesheet ()
230 if (styleStack.Count == 0)
233 currentStyle = (XslStylesheet)styleStack.Pop ();
236 public void PushInputDocument (string url)
238 // todo: detect recursion
239 Uri baseUriObj = (Input.BaseURI == String.Empty) ? null : new Uri (Input.BaseURI);
240 Uri absUri = res.ResolveUri (baseUriObj, url);
241 string absUriString = absUri != null ? absUri.ToString () : String.Empty;
242 using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream)))
245 throw new XsltCompileException ("Can not access URI " + absUri.ToString (), null, Input);
246 XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUriString, s, nsMgr.NameTable));
247 vr.ValidationType = ValidationType.None;
248 XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator ();
250 n.MoveToFirstChild ();
252 if (n.NodeType == XPathNodeType.Element)
254 } while (n.MoveToNext ());
255 PushInputDocument (n);
259 public void PushInputDocument (XPathNavigator nav)
261 // Inclusion nest check
262 IXmlLineInfo li = currentInput as IXmlLineInfo;
263 bool hasLineInfo = (li != null && !li.HasLineInfo ());
264 for (int i = 0; i < inputStack.Count; i++) {
265 XPathNavigator cur = (XPathNavigator) inputStack [i];
266 if (cur.BaseURI == nav.BaseURI) {
267 throw new XsltCompileException (null,
268 currentInput.BaseURI,
269 hasLineInfo ? li.LineNumber : 0,
270 hasLineInfo ? li.LinePosition : 0);
273 if (currentInput != null)
274 inputStack.Add (currentInput);
278 public void PopInputDocument ()
280 int last = inputStack.Count - 1;
281 currentInput = (XPathNavigator) inputStack [last];
282 inputStack.RemoveAt (last);
285 public QName ParseQNameAttribute (string localName)
287 return ParseQNameAttribute (localName, String.Empty);
289 public QName ParseQNameAttribute (string localName, string ns)
291 return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input);
294 public QName [] ParseQNameListAttribute (string localName)
296 return ParseQNameListAttribute (localName, String.Empty);
299 public QName [] ParseQNameListAttribute (string localName, string ns)
301 string s = GetAttribute (localName, ns);
302 if (s == null) return null;
304 string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'});
306 for (int i=0; i<names.Length; i++)
307 if (names[i].Length != 0)
310 QName [] ret = new QName [count];
312 for (int i = 0, j=0; i < names.Length; i++)
313 if (names[i].Length != 0)
314 ret [j++] = XslNameUtil.FromString (names [i], Input);
319 public bool ParseYesNoAttribute (string localName, bool defaultVal)
321 return ParseYesNoAttribute (localName, String.Empty, defaultVal);
324 public bool ParseYesNoAttribute (string localName, string ns, bool defaultVal)
326 string s = GetAttribute (localName, ns);
329 case null: return defaultVal;
330 case "yes": return true;
331 case "no": return false;
333 throw new XsltCompileException ("Invalid value for " + localName, null, Input);
337 public string GetAttribute (string localName)
339 return GetAttribute (localName, String.Empty);
342 public string GetAttribute (string localName, string ns)
344 if (!Input.MoveToAttribute (localName, ns))
347 string ret = Input.Value;
348 Input.MoveToParent ();
351 public XslAvt ParseAvtAttribute (string localName)
353 return ParseAvtAttribute (localName, String.Empty);
355 public XslAvt ParseAvtAttribute (string localName, string ns)
357 return ParseAvt (GetAttribute (localName, ns));
360 public void AssertAttribute (string localName)
362 AssertAttribute (localName, "");
364 public void AssertAttribute (string localName, string ns)
366 if (Input.GetAttribute (localName, ns) == null)
367 throw new XsltCompileException ("Was expecting the " + localName + " attribute", null, Input);
370 public XslAvt ParseAvt (string s)
372 if (s == null) return null;
373 return new XslAvt (s, this);
379 public Pattern CompilePattern (string pattern, XPathNavigator loc)
381 if (pattern == null || pattern == "") return null;
382 Pattern p = Pattern.Compile (pattern, this);
384 throw new XsltCompileException (String.Format ("Invalid pattern '{0}'", pattern), null, loc);
389 internal XPathParser xpathParser;
390 internal XsltPatternParser patternParser;
391 internal CompiledExpression CompileExpression (string expression)
393 return CompileExpression (expression, false);
396 internal CompiledExpression CompileExpression (string expression, bool isKey)
398 if (expression == null || expression == "") return null;
400 Expression expr = xpathParser.Compile (expression);
402 expr = new ExprKeyContainer (expr);
403 CompiledExpression e = new CompiledExpression (expression, expr);
408 public XslOperation CompileTemplateContent ()
410 return CompileTemplateContent (XPathNodeType.All, false);
413 public XslOperation CompileTemplateContent (XPathNodeType parentType)
415 return CompileTemplateContent (parentType, false);
418 public XslOperation CompileTemplateContent (XPathNodeType parentType, bool xslForEach)
420 return new XslTemplateContent (this, parentType, xslForEach);
424 public void AddGlobalVariable (XslGlobalVariable var)
426 globalVariables [var.Name] = var;
429 public void AddKey (XslKey key)
431 if (keys [key.Name] == null)
432 keys [key.Name] = new ArrayList ();
433 ((ArrayList) keys [key.Name]).Add (key);
436 public void AddAttributeSet (XslAttributeSet set)
438 XslAttributeSet existing = attrSets [set.Name] as XslAttributeSet;
439 // The latter set will have higher priority
440 if (existing != null) {
441 existing.Merge (set);
442 attrSets [set.Name] = existing;
445 attrSets [set.Name] = set;
448 VariableScope curVarScope;
450 public void PushScope ()
452 curVarScope = new VariableScope (curVarScope);
455 public VariableScope PopScope ()
457 curVarScope.giveHighTideToParent ();
458 VariableScope cur = curVarScope;
459 curVarScope = curVarScope.Parent;
463 public int AddVariable (XslLocalVariable v)
465 if (curVarScope == null)
466 throw new XsltCompileException ("Not initialized variable", null, Input);
468 return curVarScope.AddVariable (v);
471 public VariableScope CurrentVariableScope { get { return curVarScope; }}
474 #region Scope (version, {excluded, extension} namespaces)
475 // FIXME: This will work, but is *very* slow
476 public bool IsExtensionNamespace (string nsUri)
478 if (nsUri == XsltNamespace) return true;
480 XPathNavigator nav = Input.Clone ();
481 XPathNavigator nsScope = nav.Clone ();
483 bool isXslt = nav.NamespaceURI == XsltNamespace;
484 nsScope.MoveTo (nav);
485 if (nav.MoveToFirstAttribute ()) {
487 if (nav.LocalName == "extension-element-prefixes" &&
488 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
491 foreach (string ns in nav.Value.Split (' '))
492 if (nsScope.GetNamespace (ns == "#default" ? "" : ns) == nsUri)
495 } while (nav.MoveToNextAttribute ());
498 } while (nav.MoveToParent ());
503 public Hashtable GetNamespacesToCopy ()
505 Hashtable ret = new Hashtable ();
507 XPathNavigator nav = Input.Clone ();
508 XPathNavigator nsScope = nav.Clone ();
510 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml)) {
512 if (nav.Value != XsltNamespace && !ret.Contains (nav.Name))
513 ret.Add (nav.Name, nav.Value);
514 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
519 bool isXslt = nav.NamespaceURI == XsltNamespace;
520 nsScope.MoveTo (nav);
522 if (nav.MoveToFirstAttribute ()) {
524 if ((nav.LocalName == "extension-element-prefixes" || nav.LocalName == "exclude-result-prefixes") &&
525 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
527 foreach (string ns in nav.Value.Split (' ')) {
528 string realNs = ns == "#default" ? "" : ns;
530 if ((string)ret [realNs] == nsScope.GetNamespace (realNs))
534 } while (nav.MoveToNextAttribute ());
537 } while (nav.MoveToParent ());
543 #region Decimal Format
544 Hashtable decimalFormats = new Hashtable ();
546 public void CompileDecimalFormat ()
548 QName nm = ParseQNameAttribute ("name");
550 if (nm.Name != String.Empty)
551 XmlConvert.VerifyNCName (nm.Name);
552 } catch (XmlException ex) {
553 throw new XsltCompileException ("Invalid qualified name", ex, Input);
555 XslDecimalFormat df = new XslDecimalFormat (this);
557 if (decimalFormats.Contains (nm))
558 ((XslDecimalFormat)decimalFormats [nm]).CheckSameAs (df);
560 decimalFormats [nm] = df;
563 #region Static XSLT context
564 Expression IStaticXsltContext.TryGetVariable (string nm)
566 if (curVarScope == null)
569 XslLocalVariable var = curVarScope.ResolveStatic (XslNameUtil.FromString (nm, Input));
574 return new XPathVariableBinding (var);
577 Expression IStaticXsltContext.TryGetFunction (QName name, FunctionArguments args)
579 string ns = LookupNamespace (name.Namespace);
580 if (ns == XslStylesheet.MSXsltNamespace && name.Name == "node-set")
581 return new MSXslNodeSet (strictMSXslNodeSet, args);
587 case "current": return new XsltCurrent (args);
588 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args);
589 case "element-available": return new XsltElementAvailable (args, this);
590 case "system-property": return new XsltSystemProperty (args, this);
591 case "function-available": return new XsltFunctionAvailable (args, this);
592 case "generate-id": return new XsltGenerateId (args);
593 case "format-number": return new XsltFormatNumber (args, this);
595 if (KeyCompilationMode)
596 throw new XsltCompileException ("Cannot use key() function inside key definition", null, this.Input);
597 return new XsltKey (args, this);
598 case "document": return new XsltDocument (args, this);
604 QName IStaticXsltContext.LookupQName (string s)
606 return XslNameUtil.FromString (s, Input);
609 public string LookupNamespace (string prefix)
611 if (prefix == "" || prefix == null)
614 XPathNavigator n = Input;
615 if (Input.NodeType == XPathNodeType.Attribute) {
620 return n.GetNamespace (prefix);
623 public void CompileOutput ()
625 XPathNavigator n = Input;
626 string uri = n.GetAttribute ("href", "");
627 XslOutput output = outputs [uri] as XslOutput;
628 if (output == null) {
629 output = new XslOutput (uri, stylesheetVersion);
630 outputs.Add (uri, output);
636 internal class VariableScope {
637 ArrayList variableNames;
639 VariableScope parent;
641 int highTide = 0; // this will be the size of the stack frame
643 internal void giveHighTideToParent ()
646 parent.highTide = System.Math.Max (VariableHighTide, parent.VariableHighTide);
649 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
651 public VariableScope (VariableScope parent)
653 this.parent = parent;
655 this.nextSlot = parent.nextSlot;
658 public VariableScope Parent { get { return parent; }}
660 public int AddVariable (XslLocalVariable v)
662 if (variables == null) {
663 variableNames = new ArrayList ();
664 variables = new Hashtable ();
666 variables [v.Name] = v;
667 int idx = variableNames.IndexOf (v.Name);
670 variableNames.Add (v.Name);
674 public XslLocalVariable ResolveStatic (QName name)
676 for (VariableScope s = this; s != null; s = s.Parent) {
677 if (s.variables == null) continue;
678 XslLocalVariable v = s.variables [name] as XslLocalVariable;
679 if (v != null) return v;
684 public XslLocalVariable Resolve (XslTransformProcessor p, QName name)
686 for (VariableScope s = this; s != null; s = s.Parent) {
687 if (s.variables == null) continue;
688 XslLocalVariable v = s.variables [name] as XslLocalVariable;
689 if (v != null && v.IsEvaluated (p))
697 internal class Sort {
699 XmlDataType dataType;
701 XmlCaseOrder caseOrder;
703 XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt;
704 XPathExpression expr;
706 public Sort (Compiler c)
708 c.CheckExtraAttributes ("sort", "select", "lang", "data-type", "order", "case-order");
710 expr = c.CompileExpression (c.GetAttribute ("select"));
712 expr = c.CompileExpression ("string(.)");
714 langAvt = c.ParseAvtAttribute ("lang");
715 dataTypeAvt = c.ParseAvtAttribute ("data-type");
716 orderAvt = c.ParseAvtAttribute ("order");
717 caseOrderAvt = c.ParseAvtAttribute ("case-order");
719 // Precalc whatever we can
720 lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt));
721 dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt));
722 order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt));
723 caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt));
726 public bool IsContextDependent {
727 get { return orderAvt != null || caseOrderAvt != null || langAvt != null || dataTypeAvt != null; }
730 string ParseLang (string value)
735 XmlDataType ParseDataType (string value)
740 return XmlDataType.Number;
744 return XmlDataType.Text;
748 XmlSortOrder ParseOrder (string value)
753 return XmlSortOrder.Descending;
757 return XmlSortOrder.Ascending;
761 XmlCaseOrder ParseCaseOrder (string value)
766 return XmlCaseOrder.UpperFirst;
768 return XmlCaseOrder.LowerFirst;
771 return XmlCaseOrder.None;
776 public void AddToExpr (XPathExpression e, XslTransformProcessor p)
780 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
781 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
782 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
783 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
787 public XPathSorter ToXPathSorter (XslTransformProcessor p)
789 return new XPathSorter (expr,
790 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
791 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
792 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
793 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
798 internal class XslNameUtil
800 public static QName [] FromListString (string names, XPathNavigator current)
802 string [] nameArray = names.Split (XmlChar.WhitespaceChars);
804 for (int i = 0; i < nameArray.Length; i++)
805 if (nameArray [i] != String.Empty)
808 XmlQualifiedName [] qnames = new XmlQualifiedName [idx];
811 for (int i = 0; i < nameArray.Length; i++)
812 if (nameArray [i] != String.Empty)
813 qnames [idx++] = FromString (nameArray [i], current, true);
818 public static QName FromString (string name, XPathNavigator current)
820 return FromString (name, current, false);
823 public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns)
825 if (current.NodeType == XPathNodeType.Attribute)
826 (current = current.Clone ()).MoveToParent ();
828 int colon = name.IndexOf (':');
830 return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon)));
832 return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : "");
834 throw new ArgumentException ("Invalid name: " + name);
837 public static QName FromString (string name, Hashtable nsDecls)
839 int colon = name.IndexOf (':');
841 return new QName (name.Substring (colon + 1), nsDecls [name.Substring (0, colon)] as string);
843 return new QName (name,
844 nsDecls.ContainsKey (String.Empty) ?
845 (string) nsDecls [String.Empty] : String.Empty);
847 throw new ArgumentException ("Invalid name: " + name);
850 public static QName FromString (string name, IStaticXsltContext ctx)
852 int colon = name.IndexOf (':');
854 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon)));
856 // Default namespace is not used for unprefixed names.
857 return new QName (name, "");
859 throw new ArgumentException ("Invalid name: " + name);
862 public static QName FromString (string name, XmlNamespaceManager ctx)
864 int colon = name.IndexOf (':');
866 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon), false));
868 // Default namespace is not used for unprefixed names.
869 return new QName (name, "");
871 throw new ArgumentException ("Invalid name: " + name);
874 public static string LocalNameOf (string name)
876 int colon = name.IndexOf (':');
878 return name.Substring (colon + 1);
882 throw new ArgumentException ("Invalid name: " + name);