2 // System.Xml.XmlConvert
5 // Dwivedi, Ajay kumar (Adwiv@Yahoo.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // Alan Tam Siu Lung (Tam@SiuLung.com)
8 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
10 // (C) 2002 Ximian, Inc (http://www.ximian.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Globalization;
37 using System.Xml.Schema;
39 namespace System.Xml {
41 public class XmlConvert {
43 const string encodedColon = "_x003A_";
44 const NumberStyles floatStyle = NumberStyles.AllowCurrencySymbol |
45 NumberStyles.AllowExponent |
46 NumberStyles.AllowDecimalPoint |
47 NumberStyles.AllowLeadingSign |
48 NumberStyles.AllowLeadingWhite |
49 NumberStyles.AllowTrailingWhite;
51 const NumberStyles integerStyle = NumberStyles.Integer |
52 NumberStyles.AllowLeadingWhite |
53 NumberStyles.AllowTrailingWhite;
55 static readonly string [] datetimeFormats = {
58 "yyyy-MM-ddTHH:mm:sszzz",
59 "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz",
60 "yyyy-MM-ddTHH:mm:ssZ",
61 "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ",
62 "yyyy-MM-ddTHH:mm:ss",
63 "yyyy-MM-ddTHH:mm:ss.FFFFFFF",
67 "HH:mm:ss.FFFFFFFzzz",
70 #else // it is not required in trunk but should make it easy to backport...
71 "yyyy-MM-ddTHH:mm:sszzz",
72 "yyyy-MM-ddTHH:mm:ss.fzzz",
73 "yyyy-MM-ddTHH:mm:ss.ffzzz",
74 "yyyy-MM-ddTHH:mm:ss.fffzzz",
75 "yyyy-MM-ddTHH:mm:ss.ffffzzz",
76 "yyyy-MM-ddTHH:mm:ss.fffffzzz",
77 "yyyy-MM-ddTHH:mm:ss.ffffffzzz",
78 "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
79 "yyyy-MM-ddTHH:mm:ssZ",
80 "yyyy-MM-ddTHH:mm:ss.fZ",
81 "yyyy-MM-ddTHH:mm:ss.ffZ",
82 "yyyy-MM-ddTHH:mm:ss.fffZ",
83 "yyyy-MM-ddTHH:mm:ss.ffffZ",
84 "yyyy-MM-ddTHH:mm:ss.fffffZ",
85 "yyyy-MM-ddTHH:mm:ss.ffffffZ",
86 "yyyy-MM-ddTHH:mm:ss.fffffffZ",
87 "yyyy-MM-ddTHH:mm:ss",
88 "yyyy-MM-ddTHH:mm:ss.f",
89 "yyyy-MM-ddTHH:mm:ss.ff",
90 "yyyy-MM-ddTHH:mm:ss.fff",
91 "yyyy-MM-ddTHH:mm:ss.ffff",
92 "yyyy-MM-ddTHH:mm:ss.fffff",
93 "yyyy-MM-ddTHH:mm:ss.ffffff",
94 "yyyy-MM-ddTHH:mm:ss.fffffff",
110 "HH:mm:ss.ffffffzzz",
111 "HH:mm:ss.fffffffzzz",
143 static DateTimeStyles _defaultStyle = DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite;
148 private static string TryDecoding (string s)
150 if (s == null || s.Length < 6)
155 c = (char) Int32.Parse (s.Substring (1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
157 return s [0] + DecodeName (s.Substring (1));
161 return c.ToString ();
162 return c + DecodeName (s.Substring (6));
165 public static string DecodeName (string name)
167 if (name == null || name.Length == 0)
170 int pos = name.IndexOf ('_');
171 if (pos == -1 || pos + 6 >= name.Length)
174 if ((name [pos + 1] != 'X' && name [pos + 1] != 'x') || name [pos + 6] != '_')
175 return name [0] + DecodeName (name.Substring (1));
177 return name.Substring (0, pos) + TryDecoding (name.Substring (pos + 1));
180 public static string EncodeLocalName (string name)
185 string encoded = EncodeName (name);
186 int pos = encoded.IndexOf (':');
189 return encoded.Replace (":", encodedColon);
192 internal static bool IsInvalid (char c, bool firstOnlyLetter)
194 if (c == ':') // Special case. allowed in EncodeName, but encoded in EncodeLocalName
198 return !XmlChar.IsFirstNameChar (c);
200 return !XmlChar.IsNameChar (c);
203 private static string EncodeName (string name, bool nmtoken)
205 if (name == null || name.Length == 0)
208 StringBuilder sb = new StringBuilder ();
209 int length = name.Length;
210 for (int i = 0; i < length; i++) {
212 if (IsInvalid (c, i == 0 && !nmtoken))
213 sb.AppendFormat ("_x{0:X4}_", (int) c);
214 else if (c == '_' && i + 6 < length && name [i+1] == 'x' && name [i + 6] == '_')
215 sb.Append ("_x005F_");
219 return sb.ToString ();
222 public static string EncodeName (string name)
224 return EncodeName (name, false);
227 public static string EncodeNmToken (string name)
229 if (name == String.Empty)
230 throw new XmlException ("Invalid NmToken: ''");
231 return EncodeName (name, true);
234 // {true, false, 1, 0}
235 public static bool ToBoolean(string s)
237 s = s.Trim (XmlChar.WhitespaceChars);
249 throw new FormatException(s + " is not a valid boolean value");
253 // LAMESPEC: It has been documented as public, but is marked as internal.
254 internal static string ToBinHexString (byte [] buffer)
256 StringWriter w = new StringWriter ();
257 WriteBinHex (buffer, 0, buffer.Length, w);
258 return w.ToString ();
261 internal static void WriteBinHex (byte [] buffer, int index, int count, TextWriter w)
264 throw new ArgumentNullException ("buffer");
266 throw new ArgumentOutOfRangeException (
270 "index must be non negative integer.");
273 throw new ArgumentOutOfRangeException (
277 "count must be non negative integer.");
279 if (buffer.Length < index + count)
280 throw new ArgumentOutOfRangeException ("index and count must be smaller than the length of the buffer.");
282 // Copied from XmlTextWriter.WriteBinHex ()
283 int end = index + count;
284 for (int i = index; i < end; i++) {
285 int val = buffer [i];
289 w.Write ((char) (high + 55));
291 w.Write ((char) (high + 0x30));
293 w.Write ((char) (low + 55));
295 w.Write ((char) (low + 0x30));
299 public static byte ToByte(string s)
301 return Byte.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture);
304 public static char ToChar(string s)
307 return Char.Parse(s);
310 throw new ArgumentNullException ("s");
313 throw new FormatException ("String contain more than one char");
322 public static DateTime ToDateTime (string s)
324 return ToDateTime (s, datetimeFormats);
328 public static DateTime ToDateTime (string s, XmlDateTimeSerializationMode dateTimeOption)
330 switch (dateTimeOption) {
331 case XmlDateTimeSerializationMode.Local:
332 return ToDateTime(s, datetimeFormats, _defaultStyle | DateTimeStyles.AssumeLocal).ToLocalTime();
333 case XmlDateTimeSerializationMode.RoundtripKind:
334 return ToDateTime(s, datetimeFormats, _defaultStyle | DateTimeStyles.RoundtripKind);
335 case XmlDateTimeSerializationMode.Utc:
336 return ToDateTime(s, datetimeFormats, _defaultStyle | DateTimeStyles.AssumeUniversal).ToUniversalTime();
338 return new DateTime (ToDateTime(s, datetimeFormats, _defaultStyle | DateTimeStyles.RoundtripKind).Ticks, DateTimeKind.Unspecified);
342 public static DateTime ToDateTime(string s, string format)
344 //DateTimeFormatInfo d = new DateTimeFormatInfo();
345 //d.FullDateTimePattern = format;
346 //return DateTime.Parse(s, d);
347 DateTimeStyles style = DateTimeStyles.AllowLeadingWhite |
348 DateTimeStyles.AllowTrailingWhite;
349 return DateTime.ParseExact (s, format, DateTimeFormatInfo.InvariantInfo, style);
352 public static DateTime ToDateTime(string s, string[] formats)
354 return ToDateTime (s, formats, _defaultStyle);
357 private static DateTime ToDateTime (string s, string [] formats, DateTimeStyles style)
360 return DateTime.ParseExact (s, formats, DateTimeFormatInfo.InvariantInfo, style);
361 } catch (ArgumentOutOfRangeException) {
362 return DateTime.MinValue;
366 public static Decimal ToDecimal(string s)
368 return Decimal.Parse(s, CultureInfo.InvariantCulture);
371 public static double ToDouble(string s)
374 throw new ArgumentNullException();
376 float f = TryParseStringFloatConstants (s);
380 return Double.Parse (s, floatStyle, CultureInfo.InvariantCulture);
383 static float TryParseStringFloatConstants (string s)
386 while (sidx < s.Length && Char.IsWhiteSpace (s [sidx]))
388 if (sidx == s.Length)
389 throw new FormatException ();
390 int sEndPos = s.Length - 1;
391 while (Char.IsWhiteSpace (s [sEndPos]))
394 if (TryParseStringConstant ("NaN", s, sidx, sEndPos))
396 if (TryParseStringConstant ("INF", s, sidx, sEndPos))
397 return Single.PositiveInfinity;
398 if (TryParseStringConstant ("-INF", s, sidx, sEndPos))
399 return Single.NegativeInfinity;
400 // Handle these here because Single.Parse("Infinity") is invalid while XmlConvert.ToSingle("Infinity") is valid.
401 if (TryParseStringConstant ("Infinity", s, sidx, sEndPos))
402 return Single.PositiveInfinity;
403 if (TryParseStringConstant ("-Infinity", s, sidx, sEndPos))
404 return Single.NegativeInfinity;
408 static bool TryParseStringConstant (string format, string s, int start, int end)
410 return end - start + 1 == format.Length && String.CompareOrdinal (format, 0, s, start, format.Length) == 0;
413 public static Guid ToGuid (string s)
417 } catch (FormatException ex) {
418 throw new FormatException (String.Format ("Invalid Guid input '{0}'", ex.InnerException));
422 public static short ToInt16(string s)
424 return Int16.Parse (s, integerStyle, CultureInfo.InvariantCulture);
427 public static int ToInt32(string s)
429 return Int32.Parse (s, integerStyle, CultureInfo.InvariantCulture);
432 public static long ToInt64(string s)
434 return Int64.Parse (s, integerStyle, CultureInfo.InvariantCulture);
437 [CLSCompliant (false)]
438 public static SByte ToSByte(string s)
440 return SByte.Parse(s, integerStyle, CultureInfo.InvariantCulture);
443 public static float ToSingle(string s)
446 throw new ArgumentNullException();
448 float f = TryParseStringFloatConstants (s);
452 return Single.Parse(s, floatStyle, CultureInfo.InvariantCulture);
455 public static string ToString(Guid value)
457 return value.ToString("D", CultureInfo.InvariantCulture);
460 public static string ToString(int value)
462 return value.ToString(CultureInfo.InvariantCulture);
465 public static string ToString(short value)
467 return value.ToString(CultureInfo.InvariantCulture);
470 public static string ToString(byte value)
472 return value.ToString(CultureInfo.InvariantCulture);
475 public static string ToString(long value)
477 return value.ToString(CultureInfo.InvariantCulture);
480 public static string ToString(char value)
482 return value.ToString(CultureInfo.InvariantCulture);
485 public static string ToString(bool value)
487 if (value) return "true";
491 [CLSCompliant (false)]
492 public static string ToString(SByte value)
494 return value.ToString(CultureInfo.InvariantCulture);
497 public static string ToString(Decimal value)
499 return value.ToString (CultureInfo.InvariantCulture);
502 [CLSCompliant (false)]
503 public static string ToString(UInt64 value)
505 return value.ToString(CultureInfo.InvariantCulture);
508 public static string ToString (TimeSpan value)
510 if (value == TimeSpan.Zero)
513 StringBuilder builder = new StringBuilder ();
514 if (value.Ticks < 0) {
515 if (value == TimeSpan.MinValue)
516 return "-P10675199DT2H48M5.4775808S"; // There's one fewer tick on the positive side, so we cannot Negate this value; just hard-code it
517 builder.Append ('-');
518 value = value.Negate ();
520 builder.Append ('P');
522 builder.Append (value.Days).Append ('D');
523 long ticks = value.Ticks % TimeSpan.TicksPerMillisecond;
524 if (value.Hours > 0 || value.Minutes > 0 || value.Seconds > 0 || value.Milliseconds > 0 || ticks > 0) {
527 builder.Append (value.Hours).Append ('H');
528 if (value.Minutes > 0)
529 builder.Append (value.Minutes).Append ('M');
530 if (value.Seconds > 0 || value.Milliseconds > 0 || ticks > 0) {
531 builder.Append (value.Seconds);
532 bool trimZero = true;
534 builder.Append ('.').AppendFormat ("{0:0000000}", value.Ticks % TimeSpan.TicksPerSecond);
535 else if (value.Milliseconds > 0)
536 builder.Append ('.').AppendFormat ("{0:000}", value.Milliseconds);
540 while (builder [builder.Length - 1] == '0')
541 builder.Remove (builder.Length - 1, 1);
543 builder.Append ('S');
546 return builder.ToString ();
549 public static string ToString(double value)
551 if (Double.IsNegativeInfinity(value)) return "-INF";
552 if (Double.IsPositiveInfinity(value)) return "INF";
553 if (Double.IsNaN(value)) return "NaN";
554 return value.ToString("R", CultureInfo.InvariantCulture);
557 public static string ToString(float value)
559 if (Single.IsNegativeInfinity(value)) return "-INF";
560 if (Single.IsPositiveInfinity(value)) return "INF";
561 if (Single.IsNaN(value)) return "NaN";
562 return value.ToString("R", CultureInfo.InvariantCulture);
565 [CLSCompliant (false)]
566 public static string ToString(UInt32 value)
568 return value.ToString(CultureInfo.InvariantCulture);
571 [CLSCompliant (false)]
572 public static string ToString(UInt16 value)
574 return value.ToString(CultureInfo.InvariantCulture);
580 public static string ToString (DateTime value)
582 return value.ToString ("yyyy-MM-ddTHH:mm:ss.fffffffzzz", CultureInfo.InvariantCulture);
586 public static string ToString (DateTime value, XmlDateTimeSerializationMode dateTimeOption)
588 // Unlike usual DateTime formatting, it preserves
589 // MaxValue/MinValue as is.
590 switch (dateTimeOption) {
591 case XmlDateTimeSerializationMode.Local:
592 return (value == DateTime.MinValue ? DateTime.MinValue : value == DateTime.MaxValue ? value : value.ToLocalTime ()).ToString (
593 "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz",
594 CultureInfo.InvariantCulture);
595 case XmlDateTimeSerializationMode.RoundtripKind:
596 return value.ToString (
597 "yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
598 CultureInfo.InvariantCulture);
600 return value.ToString (
601 "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz",
602 CultureInfo.InvariantCulture);
603 case XmlDateTimeSerializationMode.Utc:
604 return (value == DateTime.MinValue ? DateTime.MinValue : value == DateTime.MaxValue ? value : value.ToUniversalTime ()).ToString (
605 "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ",
606 CultureInfo.InvariantCulture);
607 case XmlDateTimeSerializationMode.Unspecified:
608 return value.ToString (
609 "yyyy-MM-ddTHH:mm:ss.FFFFFFF",
610 CultureInfo.InvariantCulture);
615 public static string ToString(DateTime value, string format)
617 return value.ToString(format, CultureInfo.InvariantCulture);
620 public static TimeSpan ToTimeSpan(string s)
622 s = s.Trim (XmlChar.WhitespaceChars);
624 throw new FormatException ("Invalid format string for duration schema datatype.");
629 bool minusValue = (start == 1);
631 if (s [start] != 'P')
632 throw new FormatException ("Invalid format string for duration schema datatype.");
642 int parsedDigits = 0;
647 while (i < s.Length) {
655 for (; i < s.Length; i++)
656 if (s [i] < '0' || '9' < s [i])
659 parsedDigits = i - start;
660 int value = int.Parse (s.Substring (start, i - start), CultureInfo.InvariantCulture);
661 if (parseStep == 7) {
662 // adjust to 7 digits so that it makes sense as millisecond digits
663 for (; parsedDigits > 7; parsedDigits--)
665 for (; parsedDigits < 7; parsedDigits++)
678 days += 365 * (value / 12) + 30 * (value % 12);
680 } else if (isTime && parseStep < 6) {
696 if (!isTime || parseStep > 4)
706 if (!isTime || parseStep > 7)
727 throw new FormatException ("Invalid format string for duration schema datatype.");
728 TimeSpan ts = new TimeSpan (days, hours, minutes, seconds);
730 return TimeSpan.FromTicks (- (ts.Ticks + ticks));
732 return TimeSpan.FromTicks (ts.Ticks + ticks);
735 [CLSCompliant (false)]
736 public static UInt16 ToUInt16(string s)
738 return UInt16.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture);
741 [CLSCompliant (false)]
742 public static UInt32 ToUInt32(string s)
744 return UInt32.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture);
747 [CLSCompliant (false)]
748 public static UInt64 ToUInt64(string s)
750 return UInt64.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture);
753 public static string VerifyName (string name)
755 if (name == null || name.Length == 0)
756 throw new ArgumentNullException("name");
758 if (!XmlChar.IsName (name))
759 throw new XmlException("'" + name + "' is not a valid XML Name");
764 public static string VerifyNCName (string name)
766 if (name == null || name.Length == 0)
767 throw new ArgumentNullException("name");
769 if (!XmlChar.IsNCName (name))
770 throw new XmlException ("'" + name + "' is not a valid XML NCName");
774 public static string VerifyTOKEN (string token)
777 throw new ArgumentNullException("token");
779 if (token.Length == 0)
782 if (XmlChar.IsWhitespace (token [0]) ||
783 XmlChar.IsWhitespace (token [token.Length - 1]))
784 throw new XmlException ("Whitespace characters (#xA, #xD, #x9, #x20) are not allowed as leading or trailing whitespaces of xs:token.");
786 for (int i = 0; i < token.Length; i++)
787 if (XmlChar.IsWhitespace (token [i]) && token [i] != ' ')
788 throw new XmlException ("Either #xA, #xD or #x9 are not allowed inside xs:token.");
794 public static string VerifyNMTOKEN (string name)
796 internal static string VerifyNMTOKEN (string name)
800 throw new ArgumentNullException("name");
802 if (!XmlChar.IsNmToken (name))
803 throw new XmlException("'" + name + "' is not a valid XML NMTOKEN");
808 // It is documented as public method, but in fact it is not.
809 internal static byte [] FromBinHexString (string s)
811 char [] chars = s.ToCharArray ();
812 byte [] bytes = new byte [chars.Length / 2 + chars.Length % 2];
813 FromBinHexString (chars, 0, chars.Length, bytes);
817 internal static int FromBinHexString (char [] chars, int offset, int charLength, byte [] buffer)
819 int bufIndex = offset;
820 for (int i = 0; i < charLength - 1; i += 2) {
821 buffer [bufIndex] = (chars [i] > '9' ?
822 (byte) (chars [i] - 'A' + 10) :
823 (byte) (chars [i] - '0'));
824 buffer [bufIndex] <<= 4;
825 buffer [bufIndex] += chars [i + 1] > '9' ?
826 (byte) (chars [i + 1] - 'A' + 10) :
827 (byte) (chars [i + 1] - '0');
830 if (charLength %2 != 0)
831 buffer [bufIndex++] = (byte)
832 ((chars [charLength - 1] > '9' ?
833 (byte) (chars [charLength - 1] - 'A' + 10) :
834 (byte) (chars [charLength - 1] - '0'))
837 return bufIndex - offset;
840 #if NET_2_0 // actually NET_3_5
843 public static DateTimeOffset ToDateTimeOffset (string s)
845 return ToDateTimeOffset (s, datetimeFormats);
848 public static DateTimeOffset ToDateTimeOffset (string s, string format)
850 return DateTimeOffset.ParseExact (s, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
853 public static DateTimeOffset ToDateTimeOffset (string s, string [] formats)
855 DateTimeStyles style = DateTimeStyles.AllowLeadingWhite |
856 DateTimeStyles.AllowTrailingWhite |
857 DateTimeStyles.AssumeUniversal;
858 return DateTimeOffset.ParseExact (s, formats, CultureInfo.InvariantCulture, style);
861 public static string ToString (DateTimeOffset value)
863 return ToString (value, "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz");
866 public static string ToString (DateTimeOffset value, string format)
868 return value.ToString (format, CultureInfo.InvariantCulture);
872 // it is used only from 2.1 System.Xml.Serialization.dll from
873 // MS Silverlight SDK. We don't use it so far.
874 internal static Uri ToUri (string s)
876 return new Uri (s, UriKind.RelativeOrAbsolute);
882 public static bool IsNCNameChar (char ch)
884 return XmlChar.IsNCNameChar (ch);
887 public static bool IsPublicIdChar (char ch)
889 return XmlChar.IsPubidChar (ch);
892 public static bool IsStartNCNameChar (char ch)
894 return XmlChar.IsFirstNameChar (ch);
897 public static bool IsWhitespaceChar (char ch)
899 return XmlChar.IsWhitespace (ch);
902 public static bool IsXmlChar (char ch)
904 return XmlChar.IsValid (ch);
907 public static bool IsXmlSurrogatePair (char lowChar, char highChar)
909 return 0xD800 <= lowChar && lowChar <= 0xDBFF && 0xDC00 <= highChar && highChar <= 0xDFFF;
912 public static string VerifyPublicId (string publicId)
914 if (publicId == null)
915 throw new ArgumentNullException ("publicId");
916 if (XmlChar.IsPubid (publicId))
918 throw new XmlException (string.Format ("'{0}' is not a valid PUBLIC ID", publicId));
921 public static string VerifyWhitespace (string content)
924 throw new ArgumentNullException ("content");
925 if (XmlChar.IsWhitespace (content))
927 throw new XmlException (string.Format ("'{0}' is not whitespace", content));
930 public static string VerifyXmlChars (string content)
933 throw new ArgumentNullException ("content");
934 var idx = XmlChar.IndexOfInvalid (content, true);
937 throw new XmlException (string.Format ("Invalid XML character was found in the content, at index {0}.", idx));