Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Runtime / XsltFunctions.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XsltFunctions.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.IO;
9 using System.Text;
10 using System.Reflection;
11 using System.Diagnostics;
12 using System.ComponentModel;
13 using System.Globalization;
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.Collections.Specialized;
17 using System.Xml.Schema;
18 using System.Xml.XPath;
19 using System.Xml.Xsl.Xslt;
20 using System.Runtime.InteropServices;
21 using System.Runtime.Versioning;
22
23 namespace System.Xml.Xsl.Runtime {
24     using Res = System.Xml.Utils.Res;
25
26     [EditorBrowsable(EditorBrowsableState.Never)]
27     public static class XsltFunctions {
28         private static readonly CompareInfo compareInfo = CultureInfo.InvariantCulture.CompareInfo;
29
30
31         //------------------------------------------------
32         // Xslt/XPath functions
33         //------------------------------------------------
34
35         public static bool StartsWith(string s1, string s2) {
36             //return collation.IsPrefix(s1, s2);
37             return s1.Length >= s2.Length && string.CompareOrdinal(s1, 0, s2, 0, s2.Length) == 0;
38         }
39
40         public static bool Contains(string s1, string s2) {
41             //return collation.IndexOf(s1, s2) >= 0;
42             return compareInfo.IndexOf(s1, s2, CompareOptions.Ordinal) >= 0;
43         }
44
45         public static string SubstringBefore(string s1, string s2) {
46             if (s2.Length == 0) { return s2; }
47             //int idx = collation.IndexOf(s1, s2);
48             int idx = compareInfo.IndexOf(s1, s2, CompareOptions.Ordinal);
49             return (idx < 1) ? string.Empty : s1.Substring(0, idx);
50         }
51
52         public static string SubstringAfter(string s1, string s2) {
53             if (s2.Length == 0) { return s1; }
54             //int idx = collation.IndexOf(s1, s2);
55             int idx = compareInfo.IndexOf(s1, s2, CompareOptions.Ordinal);
56             return (idx < 0) ? string.Empty : s1.Substring(idx + s2.Length);
57         }
58
59         public static string Substring(string value, double startIndex) {
60             startIndex = Round(startIndex);
61             if (startIndex <= 0) {
62                 return value;
63             } else if (startIndex <= value.Length) {
64                 return value.Substring((int)startIndex - 1);
65             } else {
66                 Debug.Assert(value.Length < startIndex || Double.IsNaN(startIndex));
67                 return string.Empty;
68             }
69         }
70
71         public static string Substring(string value, double startIndex, double length) {
72             startIndex = Round(startIndex) - 1;             // start index
73             if (startIndex >= value.Length) {
74                 return string.Empty;
75             }
76
77             double endIndex = startIndex + Round(length);   // end index
78             startIndex = (startIndex <= 0) ? 0 : startIndex;
79
80             if (startIndex < endIndex) {
81                 if (endIndex > value.Length) {
82                     endIndex = value.Length;
83                 }
84                 Debug.Assert(0 <= startIndex && startIndex <= endIndex && endIndex <= value.Length);
85                 return value.Substring((int)startIndex, (int)(endIndex - startIndex));
86             } else {
87                 Debug.Assert(endIndex <= startIndex || Double.IsNaN(endIndex));
88                 return string.Empty;
89             }
90         }
91
92         public static string NormalizeSpace(string value) {
93             XmlCharType xmlCharType = XmlCharType.Instance;
94             StringBuilder sb = null;
95             int idx, idxStart = 0, idxSpace = 0;
96
97             for (idx = 0; idx < value.Length; idx++) {
98                 if (xmlCharType.IsWhiteSpace(value[idx])) {
99                     if (idx == idxStart) {
100                         // Previous character was a whitespace character, so discard this character
101                         idxStart++;
102                     }
103                     else if (value[idx] != ' ' || idxSpace == idx) {
104                         // Space was previous character or this is a non-space character
105                         if (sb == null)
106                             sb = new StringBuilder(value.Length);
107                         else
108                             sb.Append(' ');
109
110                         // Copy non-space characters into string builder
111                         if (idxSpace == idx)
112                             sb.Append(value, idxStart, idx - idxStart - 1);
113                         else
114                             sb.Append(value, idxStart, idx - idxStart);
115
116                         idxStart = idx + 1;
117                     }
118                     else {
119                         // Single whitespace character doesn't cause normalization, but mark its position
120                         idxSpace = idx + 1;
121                     }
122                 }
123             }
124
125             if (sb == null) {
126                 // Check for string that is entirely composed of whitespace
127                 if (idxStart == idx) return string.Empty;
128
129                 // If string does not end with a space, then it must already be normalized
130                 if (idxStart == 0 && idxSpace != idx) return value;
131
132                 sb = new StringBuilder(value.Length);
133             }
134             else if (idx != idxStart) {
135                 sb.Append(' ');
136             }
137
138             // Copy non-space characters into string builder
139             if (idxSpace == idx)
140                 sb.Append(value, idxStart, idx - idxStart - 1);
141             else
142                 sb.Append(value, idxStart, idx - idxStart);
143
144             return sb.ToString();
145         }
146
147         public static string Translate(string arg, string mapString, string transString) {
148             if (mapString.Length == 0) {
149                 return arg;
150             }
151
152             StringBuilder sb = new StringBuilder(arg.Length);
153
154             for (int i = 0; i < arg.Length; i++) {
155                 int index = mapString.IndexOf(arg[i]);
156                 if (index < 0) {
157                     // Keep the character
158                     sb.Append(arg[i]);
159                 } else if (index < transString.Length) {
160                     // Replace the character
161                     sb.Append(transString[index]);
162                 } else {
163                     // Remove the character
164                 }
165             }
166             return sb.ToString();
167         }
168
169         public static bool Lang(string value, XPathNavigator context) {
170             string lang = context.XmlLang;
171
172             if (!lang.StartsWith(value, StringComparison.OrdinalIgnoreCase)) {
173                 return false;
174             }
175             return (lang.Length == value.Length || lang[value.Length] == '-');
176         }
177
178         // Round value using XPath rounding rules (round towards positive infinity).
179         // Values between -0.5 and -0.0 are rounded to -0.0 (negative zero).
180         public static double Round(double value) {
181             double temp = Math.Round(value);
182             return (value - temp == 0.5) ? temp + 1 : temp;
183         }
184
185         // Spec: http://www.w3.org/TR/xslt.html#function-system-property
186         public static XPathItem SystemProperty(XmlQualifiedName name) {
187             if (name.Namespace == XmlReservedNs.NsXslt) {
188                 // "xsl:version" must return 1.0 as a number, see http://www.w3.org/TR/xslt20/#incompatility-without-schema
189                 switch (name.Name) {
190                 case "version"   :  return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.Double), 1.0);
191                 case "vendor"    :  return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), "Microsoft");
192                 case "vendor-url":  return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), "http://www.microsoft.com");
193                 }
194             } else if (name.Namespace == XmlReservedNs.NsMsxsl && name.Name == "version") {
195                 // msxsl:version
196                 return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), typeof(XsltLibrary).Assembly.ImageRuntimeVersion);
197             }
198             // If the property name is not recognized, return the empty string
199             return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), string.Empty);
200         }
201
202
203         //------------------------------------------------
204         // Navigator functions
205         //------------------------------------------------
206
207         public static string BaseUri(XPathNavigator navigator) {
208             return navigator.BaseURI;
209         }
210
211         public static string OuterXml(XPathNavigator navigator) {
212             RtfNavigator rtf = navigator as RtfNavigator;
213             if (rtf == null) {
214                 return navigator.OuterXml;
215             }
216             StringBuilder sb = new StringBuilder();
217             XmlWriterSettings settings = new XmlWriterSettings();
218             settings.OmitXmlDeclaration = true;
219             settings.ConformanceLevel = ConformanceLevel.Fragment;
220             settings.CheckCharacters = false;
221             XmlWriter xw = XmlWriter.Create(sb, settings);
222             rtf.CopyToWriter(xw);
223             xw.Close();
224             return sb.ToString();
225         }
226
227
228         //------------------------------------------------
229         // EXslt Functions
230         //------------------------------------------------
231
232         public static string EXslObjectType(IList<XPathItem> value) {
233             if (value.Count != 1) {
234                 XsltLibrary.CheckXsltValue(value);
235                 return "node-set";
236             }
237
238             XPathItem item = value[0];
239             if (item is RtfNavigator) {
240                 return "RTF";
241             } else if (item.IsNode) {
242                 Debug.Assert(item is XPathNavigator);
243                 return "node-set";
244             }
245
246             object o = item.TypedValue;
247             if (o is string) {
248                 return "string";
249             } else if (o is double) {
250                 return "number";
251             } else if (o is bool) {
252                 return "boolean";
253             } else {
254                 Debug.Fail("Unexpected type: " + o.GetType().ToString());
255                 return "external";
256             }
257         }
258
259
260         //------------------------------------------------
261         // Msxml Extension Functions
262         //------------------------------------------------
263
264         public static double MSNumber(IList<XPathItem> value) {
265             XsltLibrary.CheckXsltValue(value);
266             if (value.Count == 0) {
267                 return Double.NaN;
268             }
269             XPathItem item = value[0];
270
271             string stringValue;
272
273             if (item.IsNode) {
274                 stringValue = item.Value;
275             } else {
276                 Type itemType = item.ValueType;
277                 if (itemType == XsltConvert.StringType) {
278                     stringValue = item.Value;
279                 } else if (itemType == XsltConvert.DoubleType) {
280                     return item.ValueAsDouble;
281                 } else {
282                     Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString());
283                     return item.ValueAsBoolean ? 1d : 0d;
284                 }
285             }
286
287             Debug.Assert(stringValue != null);
288             double d;
289             if (XmlConvert.TryToDouble(stringValue, out d) != null) {
290                 d = double.NaN;
291             }
292             return d;
293         }
294 #if !MONO
295         // CharSet.Auto is needed to work on Windows 98 and Windows Me
296         [DllImport("kernel32.dll", CharSet=CharSet.Auto, BestFitMapping=false)]
297         // SxS: Time formatting does not expose any system resource hence Resource Exposure scope is None.
298         [ResourceExposure(ResourceScope.None)]
299         static extern int GetDateFormat(int locale, uint dwFlags, ref SystemTime sysTime, string format, StringBuilder sb, int sbSize);
300
301         [DllImport("kernel32.dll", CharSet=CharSet.Auto, BestFitMapping=false)]
302         // SxS: Time formatting does not expose any system resource hence Resource Exposure scope is None.
303         [ResourceExposure(ResourceScope.None)]
304         static extern int GetTimeFormat(int locale, uint dwFlags, ref SystemTime sysTime, string format, StringBuilder sb, int sbSize);
305
306         [StructLayout(LayoutKind.Sequential)]
307         private struct SystemTime {
308             [MarshalAs(UnmanagedType.U2)] public ushort Year;
309             [MarshalAs(UnmanagedType.U2)] public ushort Month;
310             [MarshalAs(UnmanagedType.U2)] public ushort DayOfWeek;
311             [MarshalAs(UnmanagedType.U2)] public ushort Day;
312             [MarshalAs(UnmanagedType.U2)] public ushort Hour;
313             [MarshalAs(UnmanagedType.U2)] public ushort Minute;
314             [MarshalAs(UnmanagedType.U2)] public ushort Second;
315             [MarshalAs(UnmanagedType.U2)] public ushort Milliseconds;
316
317             public SystemTime(DateTime dateTime) {
318                 this.Year         = (ushort)dateTime.Year;
319                 this.Month        = (ushort)dateTime.Month;
320                 this.DayOfWeek    = (ushort)dateTime.DayOfWeek;
321                 this.Day          = (ushort)dateTime.Day;
322                 this.Hour         = (ushort)dateTime.Hour;
323                 this.Minute       = (ushort)dateTime.Minute;
324                 this.Second       = (ushort)dateTime.Second;
325                 this.Milliseconds = (ushort)dateTime.Millisecond;
326             }
327         }
328 #endif
329         // string ms:format-date(string datetime[, string format[, string language]])
330         // string ms:format-time(string datetime[, string format[, string language]])
331         //
332         // Format xsd:dateTime as a date/time string for a given language using a given format string.
333         // * Datetime contains a lexical representation of xsd:dateTime. If datetime is not valid, the
334         //   empty string is returned.
335         // * Format specifies a format string in the same way as for GetDateFormat/GetTimeFormat system
336         //   functions. If format is the empty string or not passed, the default date/time format for the
337         //   given culture is used.
338         // * Language specifies a culture used for formatting. If language is the empty string or not
339         //   passed, the current culture is used. If language is not recognized, a runtime error happens.
340         public static string MSFormatDateTime(string dateTime, string format, string lang, bool isDate) {
341             try {
342                 XsdDateTime xdt;
343                 if (! XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt)) {
344                     return string.Empty;
345                 }
346 #if MONO
347                 string locale = GetCultureInfo(lang).Name;
348
349                 DateTime dt = xdt.ToZulu();
350
351                 // If format is the empty string or not specified, use the default format for the given locale
352                 if (format.Length == 0)
353                 {
354                     format = null;
355                 }
356                 return dt.ToString(format, new CultureInfo(locale));
357 #else
358                 int locale = GetCultureInfo(lang).LCID;
359
360                 SystemTime st = new SystemTime(xdt.ToZulu());
361
362                 StringBuilder sb = new StringBuilder(format.Length + 16);
363
364                 // If format is the empty string or not specified, use the default format for the given locale
365                 if (format.Length == 0) {
366                     format = null;
367                 }
368                 if (isDate) {
369                     int res = GetDateFormat(locale, 0, ref st, format, sb, sb.Capacity);
370                     if (res == 0) {
371                         res = GetDateFormat(locale, 0, ref st, format, sb, 0);
372                         if (res != 0) {
373                             sb = new StringBuilder(res);
374                             res = GetDateFormat(locale, 0, ref st, format, sb, sb.Capacity);
375                         }
376                     }
377                 } else {
378                     int res = GetTimeFormat(locale, 0, ref st, format, sb, sb.Capacity);
379                     if (res == 0) {
380                         res = GetTimeFormat(locale, 0, ref st, format, sb, 0);
381                         if (res != 0) {
382                             sb = new StringBuilder(res);
383                             res = GetTimeFormat(locale, 0, ref st, format, sb, sb.Capacity);
384                         }
385                     }
386                 }
387                 return sb.ToString();
388 #endif
389             } catch (ArgumentException) { // Operations with DateTime can throw this exception eventualy
390                 return string.Empty;
391             }
392         }
393
394         public static double MSStringCompare(string s1, string s2, string lang, string options) {
395             CultureInfo cultinfo = GetCultureInfo(lang);
396             CompareOptions opts = CompareOptions.None;
397             bool upperFirst = false;
398             for (int idx = 0; idx < options.Length; idx++) {
399                 switch (options[idx]) {
400                 case 'i':
401                     opts = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth;
402                     break;
403                 case 'u':
404                     upperFirst = true;
405                     break;
406                 default:
407                     upperFirst = true;
408                     opts = CompareOptions.IgnoreCase;
409                     break;
410                 }
411             }
412
413             if (upperFirst) {
414                 if (opts != CompareOptions.None) {
415                     throw new XslTransformException(Res.Xslt_InvalidCompareOption, options);
416                 }
417                 opts = CompareOptions.IgnoreCase;
418             }
419
420             int result = cultinfo.CompareInfo.Compare(s1, s2, opts);
421             if (upperFirst && result == 0) {
422                 result = -cultinfo.CompareInfo.Compare(s1, s2, CompareOptions.None);
423             }
424             return result;
425         }
426
427         public static string MSUtc(string dateTime) {
428             XsdDateTime xdt;
429             DateTime dt;
430             try {
431                 if (! XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt)) {
432                     return string.Empty;
433                 }
434                 dt = xdt.ToZulu();
435             } catch (ArgumentException) { // Operations with DateTime can throw this exception eventualy
436                 return string.Empty;
437             }
438             char[] text = "----------T00:00:00.000".ToCharArray();
439             //            "YYYY-MM-DDTHH:NN:SS.III"
440             //             0         1         2
441             //             01234567890123456789012
442             switch (xdt.TypeCode) {
443             case XmlTypeCode.DateTime:
444                 PrintDate(text, dt);
445                 PrintTime(text, dt);
446                 break;
447             case XmlTypeCode.Time:
448                 PrintTime(text, dt);
449                 break;
450             case XmlTypeCode.Date:
451                 PrintDate(text, dt);
452                 break;
453             case XmlTypeCode.GYearMonth:
454                 PrintYear(  text, dt.Year);
455                 ShortToCharArray(text, 5, dt.Month);
456                 break;
457             case XmlTypeCode.GYear:
458                 PrintYear(text, dt.Year);
459                 break;
460             case XmlTypeCode.GMonthDay:
461                 ShortToCharArray(text, 5, dt.Month);
462                 ShortToCharArray(text, 8, dt.Day );
463                 break;
464             case XmlTypeCode.GDay:
465                 ShortToCharArray(text, 8, dt.Day );
466                 break;
467             case XmlTypeCode.GMonth:
468                 ShortToCharArray(text, 5, dt.Month);
469                 break;
470             }
471             return new String(text);
472         }
473
474         public static string MSLocalName(string name) {
475             int colonOffset;
476             int len = ValidateNames.ParseQName(name, 0, out colonOffset);
477
478             if (len != name.Length) {
479                 return string.Empty;
480             }
481             if (colonOffset == 0) {
482                 return name;
483             } else {
484                 return name.Substring(colonOffset + 1);
485             }
486         }
487
488         public static string MSNamespaceUri(string name, XPathNavigator currentNode) {
489             int colonOffset;
490             int len = ValidateNames.ParseQName(name, 0, out colonOffset);
491
492             if (len != name.Length) {
493                 return string.Empty;
494             }
495             string prefix = name.Substring(0, colonOffset);
496             if (prefix == "xmlns") {
497                 return string.Empty;
498             }
499             string ns = currentNode.LookupNamespace(prefix);
500             if (ns != null) {
501                 return ns;
502             }
503             if (prefix == "xml") {
504                 return XmlReservedNs.NsXml;
505             }
506             return string.Empty;
507         }
508
509
510         //------------------------------------------------
511         // Helper Functions
512         //------------------------------------------------
513
514         private static CultureInfo GetCultureInfo(string lang) {
515             Debug.Assert(lang != null);
516             if (lang.Length == 0) {
517                 return CultureInfo.CurrentCulture;
518             } else {
519                 try {
520                     return new CultureInfo(lang);
521                 } catch (System.ArgumentException) {
522                     throw new XslTransformException(Res.Xslt_InvalidLanguage, lang);
523                 }
524             }
525         }
526
527         private static void PrintDate(char[] text, DateTime dt) {
528             PrintYear(text, dt.Year);
529             ShortToCharArray(text, 5, dt.Month);
530             ShortToCharArray(text, 8, dt.Day );
531         }
532
533         private static void PrintTime(char[] text, DateTime dt) {
534             ShortToCharArray(text, 11, dt.Hour  );
535             ShortToCharArray(text, 14, dt.Minute);
536             ShortToCharArray(text, 17, dt.Second);
537             PrintMsec(text, dt.Millisecond);
538         }
539
540         private static void PrintYear(char[] text, int value) {
541             text[0] = (char) ((value / 1000) % 10 + '0');
542             text[1] = (char) ((value / 100 ) % 10 + '0');
543             text[2] = (char) ((value / 10  ) % 10 + '0');
544             text[3] = (char) ((value / 1   ) % 10 + '0');
545         }
546
547         private static void PrintMsec(char[] text, int value) {
548             if (value == 0) {
549                 return;
550             }
551             text[20] = (char) ((value / 100) % 10 + '0');
552             text[21] = (char) ((value / 10 ) % 10 + '0');
553             text[22] = (char) ((value / 1  ) % 10 + '0');
554         }
555
556         private static void ShortToCharArray(char[] text, int start, int value) {
557             text[start    ] = (char)(value / 10 + '0');
558             text[start + 1] = (char)(value % 10 + '0');
559         }
560     }
561 }