| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
| PropertyListParser |
|
| 4.166666666666667;4.167 |
| 1 | /* |
|
| 2 | Wotonomy: OpenStep design patterns for pure Java applications. |
|
| 3 | Copyright (C) 2000 Blacksmith, Inc. |
|
| 4 | ||
| 5 | This library is free software; you can redistribute it and/or |
|
| 6 | modify it under the terms of the GNU Lesser General Public |
|
| 7 | License as published by the Free Software Foundation; either |
|
| 8 | version 2.1 of the License, or (at your option) any later version. |
|
| 9 | ||
| 10 | This library is distributed in the hope that it will be useful, |
|
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
| 13 | Lesser General Public License for more details. |
|
| 14 | ||
| 15 | You should have received a copy of the GNU Lesser General Public |
|
| 16 | License along with this library; if not, see http://www.gnu.org |
|
| 17 | */ |
|
| 18 | ||
| 19 | package net.wotonomy.foundation.internal; |
|
| 20 | ||
| 21 | import java.util.*; //collections |
|
| 22 | import java.io.*; |
|
| 23 | ||
| 24 | /** |
|
| 25 | * PropertyListParser can parse a property list (plist) file or string, and |
|
| 26 | * return the top-level object represented by the plist. <p> |
|
| 27 | * |
|
| 28 | * A property list is a heirarchical data structure containing only Maps, |
|
| 29 | * Lists, and Strings -- nothing else. In other words, a property list is |
|
| 30 | * either a Map, List, or String instance, with the restrictions that the |
|
| 31 | * collections may only contain Map, List, or String instances. <p> |
|
| 32 | * |
|
| 33 | * This class can read a particularly-formatted string or file, and create |
|
| 34 | * the property list structure described. It provides a convenient means |
|
| 35 | * for having a structured data file, letting programs simply deal with the |
|
| 36 | * structure rather than having to do a lot of string parsing work as well. |
|
| 37 | * The concept is similar to Properties files, except that the values can |
|
| 38 | * be nested Maps or Lists instead of only Strings. <p> |
|
| 39 | * |
|
| 40 | * A Map is specified in a file by key/value pairs surrounded by brace |
|
| 41 | * characters. An equal sign (=) must be between the key and value, and |
|
| 42 | * there must be a semicolon (;) following the value. |
|
| 43 | * |
|
| 44 | * <pre> |
|
| 45 | * { |
|
| 46 | * key1 = value1; |
|
| 47 | * key2 = value2; |
|
| 48 | * etc... |
|
| 49 | * } |
|
| 50 | * </pre> |
|
| 51 | * |
|
| 52 | * A List is specified by a comma-separated list of values surrounded by parentheses, like: |
|
| 53 | * <pre> |
|
| 54 | * ( value1, value2, value3, etc... ) |
|
| 55 | * </pre> |
|
| 56 | * |
|
| 57 | * A String can either be quoted in the manner of a constant string in |
|
| 58 | * Java, or unquoted. If unquoted, the string can only contain |
|
| 59 | * alphanumerics, underscores (_), periods (.), dollar signs ($), colons |
|
| 60 | * (:), or forward slashes (/). If any other character appears in the |
|
| 61 | * string, it must be quoted (i.e., surrounded by " characters). |
|
| 62 | * Quoted strings may also contain \n, \t, \f, \v, \b, and \a escapes, |
|
| 63 | * octal escapes of the form \000, and unicode escapes of the form of \U |
|
| 64 | * followed by four hexadecimal characters. Any other character escaped |
|
| 65 | * by a backslash will be treated as that character, and the escaping |
|
| 66 | * backslash character will be omitted. Thus, to represent an actual |
|
| 67 | * backslash, it must appear as \\ in the quoted string. <p> |
|
| 68 | * |
|
| 69 | * All whitespace between elements is ignored, and both //-style and |
|
| 70 | * /*-style comments are allowed to appear anywhere between elements. <p> |
|
| 71 | * |
|
| 72 | * If there are any syntax errors encountered while parsing, |
|
| 73 | * RuntimeExceptions are thrown with the line number and column of the |
|
| 74 | * problem. <p> |
|
| 75 | * |
|
| 76 | * Currenty, HashMaps and ArrayLists are the actual Map and List classes |
|
| 77 | * used when creating the property list. <p> |
|
| 78 | * |
|
| 79 | * Examples: <p><blockquote> |
|
| 80 | <pre> |
|
| 81 | // This plist file represents a Map, since it starts with a '{'. |
|
| 82 | { |
|
| 83 | Map1 = { subkey1 = "foo"; }; |
|
| 84 | Map2 = |
|
| 85 | { |
|
| 86 | "key1" = "This is a quoted string."; |
|
| 87 | "key 2" = "bar\nbaz"; // the value has a newline in it |
|
| 88 | key3 = ("a", b, c, "quux quux"); // a List of four Strings |
|
| 89 | }; // We need a semicolon here, since it's following the value of the "Map2" key |
|
| 90 | ||
| 91 | List1 = (foobar,foobaz,"foo,baz", (aa, ab, ac)); // a List of 3 Strings and a List |
|
| 92 | ||
| 93 | // And now a List of two Maps |
|
| 94 | List2 = ( |
|
| 95 | { |
|
| 96 | key1 = value1; |
|
| 97 | key2 = "value 2"; |
|
| 98 | key3 = (a,b,c,d); |
|
| 99 | key4 = (); |
|
| 100 | }, // We need the comma here |
|
| 101 | { |
|
| 102 | key1 = {}; // an empty Map |
|
| 103 | key2 = "another String value"; |
|
| 104 | } |
|
| 105 | ); |
|
| 106 | } |
|
| 107 | </pre> |
|
| 108 | </blockquote> |
|
| 109 | * For those wondering, this is essentially a re-implementation of |
|
| 110 | * NeXT/Apple's property lists, except that data values are not supported. |
|
| 111 | * |
|
| 112 | * @author clindberg@blacksmith.com |
|
| 113 | * @version $Revision: 899 $ |
|
| 114 | */ |
|
| 115 | ||
| 116 | 0 | public class PropertyListParser |
| 117 | { |
|
| 118 | private char buffer[]; |
|
| 119 | private int currIndex; |
|
| 120 | private int lineNumber; |
|
| 121 | private int currLineStartIndex; |
|
| 122 | ||
| 123 | /** Reads an object (String, List, or Map) from plistString and returns it. |
|
| 124 | * RuntimeExceptions are raised if there are parse problems. |
|
| 125 | */ |
|
| 126 | public static Object propertyListFromString(String plistString) |
|
| 127 | { |
|
| 128 | 0 | PropertyListParser parser = new PropertyListParser(plistString); |
| 129 | 0 | return parser.readTopLevelObject(); |
| 130 | } |
|
| 131 | ||
| 132 | /** |
|
| 133 | * Reads all remaining characters from the Reader, and returns the |
|
| 134 | * result of propertyListFromString(). RuntimeExceptions are raised if |
|
| 135 | * there are parse problems |
|
| 136 | */ |
|
| 137 | public static Object propertyListFromReader(Reader reader) throws IOException |
|
| 138 | { |
|
| 139 | 0 | char charBuffer[] = new char[2048]; |
| 140 | 0 | StringBuffer stringBuffer = new StringBuffer(); |
| 141 | 0 | int numRead = 0; |
| 142 | ||
| 143 | 0 | while (numRead >= 0) |
| 144 | { |
|
| 145 | 0 | numRead = reader.read(charBuffer); |
| 146 | 0 | if (numRead > 0) stringBuffer.append(charBuffer, 0, numRead); |
| 147 | } |
|
| 148 | ||
| 149 | 0 | return propertyListFromString(stringBuffer.toString()); |
| 150 | } |
|
| 151 | ||
| 152 | /** |
|
| 153 | * Reads the contents of the specified file, and parses the contents. |
|
| 154 | * If any error occurs, prints out a message using System.out.println() |
|
| 155 | * and returns null. |
|
| 156 | */ |
|
| 157 | public static Object propertyListFromFile(String filename) |
|
| 158 | { |
|
| 159 | try { |
|
| 160 | 0 | FileInputStream stream = new FileInputStream(filename); |
| 161 | 0 | return propertyListFromReader(new InputStreamReader(stream)); |
| 162 | 0 | } catch (Exception exception) { |
| 163 | 0 | String errorMessage = exception.getMessage(); |
| 164 | 0 | System.out.println("Error parsing property list from "+filename+": "+errorMessage); |
| 165 | } |
|
| 166 | ||
| 167 | 0 | return null; |
| 168 | } |
|
| 169 | ||
| 170 | /** |
|
| 171 | * Creates a new PropertyListParser to parse the contents of the |
|
| 172 | * specified String. |
|
| 173 | */ |
|
| 174 | public PropertyListParser(String plistString) |
|
| 175 | { |
|
| 176 | 0 | this(plistString.toCharArray()); |
| 177 | 0 | } |
| 178 | ||
| 179 | /** |
|
| 180 | * Creates a new PropertyListParser to parse the specified char array. |
|
| 181 | */ |
|
| 182 | 0 | public PropertyListParser(char[] charArray) |
| 183 | 0 | { |
| 184 | 0 | buffer = charArray; |
| 185 | 0 | lineNumber = 1; |
| 186 | 0 | currLineStartIndex = 1; |
| 187 | 0 | currIndex = 0; |
| 188 | 0 | } |
| 189 | ||
| 190 | public Object readTopLevelObject() |
|
| 191 | { |
|
| 192 | 0 | Object plist = readObject(); |
| 193 | ||
| 194 | 0 | skipCommentWhitespace(); |
| 195 | 0 | if (!isAtEnd()) |
| 196 | { |
|
| 197 | 0 | throwParseException("Extra characters in plist string after parsing object. A plist should only contain one top-level object."); |
| 198 | } |
|
| 199 | ||
| 200 | 0 | return plist; |
| 201 | } |
|
| 202 | ||
| 203 | private void throwParseException(String errorMessage) |
|
| 204 | { |
|
| 205 | 0 | int column = currIndex - currLineStartIndex + 1; |
| 206 | 0 | throw new RuntimeException(errorMessage + " (Line " + lineNumber + ", column " + column + ")"); |
| 207 | } |
|
| 208 | ||
| 209 | private void updateLineNumberWithIndex(int lineStartIndex) |
|
| 210 | { |
|
| 211 | 0 | lineNumber++; |
| 212 | 0 | currLineStartIndex = lineStartIndex; |
| 213 | 0 | } |
| 214 | ||
| 215 | private boolean isAtEnd() |
|
| 216 | { |
|
| 217 | 0 | return currIndex >= buffer.length; |
| 218 | } |
|
| 219 | ||
| 220 | private void skipDoubleslashComment() |
|
| 221 | { |
|
| 222 | 0 | while (!isAtEnd() && buffer[currIndex] != '\n') { |
| 223 | 0 | currIndex++; |
| 224 | 0 | } |
| 225 | 0 | } |
| 226 | ||
| 227 | private void skipStandardCComment() |
|
| 228 | { |
|
| 229 | 0 | currIndex++; //skip over the starting '/' |
| 230 | ||
| 231 | 0 | while (!isAtEnd()) |
| 232 | { |
|
| 233 | 0 | if (buffer[currIndex] == '\n') |
| 234 | 0 | updateLineNumberWithIndex(currIndex+1); |
| 235 | ||
| 236 | 0 | currIndex++; |
| 237 | ||
| 238 | 0 | if (buffer[currIndex-2] == '*' && buffer[currIndex-1] == '/') |
| 239 | { |
|
| 240 | 0 | return; |
| 241 | } |
|
| 242 | } |
|
| 243 | ||
| 244 | 0 | throwParseException("Input exhausted while parsing comment"); |
| 245 | 0 | } |
| 246 | ||
| 247 | private void skipWhitespace() |
|
| 248 | { |
|
| 249 | 0 | while (!isAtEnd() && isWhitespace(buffer[currIndex])) |
| 250 | { |
|
| 251 | 0 | if (buffer[currIndex] == '\n') |
| 252 | 0 | updateLineNumberWithIndex(currIndex+1); |
| 253 | 0 | currIndex++; |
| 254 | 0 | } |
| 255 | 0 | } |
| 256 | ||
| 257 | private void skipCommentWhitespace() |
|
| 258 | { |
|
| 259 | 0 | boolean done = false; |
| 260 | ||
| 261 | 0 | while (!done) |
| 262 | { |
|
| 263 | 0 | done = true; |
| 264 | ||
| 265 | 0 | skipWhitespace(); |
| 266 | 0 | if ((buffer.length - currIndex) > 1 && buffer[currIndex] == '/') |
| 267 | { |
|
| 268 | 0 | if (buffer[currIndex+1] == '/') { |
| 269 | 0 | done = false; //iterate again |
| 270 | 0 | skipDoubleslashComment(); |
| 271 | 0 | } |
| 272 | 0 | else if (buffer[currIndex+1] == '*') { |
| 273 | 0 | done = false; //iterate again |
| 274 | 0 | skipStandardCComment(); |
| 275 | 0 | } |
| 276 | } |
|
| 277 | } |
|
| 278 | 0 | } |
| 279 | ||
| 280 | private Object readObject() |
|
| 281 | { |
|
| 282 | 0 | skipCommentWhitespace(); |
| 283 | 0 | if (isAtEnd()) return null; |
| 284 | ||
| 285 | // Data (i.e. byte[]) not supported |
|
| 286 | 0 | if (buffer[currIndex] == '"') |
| 287 | 0 | return readQuotedString(); |
| 288 | 0 | if (buffer[currIndex] == '(') |
| 289 | 0 | return readList(); |
| 290 | 0 | if (buffer[currIndex] == '{') |
| 291 | 0 | return readMap(); |
| 292 | ||
| 293 | 0 | return readUnquotedString(); |
| 294 | } |
|
| 295 | ||
| 296 | private static final byte valueForHexDigit(char c) |
|
| 297 | { |
|
| 298 | 0 | if(c >= '0' && c <= '9') return (byte)(c - '0'); |
| 299 | 0 | if(c >= 'a' && c <= 'f') return (byte)((c - 'a') + 10); |
| 300 | 0 | if(c >= 'A' && c <= 'F') return (byte)((c - 'A') + 10); |
| 301 | ||
| 302 | 0 | return 0; |
| 303 | } |
|
| 304 | ||
| 305 | private static final boolean isOctalDigit(char c) |
|
| 306 | { |
|
| 307 | 0 | return c >= '0' && c <= '7'; |
| 308 | } |
|
| 309 | ||
| 310 | private static final boolean isHexDigit(char c) |
|
| 311 | { |
|
| 312 | 0 | return (c >= '0' && c <= '9') || |
| 313 | 0 | (c >= 'a' && c <= 'f') || |
| 314 | 0 | (c >= 'A' && c <= 'F'); |
| 315 | } |
|
| 316 | ||
| 317 | 0 | private static String unquotedStringChars = "._$:/"; // chars allowed in unquoted strings |
| 318 | 0 | private static String whitespaceChars = " \t\n\r\f"; |
| 319 | ||
| 320 | private static final boolean isWhitespace(char c) |
|
| 321 | { |
|
| 322 | 0 | return whitespaceChars.indexOf(c) >= 0; |
| 323 | } |
|
| 324 | ||
| 325 | private static final boolean isValidUnquotedStringChar(char c) |
|
| 326 | { |
|
| 327 | 0 | return ((c >= 'a' && c <= 'z') || |
| 328 | 0 | (c >= 'A' && c <= 'Z') || |
| 329 | 0 | (c >= '0' && c <= '9') || |
| 330 | 0 | unquotedStringChars.indexOf(c) >= 0); |
| 331 | } |
|
| 332 | ||
| 333 | private String readUnquotedString() |
|
| 334 | { |
|
| 335 | 0 | int startIndex = currIndex; |
| 336 | ||
| 337 | 0 | while (!isAtEnd() && isValidUnquotedStringChar(buffer[currIndex])) |
| 338 | 0 | currIndex++; |
| 339 | ||
| 340 | 0 | if (startIndex == currIndex) |
| 341 | 0 | throwParseException("No allowable characters found to parse unquoted string"); |
| 342 | ||
| 343 | 0 | return new String(buffer, startIndex, currIndex - startIndex); |
| 344 | } |
|
| 345 | ||
| 346 | private String readQuotedString() |
|
| 347 | { |
|
| 348 | 0 | currIndex++; //skip over '"' |
| 349 | ||
| 350 | 0 | StringBuffer stringBuffer = new StringBuffer(); |
| 351 | 0 | int startIndex = currIndex; |
| 352 | ||
| 353 | 0 | while (!isAtEnd() && buffer[currIndex] != '"') |
| 354 | { |
|
| 355 | 0 | if (buffer[currIndex] != '\\') |
| 356 | { |
|
| 357 | 0 | if (buffer[currIndex] == '\n') |
| 358 | 0 | updateLineNumberWithIndex(currIndex+1); |
| 359 | ||
| 360 | /* |
|
| 361 | * Just increment the index -- all these characters will be |
|
| 362 | * appended in chunks, either before an escape sequence or |
|
| 363 | * at the end. |
|
| 364 | */ |
|
| 365 | 0 | currIndex++; |
| 366 | 0 | } |
| 367 | else // it's an escape |
|
| 368 | { |
|
| 369 | /* Append anything scanned past before the '\\' */ |
|
| 370 | 0 | if (startIndex < currIndex) |
| 371 | 0 | stringBuffer.append(buffer, startIndex, currIndex - startIndex); |
| 372 | 0 | currIndex++; // skip over '\\' |
| 373 | ||
| 374 | 0 | if (isAtEnd()) |
| 375 | 0 | throwParseException("Input exhausted while parsing escape sequence"); |
| 376 | ||
| 377 | 0 | switch (buffer[currIndex]) |
| 378 | { |
|
| 379 | 0 | case 't': stringBuffer.append('\t'); currIndex++; break; // tab |
| 380 | 0 | case 'n': stringBuffer.append('\n'); currIndex++; break; // newline |
| 381 | 0 | case 'r': stringBuffer.append('\r'); currIndex++; break; // carriage return |
| 382 | 0 | case 'f': stringBuffer.append('\f'); currIndex++; break; // form feed |
| 383 | 0 | case 'b': stringBuffer.append('\b'); currIndex++; break; // backspace |
| 384 | 0 | case 'a': stringBuffer.append('\007'); currIndex++; break; // bell |
| 385 | 0 | case 'v': stringBuffer.append('\013'); currIndex++; break; // vertical tab |
| 386 | case 'U': |
|
| 387 | case 'u': |
|
| 388 | { |
|
| 389 | /* A Unicode escape. Always followed by 4 hex digits. */ |
|
| 390 | 0 | currIndex++; // skip past the 'U' |
| 391 | 0 | if ((currIndex+4) > buffer.length) |
| 392 | 0 | throwParseException("Not enough chars to parse \\U sequence"); |
| 393 | ||
| 394 | 0 | if(!isHexDigit(buffer[currIndex]) || !isHexDigit(buffer[currIndex+1]) || |
| 395 | 0 | !isHexDigit(buffer[currIndex+2]) || !isHexDigit(buffer[currIndex+3])) |
| 396 | { |
|
| 397 | 0 | throwParseException("Four hex digits not found for \\U sequence"); |
| 398 | } |
|
| 399 | ||
| 400 | 0 | byte byte3 = valueForHexDigit(buffer[currIndex]); |
| 401 | 0 | byte byte2 = valueForHexDigit(buffer[currIndex+1]); |
| 402 | 0 | byte byte1 = valueForHexDigit(buffer[currIndex+2]); |
| 403 | 0 | byte byte0 = valueForHexDigit(buffer[currIndex+3]); |
| 404 | 0 | char theChar = (char)((byte3 << 12) + (byte2 << 8) + (byte1 << 4) + byte0); |
| 405 | 0 | stringBuffer.append(theChar); |
| 406 | 0 | currIndex += 4; |
| 407 | 0 | break; |
| 408 | } |
|
| 409 | case '0': case '1': case '2': case '3': |
|
| 410 | case '4': case '5': case '6': case '7': |
|
| 411 | { |
|
| 412 | /* An octal escape. Expect 1, 2, or 3 octal digits. */ |
|
| 413 | 0 | int digits = 0; |
| 414 | 0 | int value = 0; |
| 415 | ||
| 416 | 0 | do { |
| 417 | 0 | value *= 8; |
| 418 | 0 | value += (int)(buffer[currIndex] - '0'); |
| 419 | 0 | currIndex++; |
| 420 | 0 | digits++; |
| 421 | 0 | } while (digits <= 3 && !isAtEnd() && isOctalDigit(buffer[currIndex])); |
| 422 | ||
| 423 | 0 | if (value > 255) |
| 424 | 0 | throwParseException("Value too large in octal escape sequence (> 0377)"); |
| 425 | ||
| 426 | // This assumes value is in ISO Latin 1 encoding |
|
| 427 | 0 | stringBuffer.append((char)value); |
| 428 | 0 | break; |
| 429 | } |
|
| 430 | /* I guess plists can't have the \x{HEX}{HEX} escapes */ |
|
| 431 | default: |
|
| 432 | { |
|
| 433 | // Unknown escape sequence, just add the character. |
|
| 434 | // GCC warns if this isn't a '"', '\'', or '\\'... |
|
| 435 | 0 | stringBuffer.append(buffer[currIndex]); |
| 436 | 0 | if (buffer[currIndex] == '\n') |
| 437 | 0 | updateLineNumberWithIndex(currIndex+1); |
| 438 | 0 | currIndex++; |
| 439 | break; |
|
| 440 | } |
|
| 441 | } // end case |
|
| 442 | ||
| 443 | /* Reset startIndex, so a verbatim copy will now start from this index */ |
|
| 444 | 0 | startIndex = currIndex; |
| 445 | ||
| 446 | } //end '\\' escape |
|
| 447 | 0 | } |
| 448 | ||
| 449 | 0 | if (isAtEnd()) |
| 450 | 0 | throwParseException("Input exhausted while parsing quoted string"); |
| 451 | 0 | if (startIndex < currIndex) |
| 452 | 0 | stringBuffer.append(buffer, startIndex, currIndex - startIndex); |
| 453 | 0 | currIndex++; //skip past '"' |
| 454 | ||
| 455 | 0 | return stringBuffer.toString(); |
| 456 | } |
|
| 457 | ||
| 458 | private List readList() |
|
| 459 | { |
|
| 460 | 0 | List newList = new ArrayList(); |
| 461 | ||
| 462 | 0 | currIndex++; //skip over '(' |
| 463 | 0 | skipCommentWhitespace(); |
| 464 | 0 | while (!isAtEnd() && buffer[currIndex] != ')') |
| 465 | { |
|
| 466 | /* A comma is required between list elements */ |
|
| 467 | 0 | if (newList.size() > 0) |
| 468 | { |
|
| 469 | 0 | if (buffer[currIndex] != ',') |
| 470 | 0 | throwParseException("List parsing failed: expecting ','"); |
| 471 | 0 | currIndex++; |
| 472 | 0 | skipCommentWhitespace(); |
| 473 | 0 | if (isAtEnd()) |
| 474 | 0 | throwParseException("Input exhausted while parsing list"); |
| 475 | } |
|
| 476 | ||
| 477 | 0 | if (buffer[currIndex] != ')') |
| 478 | { |
|
| 479 | 0 | Object plistObject = readObject(); |
| 480 | 0 | if (plistObject == null) |
| 481 | 0 | throwParseException("List parsing failed: could not read contained object."); |
| 482 | 0 | newList.add(plistObject); |
| 483 | 0 | skipCommentWhitespace(); |
| 484 | 0 | } |
| 485 | } |
|
| 486 | ||
| 487 | 0 | if (isAtEnd()) |
| 488 | 0 | throwParseException("Input exhausted while parsing list"); |
| 489 | 0 | currIndex++; //skip past ')' |
| 490 | ||
| 491 | 0 | return newList; |
| 492 | } |
|
| 493 | ||
| 494 | private Map readMap() |
|
| 495 | { |
|
| 496 | 0 | HashMap newMap = new HashMap(); |
| 497 | ||
| 498 | 0 | currIndex++; // skip over open brace |
| 499 | 0 | skipCommentWhitespace(); |
| 500 | ||
| 501 | 0 | while (!isAtEnd() && buffer[currIndex] != '}') |
| 502 | { |
|
| 503 | Object key; |
|
| 504 | Object value; |
|
| 505 | ||
| 506 | 0 | key = readObject(); |
| 507 | 0 | if (key == null || !(key instanceof String)) |
| 508 | 0 | throwParseException("Map parsing failed: could not parse key or key is not a String"); |
| 509 | ||
| 510 | 0 | skipCommentWhitespace(); |
| 511 | 0 | if (isAtEnd() || buffer[currIndex] != '=') |
| 512 | 0 | throwParseException("Map parsing failed: expecting '='"); |
| 513 | 0 | currIndex++; //skip over '=' |
| 514 | 0 | skipCommentWhitespace(); |
| 515 | 0 | if (isAtEnd()) |
| 516 | 0 | throwParseException("Input exhausted while parsing map"); |
| 517 | ||
| 518 | 0 | value = readObject(); |
| 519 | 0 | if (value == null) |
| 520 | 0 | throwParseException("Map parsing failed: could not parse value object"); |
| 521 | ||
| 522 | 0 | skipCommentWhitespace(); |
| 523 | 0 | if (isAtEnd() || buffer[currIndex] != ';') |
| 524 | 0 | throwParseException("Map parsing failed: expecting ';'"); |
| 525 | 0 | currIndex++; //skip over ';' |
| 526 | 0 | skipCommentWhitespace(); |
| 527 | ||
| 528 | 0 | newMap.put(key, value); |
| 529 | 0 | } |
| 530 | ||
| 531 | 0 | if (isAtEnd()) |
| 532 | 0 | throwParseException("Input exhausted while parsing map"); |
| 533 | 0 | currIndex++; //skip past '}' |
| 534 | ||
| 535 | 0 | return newMap; |
| 536 | } |
|
| 537 | ||
| 538 | ||
| 539 | public static void main(String[] args) |
|
| 540 | { |
|
| 541 | 0 | String filename = args[0]; |
| 542 | 0 | Object plist = PropertyListParser.propertyListFromFile(filename); |
| 543 | 0 | System.out.println(plist); |
| 544 | 0 | } |
| 545 | } |
|
| 546 |