diff options
Diffstat (limited to 'OpenSim/Framework/Communications/Capabilities/LLSD.cs')
-rw-r--r-- | OpenSim/Framework/Communications/Capabilities/LLSD.cs | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/OpenSim/Framework/Communications/Capabilities/LLSD.cs b/OpenSim/Framework/Communications/Capabilities/LLSD.cs new file mode 100644 index 0000000..60b5f75 --- /dev/null +++ b/OpenSim/Framework/Communications/Capabilities/LLSD.cs | |||
@@ -0,0 +1,648 @@ | |||
1 | using System; | ||
2 | using System.Collections; | ||
3 | using System.Collections.Generic; | ||
4 | using System.Xml; | ||
5 | using System.IO; | ||
6 | using libsecondlife; | ||
7 | using System.Security.Cryptography; | ||
8 | using System.Text; | ||
9 | |||
10 | namespace OpenSim.Region.Capabilities | ||
11 | { | ||
12 | /// <summary> | ||
13 | /// | ||
14 | /// </summary> | ||
15 | public static class LLSD | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// | ||
19 | /// </summary> | ||
20 | public class LLSDParseException : Exception | ||
21 | { | ||
22 | public LLSDParseException(string message) : base(message) { } | ||
23 | } | ||
24 | |||
25 | /// <summary> | ||
26 | /// | ||
27 | /// </summary> | ||
28 | public class LLSDSerializeException : Exception | ||
29 | { | ||
30 | public LLSDSerializeException(string message) : base(message) { } | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// | ||
35 | /// </summary> | ||
36 | /// <param name="b"></param> | ||
37 | /// <returns></returns> | ||
38 | public static object LLSDDeserialize(byte[] b) | ||
39 | { | ||
40 | return LLSDDeserialize(new MemoryStream(b, false)); | ||
41 | } | ||
42 | |||
43 | /// <summary> | ||
44 | /// | ||
45 | /// </summary> | ||
46 | /// <param name="st"></param> | ||
47 | /// <returns></returns> | ||
48 | public static object LLSDDeserialize(Stream st) | ||
49 | { | ||
50 | XmlTextReader reader = new XmlTextReader(st); | ||
51 | reader.Read(); | ||
52 | SkipWS(reader); | ||
53 | |||
54 | if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd") | ||
55 | throw new LLSDParseException("Expected <llsd>"); | ||
56 | |||
57 | reader.Read(); | ||
58 | object ret = LLSDParseOne(reader); | ||
59 | SkipWS(reader); | ||
60 | |||
61 | if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd") | ||
62 | throw new LLSDParseException("Expected </llsd>"); | ||
63 | |||
64 | return ret; | ||
65 | } | ||
66 | |||
67 | /// <summary> | ||
68 | /// | ||
69 | /// </summary> | ||
70 | /// <param name="obj"></param> | ||
71 | /// <returns></returns> | ||
72 | public static byte[] LLSDSerialize(object obj) | ||
73 | { | ||
74 | StringWriter sw = new StringWriter(); | ||
75 | XmlTextWriter writer = new XmlTextWriter(sw); | ||
76 | writer.Formatting = Formatting.None; | ||
77 | |||
78 | writer.WriteStartElement(String.Empty, "llsd", String.Empty); | ||
79 | LLSDWriteOne(writer, obj); | ||
80 | writer.WriteEndElement(); | ||
81 | |||
82 | writer.Close(); | ||
83 | |||
84 | return Encoding.UTF8.GetBytes(sw.ToString()); | ||
85 | } | ||
86 | |||
87 | /// <summary> | ||
88 | /// | ||
89 | /// </summary> | ||
90 | /// <param name="writer"></param> | ||
91 | /// <param name="obj"></param> | ||
92 | public static void LLSDWriteOne(XmlTextWriter writer, object obj) | ||
93 | { | ||
94 | if (obj == null) | ||
95 | { | ||
96 | writer.WriteStartElement(String.Empty, "undef", String.Empty); | ||
97 | writer.WriteEndElement(); | ||
98 | return; | ||
99 | } | ||
100 | |||
101 | if (obj is string) | ||
102 | { | ||
103 | writer.WriteStartElement(String.Empty, "string", String.Empty); | ||
104 | writer.WriteString((string)obj); | ||
105 | writer.WriteEndElement(); | ||
106 | } | ||
107 | else if (obj is int) | ||
108 | { | ||
109 | writer.WriteStartElement(String.Empty, "integer", String.Empty); | ||
110 | writer.WriteString(obj.ToString()); | ||
111 | writer.WriteEndElement(); | ||
112 | } | ||
113 | else if (obj is double) | ||
114 | { | ||
115 | writer.WriteStartElement(String.Empty, "real", String.Empty); | ||
116 | writer.WriteString(obj.ToString()); | ||
117 | writer.WriteEndElement(); | ||
118 | } | ||
119 | else if (obj is bool) | ||
120 | { | ||
121 | bool b = (bool)obj; | ||
122 | writer.WriteStartElement(String.Empty, "boolean", String.Empty); | ||
123 | writer.WriteString(b ? "1" : "0"); | ||
124 | writer.WriteEndElement(); | ||
125 | } | ||
126 | else if (obj is ulong) | ||
127 | { | ||
128 | throw new Exception("ulong in LLSD is currently not implemented, fix me!"); | ||
129 | } | ||
130 | else if (obj is LLUUID) | ||
131 | { | ||
132 | LLUUID u = (LLUUID)obj; | ||
133 | writer.WriteStartElement(String.Empty, "uuid", String.Empty); | ||
134 | writer.WriteString(u.ToStringHyphenated()); | ||
135 | writer.WriteEndElement(); | ||
136 | } | ||
137 | else if (obj is Hashtable) | ||
138 | { | ||
139 | Hashtable h = obj as Hashtable; | ||
140 | writer.WriteStartElement(String.Empty, "map", String.Empty); | ||
141 | foreach (string key in h.Keys) | ||
142 | { | ||
143 | writer.WriteStartElement(String.Empty, "key", String.Empty); | ||
144 | writer.WriteString(key); | ||
145 | writer.WriteEndElement(); | ||
146 | LLSDWriteOne(writer, h[key]); | ||
147 | } | ||
148 | writer.WriteEndElement(); | ||
149 | } | ||
150 | else if (obj is ArrayList) | ||
151 | { | ||
152 | ArrayList a = obj as ArrayList; | ||
153 | writer.WriteStartElement(String.Empty, "array", String.Empty); | ||
154 | foreach (object item in a) | ||
155 | { | ||
156 | LLSDWriteOne(writer, item); | ||
157 | } | ||
158 | writer.WriteEndElement(); | ||
159 | } | ||
160 | else if (obj is byte[]) | ||
161 | { | ||
162 | byte[] b = obj as byte[]; | ||
163 | writer.WriteStartElement(String.Empty, "binary", String.Empty); | ||
164 | |||
165 | writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); | ||
166 | writer.WriteString("base64"); | ||
167 | writer.WriteEndAttribute(); | ||
168 | |||
169 | //// Calculate the length of the base64 output | ||
170 | //long length = (long)(4.0d * b.Length / 3.0d); | ||
171 | //if (length % 4 != 0) length += 4 - (length % 4); | ||
172 | |||
173 | //// Create the char[] for base64 output and fill it | ||
174 | //char[] tmp = new char[length]; | ||
175 | //int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0); | ||
176 | |||
177 | //writer.WriteString(new String(tmp)); | ||
178 | |||
179 | writer.WriteString(Convert.ToBase64String(b)); | ||
180 | writer.WriteEndElement(); | ||
181 | } | ||
182 | else | ||
183 | { | ||
184 | throw new LLSDSerializeException("Unknown type " + obj.GetType().Name); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// | ||
190 | /// </summary> | ||
191 | /// <param name="reader"></param> | ||
192 | /// <returns></returns> | ||
193 | public static object LLSDParseOne(XmlTextReader reader) | ||
194 | { | ||
195 | SkipWS(reader); | ||
196 | if (reader.NodeType != XmlNodeType.Element) | ||
197 | throw new LLSDParseException("Expected an element"); | ||
198 | |||
199 | string dtype = reader.LocalName; | ||
200 | object ret = null; | ||
201 | |||
202 | switch (dtype) | ||
203 | { | ||
204 | case "undef": | ||
205 | { | ||
206 | if (reader.IsEmptyElement) | ||
207 | { | ||
208 | reader.Read(); | ||
209 | return null; | ||
210 | } | ||
211 | |||
212 | reader.Read(); | ||
213 | SkipWS(reader); | ||
214 | ret = null; | ||
215 | break; | ||
216 | } | ||
217 | case "boolean": | ||
218 | { | ||
219 | if (reader.IsEmptyElement) | ||
220 | { | ||
221 | reader.Read(); | ||
222 | return false; | ||
223 | } | ||
224 | |||
225 | reader.Read(); | ||
226 | string s = reader.ReadString().Trim(); | ||
227 | |||
228 | if (s == String.Empty || s == "false" || s == "0") | ||
229 | ret = false; | ||
230 | else if (s == "true" || s == "1") | ||
231 | ret = true; | ||
232 | else | ||
233 | throw new LLSDParseException("Bad boolean value " + s); | ||
234 | |||
235 | break; | ||
236 | } | ||
237 | case "integer": | ||
238 | { | ||
239 | if (reader.IsEmptyElement) | ||
240 | { | ||
241 | reader.Read(); | ||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | reader.Read(); | ||
246 | ret = Convert.ToInt32(reader.ReadString().Trim()); | ||
247 | break; | ||
248 | } | ||
249 | case "real": | ||
250 | { | ||
251 | if (reader.IsEmptyElement) | ||
252 | { | ||
253 | reader.Read(); | ||
254 | return 0.0f; | ||
255 | } | ||
256 | |||
257 | reader.Read(); | ||
258 | ret = Convert.ToDouble(reader.ReadString().Trim()); | ||
259 | break; | ||
260 | } | ||
261 | case "uuid": | ||
262 | { | ||
263 | if (reader.IsEmptyElement) | ||
264 | { | ||
265 | reader.Read(); | ||
266 | return LLUUID.Zero; | ||
267 | } | ||
268 | |||
269 | reader.Read(); | ||
270 | ret = new LLUUID(reader.ReadString().Trim()); | ||
271 | break; | ||
272 | } | ||
273 | case "string": | ||
274 | { | ||
275 | if (reader.IsEmptyElement) | ||
276 | { | ||
277 | reader.Read(); | ||
278 | return String.Empty; | ||
279 | } | ||
280 | |||
281 | reader.Read(); | ||
282 | ret = reader.ReadString(); | ||
283 | break; | ||
284 | } | ||
285 | case "binary": | ||
286 | { | ||
287 | if (reader.IsEmptyElement) | ||
288 | { | ||
289 | reader.Read(); | ||
290 | return new byte[0]; | ||
291 | } | ||
292 | |||
293 | if (reader.GetAttribute("encoding") != null && | ||
294 | reader.GetAttribute("encoding") != "base64") | ||
295 | { | ||
296 | throw new LLSDParseException("Unknown encoding: " + reader.GetAttribute("encoding")); | ||
297 | } | ||
298 | |||
299 | reader.Read(); | ||
300 | FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces); | ||
301 | byte[] inp = Encoding.ASCII.GetBytes(reader.ReadString()); | ||
302 | ret = b64.TransformFinalBlock(inp, 0, inp.Length); | ||
303 | break; | ||
304 | } | ||
305 | case "date": | ||
306 | { | ||
307 | reader.Read(); | ||
308 | throw new Exception("LLSD TODO: date"); | ||
309 | } | ||
310 | case "map": | ||
311 | { | ||
312 | return LLSDParseMap(reader); | ||
313 | } | ||
314 | case "array": | ||
315 | { | ||
316 | return LLSDParseArray(reader); | ||
317 | } | ||
318 | default: | ||
319 | throw new LLSDParseException("Unknown element <" + dtype + ">"); | ||
320 | } | ||
321 | |||
322 | if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype) | ||
323 | { | ||
324 | throw new LLSDParseException("Expected </" + dtype + ">"); | ||
325 | } | ||
326 | |||
327 | reader.Read(); | ||
328 | return ret; | ||
329 | } | ||
330 | |||
331 | /// <summary> | ||
332 | /// | ||
333 | /// </summary> | ||
334 | /// <param name="reader"></param> | ||
335 | /// <returns></returns> | ||
336 | public static Hashtable LLSDParseMap(XmlTextReader reader) | ||
337 | { | ||
338 | Hashtable ret = new Hashtable(); | ||
339 | |||
340 | if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") | ||
341 | throw new LLSDParseException("Expected <map>"); | ||
342 | |||
343 | if (reader.IsEmptyElement) | ||
344 | { | ||
345 | reader.Read(); | ||
346 | return ret; | ||
347 | } | ||
348 | |||
349 | reader.Read(); | ||
350 | |||
351 | while (true) | ||
352 | { | ||
353 | SkipWS(reader); | ||
354 | if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") | ||
355 | { | ||
356 | reader.Read(); | ||
357 | break; | ||
358 | } | ||
359 | |||
360 | if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") | ||
361 | throw new LLSDParseException("Expected <key>"); | ||
362 | |||
363 | string key = reader.ReadString(); | ||
364 | |||
365 | if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") | ||
366 | throw new LLSDParseException("Expected </key>"); | ||
367 | |||
368 | reader.Read(); | ||
369 | object val = LLSDParseOne(reader); | ||
370 | ret[key] = val; | ||
371 | } | ||
372 | |||
373 | return ret; // TODO | ||
374 | } | ||
375 | |||
376 | /// <summary> | ||
377 | /// | ||
378 | /// </summary> | ||
379 | /// <param name="reader"></param> | ||
380 | /// <returns></returns> | ||
381 | public static ArrayList LLSDParseArray(XmlTextReader reader) | ||
382 | { | ||
383 | ArrayList ret = new ArrayList(); | ||
384 | |||
385 | if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") | ||
386 | throw new LLSDParseException("Expected <array>"); | ||
387 | |||
388 | if (reader.IsEmptyElement) | ||
389 | { | ||
390 | reader.Read(); | ||
391 | return ret; | ||
392 | } | ||
393 | |||
394 | reader.Read(); | ||
395 | |||
396 | while (true) | ||
397 | { | ||
398 | SkipWS(reader); | ||
399 | |||
400 | if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") | ||
401 | { | ||
402 | reader.Read(); | ||
403 | break; | ||
404 | } | ||
405 | |||
406 | ret.Insert(ret.Count, LLSDParseOne(reader)); | ||
407 | } | ||
408 | |||
409 | return ret; // TODO | ||
410 | } | ||
411 | |||
412 | /// <summary> | ||
413 | /// | ||
414 | /// </summary> | ||
415 | /// <param name="count"></param> | ||
416 | /// <returns></returns> | ||
417 | private static string GetSpaces(int count) | ||
418 | { | ||
419 | StringBuilder b = new StringBuilder(); | ||
420 | for (int i = 0; i < count; i++) b.Append(" "); | ||
421 | return b.ToString(); | ||
422 | } | ||
423 | |||
424 | /// <summary> | ||
425 | /// | ||
426 | /// </summary> | ||
427 | /// <param name="obj"></param> | ||
428 | /// <param name="indent"></param> | ||
429 | /// <returns></returns> | ||
430 | public static String LLSDDump(object obj, int indent) | ||
431 | { | ||
432 | if (obj == null) | ||
433 | { | ||
434 | return GetSpaces(indent) + "- undef\n"; | ||
435 | } | ||
436 | else if (obj is string) | ||
437 | { | ||
438 | return GetSpaces(indent) + "- string \"" + (string)obj + "\"\n"; | ||
439 | } | ||
440 | else if (obj is int) | ||
441 | { | ||
442 | return GetSpaces(indent) + "- integer " + obj.ToString() + "\n"; | ||
443 | } | ||
444 | else if (obj is double) | ||
445 | { | ||
446 | return GetSpaces(indent) + "- float " + obj.ToString() + "\n"; | ||
447 | } | ||
448 | else if (obj is LLUUID) | ||
449 | { | ||
450 | return GetSpaces(indent) + "- uuid " + ((LLUUID)obj).ToStringHyphenated() + Environment.NewLine; | ||
451 | } | ||
452 | else if (obj is Hashtable) | ||
453 | { | ||
454 | StringBuilder ret = new StringBuilder(); | ||
455 | ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine); | ||
456 | Hashtable map = (Hashtable)obj; | ||
457 | |||
458 | foreach (string key in map.Keys) | ||
459 | { | ||
460 | ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine); | ||
461 | ret.Append(LLSDDump(map[key], indent + 3)); | ||
462 | } | ||
463 | |||
464 | return ret.ToString(); | ||
465 | } | ||
466 | else if (obj is ArrayList) | ||
467 | { | ||
468 | StringBuilder ret = new StringBuilder(); | ||
469 | ret.Append(GetSpaces(indent) + "- array\n"); | ||
470 | ArrayList list = (ArrayList)obj; | ||
471 | |||
472 | foreach (object item in list) | ||
473 | { | ||
474 | ret.Append(LLSDDump(item, indent + 2)); | ||
475 | } | ||
476 | |||
477 | return ret.ToString(); | ||
478 | } | ||
479 | else if (obj is byte[]) | ||
480 | { | ||
481 | return GetSpaces(indent) + "- binary\n" + Helpers.FieldToHexString((byte[])obj, GetSpaces(indent)) + | ||
482 | Environment.NewLine; | ||
483 | } | ||
484 | else | ||
485 | { | ||
486 | return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | public static object ParseTerseLLSD(string llsd) | ||
491 | { | ||
492 | int notused; | ||
493 | return ParseTerseLLSD(llsd, out notused); | ||
494 | } | ||
495 | |||
496 | public static object ParseTerseLLSD(string llsd, out int endPos) | ||
497 | { | ||
498 | if (llsd.Length == 0) | ||
499 | { | ||
500 | endPos = 0; | ||
501 | return null; | ||
502 | } | ||
503 | |||
504 | // Identify what type of object this is | ||
505 | switch (llsd[0]) | ||
506 | { | ||
507 | case '!': | ||
508 | throw new LLSDParseException("Undefined value type encountered"); | ||
509 | case '1': | ||
510 | endPos = 1; | ||
511 | return true; | ||
512 | case '0': | ||
513 | endPos = 1; | ||
514 | return false; | ||
515 | case 'i': | ||
516 | { | ||
517 | if (llsd.Length < 2) throw new LLSDParseException("Integer value type with no value"); | ||
518 | int value; | ||
519 | endPos = FindEnd(llsd, 1); | ||
520 | |||
521 | if (Int32.TryParse(llsd.Substring(1, endPos - 1), out value)) | ||
522 | return value; | ||
523 | else | ||
524 | throw new LLSDParseException("Failed to parse integer value type"); | ||
525 | } | ||
526 | case 'r': | ||
527 | { | ||
528 | if (llsd.Length < 2) throw new LLSDParseException("Real value type with no value"); | ||
529 | double value; | ||
530 | endPos = FindEnd(llsd, 1); | ||
531 | |||
532 | if (Double.TryParse(llsd.Substring(1, endPos - 1), System.Globalization.NumberStyles.Float, | ||
533 | Helpers.EnUsCulture.NumberFormat, out value)) | ||
534 | return value; | ||
535 | else | ||
536 | throw new LLSDParseException("Failed to parse double value type"); | ||
537 | } | ||
538 | case 'u': | ||
539 | { | ||
540 | if (llsd.Length < 17) throw new LLSDParseException("LLUUID value type with no value"); | ||
541 | LLUUID value; | ||
542 | endPos = FindEnd(llsd, 1); | ||
543 | |||
544 | if (LLUUID.TryParse(llsd.Substring(1, endPos - 1), out value)) | ||
545 | return value; | ||
546 | else | ||
547 | throw new LLSDParseException("Failed to parse LLUUID value type"); | ||
548 | } | ||
549 | case 'b': | ||
550 | //byte[] value = new byte[llsd.Length - 1]; | ||
551 | // This isn't the actual binary LLSD format, just the terse format sent | ||
552 | // at login so I don't even know if there is a binary type | ||
553 | throw new LLSDParseException("Binary value type is unimplemented"); | ||
554 | case 's': | ||
555 | case 'l': | ||
556 | if (llsd.Length < 2) throw new LLSDParseException("String value type with no value"); | ||
557 | endPos = FindEnd(llsd, 1); | ||
558 | return llsd.Substring(1, endPos - 1); | ||
559 | case 'd': | ||
560 | // Never seen one before, don't know what the format is | ||
561 | throw new LLSDParseException("Date value type is unimplemented"); | ||
562 | case '[': | ||
563 | { | ||
564 | if (llsd.IndexOf(']') == -1) throw new LLSDParseException("Invalid array"); | ||
565 | |||
566 | int pos = 0; | ||
567 | ArrayList array = new ArrayList(); | ||
568 | |||
569 | while (llsd[pos] != ']') | ||
570 | { | ||
571 | ++pos; | ||
572 | |||
573 | // Advance past comma if need be | ||
574 | if (llsd[pos] == ',') ++pos; | ||
575 | |||
576 | // Allow a single whitespace character | ||
577 | if (pos < llsd.Length && llsd[pos] == ' ') ++pos; | ||
578 | |||
579 | int end; | ||
580 | array.Add(ParseTerseLLSD(llsd.Substring(pos), out end)); | ||
581 | pos += end; | ||
582 | } | ||
583 | |||
584 | endPos = pos + 1; | ||
585 | return array; | ||
586 | } | ||
587 | case '{': | ||
588 | { | ||
589 | if (llsd.IndexOf('}') == -1) throw new LLSDParseException("Invalid map"); | ||
590 | |||
591 | int pos = 0; | ||
592 | Hashtable hashtable = new Hashtable(); | ||
593 | |||
594 | while (llsd[pos] != '}') | ||
595 | { | ||
596 | ++pos; | ||
597 | |||
598 | // Advance past comma if need be | ||
599 | if (llsd[pos] == ',') ++pos; | ||
600 | |||
601 | // Allow a single whitespace character | ||
602 | if (pos < llsd.Length && llsd[pos] == ' ') ++pos; | ||
603 | |||
604 | if (llsd[pos] != '\'') throw new LLSDParseException("Expected a map key"); | ||
605 | int endquote = llsd.IndexOf('\'', pos + 1); | ||
606 | if (endquote == -1 || (endquote + 1) >= llsd.Length || llsd[endquote + 1] != ':') | ||
607 | throw new LLSDParseException("Invalid map format"); | ||
608 | string key = llsd.Substring(pos, endquote - pos); | ||
609 | key = key.Replace("'", String.Empty); | ||
610 | pos += (endquote - pos) + 2; | ||
611 | |||
612 | int end; | ||
613 | hashtable.Add(key, ParseTerseLLSD(llsd.Substring(pos), out end)); | ||
614 | pos += end; | ||
615 | } | ||
616 | |||
617 | endPos = pos + 1; | ||
618 | return hashtable; | ||
619 | } | ||
620 | default: | ||
621 | throw new Exception("Unknown value type"); | ||
622 | } | ||
623 | } | ||
624 | |||
625 | private static int FindEnd(string llsd, int start) | ||
626 | { | ||
627 | int end = llsd.IndexOfAny(new char[] { ',', ']', '}' }); | ||
628 | if (end == -1) end = llsd.Length - 1; | ||
629 | return end; | ||
630 | } | ||
631 | |||
632 | /// <summary> | ||
633 | /// | ||
634 | /// </summary> | ||
635 | /// <param name="reader"></param> | ||
636 | private static void SkipWS(XmlTextReader reader) | ||
637 | { | ||
638 | while ( | ||
639 | reader.NodeType == XmlNodeType.Comment || | ||
640 | reader.NodeType == XmlNodeType.Whitespace || | ||
641 | reader.NodeType == XmlNodeType.SignificantWhitespace || | ||
642 | reader.NodeType == XmlNodeType.XmlDeclaration) | ||
643 | { | ||
644 | reader.Read(); | ||
645 | } | ||
646 | } | ||
647 | } | ||
648 | } | ||