/*
 * Copyright (c) 2005 Apple Computer, 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@
 */
/*	CFXMLTree.c
	Copyright 1999-2002, Apple, Inc. All rights reserved.
	Responsibility: Chris Parker
*/

#include "CFInternal.h"
#include <CoreFoundation/CFXMLParser.h>

/*************/
/* CFXMLTree */
/*************/

/* Creates a childless node from desc */
CFXMLTreeRef CFXMLTreeCreateWithNode(CFAllocatorRef allocator, CFXMLNodeRef node) {
    CFTreeContext treeCtxt;
    treeCtxt.version = 0;
    treeCtxt.info = (void *)node;
    treeCtxt.retain = CFRetain;
    treeCtxt.release = CFRelease;
    treeCtxt.copyDescription = CFCopyDescription;
    return CFTreeCreate(allocator, &treeCtxt);
}

CFXMLNodeRef CFXMLTreeGetNode(CFXMLTreeRef xmlNode) {
    CFTreeContext treeContext;
    treeContext.version = 0;
    CFTreeGetContext(xmlNode, &treeContext);
    return (CFXMLNodeRef)treeContext.info;
}

// We will probably ultimately want to export this under some public API name
__private_extern__ Boolean CFXMLTreeEqual(CFXMLTreeRef xmlTree1, CFXMLTreeRef xmlTree2) {
    CFXMLNodeRef node1, node2;
    CFXMLTreeRef child1, child2;
    if (CFTreeGetChildCount(xmlTree1) != CFTreeGetChildCount(xmlTree2)) return false;
    node1 = CFXMLTreeGetNode(xmlTree1);
    node2 = CFXMLTreeGetNode(xmlTree2);
    if (!CFEqual(node1, node2)) return false;
    for (child1 = CFTreeGetFirstChild(xmlTree1), child2 = CFTreeGetFirstChild(xmlTree2); child1 && child2; child1 = CFTreeGetNextSibling(child1), child2 = CFTreeGetNextSibling(child2)) {
        if (!CFXMLTreeEqual(child1, child2)) return false;
    }
    return true;
}

static void _CFAppendXML(CFMutableStringRef str, CFXMLTreeRef tree);
static void _CFAppendXMLProlog(CFMutableStringRef str, const CFXMLTreeRef node);
static void _CFAppendXMLEpilog(CFMutableStringRef str, const CFXMLTreeRef node);

CFDataRef CFXMLTreeCreateXMLData(CFAllocatorRef allocator, CFXMLTreeRef xmlTree) {
    CFMutableStringRef xmlStr; 
    CFDataRef result;
    CFStringEncoding encoding;

    __CFGenericValidateType(xmlTree, CFTreeGetTypeID());
    
    xmlStr = CFStringCreateMutable(allocator, 0);
    _CFAppendXML(xmlStr, xmlTree);
    if (CFXMLNodeGetTypeCode(CFXMLTreeGetNode(xmlTree)) == kCFXMLNodeTypeDocument) {
        const CFXMLDocumentInfo *docData = CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(xmlTree));
        encoding = docData ? docData->encoding : kCFStringEncodingUTF8;
    } else {
        encoding = kCFStringEncodingUTF8;
    }
    result = CFStringCreateExternalRepresentation(allocator, xmlStr, encoding, 0);
    CFRelease(xmlStr);
    return result;
}

static void _CFAppendXML(CFMutableStringRef str, CFXMLTreeRef tree) {
    CFXMLTreeRef child;
    _CFAppendXMLProlog(str, tree);
    for (child = CFTreeGetFirstChild(tree); child; child = CFTreeGetNextSibling(child)) {
        _CFAppendXML(str, child);
    }
    _CFAppendXMLEpilog(str, tree);
}

__private_extern__ void appendQuotedString(CFMutableStringRef str, CFStringRef strToQuote) {
    char quoteChar = CFStringFindWithOptions(strToQuote, CFSTR("\""), CFRangeMake(0, CFStringGetLength(strToQuote)), 0, NULL) ? '\'' : '\"';
    CFStringAppendFormat(str, NULL, CFSTR("%c%@%c"), quoteChar, strToQuote, quoteChar);
}

static void appendExternalID(CFMutableStringRef str, CFXMLExternalID *extID) {
    if (extID->publicID) {
        CFStringAppendCString(str, " PUBLIC ", kCFStringEncodingASCII);
        appendQuotedString(str, extID->publicID);
        if (extID->systemID) {
            // Technically, for externalIDs, systemID must not be NULL.  However, by testing for a NULL systemID, we can use this to emit publicIDs, too.  REW, 2/15/2000
            CFStringAppendCString(str, " ", kCFStringEncodingASCII);
            appendQuotedString(str, CFURLGetString(extID->systemID));
        }
    } else if (extID->systemID) {
        CFStringAppendCString(str, " SYSTEM ", kCFStringEncodingASCII);
        appendQuotedString(str, CFURLGetString(extID->systemID));
    } else {
        // Should never get here
    }
}

