// // XslStylesheet.cs // // Authors: // Ben Maurer (bmaurer@users.sourceforge.net) // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp) // // (C) 2003 Ben Maurer // (C) 2003 Atsushi Enomoto // using System; using System.CodeDom; using System.Collections; using System.Collections.Specialized; using System.Xml; using System.Xml.Schema; using System.Xml.XPath; using System.Xml.Xsl; using System.IO; using Mono.Xml.Xsl.Operations; using QName = System.Xml.XmlQualifiedName; namespace Mono.Xml.Xsl { public class XslStylesheet { public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform"; public const string MSXsltNamespace = "urn:schemas-microsoft-com:xslt"; Compiler c; XslStylesheet importer; // Top-level elements ArrayList imports = new ArrayList (); // [QName]=>XmlSpace Hashtable spaceControls = new Hashtable (); // [string stylesheet-prefix]=>string result-prefix NameValueCollection namespaceAliases = new NameValueCollection (); // [QName]=>XmlSpace Hashtable parameters = new Hashtable (); // [QName]=>XslKey Hashtable keys = new Hashtable(); XslTemplateTable templates; // stylesheet attributes string version; XmlQualifiedName [] extensionElementPrefixes; XmlQualifiedName [] excludeResultPrefixes; ArrayList stylesheetNamespaces = new ArrayList (); // below are newly introduced in XSLT 2.0 // elements:: // xsl:import-schema should be interpreted into it. XmlSchemaCollection schemas = new XmlSchemaCollection (); // [QName]=>XslCharacterMap Hashtable characterMap = new Hashtable (); // [QName]=>XslDateFormat Hashtable dateFormats = new Hashtable (); // [QName]=>XslFunction Hashtable functions = new Hashtable (); // [QName]=>XslSortKey Hashtable sortKeys = new Hashtable (); // attributes:: string xpathDefaultNamespace = ""; XslDefaultValidation defaultValidation = XslDefaultValidation.Lax; public string BaseUri { get { return c.Input.BaseURI; } } public XmlQualifiedName [] ExtensionElementPrefixes { get { return extensionElementPrefixes; } } public XmlQualifiedName [] ExcludeResultPrefixes { get { return excludeResultPrefixes; } } public ArrayList StylesheetNamespaces { get { return stylesheetNamespaces; } } public ArrayList Imports { get { return imports; } } public Hashtable SpaceControls { get { return spaceControls; } } public NameValueCollection NamespaceAliases { get { return namespaceAliases; } } public Hashtable Parameters { get { return parameters; } } public XPathNavigator StyleDocument { get { return c.Input; } } public XslTemplateTable Templates { get { return templates; } } public Hashtable Keys { get { return keys; } } public string Version { get { return version; } } public XslStylesheet (Compiler c) { this.c = c; c.PushStylesheet (this); templates = new XslTemplateTable (this); if (c.Input.NamespaceURI != XsltNamespace) { // then it is simplified stylesheet. Templates.Add (new XslTemplate (c)); } else { version = c.Input.GetAttribute ("version", ""); extensionElementPrefixes = c.ParseQNameListAttribute ("extension-element-prefixes"); excludeResultPrefixes = c.ParseQNameListAttribute ("exclude-result-prefixes"); if (c.Input.MoveToFirstNamespace (XPathNamespaceScope.Local)) { do { if (c.Input.Value == XsltNamespace) continue; this.stylesheetNamespaces.Insert (0, new QName (c.Input.Name, c.Input.Value)); } while (c.Input.MoveToNextNamespace (XPathNamespaceScope.Local)); c.Input.MoveToParent (); } ProcessTopLevelElements (); } c.PopStylesheet (); } public XslKey FindKey (QName name) { XslKey key = Keys [name] as XslKey; if (key != null) return key; for (int i = Imports.Count - 1; i >= 0; i--) { key = ((XslStylesheet) Imports [i]).FindKey (name); if (key != null) return key; } return null; } bool countedSpaceControlExistence; bool cachedHasSpaceControls; public bool HasSpaceControls { get { if (!countedSpaceControlExistence) { countedSpaceControlExistence = true; if (this.spaceControls.Count > 0) cachedHasSpaceControls = true; else if (imports.Count == 0) cachedHasSpaceControls = false; else { for (int i = 0; i < imports.Count; i++) if (((XslStylesheet) imports [i]).spaceControls.Count > 0) countedSpaceControlExistence = true; cachedHasSpaceControls = false; } } return cachedHasSpaceControls; } } public bool GetPreserveWhitespace (string localName, string ns) { if (!HasSpaceControls) return true; XmlQualifiedName qname = new XmlQualifiedName (localName, ns); object o = spaceControls [qname]; if (o == null) { foreach (XslStylesheet s in imports) { o = s.SpaceControls [qname]; if (o != null) break; } } if (o == null) { qname = new XmlQualifiedName ("*", ns); o = spaceControls [qname]; if (o == null) { foreach (XslStylesheet s in imports) { o = s.SpaceControls [qname]; if (o != null) break; } } } if (o == null) { qname = new XmlQualifiedName ("*", String.Empty); o = spaceControls [qname]; if (o == null) { foreach (XslStylesheet s in imports) { o = s.SpaceControls [qname]; if (o != null) break; } } } if (o != null) { XmlSpace space = (XmlSpace) o; switch ((XmlSpace) o) { case XmlSpace.Preserve: return true; case XmlSpace.Default: return false; } } return true; } bool countedNamespaceAliases; bool cachedHasNamespaceAliases; public bool HasNamespaceAliases { get { if (!countedNamespaceAliases) { countedNamespaceAliases = true; if (namespaceAliases.Count > 0) cachedHasNamespaceAliases = true; else if (imports.Count == 0) cachedHasNamespaceAliases = false; else { for (int i = 0; i < imports.Count; i++) if (((XslStylesheet) imports [i]).namespaceAliases.Count > 0) countedNamespaceAliases = true; cachedHasNamespaceAliases = false; } } return cachedHasNamespaceAliases; } } public string GetActualPrefix (string prefix) { if (!HasNamespaceAliases) return prefix; string result = namespaceAliases [prefix]; if (result == null) { foreach (XslStylesheet s in imports) { result = s.namespaceAliases [prefix]; if (result != null) break; } } return result != null ? result : prefix; } private XslStylesheet (Compiler c, XslStylesheet importer) : this (c) { this.importer = importer; } private void HandleInclude (string href) { c.PushInputDocument (href); ProcessTopLevelElements (); c.PopInputDocument (); } private void HandleImport (string href) { c.PushInputDocument (href); imports.Add (new XslStylesheet (c, this)); c.PopInputDocument (); } private void HandleTopLevelElement () { XPathNavigator n = c.Input; switch (n.NamespaceURI) { case XsltNamespace: switch (n.LocalName) { case "include": HandleInclude (c.GetAttribute ("href")); break; case "import": HandleImport (c.GetAttribute ("href")); break; case "preserve-space": AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Preserve, n); break; case "strip-space": AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Default, n); break; case "namespace-alias": namespaceAliases.Add ((string) c.GetAttribute ("stylesheet-prefix", ""), (string) c.GetAttribute ("result-prefix", "")); break; case "attribute-set": c.AddAttributeSet (new XslAttributeSet (c)); break; case "key": keys.Add (c.ParseQNameAttribute ("name"), new XslKey (c)); break; case "output": c.CompileOutput (); break; case "decimal-format": c.CompileDecimalFormat (); break; case "template": templates.Add (new XslTemplate (c)); break; case "variable": c.AddGlobalVariable (new XslGlobalVariable (c)); break; case "param": c.AddGlobalVariable (new XslGlobalParam (c)); break; default: if (version == "1.0") throw new XsltCompileException ("Unrecognized top level element.", null, c.Input); break; } break; case MSXsltNamespace: switch (n.LocalName) { case "script": c.ScriptManager.AddScript (c); break; } break; } } private void ProcessTopLevelElements () { if (c.Input.MoveToFirstChild ()) { do { if (c.Input.NodeType == XPathNodeType.Element) { Debug.EnterNavigator (c); this.HandleTopLevelElement(); Debug.ExitNavigator (c); } } while (c.Input.MoveToNext ()); c.Input.MoveToParent (); } } private void AddSpaceControls (QName [] names, XmlSpace result, XPathNavigator styleElem) { // XSLT 3.4 - This implementation recovers from errors. foreach (QName name in names) spaceControls [name] = result; } public string PrefixInEffect (string prefix, ArrayList additionalExcluded) { if (additionalExcluded != null && additionalExcluded.Contains (prefix == String.Empty ? "#default" : prefix)) return null; if (prefix == "#default") prefix = String.Empty; if (ExcludeResultPrefixes != null) { bool exclude = false; foreach (XmlQualifiedName exc in ExcludeResultPrefixes) if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) { exclude = true; break; } if (exclude) return null; } if (ExtensionElementPrefixes != null) { bool exclude = false; foreach (XmlQualifiedName exc in ExtensionElementPrefixes) if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) { exclude = true; break; } if (exclude) return null; } return GetActualPrefix (prefix); } } public enum XslDefaultValidation { Strict, Lax, Preserve, Strip } }