2004-04-05 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml / XmlConvert.cs
1 //
2 // System.Xml.XmlConvert
3 //
4 // Authors:
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)
9 //
10 // (C) 2002 Ximian, Inc (http://www.ximian.com)
11 //
12 using System;
13 using System.Text;
14 using System.Globalization;
15 using System.Xml.Schema;
16
17 namespace System.Xml {
18
19         public class XmlConvert {
20
21                 static string encodedColon;
22                 static string [] datetimeFormats;
23                 static NumberStyles floatStyle;
24
25                 static XmlConvert ()
26                 {
27                         floatStyle = NumberStyles.AllowCurrencySymbol | 
28                                 NumberStyles.AllowExponent | 
29                                 NumberStyles.AllowDecimalPoint |
30                                 NumberStyles.AllowLeadingSign;
31                         encodedColon = "_x003A_";
32                         datetimeFormats = new string[] {
33                           // dateTime
34                           "yyyy-MM-ddTHH:mm:ss",
35                           "yyyy-MM-ddTHH:mm:ss.f",
36                           "yyyy-MM-ddTHH:mm:ss.ff",
37                           "yyyy-MM-ddTHH:mm:ss.fff",
38                           "yyyy-MM-ddTHH:mm:ss.ffff",
39                           "yyyy-MM-ddTHH:mm:ss.fffff",
40                           "yyyy-MM-ddTHH:mm:ss.ffffff",
41                           "yyyy-MM-ddTHH:mm:ss.fffffff",
42                           "yyyy-MM-ddTHH:mm:sszzz",
43                           "yyyy-MM-ddTHH:mm:ss.fzzz",
44                           "yyyy-MM-ddTHH:mm:ss.ffzzz",
45                           "yyyy-MM-ddTHH:mm:ss.fffzzz",
46                           "yyyy-MM-ddTHH:mm:ss.ffffzzz",
47                           "yyyy-MM-ddTHH:mm:ss.fffffzzz",
48                           "yyyy-MM-ddTHH:mm:ss.ffffffzzz",
49                           "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
50                           "yyyy-MM-ddTHH:mm:ssZ",
51                           "yyyy-MM-ddTHH:mm:ss.fZ",
52                           "yyyy-MM-ddTHH:mm:ss.ffZ",
53                           "yyyy-MM-ddTHH:mm:ss.fffZ",
54                           "yyyy-MM-ddTHH:mm:ss.ffffZ",
55                           "yyyy-MM-ddTHH:mm:ss.fffffZ",
56                           "yyyy-MM-ddTHH:mm:ss.ffffffZ",
57                           "yyyy-MM-ddTHH:mm:ss.fffffffZ",
58                           // time
59                           "HH:mm:ss",
60                           "HH:mm:ss.f",
61                           "HH:mm:ss.ff",
62                           "HH:mm:ss.fff",
63                           "HH:mm:ss.ffff",
64                           "HH:mm:ss.fffff",
65                           "HH:mm:ss.ffffff",
66                           "HH:mm:ss.fffffff",
67                           "HH:mm:sszzz",
68                           "HH:mm:ss.fzzz",
69                           "HH:mm:ss.ffzzz",
70                           "HH:mm:ss.fffzzz",
71                           "HH:mm:ss.ffffzzz",
72                           "HH:mm:ss.fffffzzz",
73                           "HH:mm:ss.ffffffzzz",
74                           "HH:mm:ss.fffffffzzz",
75                           "HH:mm:ssZ",
76                           "HH:mm:ss.fZ",
77                           "HH:mm:ss.ffZ",
78                           "HH:mm:ss.fffZ",
79                           "HH:mm:ss.ffffZ",
80                           "HH:mm:ss.fffffZ",
81                           "HH:mm:ss.ffffffZ",
82                           "HH:mm:ss.fffffffZ",
83                           // date
84                           "yyyy-MM-dd",
85                           "yyyy-MM-ddzzz",
86                           "yyyy-MM-ddZ",
87                           // gYearMonth
88                           "yyyy-MM",
89                           "yyyy-MMzzz",
90                           "yyyy-MMZ",
91                           // gYear
92                           "yyyy",
93                           "yyyyzzz",
94                           "yyyyZ",
95                           // gMonthDay
96                           "--MM-dd",
97                           "--MM-ddzzz",
98                           "--MM-ddZ",
99                           // gDay
100                           "---dd",
101                           "---ddzzz",
102                           "---ddZ",
103                         };
104                 }
105
106                 public XmlConvert()
107                 {}
108
109                 private static string TryDecoding (string s)
110                 {
111                         if (s == null || s.Length < 6)
112                                 return s;
113
114                         char c = '\uFFFF';
115                         try {
116                                 c = (char) Int32.Parse (s.Substring (1, 4), NumberStyles.HexNumber);
117                         } catch {
118                                 return s [0] + DecodeName (s.Substring (1));
119                         }
120                         
121                         if (s.Length == 6)
122                                 return c.ToString ();
123                         return c + DecodeName (s.Substring (6));
124                 }
125                 
126                 public static string DecodeName (string name)
127                 {
128                         if (name == null || name.Length == 0)
129                                 return name;
130
131                         int pos = name.IndexOf ('_');
132                         if (pos == -1 || pos + 6 >= name.Length)
133                                 return name;
134
135                         if (Char.ToUpper (name [pos + 1]) != 'X' || name [pos + 6] != '_')
136                                 return name [0] + DecodeName (name.Substring (1));
137
138                         return name.Substring (0, pos) + TryDecoding (name.Substring (pos + 1));
139                 }
140
141                 public static string EncodeLocalName (string name)
142                 {
143                         string encoded = EncodeName (name);
144                         int pos = encoded.IndexOf (':');
145                         if (pos == -1)
146                                 return encoded;
147                         return encoded.Replace (":", encodedColon);
148                 }
149
150                 internal static bool IsInvalid (char c, bool firstOnlyLetter)
151                 {
152                         if (c == ':') // Special case. allowed in EncodeName, but encoded in EncodeLocalName
153                                 return false;
154                         
155                         if (firstOnlyLetter)
156                                 return !XmlChar.IsFirstNameChar (c);
157                         else
158                                 return !XmlChar.IsNameChar (c);
159                 }
160
161                 private static string EncodeName (string name, bool nmtoken)
162                 {
163                         StringBuilder sb = new StringBuilder ();
164                         int length = name.Length;
165                         for (int i = 0; i < length; i++) {
166                                 char c = name [i];
167                                 if (IsInvalid (c, i == 0 && !nmtoken))
168                                         sb.AppendFormat ("_x{0:X4}_", (int) c);
169                                 else if (c == '_' && i + 6 < length && name [i+1] == 'x' && name [i + 6] == '_')
170                                         sb.Append ("_x005F_");
171                                 else
172                                         sb.Append (c);
173                         }
174                         return sb.ToString ();
175                 }
176
177                 public static string EncodeName (string name)
178                 {
179                         return EncodeName (name, false);
180                 }
181                 
182                 public static string EncodeNmToken(string name)
183                 {
184                         return EncodeName (name, true);
185                 }
186
187                 // {true, false, 1, 0}
188                 public static bool ToBoolean(string s)
189                 {
190                         s = s.Trim (XmlChar.WhitespaceChars);
191                         switch(s)
192                         {
193                                 case "1":
194                                         return true;
195                                 case "true":
196                                         return true;
197                                 case "0":
198                                         return false;
199                                 case "false":
200                                         return false;
201                                 default:
202                                         throw new FormatException(s + " is not a valid boolean value");
203                         }
204                 }
205
206                 public static byte ToByte(string s)
207                 {
208                         return Byte.Parse(s, CultureInfo.InvariantCulture);
209                 }
210
211                 public static char ToChar(string s)
212                 {
213                         return Char.Parse(s);
214                 }
215
216                 public static DateTime ToDateTime(string s)
217                 {
218                         return ToDateTime(s, datetimeFormats);
219                 }
220                 
221                 public static DateTime ToDateTime(string s, string format)
222                 {
223                         DateTimeFormatInfo d = new DateTimeFormatInfo();
224                         d.FullDateTimePattern = format;
225                         return DateTime.Parse(s, d);
226                 }
227
228                 public static DateTime ToDateTime(string s, string[] formats)
229                 {
230                         DateTimeStyles style = DateTimeStyles.AllowLeadingWhite |
231                                                DateTimeStyles.AllowTrailingWhite;
232                         return DateTime.ParseExact (s, formats, DateTimeFormatInfo.InvariantInfo, style);
233                 }
234                 
235                 public static Decimal ToDecimal(string s)
236                 {
237                         return Decimal.Parse(s, CultureInfo.InvariantCulture);
238                 }
239
240                 public static double ToDouble(string s)
241                 {
242                         if (s == null)
243                                 throw new ArgumentNullException();
244                         if (s == "INF")
245                                 return Double.PositiveInfinity;
246                         if (s == "-INF")
247                                 return Double.NegativeInfinity;
248                         if (s == "NaN")
249                                 return Double.NaN;
250                         return Double.Parse (s, floatStyle, CultureInfo.InvariantCulture);
251                 }
252
253                 public static Guid ToGuid(string s)
254                 {
255                         return new Guid(s);
256                 }
257
258                 public static short ToInt16(string s)
259                 {
260                         return Int16.Parse(s, CultureInfo.InvariantCulture);
261                 }
262
263                 public static int ToInt32(string s)
264                 {
265                         return Int32.Parse(s, CultureInfo.InvariantCulture);
266                 }
267
268                 public static long ToInt64(string s)
269                 {
270                         return Int64.Parse(s, CultureInfo.InvariantCulture);
271                 }
272
273                 [CLSCompliant (false)]
274                 public static SByte ToSByte(string s)
275                 {
276                         return SByte.Parse(s, CultureInfo.InvariantCulture);
277                 }
278
279                 public static float ToSingle(string s)
280                 {
281                         if (s == null)
282                                 throw new ArgumentNullException();
283                         if (s == "INF")
284                                 return Single.PositiveInfinity;
285                         if (s == "-INF")
286                                 return Single.NegativeInfinity;
287                         if (s == "NaN")
288                                 return Single.NaN;
289                         return Single.Parse(s, floatStyle, CultureInfo.InvariantCulture);
290                 }
291
292                 public static string ToString(Guid value)
293                 {
294                         return value.ToString("D", CultureInfo.InvariantCulture);
295                 }
296
297                 public static string ToString(int value)
298                 {
299                         return value.ToString(CultureInfo.InvariantCulture);
300                 }
301
302                 public static string ToString(short value)
303                 {
304                         return value.ToString(CultureInfo.InvariantCulture);
305                 }
306
307                 public static string ToString(byte value)
308                 {
309                         return value.ToString(CultureInfo.InvariantCulture);
310                 }
311
312                 public static string ToString(long value)
313                 {
314                         return value.ToString(CultureInfo.InvariantCulture);
315                 }
316
317                 public static string ToString(char value)
318                 {
319                         return value.ToString(CultureInfo.InvariantCulture);
320                 }
321
322                 public static string ToString(bool value)
323                 {
324                         if (value) return "true";
325                         return "false";
326                 }
327
328                 [CLSCompliant (false)]
329                 public static string ToString(SByte value)
330                 {
331                         return value.ToString(CultureInfo.InvariantCulture);
332                 }
333
334                 public static string ToString(Decimal value)
335                 {
336                         return value.ToString (CultureInfo.InvariantCulture);
337                 }
338
339                 [CLSCompliant (false)]
340                 public static string ToString(UInt64 value)
341                 {
342                         return value.ToString(CultureInfo.InvariantCulture);
343                 }
344
345                 public static string ToString(TimeSpan value)
346                 {
347                         StringBuilder builder = new StringBuilder();
348                         if (value.Ticks < 0) {
349                                 builder.Append('-');
350                                 value = value.Negate();
351                         }
352                         builder.Append('P');
353                         if (value.Days > 0) builder.Append(value.Days).Append('D');
354                         if (value.Days > 0 || value.Minutes > 0 || value.Seconds > 0 || value.Milliseconds > 0) {
355                                 builder.Append('T');
356                                 if (value.Hours > 0) builder.Append(value.Hours).Append('D');
357                                 if (value.Minutes > 0) builder.Append(value.Minutes).Append('M');
358                                 if (value.Seconds > 0 || value.Milliseconds > 0) {
359                                         builder.Append(value.Seconds);
360                                         if (value.Milliseconds > 0) builder.Append('.').Append(String.Format("{0:000}", value.Milliseconds));
361                                         builder.Append('S');
362                                 }
363                         }
364                         return builder.ToString();
365                 }
366
367                 public static string ToString(double value)
368                 {
369                         if (Double.IsNegativeInfinity(value)) return "-INF";
370                         if (Double.IsPositiveInfinity(value)) return "INF";
371                         if (Double.IsNaN(value)) return "NaN";
372                         return value.ToString(CultureInfo.InvariantCulture);
373                 }
374
375                 public static string ToString(float value)
376                 {
377                         if (Single.IsNegativeInfinity(value)) return "-INF";
378                         if (Single.IsPositiveInfinity(value)) return "INF";
379                         if (Single.IsNaN(value)) return "NaN";
380                         return value.ToString(CultureInfo.InvariantCulture);
381                 }
382
383                 [CLSCompliant (false)]
384                 public static string ToString(UInt32 value)
385                 {
386                         return value.ToString(CultureInfo.InvariantCulture);
387                 }
388
389                 [CLSCompliant (false)]
390                 public static string ToString(UInt16 value)
391                 {
392                         return value.ToString(CultureInfo.InvariantCulture);
393                 }
394
395                 public static string ToString(DateTime value)
396                 {
397                         return value.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz", CultureInfo.InvariantCulture);
398                 }
399
400                 public static string ToString(DateTime value, string format)
401                 {
402                         return value.ToString(format, CultureInfo.InvariantCulture);
403                 }
404
405                 public static TimeSpan ToTimeSpan(string s)
406                 {
407                         if (s.Length == 0)
408                                 throw new ArgumentException ("Invalid format string for duration schema datatype.");
409
410                         int start = 0;
411                         if (s [0] == '-')
412                                 start = 1;
413                         bool minusValue = (start == 1);
414
415                         if (s [start] != 'P')
416                                 throw new ArgumentException ("Invalid format string for duration schema datatype.");
417                         start++;
418
419                         int parseStep = 0;
420                         int days = 0;
421                         bool isTime = false;
422                         int hours = 0;
423                         int minutes = 0;
424                         int seconds = 0;
425
426                         bool error = false;
427
428                         int i = start;
429                         while (i < s.Length) {
430                                 if (s [i] == 'T') {
431                                         isTime = true;
432                                         parseStep = 4;
433                                         i++;
434                                         start = i;
435                                         continue;
436                                 }
437                                 for (; i < s.Length; i++) {
438                                         if (!Char.IsDigit (s [i]))
439                                                 break;
440                                 }
441                                 int value = int.Parse (s.Substring (start, i - start));
442                                 switch (s [i]) {
443                                 case 'Y':
444                                         days += value * 365;
445                                         if (parseStep > 0)
446                                                 error = true;
447                                         else
448                                                 parseStep = 1;
449                                         break;
450                                 case 'M':
451                                         if (parseStep < 2) {
452                                                 days += 365 * (value / 12) + 30 * (value % 12);
453                                                 parseStep = 2;
454                                         } else if (isTime && parseStep < 6) {
455                                                 minutes = value;
456                                                 parseStep = 6;
457                                         }
458                                         else
459                                                 error = true;
460                                         break;
461                                 case 'D':
462                                         days += value;
463                                         if (parseStep > 2)
464                                                 error = true;
465                                         else
466                                                 parseStep = 3;
467                                         break;
468                                 case 'H':
469                                         hours = value;
470                                         if (!isTime || parseStep > 4)
471                                                 error = true;
472                                         else
473                                                 parseStep = 5;
474                                         break;
475                                 case 'S':
476                                         seconds = value;
477                                         if (!isTime || parseStep > 6)
478                                                 error = true;
479                                         else
480                                                 parseStep = 7;
481                                         break;
482                                 default:
483                                         error = true;
484                                         break;
485                                 }
486                                 if (error)
487                                         break;
488                                 ++i;
489                                 start = i;
490                         }
491                         if (error)
492                                 throw new ArgumentException ("Invalid format string for duration schema datatype.");
493                         TimeSpan ts = new TimeSpan (days, hours, minutes, seconds);
494                         return minusValue ? -ts : ts;
495                 }
496
497                 [CLSCompliant (false)]
498                 public static UInt16 ToUInt16(string s)
499                 {
500                         return UInt16.Parse(s, CultureInfo.InvariantCulture);
501                 }
502
503                 [CLSCompliant (false)]
504                 public static UInt32 ToUInt32(string s)
505                 {
506                         return UInt32.Parse(s, CultureInfo.InvariantCulture);
507                 }
508
509                 [CLSCompliant (false)]
510                 public static UInt64 ToUInt64(string s)
511                 {
512                         return UInt64.Parse(s, CultureInfo.InvariantCulture);
513                 }
514
515                 public static string VerifyName (string name)
516                 {
517                         if(name == null)
518                                 throw new ArgumentNullException("name");
519
520                         if(!XmlChar.IsName (name))
521                                 throw new XmlException("'" + name + "' is not a valid XML Name");
522                         return name;
523                         
524                 }
525
526                 public static string VerifyNCName(string ncname)
527                 {
528                         if(ncname == null)
529                                 throw new ArgumentNullException("ncname");
530
531                         if(!XmlChar.IsNCName (ncname))
532                                 throw new XmlException ("'" + ncname + "' is not a valid XML NCName");
533                         return ncname;
534                 }
535
536                 // It is documented as public method, but in fact it is not.
537                 internal static byte [] FromBinHexString (string s)
538                 {
539                         char [] chars = s.ToCharArray ();
540                         byte [] bytes = new byte [chars.Length / 2 + chars.Length % 2];
541                         FromBinHexString (chars, 0, chars.Length, bytes);
542                         return bytes;
543                 }
544
545                 internal static int FromBinHexString (char [] chars, int offset, int charLength, byte [] buffer)
546                 {
547                         int bufIndex = offset;
548                         for (int i = 0; i < charLength - 1; i += 2) {
549                                 buffer [bufIndex] = (chars [i] > '9' ?
550                                                 (byte) (chars [i] - 'A' + 10) :
551                                                 (byte) (chars [i] - '0'));
552                                 buffer [bufIndex] <<= 4;
553                                 buffer [bufIndex] += chars [i + 1] > '9' ?
554                                                 (byte) (chars [i + 1] - 'A' + 10) : 
555                                                 (byte) (chars [i + 1] - '0');
556                                 bufIndex++;
557                         }
558                         if (charLength %2 != 0)
559                                 buffer [bufIndex++] = (byte)
560                                         ((chars [charLength - 1] > '9' ?
561                                                 (byte) (chars [charLength - 1] - 'A' + 10) :
562                                                 (byte) (chars [charLength - 1] - '0'))
563                                         << 4);
564
565                         return bufIndex - offset;
566                 }
567         }
568 }