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.Collections.Specialized;
37 using System.Xml.Schema;
38 using System.Xml.XPath;
42 using Mono.Xml.Xsl.Operations;
44 using QName = System.Xml.XmlQualifiedName;
46 namespace Mono.Xml.Xsl {
48 internal class XslStylesheet {
49 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
50 public const string MSXsltNamespace = "urn:schemas-microsoft-com:xslt";
53 ArrayList imports = new ArrayList ();
55 Hashtable spaceControls = new Hashtable ();
56 // [string stylesheet-prefix]=>string result-prefix
57 NameValueCollection namespaceAliases = new NameValueCollection ();
59 Hashtable parameters = new Hashtable ();
61 Hashtable keys = new Hashtable();
62 // [QName]=>XslVariable
63 Hashtable variables = new Hashtable ();
65 XslTemplateTable templates;
67 // stylesheet attributes
69 XmlQualifiedName [] extensionElementPrefixes;
70 XmlQualifiedName [] excludeResultPrefixes;
71 ArrayList stylesheetNamespaces = new ArrayList ();
73 // in-process includes. They must be first parsed as
74 // XPathNavigator, collected imports, and then processed
76 Hashtable inProcessIncludes = new Hashtable ();
78 public XmlQualifiedName [] ExtensionElementPrefixes {
79 get { return extensionElementPrefixes; }
82 public XmlQualifiedName [] ExcludeResultPrefixes {
83 get { return excludeResultPrefixes; }
86 public ArrayList StylesheetNamespaces {
87 get { return stylesheetNamespaces; }
90 public ArrayList Imports {
91 get { return imports; }
94 public Hashtable SpaceControls {
95 get { return spaceControls; }
98 public NameValueCollection NamespaceAliases {
99 get { return namespaceAliases; }
102 public Hashtable Parameters {
103 get { return parameters; }
106 public XslTemplateTable Templates {
107 get { return templates; }
110 public string Version {
111 get { return version; }
114 public XslStylesheet ()
118 internal void Compile (Compiler c)
120 c.PushStylesheet (this);
122 templates = new XslTemplateTable (this);
124 // move to root element
125 while (c.Input.NodeType != XPathNodeType.Element)
126 if (!c.Input.MoveToNext ())
127 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, c.Input);
129 if (c.Input.NamespaceURI != XsltNamespace) {
130 if (c.Input.GetAttribute ("version", XsltNamespace) == String.Empty)
131 throw new XsltCompileException ("Mandatory global attribute version is missing", null, c.Input);
132 // then it is simplified stylesheet.
133 templates.Add (new XslTemplate (c));
135 if (c.Input.LocalName != "stylesheet" &&
136 c.Input.LocalName != "transform")
137 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element", null, c.Input);
139 version = c.Input.GetAttribute ("version", "");
140 if (version == String.Empty)
141 throw new XsltCompileException ("Mandatory attribute version is missing", null, c.Input);
143 extensionElementPrefixes = ParseMappedPrefixes (c.GetAttribute ("extension-element-prefixes"), c.Input);
144 excludeResultPrefixes = ParseMappedPrefixes (c.GetAttribute ("exclude-result-prefixes"), c.Input);
145 if (c.Input.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
147 if (c.Input.Value == XsltNamespace)
149 this.stylesheetNamespaces.Insert (0, new QName (c.Input.Name, c.Input.Value));
150 } while (c.Input.MoveToNextNamespace (XPathNamespaceScope.Local));
151 c.Input.MoveToParent ();
153 ProcessTopLevelElements (c);
156 foreach (XslGlobalVariable v in variables.Values)
157 c.AddGlobalVariable (v);
158 foreach (XslKey key in keys.Values)
162 inProcessIncludes = null;
165 private QName [] ParseMappedPrefixes (string list, XPathNavigator nav)
169 ArrayList al = new ArrayList ();
170 foreach (string entry in list.Split (XmlChar.WhitespaceChars)) {
171 if (entry.Length == 0)
173 if (entry == "#default")
174 al.Add (new QName (String.Empty, String.Empty));
176 string entryNS = nav.GetNamespace (entry);
177 if (entryNS != String.Empty)
178 al.Add (new QName (entry, entryNS));
181 return (QName []) al.ToArray (typeof (QName));
184 bool countedSpaceControlExistence;
185 bool cachedHasSpaceControls;
186 static readonly QName allMatchName = new QName ("*");
188 public bool HasSpaceControls {
190 if (!countedSpaceControlExistence) {
191 countedSpaceControlExistence = true;
192 cachedHasSpaceControls =
193 ComputeHasSpaceControls ();
195 return cachedHasSpaceControls;
199 private bool ComputeHasSpaceControls ()
201 if (this.spaceControls.Count > 0
202 && HasStripSpace (spaceControls))
205 if (imports.Count == 0)
208 for (int i = 0; i < imports.Count; i++) {
209 XslStylesheet s = (XslStylesheet) imports [i];
210 if (s.spaceControls.Count > 0 &&
211 HasStripSpace (s.spaceControls))
217 private bool HasStripSpace (IDictionary table)
219 foreach (XmlSpace space in table.Values)
220 if (space == XmlSpace.Default)
225 public bool GetPreserveWhitespace (XPathNavigator nav)
227 if (!HasSpaceControls)
229 return GetPreserveWhitespaceInternal (nav.Clone ());
232 private bool GetPreserveWhitespaceInternal (XPathNavigator nav)
234 string localName = nav.LocalName;
235 string ns = nav.NamespaceURI;
237 XmlQualifiedName qname = new XmlQualifiedName (localName, ns);
238 object o = spaceControls [qname];
240 for (int i = 0; i < imports.Count; i++) {
241 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
248 qname = new XmlQualifiedName ("*", ns);
249 o = spaceControls [qname];
251 for (int i = 0; i < imports.Count; i++) {
252 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
260 o = spaceControls [allMatchName];
262 for (int i = 0; i < imports.Count; i++) {
263 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
271 switch ((XmlSpace) o) {
272 case XmlSpace.Preserve:
274 case XmlSpace.Default:
278 if (nav.MoveToParent () &&
279 nav.NodeType == XPathNodeType.Element)
280 return GetPreserveWhitespace (nav);
284 bool countedNamespaceAliases;
285 bool cachedHasNamespaceAliases;
286 public bool HasNamespaceAliases {
288 if (!countedNamespaceAliases) {
289 countedNamespaceAliases = true;
290 if (namespaceAliases.Count > 0)
291 cachedHasNamespaceAliases = true;
292 else if (imports.Count == 0)
293 cachedHasNamespaceAliases = false;
295 for (int i = 0; i < imports.Count; i++)
296 if (((XslStylesheet) imports [i]).namespaceAliases.Count > 0)
297 countedNamespaceAliases = true;
298 cachedHasNamespaceAliases = false;
301 return cachedHasNamespaceAliases;
305 public string GetActualPrefix (string prefix)
307 if (!HasNamespaceAliases)
310 string result = namespaceAliases [prefix];
311 if (result == null) {
312 for (int i = 0; i < imports.Count; i++) {
313 result = ((XslStylesheet) imports [i]).namespaceAliases [prefix];
319 return result != null ? result : prefix;
322 private void StoreInclude (Compiler c)
324 XPathNavigator including = c.Input.Clone ();
325 c.PushInputDocument (c.Input.GetAttribute ("href", String.Empty));
326 inProcessIncludes [including] = c.Input;
328 HandleImportsInInclude (c);
329 c.PopInputDocument ();
332 private void HandleImportsInInclude (Compiler c)
334 if (c.Input.NamespaceURI != XsltNamespace) {
335 if (c.Input.GetAttribute ("version",
336 XsltNamespace) == String.Empty)
337 throw new XsltCompileException ("Mandatory global attribute version is missing", null, c.Input);
338 // simplified style == never imports.
339 // Keep this position
343 if (!c.Input.MoveToFirstChild ()) {
344 c.Input.MoveToRoot ();
348 HandleIncludesImports (c);
351 private void HandleInclude (Compiler c)
353 XPathNavigator included = null;
354 foreach (XPathNavigator inc in inProcessIncludes.Keys) {
355 if (inc.IsSamePosition (c.Input)) {
356 included = (XPathNavigator) inProcessIncludes [inc];
360 if (included == null)
361 throw new Exception ("Should not happen. Current input is " + c.Input.BaseURI + " / " + c.Input.Name + ", " + inProcessIncludes.Count);
363 if (included.NodeType == XPathNodeType.Root)
364 return; // Already done.
366 c.PushInputDocument (included);
368 while (c.Input.NodeType != XPathNodeType.Element)
369 if (!c.Input.MoveToNext ())
372 if (c.Input.NamespaceURI != XsltNamespace &&
373 c.Input.NodeType == XPathNodeType.Element) {
374 // then it is simplified stylesheet.
375 templates.Add (new XslTemplate (c));
379 if (c.Input.NodeType != XPathNodeType.Element)
381 Debug.EnterNavigator (c);
382 HandleTopLevelElement (c);
383 Debug.ExitNavigator (c);
384 } while (c.Input.MoveToNext ());
387 c.Input.MoveToParent ();
388 c.PopInputDocument ();
391 private void HandleImport (Compiler c, string href)
393 c.PushInputDocument (href);
394 XslStylesheet imported = new XslStylesheet ();
395 imported.Compile (c);
396 imports.Add (imported);
397 c.PopInputDocument ();
400 private void HandleTopLevelElement (Compiler c)
402 XPathNavigator n = c.Input;
403 switch (n.NamespaceURI)
411 case "preserve-space":
412 AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Preserve, n);
416 AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Default, n);
418 case "namespace-alias":
419 // do nothing. It is handled in prior.
422 case "attribute-set":
423 c.AddAttributeSet (new XslAttributeSet (c));
427 XslKey key = new XslKey (c);
428 keys [key.Name] = key;
435 case "decimal-format":
436 c.CompileDecimalFormat ();
440 templates.Add (new XslTemplate (c));
443 XslGlobalVariable gvar = new XslGlobalVariable (c);
444 variables [gvar.Name] = gvar;
447 XslGlobalParam gpar = new XslGlobalParam (c);
448 variables [gpar.Name] = gpar;
451 if (version == "1.0")
452 throw new XsltCompileException ("Unrecognized top level element after imports", null, c.Input);
456 case MSXsltNamespace:
460 c.ScriptManager.AddScript (c);
467 private XPathNavigator HandleIncludesImports (Compiler c)
469 // process imports. They must precede to other
470 // top level elements by schema.
472 if (c.Input.NodeType != XPathNodeType.Element)
474 if (c.Input.LocalName != "import" ||
475 c.Input.NamespaceURI != XsltNamespace)
477 Debug.EnterNavigator (c);
478 HandleImport (c, c.GetAttribute ("href"));
479 Debug.ExitNavigator (c);
480 } while (c.Input.MoveToNext ());
482 XPathNavigator saved = c.Input.Clone ();
484 // process includes to handle nested imports. They must precede to other
485 // top level elements by schema.
487 if (c.Input.NodeType != XPathNodeType.Element ||
488 c.Input.LocalName != "include" ||
489 c.Input.NamespaceURI != XsltNamespace)
491 Debug.EnterNavigator (c);
493 Debug.ExitNavigator (c);
494 } while (c.Input.MoveToNext ());
496 c.Input.MoveTo (saved);
501 private void ProcessTopLevelElements (Compiler c)
503 if (!c.Input.MoveToFirstChild ())
506 XPathNavigator saved = HandleIncludesImports (c);
509 // Collect namespace aliases first.
510 if (c.Input.NodeType != XPathNodeType.Element ||
511 c.Input.LocalName != "namespace-alias" ||
512 c.Input.NamespaceURI != XsltNamespace)
514 string sprefix = (string) c.GetAttribute ("stylesheet-prefix", "");
515 if (sprefix == "#default")
516 sprefix = String.Empty;
517 string rprefix= (string) c.GetAttribute ("result-prefix", "");
518 if (rprefix == "#default")
519 rprefix = String.Empty;
520 namespaceAliases.Set (sprefix, rprefix);
521 } while (c.Input.MoveToNext ());
523 c.Input.MoveTo (saved);
525 if (c.Input.NodeType != XPathNodeType.Element)
527 Debug.EnterNavigator (c);
528 this.HandleTopLevelElement (c);
529 Debug.ExitNavigator (c);
530 } while (c.Input.MoveToNext ());
532 c.Input.MoveToParent ();
535 private void AddSpaceControls (QName [] names, XmlSpace result, XPathNavigator styleElem)
537 // XSLT 3.4 - This implementation recovers from errors.
538 foreach (QName name in names)
539 spaceControls [name] = result;