All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.main.java.com.dd.plist.ASCIIPropertyListParser Maven / Gradle / Ivy

Go to download

This library enables Java applications to work with property lists in various formats. Supported formats for reading and writing are OS X/iOS binary and XML property lists. ASCII property lists are also supported. The library also provides access to basic functions of NeXTSTEP/Cocoa classes like NSDictionary, NSArray, etc.

The newest version!
/*
 * plist - An open source library to parse and generate property lists
 * Copyright (C) 2014 Daniel Dreibrodt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.dd.plist;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.text.StringCharacterIterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

/**
 * 

* Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format. This parser * is based on the recursive descent paradigm, but the underlying grammar is not explicitly * defined. *

*

* Resources on ASCII property list format: *

* * * @author Daniel Dreibrodt */ public final class ASCIIPropertyListParser { /** * The space character token. */ public static final char WHITESPACE_SPACE = ' '; /** * The tab character token. */ public static final char WHITESPACE_TAB = '\t'; /** * The newline character token. */ public static final char WHITESPACE_NEWLINE = '\n'; /** * The carriage return character token. */ public static final char WHITESPACE_CARRIAGE_RETURN = '\r'; /** * The token marking the beginning of an array. */ public static final char ARRAY_BEGIN_TOKEN = '('; /** * The token marking the end of an array. */ public static final char ARRAY_END_TOKEN = ')'; /** * The token marking the end of an array element. */ public static final char ARRAY_ITEM_DELIMITER_TOKEN = ','; /** * The token marking the beginning of a dictionary. */ public static final char DICTIONARY_BEGIN_TOKEN = '{'; /** * The token marking the end of a dictionary. */ public static final char DICTIONARY_END_TOKEN = '}'; /** * The token marking the assignment of a value to a dictionary key. */ public static final char DICTIONARY_ASSIGN_TOKEN = '='; /** * The token marking the end of a dictionary entry. */ public static final char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; /** * The token marking the beginning of a quoted string. */ public static final char QUOTEDSTRING_BEGIN_TOKEN = '"'; /** * The token marking the end of a quoted string. */ public static final char QUOTEDSTRING_END_TOKEN = '"'; /** * The token marking the beginning of an escape sequence in a quoted string. */ public static final char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; /** * The token marking the beginning of a data element. */ public static final char DATA_BEGIN_TOKEN = '<'; /** * The token marking the end of a data element. */ public static final char DATA_END_TOKEN = '>'; /** * The token marking the beginning of a data element in Base-64 encoding. */ public static final char DATA_BASE64_BEGIN_TOKEN = '['; /** * The token marking the end of a data element in Base-64 encoding. */ public static final char DATA_BASE64_END_TOKEN = ']'; /** * The token marking the beginning of a GnuStep object. */ public static final char DATA_GSOBJECT_BEGIN_TOKEN = '*'; /** * The token marking the beginning of a GnuStep date. */ public static final char DATA_GSDATE_BEGIN_TOKEN = 'D'; /** * The token marking the beginning of a GnuStep boolean value. */ public static final char DATA_GSBOOL_BEGIN_TOKEN = 'B'; /** * The token representing the boolean value {@code true} in the GnuStep format. */ public static final char DATA_GSBOOL_TRUE_TOKEN = 'Y'; /** * The token representing the boolean value {@code false} in the GnuStep format. */ public static final char DATA_GSBOOL_FALSE_TOKEN = 'N'; /** * The token marking the beginning of a GnuStep integer value. */ public static final char DATA_GSINT_BEGIN_TOKEN = 'I'; /** * The token marking the beginning of a GnuStep real value. */ public static final char DATA_GSREAL_BEGIN_TOKEN = 'R'; /** * The token that separates the parts of a date value (year, month and day). */ public static final char DATE_DATE_FIELD_DELIMITER = '-'; /** * The token that separates the parts of a time value (hour, minute and second). */ public static final char DATE_TIME_FIELD_DELIMITER = ':'; /** * The token that separates the date and time in the GnuStep format. */ public static final char DATE_GS_DATE_TIME_DELIMITER = ' '; /** * The token that marks the beginning of the time zone in the Apple format. */ public static final char DATE_APPLE_DATE_TIME_DELIMITER = 'T'; /** * The token that marks the end of the time zone in the Apple format. */ public static final char DATE_APPLE_END_TOKEN = 'Z'; /** * The token marking the beginning of a comment. */ public static final char COMMENT_BEGIN_TOKEN = '/'; /** * The token marking a comment to be multi-line. */ public static final char MULTILINE_COMMENT_SECOND_TOKEN = '*'; /** * The token marking a comment to be single-line. */ public static final char SINGLELINE_COMMENT_SECOND_TOKEN = '/'; /** * The token marking the end of a multi-line comment. Must be preceded by a * {@link ASCIIPropertyListParser#MULTILINE_COMMENT_SECOND_TOKEN}. */ public static final char MULTILINE_COMMENT_END_TOKEN = '/'; /** * Property list source data */ private final char[] data; /** * Current parsing index */ private int index; /** * Current line number. */ private int lineNo = 1; /** * The index at which the current line began. */ private int lineBeginning = -1; /** * Creates a new parser for the given property list content. * * @param propertyListContent The content of the property list that is to be parsed. * @param encoding The name of a supported {@link java.nio.charset.Charset charset} to * decode the property list. * @throws java.io.UnsupportedEncodingException If no support for the named charset is available * in this instance of the Java virtual machine. */ private ASCIIPropertyListParser(byte[] propertyListContent, String encoding) throws UnsupportedEncodingException { this(new String(propertyListContent, encoding).toCharArray()); } /** * Creates a new parser for the given property list content. * * @param propertyListContent The content of the property list that is to be parsed. */ private ASCIIPropertyListParser(char[] propertyListContent) { this.data = propertyListContent; } /** * Parses an ASCII property list file. * * @param f The ASCII property list file. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input stream. */ public static NSObject parse(File f) throws IOException, ParseException { return parse(f.toPath()); } /** * Parses an ASCII property list file. * * @param f The ASCII property list file. * @param encoding The name of a supported {@link java.nio.charset.Charset charset} to decode the * property list. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input * stream. * @throws java.io.UnsupportedEncodingException If no support for the named charset is available * in this instance of the Java virtual machine. */ public static NSObject parse(File f, String encoding) throws IOException, ParseException { return parse(f.toPath(), encoding); } /** * Parses an ASCII property list file. * * @param path The path to the ASCII property list file. * @param encoding The name of a supported {@link java.nio.charset.Charset charset} to decode the * property list. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input * stream. * @throws java.io.UnsupportedEncodingException If no support for the named charset is available * in this instance of the Java virtual machine. */ public static NSObject parse(Path path, String encoding) throws IOException, ParseException { try (InputStream fileInputStream = Files.newInputStream(path)) { return parse(fileInputStream, encoding); } } /** * Parses an ASCII property list file. * * @param path The path to the ASCII property list file. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input stream. */ public static NSObject parse(Path path) throws IOException, ParseException { try (InputStream fileInputStream = Files.newInputStream(path)) { return parse(fileInputStream); } } /** * Parses an ASCII property list from an input stream. This method does not close the specified * input stream. * * @param in The input stream that provides the property list's data. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input stream. */ public static NSObject parse(InputStream in) throws ParseException, IOException { return parse(PropertyListParser.readAll(in)); } /** * Parses an ASCII property list from an input stream. This method does not close the specified * input stream. * * @param in The input stream that points to the property list's data. * @param encoding The name of a supported {@link java.nio.charset.Charset charset} to decode the * property list. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input * stream. * @throws java.io.UnsupportedEncodingException If no support for the named charset is available * in this instance of the Java virtual machine. */ public static NSObject parse(InputStream in, String encoding) throws ParseException, IOException { return parse(PropertyListParser.readAll(in), encoding); } /** * Parses an ASCII property list from a {@link Reader}. This method does not close the specified * reader. * * @param reader The reader that provides the property list's data. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. * @throws java.io.IOException If an error occurs while reading from the input reader. */ public static NSObject parse(Reader reader) throws ParseException, IOException { Objects.requireNonNull(reader, "The specified reader is null"); CharArrayWriter charArrayWriter = new CharArrayWriter(); char[] buf = new char[4096]; int read; while ((read = reader.read(buf)) >= 0) { charArrayWriter.write(buf, 0, read); } ASCIIPropertyListParser parser = new ASCIIPropertyListParser(charArrayWriter.toCharArray()); return parser.parse(); } /** * Parses an ASCII property list from a {@link String} * * @param plistData A string containing the property list's data. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws java.text.ParseException If an error occurs during parsing. */ public static NSObject parse(String plistData) throws ParseException { ASCIIPropertyListParser parser = new ASCIIPropertyListParser(plistData.toCharArray()); return parser.parse(); } /** * Parses an ASCII property list from a byte array. * * @param bytes The ASCII property list data. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws ParseException If an error occurs during parsing. */ public static NSObject parse(byte[] bytes) throws ParseException { String charset = ByteOrderMarkReader.detect(bytes); if (charset == null) { charset = "UTF-8"; } try { return parse(bytes, charset); } catch (UnsupportedEncodingException e) { // Unlikely to happen as only standard codepages are requested throw new RuntimeException( "Unsupported property list encoding (" + charset + "): " + e.getMessage()); } } /** * Parses an ASCII property list from a byte array. * * @param bytes The ASCII property list data. * @param encoding The name of a supported {@link java.nio.charset.Charset} charset to decode the * property list. * @return The root object of the property list. This is usually a {@link NSDictionary} but can * also be a {@link NSArray}. * @throws ParseException If an error occurs during parsing. * @throws java.io.UnsupportedEncodingException If no support for the named charset is available * in this instance of the Java virtual machine. */ public static NSObject parse(byte[] bytes, String encoding) throws ParseException, UnsupportedEncodingException { ASCIIPropertyListParser parser = new ASCIIPropertyListParser(bytes, encoding); return parser.parse(); } /** * Checks whether the given sequence of symbols can be accepted. * * @param sequence The sequence of tokens to look for. * @return Whether the given tokens occur at the current parsing position. */ private boolean acceptSequence(char... sequence) { if (this.index + sequence.length > this.data.length) { return false; } for (int i = 0; i < sequence.length; i++) { if (this.data[this.index + i] != sequence[i]) { return false; } } return true; } /** * Checks whether the given symbols can be accepted, that is, if one of the given symbols is found * at the current parsing position. * * @param acceptableSymbols The symbols to check. * @return Whether one of the symbols can be accepted or not. */ private boolean accept(char... acceptableSymbols) { boolean symbolPresent = false; if (this.index < this.data.length) { for (char c : acceptableSymbols) { if (this.data[this.index] == c) { symbolPresent = true; break; } } } return symbolPresent; } /** * Checks whether the given symbol can be accepted, that is, if the given symbols is found at the * current parsing position. * * @param acceptableSymbol The symbol to check. * @return Whether the symbol can be accepted or not. */ private boolean accept(char acceptableSymbol) { return this.index < this.data.length && this.data[this.index] == acceptableSymbol; } /** * Expects the input to have one of the given symbols at the current parsing position. * * @param expectedSymbols The expected symbols. * @throws ParseException If none of the expected symbols could be found. */ private void expect(char... expectedSymbols) throws ParseException { if (!this.accept(expectedSymbols)) { StringBuilder excString = new StringBuilder(); excString.append("Expected '").append(expectedSymbols[0]).append("'"); for (int i = 1; i < expectedSymbols.length; i++) { excString.append(" or '").append(expectedSymbols[i]).append("'"); } if (this.index < this.data.length) { excString.append(" but found '").append(this.data[this.index]).append("'"); } else { excString.append(" but reached end of input"); } throw this.createParseException(excString.toString(), this.index); } } /** * Expects the input to have the given symbol at the current parsing position. * * @param expectedSymbol The expected symbol. * @throws ParseException If the expected symbol could not be found. */ private void expect(char expectedSymbol) throws ParseException { if (!this.accept(expectedSymbol)) { throw this.createParseException( this.index < this.data.length ? "Expected '" + expectedSymbol + "' but found '" + this.data[this.index] + "'" : "Expected '" + expectedSymbol + "' but reached end of input", this.index); } } /** * Reads an expected symbol. * * @param symbol The symbol to read. * @throws ParseException If the expected symbol could not be read. */ private void read(char symbol) throws ParseException { this.expect(symbol); this.index++; } /** * Skips the current symbol. */ private void skip() { this.index++; } /** * Skips several symbols * * @param numSymbols The amount of symbols to skip. */ private void skip(int numSymbols) { this.index += numSymbols; } private void trackLineBreak() { if (this.data[this.index] == WHITESPACE_NEWLINE) { // \n or \r\n this.lineNo++; this.lineBeginning = this.index; } if (this.data[this.index] == WHITESPACE_CARRIAGE_RETURN && !(this.index + 1 < this.data.length && this.data[this.index + 1] == WHITESPACE_NEWLINE)) { // Single \r this.lineNo++; this.lineBeginning = this.index; } } /** * Skips all whitespaces and comments from the current parsing position onward. */ private void skipWhitespacesAndComments() { boolean commentSkipped; do { commentSkipped = false; //Skip whitespaces while (this.accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) { this.trackLineBreak(); this.skip(); } //Skip single line comments "//..." if (this.acceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) { this.skip(2); this.readInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE); commentSkipped = true; } //Skip multi line comments "/* ... */" else if (this.acceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN)) { this.skip(2); while (this.index < this.data.length) { if (this.acceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN)) { this.skip(2); break; } this.trackLineBreak(); this.skip(); } commentSkipped = true; } } while (commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again } /** * Reads input until one of the given symbols is found. * * @param symbols The symbols that can occur after the string to read. * @return The input until one the given symbols. */ private String readInputUntil(char... symbols) { StringBuilder strBuf = new StringBuilder(); while (this.index < this.data.length && !this.accept(symbols)) { strBuf.append(this.data[this.index]); this.skip(); } return strBuf.toString(); } /** * Reads input until the given symbol is found. * * @param symbol The symbol that can occur after the string to read. * @return The input until the given symbol. */ private String readInputUntil(char symbol) { StringBuilder strBuf = new StringBuilder(); while (this.index < this.data.length && !this.accept(symbol)) { strBuf.append(this.data[this.index]); this.trackLineBreak(); this.skip(); } return strBuf.toString(); } /** * Parses the property list from the beginning and returns the root object of the property list. * * @return The root object of the property list. This can either be a NSDictionary or a NSArray. * @throws ParseException If an error occurred during parsing */ public NSObject parse() throws ParseException { this.index = 0; if (this.data.length == 0) { throw new ParseException("The property list is empty.", 0); } //Skip Unicode byte order mark (BOM) if (this.data[0] == '\uFEFF') { this.skip(1); } this.skipWhitespacesAndComments(); this.expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN); try { return this.parseObject(); } catch (ArrayIndexOutOfBoundsException ex) { throw this.createParseException("Reached end of input unexpectedly.", this.index); } } /** * Parses the NSObject found at the current position in the property list data stream. * * @return The parsed NSObject. * @see ASCIIPropertyListParser#index */ private NSObject parseObject() throws ParseException { LocationInformation loc = new ASCIILocationInformation(this.index, this.lineNo, this.index - this.lineBeginning); NSObject result; switch (this.data[this.index]) { case ARRAY_BEGIN_TOKEN: { result = this.parseArray(); break; } case DICTIONARY_BEGIN_TOKEN: { result = this.parseDictionary(); break; } case DATA_BEGIN_TOKEN: { result = this.parseData(); break; } case QUOTEDSTRING_BEGIN_TOKEN: { String quotedString = this.parseQuotedString(); //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found if (quotedString.length() == 20 && quotedString.charAt(4) == DATE_DATE_FIELD_DELIMITER) { try { result = new NSDate(quotedString); } catch (Exception ex) { //not a date? --> return string result = new NSString(quotedString); } } else { result = new NSString(quotedString); } break; } default: { //0-9 if (this.data[this.index] >= '0' && this.data[this.index] <= '9') { //could be a date or just a string result = this.parseDateString(); } else { //non-numerical -> string or boolean result = new NSString(this.parseString()); } break; } } if (result != null) { result.setLocationInformation(loc); } return result; } /** * Parses an array from the current parsing position. The prerequisite for calling this method is, * that an array begin token has been read. * * @return The array found at the parsing position. */ private NSArray parseArray() throws ParseException { //Skip begin token this.skip(); this.skipWhitespacesAndComments(); List objects = new LinkedList<>(); while (!this.accept(ARRAY_END_TOKEN)) { objects.add(this.parseObject()); this.skipWhitespacesAndComments(); if (this.accept(ARRAY_ITEM_DELIMITER_TOKEN)) { this.skip(); } else { break; //must have reached end of array } this.skipWhitespacesAndComments(); } //parse end token this.read(ARRAY_END_TOKEN); return new NSArray(objects.toArray(new NSObject[0])); } /** * Parses a dictionary from the current parsing position. The prerequisite for calling this method * is, that a dictionary begin token has been read. * * @return The dictionary found at the parsing position. */ private NSDictionary parseDictionary() throws ParseException { //Skip begin token this.skip(); this.skipWhitespacesAndComments(); NSDictionary dict = new NSDictionary(); while (!this.accept(DICTIONARY_END_TOKEN)) { //Parse key String keyString; if (this.accept(QUOTEDSTRING_BEGIN_TOKEN)) { keyString = this.parseQuotedString(); } else { keyString = this.parseString(); } this.skipWhitespacesAndComments(); //Parse assign token this.read(DICTIONARY_ASSIGN_TOKEN); this.skipWhitespacesAndComments(); NSObject object = this.parseObject(); dict.put(keyString, object); this.skipWhitespacesAndComments(); this.read(DICTIONARY_ITEM_DELIMITER_TOKEN); this.skipWhitespacesAndComments(); } //skip end token this.skip(); return dict; } /** * Parses a data object from the current parsing position. This can either be a NSData object or a * GnuStep NSNumber or NSDate. The prerequisite for calling this method is, that a data begin * token has been read. * * @return The data object found at the parsing position. */ private NSObject parseData() throws ParseException { int dataStartIndex = this.index; NSObject obj = null; //Skip begin token this.skip(); if (this.accept(DATA_GSOBJECT_BEGIN_TOKEN)) { this.skip(); this.expect( DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN); if (this.accept(DATA_GSBOOL_BEGIN_TOKEN)) { //Boolean this.skip(); this.expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN); if (this.accept(DATA_GSBOOL_TRUE_TOKEN)) { obj = new NSNumber(true); } else { obj = new NSNumber(false); } //Skip the parsed boolean token this.skip(); } else if (this.accept(DATA_GSDATE_BEGIN_TOKEN)) { //Date this.skip(); String dateString = this.readInputUntil(DATA_END_TOKEN); obj = new NSDate(dateString); } else if (this.accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) { //Number this.skip(); String numberString = this.readInputUntil(DATA_END_TOKEN); try { obj = new NSNumber(numberString); } catch (IllegalArgumentException ex) { throw this.createParseException( "The NSNumber object has an invalid format.", dataStartIndex); } } // parse data end token this.read(DATA_END_TOKEN); } else if (this.accept(DATA_BASE64_BEGIN_TOKEN)) { // skip DATA_BASE64_BEGIN_TOKEN token this.skip(); String dataString = this.readInputUntil(DATA_BASE64_END_TOKEN); try { obj = new NSData(dataString); } catch (IOException e) { throw this.createParseException( "The NSData object could be parsed.", dataStartIndex); } // skip DATA_BASE64_END_TOKEN token this.skip(); // parse data end token this.read(DATA_END_TOKEN); } else { String dataString = this.readInputUntil(DATA_END_TOKEN); dataString = dataString.replaceAll("\\s+", ""); int numBytes = dataString.length() / 2; byte[] bytes = new byte[numBytes]; int nibble1, nibble2; for (int bi = 0, ci = 0; bi < bytes.length; bi++, ci += 2) { nibble1 = Character.digit(dataString.charAt(ci), 16); nibble2 = Character.digit(dataString.charAt(ci + 1), 16); if (nibble1 == -1 || nibble2 == -1) { throw this.createParseException( "The NSData object contains non-hexadecimal characters.", dataStartIndex); } bytes[bi] = (byte) (nibble1 << 4 | nibble2); } obj = new NSData(bytes); // skip DATA_END_TOKEN this.skip(); } return obj; } /** * Attempts to parse a plain string as a date if possible. * * @return An NSDate if the string represents such an object. Otherwise, an NSString is returned. */ private NSObject parseDateString() { String numericalString = this.parseString(); if (numericalString.length() > 4 && numericalString.charAt(4) == DATE_DATE_FIELD_DELIMITER) { try { return new NSDate(numericalString); } catch (Exception ex) { //An exception occurs if the string is not actually a date but just a string } } return new NSString(numericalString); } /** * Parses a plain string from the current parsing position. The string is made up of all * characters to the next whitespace, delimiter token or assignment token. * * @return The string found at the current parsing position. */ private String parseString() { return this.readInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE, WHITESPACE_CARRIAGE_RETURN, ARRAY_ITEM_DELIMITER_TOKEN, DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN, ARRAY_END_TOKEN); } /** * Parses a quoted string from the current parsing position. The prerequisite for calling this * method is, that a quoted string begin token has been read. * * @return The quoted string found at the parsing method with all special characters unescaped. * @throws ParseException If an error occurred during parsing. */ private String parseQuotedString() throws ParseException { int startIndex = this.index; //Skip begin token this.skip(); StringBuilder stringBuilder = new StringBuilder(); boolean unescapedBackslash = true; EscapeSequenceHandler escapeSequenceHandler = null; while (this.data[this.index] != QUOTEDSTRING_END_TOKEN || escapeSequenceHandler != null) { char c = this.data[this.index]; if (escapeSequenceHandler != null) { if (escapeSequenceHandler.handleNextChar(c)) { escapeSequenceHandler = null; } } else if (c == QUOTEDSTRING_ESCAPE_TOKEN) { escapeSequenceHandler = new EscapeSequenceHandler(stringBuilder); } else { stringBuilder.append(c); } this.trackLineBreak(); this.skip(); } if (escapeSequenceHandler != null) { escapeSequenceHandler.handleEndOfString(); } //skip end token this.skip(); return stringBuilder.toString(); } private ParseException createParseException(String message) { return this.createParseException(message, this.index); } private ParseException createParseException(String message, int index) { return new ParseException( message + " (" + this.lineNo + ":" + (index - this.lineBeginning) + ")", index); } private class EscapeSequenceHandler { private final int startIndex; private final StringBuilder stringBuilder; private int unicodeReferenceRadix = 0; private StringBuilder unicodeReference; public EscapeSequenceHandler(StringBuilder stringBuilder) { this.startIndex = ASCIIPropertyListParser.this.index; this.stringBuilder = stringBuilder; } public boolean handleNextChar(char c) throws ParseException { switch (this.unicodeReferenceRadix) { case 8: return this.handleNextCharOfOctalEscapeSequence(c); case 16: return this.handleNextCharOfHexEscapeSequence(c); default: return this.handleFirstChar(c); } } public void handleEndOfString() throws ParseException { String sequence = new String( ASCIIPropertyListParser.this.data, this.startIndex, ASCIIPropertyListParser.this.index - this.startIndex + 1); throw ASCIIPropertyListParser.this.createParseException( "The property list contains a string with an incomplete escape sequence: " + sequence, this.startIndex); } private boolean handleFirstChar(char c) throws ParseException { switch (c) { case '\\': case '"': case '\'': this.stringBuilder.append(c); return true; case 'b': this.stringBuilder.append('\b'); return true; case 'n': this.stringBuilder.append('\n'); return true; case 'r': this.stringBuilder.append('\r'); return true; case 't': this.stringBuilder.append('\t'); return true; case 'U': case 'u': this.unicodeReferenceRadix = 16; this.unicodeReference = new StringBuilder(4); return false; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': this.unicodeReferenceRadix = 8; this.unicodeReference = new StringBuilder(3); this.unicodeReference.append(c); return false; default: throw ASCIIPropertyListParser.this.createParseException( "The property list contains an invalid escape sequence: \\" + c, this.startIndex); } } private boolean handleNextCharOfHexEscapeSequence(char c) throws ParseException { if (Character.digit(c, 16) == -1) { String sequence = new String( ASCIIPropertyListParser.this.data, this.startIndex, ASCIIPropertyListParser.this.index - this.startIndex + 1); throw ASCIIPropertyListParser.this.createParseException( "The property list contains a string with an invalid escape sequence: " + sequence, this.startIndex); } this.unicodeReference.append(c); if (this.unicodeReference.length() == 4) { char escapedChar = (char) Integer.parseInt(this.unicodeReference.toString(), 16); this.stringBuilder.append(escapedChar); return true; } return false; } private boolean handleNextCharOfOctalEscapeSequence(char c) throws ParseException { if (Character.digit(c, 8) == -1) { String sequence = new String( ASCIIPropertyListParser.this.data, this.startIndex, ASCIIPropertyListParser.this.index - this.startIndex + 1); throw ASCIIPropertyListParser.this.createParseException( "The property list contains a string with an invalid escape sequence: " + sequence, this.startIndex); } this.unicodeReference.append(c); if (this.unicodeReference.length() == 3) { char escapedChar = (char) Integer.parseInt(this.unicodeReference.toString(), 8); this.stringBuilder.append(escapedChar); return true; } return false; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy