1 //------------------------------------------------------------------------------
2 // <copyright file="XsltLibrary.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System.Collections.Specialized;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Globalization;
12 using System.Reflection;
13 using System.Xml.XPath;
14 using System.Xml.Xsl.Xslt;
15 using System.ComponentModel;
17 namespace System.Xml.Xsl.Runtime {
18 using Res = System.Xml.Utils.Res;
20 // List of all XPath/XSLT runtime methods
21 internal static class XsltMethods {
22 // Formatting error messages
23 public static readonly MethodInfo FormatMessage = GetMethod(typeof(XsltLibrary), "FormatMessage");
25 // Runtime type checks and casts
26 public static readonly MethodInfo EnsureNodeSet = GetMethod(typeof(XsltConvert), "EnsureNodeSet" , typeof(IList<XPathItem>));
29 public static readonly MethodInfo EqualityOperator = GetMethod(typeof(XsltLibrary), "EqualityOperator");
30 public static readonly MethodInfo RelationalOperator = GetMethod(typeof(XsltLibrary), "RelationalOperator");
33 public static readonly MethodInfo StartsWith = GetMethod(typeof(XsltFunctions), "StartsWith");
34 public static readonly MethodInfo Contains = GetMethod(typeof(XsltFunctions), "Contains");
35 public static readonly MethodInfo SubstringBefore = GetMethod(typeof(XsltFunctions), "SubstringBefore");
36 public static readonly MethodInfo SubstringAfter = GetMethod(typeof(XsltFunctions), "SubstringAfter");
37 public static readonly MethodInfo Substring2 = GetMethod(typeof(XsltFunctions), "Substring", typeof(string), typeof(double));
38 public static readonly MethodInfo Substring3 = GetMethod(typeof(XsltFunctions), "Substring", typeof(string), typeof(double), typeof(double));
39 public static readonly MethodInfo NormalizeSpace = GetMethod(typeof(XsltFunctions), "NormalizeSpace");
40 public static readonly MethodInfo Translate = GetMethod(typeof(XsltFunctions), "Translate");
41 public static readonly MethodInfo Lang = GetMethod(typeof(XsltFunctions), "Lang");
42 public static readonly MethodInfo Floor = GetMethod(typeof(Math) , "Floor" , typeof(double));
43 public static readonly MethodInfo Ceiling = GetMethod(typeof(Math) , "Ceiling", typeof(double));
44 public static readonly MethodInfo Round = GetMethod(typeof(XsltFunctions), "Round");
46 // XSLT functions and helper methods (static)
47 public static readonly MethodInfo SystemProperty = GetMethod(typeof(XsltFunctions), "SystemProperty");
48 public static readonly MethodInfo BaseUri = GetMethod(typeof(XsltFunctions), "BaseUri");
49 public static readonly MethodInfo OuterXml = GetMethod(typeof(XsltFunctions), "OuterXml");
50 public static readonly MethodInfo OnCurrentNodeChanged = GetMethod(typeof(XmlQueryRuntime), "OnCurrentNodeChanged");
52 // MSXML extension functions
53 public static readonly MethodInfo MSFormatDateTime = GetMethod(typeof(XsltFunctions), "MSFormatDateTime");
54 public static readonly MethodInfo MSStringCompare = GetMethod(typeof(XsltFunctions), "MSStringCompare");
55 public static readonly MethodInfo MSUtc = GetMethod(typeof(XsltFunctions), "MSUtc" );
56 public static readonly MethodInfo MSNumber = GetMethod(typeof(XsltFunctions), "MSNumber" );
57 public static readonly MethodInfo MSLocalName = GetMethod(typeof(XsltFunctions), "MSLocalName" );
58 public static readonly MethodInfo MSNamespaceUri = GetMethod(typeof(XsltFunctions), "MSNamespaceUri" );
61 public static readonly MethodInfo EXslObjectType = GetMethod(typeof(XsltFunctions), "EXslObjectType");
63 // XSLT functions and helper methods (non-static)
64 public static readonly MethodInfo CheckScriptNamespace = GetMethod(typeof(XsltLibrary), "CheckScriptNamespace");
65 public static readonly MethodInfo FunctionAvailable = GetMethod(typeof(XsltLibrary), "FunctionAvailable");
66 public static readonly MethodInfo ElementAvailable = GetMethod(typeof(XsltLibrary), "ElementAvailable");
67 public static readonly MethodInfo RegisterDecimalFormat = GetMethod(typeof(XsltLibrary), "RegisterDecimalFormat");
68 public static readonly MethodInfo RegisterDecimalFormatter = GetMethod(typeof(XsltLibrary), "RegisterDecimalFormatter");
69 public static readonly MethodInfo FormatNumberStatic = GetMethod(typeof(XsltLibrary), "FormatNumberStatic");
70 public static readonly MethodInfo FormatNumberDynamic = GetMethod(typeof(XsltLibrary), "FormatNumberDynamic");
71 public static readonly MethodInfo IsSameNodeSort = GetMethod(typeof(XsltLibrary), "IsSameNodeSort");
72 public static readonly MethodInfo LangToLcid = GetMethod(typeof(XsltLibrary), "LangToLcid");
73 public static readonly MethodInfo NumberFormat = GetMethod(typeof(XsltLibrary), "NumberFormat");
75 public static MethodInfo GetMethod(Type className, string methName) {
76 MethodInfo methInfo = className.GetMethod(methName);
77 Debug.Assert(methInfo != null, "Method " + className.Name + "." + methName + " not found");
81 public static MethodInfo GetMethod(Type className, string methName, params Type[] args) {
82 MethodInfo methInfo = className.GetMethod(methName, args);
83 Debug.Assert(methInfo != null, "Method " + className.Name + "." + methName + " not found");
88 [EditorBrowsable(EditorBrowsableState.Never)]
89 public sealed class XsltLibrary {
90 private XmlQueryRuntime runtime;
91 private HybridDictionary functionsAvail;
92 private Dictionary<XmlQualifiedName, DecimalFormat> decimalFormats;
93 private List<DecimalFormatter> decimalFormatters;
95 internal XsltLibrary(XmlQueryRuntime runtime) {
96 this.runtime = runtime;
99 public string FormatMessage(string res, IList<string> args) {
100 string[] arr = new string[args.Count];
102 for (int i = 0; i < arr.Length; i++)
105 return XslTransformException.CreateMessage(res, arr);
108 public int CheckScriptNamespace(string nsUri) {
109 // Check that extension and script namespaces do not clash
110 if (runtime.ExternalContext.GetLateBoundObject(nsUri) != null) {
111 throw new XslTransformException(Res.Xslt_ScriptAndExtensionClash, nsUri);
113 return 0; // have to return something
116 // Spec: http://www.w3.org/TR/xslt#function-element-available
117 public bool ElementAvailable(XmlQualifiedName name) {
118 return QilGenerator.IsElementAvailable(name);
121 // Spec: http://www.w3.org/TR/xslt#function-function-available
122 public bool FunctionAvailable(XmlQualifiedName name) {
123 if (functionsAvail == null) {
124 functionsAvail = new HybridDictionary();
126 object obj = functionsAvail[name];
132 bool result = FunctionAvailableHelper(name);
133 functionsAvail[name] = result;
137 private bool FunctionAvailableHelper(XmlQualifiedName name) {
138 // Is this an XPath or an XSLT function?
139 if (QilGenerator.IsFunctionAvailable(name.Name, name.Namespace)) {
143 // Script blocks and extension objects cannot implement neither null nor XSLT namespace
144 if (name.Namespace.Length == 0 || name.Namespace == XmlReservedNs.NsXslt) {
148 // Is this an extension object function?
149 if (runtime.ExternalContext.LateBoundFunctionExists(name.Name, name.Namespace)) {
153 // Is this a script function?
154 return runtime.EarlyBoundFunctionExists(name.Name, name.Namespace);
157 public int RegisterDecimalFormat(XmlQualifiedName name, string infinitySymbol, string nanSymbol, string characters) {
158 if (decimalFormats == null) {
159 decimalFormats = new Dictionary<XmlQualifiedName, DecimalFormat>();
161 decimalFormats.Add(name, CreateDecimalFormat(infinitySymbol, nanSymbol, characters));
162 return 0; // have to return something
165 private DecimalFormat CreateDecimalFormat(string infinitySymbol, string nanSymbol, string characters) {
167 NumberFormatInfo info = new NumberFormatInfo();
168 info.NumberDecimalSeparator = char.ToString(characters[0]);
169 info.NumberGroupSeparator = char.ToString(characters[1]);
170 info.PositiveInfinitySymbol = infinitySymbol;
171 info.NegativeSign = char.ToString(characters[7]);
172 info.NaNSymbol = nanSymbol;
173 info.PercentSymbol = char.ToString(characters[2]);
174 info.PerMilleSymbol = char.ToString(characters[3]);
175 info.NegativeInfinitySymbol = info.NegativeSign + info.PositiveInfinitySymbol;
177 return new DecimalFormat(info, characters[5], characters[4], characters[6]);
180 public double RegisterDecimalFormatter(string formatPicture, string infinitySymbol, string nanSymbol, string characters) {
181 if (decimalFormatters == null) {
182 decimalFormatters = new List<DecimalFormatter>();
184 decimalFormatters.Add(new DecimalFormatter(formatPicture, CreateDecimalFormat(infinitySymbol, nanSymbol, characters)));
185 return decimalFormatters.Count - 1;
189 public string FormatNumberStatic(double value, double decimalFormatterIndex) {
190 int idx = (int)decimalFormatterIndex;
191 Debug.Assert(0 <= idx && idx < decimalFormatters.Count, "Value of decimalFormatterIndex is out of range");
192 return decimalFormatters[idx].Format(value);
195 public string FormatNumberDynamic(double value, string formatPicture, XmlQualifiedName decimalFormatName, string errorMessageName) {
196 DecimalFormat format;
197 if (decimalFormats == null || ! decimalFormats.TryGetValue(decimalFormatName, out format)) {
198 throw new XslTransformException(Res.Xslt_NoDecimalFormat, errorMessageName);
201 DecimalFormatter formatter = new DecimalFormatter(formatPicture, format);
202 return formatter.Format(value);
205 public string NumberFormat(IList<XPathItem> value, string formatString,
206 double lang, string letterValue, string groupingSeparator, double groupingSize)
210 NumberFormatter formatter = new NumberFormatter(formatString, (int)lang, letterValue, groupingSeparator, (int)groupingSize);
211 return formatter.FormatSequence(value);
214 internal const int InvariantCultureLcid = 0x007f;
216 public int LangToLcid(string lang, bool forwardCompatibility) {
217 return LangToLcidInternal(lang, forwardCompatibility, null);
220 internal static int LangToLcidInternal(string lang, bool forwardCompatibility, IErrorHelper errorHelper) {
221 int lcid = InvariantCultureLcid;
224 // The value of the 'lang' attribute must be a non-empty nmtoken
225 if (lang.Length == 0) {
226 if (!forwardCompatibility) {
227 if (errorHelper != null) {
228 errorHelper.ReportError(/*[XT_032]*/Res.Xslt_InvalidAttrValue, "lang", lang);
230 throw new XslTransformException(Res.Xslt_InvalidAttrValue, "lang", lang);
234 // Check if lang is a supported culture name
236 lcid = new CultureInfo(lang).LCID;
237 } catch (System.ArgumentException) {
238 if (!forwardCompatibility) {
239 if (errorHelper != null) {
240 errorHelper.ReportError(/*[XT_033]*/Res.Xslt_InvalidLanguage, lang);
242 throw new XslTransformException(Res.Xslt_InvalidLanguage, lang);
252 internal enum ComparisonOperator {
253 /*Equality */ Eq, Ne,
254 /*Relational*/ Lt, Le, Gt, Ge,
257 // Returns TypeCode of the given atomic value
258 private static TypeCode GetTypeCode(XPathItem item) {
259 // Faster implementation of Type.GetTypeCode(item.ValueType);
260 Debug.Assert(!item.IsNode, "Atomic value expected");
261 Type itemType = item.ValueType;
262 if (itemType == XsltConvert.StringType) {
263 return TypeCode.String;
264 } else if (itemType == XsltConvert.DoubleType) {
265 return TypeCode.Double;
267 Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString());
268 return TypeCode.Boolean;
272 // Returns weakest of the two given TypeCodes, String > Double > Boolean
273 private static TypeCode WeakestTypeCode(TypeCode typeCode1, TypeCode typeCode2) {
274 Debug.Assert(TypeCode.Boolean < TypeCode.Double && TypeCode.Double < TypeCode.String, "Cannot use the smallest TypeCode as a weakest one");
275 return typeCode1 < typeCode2 ? typeCode1 : typeCode2;
278 private static bool CompareNumbers(ComparisonOperator op, double left, double right) {
280 case ComparisonOperator.Eq: return left == right;
281 case ComparisonOperator.Ne: return left != right;
282 case ComparisonOperator.Lt: return left < right;
283 case ComparisonOperator.Le: return left <= right;
284 case ComparisonOperator.Gt: return left > right;
285 default: return left >= right;
289 private static bool CompareValues(ComparisonOperator op, XPathItem left, XPathItem right, TypeCode compType) {
290 if (compType == TypeCode.Double) {
291 return CompareNumbers(op, XsltConvert.ToDouble(left), XsltConvert.ToDouble(right));
293 Debug.Assert(op == ComparisonOperator.Eq || op == ComparisonOperator.Ne);
294 if (compType == TypeCode.String) {
295 return (XsltConvert.ToString(left) == XsltConvert.ToString(right)) == (op == ComparisonOperator.Eq);
297 Debug.Assert(compType == TypeCode.Boolean);
298 return (XsltConvert.ToBoolean(left) == XsltConvert.ToBoolean(right)) == (op == ComparisonOperator.Eq);
303 private static bool CompareNodeSetAndValue(ComparisonOperator op, IList<XPathNavigator> nodeset, XPathItem val, TypeCode compType) {
304 Debug.Assert(compType == TypeCode.Boolean || compType == TypeCode.Double || compType == TypeCode.String);
305 if (compType == TypeCode.Boolean) {
306 // Cast nodeset to boolean type, then take its ordinal number
307 return CompareNumbers(op, (nodeset.Count != 0) ? 1 : 0, XsltConvert.ToBoolean(val) ? 1 : 0);
309 int length = nodeset.Count;
310 for (int idx = 0; idx < length; idx++) {
311 if (CompareValues(op, nodeset[idx], val, compType)) {
319 private static bool CompareNodeSetAndNodeSet(ComparisonOperator op, IList<XPathNavigator> left, IList<XPathNavigator> right, TypeCode compType) {
320 int leftLen = left.Count;
321 int rightLen = right.Count;
322 for (int leftIdx = 0; leftIdx < leftLen; leftIdx++) {
323 for (int rightIdx = 0; rightIdx < rightLen; rightIdx++) {
324 if (CompareValues(op, left[leftIdx], right[rightIdx], compType)) {
332 public bool EqualityOperator(double opCode, IList<XPathItem> left, IList<XPathItem> right) {
333 ComparisonOperator op = (ComparisonOperator)opCode;
334 Debug.Assert(op == ComparisonOperator.Eq || op == ComparisonOperator.Ne);
335 CheckXsltValue(left);
336 CheckXsltValue(right);
338 if (IsNodeSetOrRtf(left)) {
339 if (IsNodeSetOrRtf(right)) {
340 // Both left and right are node-sets
341 return CompareNodeSetAndNodeSet(op, ToNodeSetOrRtf(left), ToNodeSetOrRtf(right), TypeCode.String);
343 // left is a node-set, right is an atomic value
344 XPathItem rightItem = right[0];
345 return CompareNodeSetAndValue(op, ToNodeSetOrRtf(left), rightItem, GetTypeCode(rightItem));
347 } else if (IsNodeSetOrRtf(right)) {
348 // left is an atomic value, right is a node-set
349 XPathItem leftItem = left[0];
350 // Swap operands: left op right -> right op left
351 return CompareNodeSetAndValue(op, ToNodeSetOrRtf(right), leftItem, GetTypeCode(leftItem));
353 // Both left and right are atomic values
354 XPathItem leftItem = left[0];
355 XPathItem rightItem = right[0];
356 return CompareValues(op, leftItem, rightItem, WeakestTypeCode(GetTypeCode(leftItem), GetTypeCode(rightItem)));
360 // Inverts relational operator in order to swap operands of the comparison
361 private static ComparisonOperator InvertOperator(ComparisonOperator op) {
363 case ComparisonOperator.Lt: return ComparisonOperator.Gt;
364 case ComparisonOperator.Le: return ComparisonOperator.Ge;
365 case ComparisonOperator.Gt: return ComparisonOperator.Lt;
366 case ComparisonOperator.Ge: return ComparisonOperator.Le;
371 public bool RelationalOperator(double opCode, IList<XPathItem> left, IList<XPathItem> right) {
372 ComparisonOperator op = (ComparisonOperator)opCode;
373 Debug.Assert(ComparisonOperator.Lt <= op && op <= ComparisonOperator.Ge);
374 CheckXsltValue(left);
375 CheckXsltValue(right);
377 if (IsNodeSetOrRtf(left)) {
378 if (IsNodeSetOrRtf(right)) {
379 // Both left and right are node-sets
380 return CompareNodeSetAndNodeSet(op, ToNodeSetOrRtf(left), ToNodeSetOrRtf(right), TypeCode.Double);
382 // left is a node-set, right is an atomic value
383 XPathItem rightItem = right[0];
384 return CompareNodeSetAndValue(op, ToNodeSetOrRtf(left), rightItem, WeakestTypeCode(GetTypeCode(rightItem), TypeCode.Double));
386 } else if (IsNodeSetOrRtf(right)) {
387 // left is an atomic value, right is a node-set
388 XPathItem leftItem = left[0];
389 // Swap operands: left op right -> right InvertOperator(op) left
390 op = InvertOperator(op);
391 return CompareNodeSetAndValue(op, ToNodeSetOrRtf(right), leftItem, WeakestTypeCode(GetTypeCode(leftItem), TypeCode.Double));
393 // Both left and right are atomic values
394 XPathItem leftItem = left[0];
395 XPathItem rightItem = right[0];
396 return CompareValues(op, leftItem, rightItem, TypeCode.Double);
401 // nav1 and nav2 are assumed to belong to the same document
402 public bool IsSameNodeSort(XPathNavigator nav1, XPathNavigator nav2) {
403 Debug.Assert(XPathNodeType.SignificantWhitespace == XPathNodeType.Text + 1);
404 Debug.Assert(XPathNodeType.Whitespace == XPathNodeType.Text + 2);
406 XPathNodeType nt1 = nav1.NodeType;
407 XPathNodeType nt2 = nav2.NodeType;
409 // If one of nodes is a text node, the other one must also be a text node
410 if (XPathNodeType.Text <= nt1 && nt1 <= XPathNodeType.Whitespace) {
411 return XPathNodeType.Text <= nt2 && nt2 <= XPathNodeType.Whitespace;
414 // Otherwise nodes must have the same node kind, the same local name, and the same namespace URI
415 Debug.Assert((object)nav1.NameTable == (object)nav2.NameTable, "Ref.Equal cannot be used if navigators have different name tables");
416 return nt1 == nt2 && Ref.Equal(nav1.LocalName, nav2.LocalName) && Ref.Equal(nav1.NamespaceURI, nav2.NamespaceURI);
420 //------------------------------------------------
422 //------------------------------------------------
424 [Conditional("DEBUG")]
425 internal static void CheckXsltValue(XPathItem item) {
426 CheckXsltValue(new XmlQueryItemSequence(item));
429 [Conditional("DEBUG")]
430 internal static void CheckXsltValue(IList<XPathItem> val) {
431 // IsDocOrderDistinct is not always set to true even if the node-set is ordered
432 // Debug.Assert(val.Count <= 1 || val.IsDocOrderDistinct, "All node-sets must be ordered");
434 if (val.Count == 1) {
435 XsltFunctions.EXslObjectType(val);
437 // Every item must be a node, but for performance reasons we check only
438 // the first two and the last two items
439 int count = val.Count;
440 for (int idx = 0; idx < count; idx++) {
441 if (!val[idx].IsNode) {
442 Debug.Fail("Invalid XSLT value");
446 idx += Math.Max(count - 4, 0);
452 private static bool IsNodeSetOrRtf(IList<XPathItem> val) {
454 if (val.Count == 1) {
455 return val[0].IsNode;
460 private static IList<XPathNavigator> ToNodeSetOrRtf(IList<XPathItem> val) {
461 return XmlILStorageConverter.ItemsToNavigators(val);