CFPropertyList.c   [plain text]


/*
 * Copyright (c) 2008 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	CFPropertyList.c
	Copyright 1999-2002, Apple, Inc. All rights reserved.
	Responsibility: Christopher Kane
*/

#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFSet.h>
#include "CFPriv.h"
#include "CFStringEncodingConverter.h"
#include "CFInternal.h"
#include <CoreFoundation/CFStream.h>
#include <CoreFoundation/CFCalendar.h>
#include <limits.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#if DEPLOYMENT_TARGET_MACOSX
#include <CoreFoundation/CFPreferences.h>
#include <mach-o/dyld.h>
#endif


__private_extern__ bool allowMissingSemi = false;

// Should move this somewhere else
intptr_t _CFDoOperation(intptr_t code, intptr_t subcode1, intptr_t subcode2) {
    switch (code) {
    case 15317: allowMissingSemi = subcode1 ? true : false; break;
    }
    return code;
}

#define PLIST_IX    0
#define ARRAY_IX    1
#define DICT_IX     2
#define KEY_IX      3
#define STRING_IX   4
#define DATA_IX     5
#define DATE_IX     6
#define REAL_IX     7
#define INTEGER_IX  8
#define TRUE_IX     9
#define FALSE_IX    10
#define DOCTYPE_IX  11
#define CDSECT_IX   12

#define PLIST_TAG_LENGTH	5
#define ARRAY_TAG_LENGTH	5
#define DICT_TAG_LENGTH		4
#define KEY_TAG_LENGTH		3
#define STRING_TAG_LENGTH	6
#define DATA_TAG_LENGTH		4
#define DATE_TAG_LENGTH		4
#define REAL_TAG_LENGTH		4
#define INTEGER_TAG_LENGTH	7
#define TRUE_TAG_LENGTH		4
#define FALSE_TAG_LENGTH	5
#define DOCTYPE_TAG_LENGTH	7
#define CDSECT_TAG_LENGTH	9

// don't allow _CFKeyedArchiverUID here
#define __CFAssertIsPList(cf) CFAssert2(CFGetTypeID(cf) == CFStringGetTypeID() || CFGetTypeID(cf) == CFArrayGetTypeID() || CFGetTypeID(cf) == CFBooleanGetTypeID() || CFGetTypeID(cf) == CFNumberGetTypeID() || CFGetTypeID(cf) == CFDictionaryGetTypeID() || CFGetTypeID(cf) == CFDateGetTypeID() || CFGetTypeID(cf) == CFDataGetTypeID(), __kCFLogAssertion, "%s(): %p not of a property list type", __PRETTY_FUNCTION__, cf);

static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format);

static CFTypeID stringtype = -1, datatype = -1, numbertype = -1, datetype = -1;
static CFTypeID booltype = -1, nulltype = -1, dicttype = -1, arraytype = -1, settype = -1;

static void initStatics() {
    if ((CFTypeID)-1 == stringtype) {
        stringtype = CFStringGetTypeID();
    }
    if ((CFTypeID)-1 == datatype) {
        datatype = CFDataGetTypeID();
    }
    if ((CFTypeID)-1 == numbertype) {
        numbertype = CFNumberGetTypeID();
    }
    if ((CFTypeID)-1 == booltype) {
        booltype = CFBooleanGetTypeID();
    }
    if ((CFTypeID)-1 == datetype) {
        datetype = CFDateGetTypeID();
    }
    if ((CFTypeID)-1 == dicttype) {
        dicttype = CFDictionaryGetTypeID();
    }
    if ((CFTypeID)-1 == arraytype) {
        arraytype = CFArrayGetTypeID();
    }
    if ((CFTypeID)-1 == settype) {
        settype = CFSetGetTypeID();
    }
    if ((CFTypeID)-1 == nulltype) {
        nulltype = CFNullGetTypeID();
    }
}

struct context {
    bool answer;
    CFMutableSetRef set;
    CFPropertyListFormat format;
};

static void __CFPropertyListIsArrayPlistAux(const void *value, void *context) {
    struct context *ctx = (struct context *)context;
    if (!ctx->answer) return;
#if defined(DEBUG)
    if (!value) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list arrays cannot contain NULL"));
#endif
    ctx->answer = value && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
}

static void __CFPropertyListIsDictPlistAux(const void *key, const void *value, void *context) {
    struct context *ctx = (struct context *)context;
    if (!ctx->answer) return;
#if defined(DEBUG)
    if (!key) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL keys"));
    if (!value) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL values"));
    if (stringtype != CFGetTypeID(key)) {
	CFStringRef desc = CFCopyTypeIDDescription(CFGetTypeID(key));
	CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries may only have keys which are CFStrings, not '%@'"), desc);
	CFRelease(desc);
    }
#endif
    ctx->answer = key && value && (stringtype == CFGetTypeID(key)) && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
}

static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format) {
    CFTypeID type;
#if defined(DEBUG)
    if (!plist) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain NULL"));
#endif
    if (!plist) return false;
    type = CFGetTypeID(plist);
    if (stringtype == type) return true;
    if (datatype == type) return true;
    if (kCFPropertyListOpenStepFormat != format) {
	if (booltype == type) return true;
	if (numbertype == type) return true;
	if (datetype == type) return true;
#if DEPLOYMENT_TARGET_MACOSX
	if (_CFKeyedArchiverUIDGetTypeID() == type) return true;
#endif
    }
    if (!recursive && arraytype == type) return true;
    if (!recursive && dicttype == type) return true;
    // at any one invocation of this function, set should contain the objects in the "path" down to this object
#if defined(DEBUG)
    if (CFSetContainsValue(set, plist)) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain recursive container references"));
#endif
    if (CFSetContainsValue(set, plist)) return false;
    if (arraytype == type) {
	struct context ctx = {true, set, format}; 
	CFSetAddValue(set, plist);
	CFArrayApplyFunction((CFArrayRef)plist, CFRangeMake(0, CFArrayGetCount((CFArrayRef)plist)), __CFPropertyListIsArrayPlistAux, &ctx);
	CFSetRemoveValue(set, plist);
	return ctx.answer;
    }
    if (dicttype == type) {
	struct context ctx = {true, set, format}; 
	CFSetAddValue(set, plist);
	CFDictionaryApplyFunction((CFDictionaryRef)plist, __CFPropertyListIsDictPlistAux, &ctx);
	CFSetRemoveValue(set, plist);
	return ctx.answer;
    }
#if defined(DEBUG)
    {
	CFStringRef desc = CFCopyTypeIDDescription(type);
	CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain objects of type '%@'"), desc);
	CFRelease(desc);
    }
#endif
    return false;
}

Boolean CFPropertyListIsValid(CFPropertyListRef plist, CFPropertyListFormat format) {
    initStatics();
    CFAssert1(plist != NULL, __kCFLogAssertion, "%s(): NULL is not a property list", __PRETTY_FUNCTION__);
    CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
    bool result = __CFPropertyListIsValidAux(plist, true, set, format);
    CFRelease(set);
    return result;
}

static const UniChar CFXMLPlistTags[13][10]= {
{'p', 'l', 'i', 's', 't',   '\0', '\0', '\0', '\0', '\0'},
{'a', 'r', 'r', 'a', 'y',   '\0', '\0', '\0', '\0', '\0'},
{'d', 'i', 'c', 't',  '\0', '\0', '\0', '\0', '\0', '\0'},
{'k', 'e', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
{'s', 't', 'r', 'i', 'n', 'g',    '\0', '\0', '\0', '\0'},
{'d', 'a', 't', 'a',  '\0', '\0', '\0', '\0', '\0', '\0'},
{'d', 'a', 't', 'e',  '\0', '\0', '\0', '\0', '\0', '\0'},
{'r', 'e', 'a', 'l',  '\0', '\0', '\0', '\0', '\0', '\0'},
{'i', 'n', 't', 'e', 'g', 'e', 'r',     '\0', '\0', '\0'},
{'t', 'r', 'u', 'e',  '\0', '\0', '\0', '\0', '\0', '\0'},
{'f', 'a', 'l', 's', 'e',   '\0', '\0', '\0', '\0', '\0'},
{'D', 'O', 'C', 'T', 'Y', 'P', 'E',     '\0', '\0', '\0'},
{'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[',       '\0'}
};

typedef struct {
    const UniChar *begin; // first character of the XML to be parsed
    const UniChar *curr;  // current parse location
    const UniChar *end;   // the first character _after_ the end of the XML
    CFStringRef errorString;
    CFAllocatorRef allocator;
    UInt32 mutabilityOption;
    CFMutableSetRef stringSet;  // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
    CFMutableStringRef tmpString; // Mutable string with external characters that functions can feel free to use as temporary storage as the parse progresses
    Boolean allowNewTypes; // Whether to allow the new types supported by XML property lists, but not by the old, OPENSTEP ASCII property lists (CFNumber, CFBoolean, CFDate)
    char _padding[3];
} _CFXMLPlistParseInfo;

static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo);



// The following set of _plist... functions append various things to a mutable data which is in UTF8 encoding. These are pretty general. Assumption is call characters and CFStrings can be converted to UTF8 and appeneded.

// Null-terminated, ASCII or UTF8 string
//
static void _plistAppendUTF8CString(CFMutableDataRef mData, const char *cString) {
    CFDataAppendBytes (mData, (const UInt8 *)cString, strlen(cString));
}

// UniChars
//
static void _plistAppendCharacters(CFMutableDataRef mData, const UniChar *chars, CFIndex length) {
    CFIndex curLoc = 0;

    do {	// Flush out ASCII chars, BUFLEN at a time
        #define BUFLEN 400
	UInt8 buf[BUFLEN], *bufPtr = buf;
	CFIndex cnt = 0;
        while (cnt < length && (cnt - curLoc < BUFLEN) && (chars[cnt] < 128)) *bufPtr++ = (UInt8)(chars[cnt++]);
        if (cnt > curLoc) {	// Flush any ASCII bytes
            CFDataAppendBytes(mData, buf, cnt - curLoc);
            curLoc = cnt;
        }
    } while (curLoc < length && (chars[curLoc] < 128));	// We will exit out of here when we run out of chars or hit a non-ASCII char

    if (curLoc < length) {	// Now deal with non-ASCII chars
        CFDataRef data = NULL;
        CFStringRef str = NULL;
        if ((str = CFStringCreateWithCharactersNoCopy(kCFAllocatorSystemDefault, chars + curLoc, length - curLoc, kCFAllocatorNull))) {
            if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
                CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
                CFRelease(data);
            }
            CFRelease(str);
        }
        CFAssert1(str && data, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__); 
    }
}

// Append CFString
//
static void _plistAppendString(CFMutableDataRef mData, CFStringRef str) {
    const UniChar *chars;
    const char *cStr;
    CFDataRef data;
    if ((chars = CFStringGetCharactersPtr(str))) {
        _plistAppendCharacters(mData, chars, CFStringGetLength(str));
    } else if ((cStr = CFStringGetCStringPtr(str, kCFStringEncodingASCII)) || (cStr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8))) {
        _plistAppendUTF8CString(mData, cStr);
    } else if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
        CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
        CFRelease(data);
    } else {
	CFAssert1(TRUE, __kCFLogAssertion, "%s(): Error in plist writing", __PRETTY_FUNCTION__);
    }
}


// Append CFString-style format + arguments
//
static void _plistAppendFormat(CFMutableDataRef mData, CFStringRef format, ...) {
    CFStringRef fStr; 
    va_list argList;

    va_start(argList, format);
    fStr = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, NULL, format, argList);
    va_end(argList);

    CFAssert1(fStr, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
    _plistAppendString(mData, fStr);
    CFRelease(fStr);
}



