* SoapSchemaExporterTests.cs: Sync expected results with changes in test
[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                 // Top-level elements
53                 ArrayList imports = new ArrayList ();
54                 // [QName]=>XmlSpace
55                 Hashtable spaceControls = new Hashtable ();
56                 // [string stylesheet-prefix]=>string result-prefix
57                 NameValueCollection namespaceAliases = new NameValueCollection ();
58                 // [QName]=>XmlSpace
59                 Hashtable parameters = new Hashtable ();
60                 // [QName]=>XslKey
61                 Hashtable keys = new Hashtable();
62                 // [QName]=>XslVariable
63                 Hashtable variables = new Hashtable ();
64
65                 XslTemplateTable templates;
66
67                 // stylesheet attributes
68                 string version;
69                 XmlQualifiedName [] extensionElementPrefixes;
70                 XmlQualifiedName [] excludeResultPrefixes;
71                 ArrayList stylesheetNamespaces = new ArrayList ();
72
73                 // in-process includes. They must be first parsed as
74                 // XPathNavigator, collected imports, and then processed
75                 // other content.
76                 Hashtable inProcessIncludes = new Hashtable ();
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 XslTemplateTable Templates {
107                         get { return templates; }
108                 }
109
110                 public string Version {
111                         get { return version; }
112                 }
113
114                 public XslStylesheet ()
115                 {
116                 }
117
118                 internal void Compile (Compiler c)
119                 {
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) == String.Empty)
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 == String.Empty)
141                                         throw new XsltCompileException ("Mandatory attribute version is missing", null, c.Input);
142
143                                 extensionElementPrefixes = ParseMappedPrefixes (c.GetAttribute ("extension-element-prefixes"), c.Input);
144                                 excludeResultPrefixes = ParseMappedPrefixes (c.GetAttribute ("exclude-result-prefixes"), c.Input);
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 (c);
154                         }
155
156                         foreach (XslGlobalVariable v in variables.Values)
157                                 c.AddGlobalVariable (v);
158                         foreach (XslKey key in keys.Values)
159                                 c.AddKey (key);
160
161                         c.PopStylesheet ();
162                         inProcessIncludes = null;
163                 }
164
165                 private QName [] ParseMappedPrefixes (string list, XPathNavigator nav)
166                 {
167                         if (list == null)
168                                 return null;
169                         ArrayList al = new ArrayList ();
170                         foreach (string entry in list.Split (XmlChar.WhitespaceChars)) {
171                                 if (entry.Length == 0)
172                                         continue;
173                                 if (entry == "#default")
174                                         al.Add (new QName (String.Empty, String.Empty));
175                                 else {
176                                         string entryNS = nav.GetNamespace (entry);
177                                         if (entryNS != String.Empty)
178                                                 al.Add (new QName (entry, entryNS));
179                                 }
180                         }
181                         return (QName []) al.ToArray (typeof (QName));
182                 }
183
184                 bool countedSpaceControlExistence;
185                 bool cachedHasSpaceControls;
186                 static readonly QName allMatchName = new QName ("*");
187
188                 public bool HasSpaceControls {
189                         get {
190                                 if (!countedSpaceControlExistence) {
191                                         countedSpaceControlExistence = true;
192                                         cachedHasSpaceControls =
193                                                 ComputeHasSpaceControls ();
194                                 }
195                                 return cachedHasSpaceControls;
196                         }
197                 }
198
199                 private bool ComputeHasSpaceControls ()
200                 {
201                         if (this.spaceControls.Count > 0
202                                 && HasStripSpace (spaceControls))
203                                 return true;
204
205                         if (imports.Count == 0)
206                                 return false;
207
208                         for (int i = 0; i < imports.Count; i++) {
209                                 XslStylesheet s = (XslStylesheet) imports [i];
210                                 if (s.spaceControls.Count > 0 &&
211                                         HasStripSpace (s.spaceControls))
212                                         return true;
213                         }
214                         return false;
215                 }
216
217                 private bool HasStripSpace (IDictionary table)
218                 {
219                         foreach (XmlSpace space in table.Values)
220                                 if (space == XmlSpace.Default)
221                                         return true;
222                         return false;
223                 }
224
225                 public bool GetPreserveWhitespace (XPathNavigator nav)
226                 {
227                         if (!HasSpaceControls)
228                                 return true;
229                         return GetPreserveWhitespaceInternal (nav.Clone ());
230                 }
231
232                 private bool GetPreserveWhitespaceInternal (XPathNavigator nav)
233                 {
234                         string localName = nav.LocalName;
235                         string ns = nav.NamespaceURI;
236
237                         XmlQualifiedName qname = new XmlQualifiedName (localName, ns);
238                         object o = spaceControls [qname];
239                         if (o == null) {
240                                 for (int i = 0; i < imports.Count; i++) {
241                                         o = ((XslStylesheet) imports [i]).SpaceControls [qname];
242                                         if (o != null)
243                                                 break;
244                                 }
245                         }
246
247                         if (o == null) {
248                                 qname = new XmlQualifiedName ("*", ns);
249                                 o = spaceControls [qname];
250                                 if (o == null) {
251                                         for (int i = 0; i < imports.Count; i++) {
252                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
253                                                 if (o != null)
254                                                         break;
255                                         }
256                                 }
257                         }
258
259                         if (o == null) {
260                                 o = spaceControls [allMatchName];
261                                 if (o == null) {
262                                         for (int i = 0; i < imports.Count; i++) {
263                                                 o = ((XslStylesheet) imports [i]).SpaceControls [qname];
264                                                 if (o != null)
265                                                         break;
266                                         }
267                                 }
268                         }
269
270                         if (o != null) {
271                                 switch ((XmlSpace) o) {
272                                 case XmlSpace.Preserve:
273                                         return true;
274                                 case XmlSpace.Default:
275                                         return false;
276                                 }
277                         }
278                         if (nav.MoveToParent () &&
279                                 nav.NodeType == XPathNodeType.Element)
280                                 return GetPreserveWhitespace (nav);
281                         return true;
282                 }
283
284                 bool countedNamespaceAliases;
285                 bool cachedHasNamespaceAliases;
286                 public bool HasNamespaceAliases {
287                         get {
288                                 if (!countedNamespaceAliases) {
289                                         countedNamespaceAliases = true;
290                                         if (namespaceAliases.Count > 0)
291                                                 cachedHasNamespaceAliases = true;
292                                         else if (imports.Count == 0)
293                                                 cachedHasNamespaceAliases = false;
294                                         else {
295                                                 for (int i = 0; i < imports.Count; i++)
296                                                         if (((XslStylesheet) imports [i]).namespaceAliases.Count > 0)
297                                                                 countedNamespaceAliases = true;
298                                                 cachedHasNamespaceAliases = false;
299                                         }
300                                 }
301                                 return cachedHasNamespaceAliases;
302                         }
303                 }
304
305                 public string GetActualPrefix (string prefix)
306                 {
307                         if (!HasNamespaceAliases)
308                                 return prefix;
309
310                         string result = namespaceAliases [prefix];
311                         if (result == null) {
312                                 for (int i = 0; i < imports.Count; i++) {
313                                         result = ((XslStylesheet) imports [i]).namespaceAliases [prefix];
314                                         if (result != null)
315                                                 break;
316                                 }
317                         }
318
319                         return result != null ? result : prefix;
320                 }
321
322                 private void StoreInclude (Compiler c)
323                 {
324                         XPathNavigator including = c.Input.Clone ();
325                         c.PushInputDocument (c.Input.GetAttribute ("href", String.Empty));
326                         inProcessIncludes [including] = c.Input;
327
328                         HandleImportsInInclude (c);
329                         c.PopInputDocument ();
330                 }
331
332                 private void HandleImportsInInclude (Compiler c)
333                 {
334                         if (c.Input.NamespaceURI != XsltNamespace) {
335                                 if (c.Input.GetAttribute ("version",
336                                         XsltNamespace) == String.Empty)
337                                         throw new XsltCompileException ("Mandatory global attribute version is missing", null, c.Input);
338                                 // simplified style == never imports.
339                                 // Keep this position
340                                 return;
341                         }
342
343                         if (!c.Input.MoveToFirstChild ()) {
344                                 c.Input.MoveToRoot ();
345                                 return;
346                         }
347
348                         HandleIncludesImports (c);
349                 }
350
351                 private void HandleInclude (Compiler c)
352                 {
353                         XPathNavigator included = null;
354                         foreach (XPathNavigator inc in inProcessIncludes.Keys) {
355                                 if (inc.IsSamePosition (c.Input)) {
356                                         included = (XPathNavigator) inProcessIncludes [inc];
357                                         break;
358                                 }
359                         }
360                         if (included == null)
361                                 throw new Exception ("Should not happen. Current input is " + c.Input.BaseURI + " / " + c.Input.Name + ", " + inProcessIncludes.Count);
362
363                         if (included.NodeType == XPathNodeType.Root)
364                                 return; // Already done.
365
366                         c.PushInputDocument (included);
367
368                         while (c.Input.NodeType != XPathNodeType.Element)
369                                 if (!c.Input.MoveToNext ())
370                                         break;
371
372                         if (c.Input.NamespaceURI != XsltNamespace &&
373                                 c.Input.NodeType == XPathNodeType.Element) {
374                                 // then it is simplified stylesheet.
375                                 templates.Add (new XslTemplate (c));
376                         }
377                         else {
378                                 do {
379                                         if (c.Input.NodeType != XPathNodeType.Element)
380                                                 continue;
381                                         Debug.EnterNavigator (c);
382                                         HandleTopLevelElement (c);
383                                         Debug.ExitNavigator (c);
384                                 } while (c.Input.MoveToNext ());
385                         }
386
387                         c.Input.MoveToParent ();
388                         c.PopInputDocument ();
389                 }
390                 
391                 private void HandleImport (Compiler c, string href)
392                 {
393                         c.PushInputDocument (href);
394                         XslStylesheet imported = new XslStylesheet ();
395                         imported.Compile (c);
396                         imports.Add (imported);
397                         c.PopInputDocument ();
398                 }
399                 
400                 private void HandleTopLevelElement (Compiler c)
401                 {
402                         XPathNavigator n = c.Input;
403                         switch (n.NamespaceURI)
404                         {
405                         case XsltNamespace:
406                                 switch (n.LocalName)
407                                 {
408                                 case "include":
409                                         HandleInclude (c);
410                                         break;
411                                 case "preserve-space":
412                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Preserve, n);
413                                         break;
414                                 
415                                 case "strip-space":
416                                         AddSpaceControls (c.ParseQNameListAttribute ("elements"), XmlSpace.Default, n);
417                                         break;
418                                 case "namespace-alias":
419                                         // do nothing. It is handled in prior.
420                                         break;
421                                 
422                                 case "attribute-set":
423                                         c.AddAttributeSet (new XslAttributeSet (c));
424                                         break;
425
426                                 case "key":
427                                         XslKey key = new XslKey (c);
428                                         keys [key.Name] = key;
429                                         break;
430                                         
431                                 case "output":
432                                         c.CompileOutput ();
433                                         break;
434                                 
435                                 case "decimal-format":
436                                         c.CompileDecimalFormat ();
437                                         break;
438                                         
439                                 case "template":
440                                         templates.Add (new XslTemplate (c));    
441                                         break;
442                                 case "variable":
443                                         XslGlobalVariable gvar = new XslGlobalVariable (c);
444                                         variables [gvar.Name] = gvar;
445                                         break;
446                                 case "param":
447                                         XslGlobalParam gpar = new XslGlobalParam (c);
448                                         variables [gpar.Name] = gpar;
449                                         break;
450                                 default:
451                                         if (version == "1.0")
452                                                 throw new XsltCompileException ("Unrecognized top level element after imports", null, c.Input);
453                                         break;
454                                 }
455                                 break;
456                         case MSXsltNamespace:
457                                 switch (n.LocalName)
458                                 {
459                                 case "script":
460                                         c.ScriptManager.AddScript (c);
461                                         break;
462                                 }
463                                 break;
464                         }
465                 }
466
467                 private XPathNavigator HandleIncludesImports (Compiler c)
468                 {
469                         // process imports. They must precede to other
470                         // top level elements by schema.
471                         do {
472                                 if (c.Input.NodeType != XPathNodeType.Element)
473                                         continue;
474                                 if (c.Input.LocalName != "import" ||
475                                         c.Input.NamespaceURI != XsltNamespace)
476                                         break;
477                                 Debug.EnterNavigator (c);
478                                 HandleImport (c, c.GetAttribute ("href"));
479                                 Debug.ExitNavigator (c);
480                         } while (c.Input.MoveToNext ());
481
482                         XPathNavigator saved = c.Input.Clone ();
483
484                         // process includes to handle nested imports. They must precede to other
485                         // top level elements by schema.
486                         do {
487                                 if (c.Input.NodeType != XPathNodeType.Element ||
488                                         c.Input.LocalName != "include" ||
489                                         c.Input.NamespaceURI != XsltNamespace)
490                                         continue;
491                                 Debug.EnterNavigator (c);
492                                 StoreInclude (c);
493                                 Debug.ExitNavigator (c);
494                         } while (c.Input.MoveToNext ());
495
496                         c.Input.MoveTo (saved);
497
498                         return saved;
499                 }
500
501                 private void ProcessTopLevelElements (Compiler c)
502                 {
503                         if (!c.Input.MoveToFirstChild ())
504                                 return;
505
506                         XPathNavigator saved = HandleIncludesImports (c);
507
508                         do {
509                                 // Collect namespace aliases first.
510                                 if (c.Input.NodeType != XPathNodeType.Element ||
511                                         c.Input.LocalName != "namespace-alias" ||
512                                         c.Input.NamespaceURI != XsltNamespace)
513                                         continue;
514                                 string sprefix = (string) c.GetAttribute ("stylesheet-prefix", "");
515                                 if (sprefix == "#default")
516                                         sprefix = String.Empty;
517                                 string rprefix= (string) c.GetAttribute ("result-prefix", "");
518                                 if (rprefix == "#default")
519                                         rprefix = String.Empty;
520                                 namespaceAliases.Set (sprefix, rprefix);
521                         } while (c.Input.MoveToNext ());
522
523                         c.Input.MoveTo (saved);
524                         do {
525                                 if (c.Input.NodeType != XPathNodeType.Element)
526                                         continue;
527                                 Debug.EnterNavigator (c);
528                                 this.HandleTopLevelElement (c);
529                                 Debug.ExitNavigator (c);
530                         } while (c.Input.MoveToNext ());
531                         
532                         c.Input.MoveToParent ();
533                 }
534
535                 private void AddSpaceControls (QName [] names, XmlSpace result, XPathNavigator styleElem)
536                 {
537                         // XSLT 3.4 - This implementation recovers from errors.
538                         foreach (QName name in names)
539                                 spaceControls [name] = result;
540                 }
541         }
542 }