static void appendElementProlog(CFMutableStringRef str, CFXMLTreeRef tree) {
    const CFXMLElementInfo *data = CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
    CFStringAppendFormat(str, NULL, CFSTR("<%@"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
    if (data->attributeOrder) {
        CFIndex i, c = CFArrayGetCount(data->attributeOrder);
        for (i = 0; i < c; i ++) {
            CFStringRef attr = CFArrayGetValueAtIndex(data->attributeOrder, i);
            CFStringRef value = CFDictionaryGetValue(data->attributes, attr);
            CFStringAppendFormat(str, NULL, CFSTR(" %@="), attr);
            appendQuotedString(str, value);
        }
    }
    if (data->isEmpty) {
        CFStringAppendCString(str, "/>", kCFStringEncodingASCII);
    } else {
        CFStringAppendCString(str, ">", kCFStringEncodingASCII);
    }
}

/* Although named "prolog", for leafs of the tree, this is the only XML generation function called.  This is why Comments, Processing Instructions, etc. generate their XML during this function.  REW, 2/11/2000 */
static void _CFAppendXMLProlog(CFMutableStringRef str, const CFXMLTreeRef tree) {
    switch (CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree))) {
        case kCFXMLNodeTypeDocument:
            break;
        case kCFXMLNodeTypeElement:
            appendElementProlog(str, tree);
            break;
        case kCFXMLNodeTypeAttribute:
            // Should never be encountered
            break;
        case kCFXMLNodeTypeProcessingInstruction: {
            CFXMLProcessingInstructionInfo *data = (CFXMLProcessingInstructionInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
            if (data->dataString) {
                CFStringAppendFormat(str, NULL, CFSTR("<?%@ %@?>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)), data->dataString);
            } else {
                CFStringAppendFormat(str, NULL, CFSTR("<?%@?>"));
            }
            break;
        }
        case kCFXMLNodeTypeComment:
            CFStringAppendFormat(str, NULL, CFSTR("<!--%@-->"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            break;
        case kCFXMLNodeTypeText:
            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            break;
        case kCFXMLNodeTypeCDATASection:
            CFStringAppendFormat(str, NULL, CFSTR("<![CDATA[%@]]>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            break;
        case kCFXMLNodeTypeDocumentFragment:
            break;
        case kCFXMLNodeTypeEntity: {
            CFXMLEntityInfo *data = (CFXMLEntityInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
            CFStringAppendCString(str, "<!ENTITY ", kCFStringEncodingASCII);
            if (data->entityType == kCFXMLEntityTypeParameter) {
                CFStringAppend(str, CFSTR("% "));
            }
            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            CFStringAppend(str, CFSTR(" "));
            if (data->replacementText) {
                appendQuotedString(str, data->replacementText);
                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
            } else {
                appendExternalID(str, &(data->entityID));
                if (data->notationName) {
                    CFStringAppendFormat(str, NULL, CFSTR(" NDATA %@"), data->notationName);
                }
                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
            }
                break;
        }
        case kCFXMLNodeTypeEntityReference:
        {
            CFXMLEntityTypeCode entityType = ((CFXMLEntityReferenceInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->entityType;
            if (entityType == kCFXMLEntityTypeParameter) {
                CFStringAppendFormat(str, NULL, CFSTR("%%%@;"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            } else {
                CFStringAppendFormat(str, NULL, CFSTR("&%@;"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            }
            break;
        }
        case kCFXMLNodeTypeDocumentType:
            CFStringAppendCString(str, "<!DOCTYPE ", kCFStringEncodingASCII);
            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            if (CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree))) {
                CFXMLExternalID *extID = &((CFXMLDocumentTypeInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->externalID;
                appendExternalID(str, extID);
            }
                CFStringAppendCString(str, " [", kCFStringEncodingASCII);
            break;
        case kCFXMLNodeTypeWhitespace:
            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            break;
        case kCFXMLNodeTypeNotation: {
            CFXMLNotationInfo *data = (CFXMLNotationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
            CFStringAppendFormat(str, NULL, CFSTR("<!NOTATION %@ "), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            appendExternalID(str, &(data->externalID));
                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
            break;
        }
        case kCFXMLNodeTypeElementTypeDeclaration:
            CFStringAppendFormat(str, NULL, CFSTR("<!ELEMENT %@ %@>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)), ((CFXMLElementTypeDeclarationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->contentDescription);
            break;
        case kCFXMLNodeTypeAttributeListDeclaration: {
            CFXMLAttributeListDeclarationInfo *attListData = (CFXMLAttributeListDeclarationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
            CFIndex idx;
            CFStringAppendCString(str, "<!ATTLIST ", kCFStringEncodingASCII);
            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
            for (idx = 0; idx < attListData->numberOfAttributes; idx ++) {
                CFXMLAttributeDeclarationInfo *attr = &(attListData->attributes[idx]);
                CFStringAppendFormat(str, NULL, CFSTR("\n\t%@ %@ %@"), attr->attributeName, attr->typeString, attr->defaultString);
            }
            CFStringAppendCString(str, ">", kCFStringEncodingASCII);
            break;
        }
        default:
            CFAssert1(false, __kCFLogAssertion, "Encountered unexpected XMLDataTypeID %d", CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree)));
    }
}

static void _CFAppendXMLEpilog(CFMutableStringRef str, CFXMLTreeRef tree) {
    CFXMLNodeTypeCode typeID = CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree));
    if (typeID == kCFXMLNodeTypeElement) {
        if (((CFXMLElementInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->isEmpty) return;
        CFStringAppendFormat(str, NULL, CFSTR("</%@>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
    } else if (typeID == kCFXMLNodeTypeDocumentType) {
        CFIndex len = CFStringGetLength(str);
        if (CFStringHasSuffix(str, CFSTR(" ["))) {
            // There were no in-line DTD elements
            CFStringDelete(str, CFRangeMake(len-2, 2));
        } else {
            CFStringAppendCString(str, "]", kCFStringEncodingASCII);
        }
        CFStringAppendCString(str, ">", kCFStringEncodingASCII);
    }
}
