XmlFormatWriter.cs 8 KB
Newer Older
1
using System;
2
using System.Collections;
3
using System.IO;
4
using System.Text;
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
using System.Xml;
using Google.ProtocolBuffers.Descriptors;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// 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 <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>.
    /// </summary>
    public class XmlFormatWriter : AbstractTextWriter
    {
        public const string DefaultRootElementName = "root";
        private const int NestedArrayFlag = 0x0001;
        private readonly XmlWriter _output;
        private string _rootElementName;

22
        private static XmlWriterSettings DefaultSettings(Encoding encoding)
23
        {
24 25 26 27 28 29 30
            return new XmlWriterSettings()
                       {
                           CheckCharacters = false,
                           NewLineHandling = NewLineHandling.Entitize,
                           OmitXmlDeclaration = true,
                           Encoding = encoding,
                       };
31 32 33 34 35
        }

        /// <summary>
        /// Constructs the XmlFormatWriter to write to the given TextWriter
        /// </summary>
36 37 38 39 40
        public static XmlFormatWriter CreateInstance(TextWriter output)
        {
            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(output.Encoding)));
        }

41 42 43
        /// <summary>
        /// Constructs the XmlFormatWriter to write to the given stream
        /// </summary>
44 45 46 47 48
        public static XmlFormatWriter CreateInstance(Stream output)
        {
            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(Encoding.UTF8)));
        }

49 50 51
        /// <summary>
        /// Constructs the XmlFormatWriter to write to the given stream
        /// </summary>
52 53 54 55 56
        public static XmlFormatWriter CreateInstance(Stream output, Encoding encoding)
        {
            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(encoding)));
        }

57 58 59
        /// <summary>
        /// Constructs the XmlFormatWriter to write to the given XmlWriter
        /// </summary>
60 61 62 63
        public static XmlFormatWriter CreateInstance(XmlWriter output)
        {
            return new XmlFormatWriter(output);
        }
64 65

        protected XmlFormatWriter(XmlWriter output)
66 67 68 69 70 71 72 73 74 75
        {
            _output = output;
            _rootElementName = DefaultRootElementName;
        }

        /// <summary>
        /// Closes the underlying XmlTextWriter
        /// </summary>
        protected override void Dispose(bool disposing)
        {
76 77
            if (disposing)
            {
78
                _output.Close();
79
            }
80 81 82 83 84 85 86 87
        }

        /// <summary>
        /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
        /// </summary>
        public string RootElementName
        {
            get { return _rootElementName; }
88 89 90 91 92
            set
            {
                ThrowHelper.ThrowIfNull(value, "RootElementName");
                _rootElementName = value;
            }
93 94 95 96 97 98
        }

        /// <summary>
        /// Gets or sets the options to use while generating the XML
        /// </summary>
        public XmlWriterOptions Options { get; set; }
99

100 101 102
        /// <summary>
        /// Sets the options to use while generating the XML
        /// </summary>
103 104 105 106 107
        public XmlFormatWriter SetOptions(XmlWriterOptions options)
        {
            Options = options;
            return this;
        }
108

109 110 111 112
        private bool TestOption(XmlWriterOptions option)
        {
            return (Options & option) != 0;
        }
113 114 115 116 117

        /// <summary>
        /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
        /// </summary>
        public override void WriteMessage(IMessageLite message)
118 119 120
        {
            WriteMessage(_rootElementName, message);
        }
121 122 123 124

        /// <summary>
        /// Writes a message as an element with the given name
        /// </summary>
125
        public void WriteMessage(string elementName, IMessageLite message)
126 127 128 129 130 131 132
        {
            if (TestOption(XmlWriterOptions.OutputJsonTypes))
            {
                _output.WriteStartElement("root"); // json requires this is the root-element
                _output.WriteAttributeString("type", "object");
            }
            else
133
            {
134
                _output.WriteStartElement(elementName);
135
            }
136 137 138 139 140 141 142 143 144 145 146 147 148 149

            message.WriteTo(this);
            _output.WriteEndElement();
            _output.Flush();
        }

        /// <summary>
        /// Writes a message
        /// </summary>
        protected override void WriteMessageOrGroup(string field, IMessageLite message)
        {
            _output.WriteStartElement(field);

            if (TestOption(XmlWriterOptions.OutputJsonTypes))
150
            {
151
                _output.WriteAttributeString("type", "object");
152
            }
153 154 155 156 157 158 159 160 161 162 163 164 165 166

            message.WriteTo(this);
            _output.WriteEndElement();
        }

        /// <summary>
        /// Writes a String value
        /// </summary>
        protected override void WriteAsText(string field, string textValue, object typedValue)
        {
            _output.WriteStartElement(field);

            if (TestOption(XmlWriterOptions.OutputJsonTypes))
            {
167 168 169
                if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong ||
                    typedValue is double || typedValue is float)
                {
170
                    _output.WriteAttributeString("type", "number");
171
                }
172
                else if (typedValue is bool)
173
                {
174
                    _output.WriteAttributeString("type", "boolean");
175
                }
176 177 178 179 180
            }
            _output.WriteString(textValue);

            //Empty strings should not be written as empty elements '<item/>', rather as '<item></item>'
            if (_output.WriteState == WriteState.Element)
181
            {
182
                _output.WriteRaw("");
183
            }
184 185 186 187 188 189 190

            _output.WriteEndElement();
        }

        /// <summary>
        /// Writes an array of field values
        /// </summary>
191
        protected override void WriteArray(FieldType fieldType, string field, IEnumerable items)
192 193
        {
            //see if it's empty
194 195 196 197 198 199 200 201
            IEnumerator eitems = items.GetEnumerator();
            try
            {
                if (!eitems.MoveNext())
                {
                    return;
                }
            }
202
            finally
203 204 205 206 207 208
            {
                if (eitems is IDisposable)
                {
                    ((IDisposable) eitems).Dispose();
                }
            }
209 210 211 212 213

            if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes))
            {
                _output.WriteStartElement(field);
                if (TestOption(XmlWriterOptions.OutputJsonTypes))
214
                {
215
                    _output.WriteAttributeString("type", "array");
216
                }
217 218 219 220 221

                base.WriteArray(fieldType, "item", items);
                _output.WriteEndElement();
            }
            else
222
            {
223
                base.WriteArray(fieldType, field, items);
224
            }
225 226 227 228 229 230 231 232 233 234
        }

        /// <summary>
        /// Writes a System.Enum by the numeric and textual value
        /// </summary>
        protected override void WriteEnum(string field, int number, string name)
        {
            _output.WriteStartElement(field);

            if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues))
235
            {
236
                _output.WriteAttributeString("value", XmlConvert.ToString(number));
237
            }
238 239 240 241 242

            _output.WriteString(name);
            _output.WriteEndElement();
        }
    }
243
}