1 //------------------------------------------------------------------------------
2 // <copyright file="XsltFunctions.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
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;
23 namespace System.Xml.Xsl.Runtime {
24 using Res = System.Xml.Utils.Res;
26 [EditorBrowsable(EditorBrowsableState.Never)]
27 public static class XsltFunctions {
28 private static readonly CompareInfo compareInfo = CultureInfo.InvariantCulture.CompareInfo;
31 //------------------------------------------------
32 // Xslt/XPath functions
33 //------------------------------------------------
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;
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;
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);
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);
59 public static string Substring(string value, double startIndex) {
60 startIndex = Round(startIndex);
61 if (startIndex <= 0) {
63 } else if (startIndex <= value.Length) {
64 return value.Substring((int)startIndex - 1);
66 Debug.Assert(value.Length < startIndex || Double.IsNaN(startIndex));
71 public static string Substring(string value, double startIndex, double length) {
72 startIndex = Round(startIndex) - 1; // start index
73 if (startIndex >= value.Length) {
77 double endIndex = startIndex + Round(length); // end index
78 startIndex = (startIndex <= 0) ? 0 : startIndex;
80 if (startIndex < endIndex) {
81 if (endIndex > value.Length) {
82 endIndex = value.Length;
84 Debug.Assert(0 <= startIndex && startIndex <= endIndex && endIndex <= value.Length);
85 return value.Substring((int)startIndex, (int)(endIndex - startIndex));
87 Debug.Assert(endIndex <= startIndex || Double.IsNaN(endIndex));
92 public static string NormalizeSpace(string value) {
93 XmlCharType xmlCharType = XmlCharType.Instance;
94 StringBuilder sb = null;
95 int idx, idxStart = 0, idxSpace = 0;
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
103 else if (value[idx] != ' ' || idxSpace == idx) {
104 // Space was previous character or this is a non-space character
106 sb = new StringBuilder(value.Length);
110 // Copy non-space characters into string builder
112 sb.Append(value, idxStart, idx - idxStart - 1);
114 sb.Append(value, idxStart, idx - idxStart);
119 // Single whitespace character doesn't cause normalization, but mark its position
126 // Check for string that is entirely composed of whitespace
127 if (idxStart == idx) return string.Empty;
129 // If string does not end with a space, then it must already be normalized
130 if (idxStart == 0 && idxSpace != idx) return value;
132 sb = new StringBuilder(value.Length);
134 else if (idx != idxStart) {
138 // Copy non-space characters into string builder
140 sb.Append(value, idxStart, idx - idxStart - 1);
142 sb.Append(value, idxStart, idx - idxStart);
144 return sb.ToString();
147 public static string Translate(string arg, string mapString, string transString) {
148 if (mapString.Length == 0) {
152 StringBuilder sb = new StringBuilder(arg.Length);
154 for (int i = 0; i < arg.Length; i++) {
155 int index = mapString.IndexOf(arg[i]);
157 // Keep the character
159 } else if (index < transString.Length) {
160 // Replace the character
161 sb.Append(transString[index]);
163 // Remove the character
166 return sb.ToString();
169 public static bool Lang(string value, XPathNavigator context) {
170 string lang = context.XmlLang;
172 if (!lang.StartsWith(value, StringComparison.OrdinalIgnoreCase)) {
175 return (lang.Length == value.Length || lang[value.Length] == '-');
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;
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
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");
194 } else if (name.Namespace == XmlReservedNs.NsMsxsl && name.Name == "version") {
196 return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), typeof(XsltLibrary).Assembly.ImageRuntimeVersion);
198 // If the property name is not recognized, return the empty string
199 return new XmlAtomicValue(XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String), string.Empty);
203 //------------------------------------------------
204 // Navigator functions
205 //------------------------------------------------
207 public static string BaseUri(XPathNavigator navigator) {
208 return navigator.BaseURI;
211 public static string OuterXml(XPathNavigator navigator) {
212 RtfNavigator rtf = navigator as RtfNavigator;
214 return navigator.OuterXml;
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);
224 return sb.ToString();
228 //------------------------------------------------
230 //------------------------------------------------
232 public static string EXslObjectType(IList<XPathItem> value) {
233 if (value.Count != 1) {
234 XsltLibrary.CheckXsltValue(value);
238 XPathItem item = value[0];
239 if (item is RtfNavigator) {
241 } else if (item.IsNode) {
242 Debug.Assert(item is XPathNavigator);
246 object o = item.TypedValue;
249 } else if (o is double) {
251 } else if (o is bool) {
254 Debug.Fail("Unexpected type: " + o.GetType().ToString());
260 //------------------------------------------------
261 // Msxml Extension Functions
262 //------------------------------------------------
264 public static double MSNumber(IList<XPathItem> value) {
265 XsltLibrary.CheckXsltValue(value);
266 if (value.Count == 0) {
269 XPathItem item = value[0];
274 stringValue = item.Value;
276 Type itemType = item.ValueType;
277 if (itemType == XsltConvert.StringType) {
278 stringValue = item.Value;
279 } else if (itemType == XsltConvert.DoubleType) {
280 return item.ValueAsDouble;
282 Debug.Assert(itemType == XsltConvert.BooleanType, "Unexpected type of atomic value " + itemType.ToString());
283 return item.ValueAsBoolean ? 1d : 0d;
287 Debug.Assert(stringValue != null);
289 if (XmlConvert.TryToDouble(stringValue, out d) != null) {
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);
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);
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;
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;
329 // string ms:format-date(string datetime[, string format[, string language]])
330 // string ms:format-time(string datetime[, string format[, string language]])
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) {
343 if (! XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt)) {
347 string locale = GetCultureInfo(lang).Name;
349 DateTime dt = xdt.ToZulu();
351 // If format is the empty string or not specified, use the default format for the given locale
352 if (format.Length == 0)
356 return dt.ToString(format, new CultureInfo(locale));
358 int locale = GetCultureInfo(lang).LCID;
360 SystemTime st = new SystemTime(xdt.ToZulu());
362 StringBuilder sb = new StringBuilder(format.Length + 16);
364 // If format is the empty string or not specified, use the default format for the given locale
365 if (format.Length == 0) {
369 int res = GetDateFormat(locale, 0, ref st, format, sb, sb.Capacity);
371 res = GetDateFormat(locale, 0, ref st, format, sb, 0);
373 sb = new StringBuilder(res);
374 res = GetDateFormat(locale, 0, ref st, format, sb, sb.Capacity);
378 int res = GetTimeFormat(locale, 0, ref st, format, sb, sb.Capacity);
380 res = GetTimeFormat(locale, 0, ref st, format, sb, 0);
382 sb = new StringBuilder(res);
383 res = GetTimeFormat(locale, 0, ref st, format, sb, sb.Capacity);
387 return sb.ToString();
389 } catch (ArgumentException) { // Operations with DateTime can throw this exception eventualy
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]) {
401 opts = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth;
408 opts = CompareOptions.IgnoreCase;
414 if (opts != CompareOptions.None) {
415 throw new XslTransformException(Res.Xslt_InvalidCompareOption, options);
417 opts = CompareOptions.IgnoreCase;
420 int result = cultinfo.CompareInfo.Compare(s1, s2, opts);
421 if (upperFirst && result == 0) {
422 result = -cultinfo.CompareInfo.Compare(s1, s2, CompareOptions.None);
427 public static string MSUtc(string dateTime) {
431 if (! XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt)) {
435 } catch (ArgumentException) { // Operations with DateTime can throw this exception eventualy
438 char[] text = "----------T00:00:00.000".ToCharArray();
439 // "YYYY-MM-DDTHH:NN:SS.III"
441 // 01234567890123456789012
442 switch (xdt.TypeCode) {
443 case XmlTypeCode.DateTime:
447 case XmlTypeCode.Time:
450 case XmlTypeCode.Date:
453 case XmlTypeCode.GYearMonth:
454 PrintYear( text, dt.Year);
455 ShortToCharArray(text, 5, dt.Month);
457 case XmlTypeCode.GYear:
458 PrintYear(text, dt.Year);
460 case XmlTypeCode.GMonthDay:
461 ShortToCharArray(text, 5, dt.Month);
462 ShortToCharArray(text, 8, dt.Day );
464 case XmlTypeCode.GDay:
465 ShortToCharArray(text, 8, dt.Day );
467 case XmlTypeCode.GMonth:
468 ShortToCharArray(text, 5, dt.Month);
471 return new String(text);
474 public static string MSLocalName(string name) {
476 int len = ValidateNames.ParseQName(name, 0, out colonOffset);
478 if (len != name.Length) {
481 if (colonOffset == 0) {
484 return name.Substring(colonOffset + 1);
488 public static string MSNamespaceUri(string name, XPathNavigator currentNode) {
490 int len = ValidateNames.ParseQName(name, 0, out colonOffset);
492 if (len != name.Length) {
495 string prefix = name.Substring(0, colonOffset);
496 if (prefix == "xmlns") {
499 string ns = currentNode.LookupNamespace(prefix);
503 if (prefix == "xml") {
504 return XmlReservedNs.NsXml;
510 //------------------------------------------------
512 //------------------------------------------------
514 private static CultureInfo GetCultureInfo(string lang) {
515 Debug.Assert(lang != null);
516 if (lang.Length == 0) {
517 return CultureInfo.CurrentCulture;
520 return new CultureInfo(lang);
521 } catch (System.ArgumentException) {
522 throw new XslTransformException(Res.Xslt_InvalidLanguage, lang);
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 );
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);
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');
547 private static void PrintMsec(char[] text, int value) {
551 text[20] = (char) ((value / 100) % 10 + '0');
552 text[21] = (char) ((value / 10 ) % 10 + '0');
553 text[22] = (char) ((value / 1 ) % 10 + '0');
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');