View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.common;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.PushbackInputStream;
24  import java.util.Map;
25  
26  import org.apache.commons.imaging.ImageReadException;
27  
28  /**
29   * A rudimentary preprocessor and parser for the C programming
30   * language.
31   *
32   * FIXME replace this by a parser generated via ANTLR (if we really need it?!)
33   */
34  public class BasicCParser {
35      private final PushbackInputStream is;
36  
37      public BasicCParser(final ByteArrayInputStream is) {
38          this.is = new PushbackInputStream(is);
39      }
40  
41      public String nextToken() throws IOException, ImageReadException {
42          // I don't know how complete the C parsing in an XPM file
43          // is meant to be, this is just the very basics...
44  
45          boolean inString = false;
46          boolean inIdentifier = false;
47          boolean hadBackSlash = false;
48          final StringBuilder token = new StringBuilder();
49          for (int c = is.read(); c != -1; c = is.read()) {
50              if (inString) {
51                  if (c == '\\') {
52                      token.append('\\');
53                      hadBackSlash = !hadBackSlash;
54                  } else if (c == '"') {
55                      token.append('"');
56                      if (!hadBackSlash) {
57                          return token.toString();
58                      }
59                      hadBackSlash = false;
60                  } else if (c == '\r' || c == '\n') {
61                      throw new ImageReadException(
62                              "Unterminated string in XPM file");
63                  } else {
64                      token.append((char) c);
65                      hadBackSlash = false;
66                  }
67              } else if (inIdentifier) {
68                  if (Character.isLetterOrDigit(c) || c == '_') {
69                      token.append((char) c);
70                  } else {
71                      is.unread(c);
72                      return token.toString();
73                  }
74              } else {
75                  if (c == '"') {
76                      token.append('"');
77                      inString = true;
78                  } else if (Character.isLetterOrDigit(c) || c == '_') {
79                      token.append((char) c);
80                      inIdentifier = true;
81                  } else if (c == '{' || c == '}' || c == '[' || c == ']'
82                          || c == '*' || c == ';' || c == '=' || c == ',') {
83                      token.append((char) c);
84                      return token.toString();
85                  } else if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
86                      // ignore
87                  } else {
88                      throw new ImageReadException(
89                              "Unhandled/invalid character '" + ((char) c)
90                                      + "' found in XPM file");
91                  }
92              }
93          }
94  
95          if (inIdentifier) {
96              return token.toString();
97          }
98          if (inString) {
99              throw new ImageReadException("Unterminated string ends XMP file");
100         }
101         return null;
102     }
103 
104     public static ByteArrayOutputStream preprocess(final InputStream is,
105             final StringBuilder firstComment, final Map<String, String> defines)
106             throws IOException, ImageReadException {
107         boolean inSingleQuotes = false;
108         boolean inString = false;
109         boolean inComment = false;
110         boolean inDirective = false;
111         boolean hadSlash = false;
112         boolean hadStar = false;
113         boolean hadBackSlash = false;
114         final ByteArrayOutputStream out = new ByteArrayOutputStream();
115         boolean seenFirstComment = (firstComment == null);
116         final StringBuilder directiveBuffer = new StringBuilder();
117         for (int c = is.read(); c != -1; c = is.read()) {
118             if (inComment) {
119                 if (c == '*') {
120                     if (hadStar && !seenFirstComment) {
121                         firstComment.append('*');
122                     }
123                     hadStar = true;
124                 } else if (c == '/') {
125                     if (hadStar) {
126                         hadStar = false;
127                         inComment = false;
128                         seenFirstComment = true;
129                     } else {
130                         if (!seenFirstComment) {
131                             firstComment.append((char) c);
132                         }
133                     }
134                 } else {
135                     if (hadStar && !seenFirstComment) {
136                         firstComment.append('*');
137                     }
138                     hadStar = false;
139                     if (!seenFirstComment) {
140                         firstComment.append((char) c);
141                     }
142                 }
143             } else if (inSingleQuotes) {
144                 if (c == '\\') {
145                     if (hadBackSlash) {
146                         out.write('\\');
147                         out.write('\\');
148                         hadBackSlash = false;
149                     } else {
150                         hadBackSlash = true;
151                     }
152                 } else if (c == '\'') {
153                     if (hadBackSlash) {
154                         out.write('\\');
155                         hadBackSlash = false;
156                     } else {
157                         inSingleQuotes = false;
158                     }
159                     out.write('\'');
160                 } else if (c == '\r' || c == '\n') {
161                     throw new ImageReadException("Unterminated single quote in file");
162                 } else {
163                     if (hadBackSlash) {
164                         out.write('\\');
165                         hadBackSlash = false;
166                     }
167                     out.write(c);
168                 }
169             } else if (inString) {
170                 if (c == '\\') {
171                     if (hadBackSlash) {
172                         out.write('\\');
173                         out.write('\\');
174                         hadBackSlash = false;
175                     } else {
176                         hadBackSlash = true;
177                     }
178                 } else if (c == '"') {
179                     if (hadBackSlash) {
180                         out.write('\\');
181                         hadBackSlash = false;
182                     } else {
183                         inString = false;
184                     }
185                     out.write('"');
186                 } else if (c == '\r' || c == '\n') {
187                     throw new ImageReadException("Unterminated string in file");
188                 } else {
189                     if (hadBackSlash) {
190                         out.write('\\');
191                         hadBackSlash = false;
192                     }
193                     out.write(c);
194                 }
195             } else if (inDirective) {
196                 if (c == '\r' || c == '\n') {
197                     inDirective = false;
198                     final String[] tokens = tokenizeRow(directiveBuffer.toString());
199                     if (tokens.length < 2 || tokens.length > 3) {
200                         throw new ImageReadException("Bad preprocessor directive");
201                     }
202                     if (!tokens[0].equals("define")) {
203                         throw new ImageReadException("Invalid/unsupported " 
204                                 + "preprocessor directive '" + tokens[0] + "'");
205                     }
206                     defines.put(tokens[1], (tokens.length == 3) ? tokens[2]
207                             : null);
208                     directiveBuffer.setLength(0);
209                 } else {
210                     directiveBuffer.append((char) c);
211                 }
212             } else {
213                 if (c == '/') {
214                     if (hadSlash) {
215                         out.write('/');
216                     }
217                     hadSlash = true;
218                 } else if (c == '*') {
219                     if (hadSlash) {
220                         inComment = true;
221                         hadSlash = false;
222                     } else {
223                         out.write(c);
224                     }
225                 } else if (c == '\'') {
226                     if (hadSlash) {
227                         out.write('/');
228                     }
229                     hadSlash = false;
230                     out.write(c);
231                     inSingleQuotes = true;
232                 } else if (c == '"') {
233                     if (hadSlash) {
234                         out.write('/');
235                     }
236                     hadSlash = false;
237                     out.write(c);
238                     inString = true;
239                 } else if (c == '#') {
240                     if (defines == null) {
241                         throw new ImageReadException("Unexpected preprocessor directive");
242                     }
243                     inDirective = true;
244                 } else {
245                     if (hadSlash) {
246                         out.write('/');
247                     }
248                     hadSlash = false;
249                     out.write(c);
250                     // Only whitespace allowed before first comment:
251                     if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
252                         seenFirstComment = true;
253                     }
254                 }
255             }
256         }
257         if (hadSlash) {
258             out.write('/');
259         }
260         if (hadStar) {
261             out.write('*');
262         }
263         if (inString) {
264             throw new ImageReadException("Unterminated string at the end of file");
265         }
266         if (inComment) {
267             throw new ImageReadException("Unterminated comment at the end of file");
268         }
269         return out;
270     }
271 
272     public static String[] tokenizeRow(final String row) {
273         final String[] tokens = row.split("[ \t]");
274         int numLiveTokens = 0;
275         for (final String token : tokens) {
276             if (token != null && token.length() > 0) {
277                 ++numLiveTokens;
278             }
279         }
280         final String[] liveTokens = new String[numLiveTokens];
281         int next = 0;
282         for (final String token : tokens) {
283             if (token != null && token.length() > 0) {
284                 liveTokens[next++] = token;
285             }
286         }
287         return liveTokens;
288     }
289 
290     public static void unescapeString(final StringBuilder stringBuilder, final String string)
291             throws ImageReadException {
292         if (string.length() < 2) {
293             throw new ImageReadException("Parsing XPM file failed, "
294                     + "string is too short");
295         }
296         if (string.charAt(0) != '"'
297                 || string.charAt(string.length() - 1) != '"') {
298             throw new ImageReadException("Parsing XPM file failed, "
299                     + "string not surrounded by '\"'");
300         }
301         boolean hadBackSlash = false;
302         for (int i = 1; i < (string.length() - 1); i++) {
303             final char c = string.charAt(i);
304             if (hadBackSlash) {
305                 if (c == '\\') {
306                     stringBuilder.append('\\');
307                 } else if (c == '"') {
308                     stringBuilder.append('"');
309                 } else if (c == '\'') {
310                     stringBuilder.append('\'');
311                 } else if (c == 'x') {
312                     if (i + 2 >= string.length()) {
313                         throw new ImageReadException(
314                                 "Parsing XPM file failed, "
315                                         + "hex constant in string too short");
316                     }
317                     final char hex1 = string.charAt(i + 1);
318                     final char hex2 = string.charAt(i + 2);
319                     i += 2;
320                     int constant;
321                     try {
322                         constant = Integer.parseInt(Character.toString(hex1) + Character.toString(hex2), 16);
323                     } catch (final NumberFormatException nfe) {
324                         throw new ImageReadException(
325                                 "Parsing XPM file failed, "
326                                         + "hex constant invalid", nfe);
327                     }
328                     stringBuilder.append((char) constant);
329                 } else if (c == '0' || c == '1' || c == '2' || c == '3'
330                         || c == '4' || c == '5' || c == '6' || c == '7') {
331                     int length = 1;
332                     if (i + 1 < string.length() && '0' <= string.charAt(i + 1)
333                             && string.charAt(i + 1) <= '7') {
334                         ++length;
335                     }
336                     if (i + 2 < string.length() && '0' <= string.charAt(i + 2)
337                             && string.charAt(i + 2) <= '7') {
338                         ++length;
339                     }
340                     int constant = 0;
341                     for (int j = 0; j < length; j++) {
342                         constant *= 8;
343                         constant += (string.charAt(i + j) - '0');
344                     }
345                     i += length - 1;
346                     stringBuilder.append((char) constant);
347                 } else if (c == 'a') {
348                     stringBuilder.append((char) 0x07);
349                 } else if (c == 'b') {
350                     stringBuilder.append((char) 0x08);
351                 } else if (c == 'f') {
352                     stringBuilder.append((char) 0x0c);
353                 } else if (c == 'n') {
354                     stringBuilder.append((char) 0x0a);
355                 } else if (c == 'r') {
356                     stringBuilder.append((char) 0x0d);
357                 } else if (c == 't') {
358                     stringBuilder.append((char) 0x09);
359                 } else if (c == 'v') {
360                     stringBuilder.append((char) 0x0b);
361                 } else {
362                     throw new ImageReadException("Parsing XPM file failed, "
363                             + "invalid escape sequence");
364                 }
365                 hadBackSlash = false;
366             } else {
367                 if (c == '\\') {
368                     hadBackSlash = true;
369                 } else if (c == '"') {
370                     throw new ImageReadException("Parsing XPM file failed, "
371                             + "extra '\"' found in string");
372                 } else {
373                     stringBuilder.append(c);
374                 }
375             }
376         }
377         if (hadBackSlash) {
378             throw new ImageReadException("Parsing XPM file failed, "
379                     + "unterminated escape sequence found in string");
380         }
381     }
382 }