Эх сурвалжийг харах

Added the XmlFormatWriter/Reader

csharptest 14 жил өмнө
parent
commit
2b86884659

+ 0 - 2
src/ProtocolBuffers.Test/CompatTests/BinaryCompatibilityTests.cs

@@ -6,8 +6,6 @@ namespace Google.ProtocolBuffers.CompatTests
     [TestFixture]
     public class BinaryCompatibilityTests : CompatibilityTests
     {
-        protected override string TestName { get { return "binary"; } }
-
         protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
         {
             byte[] bresult = message.ToByteArray();

+ 0 - 1
src/ProtocolBuffers.Test/CompatTests/CompatibilityTests.cs

@@ -6,7 +6,6 @@ namespace Google.ProtocolBuffers.CompatTests
 {
     public abstract class CompatibilityTests
     {
-        protected abstract string TestName { get; }
         protected abstract object SerializeMessage<TMessage, TBuilder>(TMessage message)
             where TMessage : IMessageLite<TMessage, TBuilder>
             where TBuilder : IBuilderLite<TMessage, TBuilder>;

+ 0 - 2
src/ProtocolBuffers.Test/CompatTests/TextCompatibilityTests.cs

@@ -7,8 +7,6 @@ namespace Google.ProtocolBuffers.CompatTests
     [TestFixture]
     public class TextCompatibilityTests : CompatibilityTests
     {
-        protected override string TestName { get { return "text"; } }
-
         protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
         {
             StringWriter text = new StringWriter();

+ 24 - 0
src/ProtocolBuffers.Test/CompatTests/XmlCompatibilityTests.cs

@@ -0,0 +1,24 @@
+using System.IO;
+using Google.ProtocolBuffers.Serialization;
+using NUnit.Framework;
+
+namespace Google.ProtocolBuffers.CompatTests
+{
+    [TestFixture]
+    public class XmlCompatibilityTests : CompatibilityTests
+    {
+        protected override object SerializeMessage<TMessage, TBuilder>(TMessage message)
+        {
+            StringWriter text = new StringWriter();
+            XmlFormatWriter writer = new XmlFormatWriter(text);
+            writer.WriteMessage("root", message);
+            return text.ToString();
+        }
+
+        protected override TBuilder DeerializeMessage<TMessage, TBuilder>(object message, TBuilder builder, ExtensionRegistry registry)
+        {
+            XmlFormatReader reader = new XmlFormatReader((string)message);
+            return reader.Merge("root", builder, registry);
+        }
+    }
+}

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

@@ -83,6 +83,7 @@
       <DependentUpon>TestResources.resx</DependentUpon>
     </Compile>
     <Compile Include="CompatTests\TextCompatibilityTests.cs" />
+    <Compile Include="CompatTests\XmlCompatibilityTests.cs" />
     <Compile Include="CSharpOptionsTest.cs" />
     <Compile Include="DescriptorsTest.cs" />
     <Compile Include="Descriptors\MessageDescriptorTest.cs" />
@@ -114,6 +115,7 @@
     <Compile Include="TestProtos\UnitTestXmlSerializerTestProtoFile.cs" />
     <Compile Include="TestRpcGenerator.cs" />
     <Compile Include="TestUtil.cs" />
+    <Compile Include="TestWriterFormatXml.cs" />
     <Compile Include="TextFormatTest.cs" />
     <Compile Include="UnknownFieldSetTest.cs" />
     <Compile Include="WireFormatTest.cs" />

+ 308 - 0
src/ProtocolBuffers.Test/TestWriterFormatXml.cs

@@ -0,0 +1,308 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Xml;
+using Google.ProtocolBuffers.Serialization;
+using NUnit.Framework;
+using Google.ProtocolBuffers.TestProtos;
+
+namespace Google.ProtocolBuffers
+{
+    [TestFixture]
+    public class TestWriterFormatXml
+    {
+        [Test]
+        public void TestEmptyMessage()
+        {
+            TestXmlChild message = TestXmlChild.CreateBuilder()
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            XmlTextWriter xw = new XmlTextWriter(sw);
+
+            //When we call message.WriteTo, we are responsible for the root element
+            xw.WriteStartElement("root");
+            message.WriteTo(new XmlFormatWriter(xw));
+            xw.WriteEndElement();
+            xw.Flush();
+
+            string xml = sw.ToString();
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlChild copy = rdr.Merge(TestXmlChild.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestRepeatedField()
+        {
+            TestXmlChild message = TestXmlChild.CreateBuilder()
+                .AddOptions(EnumOptions.ONE)
+                .AddOptions(EnumOptions.TWO)
+                .Build();
+
+            //Allow the writer to write the root element
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlChild copy = rdr.Merge(TestXmlChild.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestNestedEmptyMessage()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetChild(TestXmlChild.CreateBuilder().Build())
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestNestedMessage()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.TWO).Build())
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestBooleanTypes()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestFullMessage()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .SetText("text")
+                .AddTextlines("a")
+                .AddTextlines("b")
+                .AddTextlines("c")
+                .SetNumber(0x1010101010)
+                .AddNumbers(1)
+                .AddNumbers(2)
+                .AddNumbers(3)
+                .SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.ONE).SetBinary(ByteString.CopyFrom(new byte[1])))
+                .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.TWO).SetBinary(ByteString.CopyFrom(new byte[2])))
+                .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.THREE).SetBinary(ByteString.CopyFrom(new byte[3])))
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestFullMessageWithRichTypes()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetValid(true)
+                .SetText("text")
+                .AddTextlines("a")
+                .AddTextlines("b")
+                .AddTextlines("c")
+                .SetNumber(0x1010101010)
+                .AddNumbers(1)
+                .AddNumbers(2)
+                .AddNumbers(3)
+                .SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.ONE).SetBinary(ByteString.CopyFrom(new byte[1])))
+                .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.TWO).SetBinary(ByteString.CopyFrom(new byte[2])))
+                .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.THREE).SetBinary(ByteString.CopyFrom(new byte[3])))
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw) 
+            {
+                Options = XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputEnumValues
+            }.WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            rdr.Options = XmlReaderOptions.ReadNestedArrays;
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestFullMessageWithUnknownFields()
+        {
+            TestXmlMessage origial = TestXmlMessage.CreateBuilder()
+                    .SetValid(true)
+                    .SetText("text")
+                    .AddTextlines("a")
+                    .AddTextlines("b")
+                    .AddTextlines("c")
+                    .SetNumber(0x1010101010)
+                    .AddNumbers(1)
+                    .AddNumbers(2)
+                    .AddNumbers(3)
+                    .SetChild(TestXmlChild.CreateBuilder().AddOptions(EnumOptions.ONE).SetBinary(ByteString.CopyFrom(new byte[1])))
+                    .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.TWO).SetBinary(ByteString.CopyFrom(new byte[2])))
+                    .AddChildren(TestXmlMessage.Types.Children.CreateBuilder().AddOptions(EnumOptions.THREE).SetBinary(ByteString.CopyFrom(new byte[3])))
+                .Build();
+            TestXmlNoFields message = TestXmlNoFields.CreateBuilder().MergeFrom(origial.ToByteArray()).Build();
+
+            Assert.AreEqual(0, message.AllFields.Count);
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw)
+            {
+                Options = XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputEnumValues
+            }.WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            using (XmlReader x = XmlReader.Create(new StringReader(xml)))
+            {
+                x.MoveToContent();
+                Assert.AreEqual(XmlNodeType.Element, x.NodeType);
+                //should always be empty
+                Assert.IsTrue(x.IsEmptyElement ||
+                    (x.Read() && x.NodeType == XmlNodeType.EndElement)
+                    );
+            }
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            rdr.Options = XmlReaderOptions.ReadNestedArrays;
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(TestXmlMessage.DefaultInstance, copy);
+        }
+        [Test]
+        public void TestMessageWithXmlText()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetText("<text>").Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestXmlWithWhitespace()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetText(" \t <- leading space and trailing -> \r\n\t").Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder()).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestXmlWithExtensionText()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionText, " extension text value ! ")
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            ExtensionRegistry registry = ExtensionRegistry.CreateInstance();
+            UnitTestXmlSerializerTestProtoFile.RegisterAllExtensions(registry);
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestXmlWithExtensionMessage()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionMessage,
+                new TestXmlExtension.Builder().SetNumber(42).Build()).Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            ExtensionRegistry registry = ExtensionRegistry.CreateInstance();
+            UnitTestXmlSerializerTestProtoFile.RegisterAllExtensions(registry);
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestXmlWithExtensionArray()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 100)
+                .AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 101)
+                .AddExtension(UnitTestXmlSerializerTestProtoFile.ExtensionNumber, 102)
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            ExtensionRegistry registry = ExtensionRegistry.CreateInstance();
+            UnitTestXmlSerializerTestProtoFile.RegisterAllExtensions(registry);
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
+            Assert.AreEqual(message, copy);
+        }
+        [Test]
+        public void TestXmlWithExtensionEnum()
+        {
+            TestXmlMessage message = TestXmlMessage.CreateBuilder()
+                .SetExtension(UnitTestXmlSerializerTestProtoFile.ExtensionEnum, EnumOptions.ONE)
+                .Build();
+
+            StringWriter sw = new StringWriter();
+            new XmlFormatWriter(sw).WriteMessage("root", message);
+
+            string xml = sw.ToString();
+
+            ExtensionRegistry registry = ExtensionRegistry.CreateInstance();
+            UnitTestXmlSerializerTestProtoFile.RegisterAllExtensions(registry);
+
+            XmlFormatReader rdr = new XmlFormatReader(xml);
+            TestXmlMessage copy = rdr.Merge(TestXmlMessage.CreateBuilder(), registry).Build();
+            Assert.AreEqual(message, copy);
+        }
+    }
+}

