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