blob: 671490e63caa17f5b6bd7aa91567180408607a67 [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>
28 public XmlFormatReader(Stream input) : this(XmlReader.Create(input, DefaultSettings)) { }
29 /// <summary>
30 /// Constructs the XmlFormatReader using the string provided as the xml to be read
31 /// </summary>
32 public XmlFormatReader(String input) : this(XmlReader.Create(new StringReader(input))) { }
33 /// <summary>
34 /// Constructs the XmlFormatReader using the xml in the TextReader
35 /// </summary>
36 public XmlFormatReader(TextReader input) : this(XmlReader.Create(input)) { }
37 /// <summary>
38 /// Constructs the XmlFormatReader with the XmlReader
39 /// </summary>
40 public XmlFormatReader(XmlReader input) : this(input, XmlReaderOptions.None) { }
41 /// <summary>
42 /// Constructs the XmlFormatReader with the XmlReader and options
43 /// </summary>
44 public XmlFormatReader(XmlReader input, XmlReaderOptions options)
45 {
46 _input = input;
47 _rootElementName = DefaultRootElementName;
48 Options = options;
49 }
50
51 /// <summary>
52 /// Gets or sets the options to use when reading the xml
53 /// </summary>
54 public XmlReaderOptions Options { get; set; }
55
56 /// <summary>
57 /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
58 /// </summary>
59 public string RootElementName
60 {
61 get { return _rootElementName; }
62 set { ThrowHelper.ThrowIfNull(value, "RootElementName"); _rootElementName = value; }
63 }
64
65 private XmlFormatReader CloneWith(XmlReader rdr)
66 {
67 return new XmlFormatReader(rdr, Options);
68 }
69 private void NextElement()
70 {
71 while (!_input.IsStartElement() && _input.Read())
72 continue;
73 }
74 private static void Assert(bool cond)
75 {
76 if (!cond) throw new FormatException();
77 }
78
79 /// <summary>
80 /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
81 /// </summary>
82 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
83 { return Merge(_rootElementName, builder, registry); }
84
85 /// <summary>
86 /// Merge the provided builder as an element of the current context
87 /// </summary>
88 public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
89 { return Merge(element, builder, ExtensionRegistry.Empty); }
90
91 /// <summary>
92 /// Merge the provided builder as an element of the current context
93 /// </summary>
94 public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite
95 {
96 string field;
97 Assert(PeekNext(out field) && field == element);
98 ReadMessage(builder, registry);
99 return builder;
100 }
101
102 /// <summary>
103 /// Peeks at the next field in the input stream and returns what information is available.
104 /// </summary>
105 /// <remarks>
106 /// This may be called multiple times without actually reading the field. Only after the field
107 /// is either read, or skipped, should PeekNext return a different value.
108 /// </remarks>
109 protected override bool PeekNext(out string field)
110 {
111 NextElement();
112 if(_input.IsStartElement())
113 {
114 field = _input.LocalName;
115 return true;
116 }
117 field = null;
118 return false;
119 }
120
121 /// <summary>
122 /// Causes the reader to skip past this field
123 /// </summary>
124 protected override void Skip()
125 {
126 if (_input.IsStartElement())
127 {
128 if (!_input.IsEmptyElement)
129 {
130 int depth = _input.Depth;
131 while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
132 Assert(_input.Read());
133 }
134 _input.Read();
135 }
136 }
137
138 /// <summary>
139 /// returns true if it was able to read a single value into the value reference. The value
140 /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
141 /// </summary>
142 protected override bool ReadEnum(ref object value)
143 {
144 int number;
145 string temp;
146 if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
147 {
148 Skip();
149 value = number;
150 return true;
151 }
152 return base.ReadEnum(ref value);
153 }
154
155 /// <summary>
156 /// Returns true if it was able to read a String from the input
157 /// </summary>
158 protected override bool ReadAsText(ref string value, Type type)
159 {
160 Assert(_input.NodeType == XmlNodeType.Element);
161 value = _input.ReadElementContentAsString();
162
163 return true;
164 }
165
166 /// <summary>
167 /// Merges the input stream into the provided IBuilderLite
168 /// </summary>
169 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
170 {
171 Assert(_input.IsStartElement());
172
173 if (!_input.IsEmptyElement)
174 {
175 int depth = _input.Depth;
176 XmlReader child = _input.ReadSubtree();
177 while (!child.IsStartElement() && child.Read())
178 continue;
179 child.Read();
180 builder.WeakMergeFrom(CloneWith(child), registry);
181 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
182 }
183 _input.Read();
184 return true;
185 }
186
187 private IEnumerable<string> NonNestedArrayItems(string field)
188 {
189 return base.ForeachArrayItem(field);
190 }
191
192 /// <summary>
193 /// Cursors through the array elements and stops at the end of the array
194 /// </summary>
195 protected override IEnumerable<string> ForeachArrayItem(string field)
196 {
197 bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
198
199 if (!isNested)
200 {
201 foreach (string item in NonNestedArrayItems(field))
202 yield return item;
203 yield break;
204 }
205 if (!_input.IsEmptyElement)
206 {
207 int depth = _input.Depth;
208 XmlReader child = _input.ReadSubtree();
209
210 while (!child.IsStartElement() && child.Read())
211 continue;
212 child.Read();
213
214 foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
215 yield return item;
216 Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
217 }
218 _input.Read();
219 yield break;
220 }
221 }
222}