Browse Source

Completed work and testing for manually reading/writing start/end message

csharptest 14 years ago
parent
commit
60fd773d30

+ 2 - 8
src/ProtocolBuffers.Serialization/AbstractReader.cs

@@ -17,12 +17,6 @@ namespace Google.ProtocolBuffers.Serialization
         
         
         /// <summary> Constructs a new reader </summary>
         /// <summary> Constructs a new reader </summary>
         protected AbstractReader() { MaxDepth = DefaultMaxDepth; }
         protected AbstractReader() { MaxDepth = DefaultMaxDepth; }
-        /// <summary> Constructs a new child reader </summary>
-        protected AbstractReader(AbstractReader copyFrom)
-        {
-            _depth = copyFrom._depth + 1;
-            MaxDepth = copyFrom.MaxDepth;
-        }
 
 
         /// <summary> Gets or sets the maximum recursion depth allowed </summary>
         /// <summary> Gets or sets the maximum recursion depth allowed </summary>
         public int MaxDepth { get; set; }
         public int MaxDepth { get; set; }
@@ -116,12 +110,12 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// Reads the root-message preamble specific to this formatter
         /// Reads the root-message preamble specific to this formatter
         /// </summary>
         /// </summary>
-        public abstract AbstractReader ReadStartMessage();
+        public abstract void ReadMessageStart();
 
 
         /// <summary>
         /// <summary>
         /// Reads the root-message close specific to this formatter
         /// Reads the root-message close specific to this formatter
         /// </summary>
         /// </summary>
-        public abstract void ReadEndMessage();
+        public abstract void ReadMessageEnd();
 
 
         /// <summary>
         /// <summary>
         /// Merges the input stream into the provided IBuilderLite 
         /// Merges the input stream into the provided IBuilderLite 

+ 0 - 4
src/ProtocolBuffers.Serialization/AbstractTextReader.cs

