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