001: /*
002: ******************************************************************
003: Copyright (c) 2001-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.BufferedReader;
040: import java.io.IOException;
041: import java.io.Reader;
042:
043: /**
044: * Adapts the marked-up content in a source Reader to specify that it
045: * conforms to a different DTD.
046: * Combines Reader semantics with the ability to specify a target doctype
047: * for a character stream containing XML markup.
048: * Used by Validator class to wrap a Reader when performing validation of a
049: * document against a DTD.
050: * <br />Examples and more at <a href="http://xmlunit.sourceforge.net"/>xmlunit.sourceforge.net</a>
051: */
052: public class DoctypeReader extends Reader {
053:
054: private final Reader originalReader;
055: private final StringBuffer sourceBuffer = new StringBuffer(1024);
056:
057: private final DoctypeSupport support;
058:
059: /**
060: * Create a Reader whose XML content is provided by the originalSource with
061: * the exception of the DOCTYPE which is provided by the doctypeName
062: * and systemID.
063: * @param originalSource
064: * @param doctypeName
065: * @param systemID
066: */
067: public DoctypeReader(Reader originalSource, String doctypeName,
068: String systemID) {
069: originalReader = originalSource instanceof BufferedReader ? originalSource
070: : new BufferedReader(originalSource);
071: support = new DoctypeSupport(doctypeName, systemID,
072: new DoctypeSupport.Readable() {
073: public int read() throws IOException {
074: return originalReader.read();
075: }
076: }, true, null);
077: }
078:
079: /**
080: * @return the content of the original source, without amendments or
081: * substitutions. Safe to call multiple times.
082: * @throws IOException if thrown while reading from the original source
083: */
084: protected String getContent() throws IOException {
085: return getContent(originalReader).toString();
086: }
087:
088: /**
089: * @param originalSource
090: * @return the contents of the originalSource within a StringBuffer
091: * @throws IOException if thrown while reading from the original source
092: */
093: private StringBuffer getContent(Reader originalSource)
094: throws IOException {
095: if (sourceBuffer.length() == 0) {
096: BufferedReader bufferedReader;
097: if (originalSource instanceof BufferedReader) {
098: bufferedReader = (BufferedReader) originalSource;
099: } else {
100: bufferedReader = new BufferedReader(originalSource);
101: }
102: String newline = System.getProperty("line.separator");
103: String source;
104: boolean atFirstLine = true;
105: while ((source = bufferedReader.readLine()) != null) {
106: if (atFirstLine) {
107: atFirstLine = false;
108: } else {
109: sourceBuffer.append(newline);
110: }
111: sourceBuffer.append(source);
112: }
113:
114: bufferedReader.close();
115: }
116:
117: return sourceBuffer;
118: }
119:
120: /**
121: * Determine where to place the DOCTYPE declaration within some marked-up
122: * content
123: * @param withinContent
124: * @return
125: */
126: private int obsoleteFindStartDoctype(StringBuffer withinContent) {
127: int startAt = -1;
128: char curChar;
129: boolean canInsert = true;
130: for (int i = 0; startAt == -1; ++i) {
131: curChar = withinContent.charAt(i);
132: if (curChar == '<') {
133: switch (withinContent.charAt(i + 1)) {
134: case '?':
135: case '!':
136: case '-':
137: canInsert = false;
138: break;
139: default:
140: startAt = i;
141: }
142: } else if (curChar == '>') {
143: canInsert = true;
144: } else if (canInsert) {
145: startAt = i;
146: }
147: }
148: return startAt;
149: }
150:
151: /**
152: * Perform DOCTYPE amendment / addition within some marked-up content
153: * @param withinContent
154: * @param doctypeName
155: * @param systemId
156: * @return the content, after DOCTYPE amendment / addition
157: * @deprecated this method is only here for BWC, it is no longer
158: * used by this class
159: */
160: public String replaceDoctype(StringBuffer withinContent,
161: String doctypeName, String systemId) {
162: String content = withinContent.toString();
163: int startDoctype = content.indexOf(DoctypeSupport.DOCTYPE);
164: boolean noCurrentDoctype = false;
165: if (startDoctype == -1) {
166: startDoctype = obsoleteFindStartDoctype(withinContent);
167: noCurrentDoctype = true;
168: }
169:
170: int endDoctype = startDoctype + DoctypeSupport.DOCTYPE.length();
171:
172: if (noCurrentDoctype) {
173: withinContent.insert(startDoctype,
174: DoctypeSupport.DOCTYPE_OPEN_DECL);
175: withinContent.insert(startDoctype
176: + DoctypeSupport.DOCTYPE_OPEN_DECL.length(),
177: DoctypeSupport.DOCTYPE);
178: endDoctype += DoctypeSupport.DOCTYPE_OPEN_DECL.length();
179: } else {
180: int startInternalDecl = content.indexOf('[', endDoctype);
181: if (startInternalDecl > 0) {
182: int endInternalDecl = content.indexOf(']',
183: startInternalDecl);
184: withinContent.delete(endDoctype, endInternalDecl + 1);
185: } else {
186: int endDoctypeTag = content.indexOf('>', endDoctype);
187: withinContent.delete(endDoctype, endDoctypeTag);
188: }
189: }
190:
191: int atPos = endDoctype;
192: withinContent.insert(atPos, doctypeName);
193: atPos += doctypeName.length();
194: withinContent.insert(atPos, DoctypeSupport.SYSTEM);
195: atPos += DoctypeSupport.SYSTEM.length();
196: withinContent.insert(atPos, systemId);
197: atPos += systemId.length();
198: withinContent.insert(atPos, '"');
199:
200: if (noCurrentDoctype) {
201: withinContent.insert(++atPos,
202: DoctypeSupport.DOCTYPE_CLOSE_DECL);
203: }
204: return withinContent.toString();
205: }
206:
207: /**
208: * Read DOCTYPE-replaced content from the wrapped Reader
209: * @param cbuf
210: * @param off
211: * @param len
212: * @return The number of characters read, or -1 if the end of the
213: * stream has been reached
214: * @throws IOException
215: */
216: public int read(char cbuf[], int off, int len) throws IOException {
217: int startPos = off;
218: int currentlyRead;
219: while (off - startPos < len && (currentlyRead = read()) != -1) {
220: cbuf[off++] = (char) currentlyRead;
221: }
222: return off == startPos && len != 0 ? -1 : off - startPos;
223: }
224:
225: /**
226: * Read DOCTYPE-replaced content from the wrapped Reader
227: */
228: public int read() throws IOException {
229: return support.read();
230: }
231:
232: public void close() throws IOException {
233: originalReader.close();
234: }
235: }
|