static void _appendIndents(CFIndex numIndents, CFMutableDataRef str) {
#define NUMTABS 4
    static const UniChar tabs[NUMTABS] = {'\t','\t','\t','\t'};
    for (; numIndents > 0; numIndents -= NUMTABS) _plistAppendCharacters(str, tabs, (numIndents >= NUMTABS) ? NUMTABS : numIndents);
}

/* Append the escaped version of origStr to mStr.
*/
static void _appendEscapedString(CFStringRef origStr, CFMutableDataRef mStr) {
#define BUFSIZE 64
    CFIndex i, length = CFStringGetLength(origStr);
    CFIndex bufCnt = 0;
    UniChar buf[BUFSIZE];
    CFStringInlineBuffer inlineBuffer;

    CFStringInitInlineBuffer(origStr, &inlineBuffer, CFRangeMake(0, length));

    for (i = 0; i < length; i ++) {
	UniChar ch = __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer, i);
        switch(ch) {
            case '<':
		if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
		bufCnt = 0;
	  	_plistAppendUTF8CString(mStr, "&lt;");
                break;
            case '>':
		if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
		bufCnt = 0;
	  	_plistAppendUTF8CString(mStr, "&gt;");
                break;
            case '&':
		if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
		bufCnt = 0;
	  	_plistAppendUTF8CString(mStr, "&amp;");
                break;
            default:
		buf[bufCnt++] = ch;
		if (bufCnt == BUFSIZE) {
		    _plistAppendCharacters(mStr, buf, bufCnt);
		    bufCnt = 0;
		}
		break;
        }
    }
    if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
}



/* Base-64 encoding/decoding */

/* The base-64 encoding packs three 8-bit bytes into four 7-bit ASCII
 * characters.  If the number of bytes in the original data isn't divisable
 * by three, "=" characters are used to pad the encoded data.  The complete
 * set of characters used in base-64 are:
 *
 *      'A'..'Z' => 00..25
 *      'a'..'z' => 26..51
 *      '0'..'9' => 52..61
 *      '+'      => 62
 *      '/'      => 63
 *      '='      => pad
 */

// Write the inputData to the mData using Base 64 encoding

static void _XMLPlistAppendDataUsingBase64(CFMutableDataRef mData, CFDataRef inputData, CFIndex indent) {
    static const char __CFPLDataEncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    #define MAXLINELEN 76
    char buf[MAXLINELEN + 4 + 2];	// For the slop and carriage return and terminating NULL
    
    const uint8_t *bytes = CFDataGetBytePtr(inputData);
    CFIndex length = CFDataGetLength(inputData);
    CFIndex i, pos;
    const uint8_t *p;

    if (indent > 8) indent = 8; // refuse to indent more than 64 characters

    pos = 0;		// position within buf

    for (i = 0, p = bytes; i < length; i++, p++) {
        /* 3 bytes are encoded as 4 */
        switch (i % 3) {
            case 0:
                buf[pos++] = __CFPLDataEncodeTable [ ((p[0] >> 2) & 0x3f)];
                break;
            case 1:
                buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 4) & 0x3f)];
                break;
            case 2:
                buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 6) & 0x3f)];
                buf[pos++] = __CFPLDataEncodeTable [ (p[0] & 0x3f)];
                break;
        }
        /* Flush the line out every 76 (or fewer) chars --- indents count against the line length*/
        if (pos >= MAXLINELEN - 8 * indent) {
            buf[pos++] = '\n';
            buf[pos++] = 0;
            _appendIndents(indent, mData);
            _plistAppendUTF8CString(mData, buf);
            pos = 0;
        }
    }
        
    switch (i % 3) {
	case 0:
            break;
	case 1:
            buf[pos++] = __CFPLDataEncodeTable [ ((p[-1] << 4) & 0x30)];
            buf[pos++] = '=';
            buf[pos++] = '=';
            break;
	case 2:
            buf[pos++] =  __CFPLDataEncodeTable [ ((p[-1] << 2) & 0x3c)];
            buf[pos++] = '=';
            break;
    }
    
    if (pos > 0) {
        buf[pos++] = '\n';
        buf[pos++] = 0;
        _appendIndents(indent, mData);
        _plistAppendUTF8CString(mData, buf);
    }
}

extern CFStringRef __CFNumberCopyFormattingDescriptionAsFloat64(CFTypeRef cf);

static void _CFAppendXML0(CFTypeRef object, UInt32 indentation, CFMutableDataRef xmlString) {
    UInt32 typeID = CFGetTypeID(object);
    _appendIndents(indentation, xmlString);
    if (typeID == stringtype) {
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">");
	_appendEscapedString((CFStringRef)object, xmlString);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
#if DEPLOYMENT_TARGET_MACOSX
    } else if (typeID == _CFKeyedArchiverUIDGetTypeID()) {
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
	    _appendIndents(indentation+1, xmlString);
	    _plistAppendUTF8CString(xmlString, "<");
	    _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
	    _plistAppendUTF8CString(xmlString, ">");
	    _appendEscapedString(CFSTR("CF$UID"), xmlString);
	    _plistAppendUTF8CString(xmlString, "</");
	    _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
	    _plistAppendUTF8CString(xmlString, ">\n");
	    _appendIndents(indentation + 1, xmlString);
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">");

	    uint64_t v = _CFKeyedArchiverUIDGetValue((CFKeyedArchiverUIDRef)object);
	    CFNumberRef num = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt64Type, &v);
            _plistAppendFormat(xmlString, CFSTR("%@"), num);
	    CFRelease(num);

            _plistAppendUTF8CString(xmlString, "</");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">\n");
        _appendIndents(indentation, xmlString);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
#elif 0
#else
#error Unknown or unspecified DEPLOYMENT_TARGET
#endif
    } else if (typeID == arraytype) {
        UInt32 i, count = CFArrayGetCount((CFArrayRef)object);
        if (count == 0) {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, "/>\n");
            return;
        }
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
        for (i = 0; i < count; i ++) {
            _CFAppendXML0(CFArrayGetValueAtIndex((CFArrayRef)object, i), indentation+1, xmlString);
        }
        _appendIndents(indentation, xmlString);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
    } else if (typeID == dicttype) {
        UInt32 i, count = CFDictionaryGetCount((CFDictionaryRef)object);
        CFMutableArrayRef keyArray;
        CFTypeRef *keys;
        if (count == 0) {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, "/>\n");
            return;
        }
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
        keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, count * sizeof(CFTypeRef), 0);
        CFDictionaryGetKeysAndValues((CFDictionaryRef)object, keys, NULL);
        keyArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, count, &kCFTypeArrayCallBacks);
        CFArrayReplaceValues(keyArray, CFRangeMake(0, 0), keys, count);
        CFArraySortValues(keyArray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, NULL);
        CFArrayGetValues(keyArray, CFRangeMake(0, count), keys);
        CFRelease(keyArray);
        for (i = 0; i < count; i ++) {
            CFTypeRef key = keys[i];
            _appendIndents(indentation+1, xmlString);
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">");
	    _appendEscapedString((CFStringRef)key, xmlString);
            _plistAppendUTF8CString(xmlString, "</");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">\n");
            _CFAppendXML0(CFDictionaryGetValue((CFDictionaryRef)object, key), indentation+1, xmlString);
        }
        CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys);
        _appendIndents(indentation, xmlString);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
    } else if (typeID == datatype) {
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
        _XMLPlistAppendDataUsingBase64(xmlString, (CFDataRef)object, indentation);       
        _appendIndents(indentation, xmlString);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
    } else if (typeID == datetype) {
        // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
	int32_t y = 0, M = 0, d = 0, H = 0, m = 0, s = 0;
#if 1
        CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime((CFDateRef)object), NULL);
	y = date.year;
	M = date.month;
	d = date.day;
	H = date.hour;
	m = date.minute;
	s = (int32_t)date.second;
#else
	CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, kCFGregorianCalendar);
	CFTimeZoneRef tz = CFTimeZoneCreateWithName(kCFAllocatorSystemDefault, CFSTR("GMT"), true);
	CFCalendarSetTimeZone(calendar, tz);
	CFCalendarDecomposeAbsoluteTime(calendar, CFDateGetAbsoluteTime((CFDateRef)object), (const uint8_t *)"yMdHms", &y, &M, &d, &H, &m, &s);
	CFRelease(calendar);
	CFRelease(tz);
#endif
        _plistAppendUTF8CString(xmlString, "<");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">");
        _plistAppendFormat(xmlString, CFSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), y, M, d, H, m, s);
        _plistAppendUTF8CString(xmlString, "</");
        _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
        _plistAppendUTF8CString(xmlString, ">\n");
    } else if (typeID == numbertype) {
        if (CFNumberIsFloatType((CFNumberRef)object)) {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">");
                CFStringRef s = __CFNumberCopyFormattingDescriptionAsFloat64(object);
                _plistAppendString(xmlString, s);
                CFRelease(s);
            _plistAppendUTF8CString(xmlString, "</");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">\n");
        } else {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">");

            _plistAppendFormat(xmlString, CFSTR("%@"), object);

            _plistAppendUTF8CString(xmlString, "</");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, ">\n");
        }
    } else if (typeID == booltype) {
        if (CFBooleanGetValue((CFBooleanRef)object)) {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, "/>\n");
        } else {
            _plistAppendUTF8CString(xmlString, "<");
            _plistAppendCharacters(xmlString, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH);
            _plistAppendUTF8CString(xmlString, "/>\n");
        }
    }
}

static void _CFGenerateXMLPropertyListToData(CFMutableDataRef xml, CFTypeRef propertyList) {
    _plistAppendUTF8CString(xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ");
    _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
    _plistAppendUTF8CString(xml, " PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<");
    _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
    _plistAppendUTF8CString(xml, " version=\"1.0\">\n");

    _CFAppendXML0(propertyList, 0, xml);

    _plistAppendUTF8CString(xml, "</");
    _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
    _plistAppendUTF8CString(xml, ">\n");
}

CFDataRef CFPropertyListCreateXMLData(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
    initStatics();
    CFMutableDataRef xml;
    CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
    __CFAssertIsPList(propertyList);
	if (!CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0)) return NULL;
    xml = CFDataCreateMutable(allocator, 0);
    _CFGenerateXMLPropertyListToData(xml, propertyList);
    return xml;
}

CFDataRef _CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
    initStatics();
    CFMutableDataRef xml;
    CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
    xml = CFDataCreateMutable(allocator, 0);
    _CFGenerateXMLPropertyListToData(xml, propertyList);
    return xml;
}

// ========================================================================

//
// ------------------------- Reading plists ------------------
// 

static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo);
static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey);

// warning: doesn't have a good idea of Unicode line separators
static UInt32 lineNumber(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *p = pInfo->begin;
    UInt32 count = 1;
    while (p < pInfo->curr) {
        if (*p == '\r') {
            count ++;
            if (*(p + 1) == '\n')
                p ++;
        } else if (*p == '\n') {
            count ++;
        }
        p ++;
    }
    return count;
}

// warning: doesn't have a good idea of Unicode white space
CF_INLINE void skipWhitespace(_CFXMLPlistParseInfo *pInfo) {
    while (pInfo->curr < pInfo->end) {
        switch (*(pInfo->curr)) {
            case ' ':
            case '\t':
            case '\n':
            case '\r':
                pInfo->curr ++;
                continue;
            default:
                return;
        }
    }
}

/* All of these advance to the end of the given construct and return a pointer to the first character beyond the construct.  If the construct doesn't parse properly, NULL is returned. */

