blob: baab3d7f6bdb8abc1f75d04580e612f887eaffe5 [file] [log] [blame]
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package org.apache.commons.imaging.common;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.Map;
import org.apache.commons.imaging.ImageReadException;
public class BasicCParser
{
private PushbackInputStream is;
public BasicCParser(ByteArrayInputStream is)
{
this.is = new PushbackInputStream(is);
}
public String nextToken() throws IOException, ImageReadException
{
// I don't know how complete the C parsing in an XPM file
// is meant to be, this is just the very basics...
boolean inString = false;
boolean inIdentifier = false;
boolean hadBackSlash = false;
StringBuilder token = new StringBuilder();
for (int c = is.read(); c != -1; c = is.read())
{
if (inString)
{
if (c == '\\')
{
token.append('\\');
hadBackSlash = !hadBackSlash;
}
else if (c == '"')
{
token.append('"');
if (!hadBackSlash)
return token.toString();
hadBackSlash = false;
}
else if (c == '\r' || c == '\n')
throw new ImageReadException("Unterminated string in XPM file");
else
{
token.append((char)c);
hadBackSlash = false;
}
}
else if (inIdentifier)
{
if (Character.isLetterOrDigit(c) || c == '_')
token.append((char)c);
else
{
is.unread(c);
return token.toString();
}
}
else
{
if (c == '"')
{
token.append('"');
inString = true;
}
else if (Character.isLetterOrDigit(c) || c == '_')
{
token.append((char)c);
inIdentifier = true;
}
else if (c == '{' || c == '}' ||
c == '[' || c == ']' ||
c == '*' || c == ';' ||
c == '=' || c == ',')
{
token.append((char)c);
return token.toString();
}
else if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
{ /* do nothing */ }
else
throw new ImageReadException("Unhandled/invalid character '" +
((char)c) + "' found in XPM file");
}
}
if (inIdentifier)
return token.toString();
if (inString)
throw new ImageReadException("Unterminated string ends XMP file");
return null;
}
public static ByteArrayOutputStream preprocess(InputStream is,
StringBuilder firstComment, Map<String, String> defines)
throws IOException, ImageReadException
{
boolean inString = false;
boolean inComment = false;
boolean inDirective = false;
boolean hadSlash = false;
boolean hadStar = false;
boolean hadBackSlash = false;
ByteArrayOutputStream out = new ByteArrayOutputStream();
boolean seenFirstComment = (firstComment == null);
StringBuilder directiveBuffer = new StringBuilder();
for (int c = is.read(); c != -1; c = is.read())
{
if (inComment)
{
if (c == '*')
{
if (hadStar && !seenFirstComment)
firstComment.append('*');
hadStar = true;
}
else if(c == '/')
{
if (hadStar)
{
hadStar = false;
inComment = false;
seenFirstComment = true;
}
else
out.write(c);
}
else
{
if (hadStar && !seenFirstComment)
firstComment.append('*');
hadStar = false;
if (!seenFirstComment)
firstComment.append((char)c);
}
}
else if (inString)
{
if (c == '\\')
{
if (hadBackSlash)
out.write('\\');
hadBackSlash = true;
}
else if (c == '"')
{
if (hadBackSlash)
{
out.write('\\');
hadBackSlash = false;
}
else
inString = false;
out.write('"');
}
else if (c == '\r' || c == '\n')
throw new ImageReadException("Unterminated string in file");
else
{
if (hadBackSlash)
{
out.write('\\');
hadBackSlash = false;
}
out.write(c);
}
}
else if (inDirective)
{
if (c == '\r' || c == '\n')
{
inDirective = false;
String[] tokens = tokenizeRow(directiveBuffer.toString());
if (tokens.length < 2 || tokens.length > 3)
throw new ImageReadException("Bad preprocessor directive");
if (!tokens[0].equals("define"))
throw new ImageReadException("Invalid/unsupported " +
"preprocessor directive '" + tokens[0] + "'");
defines.put(tokens[1], (tokens.length == 3) ? tokens[2] : null);
directiveBuffer.setLength(0);
}
else
directiveBuffer.append((char)c);
}
else
{
if (c == '/')
{
if (hadSlash)
out.write('/');
hadSlash = true;
}
else if (c == '*')
{
if (hadSlash)
{
inComment = true;
hadSlash = false;
}
else
out.write(c);
}
else if (c == '"')
{
if (hadSlash)
out.write('/');
hadSlash = false;
out.write(c);
inString = true;
}
else if (c == '#')
{
if (defines == null)
throw new ImageReadException("Unexpected preprocessor directive");
inDirective = true;
}
else
{
if (hadSlash)
out.write('/');
hadSlash = false;
out.write(c);
// Only whitespace allowed before first comment:
if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
seenFirstComment = true;
}
}
}
if (hadSlash)
out.write('/');
if (hadStar)
out.write('*');
if (inString)
throw new ImageReadException("Unterminated string at the end of file");
if (inComment)
throw new ImageReadException("Unterminated comment at the end of file");
return out;
}
public static String[] tokenizeRow(String row)
{
String[] tokens = row.split("[ \t]");
int numLiveTokens = 0;
for (int i = 0; i < tokens.length; i++)
{
if (tokens[i] != null && tokens[i].length() > 0)
++numLiveTokens;
}
String[] liveTokens = new String[numLiveTokens];
int next = 0;
for (int i = 0; i < tokens.length; i++)
{
if (tokens[i] != null && tokens[i].length() > 0)
liveTokens[next++] = tokens[i];
}
return liveTokens;
}
public static void unescapeString(StringBuilder stringBuilder, String string)
throws ImageReadException
{
if (string.length() < 2)
throw new ImageReadException("Parsing XPM file failed, " +
"string is too short");
if (string.charAt(0) != '"' || string.charAt(string.length() - 1) != '"')
throw new ImageReadException("Parsing XPM file failed, " +
"string not surrounded by '\"'");
boolean hadBackSlash = false;
for (int i = 1; i < (string.length() - 1); i++)
{
char c = string.charAt(i);
if (hadBackSlash)
{
if (c == '\\')
stringBuilder.append('\\');
else if (c == '"')
stringBuilder.append('"');
else if (c == '\'')
stringBuilder.append('\'');
else if (c == 'x')
{
if (i + 2 >= string.length())
throw new ImageReadException("Parsing XPM file failed, " +
"hex constant in string too short");
char hex1 = string.charAt(i + 1);
char hex2 = string.charAt(i + 2);
i += 2;
int constant;
try
{
constant = Integer.parseInt("" + hex1 + hex2, 16);
}
catch (NumberFormatException nfe)
{
throw new ImageReadException("Parsing XPM file failed, " +
"hex constant invalid", nfe);
}
stringBuilder.append((char)constant);
}
else if (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' ||
c == '5' || c == '6' || c == '7')
{
int length = 1;
if (i+1 < string.length() && '0' <= string.charAt(i+1) &&
string.charAt(i+1) <= '7')
++length;
if (i+2 < string.length() && '0' <= string.charAt(i+2) &&
string.charAt(i+2) <= '7')
++length;
int constant = 0;
for (int j = 0; j < length; j++)
{
constant *= 8;
constant += (string.charAt(i + j) - '0');
}
i += length - 1;
stringBuilder.append((char)constant);
}
else if (c == 'a')
stringBuilder.append((char)0x07);
else if (c == 'b')
stringBuilder.append((char)0x08);
else if (c == 'f')
stringBuilder.append((char)0x0c);
else if (c == 'n')
stringBuilder.append((char)0x0a);
else if (c == 'r')
stringBuilder.append((char)0x0d);
else if (c == 't')
stringBuilder.append((char)0x09);
else if (c == 'v')
stringBuilder.append((char)0x0b);
else
throw new ImageReadException("Parsing XPM file failed, " +
"invalid escape sequence");
hadBackSlash = false;
}
else
{
if (c == '\\')
hadBackSlash = true;
else if (c == '"')
throw new ImageReadException("Parsing XPM file failed, " +
"extra '\"' found in string");
else
stringBuilder.append(c);
}
}
if (hadBackSlash)
throw new ImageReadException("Parsing XPM file failed, " +
"unterminated escape sequence found in string");
}
}