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; }
77 case CommandState.ReadElementContentAsBase64:
78 case CommandState.ReadElementContentAsBinHex:
83 base64CacheStartsAt = -1;
84 state = CommandState.None;
90 InvalidOperationException StateError (CommandState action)
92 return new InvalidOperationException (
93 String.Format ("Invalid attempt to read binary content by {0}, while once binary reading was started by {1}", action, state));
96 private void CheckState (bool element, CommandState action)
98 if (state == CommandState.None) {
99 if (textCache == null)
100 textCache = new StringBuilder ();
102 textCache.Length = 0;
103 if (action == CommandState.None)
104 return; // for ReadValueChunk()
105 if (reader.ReadState != ReadState.Interactive)
107 switch (reader.NodeType) {
108 case XmlNodeType.Text:
109 case XmlNodeType.CDATA:
110 case XmlNodeType.SignificantWhitespace:
111 case XmlNodeType.Whitespace:
117 case XmlNodeType.Element:
119 if (!reader.IsEmptyElement)
126 throw new XmlException ((element ?
127 "Reader is not positioned on an element."
128 : "Reader is not positioned on a text node."));
132 throw StateError (action);
135 public int ReadElementContentAsBase64 (
136 byte [] buffer, int offset, int length)
138 CheckState (true, CommandState.ReadElementContentAsBase64);
139 return ReadBase64 (buffer, offset, length);
142 public int ReadContentAsBase64 (
143 byte [] buffer, int offset, int length)
145 CheckState (false, CommandState.ReadContentAsBase64);
146 return ReadBase64 (buffer, offset, length);
149 public int ReadElementContentAsBinHex (
150 byte [] buffer, int offset, int length)
152 CheckState (true, CommandState.ReadElementContentAsBinHex);
153 return ReadBinHex (buffer, offset, length);
156 public int ReadContentAsBinHex (
157 byte [] buffer, int offset, int length)
159 CheckState (false, CommandState.ReadContentAsBinHex);
160 return ReadBinHex (buffer, offset, length);
163 public int ReadBase64 (byte [] buffer, int offset, int length)
166 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
168 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
169 else if (buffer.Length < offset + length)
170 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
172 if (reader.IsEmptyElement)
174 if (length == 0) // It does not raise an error.
177 int bufIndex = offset;
178 int bufLast = offset + length;
180 if (base64CacheStartsAt >= 0) {
181 for (int i = base64CacheStartsAt; i < 3; i++) {
182 buffer [bufIndex++] = base64Cache [base64CacheStartsAt++];
183 if (bufIndex == bufLast)
184 return bufLast - offset;
188 for (int i = 0; i < 3; i++)
190 base64CacheStartsAt = -1;
192 int max = (int) System.Math.Ceiling (4.0 / 3 * length);
193 int additional = max % 4;
195 max += 4 - additional;
196 char [] chars = new char [max];
197 int charsLength = getter != null ?
198 getter (chars, 0, max) :
199 ReadValueChunk (chars, 0, max);
203 for (int i = 0; i < charsLength - 3; i++) {
204 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
206 b = (byte) (GetBase64Byte (chars [i]) << 2);
207 if (bufIndex < bufLast)
208 buffer [bufIndex] = b;
210 if (base64CacheStartsAt < 0)
211 base64CacheStartsAt = 0;
214 // charsLength mod 4 might not equals to 0.
215 if (++i == charsLength)
217 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
219 b = GetBase64Byte (chars [i]);
220 work = (byte) (b >> 4);
221 if (bufIndex < bufLast) {
222 buffer [bufIndex] += work;
226 base64Cache [0] += work;
228 work = (byte) ((b & 0xf) << 4);
229 if (bufIndex < bufLast) {
230 buffer [bufIndex] = work;
233 if (base64CacheStartsAt < 0)
234 base64CacheStartsAt = 1;
235 base64Cache [1] = work;
238 if (++i == charsLength)
240 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
242 b = GetBase64Byte (chars [i]);
243 work = (byte) (b >> 2);
244 if (bufIndex < bufLast) {
245 buffer [bufIndex] += work;
249 base64Cache [1] += work;
251 work = (byte) ((b & 3) << 6);
252 if (bufIndex < bufLast)
253 buffer [bufIndex] = work;
255 if (base64CacheStartsAt < 0)
256 base64CacheStartsAt = 2;
257 base64Cache [2] = work;
259 if (++i == charsLength)
261 if ((i = SkipIgnorableBase64Chars (chars, charsLength, i)) == charsLength)
263 work = GetBase64Byte (chars [i]);
264 if (bufIndex < bufLast) {
265 buffer [bufIndex] += work;
269 base64Cache [2] += work;
271 int ret = System.Math.Min (bufLast - offset, bufIndex - offset);
272 if (ret < length && charsLength > 0)
273 return ret + ReadBase64 (buffer, offset + ret, length - ret);
278 // Since ReadBase64() is processed for every 4 chars, it does
279 // not handle '=' here.
280 private byte GetBase64Byte (char ch)
288 if (ch >= 'A' && ch <= 'Z')
289 return (byte) (ch - 'A');
290 else if (ch >= 'a' && ch <= 'z')
291 return (byte) (ch - 'a' + 26);
292 else if (ch >= '0' && ch <= '9')
293 return (byte) (ch - '0' + 52);
295 throw new XmlException ("Invalid Base64 character was found.");
299 private int SkipIgnorableBase64Chars (char [] chars, int charsLength, int i)
301 while (chars [i] == '=' || XmlChar.IsWhitespace (chars [i]))
302 if (charsLength == ++i)
307 static Exception CreateArgumentOutOfRangeException (string name, object value, string message)
309 return new ArgumentOutOfRangeException (
316 public int ReadBinHex (byte [] buffer, int offset, int length)
319 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
321 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
322 else if (buffer.Length < offset + length)
323 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
328 char [] chars = new char [length * 2];
329 int charsLength = getter != null ?
330 getter (chars, 0, length * 2) :
331 ReadValueChunk (chars, 0, length * 2);
332 return XmlConvert.FromBinHexString (chars, offset, charsLength, buffer);
335 public int ReadValueChunk (
336 char [] buffer, int offset, int length)
338 CommandState backup = state;
339 if (state == CommandState.None)
340 CheckState (false, CommandState.None);
343 throw CreateArgumentOutOfRangeException ("offset", offset, "Offset must be non-negative integer.");
345 throw CreateArgumentOutOfRangeException ("length", length, "Length must be non-negative integer.");
346 else if (buffer.Length < offset + length)
347 throw new ArgumentOutOfRangeException ("buffer length is smaller than the sum of offset and length.");
353 if (reader.IsEmptyElement)
357 bool consumeToEnd = false;
358 while (!consumeToEnd && textCache.Length < length) {
359 switch (reader.NodeType) {
360 case XmlNodeType.Text:
361 case XmlNodeType.CDATA:
362 case XmlNodeType.SignificantWhitespace:
363 case XmlNodeType.Whitespace:
365 switch (reader.NodeType) {
366 case XmlNodeType.Text:
367 case XmlNodeType.CDATA:
368 case XmlNodeType.SignificantWhitespace:
369 case XmlNodeType.Whitespace:
377 textCache.Append (reader.Value);
383 int min = textCache.Length;
386 string str = textCache.ToString (0, min);
387 textCache.Remove (0, str.Length);
388 str.CopyTo (0, buffer, offset, str.Length);
390 return min + ReadValueChunk (buffer, offset + min, length - min);
398 bool b = reader.Read ();