// pInfo should be just past "<!--"
static void skipXMLComment(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *p = pInfo->curr;
    const UniChar *end = pInfo->end - 3; // Need at least 3 characters to compare against
    while (p < end) {
        if (*p == '-' && *(p+1) == '-' && *(p+2) == '>') {
            pInfo->curr = p+3;
            return;
        }
        p ++; 
    }
    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo));
}

// stringToMatch and buf must both be of at least len
static Boolean matchString(const UniChar *buf, const UniChar *stringToMatch, UInt32 len) {
    switch (len) {
	case 10: if (buf[9] != stringToMatch[9]) return false;
	case 9: if (buf[8] != stringToMatch[8]) return false;
	case 8: if (buf[7] != stringToMatch[7]) return false;
	case 7: if (buf[6] != stringToMatch[6]) return false;
	case 6: if (buf[5] != stringToMatch[5]) return false;
	case 5: if (buf[4] != stringToMatch[4]) return false;
	case 4: if (buf[3] != stringToMatch[3]) return false;
	case 3: if (buf[2] != stringToMatch[2]) return false;
	case 2: if (buf[1] != stringToMatch[1]) return false;
	case 1: if (buf[0] != stringToMatch[0]) return false;
	case 0: return true;
    }
    return false; // internal error
}

// pInfo should be set to the first character after "<?"
static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *begin = pInfo->curr, *end = pInfo->end - 2; // Looking for "?>" so we need at least 2 characters
    while (pInfo->curr < end) {
        if (*(pInfo->curr) == '?' && *(pInfo->curr+1) == '>') {
            pInfo->curr += 2;
            return;
        }
        pInfo->curr ++; 
    }
    pInfo->curr = begin;
    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing the processing instruction begun on line %d"), lineNumber(pInfo));
}

// first character should be immediately after the "<!"
static void skipDTD(_CFXMLPlistParseInfo *pInfo) {
    // First pass "DOCTYPE"
    if (pInfo->end - pInfo->curr < DOCTYPE_TAG_LENGTH || !matchString(pInfo->curr, CFXMLPlistTags[DOCTYPE_IX], DOCTYPE_TAG_LENGTH)) {
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed DTD on line %d"), lineNumber(pInfo));
        return;
    }
    pInfo->curr += DOCTYPE_TAG_LENGTH;
    skipWhitespace(pInfo);

    // Look for either the beginning of a complex DTD or the end of the DOCTYPE structure
    while (pInfo->curr < pInfo->end) {
        UniChar ch = *(pInfo->curr);
        if (ch == '[') break; // inline DTD
        if (ch == '>') {  // End of the DTD
            pInfo->curr ++;
            return;
        }
        pInfo->curr ++;
    }
    if (pInfo->curr == pInfo->end) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
        return;
    }

    // *Sigh* Must parse in-line DTD
    skipInlineDTD(pInfo);
    if (pInfo->errorString)  return;
    skipWhitespace(pInfo);
    if (pInfo->errorString) return;
    if (pInfo->curr < pInfo->end) {
        if (*(pInfo->curr) == '>') {
            pInfo->curr ++;
        } else {
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo->curr), lineNumber(pInfo));
        }
    } else {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
    }
}

static void skipPERef(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *p = pInfo->curr;
    while (p < pInfo->end) {
        if (*p == ';') {
            pInfo->curr = p+1;
            return;
        }
        p ++;
    }
    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing percent-escape sequence begun on line %d"), lineNumber(pInfo));
}

// First character should be just past '['
static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo) {
    while (!pInfo->errorString && pInfo->curr < pInfo->end) {
        UniChar ch;
        skipWhitespace(pInfo);
        ch = *pInfo->curr;
        if (ch == '%') {
            pInfo->curr ++;
            skipPERef(pInfo);
        } else if (ch == '<') {
            pInfo->curr ++;
            if (pInfo->curr >= pInfo->end) {
                pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
                return;
            }
            ch = *(pInfo->curr);
            if (ch == '?') {
                pInfo->curr ++;
                skipXMLProcessingInstruction(pInfo);
            } else if (ch == '!') {
                if (pInfo->curr + 2 < pInfo->end && (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-')) {
                    pInfo->curr += 3;
                    skipXMLComment(pInfo);
                } else {
                    // Skip the myriad of DTD declarations of the form "<!string" ... ">"
                    pInfo->curr ++; // Past both '<' and '!'
                    while (pInfo->curr < pInfo->end) {
                        if (*(pInfo->curr) == '>') break;
                        pInfo->curr ++;
                    }
                    if (*(pInfo->curr) != '>') {
                        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
                        return;
                 
   }
                    pInfo->curr ++;
                }
            } else {
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
                return;
            }
        } else if (ch == ']') {
            pInfo->curr ++;
            return;
        } else {
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
            return;
        }
    }
    if (!pInfo->errorString)
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
}

/* A bit wasteful to do everything with unichars (since we know all the characters we're going to see are 7-bit ASCII), but since our data is coming from or going to a CFString, this prevents the extra cost of converting formats. */

static const signed char __CFPLDataDecodeTable[128] = {
    /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1,
    /* 010 */ -1, -1, -1, -1, -1, -1, -1, -1,
    /* 020 */ -1, -1, -1, -1, -1, -1, -1, -1,
    /* 030 */ -1, -1, -1, -1, -1, -1, -1, -1,
    /* ' ' */ -1, -1, -1, -1, -1, -1, -1, -1,
    /* '(' */ -1, -1, -1, 62, -1, -1, -1, 63,
    /* '0' */ 52, 53, 54, 55, 56, 57, 58, 59,
    /* '8' */ 60, 61, -1, -1, -1,  0, -1, -1,
    /* '@' */ -1,  0,  1,  2,  3,  4,  5,  6,
    /* 'H' */  7,  8,  9, 10, 11, 12, 13, 14,
    /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22,
    /* 'X' */ 23, 24, 25, -1, -1, -1, -1, -1,
    /* '`' */ -1, 26, 27, 28, 29, 30, 31, 32,
    /* 'h' */ 33, 34, 35, 36, 37, 38, 39, 40,
    /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48,
    /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1
};

static CFDataRef __CFPLDataDecode(_CFXMLPlistParseInfo *pInfo, Boolean isMutable) {
    int tmpbufpos = 0;
    int tmpbuflen = 256;
    uint8_t *tmpbuf;
    int numeq = 0;
    int acc = 0;
    int cntr = 0;

    tmpbuf = (uint8_t *)CFAllocatorAllocate(pInfo->allocator, tmpbuflen, 0);
    for (; pInfo->curr < pInfo->end; pInfo->curr++) {
        UniChar c = *(pInfo->curr);
        if (c == '<') {
            break;
	}
        if ('=' == c) {
            numeq++;
        } else if (!isspace(c)) {
            numeq = 0;
        }
        if (__CFPLDataDecodeTable[c] < 0)
            continue;
        cntr++;
        acc <<= 6;
        acc += __CFPLDataDecodeTable[c];
        if (0 == (cntr & 0x3)) {
            if (tmpbuflen <= tmpbufpos + 2) {
		if (tmpbuflen < 256 * 1024) {
		    tmpbuflen *= 4;
		} else if (tmpbuflen < 16 * 1024 * 1024) {
		    tmpbuflen *= 2;
		} else {
		    // once in this stage, this will be really slow
		    // and really potentially fragment memory
		    tmpbuflen += 256 * 1024;
		}
                tmpbuf = (uint8_t *)CFAllocatorReallocate(pInfo->allocator, tmpbuf, tmpbuflen, 0);
		if (!tmpbuf) HALT;
            }
            tmpbuf[tmpbufpos++] = (acc >> 16) & 0xff;
            if (numeq < 2)
                tmpbuf[tmpbufpos++] = (acc >> 8) & 0xff;
            if (numeq < 1)
                tmpbuf[tmpbufpos++] = acc & 0xff;
        }
    }
    if (isMutable) {
        CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
        CFDataAppendBytes(result, tmpbuf, tmpbufpos);
	CFAllocatorDeallocate(pInfo->allocator, tmpbuf);
        return result;
    } else {
        return CFDataCreateWithBytesNoCopy(pInfo->allocator, tmpbuf, tmpbufpos, pInfo->allocator);
    }
}

// content ::== (element | CharData | Reference | CDSect | PI | Comment)*
// In the context of a plist, CharData, Reference and CDSect are not legal (they all resolve to strings).  Skipping whitespace, then, the next character should be '<'.  From there, we figure out which of the three remaining cases we have (element, PI, or Comment).
static CFTypeRef getContentObject(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
    if (isKey) *isKey = false;
    while (!pInfo->errorString && pInfo->curr < pInfo->end) {
        skipWhitespace(pInfo);
        if (pInfo->curr >= pInfo->end) {
            pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
            return NULL;
        }
        if (*(pInfo->curr) != '<') {
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
            return NULL;
        }
        pInfo->curr ++;
        if (pInfo->curr >= pInfo->end) {
            pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
            return NULL;
        }
        switch (*(pInfo->curr)) {
            case '?':
                // Processing instruction
                skipXMLProcessingInstruction(pInfo);
                break;
            case '!':
                // Could be a comment
                if (pInfo->curr+2 >= pInfo->end) {
                    pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
                    return NULL;
                }
                if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') {
                    pInfo->curr += 2;
                    skipXMLComment(pInfo);
                } else {
                    pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
                    return NULL;
                }
                break;
            case '/':
                // Whoops!  Looks like we got to the end tag for the element whose content we're parsing
                pInfo->curr --; // Back off to the '<'
                return NULL;
            default:
                // Should be an element
                return parseXMLElement(pInfo, isKey);
        }
    }
    // Do not set the error string here; if it wasn't already set by one of the recursive parsing calls, the caller will quickly detect the failure (b/c pInfo->curr >= pInfo->end) and provide a more useful one of the form "end tag for <blah> not found"
    return NULL;
}

static void _catFromMarkToBuf(const UniChar *mark, const UniChar *buf, CFMutableStringRef *string, CFAllocatorRef allocator ) {
    if (!(*string)) {
        *string = CFStringCreateMutable(allocator, 0);
    }
    CFStringAppendCharacters(*string, mark, buf-mark);
}

static void parseCDSect_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
    const UniChar *end, *begin;
    if (pInfo->end - pInfo->curr < CDSECT_TAG_LENGTH) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
        return;
    }
    if (!matchString(pInfo->curr, CFXMLPlistTags[CDSECT_IX], CDSECT_TAG_LENGTH)) {
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered improper CDATA opening at line %d"), lineNumber(pInfo));
        return;
    }
    pInfo->curr += CDSECT_TAG_LENGTH;
    begin = pInfo->curr; // Marks the first character of the CDATA content
    end = pInfo->end-2; // So we can safely look 2 characters beyond p
    while (pInfo->curr < end) {
        if (*(pInfo->curr) == ']' && *(pInfo->curr+1) == ']' && *(pInfo->curr+2) == '>') {
           // Found the end!
            CFStringAppendCharacters(string, begin, pInfo->curr-begin);
            pInfo->curr += 3;
            return;
        }
        pInfo->curr ++;
    }
    // Never found the end mark
    pInfo->curr = begin;
    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not find end of CDATA started on line %d"), lineNumber(pInfo));
}

