public-transport-enabler/src/de/schildbach/pte/util/StringReplaceReader.java
andreas.schildbach@gmail.com c99e99b0f7 workaround for < and > in XML attributes
git-svn-id: https://public-transport-enabler.googlecode.com/svn/trunk@704 0924bc21-9374-b0fa-ee44-9ff1593b38f0
2011-06-16 20:48:29 +00:00

367 lines
11 KiB
Java

package de.schildbach.pte.util;
/*
* Copyright (C) 1997 Roger Whitney <whitney@cs.sdsu.edu>
*
* This file is part of the San Diego State University Java Library.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.io.BufferedReader;
import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
/**
* Given a string <b>pattern</b>, a string <b>replacementPattern</b> and an input stream, this class will replace all
* occurances of <b>pattern</b> with <b>replacementPattern</b> in the inputstream. You can give multiple
* pattern-replacementPattern pairs. Multiple pairs are done in order they are given. If first pair is "cat"-"dog" and
* second pair is "dog"-"house", then the result will be all occurences of "cat" or "dog" will be replaced with "house".
*
* @version 0.6 21 August 1997
* @since version 0.5, Fixed error that occured when input was shorter than the pattern
* @author Roger Whitney (<a href=mailto:whitney@cs.sdsu.edu>whitney@cs.sdsu.edu</a>)
*/
public class StringReplaceReader extends FilterReader implements Cloneable
{
protected CharQueue outputBuffer; // holds filtered data
protected char[] inputBuffer;
protected int[] shiftTable; // quick search shift table
protected int inputBufferCharCount; // number of chars in inputBuffer
protected char[] patternToFind = null;
protected char[] replacementPattern = null;
protected boolean reachedEOF = false;
protected static int EOFIndicator = -1;
protected static int DEFAULT_BUFFER_SIZE = 1024;
/**
* Create an StringReplaceReader object that will replace all occurrences ofpattern with replacementPattern in the
* Reader in.
*/
public StringReplaceReader(Reader in, String pattern, String replacementPattern)
{
super(in);
patternToFind = pattern.toCharArray();
this.replacementPattern = replacementPattern.toCharArray();
allocateBuffers();
}
/**
* Create an StringReplaceReader object that will replace all occurrences of pattern with replacementPattern in the
* inputstream in.
*/
public StringReplaceReader(InputStream in, String pattern, String replacementPattern)
{
this(new BufferedReader(new InputStreamReader(in)), pattern, replacementPattern);
}
/**
* Create an StringReplaceReader object that will replace all occurrences of pattern with replacementPattern in the
* string input.
*/
public StringReplaceReader(String input, String pattern, String replacementPattern)
{
this(new StringReader(input), pattern, replacementPattern);
}
/**
* Returns the entire contents of the input stream.
*/
public String contents() throws IOException
{
StringBuffer contents = new StringBuffer(1024);
int readSize = 512;
char[] filteredChars = new char[readSize];
int charsRead = read(filteredChars, 0, readSize);
while (charsRead != EOFIndicator)
{
contents.append(filteredChars, 0, charsRead);
charsRead = read(filteredChars, 0, readSize);
}
return contents.toString();
}
/**
* Adds another pattern-replacementPattern pair. All occurrences of pattern will be replaced with
* replacementPattern.
*
* @exception OutOfMemoryError
* if there is not enough memory to add new pattern-replacementPattern pair
*/
public void replace(String pattern, String replacementPattern) throws OutOfMemoryError
{
// Chain StringReplaceReader objects. Clone current object
// add clone to input stream to insure it filters before this
// object, which gets the new pattern-replacement pair
if (patternToFind != null)
{
// Replace this with clone
try
{
StringReplaceReader currentReplace = (StringReplaceReader) this.clone();
in = currentReplace;
}
catch (CloneNotSupportedException x)
{
}
}
patternToFind = pattern.toCharArray();
this.replacementPattern = replacementPattern.toCharArray();
allocateBuffers();
reachedEOF = false;
}
/**
* Read characters into a portion of an array. This method will block until some input is available, an I/O error
* occurs, or the end of the stream is reached.
*
* @parm buffer Destination buffer
* @parm offset location in buffer to start storing characters
* @parm charsToRead maximum characters to read
* @return number of characters actually read, -1 if reah EOF on reading first character
* @exception IOException
* if an I/O error occurs
*/
@Override
public int read(char[] buffer, int offset, int charsToRead) throws IOException
{
int charsRead = 0;
while ((charsRead < charsToRead) && (!eof()))
{
if (outputBuffer.isEmpty())
{
fillInputWindow();
filterInput();
}
charsRead += outputBuffer.dequeue(buffer, offset + charsRead, charsToRead - charsRead);
}
if (charsRead > 0)
return charsRead;
else if (outputBuffer.size() > 0)
{
charsRead = outputBuffer.dequeue(buffer, offset, charsToRead);
return charsRead;
}
else if ((eof()) && (inputBufferCharCount > 0) && (inputBufferCharCount < patternToFind.length))
{
// remaining input is less than length of pattern
transferRemainingInputToOutputBuffer();
System.out.println(">> End << " + outputBuffer);
charsRead = outputBuffer.dequeue(buffer, offset, charsToRead);
return charsRead;
}
else if (eof())
return EOFIndicator;
else
// this should never happen
throw new IOException("Read attempted. Did not reach EOF and " + " no chars were read");
}
/**
* Call when remaining input is less than the pattern size, so pattern can not exist in remaining input. Just shift
* all input to output. Assumes that have reached EOF and inputBufferCharCount < patternToFind.length
*/
private void transferRemainingInputToOutputBuffer()
{
outputBuffer.enqueue(inputBuffer, 0, inputBufferCharCount);
inputBufferCharCount = 0;
}
/**
* Returns the next character in the inputstream with string replacement done.
*
* @exception IOException
* if error occurs reading io stream
*/
@Override
public int read() throws IOException
{
char[] output = new char[1];
int charsRead = read(output, 0, 1);
if (charsRead == EOFIndicator)
return EOFIndicator;
else if (charsRead == 1)
return output[0];
else
throw new IOException("Single Read attempted. Did not reach EOF and " + " no chars were read");
}
/**
* Determines if a previous ASCII I/O operation caught End Of File.
*
* @return <i>true</i> if end of file was reached.
*/
public boolean eof()
{
return reachedEOF;
}
/**
* Read inpout to see if we have found the pattern. <B>Requires:</B> When this is called we have already have read
* first character in pattern.<BR>
* <B>Side Effects: </B> After attempt to find pattern, output buffer contains either the replacement pattern or all
* characters we konw are not part of pattern.
*/
protected void filterInput() throws IOException
{
// Use quick-search to find pattern. Fill inputBuffer with text.
// Process all text in inputBuffer. Place processed text in
// outputBuffer.
int searchStart = 0;
int windowStart = 0;
int patternLength = patternToFind.length;
// Search until pattern extends past end of inputBuffer
while (searchStart < inputBufferCharCount - patternLength + 1)
{
boolean foundPattern = true;
// The search
for (int index = 0; index < patternLength; index++)
if (patternToFind[index] != inputBuffer[index + searchStart])
{
foundPattern = false;
break; // for loop
}
if (foundPattern)
{
// move text before pattern
outputBuffer.enqueue(inputBuffer, windowStart, searchStart - windowStart);
replacementPatternToBuffer();
windowStart = searchStart + patternLength;
searchStart = windowStart;
}
else
{
// look farther along in inputBuffer
int charLocationAfterPattern = searchStart + patternLength;
if (charLocationAfterPattern >= inputBufferCharCount)
searchStart += 1;
else
searchStart += getShift(inputBuffer[charLocationAfterPattern]);
}
}
if (searchStart > inputBufferCharCount)
searchStart = inputBufferCharCount;
// move chars already searched
if (reachedEOF)
{
outputBuffer.enqueue(inputBuffer, windowStart, inputBufferCharCount - windowStart);
inputBufferCharCount = 0;
}
else
{
outputBuffer.enqueue(inputBuffer, windowStart, searchStart - windowStart);
System.arraycopy(inputBuffer, searchStart, inputBuffer, 0, inputBufferCharCount - searchStart);
inputBufferCharCount = inputBufferCharCount - searchStart;
}
}
/**
* Fill sliding input window with chars from input Read until window is full or reach EOF
*/
final protected void fillInputWindow() throws IOException
{
int charsToRead = inputBuffer.length - inputBufferCharCount;
int firstEmptySlotInWindow = inputBufferCharCount;
int charsRead = in.read(inputBuffer, firstEmptySlotInWindow, charsToRead);
if (charsRead == charsToRead) // full read
{
inputBufferCharCount = inputBufferCharCount + charsRead;
charsToRead = 0;
}
else if (charsRead > 0) // parial read
{
inputBufferCharCount = inputBufferCharCount + charsRead;
charsToRead = charsToRead - charsRead;
}
else if (charsRead == EOFIndicator)
{
reachedEOF = true;
}
else
throw new IOException("Read attempted. Did not reach EOF and " + " no chars were read");
}
/**
* Return the number of positions we can shift pattern when findMyShift is character in inputBuffer after the
* pattern
*/
protected int getShift(char findMyShift)
{
if (findMyShift >= shiftTable.length)
return 1;
else
return shiftTable[findMyShift];
}
/**
* Put replacement pattern in output buffer. Subclass overrides for more complex replacement
*/
protected void replacementPatternToBuffer()
{
outputBuffer.enqueue(replacementPattern);
}
private void allocateBuffers()
{
outputBuffer = new CharQueue(DEFAULT_BUFFER_SIZE);
inputBuffer = new char[Math.max(patternToFind.length + 1, DEFAULT_BUFFER_SIZE)];
inputBufferCharCount = 0;
// allocate for most ascii characters
shiftTable = new int[126];
// build shiftTable for quick search
// Entry for character X contains how far to shift
// pattern when pattern does not match text and
// character X is the character in text after end of
// pattern
// Default for characters not in pattern
for (int k = 0; k < shiftTable.length; k++)
shiftTable[k] = patternToFind.length + 1;
for (int k = 0; k < patternToFind.length; k++)
if (patternToFind[k] < shiftTable.length)
shiftTable[patternToFind[k]] = patternToFind.length - k;
}
}