@@ -11,10 +11,6 @@ namespace Google.ProtocolBuffers.Serialization
     {
     {
         /// <summary> Constructs a new reader </summary>
         /// <summary> Constructs a new reader </summary>
         protected AbstractTextReader() { }
         protected AbstractTextReader() { }
-        /// <summary> Constructs a new child reader </summary>
-        protected AbstractTextReader(AbstractTextReader copyFrom)
-            : base(copyFrom)
-        { }
 
 
         /// <summary>
         /// <summary>
         /// Reads a typed field as a string
         /// Reads a typed field as a string

+ 6 - 6
src/ProtocolBuffers.Serialization/AbstractWriter.cs

@@ -45,23 +45,23 @@ namespace Google.ProtocolBuffers.Serialization
 
 
         /// <summary>
         /// <summary>
         /// Used to write any nessary root-message preamble. After this call you can call 
         /// Used to write any nessary root-message preamble. After this call you can call 
-        /// IMessageLite.MergeTo(...) and complete the message with a call to EndMessage().
+        /// IMessageLite.MergeTo(...) and complete the message with a call to WriteMessageEnd().
         /// These three calls are identical to just calling WriteMessage(message);
         /// These three calls are identical to just calling WriteMessage(message);
         /// </summary>
         /// </summary>
         /// <example>
         /// <example>
         /// AbstractWriter writer;
         /// AbstractWriter writer;
-        /// writer.StartMessage();
+        /// writer.WriteMessageStart();
         /// message.WriteTo(writer);
         /// message.WriteTo(writer);
-        /// writer.EndMessage();
+        /// writer.WriteMessageEnd();
         /// // ... or, but not both ...
         /// // ... or, but not both ...
         /// writer.WriteMessage(message);
         /// writer.WriteMessage(message);
         /// </example>
         /// </example>
-        public abstract void StartMessage();
+        public abstract void WriteMessageStart();
 
 
         /// <summary>
         /// <summary>
-        /// Used to complete a root-message previously started with a call to StartMessage()
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
         /// </summary>
         /// </summary>
-        public abstract void EndMessage();
+        public abstract void WriteMessageEnd();
 
 
         /// <summary>
         /// <summary>
         /// Writes a Boolean value
         /// Writes a Boolean value

+ 5 - 8
src/ProtocolBuffers.Serialization/DictionaryReader.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using Google.ProtocolBuffers.Descriptors;
 using Google.ProtocolBuffers.Descriptors;
@@ -25,17 +25,14 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// No-op
         /// No-op
         /// </summary>
         /// </summary>
-        public override AbstractReader ReadStartMessage()
-        {
-            return this;
-        }
+        public override void ReadMessageStart()
+        { }
 
 
         /// <summary>
         /// <summary>
         /// No-op
         /// No-op
         /// </summary>
         /// </summary>
-        public override void ReadEndMessage()
-        {
-        }
+        public override void ReadMessageEnd()
+        { }
 
 
         /// <summary>
         /// <summary>
         /// Merges the contents of stream into the provided message builder
         /// Merges the contents of stream into the provided message builder

+ 3 - 3
src/ProtocolBuffers.Serialization/DictionaryWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Google.ProtocolBuffers.Descriptors;
 using Google.ProtocolBuffers.Descriptors;
@@ -57,13 +57,13 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// No-op
         /// No-op
         /// </summary>
         /// </summary>
-        public override void StartMessage()
+        public override void WriteMessageStart()
         { }
         { }
 
 
         /// <summary>
         /// <summary>
         /// No-op
         /// No-op
         /// </summary>
         /// </summary>
-        public override void EndMessage()
+        public override void WriteMessageEnd()
         { }
         { }
 
 
         /// <summary>
         /// <summary>

+ 15 - 18
src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs

@@ -3,7 +3,7 @@ using System.IO;
 using System.Xml;
 using System.Xml;
 using System.Text;
 using System.Text;
 
 
-namespace Google.ProtocolBuffers.Serialization
+namespace Google.ProtocolBuffers.Serialization.Http
 {
 {
     /// <summary>
     /// <summary>
     /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
     /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
@@ -29,14 +29,14 @@ namespace Google.ProtocolBuffers.Serialization
             else if (inputType == FormatType.Json)
             else if (inputType == FormatType.Json)
             {
             {
                 JsonFormatReader reader = JsonFormatReader.CreateInstance(input);
                 JsonFormatReader reader = JsonFormatReader.CreateInstance(input);
-                codedInput = reader.ReadStartMessage();
+                codedInput = reader;
             }
             }
             else if (inputType == FormatType.Xml)
             else if (inputType == FormatType.Xml)
             {
             {
                 XmlFormatReader reader = XmlFormatReader.CreateInstance(input);
                 XmlFormatReader reader = XmlFormatReader.CreateInstance(input);
                 reader.RootElementName = options.XmlReaderRootElementName;
                 reader.RootElementName = options.XmlReaderRootElementName;
                 reader.Options = options.XmlReaderOptions;
                 reader.Options = options.XmlReaderOptions;
-                codedInput = reader.ReadStartMessage();
+                codedInput = reader;
             }
             }
             else
             else
                 throw new NotSupportedException();
                 throw new NotSupportedException();
@@ -56,6 +56,7 @@ namespace Google.ProtocolBuffers.Serialization
         public static TBuilder MergeFrom<TBuilder>(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite
         public static TBuilder MergeFrom<TBuilder>(this TBuilder builder, MessageFormatOptions options, string contentType, Stream input) where TBuilder : IBuilderLite
         {
         {
             ICodedInputStream codedInput = CreateInputStream(options, contentType, input);
             ICodedInputStream codedInput = CreateInputStream(options, contentType, input);
+            codedInput.ReadMessageStart();
             return (TBuilder)builder.WeakMergeFrom(codedInput, options.ExtensionRegistry);
             return (TBuilder)builder.WeakMergeFrom(codedInput, options.ExtensionRegistry);
         }
         }
         
         
@@ -82,13 +83,12 @@ namespace Google.ProtocolBuffers.Serialization
                 {
                 {
                     writer.Formatted();
                     writer.Formatted();
                 }
                 }
-                writer.StartMessage();
                 codedOutput = writer;
                 codedOutput = writer;
             }
             }
             else if (outputType == FormatType.Xml)
             else if (outputType == FormatType.Xml)
             {
             {
                 XmlFormatWriter writer;
                 XmlFormatWriter writer;
-                if (options.FormattedOutput)
+                if (!options.FormattedOutput)
                 {
                 {
                     writer = XmlFormatWriter.CreateInstance(output);
                     writer = XmlFormatWriter.CreateInstance(output);
                 }
                 }
@@ -99,16 +99,15 @@ namespace Google.ProtocolBuffers.Serialization
                                                          CheckCharacters = false,
                                                          CheckCharacters = false,
                                                          NewLineHandling = NewLineHandling.Entitize,
                                                          NewLineHandling = NewLineHandling.Entitize,
                                                          OmitXmlDeclaration = true,
                                                          OmitXmlDeclaration = true,
-                                                         Encoding = Encoding.UTF8,
+                                                         Encoding = new UTF8Encoding(false),
                                                          Indent = true,
                                                          Indent = true,
-                                                         IndentChars = "  ",
+                                                         IndentChars = "    ",
                                                          NewLineChars = Environment.NewLine,
                                                          NewLineChars = Environment.NewLine,
                                                      };
                                                      };
                     writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
                     writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));
                 }
                 }
                 writer.RootElementName = options.XmlWriterRootElementName;
                 writer.RootElementName = options.XmlWriterRootElementName;
                 writer.Options = options.XmlWriterOptions;
                 writer.Options = options.XmlWriterOptions;
-                writer.StartMessage();
                 codedOutput = writer;
                 codedOutput = writer;
             }
             }
             else
             else
@@ -126,19 +125,17 @@ namespace Google.ProtocolBuffers.Serialization
         /// <param name="output">The stream to write the message to</param>
         /// <param name="output">The stream to write the message to</param>
         public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output)
         public static void WriteTo(this IMessageLite message, MessageFormatOptions options, string contentType, Stream output)
         {
         {
-            using (ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output))
-            {
-                message.WriteTo(codedOutput);
+            ICodedOutputStream codedOutput = CreateOutputStream(options, contentType, output);
 
 
-                // This is effectivly done by Dispose(); however, if you need to finalize a message
-                // without disposing the underlying stream, this is the only way to do it.
-                if (codedOutput is AbstractWriter)
-                    ((AbstractWriter)codedOutput).EndMessage();
+            // Output the appropriate message preamble
+            codedOutput.WriteMessageStart();
 
 
-                codedOutput.Flush();
-            }
-        }
+            // Write the message content to the output
+            message.WriteTo(codedOutput);
 
 
+            // Write the closing message fragment
+            codedOutput.WriteMessageEnd();
+        }
 
 
         enum FormatType { ProtoBuffer, Json, Xml };
         enum FormatType { ProtoBuffer, Json, Xml };
 
 

+ 1 - 1
src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs

@@ -1,6 +1,6 @@
 using System;
 using System;
 
 
