csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 1 | using System;
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 2 | using System.Collections.Generic;
|
| 3 | using System.IO;
|
| 4 | using System.Xml;
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 5 |
|
| 6 | namespace 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;
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 17 | private readonly Stack<ElementStackEntry> _elements;
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 18 | private string _rootElementName;
|
| 19 |
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 20 | private struct ElementStackEntry
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 21 | {
|
| 22 | public readonly string LocalName;
|
| 23 | public readonly int Depth;
|
| 24 | public readonly bool IsEmpty;
|
| 25 |
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 26 | public ElementStackEntry(string localName, int depth, bool isEmpty) : this()
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 27 | {
|
| 28 | LocalName = localName;
|
| 29 | IsEmpty = isEmpty;
|
| 30 | Depth = depth;
|
| 31 | }
|
| 32 | }
|
| 33 |
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 34 | private static XmlReaderSettings DefaultSettings
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 35 | {
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 36 | get
|
| 37 | {
|
| 38 | return new XmlReaderSettings()
|
| 39 | {CheckCharacters = false, IgnoreComments = true, IgnoreProcessingInstructions = true};
|
| 40 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 41 | }
|
| 42 |
|
| 43 | /// <summary>
|
| 44 | /// Constructs the XmlFormatReader using the stream provided as the xml
|
| 45 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 46 | public static XmlFormatReader CreateInstance(byte[] input)
|
| 47 | {
|
| 48 | return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings));
|
| 49 | }
|
| 50 |
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame] | 51 | /// <summary>
|
| 52 | /// Constructs the XmlFormatReader using the stream provided as the xml
|
| 53 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 54 | public static XmlFormatReader CreateInstance(Stream input)
|
| 55 | {
|
| 56 | return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
|
| 57 | }
|
| 58 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 59 | /// <summary>
|
| 60 | /// Constructs the XmlFormatReader using the string provided as the xml to be read
|
| 61 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 62 | public static XmlFormatReader CreateInstance(String input)
|
| 63 | {
|
| 64 | return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings));
|
| 65 | }
|
| 66 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 67 | /// <summary>
|
| 68 | /// Constructs the XmlFormatReader using the xml in the TextReader
|
| 69 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 70 | public static XmlFormatReader CreateInstance(TextReader input)
|
| 71 | {
|
| 72 | return new XmlFormatReader(XmlReader.Create(input, DefaultSettings));
|
| 73 | }
|
| 74 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 75 | /// <summary>
|
| 76 | /// Constructs the XmlFormatReader with the XmlReader
|
| 77 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 78 | public static XmlFormatReader CreateInstance(XmlReader input)
|
| 79 | {
|
| 80 | return new XmlFormatReader(input);
|
| 81 | }
|
| 82 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 83 | /// <summary>
|
| 84 | /// Constructs the XmlFormatReader with the XmlReader and options
|
| 85 | /// </summary>
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame] | 86 | protected XmlFormatReader(XmlReader input)
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 87 | {
|
| 88 | _input = input;
|
| 89 | _rootElementName = DefaultRootElementName;
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 90 | _elements = new Stack<ElementStackEntry>();
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame] | 91 | Options = XmlReaderOptions.None;
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 92 | }
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 93 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 94 | /// <summary>
|
| 95 | /// Gets or sets the options to use when reading the xml
|
| 96 | /// </summary>
|
| 97 | public XmlReaderOptions Options { get; set; }
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 98 |
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame] | 99 | /// <summary>
|
| 100 | /// Sets the options to use while generating the XML
|
| 101 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 102 | public XmlFormatReader SetOptions(XmlReaderOptions options)
|
| 103 | {
|
| 104 | Options = options;
|
| 105 | return this;
|
| 106 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 107 |
|
| 108 | /// <summary>
|
| 109 | /// Gets or sets the default element name to use when using the Merge<TBuilder>()
|
| 110 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 111 | public string RootElementName
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 112 | {
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 113 | get { return _rootElementName; }
|
| 114 | set
|
| 115 | {
|
| 116 | ThrowHelper.ThrowIfNull(value, "RootElementName");
|
| 117 | _rootElementName = value;
|
| 118 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 119 | }
|
| 120 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 121 | private static void Assert(bool cond)
|
| 122 | {
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 123 | if (!cond)
|
| 124 | {
|
| 125 | throw new FormatException();
|
| 126 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 127 | }
|
| 128 |
|
| 129 | /// <summary>
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 130 | /// Reads the root-message preamble specific to this formatter
|
| 131 | /// </summary>
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 132 | public override void ReadMessageStart()
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 133 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 134 | ReadMessageStart(_rootElementName);
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 135 | }
|
| 136 |
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 137 | /// <summary>
|
| 138 | /// Reads the root-message preamble specific to this formatter
|
| 139 | /// </summary>
|
| 140 | public void ReadMessageStart(string element)
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 141 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 142 | while (!_input.IsStartElement() && _input.Read())
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 143 | {
|
| 144 | continue;
|
| 145 | }
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 146 | Assert(_input.IsStartElement() && _input.LocalName == element);
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 147 | _elements.Push(new ElementStackEntry(element, _input.Depth, _input.IsEmptyElement));
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 148 | _input.Read();
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 149 | }
|
| 150 |
|
| 151 | /// <summary>
|
| 152 | /// Reads the root-message close specific to this formatter, MUST be called
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 153 | /// on the reader obtained from ReadMessageStart(string element).
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 154 | /// </summary>
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 155 | public override void ReadMessageEnd()
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 156 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 157 | Assert(_elements.Count > 0);
|
| 158 |
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 159 | ElementStackEntry stop = _elements.Peek();
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 160 | while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element
|
| 161 | && _input.Depth > stop.Depth && _input.Read())
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 162 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 163 | continue;
|
| 164 | }
|
| 165 |
|
| 166 | if (!stop.IsEmpty)
|
| 167 | {
|
| 168 | Assert(_input.NodeType == XmlNodeType.EndElement
|
| 169 | && _input.LocalName == stop.LocalName
|
| 170 | && _input.Depth == stop.Depth);
|
| 171 |
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 172 | _input.Read();
|
| 173 | }
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 174 | _elements.Pop();
|
csharptest | c2d2c1a | 2011-09-08 20:02:11 -0500 | [diff] [blame] | 175 | }
|
| 176 |
|
| 177 | /// <summary>
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 178 | /// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context
|
| 179 | /// </summary>
|
| 180 | public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 181 | {
|
| 182 | return Merge(_rootElementName, builder, registry);
|
| 183 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 184 |
|
| 185 | /// <summary>
|
| 186 | /// Merge the provided builder as an element of the current context
|
| 187 | /// </summary>
|
| 188 | public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 189 | {
|
| 190 | return Merge(element, builder, ExtensionRegistry.Empty);
|
| 191 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 192 |
|
| 193 | /// <summary>
|
| 194 | /// Merge the provided builder as an element of the current context
|
| 195 | /// </summary>
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 196 | public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
|
| 197 | where TBuilder : IBuilderLite
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 198 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 199 | ReadMessageStart(element);
|
| 200 | builder.WeakMergeFrom(this, registry);
|
| 201 | ReadMessageEnd();
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 202 | return builder;
|
| 203 | }
|
| 204 |
|
| 205 | /// <summary>
|
| 206 | /// Peeks at the next field in the input stream and returns what information is available.
|
| 207 | /// </summary>
|
| 208 | /// <remarks>
|
| 209 | /// This may be called multiple times without actually reading the field. Only after the field
|
| 210 | /// is either read, or skipped, should PeekNext return a different value.
|
| 211 | /// </remarks>
|
| 212 | protected override bool PeekNext(out string field)
|
| 213 | {
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 214 | ElementStackEntry stopNode;
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 215 | if (_elements.Count == 0)
|
| 216 | {
|
csharptest | 353b0fa | 2011-09-29 16:33:31 -0500 | [diff] [blame^] | 217 | stopNode = new ElementStackEntry(null, _input.Depth - 1, false);
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 218 | }
|
| 219 | else
|
| 220 | {
|
| 221 | stopNode = _elements.Peek();
|
| 222 | }
|
| 223 |
|
| 224 | while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
|
| 225 | {
|
| 226 | continue;
|
| 227 | }
|
| 228 |
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 229 | if (_input.IsStartElement())
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 230 | {
|
| 231 | field = _input.LocalName;
|
| 232 | return true;
|
| 233 | }
|
| 234 | field = null;
|
| 235 | return false;
|
| 236 | }
|
| 237 |
|
| 238 | /// <summary>
|
| 239 | /// Causes the reader to skip past this field
|
| 240 | /// </summary>
|
| 241 | protected override void Skip()
|
| 242 | {
|
| 243 | if (_input.IsStartElement())
|
| 244 | {
|
| 245 | if (!_input.IsEmptyElement)
|
| 246 | {
|
| 247 | int depth = _input.Depth;
|
| 248 | while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement)
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 249 | {
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 250 | Assert(_input.Read());
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 251 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 252 | }
|
| 253 | _input.Read();
|
| 254 | }
|
| 255 | }
|
| 256 |
|
| 257 | /// <summary>
|
| 258 | /// returns true if it was able to read a single value into the value reference. The value
|
| 259 | /// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap.
|
| 260 | /// </summary>
|
| 261 | protected override bool ReadEnum(ref object value)
|
| 262 | {
|
| 263 | int number;
|
| 264 | string temp;
|
| 265 | if (null != (temp = _input.GetAttribute("value")) && int.TryParse(temp, out number))
|
| 266 | {
|
| 267 | Skip();
|
| 268 | value = number;
|
| 269 | return true;
|
| 270 | }
|
| 271 | return base.ReadEnum(ref value);
|
| 272 | }
|
| 273 |
|
| 274 | /// <summary>
|
| 275 | /// Returns true if it was able to read a String from the input
|
| 276 | /// </summary>
|
| 277 | protected override bool ReadAsText(ref string value, Type type)
|
| 278 | {
|
| 279 | Assert(_input.NodeType == XmlNodeType.Element);
|
| 280 | value = _input.ReadElementContentAsString();
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 281 |
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 282 | return true;
|
| 283 | }
|
| 284 |
|
| 285 | /// <summary>
|
| 286 | /// Merges the input stream into the provided IBuilderLite
|
| 287 | /// </summary>
|
| 288 | protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
|
| 289 | {
|
| 290 | Assert(_input.IsStartElement());
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 291 | ReadMessageStart(_input.LocalName);
|
| 292 | builder.WeakMergeFrom(this, registry);
|
| 293 | ReadMessageEnd();
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 294 | return true;
|
| 295 | }
|
| 296 |
|
| 297 | private IEnumerable<string> NonNestedArrayItems(string field)
|
| 298 | {
|
| 299 | return base.ForeachArrayItem(field);
|
| 300 | }
|
| 301 |
|
| 302 | /// <summary>
|
| 303 | /// Cursors through the array elements and stops at the end of the array
|
| 304 | /// </summary>
|
| 305 | protected override IEnumerable<string> ForeachArrayItem(string field)
|
| 306 | {
|
| 307 | bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0;
|
| 308 |
|
| 309 | if (!isNested)
|
| 310 | {
|
| 311 | foreach (string item in NonNestedArrayItems(field))
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 312 | {
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 313 | yield return item;
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 314 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 315 | }
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 316 | else
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 317 | {
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 318 | ReadMessageStart(field);
|
| 319 | foreach (string item in NonNestedArrayItems("item"))
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 320 | {
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 321 | yield return item;
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 322 | }
|
csharptest | 60fd773 | 2011-09-09 12:18:16 -0500 | [diff] [blame] | 323 | ReadMessageEnd();
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 324 | }
|
csharptest | 2b86884 | 2011-06-10 14:41:47 -0500 | [diff] [blame] | 325 | }
|
| 326 | }
|
csharptest | 74c5e0c | 2011-07-14 13:06:22 -0500 | [diff] [blame] | 327 | } |