blob: 0f307dcada62826886622cbcfabc3f7707bbe6eb [file] [log] [blame]
/*
* plist - An open source library to parse and generate property lists
* Copyright (C) 2011-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 org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.ParseException;
/**
* This class provides methods to parse property lists. It can handle files,
* input streams and byte arrays. All known property list formats are supported.
*
* This class also provides methods to save and convert property lists.
*
* @author Daniel Dreibrodt
*/
public class PropertyListParser {
private static final int TYPE_XML = 0;
private static final int TYPE_BINARY = 1;
private static final int TYPE_ASCII = 2;
private static final int TYPE_ERROR_BLANK = 10;
private static final int TYPE_ERROR_UNKNOWN = 11;
private static final int READ_BUFFER_LENGTH = 2048;
/**
* Prevent instantiation.
*/
protected PropertyListParser() {
/** empty **/
}
/**
* Determines the type of a property list by means of the first bytes of its data
* @param dataBeginning The very first bytes of data of the property list (minus any whitespace) as a string
* @return The type of the property list
*/
private static int determineType(String dataBeginning) {
dataBeginning = dataBeginning.trim();
if(dataBeginning.length() == 0) {
return TYPE_ERROR_BLANK;
}
if(dataBeginning.startsWith("bplist")) {
return TYPE_BINARY;
}
if(dataBeginning.startsWith("(") || dataBeginning.startsWith("{") || dataBeginning.startsWith("/")) {
return TYPE_ASCII;
}
if(dataBeginning.startsWith("<")) {
return TYPE_XML;
}
return TYPE_ERROR_UNKNOWN;
}
/**
* Determines the type of a property list by means of the first bytes of its data
* @param bytes The very first bytes of data of the property list (minus any whitespace)
* @return The type of the property list
*/
private static int determineType(byte[] bytes) {
//Skip any possible whitespace at the beginning of the file
int offset = 0;
if(bytes.length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF) {
//Skip Unicode byte order mark (BOM)
offset += 3;
}
while(offset < bytes.length && (bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' || bytes[offset] == '\f')) {
offset++;
}
return determineType(new String(bytes, offset, Math.min(8, bytes.length - offset)));
}
/**
* Determines the type of a property list by means of the first bytes of its data
* @param is An input stream pointing to the beginning of the property list data.
* If the stream supports marking it will be reset to the beginning of the property
* list data after the type has been determined.
* @return The type of the property list
*/
private static int determineType(InputStream is) throws IOException {
//Skip any possible whitespace at the beginning of the file
byte[] magicBytes = new byte[8];
int b;
long index = -1;
boolean bom = false;
do {
if(is.markSupported())
is.mark(16);
b = is.read();
index++;
//Check if we are reading the Unicode byte order mark (BOM) and skip it
bom = index < 3 && ((index == 0 && b == 0xEF) || (bom && ((index == 1 && b == 0xBB) || (index == 2 && b == 0xBF))));
}
while(b != -1 && b == ' ' || b == '\t' || b == '\r' || b == '\n' || b == '\f' || bom);
magicBytes[0] = (byte)b;
int read = is.read(magicBytes, 1, 7);
int type = determineType(new String(magicBytes, 0, read));
if(is.markSupported())
is.reset();
return type;
}
/**
* Reads all bytes from an InputStream and stores them in an array, up to
* a maximum count.
*
* @param in The InputStream pointing to the data that should be stored in the array.
* @return An array containing all bytes that were read from the input stream.
* @throws java.io.IOException When an IO error while reading from the input stream.
*/
protected static byte[] readAll(InputStream in) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buf = new byte[READ_BUFFER_LENGTH];
int read;
while ((read = in.read(buf, 0, READ_BUFFER_LENGTH)) != -1) {
outputStream.write(buf, 0, read);
}
return outputStream.toByteArray();
}
/**
* Parses a property list from a file.
*
* @param filePath Path to the property list file.
* @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static NSObject parse(String filePath) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
return parse(new File(filePath));
}
/**
* Parses a property list from a file.
*
* @param f The property list file.
* @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static NSObject parse(File f) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
FileInputStream fis = new FileInputStream(f);
int type = determineType(fis);
fis.close();
switch(type) {
case TYPE_BINARY:
return BinaryPropertyListParser.parse(f);
case TYPE_XML:
return XMLPropertyListParser.parse(f);
case TYPE_ASCII:
return ASCIIPropertyListParser.parse(f);
default:
throw new PropertyListFormatException("The given file is not a property list of a supported format.");
}
}
/**
* Parses a property list from a byte array.
*
* @param bytes The property list data as a byte array.
* @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the byte array.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static NSObject parse(byte[] bytes) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
switch(determineType(bytes)) {
case TYPE_BINARY:
return BinaryPropertyListParser.parse(bytes);
case TYPE_XML:
return XMLPropertyListParser.parse(bytes);
case TYPE_ASCII:
return ASCIIPropertyListParser.parse(bytes);
default:
throw new PropertyListFormatException("The given data is not a property list of a supported format.");
}
}
/**
* Parses a property list from an InputStream.
*
* @param is The InputStream delivering the property list data.
* @return The root object of the property list. This is usually a NSDictionary but can also be a NSArray.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the input stream.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static NSObject parse(InputStream is) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
return parse(readAll(is));
}
/**
* Saves a property list with the given object as root into a XML file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsXML(NSObject root, File out) throws IOException {
File parent = out.getParentFile();
if (!parent.exists())
if(!parent.mkdirs())
throw new IOException("The output directory does not exist and could not be created.");
FileOutputStream fous = new FileOutputStream(out);
saveAsXML(root, fous);
fous.close();
}
/**
* Saves a property list with the given object as root in XML format into an output stream.
*
* @param root The root object.
* @param out The output stream.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsXML(NSObject root, OutputStream out) throws IOException {
OutputStreamWriter w = new OutputStreamWriter(out, "UTF-8");
w.write(root.toXMLPropertyList());
w.close();
}
/**
* Converts a given property list file into the OS X and iOS XML format.
*
* @param in The source file.
* @param out The target file.
*
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the input file or writing the output file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static void convertToXml(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
NSObject root = parse(in);
saveAsXML(root, out);
}
/**
* Saves a property list with the given object as root into a binary file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsBinary(NSObject root, File out) throws IOException {
File parent = out.getParentFile();
if (!parent.exists())
if(!parent.mkdirs())
throw new IOException("The output directory does not exist and could not be created.");
BinaryPropertyListWriter.write(out, root);
}
/**
* Saves a property list with the given object as root in binary format into an output stream.
*
* @param root The root object.
* @param out The output stream.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsBinary(NSObject root, OutputStream out) throws IOException {
BinaryPropertyListWriter.write(out, root);
}
/**
* Converts a given property list file into the OS X and iOS binary format.
*
* @param in The source file.
* @param out The target file.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the input file or writing the output file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static void convertToBinary(File in, File out) throws IOException, ParserConfigurationException, ParseException, SAXException, PropertyListFormatException {
NSObject root = parse(in);
saveAsBinary(root, out);
}
/**
* Saves a property list with the given object as root into a ASCII file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsASCII(NSDictionary root, File out) throws IOException {
File parent = out.getParentFile();
if (!parent.exists())
if(!parent.mkdirs())
throw new IOException("The output directory does not exist and could not be created.");
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
w.write(root.toASCIIPropertyList());
w.close();
}
/**
* Saves a property list with the given object as root into a ASCII file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsASCII(NSArray root, File out) throws IOException {
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
w.write(root.toASCIIPropertyList());
w.close();
}
/**
* Converts a given property list file into ASCII format.
*
* @param in The source file.
* @param out The target file.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the input file or writing the output file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static void convertToASCII(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
NSObject root = parse(in);
if(root instanceof NSDictionary) {
saveAsASCII((NSDictionary) root, out);
}
else if(root instanceof NSArray) {
saveAsASCII((NSArray) root, out);
}
else {
throw new PropertyListFormatException("The root of the given input property list "
+ "is neither a Dictionary nor an Array!");
}
}
/**
* Saves a property list with the given object as root into a ASCII file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsGnuStepASCII(NSDictionary root, File out) throws IOException {
File parent = out.getParentFile();
if (!parent.exists())
if(!parent.mkdirs())
throw new IOException("The output directory does not exist and could not be created.");
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
w.write(root.toGnuStepASCIIPropertyList());
w.close();
}
/**
* Saves a property list with the given object as root into a ASCII file.
*
* @param root The root object.
* @param out The output file.
* @throws IOException When an error occurs during the writing process.
*/
public static void saveAsGnuStepASCII(NSArray root, File out) throws IOException {
File parent = out.getParentFile();
if (!parent.exists())
if(!parent.mkdirs())
throw new IOException("The output directory does not exist and could not be created.");
OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
w.write(root.toGnuStepASCIIPropertyList());
w.close();
}
/**
* Converts a given property list file into ASCII format.
*
* @param in The source file.
* @param out The target file.
* @throws javax.xml.parsers.ParserConfigurationException If a document builder for parsing a XML property list
* could not be created. This should not occur.
* @throws java.io.IOException If any IO error occurs while reading the input file or writing the output file.
* @throws org.xml.sax.SAXException If any parse error occurs.
* @throws com.dd.plist.PropertyListFormatException If the given property list has an invalid format.
* @throws java.text.ParseException If a date string could not be parsed.
*/
public static void convertToGnuStepASCII(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
NSObject root = parse(in);
if(root instanceof NSDictionary) {
saveAsGnuStepASCII((NSDictionary) root, out);
}
else if(root instanceof NSArray) {
saveAsGnuStepASCII((NSArray) root, out);
}
else {
throw new PropertyListFormatException("The root of the given input property list "
+ "is neither a Dictionary nor an Array!");
}
}
}