blob: 0d3bca67f621261089d20d924527cdcaf6879a82 [file] [log] [blame]
csharptest2b868842011-06-10 14:41:47 -05001using System;
2using 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;
17 private string _rootElementName;
18
csharptest74c5e0c2011-07-14 13:06:22 -050019 private static XmlReaderSettings DefaultSettings
csharptest2b868842011-06-10 14:41:47 -050020 {
csharptest74c5e0c2011-07-14 13:06:22 -050021 get
22 {
23 return new XmlReaderSettings()
24 {CheckCharacters = false, IgnoreComments = true, IgnoreProcessingInstructions = true};
25 }
csharptest2b868842011-06-10 14:41:47 -050026 }
27
28 /// <summary>
29 /// Constructs the XmlFormatReader using the stream provided as the xml
30 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050031 public static XmlFormatReader CreateInstance(byte[] input)
32 {
33 return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings));
34 }
35
csharptest7fc785c2011-06-10 23:54:53 -050036 /// <summary>
37 /// Constructs the XmlFormatReader using the stream provided as the xml
38 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050039 public static XmlFormatReader CreateInstance(Stream input)
40 {
41 return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
42 }
43
csharptest2b868842011-06-10 14:41:47 -050044 /// <summary>
45 /// Constructs the XmlFormatReader using the string provided as the xml to be read
46 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050047 public static XmlFormatReader CreateInstance(String input)
48 {
49 return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings));
50 }
51
csharptest2b868842011-06-10 14:41:47 -050052 /// <summary>
53 /// Constructs the XmlFormatReader using the xml in the TextReader
54 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050055 public static XmlFormatReader CreateInstance(TextReader input)
56 {
57 return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
58 }
59
csharptest2b868842011-06-10 14:41:47 -050060 /// <summary>
61 /// Constructs the XmlFormatReader with the XmlReader
62 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050063 public static XmlFormatReader CreateInstance(XmlReader input)
64 {
65 return new XmlFormatReader(input);
66 }
67
csharptest2b868842011-06-10 14:41:47 -050068 /// <summary>
69 /// Constructs the XmlFormatReader with the XmlReader and options
70 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050071 protected XmlFormatReader(XmlReader input)
csharptest2b868842011-06-10 14:41:47 -050072 {
73 _input = input;
74 _rootElementName = DefaultRootElementName;
csharptest7fc785c2011-06-10 23:54:53 -050075 Options = XmlReaderOptions.None;
csharptest2b868842011-06-10 14:41:47 -050076 }
csharptest74c5e0c2011-07-14 13:06:22 -050077
csharptest2b868842011-06-10 14:41:47 -050078 /// <summary>
csharptest0f3540e2011-08-05 20:40:14 -050079 /// Constructs the XmlFormatReader with the XmlReader and options
80 /// </summary>
81 protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input)
82 : base(copyFrom)
83 {
84 _input = input;
85 _rootElementName = copyFrom._rootElementName;
86 Options = copyFrom.Options;
87 }
88
89 /// <summary>
csharptest2b868842011-06-10 14:41:47 -050090 /// Gets or sets the options to use when reading the xml
91 /// </summary>
92 public XmlReaderOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -050093
csharptest7fc785c2011-06-10 23:54:53 -050094 /// <summary>
95 /// Sets the options to use while generating the XML
96 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050097 public XmlFormatReader SetOptions(XmlReaderOptions options)
98 {
99 Options = options;
100 return this;
101 }
csharptest2b868842011-06-10 14:41:47 -0500102
103 /// <summary>
104 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
105 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500106 public string RootElementName
csharptest2b868842011-06-10 14:41:47 -0500107 {
csharptest74c5e0c2011-07-14 13:06:22 -0500108 get { return _rootElementName; }
109 set
110 {
111 ThrowHelper.ThrowIfNull(value, "RootElementName");
112 _rootElementName = value;
113 }
csharptest2b868842011-06-10 14:41:47 -0500114 }
115
116 private XmlFormatReader CloneWith(XmlReader rdr)
117 {
csharptest0f3540e2011-08-05 20:40:14 -0500118 XmlFormatReader copy = new XmlFormatReader(this, rdr);
csharptest3b70dd72011-06-11 12:22:17 -0500119 return copy;
csharptest2b868842011-06-10 14:41:47 -0500120 }
csharptest74c5e0c2011-07-14 13:06:22 -0500121
csharptest2b868842011-06-10 14:41:47 -0500122 private void NextElement()
123 {
124 while (!_input.IsStartElement() && _input.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500125 {
csharptest2b868842011-06-10 14:41:47 -0500126 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500127 }
csharptest2b868842011-06-10 14:41:47 -0500128 }
csharptest74c5e0c2011-07-14 13:06:22 -0500129
csharptest2b868842011-06-10 14:41:47 -0500130 private static void Assert(bool cond)
131 {
csharptest74c5e0c2011-07-14 13:06:22 -0500132 if (!cond)
133 {
134 throw new FormatException();
135 }
csharptest2b868842011-06-10 14:41:47 -0500136 }
137
138 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500139 /// Reads the root-message preamble specific to this formatter
140 /// </summary>
141 public override AbstractReader ReadStartMessage()
142 {
143 return ReadStartMessage(_rootElementName);
144 }
145
146 public AbstractReader ReadStartMessage(string element)
147 {
148 string field;
149 Assert(PeekNext(out field) && field == element);
150
151 XmlReader child = _input.ReadSubtree();
152 while (!child.IsStartElement() && child.Read())
153 {
154 continue;
155 }
156 child.Read();
157 return CloneWith(child);
158 }
159
160 /// <summary>
161 /// Reads the root-message close specific to this formatter, MUST be called
162 /// on the reader obtained from ReadStartMessage(string element).
163 /// </summary>
164 public override void ReadEndMessage()
165 {
166 Assert(0 == _input.Depth);
167 if(_input.NodeType == XmlNodeType.EndElement)
168 {
169 _input.Read();
170 }
171 }
172
173 /// <summary>
csharptest2b868842011-06-10 14:41:47 -0500174 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
175 /// </summary>
176 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
csharptest74c5e0c2011-07-14 13:06:22 -0500177 {
178 return Merge(_rootElementName, builder, registry);
179 }
csharptest2b868842011-06-10 14:41:47 -0500180
181 /// <summary>
182 /// Merge the provided builder as an element of the current context
183 /// </summary>
184 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
csharptest74c5e0c2011-07-14 13:06:22 -0500185 {
186 return Merge(element, builder, ExtensionRegistry.Empty);
187 }
csharptest2b868842011-06-10 14:41:47 -0500188
189 /// <summary>
190 /// Merge the provided builder as an element of the current context
191 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500192 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
193 where TBuilder : IBuilderLite
csharptest2b868842011-06-10 14:41:47 -0500194 {
195 string field;
196 Assert(PeekNext(out field) && field == element);
197 ReadMessage(builder, registry);
198 return builder;
199 }
200
201 /// <summary>
202 /// Peeks at the next field in the input stream and returns what information is available.
203 /// </summary>
204 /// <remarks>
205 /// This may be called multiple times without actually reading the field. Only after the field
206 /// is either read, or skipped, should PeekNext return a different value.
207 /// </remarks>
208 protected override bool PeekNext(out string field)
209 {
210 NextElement();
csharptest74c5e0c2011-07-14 13:06:22 -0500211 if (_input.IsStartElement())
csharptest2b868842011-06-10 14:41:47 -0500212 {
213 field = _input.LocalName;
214 return true;
215 }
216 field = null;
217 return false;
218 }
219
220 /// <summary>
221 /// Causes the reader to skip past this field
222 /// </summary>
223 protected override void Skip()
224 {
225 if (_input.IsStartElement())
226 {
227 if (!_input.IsEmptyElement)
228 {
229 int depth = _input.Depth;
230 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
csharptest74c5e0c2011-07-14 13:06:22 -0500231 {
csharptest2b868842011-06-10 14:41:47 -0500232 Assert(_input.Read());
csharptest74c5e0c2011-07-14 13:06:22 -0500233 }
csharptest2b868842011-06-10 14:41:47 -0500234 }
235 _input.Read();
236 }
237 }
238
239 /// <summary>
240 /// returns true if it was able to read a single value into the value reference. The value
241 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
242 /// </summary>
243 protected override bool ReadEnum(ref object value)
244 {
245 int number;
246 string temp;
247 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
248 {
249 Skip();
250 value = number;
251 return true;
252 }
253 return base.ReadEnum(ref value);
254 }
255
256 /// <summary>
257 /// Returns true if it was able to read a String from the input
258 /// </summary>
259 protected override bool ReadAsText(ref string value, Type type)
260 {
261 Assert(_input.NodeType == XmlNodeType.Element);
262 value = _input.ReadElementContentAsString();
csharptest74c5e0c2011-07-14 13:06:22 -0500263
csharptest2b868842011-06-10 14:41:47 -0500264 return true;
265 }
266
267 /// <summary>
268 /// Merges the input stream into the provided IBuilderLite
269 /// </summary>
270 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
271 {
272 Assert(_input.IsStartElement());
273
274 if (!_input.IsEmptyElement)
275 {
276 int depth = _input.Depth;
277 XmlReader child = _input.ReadSubtree();
278 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500279 {
csharptest2b868842011-06-10 14:41:47 -0500280 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500281 }
csharptest2b868842011-06-10 14:41:47 -0500282 child.Read();
283 builder.WeakMergeFrom(CloneWith(child), registry);
284 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
285 }
286 _input.Read();
287 return true;
288 }
289
290 private IEnumerable<string> NonNestedArrayItems(string field)
291 {
292 return base.ForeachArrayItem(field);
293 }
294
295 /// <summary>
296 /// Cursors through the array elements and stops at the end of the array
297 /// </summary>
298 protected override IEnumerable<string> ForeachArrayItem(string field)
299 {
300 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
301
302 if (!isNested)
303 {
304 foreach (string item in NonNestedArrayItems(field))
csharptest74c5e0c2011-07-14 13:06:22 -0500305 {
csharptest2b868842011-06-10 14:41:47 -0500306 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500307 }
csharptest2b868842011-06-10 14:41:47 -0500308 yield break;
309 }
310 if (!_input.IsEmptyElement)
311 {
312 int depth = _input.Depth;
313 XmlReader child = _input.ReadSubtree();
314
315 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500316 {
csharptest2b868842011-06-10 14:41:47 -0500317 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500318 }
csharptest2b868842011-06-10 14:41:47 -0500319 child.Read();
320
321 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
csharptest74c5e0c2011-07-14 13:06:22 -0500322 {
csharptest2b868842011-06-10 14:41:47 -0500323 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500324 }
csharptest2b868842011-06-10 14:41:47 -0500325 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
326 }
327 _input.Read();
328 yield break;
329 }
330 }
csharptest74c5e0c2011-07-14 13:06:22 -0500331}