blob: fc3f9dc2cac63125dfbd6cbb08de71ff10b6f7e5 [file] [log] [blame]
csharptest60fd7732011-09-09 12:18:16 -05001using System;
csharptest74c5e0c2011-07-14 13:06:22 -05002using System.Collections;
csharptest2b868842011-06-10 14:41:47 -05003using System.IO;
csharptest7fc785c2011-06-10 23:54:53 -05004using System.Text;
csharptest2b868842011-06-10 14:41:47 -05005using System.Xml;
6using Google.ProtocolBuffers.Descriptors;
7
8namespace Google.ProtocolBuffers.Serialization
9{
10 /// <summary>
11 /// Writes a proto buffer to an XML document or fragment. .NET 3.5 users may also
12 /// use this class to produce Json by setting the options to support Json and providing
13 /// an XmlWriter obtained from <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>.
14 /// </summary>
15 public class XmlFormatWriter : AbstractTextWriter
16 {
csharptest60fd7732011-09-09 12:18:16 -050017 private static readonly Encoding DefaultEncoding = new UTF8Encoding(false);
csharptest2b868842011-06-10 14:41:47 -050018 public const string DefaultRootElementName = "root";
19 private const int NestedArrayFlag = 0x0001;
20 private readonly XmlWriter _output;
21 private string _rootElementName;
csharptest60fd7732011-09-09 12:18:16 -050022 private int _messageOpenCount;
csharptest2b868842011-06-10 14:41:47 -050023
csharptest74c5e0c2011-07-14 13:06:22 -050024 private static XmlWriterSettings DefaultSettings(Encoding encoding)
csharptest2b868842011-06-10 14:41:47 -050025 {
csharptest74c5e0c2011-07-14 13:06:22 -050026 return new XmlWriterSettings()
27 {
28 CheckCharacters = false,
29 NewLineHandling = NewLineHandling.Entitize,
30 OmitXmlDeclaration = true,
31 Encoding = encoding,
32 };
csharptest2b868842011-06-10 14:41:47 -050033 }
34
35 /// <summary>
36 /// Constructs the XmlFormatWriter to write to the given TextWriter
37 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050038 public static XmlFormatWriter CreateInstance(TextWriter output)
39 {
40 return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(output.Encoding)));
41 }
42
csharptest2b868842011-06-10 14:41:47 -050043 /// <summary>
44 /// Constructs the XmlFormatWriter to write to the given stream
45 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050046 public static XmlFormatWriter CreateInstance(Stream output)
47 {
csharptest60fd7732011-09-09 12:18:16 -050048 return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding)));
csharptest74c5e0c2011-07-14 13:06:22 -050049 }
50
csharptest7fc785c2011-06-10 23:54:53 -050051 /// <summary>
52 /// Constructs the XmlFormatWriter to write to the given stream
53 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050054 public static XmlFormatWriter CreateInstance(Stream output, Encoding encoding)
55 {
56 return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(encoding)));
57 }
58
csharptest2b868842011-06-10 14:41:47 -050059 /// <summary>
60 /// Constructs the XmlFormatWriter to write to the given XmlWriter
61 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050062 public static XmlFormatWriter CreateInstance(XmlWriter output)
63 {
64 return new XmlFormatWriter(output);
65 }
csharptest7fc785c2011-06-10 23:54:53 -050066
67 protected XmlFormatWriter(XmlWriter output)
csharptest2b868842011-06-10 14:41:47 -050068 {
69 _output = output;
csharptest60fd7732011-09-09 12:18:16 -050070 _messageOpenCount = 0;
csharptest2b868842011-06-10 14:41:47 -050071 _rootElementName = DefaultRootElementName;
72 }
73
74 /// <summary>
csharptest2b868842011-06-10 14:41:47 -050075 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
76 /// </summary>
77 public string RootElementName
78 {
79 get { return _rootElementName; }
csharptest74c5e0c2011-07-14 13:06:22 -050080 set
81 {
82 ThrowHelper.ThrowIfNull(value, "RootElementName");
83 _rootElementName = value;
84 }
csharptest2b868842011-06-10 14:41:47 -050085 }
86
87 /// <summary>
88 /// Gets or sets the options to use while generating the XML
89 /// </summary>
90 public XmlWriterOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -050091
csharptest7fc785c2011-06-10 23:54:53 -050092 /// <summary>
93 /// Sets the options to use while generating the XML
94 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050095 public XmlFormatWriter SetOptions(XmlWriterOptions options)
96 {
97 Options = options;
98 return this;
99 }
csharptest2b868842011-06-10 14:41:47 -0500100
csharptest74c5e0c2011-07-14 13:06:22 -0500101 private bool TestOption(XmlWriterOptions option)
102 {
103 return (Options & option) != 0;
104 }
csharptest2b868842011-06-10 14:41:47 -0500105
106 /// <summary>
csharptest819b7152011-09-08 20:28:22 -0500107 /// Completes any pending write operations
108 /// </summary>
109 public override void Flush()
110 {
111 _output.Flush();
112 base.Flush();
113 }
114
115 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500116 /// Used to write the root-message preamble, in xml this is open element for RootElementName,
117 /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and
csharptest60fd7732011-09-09 12:18:16 -0500118 /// complete the message with a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500119 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500120 public override void WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500121 {
122 StartMessage(_rootElementName);
123 }
124
125 /// <summary>
126 /// Used to write the root-message preamble, in xml this is open element for elementName.
127 /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
csharptest60fd7732011-09-09 12:18:16 -0500128 /// a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500129 /// </summary>
130 public void StartMessage(string elementName)
131 {
132 if (TestOption(XmlWriterOptions.OutputJsonTypes))
133 {
134 _output.WriteStartElement("root"); // json requires this is the root-element
135 _output.WriteAttributeString("type", "object");
136 }
137 else
138 {
139 _output.WriteStartElement(elementName);
140 }
csharptest60fd7732011-09-09 12:18:16 -0500141 _messageOpenCount++;
csharptestc2d2c1a2011-09-08 20:02:11 -0500142 }
143
144 /// <summary>
csharptest60fd7732011-09-09 12:18:16 -0500145 /// Used to complete a root-message previously started with a call to WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500146 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500147 public override void WriteMessageEnd()
csharptestc2d2c1a2011-09-08 20:02:11 -0500148 {
csharptest60fd7732011-09-09 12:18:16 -0500149 if (_messageOpenCount <= 0)
csharptestc86b5042011-09-09 22:16:08 -0500150 {
csharptest60fd7732011-09-09 12:18:16 -0500151 throw new InvalidOperationException();
csharptestc86b5042011-09-09 22:16:08 -0500152 }
csharptest60fd7732011-09-09 12:18:16 -0500153
csharptestc2d2c1a2011-09-08 20:02:11 -0500154 _output.WriteEndElement();
155 _output.Flush();
csharptest60fd7732011-09-09 12:18:16 -0500156 _messageOpenCount--;
csharptestc2d2c1a2011-09-08 20:02:11 -0500157 }
158
159 /// <summary>
csharptest2b868842011-06-10 14:41:47 -0500160 /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
161 /// </summary>
162 public override void WriteMessage(IMessageLite message)
csharptest74c5e0c2011-07-14 13:06:22 -0500163 {
164 WriteMessage(_rootElementName, message);
165 }
csharptest2b868842011-06-10 14:41:47 -0500166
167 /// <summary>
168 /// Writes a message as an element with the given name
169 /// </summary>
csharptest4dc0dfb2011-06-10 18:01:34 -0500170 public void WriteMessage(string elementName, IMessageLite message)
csharptest2b868842011-06-10 14:41:47 -0500171 {
csharptestc2d2c1a2011-09-08 20:02:11 -0500172 StartMessage(elementName);
csharptest2b868842011-06-10 14:41:47 -0500173 message.WriteTo(this);
csharptest60fd7732011-09-09 12:18:16 -0500174 WriteMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500175 }
176
177 /// <summary>
178 /// Writes a message
179 /// </summary>
180 protected override void WriteMessageOrGroup(string field, IMessageLite message)
181 {
182 _output.WriteStartElement(field);
183
184 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500185 {
csharptest2b868842011-06-10 14:41:47 -0500186 _output.WriteAttributeString("type", "object");
csharptest74c5e0c2011-07-14 13:06:22 -0500187 }
csharptest2b868842011-06-10 14:41:47 -0500188
189 message.WriteTo(this);
190 _output.WriteEndElement();
191 }
192
193 /// <summary>
194 /// Writes a String value
195 /// </summary>
196 protected override void WriteAsText(string field, string textValue, object typedValue)
197 {
198 _output.WriteStartElement(field);
199
200 if (TestOption(XmlWriterOptions.OutputJsonTypes))
201 {
csharptest74c5e0c2011-07-14 13:06:22 -0500202 if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong ||
203 typedValue is double || typedValue is float)
204 {
csharptest2b868842011-06-10 14:41:47 -0500205 _output.WriteAttributeString("type", "number");
csharptest74c5e0c2011-07-14 13:06:22 -0500206 }
csharptest2b868842011-06-10 14:41:47 -0500207 else if (typedValue is bool)
csharptest74c5e0c2011-07-14 13:06:22 -0500208 {
csharptest2b868842011-06-10 14:41:47 -0500209 _output.WriteAttributeString("type", "boolean");
csharptest74c5e0c2011-07-14 13:06:22 -0500210 }
csharptest2b868842011-06-10 14:41:47 -0500211 }
212 _output.WriteString(textValue);
213
214 //Empty strings should not be written as empty elements '<item/>', rather as '<item></item>'
215 if (_output.WriteState == WriteState.Element)
csharptest74c5e0c2011-07-14 13:06:22 -0500216 {
csharptest2b868842011-06-10 14:41:47 -0500217 _output.WriteRaw("");
csharptest74c5e0c2011-07-14 13:06:22 -0500218 }
csharptest2b868842011-06-10 14:41:47 -0500219
220 _output.WriteEndElement();
221 }
222
223 /// <summary>
224 /// Writes an array of field values
225 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500226 protected override void WriteArray(FieldType fieldType, string field, IEnumerable items)
csharptest2b868842011-06-10 14:41:47 -0500227 {
228 //see if it's empty
csharptest74c5e0c2011-07-14 13:06:22 -0500229 IEnumerator eitems = items.GetEnumerator();
230 try
231 {
232 if (!eitems.MoveNext())
233 {
234 return;
235 }
236 }
csharptest2b868842011-06-10 14:41:47 -0500237 finally
csharptest74c5e0c2011-07-14 13:06:22 -0500238 {
239 if (eitems is IDisposable)
240 {
241 ((IDisposable) eitems).Dispose();
242 }
243 }
csharptest2b868842011-06-10 14:41:47 -0500244
245 if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes))
246 {
247 _output.WriteStartElement(field);
248 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500249 {
csharptest2b868842011-06-10 14:41:47 -0500250 _output.WriteAttributeString("type", "array");
csharptest74c5e0c2011-07-14 13:06:22 -0500251 }
csharptest2b868842011-06-10 14:41:47 -0500252
253 base.WriteArray(fieldType, "item", items);
254 _output.WriteEndElement();
255 }
256 else
csharptest74c5e0c2011-07-14 13:06:22 -0500257 {
csharptest2b868842011-06-10 14:41:47 -0500258 base.WriteArray(fieldType, field, items);
csharptest74c5e0c2011-07-14 13:06:22 -0500259 }
csharptest2b868842011-06-10 14:41:47 -0500260 }
261
262 /// <summary>
263 /// Writes a System.Enum by the numeric and textual value
264 /// </summary>
265 protected override void WriteEnum(string field, int number, string name)
266 {
267 _output.WriteStartElement(field);
268
269 if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues))
csharptest74c5e0c2011-07-14 13:06:22 -0500270 {
csharptest2b868842011-06-10 14:41:47 -0500271 _output.WriteAttributeString("value", XmlConvert.ToString(number));
csharptest74c5e0c2011-07-14 13:06:22 -0500272 }
csharptest2b868842011-06-10 14:41:47 -0500273
274 _output.WriteString(name);
275 _output.WriteEndElement();
276 }
277 }
csharptest74c5e0c2011-07-14 13:06:22 -0500278}