Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Runtime / XsltLibrary.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XsltLibrary.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
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;
16
17 namespace System.Xml.Xsl.Runtime {
18     using Res = System.Xml.Utils.Res;
19
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");
24
25         // Runtime type checks and casts
26         public static readonly MethodInfo EnsureNodeSet         = GetMethod(typeof(XsltConvert), "EnsureNodeSet"    , typeof(IList<XPathItem>));
27
28         // Comparisons
29         public static readonly MethodInfo EqualityOperator      = GetMethod(typeof(XsltLibrary), "EqualityOperator");
30         public static readonly MethodInfo RelationalOperator    = GetMethod(typeof(XsltLibrary), "RelationalOperator");
31
32         // XPath functions
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");
45
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");
51
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" );
59
60         // EXSLT functions
61         public static readonly MethodInfo EXslObjectType        = GetMethod(typeof(XsltFunctions), "EXslObjectType");
62
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");
74
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");
78             return methInfo;
79         }
80
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");
84             return methInfo;
85         }
86     }
87
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;
94
95         internal XsltLibrary(XmlQueryRuntime runtime) {
96             this.runtime = runtime;
97         }
98
99         public string FormatMessage(string res, IList<string> args) {
100             string[] arr = new string[args.Count];
101
102             for (int i = 0; i < arr.Length; i++)
103                 arr[i] = args[i];
104
105             return XslTransformException.CreateMessage(res, arr);
106         }
107
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);
112             }
113             return 0;   // have to return something
114         }
115
116         // Spec: http://www.w3.org/TR/xslt#function-element-available
117         public bool ElementAvailable(XmlQualifiedName name) {
118             return QilGenerator.IsElementAvailable(name);
119         }
120
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();
125             } else {
126                 object obj = functionsAvail[name];
127                 if (obj != null) {
128                     return (bool)obj;
129                 }
130             }
131
132             bool result = FunctionAvailableHelper(name);
133             functionsAvail[name] = result;
134             return result;
135         }
136
137         private bool FunctionAvailableHelper(XmlQualifiedName name) {
138             // Is this an XPath or an XSLT function?
139             if (QilGenerator.IsFunctionAvailable(name.Name, name.Namespace)) {
140                 return true;
141             }
142
143             // Script blocks and extension objects cannot implement neither null nor XSLT namespace
144             if (name.Namespace.Length == 0 || name.Namespace == XmlReservedNs.NsXslt) {
145                 return false;
146             }
147
148             // Is this an extension object function?
149             if (runtime.ExternalContext.LateBoundFunctionExists(name.Name, name.Namespace)) {
150                 return true;
151             }
152
153             // Is this a script function?
154             return runtime.EarlyBoundFunctionExists(name.Name, name.Namespace);
155         }
156
157         public int RegisterDecimalFormat(XmlQualifiedName name, string infinitySymbol, string nanSymbol, string characters) {
158             if (decimalFormats == null) {
159                 decimalFormats = new Dictionary<XmlQualifiedName, DecimalFormat>();
160             }
161             decimalFormats.Add(name, CreateDecimalFormat(infinitySymbol, nanSymbol, characters));
162             return 0;   // have to return something
163         }
164
165         private DecimalFormat CreateDecimalFormat(string infinitySymbol, string nanSymbol, string characters) {
166             // 
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;
176
177             return new DecimalFormat(info, characters[5], characters[4], characters[6]);
178         }
179
180         public double RegisterDecimalFormatter(string formatPicture, string infinitySymbol, string nanSymbol, string characters) {
181             if (decimalFormatters == null) {
182                 decimalFormatters = new List<DecimalFormatter>();
183             }
184             decimalFormatters.Add(new DecimalFormatter(formatPicture, CreateDecimalFormat(infinitySymbol, nanSymbol, characters)));
185             return decimalFormatters.Count - 1;
186
187         }
188
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);
193         }
194
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);
199             }
200
201             DecimalFormatter formatter = new DecimalFormatter(formatPicture, format);
202             return formatter.Format(value);
203         }
204
205         public string NumberFormat(IList<XPathItem> value, string formatString,
206             double lang, string letterValue, string groupingSeparator, double groupingSize)
207         {
208             // 
209
210             NumberFormatter formatter = new NumberFormatter(formatString, (int)lang, letterValue, groupingSeparator, (int)groupingSize);
211             return formatter.FormatSequence(value);
212         }
213
214         internal const int InvariantCultureLcid = 0x007f;
215
216         public int LangToLcid(string lang, bool forwardCompatibility) {
217             return LangToLcidInternal(lang, forwardCompatibility, null);
218         }
219
220         internal static int LangToLcidInternal(string lang, bool forwardCompatibility, IErrorHelper errorHelper) {
221             int lcid = InvariantCultureLcid;
222
223             if (lang != null) {
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);
229                         } else {
230                             throw new XslTransformException(Res.Xslt_InvalidAttrValue, "lang", lang);
231                         }
232                     }
233                 } else {
234                     // Check if lang is a supported culture name
235                     try {
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);
241                             } else {
242                                 throw new XslTransformException(Res.Xslt_InvalidLanguage, lang);
243                             }
244                         }
245                     }
246                 }
247             }
248             return lcid;
249         }
250
251         #region Comparisons
252         internal enum ComparisonOperator {
253             /*Equality  */ Eq, Ne,
254             /*Relational*/ Lt, Le, Gt, Ge,
255         }
256
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;
266             } else {
267                 Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString());
268                 return TypeCode.Boolean;
269             }
270         }
271
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;
276         }
277
278         private static bool CompareNumbers(ComparisonOperator op, double left, double right) {
279             switch (op) {
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;
286             }
287         }
288
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));
292             } else {
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);
296                 } else {
297                     Debug.Assert(compType == TypeCode.Boolean);
298                     return (XsltConvert.ToBoolean(left) == XsltConvert.ToBoolean(right)) == (op == ComparisonOperator.Eq);
299                 }
300             }
301         }
302
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);
308             } else {
309                 int length = nodeset.Count;
310                 for (int idx = 0; idx < length; idx++) {
311                     if (CompareValues(op, nodeset[idx], val, compType)) {
312                         return true;
313                     }
314                 }
315                 return false;
316             }
317         }
318
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)) {
325                         return true;
326                     }
327                 }
328             }
329             return false;
330         }
331
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);
337
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);
342                 } else {
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));
346                 }
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));
352             } else {
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)));
357             }
358         }
359
360         // Inverts relational operator in order to swap operands of the comparison
361         private static ComparisonOperator InvertOperator(ComparisonOperator op) {
362             switch (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;
367             default:                    return op;
368             }
369         }
370
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);
376
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);
381                 } else {
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));
385                 }
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));
392             } else {
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);
397             }
398         }
399         #endregion
400
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);
405
406             XPathNodeType nt1 = nav1.NodeType;
407             XPathNodeType nt2 = nav2.NodeType;
408
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;
412             }
413
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);
417         }
418
419
420         //------------------------------------------------
421         // Helper methods
422         //------------------------------------------------
423
424         [Conditional("DEBUG")]
425         internal static void CheckXsltValue(XPathItem item) {
426             CheckXsltValue(new XmlQueryItemSequence(item));
427         }
428
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");
433
434             if (val.Count == 1) {
435                 XsltFunctions.EXslObjectType(val);
436             } else {
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");
443                         break;
444                     }
445                     if (idx == 1) {
446                         idx += Math.Max(count - 4, 0);
447                     }
448                 }
449             }
450         }
451
452         private static bool IsNodeSetOrRtf(IList<XPathItem> val) {
453             CheckXsltValue(val);
454             if (val.Count == 1) {
455                 return val[0].IsNode;
456             }
457             return true;
458         }
459
460         private static IList<XPathNavigator> ToNodeSetOrRtf(IList<XPathItem> val) {
461             return XmlILStorageConverter.ItemsToNavigators(val);
462         }
463     }
464 }