blob: 112910a7b584c66e49068f72b46303bbccb4edfa [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>
79 /// Gets or sets the options to use when reading the xml
80 /// </summary>
81 public XmlReaderOptions Options { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -050082
csharptest7fc785c2011-06-10 23:54:53 -050083 /// <summary>
84 /// Sets the options to use while generating the XML
85 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050086 public XmlFormatReader SetOptions(XmlReaderOptions options)
87 {
88 Options = options;
89 return this;
90 }
csharptest2b868842011-06-10 14:41:47 -050091
92 /// <summary>
93 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
94 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050095 public string RootElementName
csharptest2b868842011-06-10 14:41:47 -050096 {
csharptest74c5e0c2011-07-14 13:06:22 -050097 get { return _rootElementName; }
98 set
99 {
100 ThrowHelper.ThrowIfNull(value, "RootElementName");
101 _rootElementName = value;
102 }
csharptest2b868842011-06-10 14:41:47 -0500103 }
104
105 private XmlFormatReader CloneWith(XmlReader rdr)
106 {
csharptest3b70dd72011-06-11 12:22:17 -0500107 XmlFormatReader copy = new XmlFormatReader(rdr).SetOptions(Options);
108 copy._rootElementName = _rootElementName;
csharptest6c693732011-06-11 12:30:15 -0500109 copy.Depth = Depth;
csharptest3b70dd72011-06-11 12:22:17 -0500110 return copy;
csharptest2b868842011-06-10 14:41:47 -0500111 }
csharptest74c5e0c2011-07-14 13:06:22 -0500112
csharptest2b868842011-06-10 14:41:47 -0500113 private void NextElement()
114 {
115 while (!_input.IsStartElement() && _input.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500116 {
csharptest2b868842011-06-10 14:41:47 -0500117 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500118 }
csharptest2b868842011-06-10 14:41:47 -0500119 }
csharptest74c5e0c2011-07-14 13:06:22 -0500120
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>
130 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
131 /// </summary>
132 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
csharptest74c5e0c2011-07-14 13:06:22 -0500133 {
134 return Merge(_rootElementName, builder, registry);
135 }
csharptest2b868842011-06-10 14:41:47 -0500136
137 /// <summary>
138 /// Merge the provided builder as an element of the current context
139 /// </summary>
140 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
csharptest74c5e0c2011-07-14 13:06:22 -0500141 {
142 return Merge(element, builder, ExtensionRegistry.Empty);
143 }
csharptest2b868842011-06-10 14:41:47 -0500144
145 /// <summary>
146 /// Merge the provided builder as an element of the current context
147 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500148 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
149 where TBuilder : IBuilderLite
csharptest2b868842011-06-10 14:41:47 -0500150 {
151 string field;
152 Assert(PeekNext(out field) && field == element);
153 ReadMessage(builder, registry);
154 return builder;
155 }
156
157 /// <summary>
158 /// Peeks at the next field in the input stream and returns what information is available.
159 /// </summary>
160 /// <remarks>
161 /// This may be called multiple times without actually reading the field. Only after the field
162 /// is either read, or skipped, should PeekNext return a different value.
163 /// </remarks>
164 protected override bool PeekNext(out string field)
165 {
166 NextElement();
csharptest74c5e0c2011-07-14 13:06:22 -0500167 if (_input.IsStartElement())
csharptest2b868842011-06-10 14:41:47 -0500168 {
169 field = _input.LocalName;
170 return true;
171 }
172 field = null;
173 return false;
174 }
175
176 /// <summary>
177 /// Causes the reader to skip past this field
178 /// </summary>
179 protected override void Skip()
180 {
181 if (_input.IsStartElement())
182 {
183 if (!_input.IsEmptyElement)
184 {
185 int depth = _input.Depth;
186 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
csharptest74c5e0c2011-07-14 13:06:22 -0500187 {
csharptest2b868842011-06-10 14:41:47 -0500188 Assert(_input.Read());
csharptest74c5e0c2011-07-14 13:06:22 -0500189 }
csharptest2b868842011-06-10 14:41:47 -0500190 }
191 _input.Read();
192 }
193 }
194
195 /// <summary>
196 /// returns true if it was able to read a single value into the value reference. The value
197 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
198 /// </summary>
199 protected override bool ReadEnum(ref object value)
200 {
201 int number;
202 string temp;
203 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
204 {
205 Skip();
206 value = number;
207 return true;
208 }
209 return base.ReadEnum(ref value);
210 }
211
212 /// <summary>
213 /// Returns true if it was able to read a String from the input
214 /// </summary>
215 protected override bool ReadAsText(ref string value, Type type)
216 {
217 Assert(_input.NodeType == XmlNodeType.Element);
218 value = _input.ReadElementContentAsString();
csharptest74c5e0c2011-07-14 13:06:22 -0500219
csharptest2b868842011-06-10 14:41:47 -0500220 return true;
221 }
222
223 /// <summary>
224 /// Merges the input stream into the provided IBuilderLite
225 /// </summary>
226 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
227 {
228 Assert(_input.IsStartElement());
229
230 if (!_input.IsEmptyElement)
231 {
232 int depth = _input.Depth;
233 XmlReader child = _input.ReadSubtree();
234 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500235 {
csharptest2b868842011-06-10 14:41:47 -0500236 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500237 }
csharptest2b868842011-06-10 14:41:47 -0500238 child.Read();
239 builder.WeakMergeFrom(CloneWith(child), registry);
240 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
241 }
242 _input.Read();
243 return true;
244 }
245
246 private IEnumerable<string> NonNestedArrayItems(string field)
247 {
248 return base.ForeachArrayItem(field);
249 }
250
251 /// <summary>
252 /// Cursors through the array elements and stops at the end of the array
253 /// </summary>
254 protected override IEnumerable<string> ForeachArrayItem(string field)
255 {
256 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
257
258 if (!isNested)
259 {
260 foreach (string item in NonNestedArrayItems(field))
csharptest74c5e0c2011-07-14 13:06:22 -0500261 {
csharptest2b868842011-06-10 14:41:47 -0500262 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500263 }
csharptest2b868842011-06-10 14:41:47 -0500264 yield break;
265 }
266 if (!_input.IsEmptyElement)
267 {
268 int depth = _input.Depth;
269 XmlReader child = _input.ReadSubtree();
270
271 while (!child.IsStartElement() && child.Read())
csharptest74c5e0c2011-07-14 13:06:22 -0500272 {
csharptest2b868842011-06-10 14:41:47 -0500273 continue;
csharptest74c5e0c2011-07-14 13:06:22 -0500274 }
csharptest2b868842011-06-10 14:41:47 -0500275 child.Read();
276
277 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
csharptest74c5e0c2011-07-14 13:06:22 -0500278 {
csharptest2b868842011-06-10 14:41:47 -0500279 yield return item;
csharptest74c5e0c2011-07-14 13:06:22 -0500280 }
csharptest2b868842011-06-10 14:41:47 -0500281 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
282 }
283 _input.Read();
284 yield break;
285 }
286 }
csharptest74c5e0c2011-07-14 13:06:22 -0500287}