using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Diagnostics; namespace Google.ProtocolBuffers.Serialization { /// /// 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 . /// 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 _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}; } } /// /// Constructs the XmlFormatReader using the stream provided as the xml /// public static XmlFormatReader CreateInstance(byte[] input) { return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings)); } /// /// Constructs the XmlFormatReader using the stream provided as the xml /// public static XmlFormatReader CreateInstance(Stream input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); } /// /// Constructs the XmlFormatReader using the string provided as the xml to be read /// public static XmlFormatReader CreateInstance(String input) { return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings)); } /// /// Constructs the XmlFormatReader using the xml in the TextReader /// public static XmlFormatReader CreateInstance(TextReader input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); } /// /// Constructs the XmlFormatReader with the XmlReader /// public static XmlFormatReader CreateInstance(XmlReader input) { return new XmlFormatReader(input); } /// /// Constructs the XmlFormatReader with the XmlReader and options /// protected XmlFormatReader(XmlReader input) { _input = input; _rootElementName = DefaultRootElementName; _elements = new Stack(); Options = XmlReaderOptions.None; } /// /// Gets or sets the options to use when reading the xml /// public XmlReaderOptions Options { get; set; } /// /// Sets the options to use while generating the XML /// public XmlFormatReader SetOptions(XmlReaderOptions options) { Options = options; return this; } /// /// Gets or sets the default element name to use when using the Merge<TBuilder>() /// 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(); } } /// /// Reads the root-message preamble specific to this formatter /// public override void ReadMessageStart() { ReadMessageStart(_rootElementName); } /// /// Reads the root-message preamble specific to this formatter /// 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(); } /// /// Reads the root-message close specific to this formatter, MUST be called /// on the reader obtained from ReadMessageStart(string element). /// 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(); } /// /// Merge the provided builder as an element named in the current context /// public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry) { return Merge(_rootElementName, builder, registry); } /// /// Merge the provided builder as an element of the current context /// public TBuilder Merge(string element, TBuilder builder) where TBuilder : IBuilderLite { return Merge(element, builder, ExtensionRegistry.Empty); } /// /// Merge the provided builder as an element of the current context /// public TBuilder Merge(string element, TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite { ReadMessageStart(element); builder.WeakMergeFrom(this, registry); ReadMessageEnd(); return builder; } /// /// Peeks at the next field in the input stream and returns what information is available. /// /// /// 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. /// 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; } /// /// Causes the reader to skip past this field /// 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(); } } /// /// 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. /// 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); } /// /// Returns true if it was able to read a String from the input /// protected override bool ReadAsText(ref string value, Type type) { Assert(_input.NodeType == XmlNodeType.Element); value = _input.ReadElementContentAsString(); return true; } /// /// Merges the input stream into the provided IBuilderLite /// protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) { Assert(_input.IsStartElement()); ReadMessageStart(_input.LocalName); builder.WeakMergeFrom(this, registry); ReadMessageEnd(); return true; } private IEnumerable NonNestedArrayItems(string field) { return base.ForeachArrayItem(field); } /// /// Cursors through the array elements and stops at the end of the array /// protected override IEnumerable 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(); } } } }