0
+ * This is a Ruby extension to read/write Cocoa property lists
0
+ * Not surprisingly, it only works on OS X
0
+ * Copyright © 2005, Kevin Ballard
0
+ * This extension provides a module named OSX::PropertyList
0
+ * This module has two methods:
0
+ * PropertyList::load(obj, format = false)
0
+ * Takes either an IO stream open for reading or a String object
0
+ * Returns an object representing the property list
0
+ * Optionally takes a boolean format argument. If true, the
0
+ * return value is an array with the second value being
0
+ * the format of the plist, which can be one of
0
+ * :xml1, :binary1, or :openstep
0
+ * PropertyList::dump(io, obj, type = :xml1)
0
+ * Takes an IO stream (open for writing) and an object
0
+ * Writes the object to the IO stream as a property list
0
+ * Posible type values are :xml1 and :binary1
0
+ * It also adds a new method to Object:
0
+ * Object#to_plist(type = :xml1)
0
+ * Returns a string representation of the property list
0
+ * Possible type values are :xml1 and :binary1
0
+ * It also adds 2 new methods to String:
0
+ * Sets whether the string is a blob
0
+ * Returns whether the string is a blob
0
+ * A blob string is turned into a CFData when dumped
0
+ * Document-class: PropertyList
0
+ * The PropertyList module provides a means of converting a
0
+ * Ruby Object to a Property List.
0
+ * The various Objects that can be converted are the ones
0
+ * with an equivalent in CoreFoundation. This includes: String,
0
+ * Integer, Float, Boolean, Time, Hash, and Array.
0
+ * See also: String#blob?, String#blob=, and Object#to_plist
0
+#include <CoreFoundation/CoreFoundation.h>
0
+// Here's some convenience macros
0
+#define StringValue(x) do { \
0
+ if (TYPE(x) != T_STRING) x = rb_str_to_str(x); \
0
+static VALUE timeEpoch;
0
+static VALUE ePropertyListError;
0
+static VALUE id_binary;
0
+static VALUE id_openstep;
0
+VALUE convertPropertyListRef(CFPropertyListRef plist);
0
+VALUE convertStringRef(CFStringRef plist);
0
+VALUE convertDictionaryRef(CFDictionaryRef plist);
0
+VALUE convertArrayRef(CFArrayRef plist);
0
+VALUE convertNumberRef(CFNumberRef plist);
0
+VALUE convertBooleanRef(CFBooleanRef plist);
0
+VALUE convertDataRef(CFDataRef plist);
0
+VALUE convertDateRef(CFDateRef plist);
0
+VALUE str_blob(VALUE self);
0
+VALUE str_setBlob(VALUE self, VALUE b);
0
+// Raises a Ruby exception with the given string
0
+void raiseError(CFStringRef error) {
0
+ char *errBuffer = (char *)CFStringGetCStringPtr(error, kCFStringEncodingUTF8);
0
+ int len = CFStringGetLength(error)*2+1;
0
+ errBuffer = ALLOC_N(char, len);
0
+ Boolean succ = CFStringGetCString(error, errBuffer, len, kCFStringEncodingUTF8);
0
+ CFStringGetCString(error, errBuffer, len, kCFStringEncodingMacRoman);
0
+ rb_raise(ePropertyListError, (char *)errBuffer);
0
+ if (freeBuffer) free(errBuffer);
0
+ * PropertyList.load(obj) -> object
0
+ * PropertyList.load(obj, format) -> [object, format]
0
+ * Loads a property list from an IO stream or a String and creates
0
+ * an equivalent Object from it.
0
+ * If +format+ is provided, it returns one of
0
+ * <tt>:xml1</tt>, <tt>:binary1</tt>, or <tt>:openstep</tt>.
0
+VALUE plist_load(int argc, VALUE *argv, VALUE self) {
0
+ int count = rb_scan_args(argc, argv, "11", &io, &retFormat);
0
+ if (count < 2) retFormat = Qfalse;
0
+ if (RTEST(rb_respond_to(io, id_read))) {
0
+ buffer = rb_funcall(io, id_read, 0);
0
+ // For some reason, the CFReadStream version doesn't work with input < 6 characters
0
+ // but the CFDataRef version doesn't return format
0
+ // So lets use the CFDataRef version unless format is requested
0
+ CFStringRef error = NULL;
0
+ CFPropertyListRef plist;
0
+ CFPropertyListFormat format;
0
+ if (RTEST(retFormat)) {
0
+ // Format was requested
0
+ // now just in case, if the input is < 6 characters, we will pad it out with newlines
0
+ // we could do this in all cases, but I don't think it will work with binary
0
+ // even though binary shouldn't be < 6 characters
0
+ if (RSTRING(buffer)->len < 6) {
0
+ bytes = ALLOC_N(UInt8, 6);
0
+ memset(bytes, '\n', 6);
0
+ MEMCPY(bytes, RSTRING(buffer)->ptr, UInt8, RSTRING(buffer)->len);
0
+ bytes = (UInt8 *)RSTRING(buffer)->ptr;
0
+ len = RSTRING(buffer)->len;
0
+ CFReadStreamRef readStream = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, bytes, len, kCFAllocatorNull);
0
+ CFReadStreamOpen(readStream);
0
+ plist = CFPropertyListCreateFromStream(kCFAllocatorDefault, readStream, 0, kCFPropertyListImmutable, &format, &error);
0
+ CFReadStreamClose(readStream);
0
+ CFRelease(readStream);
0
+ // Format wasn't requested
0
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8*)RSTRING(buffer)->ptr, RSTRING(buffer)->len, kCFAllocatorNull);
0
+ plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListImmutable, &error);
0
+ VALUE obj = convertPropertyListRef(plist);
0
+ if (RTEST(retFormat)) {
0
+ VALUE ary = rb_ary_new();
0
+ rb_ary_push(ary, obj);
0
+ if (format == kCFPropertyListOpenStepFormat) {
0
+ retFormat = id_openstep;
0
+ } else if (format == kCFPropertyListXMLFormat_v1_0) {
0
+ } else if (format == kCFPropertyListBinaryFormat_v1_0) {
0
+ retFormat = id_binary;
0
+ retFormat = rb_intern("unknown");
0
+ rb_ary_push(ary, ID2SYM(retFormat));
0
+// Maps the property list object to a ruby object
0
+VALUE convertPropertyListRef(CFPropertyListRef plist) {
0
+ CFTypeID typeID = CFGetTypeID(plist);
0
+ if (typeID == CFStringGetTypeID()) {
0
+ return convertStringRef((CFStringRef)plist);
0
+ } else if (typeID == CFDictionaryGetTypeID()) {
0
+ return convertDictionaryRef((CFDictionaryRef)plist);
0
+ } else if (typeID == CFArrayGetTypeID()) {
0
+ return convertArrayRef((CFArrayRef)plist);
0
+ } else if (typeID == CFNumberGetTypeID()) {
0
+ return convertNumberRef((CFNumberRef)plist);
0
+ } else if (typeID == CFBooleanGetTypeID()) {
0
+ return convertBooleanRef((CFBooleanRef)plist);
0
+ } else if (typeID == CFDataGetTypeID()) {
0
+ return convertDataRef((CFDataRef)plist);
0
+ } else if (typeID == CFDateGetTypeID()) {
0
+ return convertDateRef((CFDateRef)plist);
0
+// Converts a CFStringRef to a String
0
+VALUE convertStringRef(CFStringRef plist) {
0
+ CFRange range = CFRangeMake(0, CFStringGetLength(plist));
0
+ CFStringEncoding enc = kCFStringEncodingUTF8;
0
+ Boolean succ = CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
0
+ enc = kCFStringEncodingMacRoman;
0
+ CFStringGetBytes(plist, range, enc, 0, false, NULL, 0, &byteCount);
0
+ UInt8 *buffer = ALLOC_N(UInt8, byteCount);
0
+ CFStringGetBytes(plist, range, enc, 0, false, buffer, byteCount, NULL);
0
+ VALUE retval = rb_str_new((char *)buffer, (long)byteCount);
0
+// Converts the keys and values of a CFDictionaryRef
0
+void dictionaryConverter(const void *key, const void *value, void *context) {
0
+ rb_hash_aset((VALUE)context, convertPropertyListRef(key), convertPropertyListRef(value));
0
+// Converts a CFDictionaryRef to a Hash
0
+VALUE convertDictionaryRef(CFDictionaryRef plist) {
0
+ VALUE hash = rb_hash_new();
0
+ CFDictionaryApplyFunction(plist, dictionaryConverter, (void *)hash);
0
+// Converts the values of a CFArrayRef
0
+void arrayConverter(const void *value, void *context) {
0
+ rb_ary_push((VALUE)context, convertPropertyListRef(value));
0
+// Converts a CFArrayRef to an Array
0
+VALUE convertArrayRef(CFArrayRef plist) {
0
+ VALUE array = rb_ary_new();
0
+ CFRange range = CFRangeMake(0, CFArrayGetCount(plist));
0
+ CFArrayApplyFunction(plist, range, arrayConverter, (void *)array);
0
+// Converts a CFNumberRef to a Number
0
+VALUE convertNumberRef(CFNumberRef plist) {
0
+ if (CFNumberIsFloatType(plist)) {
0
+ CFNumberGetValue(plist, kCFNumberDoubleType, &val);
0
+ return rb_float_new(val);
0
+ CFNumberGetValue(plist, kCFNumberLongLongType, &val);
0
+ CFNumberGetValue(plist, kCFNumberLongType, &val);
0
+// Converts a CFBooleanRef to a Boolean
0
+VALUE convertBooleanRef(CFBooleanRef plist) {
0
+ if (CFBooleanGetValue(plist)) {
0
+// Converts a CFDataRef to a String (with blob set to true)
0
+VALUE convertDataRef(CFDataRef plist) {
0
+ const UInt8 *bytes = CFDataGetBytePtr(plist);
0
+ CFIndex len = CFDataGetLength(plist);
0
+ VALUE str = rb_str_new((char *)bytes, (long)len);
0
+ str_setBlob(str, Qtrue);
0
+// Converts a CFDateRef to a Time
0
+VALUE convertDateRef(CFDateRef plist) {
0
+ CFAbsoluteTime seconds = CFDateGetAbsoluteTime(plist);
0
+ // trunace the time since Ruby's Time object stores it as a 32 bit signed offset from 1970 (undocumented)
0
+ const float min_time = -3124310400.0f;
0
+ const float max_time = 1169098047.0f;
0
+ seconds = seconds < min_time ? min_time : (seconds > max_time ? max_time : seconds);
0
+ return rb_funcall(timeEpoch, id_plus, 1, rb_float_new(seconds));
0
+CFPropertyListRef convertObject(VALUE obj);
0
+// Converts a PropertyList object to a string representation
0
+VALUE convertPlistToString(CFPropertyListRef plist, CFPropertyListFormat format) {
0
+ CFWriteStreamRef writeStream = CFWriteStreamCreateWithAllocatedBuffers(kCFAllocatorDefault, kCFAllocatorDefault);
0
+ CFWriteStreamOpen(writeStream);
0
+ CFStringRef error = NULL;
0
+ CFPropertyListWriteToStream(plist, writeStream, format, &error);
0
+ CFWriteStreamClose(writeStream);
0
+ CFDataRef data = CFWriteStreamCopyProperty(writeStream, kCFStreamPropertyDataWritten);
0
+ CFRelease(writeStream);
0
+ VALUE plistData = convertDataRef(data);
0
+ * PropertyList.dump(io, obj) -> Integer
0
+ * PropertyList.dump(io, obj, format) -> Integer
0
+ * Writes the property list representation of +obj+
0
+ * to the IO stream (must be open for writing).
0
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
0
+ * Returns the number of bytes written, or +nil+ if
0
+ * the object could not be represented as a property list
0
+VALUE plist_dump(int argc, VALUE *argv, VALUE self) {
0
+ int count = rb_scan_args(argc, argv, "21", &io, &obj, &type);
0
+ type = rb_to_id(type);
0
+ if (type != id_xml && type != id_binary && type != id_openstep) {
0
+ rb_raise(rb_eArgError, "Argument 3 must be one of :xml1, :binary1, or :openstep");
0
+ if (!RTEST(rb_respond_to(io, id_write))) {
0
+ rb_raise(rb_eArgError, "Argument 1 must be an IO object");
0
+ CFPropertyListRef plist = convertObject(obj);
0
+ CFPropertyListFormat format;
0
+ format = kCFPropertyListXMLFormat_v1_0;
0
+ } else if (type == id_binary) {
0
+ format = kCFPropertyListBinaryFormat_v1_0;
0
+ } else if (type == id_openstep) {
0
+ format = kCFPropertyListOpenStepFormat;
0
+ VALUE data = convertPlistToString(plist, format);
0
+ return rb_funcall(io, id_write, 1, data);
0
+ * object.to_plist -> String
0
+ * object.to_plist(format) -> String
0
+ * Converts the object to a property list representation
0
+ * and returns it as a string.
0
+ * +format+ can be one of <tt>:xml1</tt> or <tt>:binary1</tt>.
0
+VALUE obj_to_plist(int argc, VALUE *argv, VALUE self) {
0
+ int count = rb_scan_args(argc, argv, "01", &type);
0
+ type = rb_to_id(type);
0
+ if (type != id_xml && type != id_binary && type != id_openstep) {
0
+ rb_raise(rb_eArgError, "Argument 2 must be one of :xml1, :binary1, or :openstep");
0
+ CFPropertyListRef plist = convertObject(self);
0
+ CFPropertyListFormat format;
0
+ format = kCFPropertyListXMLFormat_v1_0;
0
+ } else if (type == id_binary) {
0
+ format = kCFPropertyListBinaryFormat_v1_0;
0
+ } else if (type == id_openstep) {
0
+ format = kCFPropertyListOpenStepFormat;
0
+ VALUE data = convertPlistToString(plist, format);
0
+ if (type == id_xml || type == id_binary) {
0
+ str_setBlob(data, Qfalse);
0
+CFPropertyListRef convertString(VALUE obj);
0
+CFDictionaryRef convertHash(VALUE obj);
0
+CFArrayRef convertArray(VALUE obj);
0
+CFNumberRef convertNumber(VALUE obj);
0
+CFDateRef convertTime(VALUE obj);
0
+// Converts an Object to a CFTypeRef
0
+CFPropertyListRef convertObject(VALUE obj) {
0
+ case T_STRING: return convertString(obj); break;
0
+ case T_HASH: return convertHash(obj); break;
0
+ case T_ARRAY: return convertArray(obj); break;
0
+ case T_BIGNUM: return convertNumber(obj); break;
0
+ case T_TRUE: return kCFBooleanTrue; break;
0
+ case T_FALSE: return kCFBooleanFalse; break;
0
+ default: if (rb_obj_is_kind_of(obj, rb_cTime)) return convertTime(obj);
0
+ rb_raise(rb_eArgError, "An object in the argument tree could not be converted");
0
+// Converts a String to a CFStringRef
0
+CFPropertyListRef convertString(VALUE obj) {
0
+ if (RTEST(str_blob(obj))) {
0
+ // convert to CFDataRef
0
+ CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len);
0
+ // convert to CFStringRef
0
+ CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len, kCFStringEncodingUTF8, false);
0
+ string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)RSTRING(obj)->ptr, (CFIndex)RSTRING(obj)->len, kCFStringEncodingMacRoman, false);
0
+// Converts the keys and values of a Hash to CFTypeRefs
0
+int iterateHash(VALUE key, VALUE val, VALUE dict) {
0
+ CFPropertyListRef dKey = convertObject(key);
0
+ CFPropertyListRef dVal = convertObject(val);
0
+ CFDictionaryAddValue((CFMutableDictionaryRef)dict, dKey, dVal);
0
+// Converts a Hash to a CFDictionaryREf
0
+CFDictionaryRef convertHash(VALUE obj) {
0
+ CFIndex count = (CFIndex)RHASH(obj)->tbl->num_entries;
0
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
0
+ st_foreach(RHASH(obj)->tbl, iterateHash, (VALUE)dict);
0
+// Converts an Array to a CFArrayRef
0
+CFArrayRef convertArray(VALUE obj) {
0
+ CFIndex count = (CFIndex)RARRAY(obj)->len;
0
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
0
+ for (i = 0; i < count; i++) {
0
+ CFPropertyListRef aVal = convertObject(RARRAY(obj)->ptr[i]);
0
+ CFArrayAppendValue(array, aVal);
0
+// Converts a Number to a CFNumberRef
0
+CFNumberRef convertNumber(VALUE obj) {
0
+ double num = NUM2DBL(obj);
0
+ type = kCFNumberDoubleType;
0
+ int num = NUM2INT(obj);
0
+ type = kCFNumberIntType;
0
+ long long num = NUM2LL(obj);
0
+ type = kCFNumberLongLongType;
0
+ long num = NUM2LONG(obj);
0
+ type = kCFNumberLongType;