blob: 241c554a4c96ba69ce6df1270a3e254c5f7d5c9b [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 {
csharptest7fc785c2011-06-10 23:54:53 -050075 return new XmlFormatReader(rdr).SetOptions(Options);
csharptest2b868842011-06-10 14:41:47 -050076 }
77 private void NextElement()
78 {
79 while (!_input.IsStartElement() && _input.Read())
80 continue;
81 }
82 private static void Assert(bool cond)
83 {
84 if (!cond) throw new FormatException();
85 }
86
87 /// <summary>
88 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
89 /// </summary>
90 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
91 { return Merge(_rootElementName, builder, registry); }
92
93 /// <summary>
94 /// Merge the provided builder as an element of the current context
95 /// </summary>
96 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
97 { return Merge(element, builder, ExtensionRegistry.Empty); }
98
99 /// <summary>
100 /// Merge the provided builder as an element of the current context
101 /// </summary>
102 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite
103 {
104 string field;
105 Assert(PeekNext(out field) && field == element);
106 ReadMessage(builder, registry);
107 return builder;
108 }
109
110 /// <summary>
111 /// Peeks at the next field in the input stream and returns what information is available.
112 /// </summary>
113 /// <remarks>
114 /// This may be called multiple times without actually reading the field. Only after the field
115 /// is either read, or skipped, should PeekNext return a different value.
116 /// </remarks>
117 protected override bool PeekNext(out string field)
118 {
119 NextElement();
120 if(_input.IsStartElement())
121 {
122 field = _input.LocalName;
123 return true;
124 }
125 field = null;
126 return false;
127 }
128
129 /// <summary>
130 /// Causes the reader to skip past this field
131 /// </summary>
132 protected override void Skip()
133 {
134 if (_input.IsStartElement())
135 {
136 if (!_input.IsEmptyElement)
137 {
138 int depth = _input.Depth;
139 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
140 Assert(_input.Read());
141 }
142 _input.Read();
143 }
144 }
145
146 /// <summary>
147 /// returns true if it was able to read a single value into the value reference. The value
148 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
149 /// </summary>
150 protected override bool ReadEnum(ref object value)
151 {
152 int number;
153 string temp;
154 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
155 {
156 Skip();
157 value = number;
158 return true;
159 }
160 return base.ReadEnum(ref value);
161 }
162
163 /// <summary>
164 /// Returns true if it was able to read a String from the input
165 /// </summary>
166 protected override bool ReadAsText(ref string value, Type type)
167 {
168 Assert(_input.NodeType == XmlNodeType.Element);
169 value = _input.ReadElementContentAsString();
170
171 return true;
172 }
173
174 /// <summary>
175 /// Merges the input stream into the provided IBuilderLite
176 /// </summary>
177 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
178 {
179 Assert(_input.IsStartElement());
180
181 if (!_input.IsEmptyElement)
182 {
183 int depth = _input.Depth;
184 XmlReader child = _input.ReadSubtree();
185 while (!child.IsStartElement() && child.Read())
186 continue;
187 child.Read();
188 builder.WeakMergeFrom(CloneWith(child), registry);
189 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
190 }
191 _input.Read();
192 return true;
193 }
194
195 private IEnumerable<string> NonNestedArrayItems(string field)
196 {
197 return base.ForeachArrayItem(field);
198 }
199
200 /// <summary>
201 /// Cursors through the array elements and stops at the end of the array
202 /// </summary>
203 protected override IEnumerable<string> ForeachArrayItem(string field)
204 {
205 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
206
207 if (!isNested)
208 {
209 foreach (string item in NonNestedArrayItems(field))
210 yield return item;
211 yield break;
212 }
213 if (!_input.IsEmptyElement)
214 {
215 int depth = _input.Depth;
216 XmlReader child = _input.ReadSubtree();
217
218 while (!child.IsStartElement() && child.Read())
219 continue;
220 child.Read();
221
222 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
223 yield return item;
224 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
225 }
226 _input.Read();
227 yield break;
228 }
229 }
230}