-namespace Google.ProtocolBuffers.Serialization
+namespace Google.ProtocolBuffers.Serialization.Http
 {
 {
     /// <summary>
     /// <summary>
     /// Defines control information for the various formatting used with HTTP services
     /// Defines control information for the various formatting used with HTTP services

+ 2 - 1
src/ProtocolBuffers.Serialization/Http/ServiceExtensions.cs

@@ -3,7 +3,7 @@ using System.Text;
 using Google.ProtocolBuffers;
 using Google.ProtocolBuffers;
 using System.IO;
 using System.IO;
 
 
-namespace Google.ProtocolBuffers.Serialization
+namespace Google.ProtocolBuffers.Serialization.Http
 {
 {
     /// <summary>
     /// <summary>
     /// Extensions for the IRpcServerStub
     /// Extensions for the IRpcServerStub
@@ -25,6 +25,7 @@ namespace Google.ProtocolBuffers.Serialization
             string contentType, Stream input, string responseType, Stream output)
             string contentType, Stream input, string responseType, Stream output)
         {
         {
             ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input);
             ICodedInputStream codedInput = MessageFormatFactory.CreateInputStream(options, contentType, input);
+            codedInput.ReadMessageStart();
             IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry);
             IMessageLite response = stub.CallMethod(methodName, codedInput, options.ExtensionRegistry);
             response.WriteTo(options, responseType, output);
             response.WriteTo(options, responseType, output);
         }
         }

+ 6 - 7
src/ProtocolBuffers.Serialization/JsonFormatReader.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Xml;
 using System.Xml;
@@ -103,19 +103,18 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// Reads the root-message preamble specific to this formatter
         /// Reads the root-message preamble specific to this formatter
         /// </summary>
         /// </summary>
