2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.XML / System.Xml / XmlReaderBinarySupport.cs
blob19e5b281a0e5bb52fb14080b639a545098d2838b
1 //
2 // System.Xml.XmlReaderBinarySupport.cs
3 //
4 // Author:
5 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C)2004 Novell Inc,
8 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Collections;
33 using System.Text;
35 namespace System.Xml
37 internal class XmlReaderBinarySupport
39 public delegate int CharGetter (
40 char [] buffer, int offset, int length);
42 public enum CommandState {
43 None,
44 ReadElementContentAsBase64,
45 ReadContentAsBase64,
46 ReadElementContentAsBinHex,
47 ReadContentAsBinHex
50 public XmlReaderBinarySupport (XmlReader reader)
52 this.reader = reader;
53 Reset ();
56 XmlReader reader;
57 CharGetter getter;
58 byte [] base64Cache = new byte [3];
59 int base64CacheStartsAt;
60 CommandState state;
61 StringBuilder textCache;
62 bool hasCache;
63 bool dontReset;
65 public CharGetter Getter {
66 get { return getter; }
67 set { getter = value; }
70 public void Reset ()
72 if (!dontReset) {
73 dontReset = true;
74 if (hasCache) {
75 switch (reader.NodeType) {
76 case XmlNodeType.Text:
77 case XmlNodeType.CDATA:
78 case XmlNodeType.SignificantWhitespace:
79 case XmlNodeType.Whitespace:
80 reader.Read ();
81 break;
83 switch (state) {
84 case CommandState.ReadElementContentAsBase64:
85 case CommandState.ReadElementContentAsBinHex:
86 reader.Read ();
87 break;
90 base64CacheStartsAt = -1;
91 state = CommandState.None;
92 hasCache = false;
93 dontReset = false;
97 InvalidOperationException StateError (CommandState action)
99 return new InvalidOperationException (
100 String.Format ("Invalid attempt to read binary content by {0}, while once binary reading was started by {1}", action, state));
103 private void CheckState (bool element, CommandState action)
105 if (state == CommandState.None) {
106 if (textCache == null)
107 textCache = new StringBuilder ();
108 else
109 textCache.Length = 0;
110 if (action == CommandState.None)
111 return; // for ReadValueChunk()
112 if (reader.ReadState != ReadState.Interactive)
113 return;
114 switch (reader.NodeType) {
115 case XmlNodeType.Text:
116 case XmlNodeType.CDATA:
117 case XmlNodeType.SignificantWhitespace:
118 case XmlNodeType.Whitespace:
119 if (!element) {
120 state = action;
121 return;
123 break;
124 case XmlNodeType.Element:
125 if (element) {
126 if (!reader.IsEmptyElement)
127 reader.Read ();
128 state = action;
129 return;
131 break;
133 throw new XmlException ((element ?
134 "Reader is not positioned on an element."
135 : "Reader is not positioned on a text node."));
137 if (state == action)
138 return;
139 throw StateError (action);
142 public int ReadElementContentAsBase64 (
143 byte [] buffer, int offset, int length)
145 CheckState (true, CommandState.ReadElementContentAsBase64);
146 return ReadBase64 (buffer, offset, length);
149 public int ReadContentAsBase64 (
150 byte [] buffer, int offset, int length)
152 CheckState (false, CommandState.ReadContentAsBase64);
153 return ReadBase64 (buffer, offset, length);
156 public int ReadElementContentAsBinHex (
157 byte [] buffer, int offset, int length)
159 CheckState (true, CommandState.ReadElementContentAsBinHex);
160 return ReadBinHex (buffer, offset, length);
163 public int ReadContentAsBinHex (
164 byte [] buffer, int offset, int length)
166 CheckState (false, CommandState.ReadContentAsBinHex);
167 return ReadBinHex (buffer, offset, length);
170 public int ReadBase64 (byte [] buffer, int offset, int length)
172 if (offset < 0)
173 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
174 else if (length < 0)
175 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
176 else if (buffer.Length < offset + length)
177 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
179 if (reader.IsEmptyElement)
180 return 0;
181 if (length == 0) // It does not raise an error.
182 return 0;
184 int bufIndex = offset;
185 int bufLast = offset + length;
187 if (base64CacheStartsAt >= 0) {
188 for (int i = base64CacheStartsAt; i < 3; i++) {
189 buffer [bufIndex++] = base64Cache [base64CacheStartsAt++];
190 if (bufIndex == bufLast)
191 return bufLast - offset;
195 for (int i = 0; i < 3; i++)
196 base64Cache [i] = 0;
197 base64CacheStartsAt = -1;
199 int max = (int) System.Math.Ceiling (4.0 / 3 * length);
200 int additional = max % 4;
201 if (additional > 0)
202 max += 4 - additional;
203 char [] chars = new char [max];
204 int charsLength = getter != null ?
205 getter (chars, 0, max) :
206 ReadValueChunk (chars, 0, max);
208 byte b = 0;
209 byte work = 0;
210 for (int i = 0; i < charsLength - 3; i++) {
211 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
212 break;
213 b = (byte) (GetBase64Byte (chars [i]) << 2);
214 if (bufIndex < bufLast)
215 buffer [bufIndex] = b;
216 else if (b != 0) {
217 if (base64CacheStartsAt < 0)
218 base64CacheStartsAt = 0;
219 base64Cache [0] = b;
221 // charsLength mod 4 might not equals to 0.
222 if (++i == charsLength)
223 break;
224 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
225 break;
226 b = GetBase64Byte (chars [i]);
227 work = (byte) (b >> 4);
228 if (bufIndex < bufLast) {
229 buffer [bufIndex] += work;
230 bufIndex++;
232 else if (work != 0) {
233 if (base64CacheStartsAt < 0)
234 base64CacheStartsAt = 0;
235 base64Cache [0] += work;
238 work = (byte) ((b & 0xf) << 4);
239 if (bufIndex < bufLast) {
240 buffer [bufIndex] = work;
242 else if (work != 0) {
243 if (base64CacheStartsAt < 0)
244 base64CacheStartsAt = 1;
245 base64Cache [1] = work;
248 if (++i == charsLength)
249 break;
250 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
251 break;
252 b = GetBase64Byte (chars [i]);
253 work = (byte) (b >> 2);
254 if (bufIndex < bufLast) {
255 buffer [bufIndex] += work;
256 bufIndex++;
258 else if (work != 0) {
259 if (base64CacheStartsAt < 0)
260 base64CacheStartsAt = 1;
261 base64Cache [1] += work;
264 work = (byte) ((b & 3) << 6);
265 if (bufIndex < bufLast)
266 buffer [bufIndex] = work;
267 else if (work != 0) {
268 if (base64CacheStartsAt < 0)
269 base64CacheStartsAt = 2;
270 base64Cache [2] = work;
272 if (++i == charsLength)
273 break;
274 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
275 break;
276 work = GetBase64Byte (chars [i]);
277 if (bufIndex < bufLast) {
278 buffer [bufIndex] += work;
279 bufIndex++;
281 else if (work != 0) {
282 if (base64CacheStartsAt < 0)
283 base64CacheStartsAt = 2;
284 base64Cache [2] += work;
287 int ret = System.Math.Min (bufLast - offset, bufIndex - offset);
288 if (ret < length && charsLength > 0)
289 return ret + ReadBase64 (buffer, offset + ret, length - ret);
290 else
291 return ret;
294 // Since ReadBase64() is processed for every 4 chars, it does
295 // not handle '=' here.
296 private byte GetBase64Byte (char ch)
298 switch (ch) {
299 case '+':
300 return 62;
301 case '/':
302 return 63;
303 default:
304 if (ch >= 'A' && ch <= 'Z')
305 return (byte) (ch - 'A');
306 else if (ch >= 'a' && ch <= 'z')
307 return (byte) (ch - 'a' + 26);
308 else if (ch >= '0' && ch <= '9')
309 return (byte) (ch - '0' + 52);
310 else
311 throw new XmlException ("Invalid Base64 character was found.");
315 private int SkipIgnorableBase64Chars (char [] chars, int charsLength, int i)
317 while (chars [i] == '=' || XmlChar.IsWhitespace (chars [i]))
318 if (charsLength == ++i)
319 break;
320 return i;
323 static Exception CreateArgumentOutOfRangeException (string name, object value, string message)
325 return new ArgumentOutOfRangeException (
326 #if !NET_2_1
327 name, value,
328 #endif
329 message);
332 public int ReadBinHex (byte [] buffer, int offset, int length)
334 if (offset < 0)
335 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
336 else if (length < 0)
337 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
338 else if (buffer.Length < offset + length)
339 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
341 if (length == 0)
342 return 0;
344 char [] chars = new char [length * 2];
345 int charsLength = getter != null ?
346 getter (chars, 0, length * 2) :
347 ReadValueChunk (chars, 0, length * 2);
348 return XmlConvert.FromBinHexString (chars, offset, charsLength, buffer);
351 public int ReadValueChunk (
352 char [] buffer, int offset, int length)
354 CommandState backup = state;
355 if (state == CommandState.None)
356 CheckState (false, CommandState.None);
358 if (offset < 0)
359 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
360 else if (length < 0)
361 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
362 else if (buffer.Length < offset + length)
363 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
365 if (length == 0)
366 return 0;
368 if (!hasCache) {
369 if (reader.IsEmptyElement)
370 return 0;
373 bool loop = true;
374 while (loop && textCache.Length < length) {
375 switch (reader.NodeType) {
376 case XmlNodeType.Text:
377 case XmlNodeType.CDATA:
378 case XmlNodeType.SignificantWhitespace:
379 case XmlNodeType.Whitespace:
380 if (hasCache) {
381 switch (reader.NodeType) {
382 case XmlNodeType.Text:
383 case XmlNodeType.CDATA:
384 case XmlNodeType.SignificantWhitespace:
385 case XmlNodeType.Whitespace:
386 Read ();
387 break;
388 default:
389 loop = false;
390 break;
393 textCache.Append (reader.Value);
394 hasCache = true;
395 break;
396 default:
397 loop = false;
398 break;
401 state = backup;
402 int min = textCache.Length;
403 if (min > length)
404 min = length;
405 string str = textCache.ToString (0, min);
406 textCache.Remove (0, str.Length);
407 str.CopyTo (0, buffer, offset, str.Length);
408 if (min < length && loop)
409 return min + ReadValueChunk (buffer, offset + min, length - min);
410 else
411 return min;
414 private bool Read ()
416 dontReset = true;
417 bool b = reader.Read ();
418 dontReset = false;
419 return b;