// Only legal references are {lt, gt, amp, apos, quote, #ddd, #xAAA}
static void parseEntityReference_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
    int len;
    UniChar ch;
    pInfo->curr ++; // move past the '&';
    len = pInfo->end - pInfo->curr; // how many characters we can safely scan
    if (len < 1) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
        return;
    }
    switch (*(pInfo->curr)) {
        case 'l':  // "lt"
            if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
                ch = '<';
                pInfo->curr += 3;
                break;
            }
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
            return;
        case 'g': // "gt"
            if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
                ch = '>';
                pInfo->curr += 3;
                break;
            }
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
            return;
        case 'a': // "apos" or "amp"
            if (len < 4) {   // Not enough characters for either conversion
                pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
                return;
            }
            if (*(pInfo->curr+1) == 'm') {
                // "amp"
                if (*(pInfo->curr+2) == 'p' && *(pInfo->curr+3) == ';') {
                    ch = '&';
                    pInfo->curr += 4;
                    break;
                }
            } else if (*(pInfo->curr+1) == 'p') {
                // "apos"
                if (len > 4 && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 's' && *(pInfo->curr+4) == ';') {
                    ch = '\'';
                    pInfo->curr += 5;
                    break;
                }
            }
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
            return;
        case 'q':  // "quote"
            if (len >= 5 && *(pInfo->curr+1) == 'u' && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 't' && *(pInfo->curr+4) == ';') {
                ch = '\"';
                pInfo->curr += 5;
                break;
            }
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
            return;
        case '#':
        {
            uint16_t num = 0;
            Boolean isHex = false;
            if ( len < 4) {  // Not enough characters to make it all fit!  Need at least "&#d;"
                pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
                return;
            }
            pInfo->curr ++;
            if (*(pInfo->curr) == 'x') {
                isHex = true;
                pInfo->curr ++;
            }
            while (pInfo->curr < pInfo->end) {
                ch = *(pInfo->curr);
                pInfo->curr ++;
                if (ch == ';') {
                    CFStringAppendCharacters(string, &num, 1);
                    return;
                }
                if (!isHex) num = num*10;
                else num = num << 4;
                if (ch <= '9' && ch >= '0') {
                    num += (ch - '0');
                } else if (!isHex) {
                    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
                    return;
                } else if (ch >= 'a' && ch <= 'f') {
                    num += 10 + (ch - 'a');
                } else if (ch >= 'A' && ch <= 'F') {
                    num += 10 + (ch - 'A');
                } else {
                    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
                    return;                    
                }
            }
            pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
            return;
        }
        default:
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
            return;
    }
    CFStringAppendCharacters(string, &ch, 1);
}

static CFStringRef _uniqueStringForString(_CFXMLPlistParseInfo *pInfo, CFStringRef stringToUnique) {
    if (!pInfo->stringSet) {
        pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
	_CFSetSetCapacity(pInfo->stringSet, 160);	// set capacity high to avoid lots of rehashes, though waste some memory
    }
    CFSetAddValue(pInfo->stringSet, stringToUnique);
    return (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
}

extern void _CFStrSetDesiredCapacity(CFMutableStringRef str, CFIndex len);

static CFStringRef _uniqueStringForCharacters(_CFXMLPlistParseInfo *pInfo, const UniChar *base, CFIndex length) {
    CFIndex idx;
    uint8_t *ascii, buffer[1024];
    bool isASCII;
    if (!pInfo->stringSet) {
        pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
	_CFSetSetCapacity(pInfo->stringSet, 160);	// set capacity high to avoid lots of rehashes, though waste some memory
    }
    if (pInfo->tmpString) {
	CFStringDelete(pInfo->tmpString, CFRangeMake(0, CFStringGetLength(pInfo->tmpString)));
    } else {
        pInfo->tmpString = CFStringCreateMutable(pInfo->allocator, 0);
	_CFStrSetDesiredCapacity(pInfo->tmpString, 512);
    }
    // This is to avoid having to promote the buffers of all the strings compared against
    // during the set probe; if a Unicode string is passed in, that's what happens.
    isASCII = true;
    for (idx = 0; isASCII && idx < length; idx++) isASCII = isASCII && (base[idx] < 0x80);
    if (isASCII) {
	ascii = (length < (CFIndex)sizeof(buffer)) ? buffer : (uint8_t *)CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
	for (idx = 0; idx < length; idx++) ascii[idx] = (uint8_t)base[idx];
	ascii[length] = '\0';
	CFStringAppendCString(pInfo->tmpString, (char *)ascii, kCFStringEncodingASCII);
	if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
    } else {
	CFStringAppendCharacters(pInfo->tmpString, base, length);
    }
    CFSetAddValue(pInfo->stringSet, pInfo->tmpString);
    return (CFStringRef)CFSetGetValue(pInfo->stringSet, pInfo->tmpString);
}


// String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
// returns a retained object in *string.
static CFStringRef getString(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *mark = pInfo->curr; // At any time in the while loop below, the characters between mark and p have not yet been added to *string
    CFMutableStringRef string = NULL;
    while (!pInfo->errorString && pInfo->curr < pInfo->end) {
        UniChar ch = *(pInfo->curr);
        if (ch == '<') {
	    if (pInfo->curr + 1 >= pInfo->end) break;
            // Could be a CDSect; could be the end of the string
            if (*(pInfo->curr+1) != '!') break; // End of the string
            _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
            parseCDSect_pl(pInfo, string);
            mark = pInfo->curr;
        } else if (ch == '&') {
            _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
            parseEntityReference_pl(pInfo, string);
            mark = pInfo->curr;
        } else {
            pInfo->curr ++;
        }
    }

    if (pInfo->errorString) {
        if (string) CFRelease(string);
        return NULL;
    }
    if (!string) {
        if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
            CFStringRef uniqueString = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
            if (uniqueString) CFRetain(uniqueString);
            return uniqueString;
        } else {
            string = CFStringCreateMutable(pInfo->allocator, 0);
            CFStringAppendCharacters(string, mark, pInfo->curr - mark);
            return string;
        }
    }
    _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
    if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
        CFStringRef uniqueString = _uniqueStringForString(pInfo, string);
        if (uniqueString) CFRetain(uniqueString);
        CFRelease(string);
        return uniqueString;
    }
    return string;
}

static Boolean checkForCloseTag(_CFXMLPlistParseInfo *pInfo, const UniChar *tag, CFIndex tagLen) {
    if (pInfo->end - pInfo->curr < tagLen + 3) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
        return false;
    }
    if (*(pInfo->curr) != '<' || *(++pInfo->curr) != '/') {
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
        return false;
    }
    pInfo->curr ++;
    if (!matchString(pInfo->curr, tag, tagLen)) {
        CFStringRef str = CFStringCreateWithCharactersNoCopy(pInfo->allocator, tag, tagLen, kCFAllocatorNull);
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Close tag on line %d does not match open tag %@"), lineNumber(pInfo), str);
        CFRelease(str);
        return false;
    }
    pInfo->curr += tagLen;
    skipWhitespace(pInfo);
    if (pInfo->curr == pInfo->end) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
        return false;
    }
    if (*(pInfo->curr) != '>') {
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
        return false;
    }
    pInfo->curr ++;
    return true;
}

// pInfo should be set to the first content character of the <plist>
static CFTypeRef parsePListTag(_CFXMLPlistParseInfo *pInfo) {
    CFTypeRef result, tmp = NULL;
    const UniChar *save;
    result = getContentObject(pInfo, NULL);
    if (!result) {
        if (!pInfo->errorString) pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
        return NULL;
    }
    save = pInfo->curr; // Save this in case the next step fails
    tmp = getContentObject(pInfo, NULL);
    if (tmp) {
        // Got an extra object
        CFRelease(tmp);
        CFRelease(result);
        pInfo->curr = save;
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo));
        return NULL;
    }
    if (pInfo->errorString) {
        // Parse failed catastrophically
        CFRelease(result);
        return NULL;
    }
    if (checkForCloseTag(pInfo, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH)) {
        return result;
    }
    CFRelease(result);
    return NULL;
}

static int allowImmutableCollections = -1;

static void checkImmutableCollections(void) {
    allowImmutableCollections = (NULL == getenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
}

static CFTypeRef parseArrayTag(_CFXMLPlistParseInfo *pInfo) {
    CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
    CFTypeRef tmp = getContentObject(pInfo, NULL);
    while (tmp) {
        CFArrayAppendValue(array, tmp);
        CFRelease(tmp);
        tmp = getContentObject(pInfo, NULL);
    }
    if (pInfo->errorString) { // getContentObject encountered a parse error
        CFRelease(array);
        return NULL;
    }
    if (checkForCloseTag(pInfo, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH)) {
	if (-1 == allowImmutableCollections) checkImmutableCollections();
	if (1 == allowImmutableCollections) {
	    if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
		CFArrayRef newArray = CFArrayCreateCopy(pInfo->allocator, array);
		CFRelease(array);
		array = (CFMutableArrayRef)newArray;
	    }
	}
	return array;
    }
    CFRelease(array);
    return NULL;
}

static CFTypeRef parseDictTag(_CFXMLPlistParseInfo *pInfo) {
    CFMutableDictionaryRef dict = NULL;
    CFTypeRef key=NULL, value=NULL;
    Boolean gotKey;
    const UniChar *base = pInfo->curr;
    key = getContentObject(pInfo, &gotKey);
    while (key) {
        if (!gotKey) {
            if (key) CFRelease(key);
            if (dict) CFRelease(dict);
            pInfo->curr = base;
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo));
            return NULL;
        }
        value = getContentObject(pInfo, NULL);
        if (!value) {
            if (key) CFRelease(key);
            if (dict) CFRelease(dict);
            if (!pInfo->errorString)
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo));
            return NULL;
        }
	if (NULL == dict) {
	    dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	    _CFDictionarySetCapacity(dict, 10);
	}
        CFDictionarySetValue(dict, key, value);
        CFRelease(key);
        key = NULL;
        CFRelease(value);
        value = NULL;
        base = pInfo->curr;
        key = getContentObject(pInfo, &gotKey);
    }
    if (checkForCloseTag(pInfo, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH)) {
	if (NULL == dict) {
	    if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
		dict = (CFMutableDictionaryRef)CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	    } else {
		dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	    }
	} else {
	    CFIndex cnt = CFDictionaryGetCount(dict);
#if DEPLOYMENT_TARGET_MACOSX
	    if (1 == cnt) {
		CFTypeRef val = CFDictionaryGetValue(dict, CFSTR("CF$UID"));
		if (val && CFGetTypeID(val) == numbertype) {
		    CFTypeRef uid;
		    uint32_t v;
		    CFNumberGetValue((CFNumberRef)val, kCFNumberSInt32Type, &v);
		    uid = (CFTypeRef)_CFKeyedArchiverUIDCreate(pInfo->allocator, v);
		    CFRelease(dict);
		    return uid;
		}
	    }
#endif
	    if (-1 == allowImmutableCollections) checkImmutableCollections();
	    if (1 == allowImmutableCollections) {
		if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
		    CFDictionaryRef newDict = CFDictionaryCreateCopy(pInfo->allocator, dict);
		    CFRelease(dict);
		    dict = (CFMutableDictionaryRef)newDict;
		}
	    }
	}
        return dict;
    }
    if (dict) CFRelease(dict);
    return NULL;
}

static CFTypeRef parseDataTag(_CFXMLPlistParseInfo *pInfo) {
    CFDataRef result;
    const UniChar *base = pInfo->curr;
    result = __CFPLDataDecode(pInfo, pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves);
    if (!result) {
        pInfo->curr = base;
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo));
        return NULL;
    }
    if (checkForCloseTag(pInfo, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH)) return result;
    CFRelease(result);
    return NULL;
}

