// // Compiler.cs // // Authors: // Ben Maurer (bmaurer@users.sourceforge.net) // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp) // // (C) 2003 Ben Maurer // (C) 2003 Atsushi Enomoto // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Security.Policy; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; using System.IO; using Mono.Xml.Xsl.Operations; using Mono.Xml.XPath; using QName = System.Xml.XmlQualifiedName; namespace Mono.Xml.Xsl { internal class CompiledStylesheet { XslStylesheet style; Hashtable globalVariables; Hashtable attrSets; XmlNamespaceManager nsMgr; Hashtable keys; Hashtable outputs; Hashtable decimalFormats; MSXslScriptManager msScripts; public CompiledStylesheet (XslStylesheet style, Hashtable globalVariables, Hashtable attrSets, XmlNamespaceManager nsMgr, Hashtable keys, Hashtable outputs, Hashtable decimalFormats, MSXslScriptManager msScripts) { this.style = style; this.globalVariables = globalVariables; this.attrSets = attrSets; this.nsMgr = nsMgr; this.keys = keys; this.outputs = outputs; this.decimalFormats = decimalFormats; this.msScripts = msScripts; } public Hashtable Variables {get{return globalVariables;}} public XslStylesheet Style { get { return style; }} public XmlNamespaceManager NamespaceManager {get{return nsMgr;}} public Hashtable Keys {get { return keys;}} public Hashtable Outputs { get { return outputs; }} public MSXslScriptManager ScriptManager { get { return msScripts; } } public XslDecimalFormat LookupDecimalFormat (QName name) { XslDecimalFormat ret = decimalFormats [name] as XslDecimalFormat; if (ret == null && name == QName.Empty) return XslDecimalFormat.Default; return ret; } public XslGeneralVariable ResolveVariable (QName name) { return (XslGeneralVariable)globalVariables [name]; } public ArrayList ResolveKey (QName name) { return (ArrayList) keys [name]; } public XslAttributeSet ResolveAttributeSet (QName name) { return (XslAttributeSet)attrSets [name]; } } internal class Compiler : IStaticXsltContext { public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform"; ArrayList inputStack = new ArrayList (); XPathNavigator currentInput; Stack styleStack = new Stack (); XslStylesheet currentStyle; Hashtable keys = new Hashtable (); Hashtable globalVariables = new Hashtable (); Hashtable attrSets = new Hashtable (); XmlNamespaceManager nsMgr = new XmlNamespaceManager (new NameTable ()); XmlResolver res; Evidence evidence; XslStylesheet rootStyle; Hashtable outputs = new Hashtable (); bool keyCompilationMode; string stylesheetVersion; XsltDebuggerWrapper debugger; public Compiler (object debugger) { if (debugger != null) this.debugger = new XsltDebuggerWrapper (debugger); } public XsltDebuggerWrapper Debugger { get { return debugger; } } public void CheckExtraAttributes (string element, params string [] validNames) { if (Input.MoveToFirstAttribute ()) { do { if (Input.NamespaceURI.Length > 0) continue; bool valid = false; foreach (string s in validNames) if (Input.LocalName == s) { valid = true; continue; } if (!valid) throw new XsltCompileException (String.Format ("Invalid attribute '{0}' on element '{1}'", Input.LocalName, element), null, Input); } while (Input.MoveToNextAttribute ()); Input.MoveToParent (); } } public CompiledStylesheet Compile (XPathNavigator nav, XmlResolver res, Evidence evidence) { this.xpathParser = new XPathParser (this); this.patternParser = new XsltPatternParser (this); this.res = res; if (res == null) this.res = new XmlUrlResolver (); this.evidence = evidence; // reject empty document. if (nav.NodeType == XPathNodeType.Root && !nav.MoveToFirstChild ()) throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, nav); while (nav.NodeType != XPathNodeType.Element) nav.MoveToNext(); stylesheetVersion = nav.GetAttribute ("version", (nav.NamespaceURI != XsltNamespace) ? XsltNamespace : String.Empty); outputs [""] = new XslOutput ("", stylesheetVersion); PushInputDocument (nav); if (nav.MoveToFirstNamespace (XPathNamespaceScope.ExcludeXml)) { do { nsMgr.AddNamespace (nav.LocalName, nav.Value); } while (nav.MoveToNextNamespace (XPathNamespaceScope.ExcludeXml)); nav.MoveToParent (); } try { rootStyle = new XslStylesheet (); rootStyle.Compile (this); } catch (XsltCompileException) { throw; } catch (Exception x) { throw new XsltCompileException ("XSLT compile error. " + x.Message, x, Input); } return new CompiledStylesheet (rootStyle, globalVariables, attrSets, nsMgr, keys, outputs, decimalFormats, msScripts); } MSXslScriptManager msScripts = new MSXslScriptManager (); public MSXslScriptManager ScriptManager { get { return msScripts; } } public bool KeyCompilationMode { get { return keyCompilationMode; } set { keyCompilationMode = value; } } internal Evidence Evidence { get { return evidence; } } #region Input public XPathNavigator Input { get { return currentInput; } } public XslStylesheet CurrentStylesheet { get { return currentStyle; } } public void PushStylesheet (XslStylesheet style) { if (currentStyle != null) styleStack.Push (currentStyle); currentStyle = style; } public void PopStylesheet () { if (styleStack.Count == 0) currentStyle = null; else currentStyle = (XslStylesheet)styleStack.Pop (); } public void PushInputDocument (string url) { // todo: detect recursion Uri baseUriObj = (Input.BaseURI == String.Empty) ? null : new Uri (Input.BaseURI); Uri absUri = res.ResolveUri (baseUriObj, url); string absUriString = absUri != null ? absUri.ToString () : String.Empty; using (Stream s = (Stream)res.GetEntity (absUri, null, typeof(Stream))) { if (s == null) throw new XsltCompileException ("Can not access URI " + absUri.ToString (), null, Input); XmlValidatingReader vr = new XmlValidatingReader (new XmlTextReader (absUriString, s, nsMgr.NameTable)); vr.ValidationType = ValidationType.None; XPathNavigator n = new XPathDocument (vr, XmlSpace.Preserve).CreateNavigator (); vr.Close (); n.MoveToFirstChild (); do { if (n.NodeType == XPathNodeType.Element) break; } while (n.MoveToNext ()); PushInputDocument (n); } } public void PushInputDocument (XPathNavigator nav) { // Inclusion nest check IXmlLineInfo li = currentInput as IXmlLineInfo; bool hasLineInfo = (li != null && !li.HasLineInfo ()); for (int i = 0; i < inputStack.Count; i++) { XPathNavigator cur = (XPathNavigator) inputStack [i]; if (cur.BaseURI == nav.BaseURI) { throw new XsltCompileException (null, currentInput.BaseURI, hasLineInfo ? li.LineNumber : 0, hasLineInfo ? li.LinePosition : 0); } } if (currentInput != null) inputStack.Add (currentInput); currentInput = nav; } public void PopInputDocument () { int last = inputStack.Count - 1; currentInput = (XPathNavigator) inputStack [last]; inputStack.RemoveAt (last); } public QName ParseQNameAttribute (string localName) { return ParseQNameAttribute (localName, String.Empty); } public QName ParseQNameAttribute (string localName, string ns) { return XslNameUtil.FromString (Input.GetAttribute (localName, ns), Input); } public QName [] ParseQNameListAttribute (string localName) { return ParseQNameListAttribute (localName, String.Empty); } public QName [] ParseQNameListAttribute (string localName, string ns) { string s = GetAttribute (localName, ns); if (s == null) return null; string [] names = s.Split (new char [] {' ', '\r', '\n', '\t'}); int count=0; for (int i=0; i= 0) return idx; variableNames.Add (v.Name); return nextSlot++; } public XslLocalVariable ResolveStatic (QName name) { for (VariableScope s = this; s != null; s = s.Parent) { if (s.variables == null) continue; XslLocalVariable v = s.variables [name] as XslLocalVariable; if (v != null) return v; } return null; } public XslLocalVariable Resolve (XslTransformProcessor p, QName name) { for (VariableScope s = this; s != null; s = s.Parent) { if (s.variables == null) continue; XslLocalVariable v = s.variables [name] as XslLocalVariable; if (v != null && v.IsEvaluated (p)) return v; } return null; } } internal class Sort { string lang; XmlDataType dataType; XmlSortOrder order; XmlCaseOrder caseOrder; XslAvt langAvt, dataTypeAvt, orderAvt, caseOrderAvt; XPathExpression expr; public Sort (Compiler c) { c.CheckExtraAttributes ("sort", "select", "lang", "data-type", "order", "case-order"); expr = c.CompileExpression (c.GetAttribute ("select")); if (expr == null) expr = c.CompileExpression ("string(.)"); langAvt = c.ParseAvtAttribute ("lang"); dataTypeAvt = c.ParseAvtAttribute ("data-type"); orderAvt = c.ParseAvtAttribute ("order"); caseOrderAvt = c.ParseAvtAttribute ("case-order"); // Precalc whatever we can lang = ParseLang (XslAvt.AttemptPreCalc (ref langAvt)); dataType = ParseDataType (XslAvt.AttemptPreCalc (ref dataTypeAvt)); order = ParseOrder (XslAvt.AttemptPreCalc (ref orderAvt)); caseOrder = ParseCaseOrder (XslAvt.AttemptPreCalc (ref caseOrderAvt)); } public bool IsContextDependent { get { return orderAvt != null || caseOrderAvt != null || langAvt != null || dataTypeAvt != null; } } string ParseLang (string value) { return value; } XmlDataType ParseDataType (string value) { switch (value) { case "number": return XmlDataType.Number; case "text": case null: default: return XmlDataType.Text; } } XmlSortOrder ParseOrder (string value) { switch (value) { case "descending": return XmlSortOrder.Descending; case "ascending": case null: default: return XmlSortOrder.Ascending; } } XmlCaseOrder ParseCaseOrder (string value) { switch (value) { case "upper-first": return XmlCaseOrder.UpperFirst; case "lower-first": return XmlCaseOrder.LowerFirst; case null: default: return XmlCaseOrder.None; } } public void AddToExpr (XPathExpression e, XslTransformProcessor p) { e.AddSort ( expr, orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)), caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)), langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)), dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p)) ); } public XPathSorter ToXPathSorter (XslTransformProcessor p) { return new XPathSorter (expr, orderAvt == null ? order : ParseOrder (orderAvt.Evaluate (p)), caseOrderAvt == null ? caseOrder: ParseCaseOrder (caseOrderAvt.Evaluate (p)), langAvt == null ? lang : ParseLang (langAvt.Evaluate (p)), dataTypeAvt == null ? dataType : ParseDataType (dataTypeAvt.Evaluate (p)) ); } } internal class XslNameUtil { public static QName [] FromListString (string names, XPathNavigator current) { string [] nameArray = names.Split (XmlChar.WhitespaceChars); int idx = 0; for (int i = 0; i < nameArray.Length; i++) if (nameArray [i] != String.Empty) idx++; XmlQualifiedName [] qnames = new XmlQualifiedName [idx]; idx = 0; for (int i = 0; i < nameArray.Length; i++) if (nameArray [i] != String.Empty) qnames [idx++] = FromString (nameArray [i], current, true); return qnames; } public static QName FromString (string name, XPathNavigator current) { return FromString (name, current, false); } public static QName FromString (string name, XPathNavigator current, bool useDefaultXmlns) { if (current.NodeType == XPathNodeType.Attribute) (current = current.Clone ()).MoveToParent (); int colon = name.IndexOf (':'); if (colon > 0) return new QName (name.Substring (colon+ 1), current.GetNamespace (name.Substring (0, colon))); else if (colon < 0) return new QName (name, useDefaultXmlns ? current.GetNamespace (String.Empty) : ""); else throw new ArgumentException ("Invalid name: " + name); } public static QName FromString (string name, Hashtable nsDecls) { int colon = name.IndexOf (':'); if (colon > 0) return new QName (name.Substring (colon + 1), nsDecls [name.Substring (0, colon)] as string); else if (colon < 0) return new QName (name, nsDecls.ContainsKey (String.Empty) ? (string) nsDecls [String.Empty] : String.Empty); else throw new ArgumentException ("Invalid name: " + name); } public static QName FromString (string name, IStaticXsltContext ctx) { int colon = name.IndexOf (':'); if (colon > 0) return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon))); else if (colon < 0) // Default namespace is not used for unprefixed names. return new QName (name, ""); else throw new ArgumentException ("Invalid name: " + name); } public static QName FromString (string name, XmlNamespaceManager ctx) { int colon = name.IndexOf (':'); if (colon > 0) return new QName (name.Substring (colon + 1), ctx.LookupNamespace (name.Substring (0, colon), false)); else if (colon < 0) // Default namespace is not used for unprefixed names. return new QName (name, ""); else throw new ArgumentException ("Invalid name: " + name); } public static string LocalNameOf (string name) { int colon = name.IndexOf (':'); if (colon > 0) return name.Substring (colon + 1); else if (colon < 0) return name; else throw new ArgumentException ("Invalid name: " + name); } } }