blob: 98b69776784692c02a16e92f2f536e3100a46155 [file] [log] [blame]
csharptest60fd7732011-09-09 12:18:16 -05001using System;
csharptest2b868842011-06-10 14:41:47 -05002using System.Collections.Generic;
3using System.IO;
4using System.Xml;
csharptest2b868842011-06-10 14:41:47 -05005
6namespace Google.ProtocolBuffers.Serialization
7{
8 /// <summary>
9 /// Parses a proto buffer from an XML document or fragment. .NET 3.5 users may also
10 /// use this class to process Json by setting the options to support Json and providing
11 /// an XmlReader obtained from <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>.
12 /// </summary>
13 public class XmlFormatReader : AbstractTextReader
14 {
15 public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
16 private readonly XmlReader _input;
csharptest60fd7732011-09-09 12:18:16 -050017 private readonly Stack<ElementStack> _elements;
csharptest2b868842011-06-10 14:41:47 -050018 private string _rootElementName;
19
csharptest60fd7732011-09-09 12:18:16 -050020 private struct ElementStack
21 {
22 public readonly string LocalName;
23 public readonly int Depth;
24 public readonly bool IsEmpty;
25
26 public ElementStack(string localName, int depth, bool isEmpty) : this()
27 {
28 LocalName = localName;
29 IsEmpty = isEmpty;
30 Depth = depth;
31 }
32 }
33
csharptest74c5e0c2011-07-14 13:06:22 -050034 private static XmlReaderSettings DefaultSettings
csharptest2b868842011-06-10 14:41:47 -050035 {
csharptest74c5e0c2011-07-14 13:06:22 -050036 get
37 {
38 return new XmlReaderSettings()
39 {CheckCharacters = false, IgnoreComments = true, IgnoreProcessingInstructions = true};
40 }
csharptest2b868842011-06-10 14:41:47 -050041 }
42
43 /// <summary>
44 /// Constructs the XmlFormatReader using the stream provided as the xml
45 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050046 public static XmlFormatReader CreateInstance(byte[] input)
47 {
48 return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings));
49 }
50
csharptest7fc785c2011-06-10 23:54:53 -050051 /// <summary>
52 /// Constructs the XmlFormatReader using the stream provided as the xml
53 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050054 public static XmlFormatReader CreateInstance(Stream input)
55 {
56 return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
57 }
58
csharptest2b868842011-06-10 14:41:47 -050059 /// <summary>
60 /// Constructs the XmlFormatReader using the string provided as the xml to be read
61 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050062 public static XmlFormatReader CreateInstance(String input)
63 {
64 return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings));
65 }
66
csharptest2b868842011-06-10 14:41:47 -050067 /// <summary>
68 /// Constructs the XmlFormatReader using the xml in the TextReader
69 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050070 public static XmlFormatReader CreateInstance(TextReader input)
71 {
72 return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
73 }
74
csharptest2b868842011-06-10 14:41:47 -050075 /// <summary>
76 /// Constructs the XmlFormatReader with the XmlReader
77 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050078 public static XmlFormatReader CreateInstance(XmlReader input)
79 {
80 return new XmlFormatReader(input);
81 }
82
csharptest2b868842011-06-10 14:41:47 -050083 /// <summary>
84 /// Constructs the XmlFormatReader with the XmlReader and options
85 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050086 protected XmlFormatReader(XmlReader input)
csharptest2b868842011-06-10 14:41:47 -050087 {
88 _input = input;
89 _rootElementName = DefaultRootElementName;
csharptest60fd7732011-09-09 12:18:16 -050090 _elements = new Stack<ElementStack>();
csharptest7fc785c2011-06-10 23:54:53 -050091 Options = XmlReaderOptions.None;
csharptest2b868842011-06-10 14:41:47 -050092 }
csharptest74c5e0c2011-07-14 13:06:22 -050093
csharptest2b868842011-06-10 14:41:47 -050094 /// <summary>
95 /// Gets or sets the options to use when reading the xml
96 /// </summary>
97 public XmlReaderOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -050098
csharptest7fc785c2011-06-10 23:54:53 -050099 /// <summary>
100 /// Sets the options to use while generating the XML
101 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500102 public XmlFormatReader SetOptions(XmlReaderOptions options)
103 {
104 Options = options;
105 return this;
106 }
csharptest2b868842011-06-10 14:41:47 -0500107
108 /// <summary>
109 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
110 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500111 public string RootElementName
csharptest2b868842011-06-10 14:41:47 -0500112 {
csharptest74c5e0c2011-07-14 13:06:22 -0500113 get { return _rootElementName; }
114 set
115 {
116 ThrowHelper.ThrowIfNull(value, "RootElementName");
117 _rootElementName = value;
118 }
csharptest2b868842011-06-10 14:41:47 -0500119 }
120
csharptest2b868842011-06-10 14:41:47 -0500121 private static void Assert(bool cond)
122 {
csharptest74c5e0c2011-07-14 13:06:22 -0500123 if (!cond)
124 {
125 throw new FormatException();
126 }
csharptest2b868842011-06-10 14:41:47 -0500127 }
128
129 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500130 /// Reads the root-message preamble specific to this formatter
131 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500132 public override void ReadMessageStart()
csharptestc2d2c1a2011-09-08 20:02:11 -0500133 {
csharptest60fd7732011-09-09 12:18:16 -0500134 ReadMessageStart(_rootElementName);
csharptestc2d2c1a2011-09-08 20:02:11 -0500135 }
136
csharptest60fd7732011-09-09 12:18:16 -0500137 /// <summary>
138 /// Reads the root-message preamble specific to this formatter
139 /// </summary>
140 public void ReadMessageStart(string element)
csharptestc2d2c1a2011-09-08 20:02:11 -0500141 {
csharptest60fd7732011-09-09 12:18:16 -0500142 while (!_input.IsStartElement() && _input.Read())
csharptestc2d2c1a2011-09-08 20:02:11 -0500143 {
144 continue;
145 }
csharptest60fd7732011-09-09 12:18:16 -0500146 Assert(_input.IsStartElement() && _input.LocalName == element);
147 _elements.Push(new ElementStack(element, _input.Depth, _input.IsEmptyElement));
148 _input.Read();
csharptestc2d2c1a2011-09-08 20:02:11 -0500149 }
150
151 /// <summary>
152 /// Reads the root-message close specific to this formatter, MUST be called
csharptest60fd7732011-09-09 12:18:16 -0500153 /// on the reader obtained from ReadMessageStart(string element).
csharptestc2d2c1a2011-09-08 20:02:11 -0500154 /// </summary>
csharptest60fd7732011-09-09 12:18:16 -0500155 public override void ReadMessageEnd()
csharptestc2d2c1a2011-09-08 20:02:11 -0500156 {
csharptest60fd7732011-09-09 12:18:16 -0500157 Assert(_elements.Count > 0);
158
159 ElementStack stop = _elements.Peek();
160 while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element
161 && _input.Depth > stop.Depth && _input.Read())
csharptestc2d2c1a2011-09-08 20:02:11 -0500162 {
csharptest60fd7732011-09-09 12:18:16 -0500163 continue;
164 }
165
166 if (!stop.IsEmpty)
167 {
168 Assert(_input.NodeType == XmlNodeType.EndElement
169 && _input.LocalName == stop.LocalName
170 && _input.Depth == stop.Depth);
171
csharptestc2d2c1a2011-09-08 20:02:11 -0500172 _input.Read();
173 }
csharptest60fd7732011-09-09 12:18:16 -0500174 _elements.Pop();
csharptestc2d2c1a2011-09-08 20:02:11 -0500175 }
176
177 /// <summary>
csharptest2b868842011-06-10 14:41:47 -0500178 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
179 /// </summary>
180 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
csharptest74c5e0c2011-07-14 13:06:22 -0500181 {
182 return Merge(_rootElementName, builder, registry);
183 }
csharptest2b868842011-06-10 14:41:47 -0500184
185 /// <summary>
186 /// Merge the provided builder as an element of the current context
187 /// </summary>
188 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
csharptest74c5e0c2011-07-14 13:06:22 -0500189 {
190 return Merge(element, builder, ExtensionRegistry.Empty);
191 }
csharptest2b868842011-06-10 14:41:47 -0500192
193 /// <summary>
194 /// Merge the provided builder as an element of the current context
195 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500196 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
197 where TBuilder : IBuilderLite
csharptest2b868842011-06-10 14:41:47 -0500198 {
csharptest60fd7732011-09-09 12:18:16 -0500199 ReadMessageStart(element);
200 builder.WeakMergeFrom(this, registry);
201 ReadMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500202 return builder;
203 }
204
205 /// <summary>
206 /// Peeks at the next field in the input stream and returns what information is available.
207 /// </summary>
208 /// <remarks>
209 /// This may be called multiple times without actually reading the field. Only after the field
210 /// is either read, or skipped, should PeekNext return a different value.
211 /// </remarks>
212 protected override bool PeekNext(out string field)
213 {
csharptest60fd7732011-09-09 12:18:16 -0500214 ElementStack stopNode;
215 if (_elements.Count == 0)
216 {
217 stopNode = new ElementStack(null, _input.Depth - 1, false);
218 }
219 else
220 {
221 stopNode = _elements.Peek();
222 }
223
224 while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
225 {
226 continue;
227 }
228
csharptest74c5e0c2011-07-14 13:06:22 -0500229 if (_input.IsStartElement())
csharptest2b868842011-06-10 14:41:47 -0500230 {
231 field = _input.LocalName;
232 return true;
233 }
234 field = null;
235 return false;
236 }
237
238 /// <summary>
239 /// Causes the reader to skip past this field
240 /// </summary>
241 protected override void Skip()
242 {
243 if (_input.IsStartElement())
244 {
245 if (!_input.IsEmptyElement)
246 {
247 int depth = _input.Depth;
248 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
csharptest74c5e0c2011-07-14 13:06:22 -0500249 {
csharptest2b868842011-06-10 14:41:47 -0500250 Assert(_input.Read());
csharptest74c5e0c2011-07-14 13:06:22 -0500251 }
csharptest2b868842011-06-10 14:41:47 -0500252 }
253 _input.Read();
254 }
255 }
256
257 /// <summary>
258 /// returns true if it was able to read a single value into the value reference. The value
259 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
260 /// </summary>
261 protected override bool ReadEnum(ref object value)
262 {
263 int number;
264 string temp;
265 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
266 {
267 Skip();
268 value = number;
269 return true;
270 }
271 return base.ReadEnum(ref value);
272 }
273
274 /// <summary>
275 /// Returns true if it was able to read a String from the input
276 /// </summary>
277 protected override bool ReadAsText(ref string value, Type type)
278 {
279 Assert(_input.NodeType == XmlNodeType.Element);
280 value = _input.ReadElementContentAsString();
csharptest74c5e0c2011-07-14 13:06:22 -0500281
csharptest2b868842011-06-10 14:41:47 -0500282 return true;
283 }
284
285 /// <summary>
286 /// Merges the input stream into the provided IBuilderLite
287 /// </summary>
288 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
289 {
290 Assert(_input.IsStartElement());
csharptest60fd7732011-09-09 12:18:16 -0500291 ReadMessageStart(_input.LocalName);
292 builder.WeakMergeFrom(this, registry);
293 ReadMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500294 return true;
295 }
296
297 private IEnumerable<string> NonNestedArrayItems(string field)
298 {
299 return base.ForeachArrayItem(field);
300 }
301
302 /// <summary>
303 /// Cursors through the array elements and stops at the end of the array
304 /// </summary>
305 protected override IEnumerable<string> ForeachArrayItem(string field)
306 {
307 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
308
309 if (!isNested)
310 {
311 foreach (string item in NonNestedArrayItems(field))
csharptest74c5e0c2011-07-14 13:06:22 -0500312 {
csharptest2b868842011-06-10 14:41:47 -0500313 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500314 }
csharptest2b868842011-06-10 14:41:47 -0500315 }
csharptest60fd7732011-09-09 12:18:16 -0500316 else
csharptest2b868842011-06-10 14:41:47 -0500317 {
csharptest60fd7732011-09-09 12:18:16 -0500318 ReadMessageStart(field);
319 foreach (string item in NonNestedArrayItems("item"))
csharptest74c5e0c2011-07-14 13:06:22 -0500320 {
csharptest2b868842011-06-10 14:41:47 -0500321 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500322 }
csharptest60fd7732011-09-09 12:18:16 -0500323 ReadMessageEnd();
csharptest2b868842011-06-10 14:41:47 -0500324 }
csharptest2b868842011-06-10 14:41:47 -0500325 }
326 }
csharptest74c5e0c2011-07-14 13:06:22 -0500327}