aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/ProtocolBuffers.Serialization/XmlFormatReader.cs
blob: a4f111d4d1bb82bdcde020eeb6f34879a3e1d750 (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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Diagnostics;

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;
        // Tracks the message element for each nested message read
        private readonly Stack<ElementStackEntry> _elements;
        // The default element name for ReadMessageStart
        private string _rootElementName;

        private struct ElementStackEntry
        {
            public readonly string LocalName;
            public readonly int Depth;
            public readonly bool IsEmpty;

            public ElementStackEntry(string localName, int depth, bool isEmpty) : this()
            {
                LocalName = localName;
                IsEmpty = isEmpty;
                Depth = depth;
            }
        }

        private 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;
            _elements = new Stack<ElementStackEntry>();
            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;
            }
        }

        [DebuggerNonUserCode]
        private static void Assert(bool cond)
        {
            if (!cond)
            {
                throw new FormatException();
            }
        }

        /// <summary>
        /// Reads the root-message preamble specific to this formatter
        /// </summary>
        public override void ReadMessageStart()
        {
            ReadMessageStart(_rootElementName);
        }

        /// <summary>
        /// Reads the root-message preamble specific to this formatter
        /// </summary>
        public void ReadMessageStart(string element)
        {
            while (!_input.IsStartElement() && _input.Read())
            {
                continue;
            }
            Assert(_input.IsStartElement() && _input.LocalName == element);
            _elements.Push(new ElementStackEntry(element, _input.Depth, _input.IsEmptyElement));
            _input.Read();
        }

        /// <summary>
        /// Reads the root-message close specific to this formatter, MUST be called
        /// on the reader obtained from ReadMessageStart(string element).
        /// </summary>
        public override void ReadMessageEnd()
        {
            Assert(_elements.Count > 0);

            ElementStackEntry stop = _elements.Peek();
            while (_input.NodeType != XmlNodeType.EndElement && _input.NodeType != XmlNodeType.Element
                   && _input.Depth > stop.Depth && _input.Read())
            {
                continue;
            }

            if (!stop.IsEmpty)
            {
                Assert(_input.NodeType == XmlNodeType.EndElement
                       && _input.LocalName == stop.LocalName
                       && _input.Depth == stop.Depth);

                _input.Read();
            }
            _elements.Pop();
        }

        /// <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
        {
            ReadMessageStart(element);
            builder.WeakMergeFrom(this, registry);
            ReadMessageEnd();
            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)
        {
            ElementStackEntry stopNode;
            if (_elements.Count == 0)
            {
                stopNode = new ElementStackEntry(null, _input.Depth - 1, false);
            }
            else
            {
                stopNode = _elements.Peek();
            }

            if (!stopNode.IsEmpty)
            {
                while (!_input.IsStartElement() && _input.Depth > stopNode.Depth && _input.Read())
                {
                    continue;
                }

                if (_input.IsStartElement() && _input.Depth > stopNode.Depth)
                {
                    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")) && FrameworkPortability.TryParseInt32(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());
            ReadMessageStart(_input.LocalName);
            builder.WeakMergeFrom(this, registry);
            ReadMessageEnd();
            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;
                }
            }
            else
            {
                string found;
                ReadMessageStart(field);
                if (PeekNext(out found) && found == "item")
                {
                    foreach (string item in NonNestedArrayItems("item"))
                    {
                        yield return item;
                    }
                }
                ReadMessageEnd();
            }
        }
    }
}