using System; using System.Collections.Generic; using System.IO; using System.Xml; 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; private string _rootElementName; 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; Options = XmlReaderOptions.None; } /// /// Constructs the XmlFormatReader with the XmlReader and options /// protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input) : base(copyFrom) { _input = input; _rootElementName = copyFrom._rootElementName; Options = copyFrom.Options; } /// /// 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; } } private XmlFormatReader CloneWith(XmlReader rdr) { XmlFormatReader copy = new XmlFormatReader(this, rdr); return copy; } private void NextElement() { while (!_input.IsStartElement() && _input.Read()) { continue; } } private static void Assert(bool cond) { if (!cond) { throw new FormatException(); } } /// /// 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 { string field; Assert(PeekNext(out field) && field == element); ReadMessage(builder, registry); 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) { NextElement(); if (_input.IsStartElement()) { 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")) && int.TryParse(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()); if (!_input.IsEmptyElement) { int depth = _input.Depth; XmlReader child = _input.ReadSubtree(); while (!child.IsStartElement() && child.Read()) { continue; } child.Read(); builder.WeakMergeFrom(CloneWith(child), registry); Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement); } _input.Read(); 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; } yield break; } if (!_input.IsEmptyElement) { int depth = _input.Depth; XmlReader child = _input.ReadSubtree(); while (!child.IsStartElement() && child.Read()) { continue; } child.Read(); foreach (string item in CloneWith(child).NonNestedArrayItems("item")) { yield return item; } Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement); } _input.Read(); yield break; } } }