| /* |
| * 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"); |
| } |
| } |