+ 30 - 8
src/ProtocolBuffers/CodedOutputStream.cs

@@ -127,6 +127,36 @@ namespace Google.ProtocolBuffers
 
         #endregion
 
+        #region Writing of unknown fields
+
+        [Obsolete]
+        public void WriteUnknownGroup(int fieldNumber, IMessageLite value)
+        {
+            WriteTag(fieldNumber, WireFormat.WireType.StartGroup);
+            value.WriteTo(this);
+            WriteTag(fieldNumber, WireFormat.WireType.EndGroup);
+        }
+
+        public void WriteUnknownBytes(int fieldNumber, ByteString value)
+        {
+            WriteBytes(fieldNumber, null /*not used*/, value);
+        }
+
+        [CLSCompliant(false)]
+        public void WriteUnknownField(int fieldNumber, WireFormat.WireType wireType, ulong value)
+        {
+            if(wireType == WireFormat.WireType.Varint)
+                WriteUInt64(fieldNumber, null /*not used*/, value);
+            else if (wireType == WireFormat.WireType.Fixed32)
+                WriteFixed32(fieldNumber, null /*not used*/, (uint)value);
+            else if (wireType == WireFormat.WireType.Fixed64)
+                WriteFixed64(fieldNumber, null /*not used*/, value);
+            else
+                throw InvalidProtocolBufferException.InvalidWireType();
+        }
+
+        #endregion
+
         #region Writing of tags and fields
 
         public void WriteField(FieldType fieldType, int fieldNumber, string fieldName, object value)
@@ -308,14 +338,6 @@ namespace Google.ProtocolBuffers
             WriteTag(fieldNumber, WireFormat.WireType.EndGroup);
         }
 
-        [Obsolete]
-        public void WriteUnknownGroup(int fieldNumber, string fieldName, IMessageLite value)
-        {
-            WriteTag(fieldNumber, WireFormat.WireType.StartGroup);
-            value.WriteTo(this);
-            WriteTag(fieldNumber, WireFormat.WireType.EndGroup);
-        }
-
         public void WriteMessage(int fieldNumber, string fieldName, IMessageLite value)
         {
             WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited);

+ 5 - 1
src/ProtocolBuffers/ICodedOutputStream.cs

@@ -12,7 +12,11 @@ namespace Google.ProtocolBuffers
         void Flush();
 
         [Obsolete]
-        void WriteUnknownGroup(int fieldNumber, string fieldName, IMessageLite value);
+        void WriteUnknownGroup(int fieldNumber, IMessageLite value);
+        void WriteUnknownBytes(int fieldNumber, ByteString value);
+        [CLSCompliant(false)]
+        void WriteUnknownField(int fieldNumber, WireFormat.WireType wireType, ulong value);
+
         void WriteMessageSetExtension(int fieldNumber, string fieldName, IMessageLite value);
         void WriteMessageSetExtension(int fieldNumber, string fieldName, ByteString value);
 

+ 8 - 0
src/ProtocolBuffers/ProtocolBuffers.csproj

@@ -180,6 +180,14 @@
     <Compile Include="NameHelpers.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RpcUtil.cs" />
+    <Compile Include="Serialization\AbstractReader.cs" />
+    <Compile Include="Serialization\AbstractTextReader.cs" />
+    <Compile Include="Serialization\AbstractTextWriter.cs" />
+    <Compile Include="Serialization\AbstractWriter.cs" />
+    <Compile Include="Serialization\XmlFormatReader.cs" />
+    <Compile Include="Serialization\XmlFormatWriter.cs" />
+    <Compile Include="Serialization\XmlReaderOptions.cs" />
+    <Compile Include="Serialization\XmlWriterOptions.cs" />
     <Compile Include="SilverlightCompatibility.cs" />
     <Compile Include="SortedList.cs" />
     <Compile Include="TextFormat.cs" />

