2 // System.Xml.XmlReaderBinarySupport.cs
5 // Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System
.Collections
;
37 internal class XmlReaderBinarySupport
39 public delegate int CharGetter (
40 char [] buffer
, int offset
, int length
);
42 public enum CommandState
{
44 ReadElementContentAsBase64
,
46 ReadElementContentAsBinHex
,
50 public XmlReaderBinarySupport (XmlReader reader
)
58 byte [] base64Cache
= new byte [3];
59 int base64CacheStartsAt
;
61 StringBuilder textCache
;
65 public CharGetter Getter
{
66 get { return getter; }
67 set { getter = value; }
75 switch (reader
.NodeType
) {
76 case XmlNodeType
.Text
:
77 case XmlNodeType
.CDATA
:
78 case XmlNodeType
.SignificantWhitespace
:
79 case XmlNodeType
.Whitespace
:
84 case CommandState
.ReadElementContentAsBase64
:
85 case CommandState
.ReadElementContentAsBinHex
:
90 base64CacheStartsAt
= -1;
91 state
= CommandState
.None
;
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 ();
109 textCache
.Length
= 0;
110 if (action
== CommandState
.None
)
111 return; // for ReadValueChunk()
112 if (reader
.ReadState
!= ReadState
.Interactive
)
114 switch (reader
.NodeType
) {
115 case XmlNodeType
.Text
:
116 case XmlNodeType
.CDATA
:
117 case XmlNodeType
.SignificantWhitespace
:
118 case XmlNodeType
.Whitespace
:
124 case XmlNodeType
.Element
:
126 if (!reader
.IsEmptyElement
)
133 throw new XmlException ((element
?
134 "Reader is not positioned on an element."
135 : "Reader is not positioned on a text node."));
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
)
173 throw CreateArgumentOutOfRangeException ("offset", offset
, "Offset must be non-negative integer.");
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
)
181 if (length
== 0) // It does not raise an error.
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
++)
197 base64CacheStartsAt
= -1;
199 int max
= (int) System
.Math
.Ceiling (4.0 / 3 * length
);
200 int additional
= max
% 4;
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
);
210 for (int i
= 0; i
< charsLength
- 3; i
++) {
211 if ((i
= SkipIgnorableBase64Chars (chars
, charsLength
, i
)) == charsLength
)
213 b
= (byte) (GetBase64Byte (chars
[i
]) << 2);
214 if (bufIndex
< bufLast
)
215 buffer
[bufIndex
] = b
;
217 if (base64CacheStartsAt
< 0)
218 base64CacheStartsAt
= 0;
221 // charsLength mod 4 might not equals to 0.
222 if (++i
== charsLength
)
224 if ((i
= SkipIgnorableBase64Chars (chars
, charsLength
, i
)) == charsLength
)
226 b
= GetBase64Byte (chars
[i
]);
227 work
= (byte) (b
>> 4);
228 if (bufIndex
< bufLast
) {
229 buffer
[bufIndex
] += work
;
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
)
250 if ((i
= SkipIgnorableBase64Chars (chars
, charsLength
, i
)) == charsLength
)
252 b
= GetBase64Byte (chars
[i
]);
253 work
= (byte) (b
>> 2);
254 if (bufIndex
< bufLast
) {
255 buffer
[bufIndex
] += work
;
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
)
274 if ((i
= SkipIgnorableBase64Chars (chars
, charsLength
, i
)) == charsLength
)
276 work
= GetBase64Byte (chars
[i
]);
277 if (bufIndex
< bufLast
) {
278 buffer
[bufIndex
] += work
;
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
);
294 // Since ReadBase64() is processed for every 4 chars, it does
295 // not handle '=' here.
296 private byte GetBase64Byte (char ch
)
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);
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
)
323 static Exception
CreateArgumentOutOfRangeException (string name
, object value, string message
)
325 return new ArgumentOutOfRangeException (
332 public int ReadBinHex (byte [] buffer
, int offset
, int length
)
335 throw CreateArgumentOutOfRangeException ("offset", offset
, "Offset must be non-negative integer.");
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.");
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
);
359 throw CreateArgumentOutOfRangeException ("offset", offset
, "Offset must be non-negative integer.");
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.");
369 if (reader
.IsEmptyElement
)
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
:
381 switch (reader
.NodeType
) {
382 case XmlNodeType
.Text
:
383 case XmlNodeType
.CDATA
:
384 case XmlNodeType
.SignificantWhitespace
:
385 case XmlNodeType
.Whitespace
:
393 textCache
.Append (reader
.Value
);
402 int min
= textCache
.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
);
417 bool b
= reader
.Read ();