CF_INLINE Boolean read2DigitNumber(_CFXMLPlistParseInfo *pInfo, int32_t *result) {
    UniChar ch1, ch2;
    if (pInfo->curr + 2 >= pInfo->end) return false;
    ch1 = *pInfo->curr;
    ch2 = *(pInfo->curr + 1);
    pInfo->curr += 2;
    if (!isdigit(ch1) || !isdigit(ch2)) return false;
    *result = (ch1 - '0')*10 + (ch2 - '0');
    return true;
}

// YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
static CFTypeRef parseDateTag(_CFXMLPlistParseInfo *pInfo) {
    int32_t year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
    int32_t num = 0;
    Boolean badForm = false;

    while (pInfo->curr < pInfo->end && isdigit(*pInfo->curr)) {
        year = 10*year + (*pInfo->curr) - '0';
        pInfo->curr ++;
    }
    if (pInfo->curr >= pInfo->end || *pInfo->curr != '-') {
        badForm = true;
    } else {
        pInfo->curr ++;
    }

    if (!badForm && read2DigitNumber(pInfo, &month) && pInfo->curr < pInfo->end && *pInfo->curr == '-') {
        pInfo->curr ++;
    } else {
        badForm = true;
    }

    if (!badForm && read2DigitNumber(pInfo, &day) && pInfo->curr < pInfo->end && *pInfo->curr == 'T') {
        pInfo->curr ++;
    } else {
        badForm = true;
    }

    if (!badForm && read2DigitNumber(pInfo, &hour) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
        pInfo->curr ++;
    } else {
        badForm = true;
    }

    if (!badForm && read2DigitNumber(pInfo, &minute) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
        pInfo->curr ++;
    } else {
        badForm = true;
    }

    if (!badForm && read2DigitNumber(pInfo, &num) && pInfo->curr < pInfo->end && *pInfo->curr == 'Z') {
        second = num;
        pInfo->curr ++;
    } else {
        badForm = true;
    }

    if (badForm) {
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo));
        return NULL;
    }
    if (!checkForCloseTag(pInfo, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH)) return NULL;

    CFAbsoluteTime at = 0.0;
#if 1
    CFGregorianDate date = {year, month, day, hour, minute, second};
    at = CFGregorianDateGetAbsoluteTime(date, NULL);
#else
    CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, kCFGregorianCalendar);
    CFTimeZoneRef tz = CFTimeZoneCreateWithName(kCFAllocatorSystemDefault, CFSTR("GMT"), true);
    CFCalendarSetTimeZone(calendar, tz);
    CFCalendarComposeAbsoluteTime(calendar, &at, (const uint8_t *)"yMdHms", year, month, day, hour, minute, second);
    CFRelease(calendar);
    CFRelease(tz);
#endif
    return CFDateCreate(pInfo->allocator, at);
}

static CFTypeRef parseRealTag(_CFXMLPlistParseInfo *pInfo) {
    CFStringRef str = getString(pInfo);
    SInt32 idx, len;
    double val;
    CFNumberRef result;
    CFStringInlineBuffer buf;
    if (!str) {
        if (!pInfo->errorString)
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
        return NULL;
    }
    
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("nan"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNaN) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+infinity"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-infinity"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("infinity"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-inf"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("inf"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
	}
	if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+inf"), kCFCompareCaseInsensitive)) {
	    CFRelease(str);
	    return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
	}

    len = CFStringGetLength(str);
    CFStringInitInlineBuffer(str, &buf, CFRangeMake(0, len));
    idx = 0;
    if (!__CFStringScanDouble(&buf, NULL, &idx, &val) || idx != len) {
        CFRelease(str);
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo));
        return NULL;
    }
    CFRelease(str);
    result = CFNumberCreate(pInfo->allocator, kCFNumberDoubleType, &val);
    if (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) return result;
    CFRelease(result);
    return NULL;
}

#define GET_CH	if (pInfo->curr == pInfo->end) {	\
			pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
			return NULL;			\
		}					\
		ch = *(pInfo->curr)

typedef struct {
    int64_t high;
    uint64_t low;
} CFSInt128Struct;

enum {
    kCFNumberSInt128Type = 17
};

static CFTypeRef parseIntegerTag(_CFXMLPlistParseInfo *pInfo) {
    bool isHex = false, isNeg = false, hadLeadingZero = false;
    UniChar ch = 0;

	// decimal_constant	S*(-|+)?S*[0-9]+		(S == space)
	// hex_constant		S*(-|+)?S*0[xX][0-9a-fA-F]+	(S == space)

    while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
    GET_CH;
    if ('<' == ch) {
	pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
	return NULL;
    }
    if ('-' == ch || '+' == ch) {
	isNeg = ('-' == ch);
	pInfo->curr++;
	while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
    }
    GET_CH;
    if ('0' == ch) {
	if (pInfo->curr + 1 < pInfo->end && ('x' == *(pInfo->curr + 1) || 'X' == *(pInfo->curr + 1))) {
	    pInfo->curr++;
	    isHex = true;
	} else {
	    hadLeadingZero = true;
	}
	pInfo->curr++;
    }
    GET_CH;
    while ('0' == ch) {
	hadLeadingZero = true;
	pInfo->curr++;
	GET_CH;
    }
    if ('<' == ch && hadLeadingZero) {	// nothing but zeros
	int32_t val = 0;
        if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
	    // checkForCloseTag() sets error string
	    return NULL;
        }
	return CFNumberCreate(pInfo->allocator, kCFNumberSInt32Type, &val);
    }
    if ('<' == ch) {
	pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo));
	return NULL;
    }
    uint64_t value = 0;
    uint32_t multiplier = (isHex ? 16 : 10);
    while ('<' != ch) {
	uint32_t new_digit = 0;
	switch (ch) {
	case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
	    new_digit = (ch - '0');
	    break;
	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
	    new_digit = (ch - 'a' + 10);
	    break;
	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
	    new_digit = (ch - 'A' + 10);
	    break;
	default:	// other character
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch, ch, lineNumber(pInfo));
	    return NULL;
	}
	if (!isHex && new_digit > 9) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo));
	    return NULL;
	}
	if (UINT64_MAX / multiplier < value) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
	    return NULL;
	}
	value = multiplier * value;
	if (UINT64_MAX - new_digit < value) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
	    return NULL;
	}
	value = value + new_digit;
	if (isNeg && (uint64_t)INT64_MAX + 1 < value) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer underflow in <integer> on line %d"), lineNumber(pInfo));
	    return NULL;
	}
	pInfo->curr++;
	GET_CH;
    }
    if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
	// checkForCloseTag() sets error string
	return NULL;
    }
    if (isNeg || value <= INT64_MAX) {
	int64_t v = value;
	if (isNeg) v = -v;	// no-op if INT64_MIN
        return CFNumberCreate(pInfo->allocator, kCFNumberSInt64Type, &v);
    }
    CFSInt128Struct val;
    val.high = 0;
    val.low = value;
    return CFNumberCreate(pInfo->allocator, kCFNumberSInt128Type, &val);
}

#undef GET_CH

// Returned object is retained; caller must free.  pInfo->curr expected to point to the first character after the '<'
static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
    const UniChar *marker = pInfo->curr;
    int markerLength = -1;
    Boolean isEmpty;
    int markerIx = -1;
    
    if (isKey) *isKey = false;
    while (pInfo->curr < pInfo->end) {
        UniChar ch = *(pInfo->curr);
        if (ch == ' ' || ch ==  '\t' || ch == '\n' || ch =='\r') {
            if (markerLength == -1) markerLength = pInfo->curr - marker;
        } else if (ch == '>') {
            break;
        }
        pInfo->curr ++;
    }
    if (pInfo->curr >= pInfo->end) return NULL;
    isEmpty = (*(pInfo->curr-1) == '/');
    if (markerLength == -1)
        markerLength = pInfo->curr - (isEmpty ? 1 : 0) - marker;
    pInfo->curr ++; // Advance past '>'
    if (markerLength == 0) {
        // Back up to the beginning of the marker
        pInfo->curr = marker;
        pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed tag on line %d"), lineNumber(pInfo));
        return NULL;
    }
    switch (*marker) {
        case 'a':   // Array
            if (markerLength == ARRAY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH))
                markerIx = ARRAY_IX;
            break;
        case 'd': // Dictionary, data, or date; Fortunately, they all have the same marker length....
            if (markerLength != DICT_TAG_LENGTH)
                break;
            if (matchString(marker, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH))
                markerIx = DICT_IX;
            else if (matchString(marker, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH))
                markerIx = DATA_IX;
            else if (matchString(marker, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH))
                markerIx = DATE_IX;
            break;
        case 'f': // false (boolean)
            if (markerLength == FALSE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) {
                markerIx = FALSE_IX;
            }
            break;
        case 'i': // integer
            if (markerLength == INTEGER_TAG_LENGTH && matchString(marker, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH))
                markerIx = INTEGER_IX;
            break;
        case 'k': // Key of a dictionary
            if (markerLength == KEY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH)) {
                markerIx = KEY_IX;
                if (isKey) *isKey = true;
            }
            break;
        case 'p': // Plist
            if (markerLength == PLIST_TAG_LENGTH && matchString(marker, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH))
                markerIx = PLIST_IX;
            break;
        case 'r': // real
            if (markerLength == REAL_TAG_LENGTH && matchString(marker, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH))
                markerIx = REAL_IX;
            break;
        case 's': // String
            if (markerLength == STRING_TAG_LENGTH && matchString(marker, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH))
                markerIx = STRING_IX;
            break;
        case 't': // true (boolean)
            if (markerLength == TRUE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH))
                markerIx = TRUE_IX;
            break;
    }

    if (!pInfo->allowNewTypes && markerIx != PLIST_IX && markerIx != ARRAY_IX && markerIx != DICT_IX && markerIx != STRING_IX && markerIx != KEY_IX && markerIx != DATA_IX) {
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered new tag when expecting only old-style property list objects", CFStringGetSystemEncoding());
        return NULL;
    }

    switch (markerIx) {
        case PLIST_IX:
            if (isEmpty) {
                pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
                return NULL;
            }
            return parsePListTag(pInfo);
        case ARRAY_IX: 
            if (isEmpty) {
                return pInfo->mutabilityOption == kCFPropertyListImmutable ?  CFArrayCreate(pInfo->allocator, NULL, 0, &kCFTypeArrayCallBacks) : CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
            } else {
                return parseArrayTag(pInfo);
            }
        case DICT_IX:
            if (isEmpty) {
                if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
                    return CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                } else {
                    return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                }
            } else {
                return parseDictTag(pInfo);
            }
        case KEY_IX:
        case STRING_IX:
        {
            CFStringRef str;
            int tagLen = (markerIx == KEY_IX) ? KEY_TAG_LENGTH : STRING_TAG_LENGTH;
            if (isEmpty) {
                return pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves ? CFStringCreateMutable(pInfo->allocator, 0) : CFStringCreateWithCharacters(pInfo->allocator, NULL, 0);
            }
            str = getString(pInfo);
            if (!str) return NULL; // getString will already have set the error string
            if (!checkForCloseTag(pInfo, CFXMLPlistTags[markerIx], tagLen)) {
                CFRelease(str);
                return NULL;
            }
            return str;
        }
        case DATA_IX:
            if (isEmpty) {
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo));
                return NULL;
            } else {
                return parseDataTag(pInfo);
            }
        case DATE_IX:
            if (isEmpty) {
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo));
                return NULL;
            } else {
                return parseDateTag(pInfo);
            }
        case TRUE_IX:
            if (!isEmpty) {
		if (!checkForCloseTag(pInfo, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH)) return NULL;
	    }
	    return CFRetain(kCFBooleanTrue);
        case FALSE_IX:
            if (!isEmpty) {
		if (!checkForCloseTag(pInfo, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) return NULL;
	    }
            return CFRetain(kCFBooleanFalse);
        case REAL_IX:
            if (isEmpty) {
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
                return NULL;
            } else {
                return parseRealTag(pInfo);
            }
        case INTEGER_IX:
            if (isEmpty) {
                pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
                return NULL;
            } else {
                return parseIntegerTag(pInfo);
            }
        default:  {
            CFStringRef markerStr = CFStringCreateWithCharacters(pInfo->allocator, marker, markerLength);
            pInfo->curr = marker;
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown tag %@ on line %d"), markerStr, lineNumber(pInfo));
            CFRelease(markerStr);
            return NULL;
        }
    }
}

