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