blob: 79f403dfad9df58dde6f0ad3b92ab01471b3f6a0 [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 {
csharptest60fd7732011-09-09 12:18:16 -050081 while(_messageOpenCount > 0)
82 WriteMessageEnd();
csharptest819b7152011-09-08 20:28:22 -050083
csharptest2b868842011-06-10 14:41:47 -050084 _output.Close();
csharptest74c5e0c2011-07-14 13:06:22 -050085 }
csharptest819b7152011-09-08 20:28:22 -050086
87 base.Dispose(disposing);
csharptest2b868842011-06-10 14:41:47 -050088 }
89
90 /// <summary>
91 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
92 /// </summary>
93 public string RootElementName
94 {
95 get { return _rootElementName; }
csharptest74c5e0c2011-07-14 13:06:22 -050096 set
97 {
98 ThrowHelper.ThrowIfNull(value, "RootElementName");
99 _rootElementName = value;
100 }
csharptest2b868842011-06-10 14:41:47 -0500101 }
102
103 /// <summary>
104 /// Gets or sets the options to use while generating the XML
105 /// </summary>
106 public XmlWriterOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -0500107
csharptest7fc785c2011-06-10 23:54:53 -0500108 /// <summary>
109 /// Sets the options to use while generating the XML
110 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500111 public XmlFormatWriter SetOptions(XmlWriterOptions options)
112 {
113 Options = options;
114 return this;
115 }
csharptest2b868842011-06-10 14:41:47 -0500116
csharptest74c5e0c2011-07-14 13:06:22 -0500117 private bool TestOption(XmlWriterOptions option)
118 {
119 return (Options & option) != 0;
120 }
csharptest2b868842011-06-10 14:41:47 -0500121
122 /// <summary>
csharptest819b7152011-09-08 20:28:22 -0500123 /// Completes any pending write operations
124 /// </summary>
125 public override void Flush()
126 {
127 _output.Flush();
128 base.Flush();
129 }
130
131 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500132 /// Used to write the root-message preamble, in xml this is open element for RootElementName,
133 /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and
csharptest60fd7732011-09-09 12:18:16 -0500134 /// complete the message with a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500135 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500136 public override void WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500137 {
138 StartMessage(_rootElementName);
139 }
140
141 /// <summary>
142 /// Used to write the root-message preamble, in xml this is open element for elementName.
143 /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
csharptest60fd7732011-09-09 12:18:16 -0500144 /// a call to WriteMessageEnd().
csharptestc2d2c1a2011-09-08 20:02:11 -0500145 /// </summary>
146 public void StartMessage(string elementName)
147 {
148 if (TestOption(XmlWriterOptions.OutputJsonTypes))
149 {
150 _output.WriteStartElement("root"); // json requires this is the root-element
151 _output.WriteAttributeString("type", "object");
152 }
153 else
154 {
155 _output.WriteStartElement(elementName);
156 }
csharptest60fd7732011-09-09 12:18:16 -0500157 _messageOpenCount++;
csharptestc2d2c1a2011-09-08 20:02:11 -0500158 }
159
160 /// <summary>
csharptest60fd7732011-09-09 12:18:16 -0500161 /// Used to complete a root-message previously started with a call to WriteMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500162 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500163 public override void WriteMessageEnd()
csharptestc2d2c1a2011-09-08 20:02:11 -0500164 {
csharptest60fd7732011-09-09 12:18:16 -0500165 if (_messageOpenCount <= 0)
166 throw new InvalidOperationException();
167
csharptestc2d2c1a2011-09-08 20:02:11 -0500168 _output.WriteEndElement();
169 _output.Flush();
csharptest60fd7732011-09-09 12:18:16 -0500170 _messageOpenCount--;
csharptestc2d2c1a2011-09-08 20:02:11 -0500171 }
172
173 /// <summary>
csharptest2b868842011-06-10 14:41:47 -0500174 /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
175 /// </summary>
176 public override void WriteMessage(IMessageLite message)
csharptest74c5e0c2011-07-14 13:06:22 -0500177 {
178 WriteMessage(_rootElementName, message);
179 }
csharptest2b868842011-06-10 14:41:47 -0500180
181 /// <summary>
182 /// Writes a message as an element with the given name
183 /// </summary>
csharptest4dc0dfb2011-06-10 18:01:34 -0500184 public void WriteMessage(string elementName, IMessageLite message)
csharptest2b868842011-06-10 14:41:47 -0500185 {
csharptestc2d2c1a2011-09-08 20:02:11 -0500186 StartMessage(elementName);
csharptest2b868842011-06-10 14:41:47 -0500187 message.WriteTo(this);
csharptest60fd7732011-09-09 12:18:16 -0500188 WriteMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500189 }
190
191 /// <summary>
192 /// Writes a message
193 /// </summary>
194 protected override void WriteMessageOrGroup(string field, IMessageLite message)
195 {
196 _output.WriteStartElement(field);
197
198 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500199 {
csharptest2b868842011-06-10 14:41:47 -0500200 _output.WriteAttributeString("type", "object");
csharptest74c5e0c2011-07-14 13:06:22 -0500201 }
csharptest2b868842011-06-10 14:41:47 -0500202
203 message.WriteTo(this);
204 _output.WriteEndElement();
205 }
206
207 /// <summary>
208 /// Writes a String value
209 /// </summary>
210 protected override void WriteAsText(string field, string textValue, object typedValue)
211 {
212 _output.WriteStartElement(field);
213
214 if (TestOption(XmlWriterOptions.OutputJsonTypes))
215 {
csharptest74c5e0c2011-07-14 13:06:22 -0500216 if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong ||
217 typedValue is double || typedValue is float)
218 {
csharptest2b868842011-06-10 14:41:47 -0500219 _output.WriteAttributeString("type", "number");
csharptest74c5e0c2011-07-14 13:06:22 -0500220 }
csharptest2b868842011-06-10 14:41:47 -0500221 else if (typedValue is bool)
csharptest74c5e0c2011-07-14 13:06:22 -0500222 {
csharptest2b868842011-06-10 14:41:47 -0500223 _output.WriteAttributeString("type", "boolean");
csharptest74c5e0c2011-07-14 13:06:22 -0500224 }
csharptest2b868842011-06-10 14:41:47 -0500225 }
226 _output.WriteString(textValue);
227
228 //Empty strings should not be written as empty elements '<item/>', rather as '<item></item>'
229 if (_output.WriteState == WriteState.Element)
csharptest74c5e0c2011-07-14 13:06:22 -0500230 {
csharptest2b868842011-06-10 14:41:47 -0500231 _output.WriteRaw("");
csharptest74c5e0c2011-07-14 13:06:22 -0500232 }
csharptest2b868842011-06-10 14:41:47 -0500233
234 _output.WriteEndElement();
235 }
236
237 /// <summary>
238 /// Writes an array of field values
239 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500240 protected override void WriteArray(FieldType fieldType, string field, IEnumerable items)
csharptest2b868842011-06-10 14:41:47 -0500241 {
242 //see if it's empty
csharptest74c5e0c2011-07-14 13:06:22 -0500243 IEnumerator eitems = items.GetEnumerator();
244 try
245 {
246 if (!eitems.MoveNext())
247 {
248 return;
249 }
250 }
csharptest2b868842011-06-10 14:41:47 -0500251 finally
csharptest74c5e0c2011-07-14 13:06:22 -0500252 {
253 if (eitems is IDisposable)
254 {
255 ((IDisposable) eitems).Dispose();
256 }
257 }
csharptest2b868842011-06-10 14:41:47 -0500258
259 if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes))
260 {
261 _output.WriteStartElement(field);
262 if (TestOption(XmlWriterOptions.OutputJsonTypes))
csharptest74c5e0c2011-07-14 13:06:22 -0500263 {
csharptest2b868842011-06-10 14:41:47 -0500264 _output.WriteAttributeString("type", "array");
csharptest74c5e0c2011-07-14 13:06:22 -0500265 }
csharptest2b868842011-06-10 14:41:47 -0500266
267 base.WriteArray(fieldType, "item", items);
268 _output.WriteEndElement();
269 }
270 else
csharptest74c5e0c2011-07-14 13:06:22 -0500271 {
csharptest2b868842011-06-10 14:41:47 -0500272 base.WriteArray(fieldType, field, items);
csharptest74c5e0c2011-07-14 13:06:22 -0500273 }
csharptest2b868842011-06-10 14:41:47 -0500274 }
275
276 /// <summary>
277 /// Writes a System.Enum by the numeric and textual value
278 /// </summary>
279 protected override void WriteEnum(string field, int number, string name)
280 {
281 _output.WriteStartElement(field);
282
283 if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues))
csharptest74c5e0c2011-07-14 13:06:22 -0500284 {
csharptest2b868842011-06-10 14:41:47 -0500285 _output.WriteAttributeString("value", XmlConvert.ToString(number));
csharptest74c5e0c2011-07-14 13:06:22 -0500286 }
csharptest2b868842011-06-10 14:41:47 -0500287
288 _output.WriteString(name);
289 _output.WriteEndElement();
290 }
291 }
csharptest74c5e0c2011-07-14 13:06:22 -0500292}