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