package net.wotonomy.foundation; /** * Class for serializing/unserializing property lists in the .plist format */ public class NSPropertyListSerialization { public static final int PLIST_ARRAY = 0; public static final int PLIST_DICTIONARY = 1; public static final int PLIST_DATA = 2; public static final int PLIST_STRING = 3; public static final char[] TOKEN_BEGIN = new char[] { '(', '{', '<', '"' }; public static final char[] TOKEN_END = new char[] { ')', '}', '>', '"' }; public static final char[] QUOTING_CHARS = new char[] { ':', '/', '-', '.', '\\' }; private NSPropertyListSerialization() { super(); } /** * Creates a NSArray object from a string representation. * * @s The string representation of a NSArray object. */ public static NSArray arrayForString(String s) { s = s.trim(); if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_ARRAY] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_ARRAY])) return null; NSMutableArray arr = new NSMutableArray<>(); int pos = 1; int valbegin = -1; while (pos < s.length()) { char c = s.charAt(pos); int tokenCount = 0; int what = 0; for (int i = 0; i < TOKEN_BEGIN.length; i++) { if (c == TOKEN_BEGIN[i]) { tokenCount = 1; what = i; break; } } if (tokenCount > 0) { // mark it int quote = pos; // find the closing token do { pos++; try { c = s.charAt(pos); } catch (StringIndexOutOfBoundsException ex) { throw new IllegalArgumentException( "Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); } if (c == '"' && what == PLIST_STRING) { if (pos > 0 && s.charAt(pos - 1) != '\\') { tokenCount--; } } else if (c == TOKEN_BEGIN[what]) tokenCount++; else if (c == TOKEN_END[what]) tokenCount--; } while (tokenCount > 0); arr.addObject(propertyListFromString(s.substring(quote, pos + 1))); valbegin = -1; // advance to the next position do { pos++; c = s.charAt(pos); } while (Character.isWhitespace(c)); } if (c == ',' || c == ')') { if (valbegin > 0) { arr.addObject(s.substring(valbegin, pos).trim()); valbegin = -1; } } else if (!Character.isWhitespace(c)) { if (valbegin < 0) { valbegin = pos; } } pos++; } return arr; } /** * Creates a NSDictionary instance from a string representation. * * @s The string representation of a NSDictionary. */ public static NSDictionary dictionaryForString(String s) { s = s.trim(); if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DICTIONARY] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_DICTIONARY])) return null; NSMutableDictionary d = new NSMutableDictionary<>(); int pos = 1; Object key = null; int valbegin = -1; while (pos < s.length()) { // look for an opening token char c = s.charAt(pos); int tokenCount = 0; int what = 0; for (int i = 0; i < TOKEN_BEGIN.length; i++) { if (c == TOKEN_BEGIN[i]) { tokenCount = 1; what = i; break; } } if (tokenCount > 0) { // mark it int quote = pos; // find the closing token do { pos++; try { c = s.charAt(pos); } catch (StringIndexOutOfBoundsException ex) { throw new IllegalArgumentException( "Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); } if (c == '"' && what == PLIST_STRING) { if (pos > 0 && s.charAt(pos - 1) != '\\') { tokenCount--; } } else if (c == TOKEN_BEGIN[what]) tokenCount++; else if (c == TOKEN_END[what]) tokenCount--; } while (tokenCount > 0); if (key == null) { key = propertyListFromString(s.substring(quote, pos + 1)); } else { d.setObjectForKey(propertyListFromString(s.substring(quote, pos + 1)), key); key = null; } valbegin = -1; // advance to the next position do { pos++; c = s.charAt(pos); } while (Character.isWhitespace(c)); } if (c == ';' || c == '=' || c == '}') { if (valbegin > 0) { if (key == null) { key = s.substring(valbegin, pos).trim(); } else { d.setObjectForKey(s.substring(valbegin, pos).trim(), key); key = null; } valbegin = -1; } } else if (!Character.isWhitespace(c)) { if (valbegin < 0) { valbegin = pos; } } pos++; } return d; } public static boolean booleanForString(String s) { return s.trim().toLowerCase().equals("true"); } /** * Creates a NSData instance from a string representation. * * @s The string representation of a NSData object. */ public static NSData dataFromPropertyList(String s) { String hex = "0123456789ABCDEF"; s = s.trim(); if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DATA] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_DATA])) return null; int pos = 1; java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); while (pos < s.length() - 1) { char c1 = s.charAt(pos); while (c1 == ' ') { pos++; if (pos == s.length() - 1) return new NSData(bout.toByteArray()); c1 = s.charAt(pos); } if (hex.indexOf(c1) < 0) throw new IllegalArgumentException( "The string does not represent a NSData object (" + s + ", pos " + pos + ")"); pos++; char c2 = s.charAt(pos); if (hex.indexOf(c2) < 0) throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ")"); int x = (hex.indexOf(c1) << 4) | hex.indexOf(c2); bout.write(x); pos++; } return new NSData(bout.toByteArray()); } public static int intForString(String s) { return Integer.parseInt(s); } /** * Returns the string representation of a property list. * * @plist The property list. It can be a String, NSData, NSArray, NSDictionary. */ public static String stringForPropertyList(Object plist) { if (plist == null) return ""; if (plist instanceof NSArray || plist instanceof NSDictionary || plist instanceof NSData) return plist.toString(); String x = plist.toString(); boolean quote = false; for (int i = 0; i < x.length(); i++) { char c = x.charAt(i); for (int z = 0; z < TOKEN_BEGIN.length; z++) { if (c == TOKEN_BEGIN[z] || c == TOKEN_END[z]) quote = true; } if (!quote) { for (int z = 0; z < QUOTING_CHARS.length; z++) { if (c == QUOTING_CHARS[z]) quote = true; } } if (!quote && Character.isWhitespace(c)) { quote = true; i = x.length(); } } if (quote) return "\"" + x + "\""; return x; } /** * Returns an property list created from a string representation. * * @s The string with a representation of a property list. * @returns A property list object; either a NSData, NSArray, NSDictionary, or a * String. */ public static Object propertyListFromString(String s) { s = s.trim(); int type = -1; for (int i = 0; i < TOKEN_BEGIN.length; i++) { if (TOKEN_BEGIN[i] == s.charAt(0)) { if (TOKEN_END[i] == s.charAt(s.length() - 1)) type = i; } } switch (type) { case PLIST_DATA: return dataFromPropertyList(s); case PLIST_ARRAY: return arrayForString(s); case PLIST_DICTIONARY: return dictionaryForString(s); case PLIST_STRING: if (s.equals("\"\"")) return ""; return s.substring(1, s.length() - 1); } return s; } /* * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.5 2003/08/11 18:18:08 chochos improved encoding of strings, * removed warnings * * Revision 1.4 2003/08/11 17:33:45 chochos Implemented detection of escaped * quotes (\"). improved quoting strings when converting property lists to * strings. * * Revision 1.3 2003/08/04 23:50:55 chochos propertyListForString() now works. * dictionaryForString and arrayForString can parse complex structures with * nested arrays and dictionaries. * */ }