aboutsummaryrefslogtreecommitdiff
path: root/src/ProtocolBuffers/Serialization/XmlFormatReader.cs
blob: f3ca131491981a0102c631eee08a088c8a3b6957 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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 static XmlFormatReader CreateInstance(byte[] input) { return new XmlFormatReader(XmlReader.Create(new MemoryStream(input, false), DefaultSettings)); }
        /// <summary>
        /// Constructs the XmlFormatReader using the stream provided as the xml
        /// </summary>
        public static XmlFormatReader CreateInstance(Stream input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); }
        /// <summary>
        /// Constructs the XmlFormatReader using the string provided as the xml to be read
        /// </summary>
        public static XmlFormatReader CreateInstance(String input) { return new XmlFormatReader(XmlReader.Create(new StringReader(input), DefaultSettings)); }
        /// <summary>
        /// Constructs the XmlFormatReader using the xml in the TextReader
        /// </summary>
        public static XmlFormatReader CreateInstance(TextReader input) { return new XmlFormatReader(XmlReader.Create(input, DefaultSettings)); }
        /// <summary>
        /// Constructs the XmlFormatReader with the XmlReader
        /// </summary>
        public static XmlFormatReader CreateInstance(XmlReader input) { return new XmlFormatReader(input); }
        /// <summary>
        /// Constructs the XmlFormatReader with the XmlReader and options
        /// </summary>
        protected XmlFormatReader(XmlReader input)
        {
            _input = input;
            _rootElementName = DefaultRootElementName;
            Options = XmlReaderOptions.None;
        }
        
        /// <summary>
        /// Gets or sets the options to use when reading the xml
        /// </summary>
        public XmlReaderOptions Options { get; set; }
        /// <summary>
        /// Sets the options to use while generating the XML
        /// </summary>
        public XmlFormatReader SetOptions(XmlReaderOptions options) { Options = options; return this; }

        /// <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)
        {
            XmlFormatReader copy = new XmlFormatReader(rdr).SetOptions(Options);
            copy._rootElementName = _rootElementName;
            copy.Depth = Depth;
            return copy;

        }
        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;
        }
    }
}