static CFTypeRef parseXMLPropertyList(_CFXMLPlistParseInfo *pInfo) {
    while (!pInfo->errorString && pInfo->curr < pInfo->end) {
        UniChar ch;
        skipWhitespace(pInfo);
        if (pInfo->curr+1 >= pInfo->end) {
            pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "No XML content found", CFStringGetSystemEncoding());
            return NULL;
        }
        if (*(pInfo->curr) != '<') {
            pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character %c at line %d"), *(pInfo->curr), lineNumber(pInfo));
            return NULL;
        }
        ch = *(++ pInfo->curr);
        if (ch == '!') {
            // Comment or DTD
            ++ pInfo->curr;
            if (pInfo->curr+1 < pInfo->end && *pInfo->curr == '-' && *(pInfo->curr+1) == '-') {
                // Comment
                pInfo->curr += 2;
                skipXMLComment(pInfo);
            } else {
                skipDTD(pInfo);
            }
        } else if (ch == '?') {
            // Processing instruction
            pInfo->curr++;
            skipXMLProcessingInstruction(pInfo);
        } else {
            // Tag or malformed
            return parseXMLElement(pInfo, NULL);
            // Note we do not verify that there was only one element, so a file that has garbage after the first element will nonetheless successfully parse
        }
    }
    // Should never get here
    if (!(pInfo->errorString))
        pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
    return NULL;
}

static CFStringEncoding encodingForXMLData(CFDataRef data, CFStringRef *error) {
    const uint8_t *bytes = (uint8_t *)CFDataGetBytePtr(data);
    UInt32 length = CFDataGetLength(data);
    const uint8_t *idx, *end;
    char quote;
    
    // Check for the byte order mark first
    if (length > 2 &&
        ((*bytes == 0xFF && *(bytes+1) == 0xFE) ||
         (*bytes == 0xFE && *(bytes+1) == 0xFF) ||
         *bytes == 0x00 || *(bytes+1) == 0x00)) // This clause checks for a Unicode sequence lacking the byte order mark; technically an error, but this check is recommended by the XML spec
        return kCFStringEncodingUnicode;
    
    // Scan for the <?xml.... ?> opening
    if (length < 5 || strncmp((char const *) bytes, "<?xml", 5) != 0) return kCFStringEncodingUTF8;
    idx = bytes + 5;
    end = bytes + length;
    // Found "<?xml"; now we scan for "encoding"
    while (idx < end) {
        uint8_t ch = *idx;
        const uint8_t *scan;
        if ( ch == '?' || ch == '>') return kCFStringEncodingUTF8;
        idx ++;
        scan = idx;
        if (ch == 'e' && *scan++ == 'n' && *scan++ == 'c' && *scan++ == 'o' && *scan++ == 'd' && *scan++ == 'i'
            && *scan++ == 'n' && *scan++ == 'g' && *scan++ == '=') {
            idx = scan;
            break;
        }
    }
    if (idx >= end) return kCFStringEncodingUTF8;
    quote = *idx;
    if (quote != '\'' && quote != '\"') return kCFStringEncodingUTF8;
    else {
        CFStringRef encodingName;
        const uint8_t *base = idx+1; // Move past the quote character
        CFStringEncoding enc;
        UInt32 len;
        idx ++;
        while (idx < end && *idx != quote) idx ++;
        if (idx >= end) return kCFStringEncodingUTF8;
        len = idx - base;
        if (len == 5 && (*base == 'u' || *base == 'U') && (base[1] == 't' || base[1] == 'T') && (base[2] == 'f' || base[2] == 'F') && (base[3] == '-') && (base[4] == '8'))
            return kCFStringEncodingUTF8;
        encodingName = CFStringCreateWithBytes(kCFAllocatorSystemDefault, base, len, kCFStringEncodingISOLatin1, false);
        enc = CFStringConvertIANACharSetNameToEncoding(encodingName);
        if (enc != kCFStringEncodingInvalidId) {
            CFRelease(encodingName);
            return enc;
        }

        if (error) {
            *error = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("Encountered unknown encoding (%@)"), encodingName);
            CFRelease(encodingName);
        }
        return 0;
    }
}

extern bool __CFTryParseBinaryPlist(CFAllocatorRef allocator, CFDataRef data, CFOptionFlags option, CFPropertyListRef *plist, CFStringRef *errorString);
int32_t _CFPropertyListAllowNonUTF8 = 0;

#define SAVE_PLISTS 0

#if SAVE_PLISTS
static Boolean __savePlistData(CFDataRef data, CFOptionFlags opt) {
    uint8_t pn[2048];
    uint8_t fn[2048];
    uint32_t pnlen = sizeof(pn);
    uint8_t *pnp = NULL;
    if (0 == _NSGetExecutablePath((char *)pn, &pnlen)) {
	pnp = strrchr((char *)pn, '/');
    }
    if (!pnp) {
	pnp = pn;
    } else {
	pnp++;
    }
    if (0 == strcmp((char *)pnp, "parse_plists")) return true;
    CFUUIDRef r = CFUUIDCreate(kCFAllocatorSystemDefault);
    CFStringRef s = CFUUIDCreateString(kCFAllocatorSystemDefault, r);
    CFStringRef p = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("/tmp/plists/%s#%@#0x%x"), pnp, s, opt);
    _CFStringGetFileSystemRepresentation(p, fn, sizeof(fn));
    CFRelease(r);
    CFRelease(s);
    CFRelease(p);
    int fd = open((const char *)fn, O_WRONLY|O_CREAT|O_TRUNC, 0666);
    if (fd < 0) return false;
    int len = CFDataGetLength(data);
    int w = write(fd, CFDataGetBytePtr(data), len);
    fsync(fd);
    close(fd);
    if (w != len) return false;
    return true;
}
#endif

CFTypeRef _CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString, Boolean allowNewTypes, CFPropertyListFormat *format) {
    initStatics();
    CFStringEncoding encoding;
    CFStringRef xmlString;
    UInt32 length;
    CFPropertyListRef plist;

    if (errorString) *errorString = NULL;
    if (!xmlData || CFDataGetLength(xmlData) == 0) {
        if (errorString) {
            *errorString = CFSTR("Cannot parse a NULL or zero-length data");
            CFRetain(*errorString); // Caller expects to release
        }
        return NULL;
    }

#if SAVE_PLISTS
    __savePlistData(xmlData, option);
#endif

    if (__CFTryParseBinaryPlist(allocator, xmlData, option, &plist, errorString)) {
	if (format) *format = kCFPropertyListBinaryFormat_v1_0;
	return plist;
    }
    
    allocator = allocator ? allocator : __CFGetDefaultAllocator();
    CFRetain(allocator);
    
    encoding = encodingForXMLData(xmlData, errorString); // 0 is an error return, NOT MacRoman.

    if (encoding == 0) {
        // Couldn't find an encoding; encodingForXMLData already set *errorString if necessary
        // Note that encodingForXMLData() will give us the right values for a standard plist, too.
        if (errorString) *errorString = CFStringCreateWithFormat(allocator, NULL, CFSTR("Could not determine the encoding of the XML data"));
        return NULL;
    }

    xmlString = CFStringCreateWithBytes(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), encoding, true);
    if (NULL == xmlString && (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard) || _CFPropertyListAllowNonUTF8)) {	// conversion failed, probably because not in proper encoding
        // Call __CFStringCreateImmutableFunnel3() the same way CFStringCreateWithBytes() does, except with the addt'l flag
		if (encoding == kCFStringEncodingUTF8) xmlString = __CFStringCreateImmutableFunnel3(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), kCFStringEncodingUTF8, true, true, false, false, false, (CFAllocatorRef)-1  /* ALLOCATORSFREEFUNC */, kCFStringEncodingLenientUTF8Conversion);
    }
    length = xmlString ? CFStringGetLength(xmlString) : 0;

    if (length) {
        _CFXMLPlistParseInfo pInfoBuf;
        _CFXMLPlistParseInfo *pInfo = &pInfoBuf;
        CFTypeRef result;
        UniChar *buf = (UniChar *)CFStringGetCharactersPtr(xmlString);

        if (errorString) *errorString = NULL;
        if (!buf) {
            buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
            CFStringGetCharacters(xmlString, CFRangeMake(0, length), buf);
            CFRelease(xmlString);
            xmlString = NULL;
        }
        pInfo->begin = buf;
        pInfo->end = buf+length;
        pInfo->curr = buf;
        pInfo->allocator = allocator;
        pInfo->errorString = NULL;
        pInfo->stringSet = NULL;
        pInfo->tmpString = NULL;
        pInfo->mutabilityOption = option;
        pInfo->allowNewTypes = allowNewTypes;
        
        // Haven't done anything XML-specific to this point.  However, the encoding we used to translate the bytes should be kept in mind; we used Unicode if the byte-order mark was present; UTF-8 otherwise.  If the system encoding is not UTF-8 or some variant of 7-bit ASCII, we'll be in trouble.....
        result = parseXMLPropertyList(pInfo);
        if (result && format) *format = kCFPropertyListXMLFormat_v1_0;
        if (!result) {
	    CFStringRef err = pInfo->errorString;
            // Reset pInfo so we can try again
            pInfo->curr = pInfo->begin;
            pInfo->errorString = NULL;
            // Try pList
            result = parseOldStylePropertyListOrStringsFile(pInfo);
	    if (result && format) *format = kCFPropertyListOpenStepFormat;
            if (!result) {
		if (errorString) *errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("XML parser error:\n\t%@\nOld-style plist parser error:\n\t%@\n"), err, pInfo->errorString);
            }
	    if (err) CFRelease(err);
	    if (pInfo->errorString) CFRelease(pInfo->errorString);
        }
        if (xmlString) {
            CFRelease(xmlString);
        } else {
            CFAllocatorDeallocate(allocator, (void *)pInfo->begin);
        }
        if (pInfo->stringSet) CFRelease(pInfo->stringSet);
        if (pInfo->tmpString) CFRelease(pInfo->tmpString);
        CFRelease(allocator);
        return result;
    } else {
        if (errorString)
            *errorString = (CFStringRef)CFRetain(CFSTR("Conversion of data failed. The file is not UTF-8, or in the encoding specified in XML header if XML."));
        return NULL;
    }
}

CFTypeRef CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString) {
    initStatics();
    CFAssert1(xmlData != NULL, __kCFLogAssertion, "%s(): NULL data not allowed", __PRETTY_FUNCTION__);
    CFAssert2(option == kCFPropertyListImmutable || option == kCFPropertyListMutableContainers || option == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, option);
    return _CFPropertyListCreateFromXMLData(allocator, xmlData, option, errorString, true, NULL);
}

