blob: 3c5cb3ab16499209fd9d65a1593b258dcf3fc1c9 [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>
75 /// Closes the underlying XmlTextWriter
76 /// </summary>
77 protected override void Dispose(bool disposing)
78 {
csharptest74c5e0c2011-07-14 13:06:22 -050079 if (disposing)
80 {
csharptestc86b5042011-09-09 22:16:08 -050081 while (_messageOpenCount > 0)
82 {
csharptest60fd7732011-09-09 12:18:16 -050083 WriteMessageEnd();
csharptestc86b5042011-09-09 22:16:08 -050084 }
csharptest819b7152011-09-08 20:28:22 -050085
csharptest2b868842011-06-10 14:41:47 -050086 _output.Close();
csharptest74c5e0c2011-07-14 13:06:22 -050087 }
csharptest819b7152011-09-08 20:28:22 -050088
89 base.Dispose(disposing);
csharptest2b868842011-06-10 14:41:47 -050090 }
91
92 /// <summary>
93 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
94 /// </summary>
95 public string RootElementName
96 {
97 get { return _rootElementName; }
csharptest74c5e0c2011-07-14 13:06:22 -050098 set
99 {
100 ThrowHelper.ThrowIfNull(value, "RootElementName");
101 _rootElementName = value;
102 }
csharptest2b868842011-06-10 14:41:47 -0500103 }
104
105 /// <summary>
106 /// Gets or sets the options to use while generating the XML
107 /// </summary>
108 public XmlWriterOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -0500109
csharptest7fc785c2011-06-10 23:54:53 -0500110 /// <summary>
111 /// Sets the options to use while generating the XML
112 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500113 public XmlFormatWriter SetOptions(XmlWriterOptions options)
114 {
115 Options = options;
116 return this;
117 }
csharptest2b868842011-06-10 14:41:47 -0500118
csharptest74c5e0c2011-07-14 13:06:22 -0500119 private bool TestOption(XmlWriterOptions option)
120 {
121 return (Options & option) != 0;
122 }
csharptest2b868842011-06-10 14:41:47 -0500123
124 /// <summary>
csharptest819b7152011-09-08 20:28:22 -0500125 /// Completes any pending write operations
126 /// </summary>
127 public override void Flush()
128 {
129 _output.Flush();
130 base.Flush();
131 }
132
133 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500134 /// Used to write the root-message preamble, in xml this is open element for RootElementName,
135 /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and
csharptest60fd7732011-09-09 12:18:16 -0500136 /// complete the message with a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500137 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500138 public override void WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500139 {
140 StartMessage(_rootElementName);
141 }
142
143 /// <summary>
144 /// Used to write the root-message preamble, in xml this is open element for elementName.
145 /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
csharptest60fd7732011-09-09 12:18:16 -0500146 /// a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500147 /// </summary>
148 public void StartMessage(string elementName)
149 {
150 if (TestOption(XmlWriterOptions.OutputJsonTypes))
151 {
152 _output.WriteStartElement("root"); // json requires this is the root-element
153 _output.WriteAttributeString("type", "object");
154 }
155 else
156 {
157 _output.WriteStartElement(elementName);
158 }
csharptest60fd7732011-09-09 12:18:16 -0500159 _messageOpenCount++;
csharptestc2d2c1a2011-09-08 20:02:11 -0500160 }
161
162 /// <summary>
csharptest60fd7732011-09-09 12:18:16 -0500163 /// Used to complete a root-message previously started with a call to WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500164 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500165 public override void WriteMessageEnd()
csharptestc2d2c1a2011-09-08 20:02:11 -0500166 {
csharptest60fd7732011-09-09 12:18:16 -0500167 if (_messageOpenCount <= 0)
csharptestc86b5042011-09-09 22:16:08 -0500168 {
csharptest60fd7732011-09-09 12:18:16 -0500169 throw new InvalidOperationException();
csharptestc86b5042011-09-09 22:16:08 -0500170 }
csharptest60fd7732011-09-09 12:18:16 -0500171
csharptestc2d2c1a2011-09-08 20:02:11 -0500172 _output.WriteEndElement();
173 _output.Flush();
csharptest60fd7732011-09-09 12:18:16 -0500174 _messageOpenCount--;
csharptestc2d2c1a2011-09-08 20:02:11 -0500175 }
176
177 /// <summary>
csharptest2b868842011-06-10 14:41:47 -0500178 /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
179 /// </summary>
180 public override void WriteMessage(IMessageLite message)
csharptest74c5e0c2011-07-14 13:06:22 -0500181 {
182 WriteMessage(_rootElementName, message);
183 }
csharptest2b868842011-06-10 14:41:47 -0500184
185 /// <summary>
186 /// Writes a message as an element with the given name
187 /// </summary>
csharptest4dc0dfb2011-06-10 18:01:34 -0500188 public void WriteMessage(string elementName, IMessageLite message)
csharptest2b868842011-06-10 14:41:47 -0500189 {
csharptestc2d2c1a2011-09-08 20:02:11 -0500190 StartMessage(elementName);
csharptest2b868842011-06-10 14:41:47 -0500191 message.WriteTo(this);
csharptest60fd7732011-09-09 12:18:16 -0500192 WriteMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500193 }
194
195 /// <summary>
196 /// Writes a message
197 /// </summary>
198 protected override void WriteMessageOrGroup(string field, IMessageLite message)
199 {
200 _output.WriteStartElement(field);
201
202 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500203 {
csharptest2b868842011-06-10 14:41:47 -0500204 _output.WriteAttributeString("type", "object");
csharptest74c5e0c2011-07-14 13:06:22 -0500205 }
csharptest2b868842011-06-10 14:41:47 -0500206
207 message.WriteTo(this);
208 _output.WriteEndElement();
209 }
210
211 /// <summary>
212 /// Writes a String value
213 /// </summary>
214 protected override void WriteAsText(string field, string textValue, object typedValue)
215 {
216 _output.WriteStartElement(field);
217
218 if (TestOption(XmlWriterOptions.OutputJsonTypes))
219 {
csharptest74c5e0c2011-07-14 13:06:22 -0500220 if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong ||
221 typedValue is double || typedValue is float)
222 {
csharptest2b868842011-06-10 14:41:47 -0500223 _output.WriteAttributeString("type", "number");
csharptest74c5e0c2011-07-14 13:06:22 -0500224 }
csharptest2b868842011-06-10 14:41:47 -0500225 else if (typedValue is bool)
csharptest74c5e0c2011-07-14 13:06:22 -0500226 {
csharptest2b868842011-06-10 14:41:47 -0500227 _output.WriteAttributeString("type", "boolean");
csharptest74c5e0c2011-07-14 13:06:22 -0500228 }
csharptest2b868842011-06-10 14:41:47 -0500229 }
230 _output.WriteString(textValue);
231
232 //Empty strings should not be written as empty elements '<item/>', rather as '<item></item>'
233 if (_output.WriteState == WriteState.Element)
csharptest74c5e0c2011-07-14 13:06:22 -0500234 {
csharptest2b868842011-06-10 14:41:47 -0500235 _output.WriteRaw("");
csharptest74c5e0c2011-07-14 13:06:22 -0500236 }
csharptest2b868842011-06-10 14:41:47 -0500237
238 _output.WriteEndElement();
239 }
240
241 /// <summary>
242 /// Writes an array of field values
243 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500244 protected override void WriteArray(FieldType fieldType, string field, IEnumerable items)
csharptest2b868842011-06-10 14:41:47 -0500245 {
246 //see if it's empty
csharptest74c5e0c2011-07-14 13:06:22 -0500247 IEnumerator eitems = items.GetEnumerator();
248 try
249 {
250 if (!eitems.MoveNext())
251 {
252 return;
253 }
254 }
csharptest2b868842011-06-10 14:41:47 -0500255 finally
csharptest74c5e0c2011-07-14 13:06:22 -0500256 {
257 if (eitems is IDisposable)
258 {
259 ((IDisposable) eitems).Dispose();
260 }
261 }
csharptest2b868842011-06-10 14:41:47 -0500262
263 if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes))
264 {
265 _output.WriteStartElement(field);
266 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500267 {
csharptest2b868842011-06-10 14:41:47 -0500268 _output.WriteAttributeString("type", "array");
csharptest74c5e0c2011-07-14 13:06:22 -0500269 }
csharptest2b868842011-06-10 14:41:47 -0500270
271 base.WriteArray(fieldType, "item", items);
272 _output.WriteEndElement();
273 }
274 else
csharptest74c5e0c2011-07-14 13:06:22 -0500275 {
csharptest2b868842011-06-10 14:41:47 -0500276 base.WriteArray(fieldType, field, items);
csharptest74c5e0c2011-07-14 13:06:22 -0500277 }
csharptest2b868842011-06-10 14:41:47 -0500278 }
279
280 /// <summary>
281 /// Writes a System.Enum by the numeric and textual value
282 /// </summary>
283 protected override void WriteEnum(string field, int number, string name)
284 {
285 _output.WriteStartElement(field);
286
287 if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues))
csharptest74c5e0c2011-07-14 13:06:22 -0500288 {
csharptest2b868842011-06-10 14:41:47 -0500289 _output.WriteAttributeString("value", XmlConvert.ToString(number));
csharptest74c5e0c2011-07-14 13:06:22 -0500290 }
csharptest2b868842011-06-10 14:41:47 -0500291
292 _output.WriteString(name);
293 _output.WriteEndElement();
294 }
295 }
csharptest74c5e0c2011-07-14 13:06:22 -0500296}