-        public override AbstractReader ReadStartMessage()
+        public override void ReadMessageStart()
         {
         {
             _input.Consume('{');
             _input.Consume('{');
             _stopChar.Push('}');
             _stopChar.Push('}');
 
 
             _state = ReaderState.BeginObject;
             _state = ReaderState.BeginObject;
-            return this;
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Reads the root-message close specific to this formatter
         /// Reads the root-message close specific to this formatter
         /// </summary>
         /// </summary>
-        public override void ReadEndMessage()
+        public override void ReadMessageEnd()
         {
         {
             _input.Consume((char)_stopChar.Pop());
             _input.Consume((char)_stopChar.Pop());
             _state = ReaderState.EndValue;
             _state = ReaderState.EndValue;
@@ -126,9 +125,9 @@ namespace Google.ProtocolBuffers.Serialization
         /// </summary>
         /// </summary>
         public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
         public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
         {
         {
-            AbstractReader rdr = ReadStartMessage();
-            builder.WeakMergeFrom(rdr, registry);
-            rdr.ReadEndMessage();
+            ReadMessageStart();
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return builder;
             return builder;
         }
         }
 
 

+ 11 - 10
src/ProtocolBuffers.Serialization/JsonFormatWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
@@ -101,7 +101,7 @@ namespace Google.ProtocolBuffers.Serialization
         private class JsonStreamWriter : JsonFormatWriter
         private class JsonStreamWriter : JsonFormatWriter
         {
         {
 #if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
 #if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
-            static readonly Encoding Encoding = Encoding.UTF8;
+            static readonly Encoding Encoding = new UTF8Encoding(false);
 #else
 #else
             private static readonly Encoding Encoding = Encoding.ASCII;
             private static readonly Encoding Encoding = Encoding.ASCII;
 #endif
 #endif
@@ -244,9 +244,10 @@ namespace Google.ProtocolBuffers.Serialization
         /// </summary>
         /// </summary>
         protected override void Dispose(bool disposing)
         protected override void Dispose(bool disposing)
         {
         {
-            if (disposing && _counter.Count == 1)
+            if (disposing)
             {
             {
-                EndMessage();
+                while(_counter.Count > 1)
+                    WriteMessageEnd();
             }
             }
 
 
             base.Dispose(disposing);
             base.Dispose(disposing);
@@ -458,17 +459,17 @@ namespace Google.ProtocolBuffers.Serialization
         /// </summary>
         /// </summary>
         public override void WriteMessage(IMessageLite message)
         public override void WriteMessage(IMessageLite message)
         {
         {
-            StartMessage();
+            WriteMessageStart();
             message.WriteTo(this);
             message.WriteTo(this);
-            EndMessage();
+            WriteMessageEnd();
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
         /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
         /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
         /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
-        /// a call to EndMessage().
+        /// a call to WriteMessageEnd().
         /// </summary>
         /// </summary>
-        public override void StartMessage()
+        public override void WriteMessageStart()
         {
         {
             if (_isArray)
             if (_isArray)
             {
             {
@@ -479,9 +480,9 @@ namespace Google.ProtocolBuffers.Serialization
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Used to complete a root-message previously started with a call to StartMessage()
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
         /// </summary>
         /// </summary>
-        public override void EndMessage()
+        public override void WriteMessageEnd()
         {
         {
             _counter.RemoveAt(_counter.Count - 1);
             _counter.RemoveAt(_counter.Count - 1);
             WriteLine("}");
             WriteLine("}");

+ 69 - 73
src/ProtocolBuffers.Serialization/XmlFormatReader.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Xml;
 using System.Xml;
@@ -14,8 +14,23 @@ namespace Google.ProtocolBuffers.Serialization
     {
     {
         public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
         public const string DefaultRootElementName = XmlFormatWriter.DefaultRootElementName;
         private readonly XmlReader _input;
         private readonly XmlReader _input;
+        private readonly Stack<ElementStack> _elements;
         private string _rootElementName;
         private string _rootElementName;
 
 
+        private struct ElementStack
+        {
+            public readonly string LocalName;
+            public readonly int Depth;
+            public readonly bool IsEmpty;
+
+            public ElementStack(string localName, int depth, bool isEmpty) : this()
+            {
+                LocalName = localName;
+                IsEmpty = isEmpty;
+                Depth = depth;
+            }
+        }
+
         private static XmlReaderSettings DefaultSettings
         private static XmlReaderSettings DefaultSettings
         {
         {
             get
             get
@@ -72,20 +87,10 @@ namespace Google.ProtocolBuffers.Serialization
         {
         {
             _input = input;
             _input = input;
             _rootElementName = DefaultRootElementName;
             _rootElementName = DefaultRootElementName;
+            _elements = new Stack<ElementStack>();
             Options = XmlReaderOptions.None;
             Options = XmlReaderOptions.None;
         }
         }
 
 
-        /// <summary>
-        /// Constructs the XmlFormatReader with the XmlReader and options
-        /// </summary>
-        protected XmlFormatReader(XmlFormatReader copyFrom, XmlReader input)
-            : base(copyFrom)
-        {
-            _input = input;
-            _rootElementName = copyFrom._rootElementName;
-            Options = copyFrom.Options;
-        }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the options to use when reading the xml
         /// Gets or sets the options to use when reading the xml
         /// </summary>
         /// </summary>
@@ -113,20 +118,6 @@ namespace Google.ProtocolBuffers.Serialization
             }
             }
         }
         }
 
 
-        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)
         private static void Assert(bool cond)
         {
         {
             if (!cond)
             if (!cond)
@@ -138,36 +129,49 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// Reads the root-message preamble specific to this formatter
         /// Reads the root-message preamble specific to this formatter
         /// </summary>
         /// </summary>
-        public override AbstractReader ReadStartMessage()
+        public override void ReadMessageStart()
         {
         {
-            return ReadStartMessage(_rootElementName);
+            ReadMessageStart(_rootElementName);
         }
         }
 
 
-        public AbstractReader ReadStartMessage(string element)
+        /// <summary>
+        /// Reads the root-message preamble specific to this formatter
+        /// </summary>
+        public void ReadMessageStart(string element)
         {
         {
-            string field;
-            Assert(PeekNext(out field) && field == element);
-
-            XmlReader child = _input.ReadSubtree();
-            while (!child.IsStartElement() && child.Read())
+            while (!_input.IsStartElement() && _input.Read())
             {
             {
                 continue;
                 continue;
             }
             }
-            child.Read();
-            return CloneWith(child);
+            Assert(_input.IsStartElement() && _input.LocalName == element);
+            _elements.Push(new ElementStack(element, _input.Depth, _input.IsEmptyElement));
+            _input.Read();
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Reads the root-message close specific to this formatter, MUST be called
         /// Reads the root-message close specific to this formatter, MUST be called
-        /// on the reader obtained from ReadStartMessage(string element).
+        /// on the reader obtained from ReadMessageStart(string element).
         /// </summary>
         /// </summary>
-        public override void ReadEndMessage()
+        public override void ReadMessageEnd()
         {
         {
-            Assert(0 == _input.Depth);
-            if(_input.NodeType == XmlNodeType.EndElement)
+            Assert(_elements.Count > 0);
+
+            ElementStack 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();
                 _input.Read();
             }
             }
+            _elements.Pop();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -192,9 +196,9 @@ namespace Google.ProtocolBuffers.Serialization
         public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
         public TBuilder Merge<TBuilder>(string element, TBuilder builder, ExtensionRegistry registry)
             where TBuilder : IBuilderLite
             where TBuilder : IBuilderLite
         {
         {
-            string field;
-            Assert(PeekNext(out field) && field == element);
-            ReadMessage(builder, registry);
+            ReadMessageStart(element);
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return builder;
             return builder;
         }
         }
 
 
@@ -207,7 +211,21 @@ namespace Google.ProtocolBuffers.Serialization
         /// </remarks>
         /// </remarks>
         protected override bool PeekNext(out string field)
         protected override bool PeekNext(out string field)
         {
         {
-            NextElement();
+            ElementStack stopNode;
+            if (_elements.Count == 0)
+            {
+                stopNode = new ElementStack(null, _input.Depth - 1, false);
+            }
+            else
+            {
+                stopNode = _elements.Peek();
+            }
+
+            while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
+            {
+                continue;
+            }
+
             if (_input.IsStartElement())
             if (_input.IsStartElement())
             {
             {
                 field = _input.LocalName;
                 field = _input.LocalName;
@@ -270,20 +288,9 @@ namespace Google.ProtocolBuffers.Serialization
         protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
         protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
         {
         {
             Assert(_input.IsStartElement());
             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();
+            ReadMessageStart(_input.LocalName);
+            builder.WeakMergeFrom(this, registry);
+            ReadMessageEnd();
             return true;
             return true;
         }
         }
 
 
@@ -305,27 +312,16 @@ namespace Google.ProtocolBuffers.Serialization
                 {
                 {
                     yield return item;
                     yield return item;
                 }
                 }
-                yield break;
             }
             }
-            if (!_input.IsEmptyElement)
+            else
             {
             {
-                int depth = _input.Depth;
-                XmlReader child = _input.ReadSubtree();
-
-                while (!child.IsStartElement() && child.Read())
-                {
-                    continue;
-                }
-                child.Read();
-
-                foreach (string item in CloneWith(child).NonNestedArrayItems("item"))
+                ReadMessageStart(field);
+                foreach (string item in NonNestedArrayItems("item"))
                 {
                 {
                     yield return item;
                     yield return item;
                 }
                 }
-                Assert(depth == _input.Depth && _input.NodeType == XmlNodeType.EndElement);
+                ReadMessageEnd();
             }
             }
-            _input.Read();
-            yield break;
         }
         }
     }
     }
 }
 }

+ 18 - 10
src/ProtocolBuffers.Serialization/XmlFormatWriter.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections;
 using System.Collections;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -14,10 +14,12 @@ namespace Google.ProtocolBuffers.Serialization
     /// </summary>
     /// </summary>
     public class XmlFormatWriter : AbstractTextWriter
     public class XmlFormatWriter : AbstractTextWriter
     {
     {
+        private static readonly Encoding DefaultEncoding = new UTF8Encoding(false);
         public const string DefaultRootElementName = "root";
         public const string DefaultRootElementName = "root";
         private const int NestedArrayFlag = 0x0001;
         private const int NestedArrayFlag = 0x0001;
         private readonly XmlWriter _output;
         private readonly XmlWriter _output;
         private string _rootElementName;
         private string _rootElementName;
+        private int _messageOpenCount;
 
 
         private static XmlWriterSettings DefaultSettings(Encoding encoding)
         private static XmlWriterSettings DefaultSettings(Encoding encoding)
         {
         {
@@ -43,7 +45,7 @@ namespace Google.ProtocolBuffers.Serialization
         /// </summary>
         /// </summary>
         public static XmlFormatWriter CreateInstance(Stream output)
         public static XmlFormatWriter CreateInstance(Stream output)
         {
         {
-            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(Encoding.UTF8)));
+            return new XmlFormatWriter(XmlWriter.Create(output, DefaultSettings(DefaultEncoding)));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -65,6 +67,7 @@ namespace Google.ProtocolBuffers.Serialization
         protected XmlFormatWriter(XmlWriter output)
         protected XmlFormatWriter(XmlWriter output)
         {
         {
             _output = output;
             _output = output;
+            _messageOpenCount = 0;
             _rootElementName = DefaultRootElementName;
             _rootElementName = DefaultRootElementName;
         }
         }
 
 
@@ -75,8 +78,8 @@ namespace Google.ProtocolBuffers.Serialization
         {
         {
             if (disposing)
             if (disposing)
             {
             {
-                if (_output.WriteState != WriteState.Closed && _output.WriteState != WriteState.Start)
-                    _output.WriteEndDocument();
+                while(_messageOpenCount > 0)
+                    WriteMessageEnd();
 
 
                 _output.Close();
                 _output.Close();
             }
             }
@@ -128,9 +131,9 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// Used to write the root-message preamble, in xml this is open element for RootElementName,
         /// Used to write the root-message preamble, in xml this is open element for RootElementName,
         /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and 
         /// by default "&lt;root&gt;". After this call you can call IMessageLite.MergeTo(...) and 
-        /// complete the message with a call to EndMessage().
+        /// complete the message with a call to WriteMessageEnd().
         /// </summary>
         /// </summary>
-        public override void StartMessage()
+        public override void WriteMessageStart()
         {
         {
             StartMessage(_rootElementName);
             StartMessage(_rootElementName);
         }
         }
@@ -138,7 +141,7 @@ namespace Google.ProtocolBuffers.Serialization
         /// <summary>
         /// <summary>
         /// Used to write the root-message preamble, in xml this is open element for elementName. 
         /// Used to write the root-message preamble, in xml this is open element for elementName. 
         /// After this call you can call IMessageLite.MergeTo(...) and  complete the message with 
         /// After this call you can call IMessageLite.MergeTo(...) and  complete the message with 
-        /// a call to EndMessage().
+        /// a call to WriteMessageEnd().
         /// </summary>
         /// </summary>
         public void StartMessage(string elementName)
         public void StartMessage(string elementName)
         {
         {
@@ -151,15 +154,20 @@ namespace Google.ProtocolBuffers.Serialization
             {
             {
                 _output.WriteStartElement(elementName);
                 _output.WriteStartElement(elementName);
             }
             }
+            _messageOpenCount++;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Used to complete a root-message previously started with a call to StartMessage()
+        /// Used to complete a root-message previously started with a call to WriteMessageStart()
         /// </summary>
         /// </summary>
-        public override void EndMessage()
+        public override void WriteMessageEnd()
         {
         {
+            if (_messageOpenCount <= 0)
+                throw new InvalidOperationException();
+
             _output.WriteEndElement();
             _output.WriteEndElement();
             _output.Flush();
             _output.Flush();
+            _messageOpenCount--;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -177,7 +185,7 @@ namespace Google.ProtocolBuffers.Serialization
         {
         {
             StartMessage(elementName);
             StartMessage(elementName);
             message.WriteTo(this);
             message.WriteTo(this);
-            EndMessage();
+            WriteMessageEnd();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 0
src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj

@@ -106,6 +106,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ReflectionTester.cs" />
     <Compile Include="ReflectionTester.cs" />
     <Compile Include="ServiceTest.cs" />
     <Compile Include="ServiceTest.cs" />
+    <Compile Include="TestMimeMessageFormats.cs" />
     <Compile Include="TestProtos\UnitTestCSharpOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestCSharpOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestCustomOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestCustomOptionsProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestEmbedOptimizeForProtoFile.cs" />
     <Compile Include="TestProtos\UnitTestEmbedOptimizeForProtoFile.cs" />

+ 225 - 0
src/ProtocolBuffers.Test/TestMimeMessageFormats.cs

@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Google.ProtocolBuffers.Serialization;
+using Google.ProtocolBuffers.Serialization.Http;
+using Google.ProtocolBuffers.TestProtos;
+using NUnit.Framework;
+
+namespace Google.ProtocolBuffers
+{
+    [TestFixture]
+    public class TestMimeMessageFormats
+    {
+        // There is a whole host of various json mime types in use around the net, this is the set we accept...
+        readonly IEnumerable<string> JsonTypes = new string[] { "application/json", "application/x-json", "application/x-javascript", "text/javascript", "text/x-javascript", "text/x-json", "text/json" };
+        readonly IEnumerable<string> XmlTypes = new string[] { "text/xml", "application/xml" };
+        readonly IEnumerable<string> ProtobufTypes = new string[] { "application/binary", "application/x-protobuf", "application/vnd.google.protobuf" };
+
+        [Test]
+        public void TestReadJsonMimeTypes()
+        {
+            foreach (string type in JsonTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is JsonFormatReader);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+                is JsonFormatReader);
+        }
+        [Test]
+        public void TestWriteJsonMimeTypes()
+        {
+            foreach (string type in JsonTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is JsonFormatWriter);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/json" }, null, Stream.Null)
+                is JsonFormatWriter);
+        }
+        [Test]
+        public void TestReadXmlMimeTypes()
+        {
+            foreach (string type in XmlTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is XmlFormatReader);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+                is XmlFormatReader);
+        }
+        [Test]
+        public void TestWriteXmlMimeTypes()
+        {
+            foreach (string type in XmlTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is XmlFormatWriter);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/xml" }, null, Stream.Null)
+                is XmlFormatWriter);
+        }
+        [Test]
+        public void TestReadProtoMimeTypes()
+        {
+            foreach (string type in ProtobufTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateInputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is CodedInputStream);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateInputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+                is CodedInputStream);
+        }
+        [Test]
+        public void TestWriteProtoMimeTypes()
+        {
+            foreach (string type in ProtobufTypes)
+            {
+                Assert.IsTrue(
+                    MessageFormatFactory.CreateOutputStream(new MessageFormatOptions(), type, Stream.Null)
+                    is CodedOutputStream);
+            }
+            Assert.IsTrue(
+                MessageFormatFactory.CreateOutputStream(new MessageFormatOptions() { DefaultContentType = "application/vnd.google.protobuf" }, null, Stream.Null)
+                is CodedOutputStream);
+        }
+        [Test]
+        public void TestMergeFromJsonType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/json", new MemoryStream(Encoding.ASCII.GetBytes(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToJson()
+                    )))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestMergeFromXmlType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/xml", new MemoryStream(Encoding.ASCII.GetBytes(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToXml()
+                    )))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestMergeFromProtoType()
+        {
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                new MessageFormatOptions(), "application/vnd.google.protobuf", new MemoryStream(
+                    TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray()
+                    ))
+                .Build();
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.Number);
+        }
+        [Test]
+        public void TestWriteToJsonType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/json", ms);
+
+            Assert.AreEqual(@"{""text"":""a"",""number"":1}", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestWriteToXmlType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/xml", ms);
+
+            Assert.AreEqual("<root><text>a</text><number>1</number></root>", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestWriteToProtoType()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions(), "application/vnd.google.protobuf", ms);
+
+            byte[] bytes = TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build().ToByteArray();
+            Assert.AreEqual(bytes, ms.ToArray());
+        }
+        [Test]
+        public void TestXmlReaderOptions()
+        {
+            MemoryStream ms = new MemoryStream();
+            XmlFormatWriter.CreateInstance(ms)
+                .SetOptions(XmlWriterOptions.OutputNestedArrays)
+                .WriteMessage("my-root-node", TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build());
+            ms.Position = 0;
+
+            MessageFormatOptions options = new MessageFormatOptions()
+            {
+                XmlReaderOptions = XmlReaderOptions.ReadNestedArrays,
+                XmlReaderRootElementName = "my-root-node"
+            };
+
+            TestXmlMessage msg = new TestXmlMessage.Builder().MergeFrom(
+                options, "application/xml", ms)
+                .Build();
+
+            Assert.AreEqual("a", msg.Text);
+            Assert.AreEqual(1, msg.NumbersList[0]);
+            Assert.AreEqual(2, msg.NumbersList[1]);
+
+        }
+        [Test]
+        public void TestXmlWriterOptions()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder().SetText("a").AddNumbers(1).AddNumbers(2).Build();
+            MessageFormatOptions options = new MessageFormatOptions()
+            {
+                XmlWriterOptions = XmlWriterOptions.OutputNestedArrays,
+                XmlWriterRootElementName = "root-node"
+            };
+
+            MemoryStream ms = new MemoryStream();
+            message.WriteTo(options, "application/xml", ms);
+            ms.Position = 0;
+            
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            XmlFormatReader.CreateInstance(ms)
+                .SetOptions(XmlReaderOptions.ReadNestedArrays)
+                .Merge("root-node", builder);
+
+            Assert.AreEqual("a", builder.Text);
+            Assert.AreEqual(1, builder.NumbersList[0]);
+            Assert.AreEqual(2, builder.NumbersList[1]);
+        }
+        [Test]
+        public void TestJsonFormatted()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/json", ms);
+
+            Assert.AreEqual("{\r\n    \"text\": \"a\",\r\n    \"number\": 1\r\n}", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+        [Test]
+        public void TestXmlFormatted()
+        {
+            MemoryStream ms = new MemoryStream();
+            TestXmlMessage.CreateBuilder().SetText("a").SetNumber(1).Build()
+                .WriteTo(new MessageFormatOptions() { FormattedOutput = true }, "application/xml", ms);
+
+            Assert.AreEqual("<root>\r\n    <text>a</text>\r\n    <number>1</number>\r\n</root>", Encoding.UTF8.GetString(ms.ToArray()));
+        }
+    }
+}

+ 28 - 6
src/ProtocolBuffers.Test/TestWriterFormatJson.cs

@@ -45,7 +45,7 @@ namespace Google.ProtocolBuffers
             using (TextWriter output = new StringWriter())
             using (TextWriter output = new StringWriter())
             using (AbstractWriter writer = JsonFormatWriter.CreateInstance(output))
             using (AbstractWriter writer = JsonFormatWriter.CreateInstance(output))
             {
             {
-                writer.StartMessage();      //manually begin the message, output is '{'
+                writer.WriteMessageStart();      //manually begin the message, output is '{'
                 
                 
                 writer.Flush();
                 writer.Flush();
                 Assert.AreEqual("{", output.ToString());
                 Assert.AreEqual("{", output.ToString());
@@ -56,7 +56,7 @@ namespace Google.ProtocolBuffers
                 writer.Flush();
                 writer.Flush();
                 Assert.AreEqual(@"{""valid"":true", output.ToString());
                 Assert.AreEqual(@"{""valid"":true", output.ToString());
 
 
-                writer.EndMessage();        //manually write the end message '}'
+                writer.WriteMessageEnd();        //manually write the end message '}'
                 Assert.AreEqual(@"{""valid"":true}", output.ToString());
                 Assert.AreEqual(@"{""valid"":true}", output.ToString());
             }
             }
         }
         }
@@ -65,13 +65,13 @@ namespace Google.ProtocolBuffers
         public void Example_ReadJsonUsingICodedInputStream()
         public void Example_ReadJsonUsingICodedInputStream()
         {
         {
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
-            AbstractReader reader = JsonFormatReader.CreateInstance(@"{""valid"":true}");
+            ICodedInputStream reader = JsonFormatReader.CreateInstance(@"{""valid"":true}");
 
 
-            AbstractReader stream = reader.ReadStartMessage();  //manually read the begin the message '{'
+            reader.ReadMessageStart();  //manually read the begin the message '{'
 
 
-            builder.MergeFrom(stream);  //write the message normally
+            builder.MergeFrom(reader);  //write the message normally
 
 
-            stream.ReadEndMessage();    //manually read the end message '}'
+            reader.ReadMessageEnd();    //manually read the end message '}'
         }
         }
 
 
         protected string Content;
         protected string Content;
@@ -401,6 +401,28 @@ namespace Google.ProtocolBuffers
             Assert.AreEqual(3, ordinal);
             Assert.AreEqual(3, ordinal);
             Assert.AreEqual(3, builder.TextlinesCount);
             Assert.AreEqual(3, builder.TextlinesCount);
         }
         }
+        [Test]
+        public void TestReadWriteJsonWithoutRoot()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+            string Json;
+            using (StringWriter sw = new StringWriter())
+            {
+                ICodedOutputStream output = JsonFormatWriter.CreateInstance(sw);
+
+                message.WriteTo(output);
+                output.Flush();
+                Json = sw.ToString();
+            }
+            Assert.AreEqual(@"""text"":""abc"",""number"":123", Json);
+
+            ICodedInputStream input = JsonFormatReader.CreateInstance(Json);
+            TestXmlMessage copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+
+            Assert.AreEqual(message, copy);
+        }
         [Test,ExpectedException(typeof(RecursionLimitExceededException))]
         [Test,ExpectedException(typeof(RecursionLimitExceededException))]
         public void TestRecursiveLimit()
         public void TestRecursiveLimit()
         {
         {

+ 52 - 11
src/ProtocolBuffers.Test/TestWriterFormatXml.cs

@@ -48,12 +48,12 @@ namespace Google.ProtocolBuffers
             using (TextWriter output = new StringWriter())
             using (TextWriter output = new StringWriter())
             using (AbstractWriter writer = XmlFormatWriter.CreateInstance(output))
             using (AbstractWriter writer = XmlFormatWriter.CreateInstance(output))
             {
             {
-                writer.StartMessage();      //manually begin the message, output is '{'
+                writer.WriteMessageStart();      //manually begin the message, output is '{'
 
 
                 ICodedOutputStream stream = writer;
                 ICodedOutputStream stream = writer;
-                message.WriteTo(stream);    //write the message normally
+                message.WriteTo(stream);         //write the message normally
 
 
-                writer.EndMessage();        //manually write the end message '}'
+                writer.WriteMessageEnd();        //manually write the end message '}'
                 Assert.AreEqual(@"<root><valid>true</valid></root>", output.ToString());
                 Assert.AreEqual(@"<root><valid>true</valid></root>", output.ToString());
             }
             }
         }
         }
@@ -62,13 +62,13 @@ namespace Google.ProtocolBuffers
         public void Example_ReadXmlUsingICodedInputStream()
         public void Example_ReadXmlUsingICodedInputStream()
         {
         {
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
-            AbstractReader reader = XmlFormatReader.CreateInstance(@"<root><valid>true</valid></root>");
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><valid>true</valid></root>");
 
 
-            AbstractReader stream = reader.ReadStartMessage();  //manually read the begin the message '{'
+            reader.ReadMessageStart();  //manually read the begin the message '{'
 
 
-            builder.MergeFrom(stream);  //write the message normally
+            builder.MergeFrom(reader);  //read the message normally
 
 
-            stream.ReadEndMessage();    //manually read the end message '}'
+            reader.ReadMessageEnd();    //manually read the end message '}'
         }
         }
 
 
         [Test]
         [Test]
@@ -387,13 +387,54 @@ namespace Google.ProtocolBuffers
         public void TestXmlReadEmptyRoot()
         public void TestXmlReadEmptyRoot()
         {
         {
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
             TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
-            AbstractReader reader = XmlFormatReader.CreateInstance(@"<root/>");
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root/>");
 
 
-            AbstractReader stream = reader.ReadStartMessage();  //manually read the begin the message '{'
+            reader.ReadMessageStart();  //manually read the begin the message '{'
 
 
-            builder.MergeFrom(stream);  //write the message normally
+            builder.MergeFrom(reader);  //write the message normally
 
 
-            stream.ReadEndMessage();    //manually read the end message '}'
+            reader.ReadMessageEnd();    //manually read the end message '}'
+        }
+
+        [Test]
+        public void TestXmlReadEmptyChild()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            ICodedInputStream reader = XmlFormatReader.CreateInstance(@"<root><text /></root>");
+
+            reader.ReadMessageStart();  //manually read the begin the message '{'
+
+            builder.MergeFrom(reader);  //write the message normally
+            Assert.IsTrue(builder.HasText);
+            Assert.AreEqual(String.Empty, builder.Text);
+        }
+
+        [Test]
+        public void TestXmlReadWriteWithoutRoot()
+        {
+            TestXmlMessage.Builder builder = TestXmlMessage.CreateBuilder();
+            TestXmlMessage message = builder.SetText("abc").SetNumber(123).Build();
+
+            string xml;
+            using (StringWriter sw = new StringWriter())
+            {
+                ICodedOutputStream output = XmlFormatWriter.CreateInstance(
+                    XmlWriter.Create(sw, new XmlWriterSettings() { ConformanceLevel = ConformanceLevel.Fragment }));
+
+                message.WriteTo(output);
+                output.Flush();
+                xml = sw.ToString();
+            }
+            Assert.AreEqual("<text>abc</text><number>123</number>", xml);
+
+            TestXmlMessage copy;
+            using (XmlReader xr = XmlReader.Create(new StringReader(xml), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Fragment }))
+            {
+                ICodedInputStream input = XmlFormatReader.CreateInstance(xr);
+                copy = TestXmlMessage.CreateBuilder().MergeFrom(input).Build();
+            }
+
+            Assert.AreEqual(message, copy);
         }
         }
 
 
         [Test, ExpectedException(typeof(RecursionLimitExceededException))]
         [Test, ExpectedException(typeof(RecursionLimitExceededException))]

+ 3 - 0
src/ProtocolBuffers/CodedInputStream.cs

@@ -144,6 +144,9 @@ namespace Google.ProtocolBuffers
 
 
         #endregion
         #endregion
 
 
+        void ICodedInputStream.ReadMessageStart() { }
+        void ICodedInputStream.ReadMessageEnd() { }
+
         #region Validation
         #region Validation
 
 
         /// <summary>
         /// <summary>

+ 3 - 0
src/ProtocolBuffers/CodedOutputStream.cs

@@ -136,6 +136,9 @@ namespace Google.ProtocolBuffers
             }
             }
         }
         }
 
 
+        void ICodedOutputStream.WriteMessageStart() { }
+        void ICodedOutputStream.WriteMessageEnd() { Flush(); }
+
         #region Writing of unknown fields
         #region Writing of unknown fields
 
 
         [Obsolete]
         [Obsolete]

+ 18 - 0
src/ProtocolBuffers/ICodedInputStream.cs

@@ -45,6 +45,24 @@ namespace Google.ProtocolBuffers
 {
 {
     public interface ICodedInputStream
     public interface ICodedInputStream
     {
     {
+        /// <summary>
+        /// Reads any message initialization data expected from the input stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void ReadMessageStart();
+        /// <summary>
+        /// Reads any message finalization data expected from the input stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void ReadMessageEnd();
         /// <summary>
         /// <summary>
         /// Attempt to read a field tag, returning false if we have reached the end
         /// Attempt to read a field tag, returning false if we have reached the end
         /// of the input data.
         /// of the input data.

+ 18 - 0
src/ProtocolBuffers/ICodedOutputStream.cs

@@ -51,6 +51,24 @@ namespace Google.ProtocolBuffers
     /// </summary>
     /// </summary>
     public interface ICodedOutputStream : IDisposable
     public interface ICodedOutputStream : IDisposable
     {
     {
+        /// <summary>
+        /// Writes any message initialization data needed to the output stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void WriteMessageStart();
+        /// <summary>
+        /// Writes any message finalization data needed to the output stream
+        /// </summary>
+        /// <remarks>
+        /// This is primarily used by text formats and unnecessary for protobuffers' own
+        /// binary format.  The API for MessageStart/End was added for consistent handling
+        /// of output streams regardless of the actual writer implementation.
+        /// </remarks>
+        void WriteMessageEnd();
         /// <summary>
         /// <summary>
         /// Indicates that all temporary buffers be written to the final output.
         /// Indicates that all temporary buffers be written to the final output.
         /// </summary>
         /// </summary>