CFIndex CFPropertyListWriteToStream(CFPropertyListRef propertyList, CFWriteStreamRef stream, CFPropertyListFormat format, CFStringRef *errorString) {
    initStatics();
    CFAssert1(stream != NULL, __kCFLogAssertion, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__);
    CFAssert2(format == kCFPropertyListOpenStepFormat || format == kCFPropertyListXMLFormat_v1_0 || format == kCFPropertyListBinaryFormat_v1_0, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, format);
    CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
    __CFAssertIsPList(propertyList);
    CFAssert1(CFWriteStreamGetTypeID() == CFGetTypeID(stream), __kCFLogAssertion, "%s(): stream argument is not a write stream", __PRETTY_FUNCTION__);
    CFAssert1(kCFStreamStatusOpen == CFWriteStreamGetStatus(stream) || kCFStreamStatusWriting == CFWriteStreamGetStatus(stream), __kCFLogAssertion, "%s():  stream is not open", __PRETTY_FUNCTION__);

    if (errorString) *errorString = NULL;
    if (!CFPropertyListIsValid(propertyList, format)) {
        if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list invalid for format"));
        return 0;
    }
    if (format == kCFPropertyListOpenStepFormat) {
        if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list format kCFPropertyListOpenStepFormat not supported for writing"));
        return 0;
    }
    if (format == kCFPropertyListXMLFormat_v1_0) {
        CFDataRef data = CFPropertyListCreateXMLData(kCFAllocatorSystemDefault, propertyList);
	if (!data) {
	    if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list in XML format could not be created for unknown reasons."));
	    return 0;
	}
        CFIndex len = CFDataGetLength(data);
	const uint8_t *ptr = CFDataGetBytePtr(data);
	while (0 < len) {
	    CFIndex ret = CFWriteStreamWrite(stream, ptr, len);
	    if (ret == 0) {
	        if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list writing could not be completed because stream is full."));
	        return 0;
	    }
	    if (ret < 0) {
		CFErrorRef err = CFWriteStreamCopyError(stream);
		CFStringRef str = err ? CFErrorCopyDescription(err) : NULL;
		if (errorString) *errorString = str ? str : (CFStringRef)CFRetain(CFSTR("Property list writing could not be completed because the stream had an unknown error."));
		if (err) CFRelease(err);
		return 0;
	    }
	    ptr += ret;
	    len -= ret;
	}
	len = CFDataGetLength(data);
	CFRelease(data);
        return len;
    }
    if (format == kCFPropertyListBinaryFormat_v1_0) {
	CFIndex len = __CFBinaryPlistWriteToStream(propertyList, stream);
        return len;
    }
    if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Unknown format option"));
    return 0;
}

static void __CFConvertReadStreamToBytes(CFReadStreamRef stream, CFIndex max, uint8_t **buffer, CFIndex *length) {
    int32_t buflen = 0, bufsize = 0, retlen;
    uint8_t *buf = NULL, sbuf[8192];
    for (;;) {
	retlen = CFReadStreamRead(stream, sbuf, __CFMin(8192, max));
	if (retlen <= 0) {
	    *buffer = buf;
	    *length = buflen;
	    return;
	}
        if (bufsize < buflen + retlen) {
	    if (bufsize < 256 * 1024) {
		bufsize *= 4;
	    } else if (bufsize < 16 * 1024 * 1024) {
		bufsize *= 2;
	    } else {
		// once in this stage, this will be really slow
		// and really potentially fragment memory
		bufsize += 256 * 1024;
	    }
	    if (bufsize < buflen + retlen) bufsize = buflen + retlen;
	    buf = (uint8_t *)CFAllocatorReallocate(kCFAllocatorSystemDefault, buf, bufsize, 0);
	    if (!buf) HALT;
	}
	memmove(buf + buflen, sbuf, retlen);
	buflen += retlen;
        max -= retlen;
	if (max <= 0) {
	    *buffer = buf;
	    *length = buflen;
	    return;
	}
    }
}

CFPropertyListRef CFPropertyListCreateFromStream(CFAllocatorRef allocator, CFReadStreamRef stream, CFIndex length, CFOptionFlags mutabilityOption, CFPropertyListFormat *format, CFStringRef *errorString) {
    initStatics();
    CFPropertyListRef pl;
    CFDataRef data;
    CFIndex buflen = 0;
    uint8_t *buffer = NULL;
    CFAssert1(stream != NULL, __kCFLogAssertion, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__);
    CFAssert1(CFReadStreamGetTypeID() == CFGetTypeID(stream), __kCFLogAssertion, "%s(): stream argument is not a read stream", __PRETTY_FUNCTION__);
    CFAssert1(kCFStreamStatusOpen == CFReadStreamGetStatus(stream) || kCFStreamStatusReading == CFReadStreamGetStatus(stream), __kCFLogAssertion, "%s():  stream is not open", __PRETTY_FUNCTION__);
    CFAssert2(mutabilityOption == kCFPropertyListImmutable || mutabilityOption == kCFPropertyListMutableContainers || mutabilityOption == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, mutabilityOption);

    if (errorString) *errorString = NULL;
    if (0 == length) length = INT_MAX;
    __CFConvertReadStreamToBytes(stream, length, &buffer, &buflen);
    if (!buffer || buflen < 6) {
        if (buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, buffer);
        if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("stream had too few bytes"));
        return NULL;
    }
    data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, buffer, buflen, kCFAllocatorSystemDefault);
    pl = _CFPropertyListCreateFromXMLData(allocator, data, mutabilityOption, errorString, true, format);
    CFRelease(data);
    return pl;
}

// ========================================================================

//
// Old NeXT-style property lists
//

static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject);

#define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')

static void advanceToNonSpace(_CFXMLPlistParseInfo *pInfo) {
    UniChar ch2;
    while (pInfo->curr < pInfo->end) {
	ch2 = *(pInfo->curr);
        pInfo->curr ++;
        if (ch2 >= 9 && ch2 <= 0x0d) continue;	// tab, newline, vt, form feed, carriage return
        if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue;	// space and Unicode line sep, para sep
	if (ch2 == '/') {
            if (pInfo->curr >= pInfo->end) {
                // whoops; back up and return
                pInfo->curr --;
                return;
            } else if (*(pInfo->curr) == '/') {
                pInfo->curr ++;
                while (pInfo->curr < pInfo->end) {	// go to end of comment line
                    UniChar ch3 = *(pInfo->curr);
                    if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
                    pInfo->curr ++;
		}
	    } else if (*(pInfo->curr) == '*') {		// handle /* ... */
                pInfo->curr ++;
		while (pInfo->curr < pInfo->end) {
		    ch2 = *(pInfo->curr);
                    pInfo->curr ++;
		    if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
                        pInfo->curr ++; // advance past the '/'
                        break;
                    }
                }
            } else {
                pInfo->curr --;
                return;
	    }
        } else {
            pInfo->curr --;
            return;
        }
    }
}

static UniChar getSlashedChar(_CFXMLPlistParseInfo *pInfo) {
    UniChar ch = *(pInfo->curr);
    pInfo->curr ++;
    switch (ch) {
	case '0':
	case '1':	
	case '2':	
	case '3':	
	case '4':	
	case '5':	
	case '6':	
	case '7':  {
            uint8_t num = ch - '0';
            UniChar result;
            CFIndex usedCharLen;
	    /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
	    if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
                pInfo->curr ++;
		num = (num << 3) + ch - '0';
		if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
                    pInfo->curr ++;
		    num = (num << 3) + ch - '0';
		}
	    }
            CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL,  &result, 1, &usedCharLen);
            return (usedCharLen == 1) ? result : 0;
	}
	case 'U': {
	    unsigned num = 0, numDigits = 4;	/* Parse four digits */
	    while (pInfo->curr < pInfo->end && numDigits--) {
                if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) { 
                    pInfo->curr ++;
		    num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
		}
	    }
	    return num;
	}
	case 'a':	return '\a';	// Note: the meaning of '\a' varies with -traditional to gcc
	case 'b':	return '\b';
	case 'f':	return '\f';
	case 'n':	return '\n';
	case 'r':	return '\r';
	case 't':	return '\t';
	case 'v':	return '\v';
	case '"':	return '\"';
	case '\n':	return '\n';
    }
    return ch;
}

static CFStringRef parseQuotedPlistString(_CFXMLPlistParseInfo *pInfo, UniChar quote) {
    CFMutableStringRef str = NULL;
    const UniChar *startMark = pInfo->curr;
    const UniChar *mark = pInfo->curr;
    while (pInfo->curr < pInfo->end) {
	UniChar ch = *(pInfo->curr);
        if (ch == quote) break;
        if (ch == '\\') {
            _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
            pInfo->curr ++;
            ch = getSlashedChar(pInfo);
            CFStringAppendCharacters(str, &ch, 1);
            mark = pInfo->curr;
	} else {
            // Note that the original NSParser code was much more complex at this point, but it had to deal with 8-bit characters in a non-UniChar stream.  We always have UniChar (we translated the data by the system encoding at the very beginning, hopefully), so this is safe.
            pInfo->curr ++;
        }
    }
    if (pInfo->end <= pInfo->curr) {
        if (str) CFRelease(str);
	    pInfo->curr = startMark;
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated quoted string starting on line %d"), lineNumber(pInfo));
        return NULL;
    }
    if (!str) {
        if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
            _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
        } else {
            str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
            CFRetain(str);
        }
    } else {
        if (mark != pInfo->curr) {
            _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
        }
        if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
            CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
            CFRetain(uniqueString);
            CFRelease(str);
            str = (CFMutableStringRef)uniqueString;
        }
    }
    pInfo->curr ++;  // Advance past the quote character before returning.
    if (pInfo->errorString) {
	CFRelease(pInfo->errorString);
	pInfo->errorString = NULL;
    }
    return str;
}

static CFStringRef parseUnquotedPlistString(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *mark = pInfo->curr;
    while (pInfo->curr < pInfo->end) {
        UniChar ch = *pInfo->curr;
        if (isValidUnquotedStringCharacter(ch))
            pInfo->curr ++;
        else break;
    }
    if (pInfo->curr != mark) {
        if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
            CFStringRef str = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
            CFRetain(str);
            return str;
        } else {
            CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
            CFStringAppendCharacters(str, mark, pInfo->curr - mark);
            return str;
        }
    }
	pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF"));
    return NULL;
}

static CFStringRef parsePlistString(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
    UniChar ch;
    advanceToNonSpace(pInfo);
    if (pInfo->curr >= pInfo->end) {
	if (requireObject) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing string"));
	}
        return NULL;
    }
    ch = *(pInfo->curr);
    if (ch == '\'' || ch == '\"') {
        pInfo->curr ++;
        return parseQuotedPlistString(pInfo, ch);
    } else if (isValidUnquotedStringCharacter(ch)) {
        return parseUnquotedPlistString(pInfo);
    } else {
	if (requireObject) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Invalid string character at line %d"), lineNumber(pInfo));
	}
        return NULL;
    }
}

static CFTypeRef parsePlistArray(_CFXMLPlistParseInfo *pInfo) {
    CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
    CFTypeRef tmp = parsePlistObject(pInfo, false);
    while (tmp) {
        CFArrayAppendValue(array, tmp);
        CFRelease(tmp);
        advanceToNonSpace(pInfo);
        if (*pInfo->curr != ',') {
            tmp = NULL;
        } else {
            pInfo->curr ++;
            tmp = parsePlistObject(pInfo, false);
        }
    }
    advanceToNonSpace(pInfo);
    if (*pInfo->curr != ')') {
        CFRelease(array);
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating ')' for array at line %d"), lineNumber(pInfo));
        return NULL;
    }
    if (pInfo->errorString) {
	CFRelease(pInfo->errorString);
	pInfo->errorString = NULL;
    }
    pInfo->curr ++;
    return array;
}

