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 XslKey ResolveKey (QName name)
96 return (XslKey) 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;
128 public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence)
130 this.xpathParser = new XPathParser (this);
131 this.patternParser = new XsltPatternParser (this);
134 this.res = new XmlUrlResolver ();
135 this.evidence = evidence;
137 // reject empty document.
138 if (nav.NodeType == XPathNodeType.Root && !nav.MoveToFirstChild ())
139 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, nav);
140 while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext();
142 stylesheetVersion = nav.GetAttribute ("version",
143 (nav.NamespaceURI != XsltNamespace) ?
144 XsltNamespace : String.Empty);
145 outputs [""] = new XslOutput ("", stylesheetVersion);
147 PushInputDocument (nav);
148 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml))
151 nsMgr.AddNamespace (nav.LocalName, nav.Value);
152 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
156 rootStyle = new XslStylesheet ();
157 rootStyle.Compile (this);
158 } catch (XsltCompileException) {
160 } catch (Exception x) {
161 throw new XsltCompileException ("XSLT compile error. " + x.Message, x, Input);
164 return new CompiledStylesheet (rootStyle, globalVariables, attrSets, nsMgr, keys, outputs, decimalFormats, msScripts);
167 MSXslScriptManager msScripts = new MSXslScriptManager ();
168 public MSXslScriptManager ScriptManager {
169 get { return msScripts; }
172 public bool KeyCompilationMode {
173 get { return keyCompilationMode; }
174 set { keyCompilationMode = value; }
177 internal Evidence Evidence {
178 get { return evidence; }
182 public XPathNavigator Input {
183 get { return currentInput; }
186 public XslStylesheet CurrentStylesheet {
187 get { return currentStyle; }
190 public void PushStylesheet (XslStylesheet style)
192 if (currentStyle != null) styleStack.Push (currentStyle);
193 currentStyle = style;
196 public void PopStylesheet ()
198 if (styleStack.Count == 0)
201 currentStyle = (XslStylesheet)styleStack.Pop ();
204 public void PushInputDocument (string url)
206 // todo: detect recursion
207 Uri baseUriObj = (Input.BaseURI == String.Empty) ? null : new Uri (Input.BaseURI);
208 Uri absUri = res.ResolveUri (baseUriObj, url);
209 string absUriString = absUri != null ? absUri.ToString () : String.Empty;
210 using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream)))
213 throw new XsltCompileException ("Can not access URI " + absUri.ToString (), null, Input);
214 XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUriString, s, nsMgr.NameTable));
215 vr.ValidationType = ValidationType.None;
216 XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator ();
218 n.MoveToFirstChild ();
220 if (n.NodeType == XPathNodeType.Element)
222 } while (n.MoveToNext ());
223 PushInputDocument (n);
227 public void PushInputDocument (XPathNavigator nav)
229 // Inclusion nest check
230 IXmlLineInfo li = currentInput as IXmlLineInfo;
231 bool hasLineInfo = (li != null && !li.HasLineInfo ());
232 for (int i = 0; i < inputStack.Count; i++) {
233 XPathNavigator cur = (XPathNavigator) inputStack [i];
234 if (cur.BaseURI == nav.BaseURI) {
235 throw new XsltCompileException (null,
236 currentInput.BaseURI,
237 hasLineInfo ? li.LineNumber : 0,
238 hasLineInfo ? li.LinePosition : 0);
241 if (currentInput != null)
242 inputStack.Add (currentInput);
246 public void PopInputDocument ()
248 int last = inputStack.Count - 1;
249 currentInput = (XPathNavigator) inputStack [last];
250 inputStack.RemoveAt (last);
253 public QName ParseQNameAttribute (string localName)
255 return ParseQNameAttribute (localName, String.Empty);
257 public QName ParseQNameAttribute (string localName, string ns)
259 return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input);
262 public QName [] ParseQNameListAttribute (string localName)
264 return ParseQNameListAttribute (localName, String.Empty);
267 public QName [] ParseQNameListAttribute (string localName, string ns)
269 string s = GetAttribute (localName, ns);
270 if (s == null) return null;
272 string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'});
274 for (int i=0; i<names.Length; i++)
275 if (names[i].Length != 0)
278 QName [] ret = new QName [count];
280 for (int i = 0, j=0; i < names.Length; i++)
281 if (names[i].Length != 0)
282 ret [j++] = XslNameUtil.FromString (names [i], Input);
287 public bool ParseYesNoAttribute (string localName, bool defaultVal)
289 return ParseYesNoAttribute (localName, String.Empty, defaultVal);
292 public bool ParseYesNoAttribute (string localName, string ns, bool defaultVal)
294 string s = GetAttribute (localName, ns);
297 case null: return defaultVal;
298 case "yes": return true;
299 case "no": return false;
301 throw new XsltCompileException ("Invalid value for " + localName, null, Input);
305 public string GetAttribute (string localName)
307 return GetAttribute (localName, String.Empty);
310 public string GetAttribute (string localName, string ns)
312 if (!Input.MoveToAttribute (localName, ns))
315 string ret = Input.Value;
316 Input.MoveToParent ();
319 public XslAvt ParseAvtAttribute (string localName)
321 return ParseAvtAttribute (localName, String.Empty);
323 public XslAvt ParseAvtAttribute (string localName, string ns)
325 return ParseAvt (GetAttribute (localName, ns));
328 public void AssertAttribute (string localName)
330 AssertAttribute (localName, "");
332 public void AssertAttribute (string localName, string ns)
334 if (Input.GetAttribute (localName, ns) == null)
335 throw new XsltCompileException ("Was expecting the " + localName + " attribute", null, Input);
338 public XslAvt ParseAvt (string s)
340 if (s == null) return null;
341 return new XslAvt (s, this);
347 public Pattern CompilePattern (string pattern, XPathNavigator loc)
349 if (pattern == null || pattern == "") return null;
350 Pattern p = Pattern.Compile (pattern, this);
352 throw new XsltCompileException (String.Format ("Invalid pattern '{0}'", pattern), null, loc);
357 internal XPathParser xpathParser;
358 internal XsltPatternParser patternParser;
359 internal CompiledExpression CompileExpression (string expression)
361 return CompileExpression (expression, false);
364 internal CompiledExpression CompileExpression (string expression, bool isKey)
366 if (expression == null || expression == "") return null;
368 Expression expr = xpathParser.Compile (expression);
370 expr = new ExprKeyContainer (expr);
371 CompiledExpression e = new CompiledExpression (expression, expr);
376 public XslOperation CompileTemplateContent ()
378 return CompileTemplateContent (XPathNodeType.All, false);
381 public XslOperation CompileTemplateContent (XPathNodeType parentType)
383 return CompileTemplateContent (parentType, false);
386 public XslOperation CompileTemplateContent (XPathNodeType parentType, bool xslForEach)
388 return new XslTemplateContent (this, parentType, xslForEach);
392 public void AddGlobalVariable (XslGlobalVariable var)
394 globalVariables [var.Name] = var;
397 public void AddKey (XslKey key)
399 keys [key.Name] = key;
402 public void AddAttributeSet (XslAttributeSet set)
404 XslAttributeSet existing = attrSets [set.Name] as XslAttributeSet;
405 // The latter set will have higher priority
406 if (existing != null) {
407 existing.Merge (set);
408 attrSets [set.Name] = existing;
411 attrSets [set.Name] = set;
414 VariableScope curVarScope;
416 public void PushScope ()
418 curVarScope = new VariableScope (curVarScope);
421 public VariableScope PopScope ()
423 curVarScope.giveHighTideToParent ();
424 VariableScope cur = curVarScope;
425 curVarScope = curVarScope.Parent;
429 public int AddVariable (XslLocalVariable v)
431 if (curVarScope == null)
432 throw new XsltCompileException ("Not initialized variable", null, Input);
434 return curVarScope.AddVariable (v);
437 public VariableScope CurrentVariableScope { get { return curVarScope; }}
440 #region Scope (version, {excluded, extension} namespaces)
441 [MonoTODO ("This will work, but is *very* slow")]
442 public bool IsExtensionNamespace (string nsUri)
444 if (nsUri == XsltNamespace) return true;
446 XPathNavigator nav = Input.Clone ();
447 XPathNavigator nsScope = nav.Clone ();
449 bool isXslt = nav.NamespaceURI == XsltNamespace;
450 nsScope.MoveTo (nav);
451 if (nav.MoveToFirstAttribute ()) {
453 if (nav.LocalName == "extension-element-prefixes" &&
454 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
457 foreach (string ns in nav.Value.Split (' '))
458 if (nsScope.GetNamespace (ns == "#default" ? "" : ns) == nsUri)
461 } while (nav.MoveToNextAttribute ());
464 } while (nav.MoveToParent ());
469 public Hashtable GetNamespacesToCopy ()
471 Hashtable ret = new Hashtable ();
473 XPathNavigator nav = Input.Clone ();
474 XPathNavigator nsScope = nav.Clone ();
476 if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml)) {
478 if (nav.Value != XsltNamespace && !ret.Contains (nav.Name))
479 ret.Add (nav.Name, nav.Value);
480 } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml));
485 bool isXslt = nav.NamespaceURI == XsltNamespace;
486 nsScope.MoveTo (nav);
488 if (nav.MoveToFirstAttribute ()) {
490 if ((nav.LocalName == "extension-element-prefixes" || nav.LocalName == "exclude-result-prefixes") &&
491 nav.NamespaceURI == (isXslt ? String.Empty : XsltNamespace))
493 foreach (string ns in nav.Value.Split (' ')) {
494 string realNs = ns == "#default" ? "" : ns;
496 if ((string)ret [realNs] == nsScope.GetNamespace (realNs))
500 } while (nav.MoveToNextAttribute ());
503 } while (nav.MoveToParent ());
509 #region Decimal Format
510 Hashtable decimalFormats = new Hashtable ();
512 public void CompileDecimalFormat ()
514 QName nm = ParseQNameAttribute ("name");
516 if (nm.Name != String.Empty)
517 XmlConvert.VerifyNCName (nm.Name);
518 } catch (XmlException ex) {
519 throw new XsltCompileException ("Invalid qualified name", ex, Input);
521 XslDecimalFormat df = new XslDecimalFormat (this);
523 if (decimalFormats.Contains (nm))
524 ((XslDecimalFormat)decimalFormats [nm]).CheckSameAs (df);
526 decimalFormats [nm] = df;
529 #region Static XSLT context
530 Expression IStaticXsltContext.TryGetVariable (string nm)
532 if (curVarScope == null)
535 XslLocalVariable var = curVarScope.ResolveStatic (XslNameUtil.FromString (nm, Input));
540 return new XPathVariableBinding (var);
543 Expression IStaticXsltContext.TryGetFunction (QName name, FunctionArguments args)
545 string ns = LookupNamespace (name.Namespace);
546 if (ns == XslStylesheet.MSXsltNamespace && name.Name == "node-set")
547 return new MSXslNodeSet (args);
553 case "current": return new XsltCurrent (args);
554 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args);
555 case "element-available": return new XsltElementAvailable (args, this);
556 case "system-property": return new XsltSystemProperty (args, this);
557 case "function-available": return new XsltFunctionAvailable (args, this);
558 case "generate-id": return new XsltGenerateId (args);
559 case "format-number": return new XsltFormatNumber (args, this);
561 if (KeyCompilationMode)
562 throw new XsltCompileException ("Cannot use key() function inside key definition", null, this.Input);
563 return new XsltKey (args, this);
564 case "document": return new XsltDocument (args, this);
570 QName IStaticXsltContext.LookupQName (string s)
572 return XslNameUtil.FromString (s, Input);
575 public string LookupNamespace (string prefix)
577 if (prefix == "" || prefix == null)
580 XPathNavigator n = Input;
581 if (Input.NodeType == XPathNodeType.Attribute) {
586 return n.GetNamespace (prefix);
589 public void CompileOutput ()
591 XPathNavigator n = Input;
592 string uri = n.GetAttribute ("href", "");
593 XslOutput output = outputs [uri] as XslOutput;
594 if (output == null) {
595 output = new XslOutput (uri, stylesheetVersion);
596 outputs.Add (uri, output);
602 internal class VariableScope {
603 ArrayList variableNames;
605 VariableScope parent;
607 int highTide = 0; // this will be the size of the stack frame
609 internal void giveHighTideToParent ()
612 parent.highTide = System.Math.Max (VariableHighTide, parent.VariableHighTide);
615 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
617 public VariableScope (VariableScope parent)
619 this.parent = parent;
621 this.nextSlot = parent.nextSlot;
624 public VariableScope Parent { get { return parent; }}
626 public int AddVariable (XslLocalVariable v)
628 if (variables == null) {
629 variableNames = new ArrayList ();
630 variables = new Hashtable ();
632 variables [v.Name] = v;
633 int idx = variableNames.IndexOf (v.Name);
636 variableNames.Add (v.Name);
640 public XslLocalVariable ResolveStatic (QName name)
642 for (VariableScope s = this; s != null; s = s.Parent) {
643 if (s.variables == null) continue;
644 XslLocalVariable v = s.variables [name] as XslLocalVariable;
645 if (v != null) return v;
650 public XslLocalVariable Resolve (XslTransformProcessor p, QName name)
652 for (VariableScope s = this; s != null; s = s.Parent) {
653 if (s.variables == null) continue;
654 XslLocalVariable v = s.variables [name] as XslLocalVariable;
655 if (v != null && v.IsEvaluated (p))
663 internal class Sort {
665 XmlDataType dataType;
667 XmlCaseOrder caseOrder;
669 XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt;
670 XPathExpression expr;
672 public Sort (Compiler c)
674 expr = c.CompileExpression (c.GetAttribute ("select"));
676 expr = c.CompileExpression ("string(.)");
678 langAvt = c.ParseAvtAttribute ("lang");
679 dataTypeAvt = c.ParseAvtAttribute ("data-type");
680 orderAvt = c.ParseAvtAttribute ("order");
681 caseOrderAvt = c.ParseAvtAttribute ("case-order");
683 // Precalc whatever we can
684 lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt));
685 dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt));
686 order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt));
687 caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt));
690 public bool IsContextDependent {
691 get { return orderAvt != null || caseOrderAvt != null || langAvt != null || dataTypeAvt != null; }
694 string ParseLang (string value)
699 XmlDataType ParseDataType (string value)
704 return XmlDataType.Number;
708 return XmlDataType.Text;
712 XmlSortOrder ParseOrder (string value)
717 return XmlSortOrder.Descending;
721 return XmlSortOrder.Ascending;
725 XmlCaseOrder ParseCaseOrder (string value)
730 return XmlCaseOrder.UpperFirst;
732 return XmlCaseOrder.LowerFirst;
735 return XmlCaseOrder.None;
740 public void AddToExpr (XPathExpression e, XslTransformProcessor p)
744 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
745 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
746 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
747 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
751 public XPathSorter ToXPathSorter (XslTransformProcessor p)
753 return new XPathSorter (expr,
754 orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)),
755 caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)),
756 langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)),
757 dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p))
762 internal class XslNameUtil
764 public static QName [] FromListString (string names, XPathNavigator current)
766 string [] nameArray = names.Split (XmlChar.WhitespaceChars);
768 for (int i = 0; i < nameArray.Length; i++)
769 if (nameArray [i] != String.Empty)
772 XmlQualifiedName [] qnames = new XmlQualifiedName [idx];
775 for (int i = 0; i < nameArray.Length; i++)
776 if (nameArray [i] != String.Empty)
777 qnames [idx++] = FromString (nameArray [i], current, true);
782 public static QName FromString (string name, XPathNavigator current)
784 return FromString (name, current, false);
787 public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns)
789 if (current.NodeType == XPathNodeType.Attribute)
790 (current = current.Clone ()).MoveToParent ();
792 int colon = name.IndexOf (':');
794 return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon)));
796 return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : "");
798 throw new ArgumentException ("Invalid name: " + name);
801 public static QName FromString (string name, Hashtable nsDecls)
803 int colon = name.IndexOf (':');
805 return new QName (name.Substring (colon + 1), nsDecls [name.Substring (0, colon)] as string);
807 return new QName (name,
808 nsDecls.ContainsKey (String.Empty) ?
809 (string) nsDecls [String.Empty] : String.Empty);
811 throw new ArgumentException ("Invalid name: " + name);
814 public static QName FromString (string name, IStaticXsltContext ctx)
816 int colon = name.IndexOf (':');
818 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon)));
820 // Default namespace is not used for unprefixed names.
821 return new QName (name, "");
823 throw new ArgumentException ("Invalid name: " + name);
826 public static QName FromString (string name, XmlNamespaceManager ctx)
828 int colon = name.IndexOf (':');
830 return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon), false));
832 // Default namespace is not used for unprefixed names.
833 return new QName (name, "");
835 throw new ArgumentException ("Invalid name: " + name);
838 public static string LocalNameOf (string name)
840 int colon = name.IndexOf (':');
842 return name.Substring (colon + 1);
846 throw new ArgumentException ("Invalid name: " + name);