2004-03-13 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslStylesheet.cs
1 //
2 // XslStylesheet.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //      
8 // (C) 2003 Ben Maurer
9 // (C) 2003 Atsushi Enomoto
10 //
11
12 using System;
13 using System.Collections;
14 using System.Collections.Specialized;
15 using System.Xml;
16 using System.Xml.Schema;
17 using System.Xml.XPath;
18 using System.Xml.Xsl;
19 using System.IO;
20
21 using Mono.Xml.Xsl.Operations;
22
23 using QName = System.Xml.XmlQualifiedName;
24
25 namespace Mono.Xml.Xsl {
26
27         public class XslStylesheet {
28                 public const string XsltNamespace = "http://www.w3.org/1999/XSL/Transform";
29                 public const string MSXsltNamespace = "urn:schemas-microsoft-com:xslt";
30                 
31                 Compiler c;
32
33                 XslStylesheet importer;
34                 // Top-level elements
35                 ArrayList imports = new ArrayList ();
36                 // [QName]=>XmlSpace
37                 Hashtable spaceControls = new Hashtable ();
38                 // [string stylesheet-prefix]=>string result-prefix
39                 NameValueCollection namespaceAliases = new NameValueCollection ();
40                 // [QName]=>XmlSpace
41                 Hashtable parameters = new Hashtable ();
42                 // [QName]=>XslKey
43                 Hashtable keys = new Hashtable();
44
45                 XslTemplateTable templates;
46
47                 // stylesheet attributes
48                 string version;
49                 XmlQualifiedName [] extensionElementPrefixes;
50                 XmlQualifiedName [] excludeResultPrefixes;
51                 ArrayList stylesheetNamespaces = new ArrayList ();
52
53                 // below are newly introduced in XSLT 2.0
54                 //  elements::
55                 // xsl:import-schema should be interpreted into it.
56                 XmlSchemaCollection schemas = new XmlSchemaCollection ();
57                 // [QName]=>XslCharacterMap
58                 Hashtable characterMap = new Hashtable ();
59                 // [QName]=>XslDateFormat
60                 Hashtable dateFormats = new Hashtable ();
61                 // [QName]=>XslFunction
62                 Hashtable functions = new Hashtable ();
63                 // [QName]=>XslSortKey
64                 Hashtable sortKeys = new Hashtable ();
65                 //  attributes::
66                 string xpathDefaultNamespace = "";
67                 XslDefaultValidation defaultValidation = XslDefaultValidation.Lax;
68
69                 public string BaseUri {
70                         get { return c.Input.BaseURI; }
71                 }
72
73                 public XmlQualifiedName [] ExtensionElementPrefixes {
74                         get { return extensionElementPrefixes; }
75                 }
76
77                 public XmlQualifiedName [] ExcludeResultPrefixes {
78                         get { return excludeResultPrefixes; }
79                 }
80
81                 public ArrayList StylesheetNamespaces {
82                         get { return stylesheetNamespaces; }
83                 }
84
85                 public ArrayList Imports {
86                         get { return imports; }
87                 }
88
89                 public Hashtable SpaceControls {
90                         get { return spaceControls; }
91                 }
92
93                 public NameValueCollection NamespaceAliases {
94                         get { return namespaceAliases; }
95                 }
96
97                 public Hashtable Parameters {
98                         get { return parameters; }
99                 }
100
101                 public XPathNavigator StyleDocument {
102                         get { return c.Input; }
103                 }
104
105                 public XslTemplateTable Templates {
106                         get { return templates; }
107                 }
108
109                 public Hashtable Keys {
110                         get { return keys; }
111                 }
112
113                 public string Version {
114                         get { return version; }
115                 }
116
117                 public XslStylesheet (Compiler c)
118                 {
119                         this.c = c;
120                         c.PushStylesheet (this);
121                         
122                         templates = new XslTemplateTable (this);
123
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);
128
129                         if (c.Input.NamespaceURI != XsltNamespace) {
130                                 // then it is simplified stylesheet.
131                                 Templates.Add (new XslTemplate (c));
132                         } else {
133                                 if (c.Input.LocalName != "stylesheet" &&
134                                         c.Input.LocalName != "transform")
135                                         throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element.", null, c.Input);
136
137                                 version = c.Input.GetAttribute ("version", "");
138                                 if (version == null)
139                                         throw new XsltCompileException ("Mandatory attribute version is missing.", null, c.Input);
140
141                                 extensionElementPrefixes = c.ParseQNameListAttribute ("extension-element-prefixes");
142                                 excludeResultPrefixes = c.ParseQNameListAttribute ("exclude-result-prefixes");
143                                 if (c.Input.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
144                                         do {
145                                                 if (c.Input.Value == XsltNamespace)
146                                                         continue;
147                                                 this.stylesheetNamespaces.Insert (0, new QName (c.Input.Name, c.Input.Value));
148                                         } while (c.Input.MoveToNextNamespace (XPathNamespaceScope.Local));
149                                         c.Input.MoveToParent ();
150                                 }
151                                 ProcessTopLevelElements ();
152                         }
153                         
154                         c.PopStylesheet ();
155                 }
156                 
157                 public XslKey FindKey (QName name)
158                 {
159                         XslKey key = Keys [name] as XslKey;
160                         if (key != null)
161                                 return key;
162                         for (int i = Imports.Count - 1; i >= 0; i--) {
163                                 key = ((XslStylesheet) Imports [i]).FindKey (name);
164                                 if (key != null)
165                                         return key;
166                         }
167                         return null;
168                 }
169
170                 bool countedSpaceControlExistence;
171                 bool cachedHasSpaceControls;
172                 public bool HasSpaceControls {
173                         get {
174                                 if (!countedSpaceControlExistence) {
175                                         countedSpaceControlExistence = true;
176                                         if (this.spaceControls.Count > 0)
177                                                 cachedHasSpaceControls = true;
178                                         else if (imports.Count == 0)
179                                                 cachedHasSpaceControls = false;
180                                         else {
181                                                 for (int i = 0; i < imports.Count; i++)
182                                                         if (((XslStylesheet) imports [i]).spaceControls.Count > 0)
183                                                                 countedSpaceControlExistence = true;
184                                                 cachedHasSpaceControls = false;
185                                         }
186                                 }
187                                 return cachedHasSpaceControls;
188                         }
189                 }
190
191                 public bool GetPreserveWhitespace (string localName, string ns)
192                 {
193                         if (!HasSpaceControls)
194                                 return true;
195
196                         XmlQualifiedName qname = new XmlQualifiedName (localName, ns);
197                         object o = spaceControls [qname];
198                         if (o == null) {
199
200                                 for (int i = 0; i < imports.Count; i++) {
201                                         o = ((XslStylesheet) imports [i]).SpaceControls [qname];
202                                         if (o != null)
203                                                 break;
204                                 }
205                         }
206
207                         if (o == null) {
208                                 qname = new XmlQualifiedName ("*", ns);
209                                 o = spaceControls [qname];
210                                 if (o == null) {
211                                         for (int i = 0; i < imports.Count; i++) {
212                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
213                                                 if (o != null)
214                                                         break;
215                                         }
216                                 }
217                         }
218
219                         if (o == null) {
220                                 qname = new XmlQualifiedName ("*", String.Empty);
221                                 o = spaceControls [qname];
222                                 if (o == null) {
223                                         for (int i = 0; i < imports.Count; i++) {
224                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
225                                                 if (o != null)
226                                                         break;
227                                         }
228                                 }
229                         }
230
231                         if (o != null) {
232                                 XmlSpace space = (XmlSpace) o;
233                                 switch ((XmlSpace) o) {
234                                 case XmlSpace.Preserve:
235                                         return true;
236                                 case XmlSpace.Default:
237                                         return false;
238                                 }
239                         }
240                         return true;
241                 }
242
243                 bool countedNamespaceAliases;
244                 bool cachedHasNamespaceAliases;
245                 public bool HasNamespaceAliases {
246                         get {
247                                 if (!countedNamespaceAliases) {
248                                         countedNamespaceAliases = true;
249                                         if (namespaceAliases.Count > 0)
250                                                 cachedHasNamespaceAliases = true;
251                                         else if (imports.Count == 0)
252                                                 cachedHasNamespaceAliases = false;
253                                         else {
254                                                 for (int i = 0; i < imports.Count; i++)
255                                                         if (((XslStylesheet) imports [i]).namespaceAliases.Count > 0)
256                                                                 countedNamespaceAliases = true;
257                                                 cachedHasNamespaceAliases = false;
258                                         }
259                                 }
260                                 return cachedHasNamespaceAliases;
261                         }
262                 }
263
264                 public string GetActualPrefix (string prefix)
265                 {
266                         if (!HasNamespaceAliases)
267                                 return prefix;
268
269                         string result = namespaceAliases [prefix];
270                         if (result == null) {
271                                 for (int i = 0; i < imports.Count; i++) {
272                                         result = ((XslStylesheet) imports [i]).namespaceAliases [prefix];
273                                         if (result != null)
274                                                 break;
275                                 }
276                         }
277
278                         return result != null ? result : prefix;
279                 }
280
281                 private XslStylesheet (Compiler c, XslStylesheet importer) : this (c)
282                 {
283                         this.importer = importer;
284                 }
285                 
286                 private void HandleInclude (string href)
287                 {
288                         c.PushInputDocument (href);
289                         ProcessTopLevelElements ();
290                         c.PopInputDocument ();
291                 }
292                 
293                 private void HandleImport (string href)
294                 {
295                         c.PushInputDocument (href);
296                         imports.Add (new XslStylesheet (c, this));
297                         c.PopInputDocument ();
298                 }
299                 
300                 private void HandleTopLevelElement ()
301                 {
302                         XPathNavigator n = c.Input;
303                         switch (n.NamespaceURI)
304                         {
305                         case XsltNamespace:
306                                 
307                                 switch (n.LocalName)
308                                 {
309                                 case "include":
310                                         HandleInclude (c.GetAttribute ("href"));
311                                         break;
312                                 case "import":
313                                         HandleImport (c.GetAttribute ("href"));
314                                         break;
315                                 case "preserve-space":
316                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Preserve, n);
317                                         break;
318                                 
319                                 case "strip-space":
320                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Default, n);
321                                         break;
322                                 
323                                 case "namespace-alias":
324                                         namespaceAliases.Add ((string) c.GetAttribute ("stylesheet-prefix", ""), (string) c.GetAttribute ("result-prefix", ""));
325                                         break;
326                                 
327                                 case "attribute-set":
328                                         c.AddAttributeSet (new XslAttributeSet (c));
329                                         break;
330
331                                 case "key":
332                                         keys.Add (c.ParseQNameAttribute ("name"), new XslKey (c));
333                                         break;
334                                         
335                                 case "output":
336                                         c.CompileOutput ();
337                                         break;
338                                 
339                                 case "decimal-format":
340                                         c.CompileDecimalFormat ();
341                                         break;
342                                         
343                                 case "template":
344                                         templates.Add (new XslTemplate (c));    
345                                         break;
346                                 case "variable":
347                                         c.AddGlobalVariable (new XslGlobalVariable (c));
348                                         break;
349                                 case "param":
350                                         c.AddGlobalVariable (new XslGlobalParam (c));
351                                         break;
352                                 default:
353                                         if (version == "1.0")
354                                                 throw new XsltCompileException ("Unrecognized top level element.", null, c.Input);
355                                         break;
356                                 }
357                                 break;
358                         case MSXsltNamespace:
359                                 switch (n.LocalName)
360                                 {
361                                 case "script":
362                                         c.ScriptManager.AddScript (c);
363                                         break;
364                                 }
365                                 break;
366                         }
367                 }
368                 
369                 private void ProcessTopLevelElements ()
370                 {
371                         if (c.Input.MoveToFirstChild ()) {
372                                 do {
373                                         if (c.Input.NodeType == XPathNodeType.Element) {                                        
374                                                 Debug.EnterNavigator (c);
375                                                 this.HandleTopLevelElement();
376                                                 Debug.ExitNavigator (c);
377                                         }
378                                 } while (c.Input.MoveToNext ());
379                                 
380                                 c.Input.MoveToParent ();
381                         }
382                 }
383
384                 private void AddSpaceControls (QName [] names, XmlSpace result, XPathNavigator styleElem)
385                 {
386                         // XSLT 3.4 - This implementation recovers from errors.
387                         foreach (QName name in names)
388                                 spaceControls [name] = result;
389                 }
390
391                 public string PrefixInEffect (string prefix, ArrayList additionalExcluded)
392                 {
393                         if (additionalExcluded != null && additionalExcluded.Contains (prefix == String.Empty ? "#default" : prefix))
394                                 return null;
395                         if (prefix == "#default")
396                                 prefix = String.Empty;
397
398                         if (ExcludeResultPrefixes != null) {
399                                 bool exclude = false;
400                                 foreach (XmlQualifiedName exc in ExcludeResultPrefixes)
401                                         if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) {
402                                                 exclude = true;
403                                                 break;
404                                         }
405                                 if (exclude)
406                                         return null;
407                         }
408
409                         if (ExtensionElementPrefixes != null) {
410                                 bool exclude = false;
411                                 foreach (XmlQualifiedName exc in ExtensionElementPrefixes)
412                                         if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) {
413                                                 exclude = true;
414                                                 break;
415                                         }
416                                 if (exclude)
417                                         return null;
418                         }
419
420                         return GetActualPrefix (prefix);
421                 }
422         }
423
424         
425         public enum XslDefaultValidation
426         {
427                 Strict,
428                 Lax,
429                 Preserve,
430                 Strip
431         }
432 }