5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
9 // (C) 2003 Atsushi Enomoto
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
35 using System
.Collections
;
36 using System
.Collections
.Specialized
;
37 using System
.Security
.Policy
;
39 using System
.Xml
.Schema
;
40 using System
.Xml
.XPath
;
44 using Mono
.Xml
.Xsl
.Operations
;
47 using QName
= System
.Xml
.XmlQualifiedName
;
49 namespace Mono
.Xml
.Xsl
51 internal class CompiledStylesheet
{
53 Hashtable globalVariables
;
55 ExpressionStore exprStore
;
56 XmlNamespaceManager nsMgr
;
59 Hashtable decimalFormats
;
60 MSXslScriptManager msScripts
;
62 public CompiledStylesheet (XslStylesheet style
, Hashtable globalVariables
, Hashtable attrSets
, ExpressionStore exprStore
, XmlNamespaceManager nsMgr
, Hashtable keys
, Hashtable outputs
, Hashtable decimalFormats
,
63 MSXslScriptManager msScripts
)
66 this.globalVariables
= globalVariables
;
67 this.attrSets
= attrSets
;
68 this.exprStore
= exprStore
;
71 this.outputs
= outputs
;
72 this.decimalFormats
= decimalFormats
;
73 this.msScripts
= msScripts
;
75 public Hashtable Variables {get{return globalVariables;}}
76 public XslStylesheet Style { get { return style; }}
77 public ExpressionStore ExpressionStore {get{return exprStore;}}
78 public XmlNamespaceManager NamespaceManager {get{return nsMgr;}}
79 public Hashtable Keys {get { return keys;}}
80 public Hashtable Outputs { get { return outputs; }}
82 public MSXslScriptManager ScriptManager
{
83 get { return msScripts; }
87 public XslDecimalFormat
LookupDecimalFormat (QName name
)
89 XslDecimalFormat ret
= decimalFormats
[name
] as XslDecimalFormat
;
90 if (ret
== null && name
== QName
.Empty
)
91 return XslDecimalFormat
.Default
;
95 public XslGeneralVariable
ResolveVariable (QName name
)
97 return (XslGeneralVariable
)globalVariables
[name
];
100 public XslAttributeSet
ResolveAttributeSet (QName name
)
102 return (XslAttributeSet
)attrSets
[name
];
106 internal class Compiler
: IStaticXsltContext
{
107 public const string XsltNamespace
= "http://www.w3.org/1999/XSL/Transform";
109 ArrayList inputStack
= new ArrayList ();
110 XPathNavigator currentInput
;
112 Stack styleStack
= new Stack ();
113 XslStylesheet currentStyle
;
115 Hashtable globalVariables
= new Hashtable ();
116 Hashtable attrSets
= new Hashtable ();
118 ExpressionStore exprStore
= new ExpressionStore ();
119 XmlNamespaceManager nsMgr
= new XmlNamespaceManager (new NameTable ());
124 XslStylesheet rootStyle
;
125 Hashtable outputs
= new Hashtable ();
126 bool keyCompilationMode
;
128 public CompiledStylesheet
Compile (XPathNavigator nav
, XmlResolver res
, Evidence evidence
)
130 this.parser
= new XPathParser (this);
133 this.res
= new XmlUrlResolver ();
134 this.evidence
= evidence
;
136 if (!nav
.MoveToFirstChild ())
137 throw new XsltCompileException ("Stylesheet root element must be either \"stylesheet\" or \"transform\" or any literal element.", null, nav
);
139 outputs
[""] = new XslOutput ("");
141 while (nav
.NodeType
!= XPathNodeType
.Element
) nav
.MoveToNext();
143 PushInputDocument (nav
);
144 if (nav
.MoveToFirstNamespace (XPathNamespaceScope
.ExcludeXml
))
147 nsMgr
.AddNamespace (nav
.LocalName
, nav
.Value
);
148 } while (nav
.MoveToNextNamespace (XPathNamespaceScope
.ExcludeXml
));
151 this.rootStyle
= new XslStylesheet (this);
153 return new CompiledStylesheet (rootStyle
, globalVariables
, attrSets
, exprStore
, nsMgr
, rootStyle
.Keys
, outputs
, decimalFormats
, msScripts
);
156 MSXslScriptManager msScripts
= new MSXslScriptManager ();
157 public MSXslScriptManager ScriptManager
{
158 get { return msScripts; }
161 public bool KeyCompilationMode
{
162 get { return keyCompilationMode; }
163 set { keyCompilationMode = value; }
166 internal Evidence Evidence
{
167 get { return evidence; }
171 public XPathNavigator Input
{
172 get { return currentInput; }
175 public XslStylesheet CurrentStylesheet
{
176 get { return currentStyle; }
179 public void PushStylesheet (XslStylesheet style
)
181 if (currentStyle
!= null) styleStack
.Push (currentStyle
);
182 currentStyle
= style
;
185 public void PopStylesheet ()
187 if (styleStack
.Count
== 0)
190 currentStyle
= (XslStylesheet
)styleStack
.Pop ();
193 public void PushInputDocument (string url
)
195 // todo: detect recursion
196 Uri baseUriObj
= (Input
.BaseURI
== String
.Empty
) ? null : new Uri (Input
.BaseURI
);
197 Uri absUri
= res
.ResolveUri (baseUriObj
, url
);
198 using (Stream s
= (Stream
)res
.GetEntity (absUri
, null, typeof(Stream
)))
201 XmlValidatingReader vr
= new XmlValidatingReader (new XmlTextReader (absUri
.ToString (), s
, nsMgr
.NameTable
));
202 vr
.ValidationType
= ValidationType
.None
;
203 XPathNavigator n
= new XPathDocument (vr
, XmlSpace
.Preserve
).CreateNavigator ();
205 n
.MoveToFirstChild ();
207 if (n
.NodeType
== XPathNodeType
.Element
)
209 } while (n
.MoveToNext ());
210 PushInputDocument (n
);
214 private void PushInputDocument (XPathNavigator nav
)
216 for (int i
= 0; i
< inputStack
.Count
; i
++) {
217 XPathNavigator cur
= (XPathNavigator
) inputStack
[i
];
218 if (cur
.BaseURI
== nav
.BaseURI
) {
219 IXmlLineInfo li
= currentInput
as IXmlLineInfo
;
220 throw new XsltCompileException (null,
221 currentInput
.BaseURI
,
222 li
!= null ? li
.LineNumber
: 0,
223 li
!= null ? li
.LinePosition
: 0);
226 if (currentInput
!= null)
227 inputStack
.Add (currentInput
);
231 public void PopInputDocument ()
233 int last
= inputStack
.Count
- 1;
234 currentInput
= (XPathNavigator
) inputStack
[last
];
235 inputStack
.RemoveAt (last
);
238 public QName
ParseQNameAttribute (string localName
)
240 return ParseQNameAttribute (localName
, String
.Empty
);
242 public QName
ParseQNameAttribute (string localName
, string ns
)
244 return XslNameUtil
.FromString (Input
.GetAttribute (localName
, ns
), Input
);
247 public QName
[] ParseQNameListAttribute (string localName
)
249 return ParseQNameListAttribute (localName
, String
.Empty
);
252 public QName
[] ParseQNameListAttribute (string localName
, string ns
)
254 string s
= GetAttribute (localName
, ns
);
255 if (s
== null) return null;
257 string [] names
= s
.Split (new char [] {' ', '\r', '\n', '\t'}
);
258 QName
[] ret
= new QName
[names
.Length
];
260 for (int i
= 0; i
< names
.Length
; i
++)
261 ret
[i
] = XslNameUtil
.FromString (names
[i
], Input
);
266 public bool ParseYesNoAttribute (string localName
, bool defaultVal
)
268 return ParseYesNoAttribute (localName
, String
.Empty
, defaultVal
);
271 public bool ParseYesNoAttribute (string localName
, string ns
, bool defaultVal
)
273 string s
= GetAttribute (localName
, ns
);
276 case null: return defaultVal
;
277 case "yes": return true;
278 case "no": return false;
280 throw new XsltCompileException ("invalid value for " + localName
, null, Input
);
284 public string GetAttribute (string localName
)
286 return GetAttribute (localName
, String
.Empty
);
289 public string GetAttribute (string localName
, string ns
)
291 if (!Input
.MoveToAttribute (localName
, ns
))
294 string ret
= Input
.Value
;
295 Input
.MoveToParent ();
298 public XslAvt
ParseAvtAttribute (string localName
)
300 return ParseAvtAttribute (localName
, String
.Empty
);
302 public XslAvt
ParseAvtAttribute (string localName
, string ns
)
304 return ParseAvt (GetAttribute (localName
, ns
));
307 public void AssertAttribute (string localName
)
309 AssertAttribute (localName
, "");
311 public void AssertAttribute (string localName
, string ns
)
313 if (Input
.GetAttribute (localName
, ns
) == null)
314 throw new XsltCompileException ("Was expecting the " + localName
+ " attribute.", null, Input
);
317 public XslAvt
ParseAvt (string s
)
319 if (s
== null) return null;
320 return new XslAvt (s
, this);
326 public Pattern
CompilePattern (string pattern
, XPathNavigator loc
)
328 if (pattern
== null || pattern
== "") return null;
329 Pattern p
= Pattern
.Compile (pattern
, this);
331 throw new XsltCompileException (String
.Format ("Invalid pattern '{0}'.", pattern
), null, loc
);
332 exprStore
.AddPattern (p
, this);
337 internal XPathParser parser
;
338 internal CompiledExpression
CompileExpression (string expression
)
340 return CompileExpression (expression
, false);
343 internal CompiledExpression
CompileExpression (string expression
, bool isKey
)
345 if (expression
== null || expression
== "") return null;
347 Expression expr
= parser
.Compile (expression
);
349 expr
= new ExprKeyContainer (expr
);
350 CompiledExpression e
= new CompiledExpression (expression
, expr
);
352 exprStore
.AddExpression (e
, this);
357 public XslOperation
CompileTemplateContent ()
359 return CompileTemplateContent (XPathNodeType
.All
);
362 public XslOperation
CompileTemplateContent (XPathNodeType parentType
)
364 return new XslTemplateContent (this, parentType
);
368 public void AddGlobalVariable (XslGlobalVariable
var)
370 globalVariables
[var.Name
] = var;
373 public void AddAttributeSet (XslAttributeSet
set)
375 XslAttributeSet existing
= attrSets
[set.Name
] as XslAttributeSet
;
376 // The latter set will have higher priority
377 if (existing
!= null) {
378 existing
.Merge (set);
379 attrSets
[set.Name
] = existing
;
382 attrSets
[set.Name
] = set;
385 VariableScope curVarScope
;
387 public void PushScope ()
389 curVarScope
= new VariableScope (curVarScope
);
392 public VariableScope
PopScope ()
394 curVarScope
.giveHighTideToParent ();
395 VariableScope cur
= curVarScope
;
396 curVarScope
= curVarScope
.Parent
;
400 public int AddVariable (XslLocalVariable v
)
402 if (curVarScope
== null)
403 throw new XsltCompileException ("Not initialized variable", null, Input
);
405 return curVarScope
.AddVariable (v
);
408 public void AddSort (XPathExpression e
, Sort s
)
410 exprStore
.AddSort (e
, s
);
412 public VariableScope CurrentVariableScope { get { return curVarScope; }}
415 #region Scope (version, {excluded, extension} namespaces)
416 [MonoTODO ("This will work, but is *very* slow")]
417 public bool IsExtensionNamespace (string nsUri
)
419 if (nsUri
== XsltNamespace
) return true;
421 XPathNavigator nav
= Input
.Clone ();
422 XPathNavigator nsScope
= nav
.Clone ();
424 bool isXslt
= nav
.NamespaceURI
== XsltNamespace
;
425 nsScope
.MoveTo (nav
);
426 if (nav
.MoveToFirstAttribute ()) {
428 if (nav
.LocalName
== "extension-element-prefixes" &&
429 nav
.NamespaceURI
== (isXslt
? String
.Empty
: XsltNamespace
))
432 foreach (string ns
in nav
.Value
.Split (' '))
433 if (nsScope
.GetNamespace (ns
== "#default" ? "" : ns
) == nsUri
)
436 } while (nav
.MoveToNextAttribute ());
439 } while (nav
.MoveToParent ());
444 public Hashtable
GetNamespacesToCopy ()
446 Hashtable ret
= new Hashtable ();
448 XPathNavigator nav
= Input
.Clone ();
449 XPathNavigator nsScope
= nav
.Clone ();
451 if (nav
.MoveToFirstNamespace (XPathNamespaceScope
.Local
)) {
453 if (nav
.Value
!= XsltNamespace
&& !ret
.Contains (nav
.Name
))
454 ret
.Add (nav
.Name
, nav
.Value
);
455 } while (nav
.MoveToNextNamespace (XPathNamespaceScope
.Local
));
460 bool isXslt
= nav
.NamespaceURI
== XsltNamespace
;
461 nsScope
.MoveTo (nav
);
463 if (nav
.MoveToFirstAttribute()) {
465 if ((nav
.LocalName
== "extension-element-prefixes" || nav
.LocalName
== "exclude-result-prefixes") &&
466 nav
.NamespaceURI
== (isXslt
? String
.Empty
: XsltNamespace
))
468 foreach (string ns
in nav
.Value
.Split (' ')) {
469 string realNs
= ns
== "#default" ? "" : ns
;
471 if ((string)ret
[realNs
] == nsScope
.GetNamespace (realNs
))
475 } while (nav
.MoveToNextAttribute ());
478 } while (nav
.MoveToParent ());
484 #region Decimal Format
485 Hashtable decimalFormats
= new Hashtable ();
487 public void CompileDecimalFormat ()
489 QName nm
= ParseQNameAttribute ("name");
491 if (nm
.Name
!= String
.Empty
)
492 XmlConvert
.VerifyNCName (nm
.Name
);
493 } catch (XmlException ex
) {
494 throw new XsltCompileException ("Invalid qualified name.", ex
, Input
);
496 XslDecimalFormat df
= new XslDecimalFormat (this);
498 if (decimalFormats
.Contains (nm
))
499 ((XslDecimalFormat
)decimalFormats
[nm
]).CheckSameAs (df
);
501 decimalFormats
[nm
] = df
;
504 #region Static XSLT context
505 Expression IStaticXsltContext
.TryGetVariable (string nm
)
507 if (curVarScope
== null)
510 XslLocalVariable
var = curVarScope
.ResolveStatic (XslNameUtil
.FromString (nm
, Input
));
515 return new XPathVariableBinding (var);
518 Expression IStaticXsltContext
.TryGetFunction (QName name
, FunctionArguments args
)
520 string ns
= GetNsm ().LookupNamespace (name
.Namespace
, false);
521 if (ns
== XslStylesheet
.MSXsltNamespace
&& name
.Name
== "node-set")
522 return new MSXslNodeSet (args
);
528 case "current": return new XsltCurrent (args
);
529 case "unparsed-entity-uri": return new XsltUnparsedEntityUri (args
);
530 case "element-available": return new XsltElementAvailable (args
, this);
531 case "system-property": return new XsltSystemProperty (args
, this);
532 case "function-available": return new XsltFunctionAvailable (args
, this);
533 case "generate-id": return new XsltGenerateId (args
);
534 case "format-number": return new XsltFormatNumber (args
, this);
536 if (KeyCompilationMode
)
537 throw new XsltCompileException ("Cannot use key() function inside key definition.", null, this.Input
);
538 return new XsltKey (args
, this);
539 case "document": return new XsltDocument (args
, this);
545 QName IStaticXsltContext
.LookupQName (string s
)
547 return XslNameUtil
.FromString (s
, Input
);
550 XmlNamespaceManager IStaticXsltContext
.GetNsm ()
552 return new XPathNavigatorNsm (Input
);
555 public XmlNamespaceManager
GetNsm ()
557 return new XPathNavigatorNsm (Input
);
560 public void CompileOutput ()
562 XPathNavigator n
= Input
;
563 string uri
= n
.GetAttribute ("href", "");
564 XslOutput output
= outputs
[uri
] as XslOutput
;
565 if (output
== null) {
566 output
= new XslOutput (uri
);
567 outputs
.Add (uri
, output
);
573 internal class VariableScope
{
575 VariableScope parent
;
577 int highTide
= 0; // this will be the size of the stack frame
579 internal void giveHighTideToParent ()
582 parent
.highTide
= System
.Math
.Max (VariableHighTide
, parent
.VariableHighTide
);
585 public int VariableHighTide { get { return System.Math.Max (highTide, nextSlot); }}
587 public VariableScope (VariableScope parent
)
589 this.parent
= parent
;
591 this.nextSlot
= parent
.nextSlot
;
594 public VariableScope Parent { get { return parent; }}
596 public int AddVariable (XslLocalVariable v
)
598 if (variables
== null)
599 variables
= new Hashtable ();
601 variables
[v
.Name
] = v
;
605 public XslLocalVariable
ResolveStatic (QName name
)
607 for (VariableScope s
= this; s
!= null; s
= s
.Parent
) {
608 if (s
.variables
== null) continue;
609 XslLocalVariable v
= s
.variables
[name
] as XslLocalVariable
;
610 if (v
!= null) return v
;
615 public XslLocalVariable
Resolve (XslTransformProcessor p
, QName name
)
617 for (VariableScope s
= this; s
!= null; s
= s
.Parent
) {
618 if (s
.variables
== null) continue;
619 XslLocalVariable v
= s
.variables
[name
] as XslLocalVariable
;
620 if (v
!= null && v
.IsEvaluated (p
))
628 internal class Sort
{
630 XmlDataType dataType
;
632 XmlCaseOrder caseOrder
;
634 XslAvt langAvt
, dataTypeAvt
, orderAvt
, caseOrderAvt
;
635 XPathExpression expr
;
637 public Sort (Compiler c
)
639 expr
= c
.CompileExpression (c
.GetAttribute ("select"));
641 expr
= c
.CompileExpression ("string(.)");
643 langAvt
= c
.ParseAvtAttribute ("lang");
644 dataTypeAvt
= c
.ParseAvtAttribute ("data-type");
645 orderAvt
= c
.ParseAvtAttribute ("order");
646 caseOrderAvt
= c
.ParseAvtAttribute ("case-order");
648 // Precalc whatever we can
649 lang
= ParseLang (XslAvt
.AttemptPreCalc (ref langAvt
));
650 dataType
= ParseDataType (XslAvt
.AttemptPreCalc (ref dataTypeAvt
));
651 order
= ParseOrder (XslAvt
.AttemptPreCalc (ref orderAvt
));
652 caseOrder
= ParseCaseOrder (XslAvt
.AttemptPreCalc (ref caseOrderAvt
));
656 string ParseLang (string value)
661 XmlDataType
ParseDataType (string value)
666 return XmlDataType
.Number
;
670 return XmlDataType
.Text
;
674 XmlSortOrder
ParseOrder (string value)
679 return XmlSortOrder
.Descending
;
683 return XmlSortOrder
.Ascending
;
687 XmlCaseOrder
ParseCaseOrder (string value)
692 return XmlCaseOrder
.UpperFirst
;
694 return XmlCaseOrder
.LowerFirst
;
697 return XmlCaseOrder
.None
;
702 public void AddToExpr (XPathExpression e
, XslTransformProcessor p
)
706 orderAvt
== null ? order
: ParseOrder (orderAvt
.Evaluate (p
)),
707 caseOrderAvt
== null ? caseOrder
: ParseCaseOrder (caseOrderAvt
.Evaluate (p
)),
708 langAvt
== null ? lang
: ParseLang (langAvt
.Evaluate (p
)),
709 dataTypeAvt
== null ? dataType
: ParseDataType (dataTypeAvt
.Evaluate (p
))
714 internal class ExpressionStore
{
715 Hashtable exprToSorts
;
717 public void AddExpression (XPathExpression e
, Compiler c
)
721 public void AddPattern (Pattern p
, Compiler c
)
725 public void AddSort (XPathExpression e
, Sort s
)
727 if (exprToSorts
== null)
728 exprToSorts
= new Hashtable ();
730 if (exprToSorts
.Contains (e
))
731 ((ArrayList
)exprToSorts
[e
]).Add (s
);
733 ArrayList a
= new ArrayList ();
739 public XPathExpression
PrepForExecution (XPathExpression e
, XslTransformProcessor p
)
741 if (exprToSorts
!= null && exprToSorts
.Contains (e
))
743 XPathExpression expr
= e
.Clone ();
744 foreach (Sort s
in (ArrayList
)exprToSorts
[e
])
745 s
.AddToExpr (expr
,p
);
751 public bool PatternMatches (Pattern p
, XslTransformProcessor proc
, XPathNavigator n
)
753 return p
.Matches (n
, proc
.XPathContext
);
757 internal class XslNameUtil
759 public static QName
[] FromListString (string names
, XPathNavigator current
)
761 string [] nameArray
= names
.Split (XmlChar
.WhitespaceChars
);
763 for (int i
= 0; i
< nameArray
.Length
; i
++)
764 if (nameArray
[i
] != String
.Empty
)
767 XmlQualifiedName
[] qnames
= new XmlQualifiedName
[idx
];
770 for (int i
= 0; i
< nameArray
.Length
; i
++)
771 if (nameArray
[i
] != String
.Empty
)
772 qnames
[idx
++] = FromString (nameArray
[i
], current
, true);
777 public static QName
FromString (string name
, XPathNavigator current
)
779 return FromString (name
, current
, false);
782 public static QName
FromString (string name
, XPathNavigator current
, bool useDefaultXmlns
)
784 if (current
.NodeType
== XPathNodeType
.Attribute
)
785 (current
= current
.Clone ()).MoveToParent ();
787 int colon
= name
.IndexOf (':');
789 return new QName (name
.Substring (colon
+ 1), current
.GetNamespace (name
.Substring (0, colon
)));
791 return new QName (name
, useDefaultXmlns
? current
.GetNamespace (String
.Empty
) : "");
793 throw new ArgumentException ("Invalid name: " + name
);
796 public static QName
FromString (string name
, XmlNamespaceManager ctx
)
798 int colon
= name
.IndexOf (':');
800 return new QName (name
.Substring (colon
+ 1), ctx
.LookupNamespace (name
.Substring (0, colon
), false));
802 // Default namespace is not used for unprefixed names.
803 return new QName (name
, "");
805 throw new ArgumentException ("Invalid name: " + name
);
808 public static string LocalNameOf (string name
)
810 int colon
= name
.IndexOf (':');
812 return name
.Substring (colon
+ 1);
816 throw new ArgumentException ("Invalid name: " + name
);
820 internal class XPathNavigatorNsm
: XmlNamespaceManager
{
821 XPathNavigator nsScope
;
823 public XPathNavigatorNsm (XPathNavigator n
) : base (n
.NameTable
) {
824 nsScope
= n
.Clone ();
825 if (nsScope
.NodeType
== XPathNodeType
.Attribute
)
826 nsScope
.MoveToParent ();
829 public override string DefaultNamespace { get { return String.Empty; }}
832 public override string LookupNamespace (string prefix
, bool atomizedNames
)
834 internal override string LookupNamespace (string prefix
, bool atomizedNames
)
837 if (prefix
== "" || prefix
== null)
840 return nsScope
.GetNamespace (prefix
);