using System; using System.Collections; using System.IO; using System.Text; using System.Xml; using Google.ProtocolBuffers.Descriptors; namespace Google.ProtocolBuffers.Serialization { /// /// Writes a proto buffer to an XML document or fragment. .NET 3.5 users may also /// use this class to produce Json by setting the options to support Json and providing /// an XmlWriter obtained from . /// public class XmlFormatWriter : AbstractTextWriter { private static readonly Encoding DefaultEncoding = new UTF8Encoding(false); public const string DefaultRootElementName = "root"; private readonly XmlWriter _output; // The default element name used for WriteMessageStart private string _rootElementName; // Used to assert matching WriteMessageStart/WriteMessageEnd calls private int _messageOpenCount; private static XmlWriterSettings DefaultSettings(Encoding encoding) { return new XmlWriterSettings() { CheckCharacters = false, NewLineHandling = NewLineHandling.Entitize, OmitXmlDeclaration = true, Encoding = encoding, }; } /// /// Constructs the XmlFormatWriter to write to the given TextWriter /// public static XmlFormatWriter CreateInstance(TextWriter output) { return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(output.Encoding))); } /// /// Constructs the XmlFormatWriter to write to the given stream /// public static XmlFormatWriter CreateInstance(Stream output) { return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding))); } /// /// Constructs the XmlFormatWriter to write to the given stream /// public static XmlFormatWriter CreateInstance(Stream output, Encoding encoding) { return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(encoding))); } /// /// Constructs the XmlFormatWriter to write to the given XmlWriter /// public static XmlFormatWriter CreateInstance(XmlWriter output) { return new XmlFormatWriter(output); } protected XmlFormatWriter(XmlWriter output) { _output = output; _messageOpenCount = 0; _rootElementName = DefaultRootElementName; } /// /// Gets or sets the default element name to use when using the Merge<TBuilder>() /// public string RootElementName { get { return _rootElementName; } set { ThrowHelper.ThrowIfNull(value, "RootElementName"); _rootElementName = value; } } /// /// Gets or sets the options to use while generating the XML /// public XmlWriterOptions Options { get; set; } /// /// Sets the options to use while generating the XML /// public XmlFormatWriter SetOptions(XmlWriterOptions options) { Options = options; return this; } private bool TestOption(XmlWriterOptions option) { return (Options & option) != 0; } /// /// Completes any pending write operations /// public override void Flush() { _output.Flush(); base.Flush(); } /// /// Used to write the root-message preamble, in xml this is open element for RootElementName, /// by default "<root>". After this call you can call IMessageLite.MergeTo(...) and /// complete the message with a call to WriteMessageEnd(). /// public override void WriteMessageStart() { WriteMessageStart(_rootElementName); } /// /// Used to write the root-message preamble, in xml this is open element for elementName. /// After this call you can call IMessageLite.MergeTo(...) and complete the message with /// a call to WriteMessageEnd(). /// public void WriteMessageStart(string elementName) { if (TestOption(XmlWriterOptions.OutputJsonTypes)) { _output.WriteStartElement("root"); // json requires this is the root-element _output.WriteAttributeString("type", "object"); } else { _output.WriteStartElement(elementName); } _messageOpenCount++; } /// /// Used to complete a root-message previously started with a call to WriteMessageStart() /// public override void WriteMessageEnd() { if (_messageOpenCount <= 0) { throw new InvalidOperationException(); } _output.WriteEndElement(); _output.Flush(); _messageOpenCount--; } /// /// Writes a message as an element using the name defined in /// public override void WriteMessage(IMessageLite message) { WriteMessage(_rootElementName, message); } /// /// Writes a message as an element with the given name /// public void WriteMessage(string elementName, IMessageLite message) { WriteMessageStart(elementName); message.WriteTo(this); WriteMessageEnd(); } /// /// Writes a message /// protected override void WriteMessageOrGroup(string field, IMessageLite message) { _output.WriteStartElement(field); if (TestOption(XmlWriterOptions.OutputJsonTypes)) { _output.WriteAttributeString("type", "object"); } message.WriteTo(this); _output.WriteEndElement(); } /// /// Writes a String value /// protected override void WriteAsText(string field, string textValue, object typedValue) { _output.WriteStartElement(field); if (TestOption(XmlWriterOptions.OutputJsonTypes)) { if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float) { _output.WriteAttributeString("type", "number"); } else if (typedValue is bool) { _output.WriteAttributeString("type", "boolean"); } } _output.WriteString(textValue); //Empty strings should not be written as empty elements '', rather as '' if (_output.WriteState == WriteState.Element) { _output.WriteRaw(""); } _output.WriteEndElement(); } /// /// Writes an array of field values /// protected override void WriteArray(FieldType fieldType, string field, IEnumerable items) { //see if it's empty IEnumerator eitems = items.GetEnumerator(); try { if (!eitems.MoveNext()) { return; } } finally { if (eitems is IDisposable) { ((IDisposable) eitems).Dispose(); } } if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes)) { _output.WriteStartElement(field); if (TestOption(XmlWriterOptions.OutputJsonTypes)) { _output.WriteAttributeString("type", "array"); } base.WriteArray(fieldType, "item", items); _output.WriteEndElement(); } else { base.WriteArray(fieldType, field, items); } } /// /// Writes a System.Enum by the numeric and textual value /// protected override void WriteEnum(string field, int number, string name) { _output.WriteStartElement(field); if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues)) { _output.WriteAttributeString("value", XmlConvert.ToString(number)); } _output.WriteString(name); _output.WriteEndElement(); } } }