using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Xml; | |
using System.Diagnostics; | |
namespace Google.ProtocolBuffers.Serialization | |
{ | |
/// <summary> | |
/// Parses a proto buffer from an XML document or fragment. .NET 3.5 users may also | |
/// use this class to process Json by setting the options to support Json and providing | |
/// an XmlReader obtained from <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>. | |
/// </summary> | |
public class XmlFormatReader : AbstractTextReader | |
{ | |
public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName; | |
private readonly XmlReader _input; | |
// Tracks the message element for each nested message read | |
private readonly Stack<ElementStackEntry> _elements; | |
// The default element name for ReadMessageStart | |
private string _rootElementName; | |
private struct ElementStackEntry | |
{ | |
public readonly string LocalName; | |
public readonly int Depth; | |
public readonly bool IsEmpty; | |
public ElementStackEntry(string localName, int depth, bool isEmpty) : this() | |
{ | |
LocalName = localName; | |
IsEmpty = isEmpty; | |
Depth = depth; | |
} | |
} | |
private static XmlReaderSettings DefaultSettings | |
{ | |
get | |
{ | |
return new XmlReaderSettings() | |
{CheckCharacters = false, IgnoreComments = true, IgnoreProcessingInstructions = true}; | |
} | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader using the stream provided as the xml | |
/// </summary> | |
public static XmlFormatReader CreateInstance(byte[] input) | |
{ | |
return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings)); | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader using the stream provided as the xml | |
/// </summary> | |
public static XmlFormatReader CreateInstance(Stream input) | |
{ | |
return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader using the string provided as the xml to be read | |
/// </summary> | |
public static XmlFormatReader CreateInstance(String input) | |
{ | |
return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings)); | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader using the xml in the TextReader | |
/// </summary> | |
public static XmlFormatReader CreateInstance(TextReader input) | |
{ | |
return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader with the XmlReader | |
/// </summary> | |
public static XmlFormatReader CreateInstance(XmlReader input) | |
{ | |
return new XmlFormatReader(input); | |
} | |
/// <summary> | |
/// Constructs the XmlFormatReader with the XmlReader and options | |
/// </summary> | |
protected XmlFormatReader(XmlReader input) | |
{ | |
_input = input; | |
_rootElementName = DefaultRootElementName; | |
_elements = new Stack<ElementStackEntry>(); | |
Options = XmlReaderOptions.None; | |
} | |
/// <summary> | |
/// Gets or sets the options to use when reading the xml | |
/// </summary> | |
public XmlReaderOptions Options { get; set; } | |
/// <summary> | |
/// Sets the options to use while generating the XML | |
/// </summary> | |
public XmlFormatReader SetOptions(XmlReaderOptions options) | |
{ | |
Options = options; | |
return this; | |
} | |
/// <summary> | |
/// Gets or sets the default element name to use when using the Merge<TBuilder>() | |
/// </summary> | |
public string RootElementName | |
{ | |
get { return _rootElementName; } | |
set | |
{ | |
ThrowHelper.ThrowIfNull(value, "RootElementName"); | |
_rootElementName = value; | |
} | |
} | |
[DebuggerNonUserCode] | |
private static void Assert(bool cond) | |
{ | |
if (!cond) | |
{ | |
throw new FormatException(); | |
} | |
} | |
/// <summary> | |
/// Reads the root-message preamble specific to this formatter | |
/// </summary> | |
public override void ReadMessageStart() | |
{ | |
ReadMessageStart(_rootElementName); | |
} | |
/// <summary> | |
/// Reads the root-message preamble specific to this formatter | |
/// </summary> | |
public void ReadMessageStart(string element) | |
{ | |
while (!_input.IsStartElement() && _input.Read()) | |
{ | |
continue; | |
} | |
Assert(_input.IsStartElement() && _input.LocalName == element); | |
_elements.Push(new ElementStackEntry(element, _input.Depth, _input.IsEmptyElement)); | |
_input.Read(); | |
} | |
/// <summary> | |
/// Reads the root-message close specific to this formatter, MUST be called | |
/// on the reader obtained from ReadMessageStart(string element). | |
/// </summary> | |
public override void ReadMessageEnd() | |
{ | |
Assert(_elements.Count > 0); | |
ElementStackEntry stop = _elements.Peek(); | |
while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element | |
&& _input.Depth > stop.Depth && _input.Read()) | |
{ | |
continue; | |
} | |
if (!stop.IsEmpty) | |
{ | |
Assert(_input.NodeType == XmlNodeType.EndElement | |
&& _input.LocalName == stop.LocalName | |
&& _input.Depth == stop.Depth); | |
_input.Read(); | |
} | |
_elements.Pop(); | |
} | |
/// <summary> | |
/// Merge the provided builder as an element named <see cref="RootElementName"/> in the current context | |
/// </summary> | |
public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry) | |
{ | |
return Merge(_rootElementName, builder, registry); | |
} | |
/// <summary> | |
/// Merge the provided builder as an element of the current context | |
/// </summary> | |
public TBuilder Merge<TBuilder>(string element, TBuilder builder) where TBuilder : IBuilderLite | |
{ | |
return Merge(element, builder, ExtensionRegistry.Empty); | |
} | |
/// <summary> | |
/// Merge the provided builder as an element of the current context | |
/// </summary> | |
public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry) | |
where TBuilder : IBuilderLite | |
{ | |
ReadMessageStart(element); | |
builder.WeakMergeFrom(this, registry); | |
ReadMessageEnd(); | |
return builder; | |
} | |
/// <summary> | |
/// Peeks at the next field in the input stream and returns what information is available. | |
/// </summary> | |
/// <remarks> | |
/// This may be called multiple times without actually reading the field. Only after the field | |
/// is either read, or skipped, should PeekNext return a different value. | |
/// </remarks> | |
protected override bool PeekNext(out string field) | |
{ | |
ElementStackEntry stopNode; | |
if (_elements.Count == 0) | |
{ | |
stopNode = new ElementStackEntry(null, _input.Depth - 1, false); | |
} | |
else | |
{ | |
stopNode = _elements.Peek(); | |
} | |
if (!stopNode.IsEmpty) | |
{ | |
while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read()) | |
{ | |
continue; | |
} | |
if (_input.IsStartElement() && _input.Depth > stopNode.Depth) | |
{ | |
field = _input.LocalName; | |
return true; | |
} | |
} | |
field = null; | |
return false; | |
} | |
/// <summary> | |
/// Causes the reader to skip past this field | |
/// </summary> | |
protected override void Skip() | |
{ | |
if (_input.IsStartElement()) | |
{ | |
if (!_input.IsEmptyElement) | |
{ | |
int depth = _input.Depth; | |
while (_input.Depth >= depth && _input.NodeType != XmlNodeType.EndElement) | |
{ | |
Assert(_input.Read()); | |
} | |
} | |
_input.Read(); | |
} | |
} | |
/// <summary> | |
/// returns true if it was able to read a single value into the value reference. The value | |
/// stored may be of type System.String, System.Int32, or an IEnumLite from the IEnumLiteMap. | |
/// </summary> | |
protected override bool ReadEnum(ref object value) | |
{ | |
int number; | |
string temp; | |
if (null != (temp = _input.GetAttribute("value")) && FrameworkPortability.TryParseInt32(temp, out number)) | |
{ | |
Skip(); | |
value = number; | |
return true; | |
} | |
return base.ReadEnum(ref value); | |
} | |
/// <summary> | |
/// Returns true if it was able to read a String from the input | |
/// </summary> | |
protected override bool ReadAsText(ref string value, Type type) | |
{ | |
Assert(_input.NodeType == XmlNodeType.Element); | |
value = _input.ReadElementContentAsString(); | |
return true; | |
} | |
/// <summary> | |
/// Merges the input stream into the provided IBuilderLite | |
/// </summary> | |
protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) | |
{ | |
Assert(_input.IsStartElement()); | |
ReadMessageStart(_input.LocalName); | |
builder.WeakMergeFrom(this, registry); | |
ReadMessageEnd(); | |
return true; | |
} | |
private IEnumerable<string> NonNestedArrayItems(string field) | |
{ | |
return base.ForeachArrayItem(field); | |
} | |
/// <summary> | |
/// Cursors through the array elements and stops at the end of the array | |
/// </summary> | |
protected override IEnumerable<string> ForeachArrayItem(string field) | |
{ | |
bool isNested = (Options & XmlReaderOptions.ReadNestedArrays) != 0; | |
if (!isNested) | |
{ | |
foreach (string item in NonNestedArrayItems(field)) | |
{ | |
yield return item; | |
} | |
} | |
else | |
{ | |
string found; | |
ReadMessageStart(field); | |
if (PeekNext(out found) && found == "item") | |
{ | |
foreach (string item in NonNestedArrayItems("item")) | |
{ | |
yield return item; | |
} | |
} | |
ReadMessageEnd(); | |
} | |
} | |
} | |
} |