001: /*
002: ******************************************************************
003: Copyright (c) 2007, Jeff Martin, Tim Bacon
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: * Redistributions of source code must retain the above copyright
011: notice, this list of conditions and the following disclaimer.
012: * Redistributions in binary form must reproduce the above
013: copyright notice, this list of conditions and the following
014: disclaimer in the documentation and/or other materials provided
015: with the distribution.
016: * Neither the name of the xmlunit.sourceforge.net nor the names
017: of its contributors may be used to endorse or promote products
018: derived from this software without specific prior written
019: permission.
020:
021: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
022: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
023: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
024: FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
025: COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
026: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
027: BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
028: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
029: CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
030: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
031: ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
032: POSSIBILITY OF SUCH DAMAGE.
033:
034: ******************************************************************
035: */
036:
037: package org.custommonkey.xmlunit;
038:
039: import java.io.IOException;
040:
041: import org.custommonkey.xmlunit.exceptions.XMLUnitRuntimeException;
042: import org.custommonkey.xmlunit.util.IntegerBuffer;
043:
044: /**
045: * Contains some common code for DoctypeReader and DoctypeInputStream.
046: *
047: * <p>When used with DoctypeInputStream it assumes that the whole
048: * DOCTYPE declaration consists of US-ASCII characters.</p>
049: */
050: final class DoctypeSupport {
051:
052: static interface Readable {
053: int read() throws IOException;
054: }
055:
056: final static String DOCTYPE_OPEN_DECL = "<!";
057: final static String DOCTYPE_CLOSE_DECL = ">";
058: final static String DOCTYPE = "DOCTYPE ";
059: final static String SYSTEM = " SYSTEM \"";
060: private final static int[] DOCTYPE_INTS = { 'D', 'O', 'C', 'T',
061: 'Y', 'P', 'E', ' ' };
062:
063: private boolean hasSplit;
064: private final Readable original;
065: private Readable decl;
066: private Readable beforeDoctype;
067: private Readable afterDoctype;
068:
069: /**
070: * Encapsulates a DOCTYPE declaration for the given name and system id.
071: */
072: DoctypeSupport(String name, String systemId, Readable original,
073: boolean characters, String encoding) {
074: this .original = original;
075:
076: StringBuffer sb = new StringBuffer(DOCTYPE_OPEN_DECL);
077: sb.append(DOCTYPE).append(name).append(SYSTEM).append(systemId)
078: .append('\"').append(DOCTYPE_CLOSE_DECL);
079: String s = sb.toString();
080: IntegerBuffer buf = new IntegerBuffer(s.length()
081: * (characters ? 1 : 2));
082:
083: if (characters) {
084: char[] c = s.toCharArray();
085: for (int i = 0; i < c.length; i++) {
086: buf.append(c[i]);
087: }
088: } else {
089: try {
090: byte[] b = encoding == null ? s.getBytes() : s
091: .getBytes(encoding);
092: for (int i = 0; i < b.length; i++) {
093: buf.append(b[i] & 0xFF);
094: }
095: } catch (java.io.UnsupportedEncodingException use) {
096: throw new XMLUnitRuntimeException(
097: "Unsupported encoding", use);
098: }
099: }
100:
101: decl = new IntBufferReadable(buf);
102: }
103:
104: /**
105: * Reads the next character from the declaration.
106: * @return -1 if the end of the declaration has been reached.
107: */
108: int read() throws IOException {
109: int nextInt = -1;
110: if (!hasSplit) {
111: split();
112: }
113: if (beforeDoctype != null) {
114: nextInt = beforeDoctype.read();
115: if (nextInt == -1) {
116: beforeDoctype = null;
117: }
118: }
119: if (nextInt == -1 && decl != null) {
120: nextInt = decl.read();
121: if (nextInt == -1) {
122: decl = null;
123: }
124: }
125: if (nextInt == -1 && afterDoctype != null) {
126: nextInt = afterDoctype.read();
127: if (nextInt == -1) {
128: afterDoctype = null;
129: }
130: }
131: if (nextInt == -1) {
132: nextInt = original.read();
133: }
134: return nextInt;
135: }
136:
137: /**
138: * Reads enough of the original Readable to know where to place
139: * the declaration. Fills beforeDecl and afterDecl from the data
140: * read ahead. Swallows the original DOCTYPE if there is one.
141: */
142: private void split() throws IOException {
143: hasSplit = true;
144: IntegerBuffer before = new IntegerBuffer();
145: IntegerBuffer after = new IntegerBuffer();
146:
147: int current;
148: boolean ready = false;
149: boolean stillNeedToSeeDoctype = true;
150: while (!ready && (current = original.read()) != -1) {
151: if (Character.isWhitespace((char) current)) {
152: before.append(current);
153: } else if (current == '<') {
154: // could be XML declaration, comment, PI, DOCTYPE
155: // or the first element
156: int[] elementOrDeclOr = readUntilCloseCharIsReached();
157: if (elementOrDeclOr.length > 0) {
158: if (elementOrDeclOr[0] == '?') {
159: // XML declaration or PI
160: before.append('<');
161: before.append(elementOrDeclOr);
162: } else if (elementOrDeclOr[0] != '!') {
163: // first element
164: after.append('<');
165: after.append(elementOrDeclOr);
166: stillNeedToSeeDoctype = false;
167: ready = true;
168: } else {
169: // comment or doctype
170: IntegerBuffer b = new IntegerBuffer(
171: elementOrDeclOr.length);
172: b.append(elementOrDeclOr);
173: if (b.indexOf(DOCTYPE_INTS) == -1) {
174: after.append('<');
175: after.append(elementOrDeclOr);
176: } else {
177: // swallow old declaration
178: stillNeedToSeeDoctype = false;
179: }
180: ready = true;
181: }
182: } else {
183: after.append('<');
184: stillNeedToSeeDoctype = false;
185: ready = true;
186: }
187: } else {
188: after.append(current);
189: stillNeedToSeeDoctype = false;
190: ready = true;
191: }
192: }
193:
194: // need to eliminate original DOCTYPE if it exists
195: while (stillNeedToSeeDoctype
196: && (current = original.read()) != -1) {
197: if (Character.isWhitespace((char) current)) {
198: after.append(current);
199: } else if (current == '<') {
200: int[] elementOrDeclOr = readUntilCloseCharIsReached();
201: if (elementOrDeclOr.length > 0) {
202: if (elementOrDeclOr[0] == '?') {
203: // PI
204: after.append('<');
205: after.append(elementOrDeclOr);
206: } else if (elementOrDeclOr[0] != '!') {
207: // first element
208: after.append('<');
209: after.append(elementOrDeclOr);
210: stillNeedToSeeDoctype = false;
211: } else {
212: // comment or doctype
213: IntegerBuffer b = new IntegerBuffer(
214: elementOrDeclOr.length);
215: b.append(elementOrDeclOr);
216: if (b.indexOf(DOCTYPE_INTS) == -1) {
217: after.append('<');
218: after.append(elementOrDeclOr);
219: } else {
220: // swallow old declaration
221: stillNeedToSeeDoctype = false;
222: }
223: }
224: } else {
225: after.append('<');
226: stillNeedToSeeDoctype = false;
227: }
228: } else {
229: after.append(current);
230: stillNeedToSeeDoctype = false;
231: }
232: }
233:
234: beforeDoctype = before.size() > 0 ? new IntBufferReadable(
235: before) : null;
236: afterDoctype = after.size() > 0 ? new IntBufferReadable(after)
237: : null;
238: }
239:
240: private int[] readUntilCloseCharIsReached() throws IOException {
241: IntegerBuffer i = new IntegerBuffer();
242: int intRead = -1;
243: int openCount = 1;
244: while (openCount > 0 && (intRead = original.read()) != -1) {
245: i.append(intRead);
246: if (intRead == '<') {
247: openCount++;
248: }
249: if (intRead == '>') {
250: openCount--;
251: }
252: }
253: return i.toIntArray();
254: }
255:
256: private static class IntBufferReadable implements Readable {
257: private final int[] buf;
258: private int off;
259:
260: IntBufferReadable(IntegerBuffer b) {
261: buf = b.toIntArray();
262: }
263:
264: public int read() {
265: return off >= buf.length ? -1 : buf[off++];
266: }
267: }
268:
269: }
|