using System; using System.Collections.Generic; using System.IO; using System.Xml; using Google.ProtocolBuffers.Descriptors; 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; static XmlReaderSettings DefaultSettings { get { return new XmlReaderSettings() { CheckCharacters=false, IgnoreComments=true, IgnoreProcessingInstructions = true }; } } /// /// Constructs the XmlFormatReader using the stream provided as the xml /// public XmlFormatReader(Stream input) : this(XmlReader.Create(input, DefaultSettings)) { } /// /// Constructs the XmlFormatReader using the string provided as the xml to be read /// public XmlFormatReader(String input) : this(XmlReader.Create(new StringReader(input))) { } /// /// Constructs the XmlFormatReader using the xml in the TextReader /// public XmlFormatReader(TextReader input) : this(XmlReader.Create(input)) { } /// /// Constructs the XmlFormatReader with the XmlReader /// public XmlFormatReader(XmlReader input) : this(input, XmlReaderOptions.None) { } /// /// Constructs the XmlFormatReader with the XmlReader and options /// public XmlFormatReader(XmlReader input, XmlReaderOptions options) { _input = input; _rootElementName = DefaultRootElementName; Options = options; } /// /// Gets or sets the options to use when reading the xml /// public XmlReaderOptions Options { get; set; } /// /// 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) { return new XmlFormatReader(rdr, Options); } 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; } } }