blob: fcd83fb39eaa06eea90ca0e1f086f7d0239321ec [file] [log] [blame]
csharptest2b868842011-06-10 14:41:47 -05001using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Xml;
5using Google.ProtocolBuffers.Descriptors;
6
7namespace Google.ProtocolBuffers.Serialization
8{
9 /// <summary>
10 /// Parses a proto buffer from an XML document or fragment. .NET 3.5 users may also
11 /// use this class to process Json by setting the options to support Json and providing
12 /// an XmlReader obtained from <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>.
13 /// </summary>
14 public class XmlFormatReader : AbstractTextReader
15 {
16 public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
17 private readonly XmlReader _input;
18 private string _rootElementName;
19
20 static XmlReaderSettings DefaultSettings
21 {
22 get { return new XmlReaderSettings() { CheckCharacters=false, IgnoreComments=true, IgnoreProcessingInstructions = true }; }
23 }
24
25 /// <summary>
26 /// Constructs the XmlFormatReader using the stream provided as the xml
27 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050028 public static XmlFormatReader CreateInstance(byte[] input) { return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings)); }
29 /// <summary>
30 /// Constructs the XmlFormatReader using the stream provided as the xml
31 /// </summary>
32 public static XmlFormatReader CreateInstance(Stream input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); }
csharptest2b868842011-06-10 14:41:47 -050033 /// <summary>
34 /// Constructs the XmlFormatReader using the string provided as the xml to be read
35 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050036 public static XmlFormatReader CreateInstance(String input) { return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings)); }
csharptest2b868842011-06-10 14:41:47 -050037 /// <summary>
38 /// Constructs the XmlFormatReader using the xml in the TextReader
39 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050040 public static XmlFormatReader CreateInstance(TextReader input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); }
csharptest2b868842011-06-10 14:41:47 -050041 /// <summary>
42 /// Constructs the XmlFormatReader with the XmlReader
43 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050044 public static XmlFormatReader CreateInstance(XmlReader input) { return new XmlFormatReader(input); }
csharptest2b868842011-06-10 14:41:47 -050045 /// <summary>
46 /// Constructs the XmlFormatReader with the XmlReader and options
47 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050048 protected XmlFormatReader(XmlReader input)
csharptest2b868842011-06-10 14:41:47 -050049 {
50 _input = input;
51 _rootElementName = DefaultRootElementName;
csharptest7fc785c2011-06-10 23:54:53 -050052 Options = XmlReaderOptions.None;
csharptest2b868842011-06-10 14:41:47 -050053 }
54
55 /// <summary>
56 /// Gets or sets the options to use when reading the xml
57 /// </summary>
58 public XmlReaderOptions Options { get; set; }
csharptest7fc785c2011-06-10 23:54:53 -050059 /// <summary>
60 /// Sets the options to use while generating the XML
61 /// </summary>
62 public XmlFormatReader SetOptions(XmlReaderOptions options) { Options = options; return this; }
csharptest2b868842011-06-10 14:41:47 -050063
64 /// <summary>
65 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
66 /// </summary>
67 public string RootElementName
68 {
69 get { return _rootElementName; }
70 set { ThrowHelper.ThrowIfNull(value, "RootElementName"); _rootElementName = value; }
71 }
72
73 private XmlFormatReader CloneWith(XmlReader rdr)
74 {
csharptest3b70dd72011-06-11 12:22:17 -050075 XmlFormatReader copy = new XmlFormatReader(rdr).SetOptions(Options);
76 copy._rootElementName = _rootElementName;
77 copy._depth = _depth;
78 return copy;
79
csharptest2b868842011-06-10 14:41:47 -050080 }
81 private void NextElement()
82 {
83 while (!_input.IsStartElement() && _input.Read())
84 continue;
85 }
86 private static void Assert(bool cond)
87 {
88 if (!cond) throw new FormatException();
89 }
90
91 /// <summary>
92 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
93 /// </summary>
94 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
95 { return Merge(_rootElementName, builder, registry); }
96
97 /// <summary>
98 /// Merge the provided builder as an element of the current context
99 /// </summary>
100 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
101 { return Merge(element, builder, ExtensionRegistry.Empty); }
102
103 /// <summary>
104 /// Merge the provided builder as an element of the current context
105 /// </summary>
106 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite
107 {
108 string field;
109 Assert(PeekNext(out field) && field == element);
110 ReadMessage(builder, registry);
111 return builder;
112 }
113
114 /// <summary>
115 /// Peeks at the next field in the input stream and returns what information is available.
116 /// </summary>
117 /// <remarks>
118 /// This may be called multiple times without actually reading the field. Only after the field
119 /// is either read, or skipped, should PeekNext return a different value.
120 /// </remarks>
121 protected override bool PeekNext(out string field)
122 {
123 NextElement();
124 if(_input.IsStartElement())
125 {
126 field = _input.LocalName;
127 return true;
128 }
129 field = null;
130 return false;
131 }
132
133 /// <summary>
134 /// Causes the reader to skip past this field
135 /// </summary>
136 protected override void Skip()
137 {
138 if (_input.IsStartElement())
139 {
140 if (!_input.IsEmptyElement)
141 {
142 int depth = _input.Depth;
143 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
144 Assert(_input.Read());
145 }
146 _input.Read();
147 }
148 }
149
150 /// <summary>
151 /// returns true if it was able to read a single value into the value reference. The value
152 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
153 /// </summary>
154 protected override bool ReadEnum(ref object value)
155 {
156 int number;
157 string temp;
158 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
159 {
160 Skip();
161 value = number;
162 return true;
163 }
164 return base.ReadEnum(ref value);
165 }
166
167 /// <summary>
168 /// Returns true if it was able to read a String from the input
169 /// </summary>
170 protected override bool ReadAsText(ref string value, Type type)
171 {
172 Assert(_input.NodeType == XmlNodeType.Element);
173 value = _input.ReadElementContentAsString();
174
175 return true;
176 }
177
178 /// <summary>
179 /// Merges the input stream into the provided IBuilderLite
180 /// </summary>
181 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
182 {
183 Assert(_input.IsStartElement());
184
185 if (!_input.IsEmptyElement)
186 {
187 int depth = _input.Depth;
188 XmlReader child = _input.ReadSubtree();
189 while (!child.IsStartElement() && child.Read())
190 continue;
191 child.Read();
192 builder.WeakMergeFrom(CloneWith(child), registry);
193 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
194 }
195 _input.Read();
196 return true;
197 }
198
199 private IEnumerable<string> NonNestedArrayItems(string field)
200 {
201 return base.ForeachArrayItem(field);
202 }
203
204 /// <summary>
205 /// Cursors through the array elements and stops at the end of the array
206 /// </summary>
207 protected override IEnumerable<string> ForeachArrayItem(string field)
208 {
209 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
210
211 if (!isNested)
212 {
213 foreach (string item in NonNestedArrayItems(field))
214 yield return item;
215 yield break;
216 }
217 if (!_input.IsEmptyElement)
218 {
219 int depth = _input.Depth;
220 XmlReader child = _input.ReadSubtree();
221
222 while (!child.IsStartElement() && child.Read())
223 continue;
224 child.Read();
225
226 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
227 yield return item;
228 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
229 }
230 _input.Read();
231 yield break;
232 }
233 }
234}