blob: cb2cb2ea70a67f16c1ea3a9c9f82d772b3c8064e [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>
139 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
140 /// </summary>
141 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
csharptest74c5e0c2011-07-14 13:06:22 -0500142 {
143 return Merge(_rootElementName, builder, registry);
144 }
csharptest2b868842011-06-10 14:41:47 -0500145
146 /// <summary>
147 /// Merge the provided builder as an element of the current context
148 /// </summary>
149 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
csharptest74c5e0c2011-07-14 13:06:22 -0500150 {
151 return Merge(element, builder, ExtensionRegistry.Empty);
152 }
csharptest2b868842011-06-10 14:41:47 -0500153
154 /// <summary>
155 /// Merge the provided builder as an element of the current context
156 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500157 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
158 where TBuilder : IBuilderLite
csharptest2b868842011-06-10 14:41:47 -0500159 {
160 string field;
161 Assert(PeekNext(out field) && field == element);
162 ReadMessage(builder, registry);
163 return builder;
164 }
165
166 /// <summary>
167 /// Peeks at the next field in the input stream and returns what information is available.
168 /// </summary>
169 /// <remarks>
170 /// This may be called multiple times without actually reading the field. Only after the field
171 /// is either read, or skipped, should PeekNext return a different value.
172 /// </remarks>
173 protected override bool PeekNext(out string field)
174 {
175 NextElement();
csharptest74c5e0c2011-07-14 13:06:22 -0500176 if (_input.IsStartElement())
csharptest2b868842011-06-10 14:41:47 -0500177 {
178 field = _input.LocalName;
179 return true;
180 }
181 field = null;
182 return false;
183 }
184
185 /// <summary>
186 /// Causes the reader to skip past this field
187 /// </summary>
188 protected override void Skip()
189 {
190 if (_input.IsStartElement())
191 {
192 if (!_input.IsEmptyElement)
193 {
194 int depth = _input.Depth;
195 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
csharptest74c5e0c2011-07-14 13:06:22 -0500196 {
csharptest2b868842011-06-10 14:41:47 -0500197 Assert(_input.Read());
csharptest74c5e0c2011-07-14 13:06:22 -0500198 }
csharptest2b868842011-06-10 14:41:47 -0500199 }
200 _input.Read();
201 }
202 }
203
204 /// <summary>
205 /// returns true if it was able to read a single value into the value reference. The value
206 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
207 /// </summary>
208 protected override bool ReadEnum(ref object value)
209 {
210 int number;
211 string temp;
212 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
213 {
214 Skip();
215 value = number;
216 return true;
217 }
218 return base.ReadEnum(ref value);
219 }
220
221 /// <summary>
222 /// Returns true if it was able to read a String from the input
223 /// </summary>
224 protected override bool ReadAsText(ref string value, Type type)
225 {
226 Assert(_input.NodeType == XmlNodeType.Element);
227 value = _input.ReadElementContentAsString();
csharptest74c5e0c2011-07-14 13:06:22 -0500228
csharptest2b868842011-06-10 14:41:47 -0500229 return true;
230 }
231
232 /// <summary>
233 /// Merges the input stream into the provided IBuilderLite
234 /// </summary>
235 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
236 {
237 Assert(_input.IsStartElement());
238
239 if (!_input.IsEmptyElement)
240 {
241 int depth = _input.Depth;
242 XmlReader child = _input.ReadSubtree();
243 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500244 {
csharptest2b868842011-06-10 14:41:47 -0500245 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500246 }
csharptest2b868842011-06-10 14:41:47 -0500247 child.Read();
248 builder.WeakMergeFrom(CloneWith(child), registry);
249 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
250 }
251 _input.Read();
252 return true;
253 }
254
255 private IEnumerable<string> NonNestedArrayItems(string field)
256 {
257 return base.ForeachArrayItem(field);
258 }
259
260 /// <summary>
261 /// Cursors through the array elements and stops at the end of the array
262 /// </summary>
263 protected override IEnumerable<string> ForeachArrayItem(string field)
264 {
265 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
266
267 if (!isNested)
268 {
269 foreach (string item in NonNestedArrayItems(field))
csharptest74c5e0c2011-07-14 13:06:22 -0500270 {
csharptest2b868842011-06-10 14:41:47 -0500271 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500272 }
csharptest2b868842011-06-10 14:41:47 -0500273 yield break;
274 }
275 if (!_input.IsEmptyElement)
276 {
277 int depth = _input.Depth;
278 XmlReader child = _input.ReadSubtree();
279
280 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500281 {
csharptest2b868842011-06-10 14:41:47 -0500282 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500283 }
csharptest2b868842011-06-10 14:41:47 -0500284 child.Read();
285
286 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
csharptest74c5e0c2011-07-14 13:06:22 -0500287 {
csharptest2b868842011-06-10 14:41:47 -0500288 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500289 }
csharptest2b868842011-06-10 14:41:47 -0500290 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
291 }
292 _input.Read();
293 yield break;
294 }
295 }
csharptest74c5e0c2011-07-14 13:06:22 -0500296}