Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / ReadContentAsBinaryHelper.cs
1
2 //------------------------------------------------------------------------------
3 // <copyright file="ReadContentAsBinaryHelper.cs" company="Microsoft">
4 //     Copyright (c) Microsoft Corporation.  All rights reserved.
5 // </copyright>
6 // <owner current="true" primary="true">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 using System.Diagnostics;
10
11 namespace System.Xml
12 {
13     internal partial class ReadContentAsBinaryHelper {
14
15 // Private enums
16         enum State {
17             None,
18             InReadContent,
19             InReadElementContent,
20         }
21
22 // Fields 
23         XmlReader   reader;
24         State       state;
25         int         valueOffset;
26         bool        isEnd;
27         
28         bool        canReadValueChunk;
29         char[]      valueChunk;
30         int         valueChunkLength;
31         
32         IncrementalReadDecoder decoder;
33         Base64Decoder          base64Decoder;
34         BinHexDecoder          binHexDecoder;
35
36 // Constants
37         const int ChunkSize = 256;
38
39 // Constructor
40         internal ReadContentAsBinaryHelper( XmlReader reader ) {
41             this.reader = reader;
42             this.canReadValueChunk = reader.CanReadValueChunk;
43             
44             if ( canReadValueChunk ) {
45                 valueChunk = new char[ChunkSize];
46             }
47         }
48
49 // Static methods 
50         internal static ReadContentAsBinaryHelper CreateOrReset( ReadContentAsBinaryHelper helper, XmlReader reader ) {
51             if ( helper == null ) {
52                 return new ReadContentAsBinaryHelper( reader );
53             }
54             else {
55                 helper.Reset();
56                 return helper;
57             }
58         }
59
60 // Internal methods 
61
62         internal  int  ReadContentAsBase64( byte[] buffer, int index, int count ) {
63             // check arguments
64             if ( buffer == null ) {
65                 throw new ArgumentNullException( "buffer" );
66             }
67             if ( count < 0 ) {
68                 throw new ArgumentOutOfRangeException( "count" );
69             }
70             if ( index < 0 ) {
71                 throw new ArgumentOutOfRangeException( "index" );
72             }
73             if ( buffer.Length - index < count ) {
74                 throw new ArgumentOutOfRangeException( "count" );
75             }
76
77             switch ( state ) {
78                 case State.None:
79                     if ( !reader.CanReadContentAs() ) {
80                         throw reader.CreateReadContentAsException( "ReadContentAsBase64" );
81                     }
82                     if ( !Init() ) {
83                         return 0;
84                     }
85                     break;
86                 case State.InReadContent:
87                     // if we have a correct decoder, go read
88                     if ( decoder == base64Decoder ) {
89                         // read more binary data
90                         return ReadContentAsBinary( buffer, index, count );
91                     }
92                     break;
93                 case State.InReadElementContent:
94                     throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
95                 default:
96                     Debug.Assert( false );
97                     return 0;
98             }
99
100             Debug.Assert( state == State.InReadContent );
101
102             // setup base64 decoder
103             InitBase64Decoder();
104
105             // read more binary data
106             return ReadContentAsBinary( buffer, index, count );
107         }
108
109         internal  int  ReadContentAsBinHex( byte[] buffer, int index, int count ) {
110             // check arguments
111             if ( buffer == null ) {
112                 throw new ArgumentNullException( "buffer" );
113             }
114             if ( count < 0 ) {
115                 throw new ArgumentOutOfRangeException( "count" );
116             }
117             if ( index < 0 ) {
118                 throw new ArgumentOutOfRangeException( "index" );
119             }
120             if ( buffer.Length - index < count ) {
121                 throw new ArgumentOutOfRangeException( "count" );
122             }
123
124             switch ( state ) {
125                 case State.None:
126                     if ( !reader.CanReadContentAs() ) {
127                         throw reader.CreateReadContentAsException( "ReadContentAsBinHex" );
128                     }
129                     if ( !Init() ) {
130                         return 0;
131                     }
132                     break;
133                 case State.InReadContent:
134                     // if we have a correct decoder, go read
135                     if ( decoder == binHexDecoder ) {
136                         // read more binary data
137                         return ReadContentAsBinary( buffer, index, count );
138                     }
139                     break;
140                 case State.InReadElementContent:
141                     throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
142                 default:
143                     Debug.Assert( false );
144                     return 0;
145             }    
146
147             Debug.Assert( state == State.InReadContent );
148
149             // setup binhex decoder
150             InitBinHexDecoder();
151
152             // read more binary data
153             return ReadContentAsBinary( buffer, index, count );
154         }
155
156         internal  int  ReadElementContentAsBase64( byte[] buffer, int index, int count ) {
157             // check arguments
158             if ( buffer == null ) {
159                 throw new ArgumentNullException( "buffer" );
160             }
161             if ( count < 0 ) {
162                 throw new ArgumentOutOfRangeException( "count" );
163             }
164             if ( index < 0 ) {
165                 throw new ArgumentOutOfRangeException( "index" );
166             }
167             if ( buffer.Length - index < count ) {
168                 throw new ArgumentOutOfRangeException( "count" );
169             }
170
171             switch ( state ) {
172                 case State.None:
173                     if ( reader.NodeType != XmlNodeType.Element ) {
174                         throw reader.CreateReadElementContentAsException( "ReadElementContentAsBase64" );
175                     }
176                     if ( !InitOnElement() ) {
177                         return 0;
178                     }
179                     break;
180                 case State.InReadContent:
181                     throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
182                 case State.InReadElementContent:
183                     // if we have a correct decoder, go read
184                     if ( decoder == base64Decoder ) {
185                         // read more binary data
186                         return ReadElementContentAsBinary( buffer, index, count );
187                     }
188                     break;
189                 default:
190                     Debug.Assert( false );
191                     return 0;
192             }    
193
194             Debug.Assert( state == State.InReadElementContent );
195
196             // setup base64 decoder
197             InitBase64Decoder();
198
199             // read more binary data
200             return ReadElementContentAsBinary( buffer, index, count );
201         }
202
203         internal  int  ReadElementContentAsBinHex( byte[] buffer, int index, int count ) {
204             // check arguments
205             if ( buffer == null ) {
206                 throw new ArgumentNullException( "buffer" );
207             }
208             if ( count < 0 ) {
209                 throw new ArgumentOutOfRangeException( "count" );
210             }
211             if ( index < 0 ) {
212                 throw new ArgumentOutOfRangeException( "index" );
213             }
214             if ( buffer.Length - index < count ) {
215                 throw new ArgumentOutOfRangeException( "count" );
216             }
217
218             switch ( state ) {
219                 case State.None:
220                     if ( reader.NodeType != XmlNodeType.Element ) {
221                         throw reader.CreateReadElementContentAsException( "ReadElementContentAsBinHex" );
222                     }
223                     if ( !InitOnElement() ) {
224                         return 0;
225                     }
226                     break;
227                 case State.InReadContent:
228                     throw new InvalidOperationException( Res.GetString( Res.Xml_MixingBinaryContentMethods ) );
229                 case State.InReadElementContent:
230                     // if we have a correct decoder, go read
231                     if ( decoder == binHexDecoder ) {
232                         // read more binary data
233                         return ReadElementContentAsBinary( buffer, index, count );
234                     }
235                     break;
236                 default:
237                     Debug.Assert( false );
238                     return 0;
239             }    
240
241             Debug.Assert( state == State.InReadElementContent );
242
243             // setup binhex decoder
244             InitBinHexDecoder();
245
246             // read more binary data
247             return ReadElementContentAsBinary( buffer, index, count );
248         }
249
250         internal void Finish() {
251             if ( state != State.None ) {
252                 while ( MoveToNextContentNode( true ) )
253                     ;
254                 if ( state == State.InReadElementContent ) {
255                     if ( reader.NodeType != XmlNodeType.EndElement ) {
256                         throw new XmlException( Res.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo );
257                     }
258                     // move off the EndElement
259                     reader.Read();
260                 }
261             }
262             Reset();
263         }
264
265         internal void Reset() {
266             state = State.None;
267             isEnd = false;
268             valueOffset = 0;
269         }
270
271 // Private methods
272         private  bool  Init() {
273             // make sure we are on a content node
274             if ( !MoveToNextContentNode( false ) ) {
275                 return false;
276             }
277
278             state = State.InReadContent;
279             isEnd = false;
280             return true;
281         }
282
283         private  bool  InitOnElement() {
284             Debug.Assert( reader.NodeType == XmlNodeType.Element );
285             bool isEmpty = reader.IsEmptyElement;
286
287             // move to content or off the empty element
288             reader.Read();
289             if ( isEmpty ) {
290                 return false;
291             }
292
293             // make sure we are on a content node
294             if ( !MoveToNextContentNode( false ) ) {
295                 if ( reader.NodeType != XmlNodeType.EndElement ) {
296                     throw new XmlException( Res.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo );
297                 }
298                 // move off end element
299                 reader.Read();
300                 return false;
301             }
302             state = State.InReadElementContent;
303             isEnd = false;
304             return true;
305         }
306
307         private void InitBase64Decoder() {
308             if ( base64Decoder == null ) {
309                 base64Decoder = new Base64Decoder();
310             }
311             else {
312                 base64Decoder.Reset();
313             }
314             decoder = base64Decoder;
315         }
316
317         private void InitBinHexDecoder() {
318             if ( binHexDecoder == null ) {
319                 binHexDecoder = new BinHexDecoder();
320             }
321             else {
322                 binHexDecoder.Reset();
323             }
324             decoder = binHexDecoder;
325         }
326
327         private  int  ReadContentAsBinary( byte[] buffer, int index, int count ) {
328             Debug.Assert( decoder != null );
329
330             if ( isEnd ) {
331                 Reset();
332                 return 0;
333             }
334             decoder.SetNextOutputBuffer( buffer, index, count );
335
336             for (;;) {
337                 // use streaming ReadValueChunk if the reader supports it
338                 if ( canReadValueChunk ) {
339                     for (;;) {
340                         if ( valueOffset < valueChunkLength ) {
341                             int decodedCharsCount = decoder.Decode( valueChunk, valueOffset, valueChunkLength - valueOffset );
342                             valueOffset += decodedCharsCount;
343                         }
344                         if ( decoder.IsFull ) {
345                             return decoder.DecodedCount;
346                         }
347                         Debug.Assert( valueOffset == valueChunkLength );
348                         if ( ( valueChunkLength = reader.ReadValueChunk( valueChunk, 0, ChunkSize ) ) == 0 ) {
349                             break;
350                         }
351                         valueOffset = 0;
352                     }
353                 }
354                 else {
355                     // read what is reader.Value
356                     string value = reader.Value;
357                     int decodedCharsCount = decoder.Decode( value, valueOffset, value.Length - valueOffset );
358                     valueOffset += decodedCharsCount;
359
360                     if ( decoder.IsFull ) {
361                         return decoder.DecodedCount;
362                     }
363                 }
364
365                 valueOffset = 0;
366
367                 // move to next textual node in the element content; throw on sub elements
368                 if ( !MoveToNextContentNode( true ) ) {
369                     isEnd = true;
370                     return decoder.DecodedCount;
371                 }
372             }
373         }
374
375         private  int  ReadElementContentAsBinary( byte[] buffer, int index, int count ) {
376             if ( count == 0 ) {
377                 return 0;
378             }
379             // read binary
380             int decoded = ReadContentAsBinary( buffer, index, count );
381             if ( decoded > 0 ) {
382                 return decoded;
383             }
384
385             // if 0 bytes returned check if we are on a closing EndElement, throw exception if not
386             if ( reader.NodeType != XmlNodeType.EndElement ) {
387                 throw new XmlException( Res.Xml_InvalidNodeType, reader.NodeType.ToString(), reader as IXmlLineInfo );
388             }
389
390             // move off the EndElement
391             reader.Read();
392             state = State.None;
393             return 0;
394         }
395
396          bool  MoveToNextContentNode( bool moveIfOnContentNode ) {
397             do {
398                 switch ( reader.NodeType ) {
399                     case XmlNodeType.Attribute:
400                         return !moveIfOnContentNode;
401                     case XmlNodeType.Text:
402                     case XmlNodeType.Whitespace:
403                     case XmlNodeType.SignificantWhitespace:
404                     case XmlNodeType.CDATA:
405                         if ( !moveIfOnContentNode ) {
406                             return true;
407                         }
408                         break;
409                     case XmlNodeType.ProcessingInstruction:
410                     case XmlNodeType.Comment:
411                     case XmlNodeType.EndEntity:
412                         // skip comments, pis and end entity nodes
413                         break;
414                     case XmlNodeType.EntityReference:
415                         if ( reader.CanResolveEntity ) {
416                             reader.ResolveEntity();
417                             break;
418                         }
419                         goto default;
420                     default:
421                         return false;
422                 }
423                 moveIfOnContentNode = false;
424             } while ( reader.Read() );
425             return false;
426         }
427     }
428 }