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                                 if (c.Input.GetAttribute ("version", XsltNamespace) == null)
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));
134                         } else {
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);
138
139                                 version = c.Input.GetAttribute ("version", "");
140                                 if (version == null)
141                                         throw new XsltCompileException ("Mandatory attribute version is missing.", null, c.Input);
142
143                                 extensionElementPrefixes = c.ParseQNameListAttribute ("extension-element-prefixes");
144                                 excludeResultPrefixes = c.ParseQNameListAttribute ("exclude-result-prefixes");
145                                 if (c.Input.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
146                                         do {
147                                                 if (c.Input.Value == XsltNamespace)
148                                                         continue;
149                                                 this.stylesheetNamespaces.Insert (0, new QName (c.Input.Name, c.Input.Value));
150                                         } while (c.Input.MoveToNextNamespace (XPathNamespaceScope.Local));
151                                         c.Input.MoveToParent ();
152                                 }
153                                 ProcessTopLevelElements ();
154                         }
155                         
156                         c.PopStylesheet ();
157                 }
158                 
159                 public XslKey FindKey (QName name)
160                 {
161                         XslKey key = Keys [name] as XslKey;
162                         if (key != null)
163                                 return key;
164                         for (int i = Imports.Count - 1; i >= 0; i--) {
165                                 key = ((XslStylesheet) Imports [i]).FindKey (name);
166                                 if (key != null)
167                                         return key;
168                         }
169                         return null;
170                 }
171
172                 bool countedSpaceControlExistence;
173                 bool cachedHasSpaceControls;
174                 public bool HasSpaceControls {
175                         get {
176                                 if (!countedSpaceControlExistence) {
177                                         countedSpaceControlExistence = true;
178                                         if (this.spaceControls.Count > 0)
179                                                 cachedHasSpaceControls = true;
180                                         else if (imports.Count == 0)
181                                                 cachedHasSpaceControls = false;
182                                         else {
183                                                 for (int i = 0; i < imports.Count; i++)
184                                                         if (((XslStylesheet) imports [i]).spaceControls.Count > 0)
185                                                                 countedSpaceControlExistence = true;
186                                                 cachedHasSpaceControls = false;
187                                         }
188                                 }
189                                 return cachedHasSpaceControls;
190                         }
191                 }
192
193                 public bool GetPreserveWhitespace (string localName, string ns)
194                 {
195                         if (!HasSpaceControls)
196                                 return true;
197
198                         XmlQualifiedName qname = new XmlQualifiedName (localName, ns);
199                         object o = spaceControls [qname];
200                         if (o == null) {
201
202                                 for (int i = 0; i < imports.Count; i++) {
203                                         o = ((XslStylesheet) imports [i]).SpaceControls [qname];
204                                         if (o != null)
205                                                 break;
206                                 }
207                         }
208
209                         if (o == null) {
210                                 qname = new XmlQualifiedName ("*", ns);
211                                 o = spaceControls [qname];
212                                 if (o == null) {
213                                         for (int i = 0; i < imports.Count; i++) {
214                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
215                                                 if (o != null)
216                                                         break;
217                                         }
218                                 }
219                         }
220
221                         if (o == null) {
222                                 qname = new XmlQualifiedName ("*", String.Empty);
223                                 o = spaceControls [qname];
224                                 if (o == null) {
225                                         for (int i = 0; i < imports.Count; i++) {
226                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
227                                                 if (o != null)
228                                                         break;
229                                         }
230                                 }
231                         }
232
233                         if (o != null) {
234                                 XmlSpace space = (XmlSpace) o;
235                                 switch ((XmlSpace) o) {
236                                 case XmlSpace.Preserve:
237                                         return true;
238                                 case XmlSpace.Default:
239                                         return false;
240                                 }
241                         }
242                         return true;
243                 }
244
245                 bool countedNamespaceAliases;
246                 bool cachedHasNamespaceAliases;
247                 public bool HasNamespaceAliases {
248                         get {
249                                 if (!countedNamespaceAliases) {
250                                         countedNamespaceAliases = true;
251                                         if (namespaceAliases.Count > 0)
252                                                 cachedHasNamespaceAliases = true;
253                                         else if (imports.Count == 0)
254                                                 cachedHasNamespaceAliases = false;
255                                         else {
256                                                 for (int i = 0; i < imports.Count; i++)
257                                                         if (((XslStylesheet) imports [i]).namespaceAliases.Count > 0)
258                                                                 countedNamespaceAliases = true;
259                                                 cachedHasNamespaceAliases = false;
260                                         }
261                                 }
262                                 return cachedHasNamespaceAliases;
263                         }
264                 }
265
266                 public string GetActualPrefix (string prefix)
267                 {
268                         if (!HasNamespaceAliases)
269                                 return prefix;
270
271                         string result = namespaceAliases [prefix];
272                         if (result == null) {
273                                 for (int i = 0; i < imports.Count; i++) {
274                                         result = ((XslStylesheet) imports [i]).namespaceAliases [prefix];
275                                         if (result != null)
276                                                 break;
277                                 }
278                         }
279
280                         return result != null ? result : prefix;
281                 }
282
283                 private XslStylesheet (Compiler c, XslStylesheet importer) : this (c)
284                 {
285                         this.importer = importer;
286                 }
287                 
288                 private void HandleInclude (string href)
289                 {
290                         c.PushInputDocument (href);
291                         ProcessTopLevelElements ();
292                         c.PopInputDocument ();
293                 }
294                 
295                 private void HandleImport (string href)
296                 {
297                         c.PushInputDocument (href);
298                         imports.Add (new XslStylesheet (c, this));
299                         c.PopInputDocument ();
300                 }
301                 
302                 private void HandleTopLevelElement ()
303                 {
304                         XPathNavigator n = c.Input;
305                         switch (n.NamespaceURI)
306                         {
307                         case XsltNamespace:
308                                 
309                                 switch (n.LocalName)
310                                 {
311                                 case "include":
312                                         HandleInclude (c.GetAttribute ("href"));
313                                         break;
314                                 case "import":
315                                         HandleImport (c.GetAttribute ("href"));
316                                         break;
317                                 case "preserve-space":
318                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Preserve, n);
319                                         break;
320                                 
321                                 case "strip-space":
322                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Default, n);
323                                         break;
324                                 
325                                 case "namespace-alias":
326                                         namespaceAliases.Add ((string) c.GetAttribute ("stylesheet-prefix", ""), (string) c.GetAttribute ("result-prefix", ""));
327                                         break;
328                                 
329                                 case "attribute-set":
330                                         c.AddAttributeSet (new XslAttributeSet (c));
331                                         break;
332
333                                 case "key":
334                                         keys.Add (c.ParseQNameAttribute ("name"), new XslKey (c));
335                                         break;
336                                         
337                                 case "output":
338                                         c.CompileOutput ();
339                                         break;
340                                 
341                                 case "decimal-format":
342                                         c.CompileDecimalFormat ();
343                                         break;
344                                         
345                                 case "template":
346                                         templates.Add (new XslTemplate (c));    
347                                         break;
348                                 case "variable":
349                                         c.AddGlobalVariable (new XslGlobalVariable (c));
350                                         break;
351                                 case "param":
352                                         c.AddGlobalVariable (new XslGlobalParam (c));
353                                         break;
354                                 default:
355                                         if (version == "1.0")
356                                                 throw new XsltCompileException ("Unrecognized top level element.", null, c.Input);
357                                         break;
358                                 }
359                                 break;
360                         case MSXsltNamespace:
361                                 switch (n.LocalName)
362                                 {
363                                 case "script":
364                                         c.ScriptManager.AddScript (c);
365                                         break;
366                                 }
367                                 break;
368                         }
369                 }
370                 
371                 private void ProcessTopLevelElements ()
372                 {
373                         if (c.Input.MoveToFirstChild ()) {
374                                 do {
375                                         if (c.Input.NodeType == XPathNodeType.Element) {                                        
376                                                 Debug.EnterNavigator (c);
377                                                 this.HandleTopLevelElement();
378                                                 Debug.ExitNavigator (c);
379                                         }
380                                 } while (c.Input.MoveToNext ());
381                                 
382                                 c.Input.MoveToParent ();
383                         }
384                 }
385
386                 private void AddSpaceControls (QName [] names, XmlSpace result, XPathNavigator styleElem)
387                 {
388                         // XSLT 3.4 - This implementation recovers from errors.
389                         foreach (QName name in names)
390                                 spaceControls [name] = result;
391                 }
392
393                 public string PrefixInEffect (string prefix, ArrayList additionalExcluded)
394                 {
395                         if (additionalExcluded != null && additionalExcluded.Contains (prefix == String.Empty ? "#default" : prefix))
396                                 return null;
397                         if (prefix == "#default")
398                                 prefix = String.Empty;
399
400                         if (ExcludeResultPrefixes != null) {
401                                 bool exclude = false;
402                                 foreach (XmlQualifiedName exc in ExcludeResultPrefixes)
403                                         if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) {
404                                                 exclude = true;
405                                                 break;
406                                         }
407                                 if (exclude)
408                                         return null;
409                         }
410
411                         if (ExtensionElementPrefixes != null) {
412                                 bool exclude = false;
413                                 foreach (XmlQualifiedName exc in ExtensionElementPrefixes)
414                                         if (exc.Name == "#default" && prefix == String.Empty || exc.Name == prefix) {
415                                                 exclude = true;
416                                                 break;
417                                         }
418                                 if (exclude)
419                                         return null;
420                         }
421
422                         return GetActualPrefix (prefix);
423                 }
424         }
425
426         
427         public enum XslDefaultValidation
428         {
429                 Strict,
430                 Lax,
431                 Preserve,
432                 Strip
433         }
434 }