static CFDictionaryRef parsePlistDictContent(_CFXMLPlistParseInfo *pInfo) {
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFStringRef key = NULL;
    Boolean failedParse = false;
    key = parsePlistString(pInfo, false);
    while (key) {
        CFTypeRef value;
        advanceToNonSpace(pInfo);
	if (*pInfo->curr == ';') {
	    /* This is a strings file using the shortcut format */
	    /* although this check here really applies to all plists. */
	    value = CFRetain(key);
	} else if (*pInfo->curr == '=') {
	    pInfo->curr ++;
	    value = parsePlistObject(pInfo, true);
	    if (!value) {
		failedParse = true;
		break;
	    }
	} else {
		pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumber(pInfo));
	    failedParse = true;
	    break;
	}
	CFDictionarySetValue(dict, key, value);
	CFRelease(key);
	key = NULL;
	CFRelease(value);
	value = NULL;
	advanceToNonSpace(pInfo);
	if (*pInfo->curr == ';') {
	    pInfo->curr ++;
	    key = parsePlistString(pInfo, false);
	} else if (!allowMissingSemi && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
		CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary."));
	    failedParse = true;
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Missing ';' on line %d"), lineNumber(pInfo));
	} else {
	    // on pre-Jaguar systems, do nothing except silently ignore the rest
	    // of the dictionary, which is what happened on those systems.
	}
    }

    if (failedParse) {
        if (key) CFRelease(key);
        CFRelease(dict);
        return NULL;
    }
    if (pInfo->errorString) {
	CFRelease(pInfo->errorString);
	pInfo->errorString = NULL;
    }
    return dict;
}

static CFTypeRef parsePlistDict(_CFXMLPlistParseInfo *pInfo) {
    CFDictionaryRef dict = parsePlistDictContent(pInfo);
    if (!dict) return NULL;
    advanceToNonSpace(pInfo);
    if (*pInfo->curr != '}') {
        CFRelease(dict);
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumber(pInfo));
        return NULL;
    }
    pInfo->curr ++;
    return dict;
}

CF_INLINE unsigned char fromHexDigit(unsigned char ch) {
    if (isdigit(ch)) return ch - '0';
    if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10;
    if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10;
    return 0xff; // Just choose a large number for the error code
}

/* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
   -1 is returned for unexpected char, -2 for uneven number of hex digits
*/
static int getDataBytes(_CFXMLPlistParseInfo *pInfo, unsigned char *bytes, int bytesSize) {
    int numBytesRead = 0;
    while ((pInfo->curr < pInfo->end) && (numBytesRead < bytesSize)) {
	int first, second;
	UniChar ch1 = *pInfo->curr;
	if (ch1 == '>') return numBytesRead;  // Meaning we're done
	first = fromHexDigit((unsigned char)ch1);
	if (first != 0xff) {	// If the first char is a hex, then try to read a second hex
	    pInfo->curr++;
	    if (pInfo->curr >= pInfo->end) return -2;   // Error: uneven number of hex digits
	    UniChar ch2 = *pInfo->curr;
	    second = fromHexDigit((unsigned char)ch2);
	    if (second == 0xff) return -2;  // Error: uneven number of hex digits
	    bytes[numBytesRead++] = (first << 4) + second;
	    pInfo->curr++;
	} else if (ch1 == ' ' || ch1 == '\n' || ch1 == '\t' || ch1 == '\r' || ch1 == 0x2028 || ch1 == 0x2029) {
	    pInfo->curr++;
	} else {
	    return -1;  // Error: unexpected character
	}
    }
    return numBytesRead;    // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
}

#define numBytes 400
static CFTypeRef parsePlistData(_CFXMLPlistParseInfo *pInfo) {
    CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);

    // Read hex bytes and append them to result
    while (1) {
	unsigned char bytes[numBytes];
	int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
	if (numBytesRead < 0) {
	    CFRelease(result);
		switch (numBytesRead) {
		    case -2: pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumber(pInfo)); break;
		    default: pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumber(pInfo)); break;
		}
	    return NULL;
	}
	if (numBytesRead == 0) break;
	CFDataAppendBytes(result, bytes, numBytesRead);
    }

    if (pInfo->errorString) {
	CFRelease(pInfo->errorString);
	pInfo->errorString = NULL;
    }

    if (*(pInfo->curr) == '>') {
        pInfo->curr ++; // Move past '>'
        return result;
    } else {
        CFRelease(result);
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '>' for data at line %d"), lineNumber(pInfo));
        return NULL;
    }
}
#undef numBytes

// Returned object is retained; caller must free.
static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
    UniChar ch;
    advanceToNonSpace(pInfo);
    if (pInfo->curr + 1 >= pInfo->end) {
	if (requireObject) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing plist"));
	}
        return NULL;
    }
    ch = *(pInfo->curr);
    pInfo->curr ++;
    if (ch == '{') {
        return parsePlistDict(pInfo);
    } else if (ch == '(') {
        return parsePlistArray(pInfo);
    } else if (ch == '<') {
        return parsePlistData(pInfo);
    } else if (ch == '\'' || ch == '\"') {
        return parseQuotedPlistString(pInfo, ch);
    } else if (isValidUnquotedStringCharacter(ch)) {
        pInfo->curr --;
        return parseUnquotedPlistString(pInfo);
    } else {
        pInfo->curr --;  // Must back off the charcter we just read
	if (requireObject) {
	    pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumber(pInfo));
	}
        return NULL;
    }
}

static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo) {
    const UniChar *begin = pInfo->curr;
    CFTypeRef result;
    advanceToNonSpace(pInfo);
    // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
    if (pInfo->curr >= pInfo->end) return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    result = parsePlistObject(pInfo, true);
    advanceToNonSpace(pInfo);
    if (pInfo->curr >= pInfo->end) return result;
    if (!result) return NULL;
    if (CFGetTypeID(result) != stringtype) {
        CFRelease(result);
	pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Junk after plist at line %d"), lineNumber(pInfo));
        return NULL;
    }
    CFRelease(result);
    // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
    pInfo->curr = begin;
    return parsePlistDictContent(pInfo);
}

#undef isValidUnquotedStringCharacter

static CFArrayRef _arrayDeepImmutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
    CFArrayRef result = NULL;
    CFIndex i, c = CFArrayGetCount(array);
    CFTypeRef *values;
    if (c == 0) {
        result = CFArrayCreate(allocator, NULL, 0, &kCFTypeArrayCallBacks);
    } else if ((values = (CFTypeRef *)CFAllocatorAllocate(allocator, c*sizeof(CFTypeRef), 0)) != NULL) {
        CFArrayGetValues(array, CFRangeMake(0, c), values);
        for (i = 0; i < c; i ++) {
            values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
            if (values[i] == NULL) {
                break;
            }
        }
        result = (i == c) ? CFArrayCreate(allocator, values, c, &kCFTypeArrayCallBacks) : NULL;
        c = i;
        for (i = 0; i < c; i ++) {
            CFRelease(values[i]);
        }
        CFAllocatorDeallocate(allocator, values);
    }
    return result;
}

static CFMutableArrayRef _arrayDeepMutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
    CFIndex i, c = CFArrayGetCount(array);
    CFMutableArrayRef result = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);
    if (result) {
        for (i = 0; i < c; i ++) {
            CFTypeRef newValue = CFPropertyListCreateDeepCopy(allocator, CFArrayGetValueAtIndex(array, i), mutabilityOption);
            if (!newValue) break;
            CFArrayAppendValue(result, newValue);
            CFRelease(newValue);
        }
        if (i != c) {
            CFRelease(result);
            result = NULL;
        }
    }
    return result;
}

CFPropertyListRef CFPropertyListCreateDeepCopy(CFAllocatorRef allocator, CFPropertyListRef propertyList, CFOptionFlags mutabilityOption) {
    initStatics();
    CFTypeID typeID;
    CFPropertyListRef result = NULL;
    CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): cannot copy a NULL property list", __PRETTY_FUNCTION__);
    __CFAssertIsPList(propertyList);
    CFAssert2(mutabilityOption == kCFPropertyListImmutable || mutabilityOption == kCFPropertyListMutableContainers || mutabilityOption == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, mutabilityOption);
	if (!CFPropertyListIsValid(propertyList, kCFPropertyListBinaryFormat_v1_0)) return NULL;
    
    if (allocator == NULL) {
        allocator = (CFAllocatorRef)CFRetain(__CFGetDefaultAllocator());
    } else {
        CFRetain(allocator);
    }
    
    typeID = CFGetTypeID(propertyList);
    if (typeID == dicttype) {
        CFDictionaryRef dict = (CFDictionaryRef)propertyList;
        Boolean isMutable = (mutabilityOption != kCFPropertyListImmutable);
        CFIndex count = CFDictionaryGetCount(dict);
        CFTypeRef *keys, *values;
        if (count == 0) {
            result = isMutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks): CFDictionaryCreate(allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        } else if ((keys = (CFTypeRef *)CFAllocatorAllocate(allocator, 2 * count * sizeof(CFTypeRef), 0)) != NULL)   {
            CFIndex i;
            values = keys+count;
            CFDictionaryGetKeysAndValues(dict, keys, values);
            for (i = 0; i < count; i ++) {
                keys[i] = CFStringCreateCopy(allocator, (CFStringRef)keys[i]);
                if (keys[i] == NULL) {
                    break;
                }
                values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
                if (values[i] == NULL) {
                    CFRelease(keys[i]);
                    break;
                }
            }
            if (i == count) {
                result = isMutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) : CFDictionaryCreate(allocator, keys, values, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                for (i = 0; i < count; i ++) {
                    if (isMutable) {
                        CFDictionarySetValue((CFMutableDictionaryRef)result, keys[i], values[i]);
                    }
                    CFRelease(keys[i]);
                    CFRelease(values[i]);
                }
            } else {
                result = NULL;
                count = i;
                for (i = 0; i < count; i ++) {
                    CFRelease(keys[i]);
                    CFRelease(values[i]);
                }
            }
            CFAllocatorDeallocate(allocator, keys);
        } else {
            result = NULL;
        }
    } else if (typeID == arraytype) {
        if (mutabilityOption == kCFPropertyListImmutable) {
            result = _arrayDeepImmutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
        } else {
            result = _arrayDeepMutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
        }
    } else if (typeID == datatype) {
        if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
            result = CFDataCreateMutableCopy(allocator, 0, (CFDataRef)propertyList);
        } else {
            result = CFDataCreateCopy(allocator, (CFDataRef)propertyList);
        }
    } else if (typeID == numbertype) {
        // Warning - this will break if byteSize is ever greater than 32
        uint8_t bytes[32];
        CFNumberType numType = CFNumberGetType((CFNumberRef)propertyList);
        CFNumberGetValue((CFNumberRef)propertyList, numType, (void *)bytes);
        result = CFNumberCreate(allocator, numType, (void *)bytes);
    } else if (typeID == booltype) {
        // Booleans are immutable & shared instances
        CFRetain(propertyList);
        result = propertyList;
    } else if (typeID == datetype) {
        // Dates are immutable
        result = CFDateCreate(allocator, CFDateGetAbsoluteTime((CFDateRef)propertyList));
    } else if (typeID == stringtype) {
        if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
            result = CFStringCreateMutableCopy(allocator, 0, (CFStringRef)propertyList);
        } else {
            result = CFStringCreateCopy(allocator, (CFStringRef)propertyList);
        }
    } else {
        CFAssert2(false, __kCFLogAssertion, "%s(): %p is not a property list type", __PRETTY_FUNCTION__, propertyList);
        result = NULL;
    }
    CFRelease(allocator);
    return result;
}