using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Xml.Schema; using System.Text; namespace libsecondlife.LLSD { public static partial class LLSDParser { private static XmlSchema XmlSchema; private static XmlTextReader XmlTextReader; private static string LastXmlErrors = String.Empty; private static object XmlValidationLock = new object(); public static object DeserializeXml(byte[] xmlData) { return DeserializeXml(new XmlTextReader(new MemoryStream(xmlData, false))); } public static object DeserializeXml(XmlTextReader xmlData) { xmlData.Read(); SkipWhitespace(xmlData); xmlData.Read(); object ret = ParseXmlElement(xmlData); return ret; } public static byte[] SerializeXmlBytes(object data) { return Encoding.UTF8.GetBytes(SerializeXmlString(data)); } public static string SerializeXmlString(object data) { StringWriter sw = new StringWriter(); XmlTextWriter writer = new XmlTextWriter(sw); writer.Formatting = Formatting.None; writer.WriteStartElement(String.Empty, "llsd", String.Empty); SerializeXmlElement(writer, data); writer.WriteEndElement(); writer.Close(); return sw.ToString(); } public static void SerializeXmlElement(XmlTextWriter writer, object obj) { if (obj == null) { writer.WriteStartElement(String.Empty, "undef", String.Empty); writer.WriteEndElement(); } else if (obj is string) { writer.WriteStartElement(String.Empty, "string", String.Empty); writer.WriteString((string)obj); writer.WriteEndElement(); } else if (obj is int || obj is uint || obj is short || obj is ushort || obj is byte || obj is sbyte) { writer.WriteStartElement(String.Empty, "integer", String.Empty); writer.WriteString(obj.ToString()); writer.WriteEndElement(); } else if (obj is double) { double value = (double)obj; writer.WriteStartElement(String.Empty, "real", String.Empty); writer.WriteString(value.ToString(Helpers.EnUsCulture)); writer.WriteEndElement(); } else if (obj is float) { float value = (float)obj; writer.WriteStartElement(String.Empty, "real", String.Empty); writer.WriteString(value.ToString(Helpers.EnUsCulture)); writer.WriteEndElement(); } else if (obj is long) { // 64-bit integers are not natively supported in LLSD, so we convert to a byte array long value = (long)obj; byte[] bytes = BitConverter.GetBytes(value); writer.WriteStartElement(String.Empty, "binary", String.Empty); writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); writer.WriteString("base64"); writer.WriteEndAttribute(); writer.WriteString(Convert.ToBase64String(bytes)); writer.WriteEndElement(); } else if (obj is ulong) { // 64-bit integers are not natively supported in LLSD, so we convert to a byte array ulong value = (ulong)obj; byte[] bytes = BitConverter.GetBytes(value); writer.WriteStartElement(String.Empty, "binary", String.Empty); writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); writer.WriteString("base64"); writer.WriteEndAttribute(); writer.WriteString(Convert.ToBase64String(bytes)); writer.WriteEndElement(); } else if (obj is bool) { bool b = (bool)obj; writer.WriteStartElement(String.Empty, "boolean", String.Empty); writer.WriteString(b ? "1" : "0"); writer.WriteEndElement(); } else if (obj is LLUUID) { LLUUID u = (LLUUID)obj; writer.WriteStartElement(String.Empty, "uuid", String.Empty); writer.WriteString(u.ToStringHyphenated()); writer.WriteEndElement(); } else if (obj is Dictionary) { Dictionary d = obj as Dictionary; writer.WriteStartElement(String.Empty, "map", String.Empty); foreach (string key in d.Keys) { writer.WriteStartElement(String.Empty, "key", String.Empty); writer.WriteString(key); writer.WriteEndElement(); SerializeXmlElement(writer, d[key]); } writer.WriteEndElement(); } else if (obj is System.Collections.Hashtable) { System.Collections.Hashtable h = obj as System.Collections.Hashtable; writer.WriteStartElement(String.Empty, "map", String.Empty); foreach (string key in h.Keys) { writer.WriteStartElement(String.Empty, "key", String.Empty); writer.WriteString(key); writer.WriteEndElement(); SerializeXmlElement(writer, h[key]); } writer.WriteEndElement(); } else if (obj is List) { List l = obj as List; writer.WriteStartElement(String.Empty, "array", String.Empty); for (int i = 0; i < l.Count; i++) { SerializeXmlElement(writer, l[i]); } writer.WriteEndElement(); } else if (obj is System.Collections.ArrayList) { System.Collections.ArrayList a = obj as System.Collections.ArrayList; writer.WriteStartElement(String.Empty, "array", String.Empty); for (int i = 0; i < a.Count; i++) { SerializeXmlElement(writer, a[i]); } writer.WriteEndElement(); } else if (obj is byte[]) { writer.WriteStartElement(String.Empty, "binary", String.Empty); writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); writer.WriteString("base64"); writer.WriteEndAttribute(); writer.WriteString(Convert.ToBase64String((byte[])obj)); writer.WriteEndElement(); } else if (obj.GetType().IsArray) { Array a = (Array)obj; writer.WriteStartElement(String.Empty, "array", String.Empty); for (int i = 0; i < a.Length; i++) { SerializeXmlElement(writer, a.GetValue(i)); } writer.WriteEndElement(); } else { throw new LLSDException("Unknown type " + obj.GetType().Name); } } public static bool TryValidate(XmlTextReader xmlData, out string error) { lock (XmlValidationLock) { LastXmlErrors = String.Empty; XmlTextReader = xmlData; CreateSchema(); XmlReaderSettings readerSettings = new XmlReaderSettings(); readerSettings.ValidationType = ValidationType.Schema; readerSettings.Schemas.Add(XmlSchema); readerSettings.ValidationEventHandler += new ValidationEventHandler(SchemaValidationHandler); XmlReader reader = XmlReader.Create(xmlData, readerSettings); try { while (reader.Read()) { } } catch (XmlException) { error = LastXmlErrors; return false; } if (LastXmlErrors == String.Empty) { error = null; return true; } else { error = LastXmlErrors; return false; } } } private static object ParseXmlElement(XmlTextReader reader) { SkipWhitespace(reader); if (reader.NodeType != XmlNodeType.Element) throw new LLSDException("Expected an element"); string type = reader.LocalName; object ret = null; switch (type) { case "undef": if (reader.IsEmptyElement) { reader.Read(); return null; } reader.Read(); SkipWhitespace(reader); ret = null; break; case "boolean": if (reader.IsEmptyElement) { reader.Read(); return false; } if (reader.Read()) { string s = reader.ReadString().Trim(); if (!String.IsNullOrEmpty(s) && (s == "true" || s == "1")) { ret = true; break; } } ret = false; break; case "integer": if (reader.IsEmptyElement) { reader.Read(); return 0; } if (reader.Read()) { int value = 0; Helpers.TryParse(reader.ReadString().Trim(), out value); ret = value; break; } ret = 0; break; case "real": if (reader.IsEmptyElement) { reader.Read(); return 0d; } if (reader.Read()) { double value = 0d; string str = reader.ReadString().Trim().ToLower(); if (str == "nan") value = Double.NaN; else Helpers.TryParse(str, out value); ret = value; break; } ret = 0d; break; case "uuid": if (reader.IsEmptyElement) { reader.Read(); return LLUUID.Zero; } if (reader.Read()) { LLUUID value = LLUUID.Zero; LLUUID.TryParse(reader.ReadString().Trim(), out value); ret = value; break; } ret = LLUUID.Zero; break; case "date": if (reader.IsEmptyElement) { reader.Read(); return Helpers.Epoch; } if (reader.Read()) { DateTime value = Helpers.Epoch; Helpers.TryParse(reader.ReadString().Trim(), out value); ret = value; break; } ret = Helpers.Epoch; break; case "string": if (reader.IsEmptyElement) { reader.Read(); return String.Empty; } if (reader.Read()) { ret = reader.ReadString(); break; } ret = String.Empty; break; case "binary": if (reader.IsEmptyElement) { reader.Read(); return new byte[0]; } if (reader.GetAttribute("encoding") != null && reader.GetAttribute("encoding") != "base64") throw new LLSDException("Unsupported binary encoding: " + reader.GetAttribute("encoding")); if (reader.Read()) { try { ret = Convert.FromBase64String(reader.ReadString().Trim()); break; } catch (FormatException ex) { throw new LLSDException("Binary decoding exception: " + ex.Message); } } ret = new byte[0]; break; case "map": return ParseXmlMap(reader); case "array": return ParseXmlArray(reader); default: reader.Read(); ret = null; break; } if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != type) { throw new LLSDException("Expected "); } else { reader.Read(); return ret; } } private static Dictionary ParseXmlMap(XmlTextReader reader) { Dictionary dict = new Dictionary(); if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") throw new NotImplementedException("Expected "); //while (reader.NodeType != XmlNodeType.Element && reader.LocalName != "map") //{ // if (!reader.Read()) // throw new LLSDException("Couldn't find a map to parse"); //} if (reader.IsEmptyElement) { reader.Read(); return dict; } if (reader.Read()) { while (true) { SkipWhitespace(reader); if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") { reader.Read(); break; } if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") throw new LLSDException("Expected "); string key = reader.ReadString(); if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") throw new LLSDException("Expected "); if (reader.Read()) dict[key] = ParseXmlElement(reader); else throw new LLSDException("Failed to parse a value for key " + key); } } return dict; } private static List ParseXmlArray(XmlTextReader reader) { List list = new List(); if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") throw new LLSDException("Expected "); //while (reader.NodeType != XmlNodeType.Element && reader.LocalName != "array") //{ // if (!reader.Read()) // throw new LLSDException("Couldn't find an array to parse"); //} if (reader.IsEmptyElement) { reader.Read(); return list; } if (reader.Read()) { while (true) { SkipWhitespace(reader); if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") { reader.Read(); break; } list.Add(ParseXmlElement(reader)); } } return list; } private static void SkipWhitespace(XmlTextReader reader) { while ( reader.NodeType == XmlNodeType.Comment || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace || reader.NodeType == XmlNodeType.XmlDeclaration) { reader.Read(); } } private static void CreateSchema() { if (XmlSchema == null) { #region XSD string schemaText = @" "; #endregion XSD MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(schemaText)); XmlSchema = new XmlSchema(); XmlSchema = XmlSchema.Read(stream, new ValidationEventHandler(SchemaValidationHandler)); } } private static void SchemaValidationHandler(object sender, ValidationEventArgs args) { string error = String.Format("Line: {0} - Position: {1} - {2}", XmlTextReader.LineNumber, XmlTextReader.LinePosition, args.Message); if (LastXmlErrors == String.Empty) LastXmlErrors = error; else LastXmlErrors += Helpers.NewLine + error; } } }