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 StartsWith(string s1, string s2)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 Contains(string s1, string s2)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 SubstringBefore(string s1, string s2)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 SubstringAfter(string s1, string s2)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 Substring(string value, double startIndex)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 Substring(string value, double startIndex, double length)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 NormalizeSpace(string value)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 Translate(string arg, string mapString, string transString)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 Lang(string value, XPathNavigator context)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). Round(double value)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 SystemProperty(XmlQualifiedName name)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 BaseUri(XPathNavigator navigator)207 public static string BaseUri(XPathNavigator navigator) { 208 return navigator.BaseURI; 209 } 210 OuterXml(XPathNavigator navigator)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 EXslObjectType(IList<XPathItem> value)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 MSNumber(IList<XPathItem> value)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)] GetDateFormat(int locale, uint dwFlags, ref SystemTime sysTime, string format, StringBuilder sb, int sbSize)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)] GetTimeFormat(int locale, uint dwFlags, ref SystemTime sysTime, string format, StringBuilder sb, int sbSize)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 SystemTimeSystem.Xml.Xsl.Runtime.XsltFunctions.SystemTime317 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. MSFormatDateTime(string dateTime, string format, string lang, bool isDate)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 MSStringCompare(string s1, string s2, string lang, string options)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 MSUtc(string dateTime)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 MSLocalName(string name)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 MSNamespaceUri(string name, XPathNavigator currentNode)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 GetCultureInfo(string lang)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 PrintDate(char[] text, DateTime dt)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 PrintTime(char[] text, DateTime dt)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 PrintYear(char[] text, int value)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 PrintMsec(char[] text, int value)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 ShortToCharArray(char[] text, int start, int value)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 } 562