+ 508 - 0
src/ProtocolBuffers/Serialization/AbstractReader.cs

@@ -0,0 +1,508 @@
+using System;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Descriptors;
+
+//Disable CS3011: only CLS-compliant members can be abstract
+#pragma warning disable 3011
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Provides a base-class that provides some basic functionality for handling type dispatching
+    /// </summary>
+    public abstract class AbstractReader : ICodedInputStream
+    {
+        /// <summary>
+        /// Merges the contents of stream into the provided message builder
+        /// </summary>
+        public TBuilder Merge<TBuilder>(TBuilder builder) where TBuilder : IBuilderLite
+        { return Merge(builder, ExtensionRegistry.Empty); }
+
+        /// <summary>
+        /// Merges the contents of stream into the provided message builder
+        /// </summary>
+        public abstract TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry) where TBuilder : IBuilderLite;
+
+        /// <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 abstract bool PeekNext(out string field);
+
+        /// <summary>
+        /// Causes the reader to skip past this field
+        /// </summary>
+        protected abstract void Skip();
+
+        /// <summary>
+        /// Returns true if it was able to read a Boolean from the input
+        /// </summary>
+        protected abstract bool Read(ref bool value);
+
+        /// <summary>
+        /// Returns true if it was able to read a Int32 from the input
+        /// </summary>
+        protected abstract bool Read(ref int value);
+
+        /// <summary>
+        /// Returns true if it was able to read a UInt32 from the input
+        /// </summary>
+        [CLSCompliant(false)]
+        protected abstract bool Read(ref uint value);
+
+        /// <summary>
+        /// Returns true if it was able to read a Int64 from the input
+        /// </summary>
+        protected abstract bool Read(ref long value);
+
+        /// <summary>
+        /// Returns true if it was able to read a UInt64 from the input
+        /// </summary>
+        [CLSCompliant(false)]
+        protected abstract bool Read(ref ulong value);
+
+        /// <summary>
+        /// Returns true if it was able to read a Single from the input
+        /// </summary>
+        protected abstract bool Read(ref float value);
+
+        /// <summary>
+        /// Returns true if it was able to read a Double from the input
+        /// </summary>
+        protected abstract bool Read(ref double value);
+
+        /// <summary>
+        /// Returns true if it was able to read a String from the input
+        /// </summary>
+        protected abstract bool Read(ref string value);
+
+        /// <summary>
+        /// Returns true if it was able to read a ByteString from the input
+        /// </summary>
+        protected abstract bool Read(ref ByteString value);
+
+        /// <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 abstract bool ReadEnum(ref object value);
+
+        /// <summary>
+        /// Merges the input stream into the provided IBuilderLite 
+        /// </summary>
+        protected abstract bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry);
+
+        /// <summary>
+        /// Merges the input stream into the provided IBuilderLite 
+        /// </summary>
+        public virtual bool ReadGroup(IBuilderLite value, ExtensionRegistry registry)
+        {
+            return ReadMessage(value, registry);
+        }
+
+        /// <summary>
+        /// Cursors through the array elements and stops at the end of the array
+        /// </summary>
+        protected virtual IEnumerable<string> ForeachArrayItem(string field)
+        {
+            string next = field;
+            while (true)
+            {
+                yield return next;
+
+                if (!PeekNext(out next) || next != field)
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Reads an array of T messages
+        /// </summary>
+        public virtual bool ReadMessageArray<T>(string field, ICollection<T> items, IMessageLite messageType, ExtensionRegistry registry)
+        {
+            bool success = false;
+            foreach (string next in ForeachArrayItem(field))
+            {
+                IBuilderLite builder = messageType.WeakCreateBuilderForType();
+                if (ReadMessage(builder, registry))
+                {
+                    items.Add((T)builder.WeakBuild());
+                    success |= true;
+                }
+            }
+            return success;
+        }
+
+        /// <summary>
+        /// Reads an array of T messages as a proto-buffer group
+        /// </summary>
+        public virtual bool ReadGroupArray<T>(string field, ICollection<T> items, IMessageLite messageType, ExtensionRegistry registry)
+        {
+            bool success = false;
+            foreach (string next in ForeachArrayItem(field))
+            {
+                IBuilderLite builder = messageType.WeakCreateBuilderForType();
+                if (ReadGroup(builder, registry))
+                {
+                    items.Add((T)builder.WeakBuild());
+                    success |= true;
+                }
+            }
+            return success;
+        }
+
+        /// <summary>
+        /// Reads an array of System.Enum type T and adds them to the collection
+        /// </summary>
+        public virtual bool ReadEnumArray(string field, ICollection<object> items)
+        {
+            bool success = false;
+            foreach (string next in ForeachArrayItem(field))
+            {
+                object temp = null;
+                if (ReadEnum(ref temp))
+                {
+                    items.Add(temp);
+                    success |= true;
+                }
+            }
+            return success;
+        }
+
+        /// <summary>
+        /// Reads an array of T, where T is a primitive type defined by FieldType
+        /// </summary>
+        public virtual bool ReadArray<T>(FieldType type, string field, ICollection<T> items)
+        {
+            bool success = false;
+            foreach (string next in ForeachArrayItem(field))
+            {
+                object temp = null;
+                if (ReadField(type, ref temp))
+                {
+                    items.Add((T)temp);
+                    success |= true;
+                }
+            }
+            return success;
+        }
+
+        /// <summary>
+        /// returns true if it was able to read a single primitive value of FieldType into the value reference
+        /// </summary>
+        public virtual bool ReadField(FieldType type, ref object value)
+        {
+            switch (type)
+            {
+                case FieldType.Bool:
+                    {
+                        bool temp = false;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.Int64:
+                case FieldType.SInt64:
+                case FieldType.SFixed64:
+                    {
+                        long temp = 0;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.UInt64:
+                case FieldType.Fixed64:
+                    {
+                        ulong temp = 0;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.Int32:
+                case FieldType.SInt32:
+                case FieldType.SFixed32:
+                    {
+                        int temp = 0;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.UInt32:
+                case FieldType.Fixed32:
+                    {
+                        uint temp = 0;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.Float:
+                    {
+                        float temp = float.NaN;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.Double:
+                    {
+                        double temp = float.NaN;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.String:
+                    {
+                        string temp = null;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                case FieldType.Bytes:
+                    {
+                        ByteString temp = null;
+                        if (Read(ref temp))
+                            value = temp;
+                        else
+                            return false;
+                        break;
+                    }
+                default:
+                    throw InvalidProtocolBufferException.InvalidTag();
+            }
+            return true;
+        }
+
+        #region ICodedInputStream Members
+
+        bool ICodedInputStream.ReadTag(out uint fieldTag, out string fieldName)
+        {
+            fieldTag = 0;
+            if (PeekNext(out fieldName))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        bool ICodedInputStream.ReadDouble(ref double value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadFloat(ref float value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadUInt64(ref ulong value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadInt64(ref long value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadInt32(ref int value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadFixed64(ref ulong value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadFixed32(ref uint value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadBool(ref bool value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadString(ref string value)
+        { return Read(ref value); }
+
+        void ICodedInputStream.ReadGroup(int fieldNumber, IBuilderLite builder, ExtensionRegistry extensionRegistry)
+        { ReadGroup(builder, extensionRegistry); }
+
+        void ICodedInputStream.ReadUnknownGroup(int fieldNumber, IBuilderLite builder)
+        { throw new NotSupportedException(); }
+
+        void ICodedInputStream.ReadMessage(IBuilderLite builder, ExtensionRegistry extensionRegistry)
+        { ReadMessage(builder, extensionRegistry); }
+
+        bool ICodedInputStream.ReadBytes(ref ByteString value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadUInt32(ref uint value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadEnum(ref IEnumLite value, out object unknown, IEnumLiteMap mapping)
+        {
+            value = null;
+            unknown = null;
+            if(ReadEnum(ref unknown))
+            {
+                if (unknown is int) value = mapping.FindValueByNumber((int)unknown);
+                else if (unknown is string) value = mapping.FindValueByName((string)unknown);
+                return value != null;
+            }
+            return false;
+        }
+
+        bool ICodedInputStream.ReadEnum<T>(ref T value, out object rawValue)
+        {
+            rawValue = null;
+            if (ReadEnum(ref rawValue))
+            {
+                if (Enum.IsDefined(typeof(T), rawValue))
+                {
+                    if (rawValue is int)
+                        value = (T)rawValue;
+                    else if (rawValue is string)
+                        value = (T)Enum.Parse(typeof(T), (string)rawValue, false);
+                    else
+                    {
+                        value = default(T);
+                        return false;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        bool ICodedInputStream.ReadSFixed32(ref int value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadSFixed64(ref long value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadSInt32(ref int value)
+        { return Read(ref value); }
+
+        bool ICodedInputStream.ReadSInt64(ref long value)
+        { return Read(ref value); }
+
+        void ICodedInputStream.ReadPrimitiveArray(FieldType fieldType, uint fieldTag, string fieldName, ICollection<object> list)
+        { ReadArray(fieldType, fieldName, list); }
+
+        void ICodedInputStream.ReadEnumArray(uint fieldTag, string fieldName, ICollection<IEnumLite> list, out ICollection<object> unknown, IEnumLiteMap mapping)
+        {
+            unknown = null;
+            List<object> array = new List<object>();
+            if (ReadEnumArray(fieldName, array))
+            {
+                foreach (object rawValue in array)
+                {
+                    IEnumLite item = null;
+                    if (rawValue is int) item = mapping.FindValueByNumber((int)rawValue);
+                    else if (rawValue is string) item = mapping.FindValueByName((string)rawValue);
+
+                    if (item != null)
+                        list.Add(item);
+                    else
+                    {
+                        if (unknown == null) unknown = new List<object>();
+                        unknown.Add(rawValue);
+                    }
+                }
+            }
+        }
+
+        void ICodedInputStream.ReadEnumArray<T>(uint fieldTag, string fieldName, ICollection<T> list, out ICollection<object> unknown)
+        {
+            unknown = null;
+            List<object> array = new List<object>();
+            if (ReadEnumArray(fieldName, array))
+            {
+                foreach (object rawValue in array)
+                {
+                    if (rawValue is int)
+                        list.Add((T)rawValue);
+                    else if (rawValue is string)
+                        list.Add((T)Enum.Parse(typeof(T), (string)rawValue, false));
+                    else
+                    {
+                        if (unknown == null) unknown = new List<object>();
+                        unknown.Add(rawValue);
+                    }
+                }
+            }
+        }
+
+        void ICodedInputStream.ReadMessageArray<T>(uint fieldTag, string fieldName, ICollection<T> list, T messageType, ExtensionRegistry registry)
+        { ReadMessageArray(fieldName, list, messageType, registry); }
+
+        void ICodedInputStream.ReadGroupArray<T>(uint fieldTag, string fieldName, ICollection<T> list, T messageType, ExtensionRegistry registry)
+        { ReadGroupArray(fieldName, list, messageType, registry); }
+
+        bool ICodedInputStream.ReadPrimitiveField(FieldType fieldType, ref object value)
+        { return ReadField(fieldType, ref value); }
+
+        bool ICodedInputStream.IsAtEnd
+        {
+            get { string next; return PeekNext(out next) == false; }
+        }
+
+        bool ICodedInputStream.SkipField()
+        {
+            Skip();
+            return true;
+        }
+
+        void ICodedInputStream.ReadStringArray(uint fieldTag, string fieldName, ICollection<string> list)
+        { ReadArray(FieldType.String, fieldName, list); }
+
+        void ICodedInputStream.ReadBytesArray(uint fieldTag, string fieldName, ICollection<ByteString> list)
+        { ReadArray(FieldType.Bytes, fieldName, list); }
+
+        void ICodedInputStream.ReadBoolArray(uint fieldTag, string fieldName, ICollection<bool> list)
+        { ReadArray(FieldType.Bool, fieldName, list); }
+
+        void ICodedInputStream.ReadInt32Array(uint fieldTag, string fieldName, ICollection<int> list)
+        { ReadArray(FieldType.Int32, fieldName, list); }
+
+        void ICodedInputStream.ReadSInt32Array(uint fieldTag, string fieldName, ICollection<int> list)
+        { ReadArray(FieldType.SInt32, fieldName, list); }
+
+        void ICodedInputStream.ReadUInt32Array(uint fieldTag, string fieldName, ICollection<uint> list)
+        { ReadArray(FieldType.UInt32, fieldName, list); }
+
+        void ICodedInputStream.ReadFixed32Array(uint fieldTag, string fieldName, ICollection<uint> list)
+        { ReadArray(FieldType.Fixed32, fieldName, list); }
+
+        void ICodedInputStream.ReadSFixed32Array(uint fieldTag, string fieldName, ICollection<int> list)
+        { ReadArray(FieldType.SFixed32, fieldName, list); }
+
+        void ICodedInputStream.ReadInt64Array(uint fieldTag, string fieldName, ICollection<long> list)
+        { ReadArray(FieldType.Int64, fieldName, list); }
+
+        void ICodedInputStream.ReadSInt64Array(uint fieldTag, string fieldName, ICollection<long> list)
+        { ReadArray(FieldType.SInt64, fieldName, list); }
+
+        void ICodedInputStream.ReadUInt64Array(uint fieldTag, string fieldName, ICollection<ulong> list)
+        { ReadArray(FieldType.UInt64, fieldName, list); }
+
+        void ICodedInputStream.ReadFixed64Array(uint fieldTag, string fieldName, ICollection<ulong> list)
+        { ReadArray(FieldType.Fixed64, fieldName, list); }
+
+        void ICodedInputStream.ReadSFixed64Array(uint fieldTag, string fieldName, ICollection<long> list)
+        { ReadArray(FieldType.SFixed64, fieldName, list); }
+
+        void ICodedInputStream.ReadDoubleArray(uint fieldTag, string fieldName, ICollection<double> list)
+        { ReadArray(FieldType.Double, fieldName, list); }
+
+        void ICodedInputStream.ReadFloatArray(uint fieldTag, string fieldName, ICollection<float> list)
+        { ReadArray(FieldType.Float, fieldName, list); }
+
+        #endregion
+    }
+}

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

@@ -0,0 +1,171 @@
+using System;
+using System.Globalization;
+using System.Xml;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Provides a base class for text-parsing readers
+    /// </summary>
+    public abstract class AbstractTextReader : AbstractReader
+    {
+        /// <summary>
+        /// Reads a typed field as a string
+        /// </summary>
+        protected abstract bool ReadAsText(ref string textValue, Type type);
+
+        /// <summary>
+        /// Returns true if it was able to read a String from the input
+        /// </summary>
+        protected override bool Read(ref string value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(string)))
+            {
+                value = text;
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a Boolean from the input
+        /// </summary>
+        protected override bool Read(ref bool value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(bool)))
+            {
+                value = XmlConvert.ToBoolean(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a Int32 from the input
+        /// </summary>
+        protected override bool Read(ref int value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(int)))
+            {
+                value = XmlConvert.ToInt32(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a UInt32 from the input
+        /// </summary>
+        [CLSCompliant(false)]
+        protected override bool Read(ref uint value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(uint)))
+            {
+                value = XmlConvert.ToUInt32(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a Int64 from the input
+        /// </summary>
+        protected override bool Read(ref long value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(long)))
+            {
+                value = XmlConvert.ToInt64(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a UInt64 from the input
+        /// </summary>
+        [CLSCompliant(false)]
+        protected override bool Read(ref ulong value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(ulong)))
+            {
+                value = XmlConvert.ToUInt64(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a Single from the input
+        /// </summary>
+        protected override bool Read(ref float value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(float)))
+            {
+                value = XmlConvert.ToSingle(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if it was able to read a Double from the input
+        /// </summary>
+        protected override bool Read(ref double value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(double)))
+            {
+                value = XmlConvert.ToDouble(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Provides decoding of bytes read from the input stream
+        /// </summary>
+        protected virtual ByteString DecodeBytes(string bytes) { return ByteString.FromBase64(bytes); }
+
+        /// <summary>
+        /// Returns true if it was able to read a ByteString from the input
+        /// </summary>
+        protected override bool Read(ref ByteString value)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(ByteString)))
+            {
+                value = DecodeBytes(text);
+                return true;
+            }
+            return false;
+        }
+
+        /// <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)
+        {
+            string text = null;
+            if (ReadAsText(ref text, typeof(Enum)))
+            {
+                int number;
+                if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number))
+                {
+                    value = number;
+                    return true;
+                }
+                value = text;
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 73 - 0
src/ProtocolBuffers/Serialization/AbstractTextWriter.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Xml;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Provides a base class for text writers
+    /// </summary>
+    public abstract class AbstractTextWriter : AbstractWriter
+    {
+        /// <summary>
+        /// Encodes raw bytes to be written to the stream
+        /// </summary>
+        protected virtual string EncodeBytes(ByteString bytes) { return bytes.ToBase64(); }
+
+        /// <summary>
+        /// Writes a typed field as a text value
+        /// </summary>
+        protected abstract void WriteAsText(string field, string textValue, object typedValue);
+
+        /// <summary>
+        /// Writes a String value
+        /// </summary>
+        protected override void Write(string field, string value) { WriteAsText(field, value, value); }
+
+        /// <summary>
+        /// Writes a Boolean value
+        /// </summary>
+        protected override void Write(string field, bool value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a Int32 value
+        /// </summary>
+        protected override void Write(string field, int value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a UInt32 value
+        /// </summary>
+        [CLSCompliant(false)]
+        protected override void Write(string field, uint value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a Int64 value
+        /// </summary>
+        protected override void Write(string field, long value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a UInt64 value
+        /// </summary>
+        [CLSCompliant(false)]
+        protected override void Write(string field, ulong value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a Single value
+        /// </summary>
+        protected override void Write(string field, float value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a Double value
+        /// </summary>
+        protected override void Write(string field, double value) { WriteAsText(field, XmlConvert.ToString(value), value); }
+
+        /// <summary>
+        /// Writes a set of bytes
+        /// </summary>
+        protected override void Write(string field, ByteString value) { WriteAsText(field, EncodeBytes(value), value); }
+
+        /// <summary>
+        /// Writes a System.Enum by the numeric and textual value
+        /// </summary>
+        protected override void WriteEnum(string field, int number, string name) { WriteAsText(field, name, number); }
+    }
+}

+ 345 - 0
src/ProtocolBuffers/Serialization/AbstractWriter.cs

@@ -0,0 +1,345 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using Google.ProtocolBuffers.Descriptors;
+
+//Disable CS3011: only CLS-compliant members can be abstract
+#pragma warning disable 3011
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Provides a base class for writers that performs some basic type dispatching
+    /// </summary>
+    public abstract class AbstractWriter : ICodedOutputStream, IDisposable
+    {
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// </summary>
+        public void Dispose()
+        {
+            GC.SuppressFinalize(this);
+            Flush();
+            Dispose(true);
+        }
+
+        /// <summary>
+        /// Completes any pending write operations
+        /// </summary>
+        public virtual void Flush()
+        { }
+
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// </summary>
+        protected virtual void Dispose(bool disposing)
+        {
+        }
+
+        /// <summary>
+        /// Writes the message to the the formatted stream.
+        /// </summary>
+        public abstract void WriteMessage(IMessageLite message);
+
+        /// <summary>
+        /// Writes a message
+        /// </summary>
+        public abstract void WriteMessage(string field, IMessageLite message);
+
+
+
+        /// <summary>
+        /// Writes a Boolean value
+        /// </summary>
+        protected abstract void Write(string field, Boolean value);
+
+        /// <summary>
+        /// Writes a Int32 value
+        /// </summary>
+        protected abstract void Write(string field, Int32 value);
+
+        /// <summary>
+        /// Writes a UInt32 value
+        /// </summary>
+        [CLSCompliant(false)]
+        protected abstract void Write(string field, UInt32 value);
+
+        /// <summary>
+        /// Writes a Int64 value
+        /// </summary>
+        protected abstract void Write(string field, Int64 value);
+
+        /// <summary>
+        /// Writes a UInt64 value
+        /// </summary>
+        [CLSCompliant(false)]
+        protected abstract void Write(string field, UInt64 value);
+
+        /// <summary>
+        /// Writes a Single value
+        /// </summary>
+        protected abstract void Write(string field, Single value);
+
+        /// <summary>
+        /// Writes a Double value
+        /// </summary>
+        protected abstract void Write(string field, Double value);
+
+        /// <summary>
+        /// Writes a String value
+        /// </summary>
+        protected abstract void Write(string field, String value);
+
+        /// <summary>
+        /// Writes a set of bytes
+        /// </summary>
+        protected abstract void Write(string field, ByteString value);
+
+        /// <summary>
+        /// Writes a message or group as a field
+        /// </summary>
+        protected abstract void WriteMessageOrGroup(string field, IMessageLite message);
+
+        /// <summary>
+        /// Writes a System.Enum by the numeric and textual value
+        /// </summary>
+        protected abstract void WriteEnum(string field, int number, string name);
+
+        /// <summary>
+        /// Writes a field of the type determined by field.FieldType
+        /// </summary>
+        protected virtual void WriteField(FieldType fieldType, string field, object value)
+        {
+            switch (fieldType)
+            {
+                case FieldType.Bool: Write(field, (bool)value); break;
+                case FieldType.Int64:
+                case FieldType.SInt64:
+                case FieldType.SFixed64: Write(field, (long)value); break;
+                case FieldType.UInt64:
+                case FieldType.Fixed64: Write(field, (ulong)value); break;
+                case FieldType.Int32:
+                case FieldType.SInt32:
+                case FieldType.SFixed32: Write(field, (int)value); break;
+                case FieldType.UInt32:
+                case FieldType.Fixed32: Write(field, (uint)value); break;
+                case FieldType.Float: Write(field, (float)value); break;
+                case FieldType.Double: Write(field, (double)value); break;
+                case FieldType.String: Write(field, (string)value); break;
+                case FieldType.Bytes: Write(field, (ByteString)value); break;
+                case FieldType.Group: WriteMessageOrGroup(field, (IMessageLite)value); break;
+                case FieldType.Message: WriteMessageOrGroup(field, (IMessageLite)value); break;
+                case FieldType.Enum:
+                    {
+                        if (value is IEnumLite) WriteEnum(field, ((IEnumLite)value).Number, ((IEnumLite)value).Name);
+                        else if (value is IConvertible) WriteEnum(field, ((IConvertible)value).ToInt32(CultureInfo.InvariantCulture), ((IConvertible)value).ToString(CultureInfo.InvariantCulture));
+                        else throw new ArgumentException("Expected an Enum type for field " + field);
+                        break;
+                    }
+                default:
+                    throw InvalidProtocolBufferException.InvalidTag();
+            }
+        }
+
+        /// <summary>
+        /// Writes an array of field values
+        /// </summary>
+        protected virtual void WriteArray(FieldType fieldType, string field, IEnumerable items)
+        {
+            foreach (object obj in items)
+                WriteField(fieldType, field, obj);
+        }
+
+        /// <summary>
+        /// Writes a numeric unknown field of wire type: Fixed32, Fixed64, or Variant
+        /// </summary>
+        [CLSCompliant(false)]
+        protected virtual void WriteUnknown(WireFormat.WireType wireType, int fieldNumber, ulong value)
+        { }
+
+        /// <summary>
+        /// Writes an unknown field, Expect WireType of GroupStart or LengthPrefix
+        /// </summary>
+        [CLSCompliant(false)]
+        protected virtual void WriteUnknown(WireFormat.WireType wireType, int fieldNumber, ByteString value)
+        { }
+
+        #region ICodedOutputStream Members
+
+        void ICodedOutputStream.WriteUnknownGroup(int fieldNumber, IMessageLite value)
+        { }
+        void ICodedOutputStream.WriteUnknownBytes(int fieldNumber, ByteString value)
+        { }
+        void ICodedOutputStream.WriteUnknownField(int fieldNumber, WireFormat.WireType type, ulong value)
+        { }
+
+        void ICodedOutputStream.WriteMessageSetExtension(int fieldNumber, string fieldName, IMessageLite value)
+        { }
+
+        void ICodedOutputStream.WriteMessageSetExtension(int fieldNumber, string fieldName, ByteString value)
+        { }
+
+        void ICodedOutputStream.WriteField(FieldType fieldType, int fieldNumber, string fieldName, object value)
+        { WriteField(fieldType, fieldName, value); }
+
+        void ICodedOutputStream.WriteDouble(int fieldNumber, string fieldName, double value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteFloat(int fieldNumber, string fieldName, float value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteUInt64(int fieldNumber, string fieldName, ulong value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteInt64(int fieldNumber, string fieldName, long value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteInt32(int fieldNumber, string fieldName, int value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteFixed64(int fieldNumber, string fieldName, ulong value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteFixed32(int fieldNumber, string fieldName, uint value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteBool(int fieldNumber, string fieldName, bool value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteString(int fieldNumber, string fieldName, string value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteGroup(int fieldNumber, string fieldName, IMessageLite value)
+        { WriteMessageOrGroup(fieldName, value); }
+
+        void ICodedOutputStream.WriteMessage(int fieldNumber, string fieldName, IMessageLite value)
+        { WriteMessageOrGroup(fieldName, value); }
+
+        void ICodedOutputStream.WriteBytes(int fieldNumber, string fieldName, ByteString value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteUInt32(int fieldNumber, string fieldName, uint value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteEnum(int fieldNumber, string fieldName, int value, object rawValue)
+        { WriteEnum(fieldName, value, rawValue.ToString()); }
+
+        void ICodedOutputStream.WriteSFixed32(int fieldNumber, string fieldName, int value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteSFixed64(int fieldNumber, string fieldName, long value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteSInt32(int fieldNumber, string fieldName, int value)
+        { Write(fieldName, value); }
+
+        void ICodedOutputStream.WriteSInt64(int fieldNumber, string fieldName, long value)
+        { Write(fieldName, value); }
+
+
+        void ICodedOutputStream.WriteArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list)
+        { WriteArray(fieldType, fieldName, list); }
+
+        void ICodedOutputStream.WriteGroupArray<T>(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<T> list)
+        { WriteArray(FieldType.Group, fieldName, list); }
+
+        void ICodedOutputStream.WriteMessageArray<T>(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<T> list)
+        { WriteArray(FieldType.Message, fieldName, list); }
+
+        void ICodedOutputStream.WriteStringArray(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<string> list)
+        { WriteArray(FieldType.String, fieldName, list); }
+
+        void ICodedOutputStream.WriteBytesArray(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<ByteString> list)
+        { WriteArray(FieldType.Bytes, fieldName, list); }
+
+        void ICodedOutputStream.WriteBoolArray(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<bool> list)
+        { WriteArray(FieldType.Bool, fieldName, list); }
+
+        void ICodedOutputStream.WriteInt32Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.Int32, fieldName, list); }
+
+        void ICodedOutputStream.WriteSInt32Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.SInt32, fieldName, list); }
+
+        void ICodedOutputStream.WriteUInt32Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<uint> list)
+        { WriteArray(FieldType.UInt32, fieldName, list); }
+
+        void ICodedOutputStream.WriteFixed32Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<uint> list)
+        { WriteArray(FieldType.Fixed32, fieldName, list); }
+
+        void ICodedOutputStream.WriteSFixed32Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.SFixed32, fieldName, list); }
+
+        void ICodedOutputStream.WriteInt64Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.Int64, fieldName, list); }
+
+        void ICodedOutputStream.WriteSInt64Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.SInt64, fieldName, list); }
+
+        void ICodedOutputStream.WriteUInt64Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<ulong> list)
+        { WriteArray(FieldType.UInt64, fieldName, list); }
+
+        void ICodedOutputStream.WriteFixed64Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<ulong> list)
+        { WriteArray(FieldType.Fixed64, fieldName, list); }
+
+        void ICodedOutputStream.WriteSFixed64Array(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.SFixed64, fieldName, list); }
+
+        void ICodedOutputStream.WriteDoubleArray(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<double> list)
+        { WriteArray(FieldType.Double, fieldName, list); }
+
+        void ICodedOutputStream.WriteFloatArray(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<float> list)
+        { WriteArray(FieldType.Float, fieldName, list); }
+
+        void ICodedOutputStream.WriteEnumArray<T>(int fieldNumber, string fieldName, System.Collections.Generic.IEnumerable<T> list)
+        { WriteArray(FieldType.Enum, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list)
+        { WriteArray(fieldType, fieldName, list); }
+
+
+        void ICodedOutputStream.WritePackedBoolArray(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<bool> list)
+        { WriteArray(FieldType.Bool, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedInt32Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.Int32, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedSInt32Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.SInt32, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedUInt32Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<uint> list)
+        { WriteArray(FieldType.UInt32, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedFixed32Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<uint> list)
+        { WriteArray(FieldType.Fixed32, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedSFixed32Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<int> list)
+        { WriteArray(FieldType.SFixed32, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedInt64Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.Int64, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedSInt64Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.SInt64, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedUInt64Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<ulong> list)
+        { WriteArray(FieldType.UInt64, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedFixed64Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<ulong> list)
+        { WriteArray(FieldType.Fixed64, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedSFixed64Array(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<long> list)
+        { WriteArray(FieldType.SFixed64, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedDoubleArray(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<double> list)
+        { WriteArray(FieldType.Double, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedFloatArray(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<float> list)
+        { WriteArray(FieldType.Float, fieldName, list); }
+
+        void ICodedOutputStream.WritePackedEnumArray<T>(int fieldNumber, string fieldName, int computedSize, System.Collections.Generic.IEnumerable<T> list)
+        { WriteArray(FieldType.Enum, fieldName, list); }
+
+        #endregion
+    }
+}

+ 222 - 0
src/ProtocolBuffers/Serialization/XmlFormatReader.cs

@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using Google.ProtocolBuffers.Descriptors;
+
+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;
+        private string _rootElementName;
+
+        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 XmlFormatReader(Stream input) : this(XmlReader.Create(input, DefaultSettings)) { }
+        /// <summary>
+        /// Constructs the XmlFormatReader using the string provided as the xml to be read
+        /// </summary>
+        public XmlFormatReader(String input) : this(XmlReader.Create(new StringReader(input))) { }
+        /// <summary>
+        /// Constructs the XmlFormatReader using the xml in the TextReader
+        /// </summary>
+        public XmlFormatReader(TextReader input) : this(XmlReader.Create(input)) { }
+        /// <summary>
+        /// Constructs the XmlFormatReader with the XmlReader
+        /// </summary>
+        public XmlFormatReader(XmlReader input) : this(input, XmlReaderOptions.None) { }
+        /// <summary>
+        /// Constructs the XmlFormatReader with the XmlReader and options
+        /// </summary>
+        public XmlFormatReader(XmlReader input, XmlReaderOptions options)
+        {
+            _input = input;
+            _rootElementName = DefaultRootElementName;
+            Options = options;
+        }
+        
+        /// <summary>
+        /// Gets or sets the options to use when reading the xml
+        /// </summary>
+        public XmlReaderOptions Options { get; set; }
+
+        /// <summary>
+        /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
+        /// </summary>
+        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();
+        }
+
+        /// <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
+        {
+            string field;
+            Assert(PeekNext(out field) && field == element);
+            ReadMessage(builder, registry);
+            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)
+        {
+            NextElement();
+            if(_input.IsStartElement())
+            {
+                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")) && int.TryParse(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());
+
+            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<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;
+                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;
+        }
+    }
+}

+ 166 - 0
src/ProtocolBuffers/Serialization/XmlFormatWriter.cs

@@ -0,0 +1,166 @@
+using System;
+using System.IO;
+using System.Xml;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Writes a proto buffer to an XML document or fragment.  .NET 3.5 users may also
+    /// use this class to produce Json by setting the options to support Json and providing
+    /// an XmlWriter obtained from <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory"/>.
+    /// </summary>
+    public class XmlFormatWriter : AbstractTextWriter
+    {
+        public const string DefaultRootElementName = "root";
+        private const int NestedArrayFlag = 0x0001;
+        private readonly XmlWriter _output;
+        private string _rootElementName;
+
+        static XmlWriterSettings DefaultSettings
+        {
+            get { return new XmlWriterSettings() {CheckCharacters = false, NewLineHandling = NewLineHandling.Entitize}; }
+        }
+
+        /// <summary>
+        /// Constructs the XmlFormatWriter to write to the given TextWriter
+        /// </summary>
+        public XmlFormatWriter(TextWriter output) : this(XmlWriter.Create(output, DefaultSettings)) { }
+        /// <summary>
+        /// Constructs the XmlFormatWriter to write to the given stream
+        /// </summary>
+        public XmlFormatWriter(Stream output) : this(XmlWriter.Create(output, DefaultSettings)) { }
+        /// <summary>
+        /// Constructs the XmlFormatWriter to write to the given XmlWriter
+        /// </summary>
+        public XmlFormatWriter(XmlWriter output)
+        {
+            _output = output;
+            _rootElementName = DefaultRootElementName;
+        }
+
+        /// <summary>
+        /// Closes the underlying XmlTextWriter
+        /// </summary>
+        protected override void Dispose(bool disposing)
+        {
+            if(disposing)
+                _output.Close();
+        }
+
+        /// <summary>
+        /// Gets or sets the default element name to use when using the Merge&lt;TBuilder>()
+        /// </summary>
+        public string RootElementName
+        {
+            get { return _rootElementName; }
+            set { ThrowHelper.ThrowIfNull(value, "RootElementName"); _rootElementName = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the options to use while generating the XML
+        /// </summary>
+        public XmlWriterOptions Options { get; set; }
+
+        private bool TestOption(XmlWriterOptions option) { return (Options & option) != 0; }
+
+        /// <summary>
+        /// Writes a message as an element using the name defined in <see cref="RootElementName"/>
+        /// </summary>
+        public override void WriteMessage(IMessageLite message)
+        { WriteMessage(_rootElementName, message); }
+
+        /// <summary>
+        /// Writes a message as an element with the given name
+        /// </summary>
+        public override void WriteMessage(string elementName, IMessageLite message)
+        {
+            if (TestOption(XmlWriterOptions.OutputJsonTypes))
+            {
+                _output.WriteStartElement("root"); // json requires this is the root-element
+                _output.WriteAttributeString("type", "object");
+            }
+            else
+                _output.WriteStartElement(elementName);
+
+            message.WriteTo(this);
+            _output.WriteEndElement();
+            _output.Flush();
+        }
+
+        /// <summary>
+        /// Writes a message
+        /// </summary>
+        protected override void WriteMessageOrGroup(string field, IMessageLite message)
+        {
+            _output.WriteStartElement(field);
+
+            if (TestOption(XmlWriterOptions.OutputJsonTypes))
+                _output.WriteAttributeString("type", "object");
+
+            message.WriteTo(this);
+            _output.WriteEndElement();
+        }
+
+        /// <summary>
+        /// Writes a String value
+        /// </summary>
+        protected override void WriteAsText(string field, string textValue, object typedValue)
+        {
+            _output.WriteStartElement(field);
+
+            if (TestOption(XmlWriterOptions.OutputJsonTypes))
+            {
+                if (typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float)
+                    _output.WriteAttributeString("type", "number");
+                else if (typedValue is bool)
+                    _output.WriteAttributeString("type", "boolean");
+            }
+            _output.WriteString(textValue);
+
+            //Empty strings should not be written as empty elements '<item/>', rather as '<item></item>'
+            if (_output.WriteState == WriteState.Element)
+                _output.WriteRaw("");
+
+            _output.WriteEndElement();
+        }
+
+        /// <summary>
+        /// Writes an array of field values
+        /// </summary>
+        protected override void WriteArray(FieldType fieldType, string field, System.Collections.IEnumerable items)
+        {
+            //see if it's empty
+            System.Collections.IEnumerator eitems = items.GetEnumerator();
+            try { if (!eitems.MoveNext()) return; }
+            finally
+            { if (eitems is IDisposable) ((IDisposable) eitems).Dispose(); }
+
+            if (TestOption(XmlWriterOptions.OutputNestedArrays | XmlWriterOptions.OutputJsonTypes))
+            {
+                _output.WriteStartElement(field);
+                if (TestOption(XmlWriterOptions.OutputJsonTypes))
+                    _output.WriteAttributeString("type", "array");
+
+                base.WriteArray(fieldType, "item", items);
+                _output.WriteEndElement();
+            }
+            else
+                base.WriteArray(fieldType, field, items);
+        }
+
+        /// <summary>
+        /// Writes a System.Enum by the numeric and textual value
+        /// </summary>
+        protected override void WriteEnum(string field, int number, string name)
+        {
+            _output.WriteStartElement(field);
+
+            if (!TestOption(XmlWriterOptions.OutputJsonTypes) && TestOption(XmlWriterOptions.OutputEnumValues))
+                _output.WriteAttributeString("value", XmlConvert.ToString(number));
+
+            _output.WriteString(name);
+            _output.WriteEndElement();
+        }
+    }
+}

+ 16 - 0
src/ProtocolBuffers/Serialization/XmlReaderOptions.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Options available for the xml reader output
+    /// </summary>
+    [Flags]
+    public enum XmlReaderOptions
+    {
+        /// <summary> Simple xml formatting with no attributes </summary>
+        None,
+        /// <summary> Requires that arrays items are nested in an &lt;item> element </summary>
+        ReadNestedArrays = 1,
+    }
+}

+ 21 - 0
src/ProtocolBuffers/Serialization/XmlWriterOptions.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+    /// <summary>
+    /// Options available for the xml writer output
+    /// </summary>
+    [Flags]
+    public enum XmlWriterOptions
+    {
+        /// <summary> Simple xml formatting with no attributes </summary>
+        None,
+        /// <summary> Writes the 'value' attribute on all enumerations with the numeric identifier </summary>
+        OutputEnumValues = 0x1,
+        /// <summary> Embeds array items into child &lt;item> elements </summary>
+        OutputNestedArrays = 0x4,
+        /// <summary> Outputs the 'type' attribute for compatibility with the <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see> </summary>
+        /// <remarks> This option must, by nessessity, also enable NestedArrayItems </remarks>
+        OutputJsonTypes = 0x8,
+    }
+}

+ 5 - 5
src/ProtocolBuffers/UnknownField.cs

@@ -178,24 +178,24 @@ namespace Google.ProtocolBuffers
         {
             foreach (ulong value in varintList)
             {
-                output.WriteUInt64(fieldNumber, UnknownFieldName, value);
+                output.WriteUnknownField(fieldNumber, WireFormat.WireType.Varint, value);
             }
             foreach (uint value in fixed32List)
             {
-                output.WriteFixed32(fieldNumber, UnknownFieldName, value);
+                output.WriteUnknownField(fieldNumber, WireFormat.WireType.Fixed32, value);
             }
             foreach (ulong value in fixed64List)
             {
-                output.WriteFixed64(fieldNumber, UnknownFieldName, value);
+                output.WriteUnknownField(fieldNumber, WireFormat.WireType.Fixed64, value);
             }
             foreach (ByteString value in lengthDelimitedList)
             {
-                output.WriteBytes(fieldNumber, UnknownFieldName, value);
+                output.WriteUnknownBytes(fieldNumber, value);
             }
             foreach (UnknownFieldSet value in groupList)
             {
 #pragma warning disable 0612
-                output.WriteUnknownGroup(fieldNumber, UnknownFieldName, value);
+                output.WriteUnknownGroup(fieldNumber, value);
 #pragma warning restore 0612
             }
         }

+ 6 - 0
src/ProtocolBuffers/UnknownFieldSet.cs

@@ -446,6 +446,12 @@ namespace Google.ProtocolBuffers
             [CLSCompliant(false)]
             public bool MergeFieldFrom(uint tag, ICodedInputStream input)
             {
+                if (tag == 0)
+                {
+                    input.SkipField();
+                    return true;
+                }
+
                 int number = WireFormat.GetTagFieldNumber(tag);
                 switch (WireFormat.GetTagWireType(tag))
                 {