1/*
2 * Copyright (c) 2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*	CFXMLTree.c
25	Copyright (c) 1999-2013, Apple Inc. All rights reserved.
26	Responsibility: David Smith
27*/
28
29#include "CFInternal.h"
30#include <CoreFoundation/CFXMLParser.h>
31
32#pragma GCC diagnostic push
33#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
34
35/*************/
36/* CFXMLTree */
37/*************/
38
39/* Creates a childless node from desc */
40CFXMLTreeRef CFXMLTreeCreateWithNode(CFAllocatorRef allocator, CFXMLNodeRef node) {
41    CFTreeContext treeCtxt;
42    treeCtxt.version = 0;
43    treeCtxt.info = (void *)node;
44    treeCtxt.retain = CFRetain;
45    treeCtxt.release = CFRelease;
46    treeCtxt.copyDescription = CFCopyDescription;
47    return CFTreeCreate(allocator, &treeCtxt);
48}
49
50CFXMLNodeRef CFXMLTreeGetNode(CFXMLTreeRef xmlNode) {
51    CFTreeContext treeContext;
52    treeContext.version = 0;
53    CFTreeGetContext(xmlNode, &treeContext);
54    return (CFXMLNodeRef)treeContext.info;
55}
56
57// We will probably ultimately want to export this under some public API name
58CF_PRIVATE Boolean CFXMLTreeEqual(CFXMLTreeRef xmlTree1, CFXMLTreeRef xmlTree2) {
59    CFXMLNodeRef node1, node2;
60    CFXMLTreeRef child1, child2;
61    if (CFTreeGetChildCount(xmlTree1) != CFTreeGetChildCount(xmlTree2)) return false;
62    node1 = CFXMLTreeGetNode(xmlTree1);
63    node2 = CFXMLTreeGetNode(xmlTree2);
64    if (!CFEqual(node1, node2)) return false;
65    for (child1 = CFTreeGetFirstChild(xmlTree1), child2 = CFTreeGetFirstChild(xmlTree2); child1 && child2; child1 = CFTreeGetNextSibling(child1), child2 = CFTreeGetNextSibling(child2)) {
66        if (!CFXMLTreeEqual(child1, child2)) return false;
67    }
68    return true;
69}
70
71static void _CFAppendXML(CFMutableStringRef str, CFXMLTreeRef tree);
72static void _CFAppendXMLProlog(CFMutableStringRef str, const CFXMLTreeRef node);
73static void _CFAppendXMLEpilog(CFMutableStringRef str, const CFXMLTreeRef node);
74
75CFDataRef CFXMLTreeCreateXMLData(CFAllocatorRef allocator, CFXMLTreeRef xmlTree) {
76    CFMutableStringRef xmlStr;
77    CFDataRef result;
78    CFStringEncoding encoding;
79
80    __CFGenericValidateType(xmlTree, CFTreeGetTypeID());
81
82    xmlStr = CFStringCreateMutable(allocator, 0);
83    _CFAppendXML(xmlStr, xmlTree);
84    if (CFXMLNodeGetTypeCode(CFXMLTreeGetNode(xmlTree)) == kCFXMLNodeTypeDocument) {
85        const CFXMLDocumentInfo *docData = (CFXMLDocumentInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(xmlTree));
86        encoding = docData ? docData->encoding : kCFStringEncodingUTF8;
87    } else {
88        encoding = kCFStringEncodingUTF8;
89    }
90    result = CFStringCreateExternalRepresentation(allocator, xmlStr, encoding, 0);
91    CFRelease(xmlStr);
92    return result;
93}
94
95static void _CFAppendXML(CFMutableStringRef str, CFXMLTreeRef tree) {
96    CFXMLTreeRef child;
97    _CFAppendXMLProlog(str, tree);
98    for (child = CFTreeGetFirstChild(tree); child; child = CFTreeGetNextSibling(child)) {
99        _CFAppendXML(str, child);
100    }
101    _CFAppendXMLEpilog(str, tree);
102}
103
104CF_PRIVATE void appendQuotedString(CFMutableStringRef str, CFStringRef strToQuote) {
105    char quoteChar = CFStringFindWithOptions(strToQuote, CFSTR("\""), CFRangeMake(0, CFStringGetLength(strToQuote)), 0, NULL) ? '\'' : '\"';
106    CFStringAppendFormat(str, NULL, CFSTR("%c%@%c"), quoteChar, strToQuote, quoteChar);
107}
108
109static void appendExternalID(CFMutableStringRef str, CFXMLExternalID *extID) {
110    if (extID->publicID) {
111        CFStringAppendCString(str, " PUBLIC ", kCFStringEncodingASCII);
112        appendQuotedString(str, extID->publicID);
113        if (extID->systemID) {
114            // 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
115            CFStringAppendCString(str, " ", kCFStringEncodingASCII);
116            appendQuotedString(str, CFURLGetString(extID->systemID));
117        }
118    } else if (extID->systemID) {
119        CFStringAppendCString(str, " SYSTEM ", kCFStringEncodingASCII);
120        appendQuotedString(str, CFURLGetString(extID->systemID));
121    } else {
122        // Should never get here
123    }
124}
125
126static void appendElementProlog(CFMutableStringRef str, CFXMLTreeRef tree) {
127    const CFXMLElementInfo *data = (CFXMLElementInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
128    CFStringAppendFormat(str, NULL, CFSTR("<%@"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
129    if (data->attributeOrder) {
130        CFIndex i, c = CFArrayGetCount(data->attributeOrder);
131        for (i = 0; i < c; i ++) {
132            CFStringRef attr = (CFStringRef)CFArrayGetValueAtIndex(data->attributeOrder, i);
133            CFStringRef value = (CFStringRef)CFDictionaryGetValue(data->attributes, attr);
134            CFStringAppendFormat(str, NULL, CFSTR(" %@="), attr);
135            appendQuotedString(str, value);
136        }
137    }
138    if (data->isEmpty) {
139        CFStringAppendCString(str, "/>", kCFStringEncodingASCII);
140    } else {
141        CFStringAppendCString(str, ">", kCFStringEncodingASCII);
142    }
143}
144
145/* 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 */
146static void _CFAppendXMLProlog(CFMutableStringRef str, const CFXMLTreeRef tree) {
147    switch (CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree))) {
148        case kCFXMLNodeTypeDocument:
149            break;
150        case kCFXMLNodeTypeElement:
151            appendElementProlog(str, tree);
152            break;
153        case kCFXMLNodeTypeAttribute:
154            // Should never be encountered
155            break;
156        case kCFXMLNodeTypeProcessingInstruction: {
157            CFXMLProcessingInstructionInfo *data = (CFXMLProcessingInstructionInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
158            if (data->dataString) {
159                CFStringAppendFormat(str, NULL, CFSTR("<?%@ %@?>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)), data->dataString);
160            } else {
161                CFStringAppendFormat(str, NULL, CFSTR("<?%@?>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
162            }
163            break;
164        }
165        case kCFXMLNodeTypeComment:
166            CFStringAppendFormat(str, NULL, CFSTR("<!--%@-->"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
167            break;
168        case kCFXMLNodeTypeText:
169            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
170            break;
171        case kCFXMLNodeTypeCDATASection:
172            CFStringAppendFormat(str, NULL, CFSTR("<![CDATA[%@]]>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
173            break;
174        case kCFXMLNodeTypeDocumentFragment:
175            break;
176        case kCFXMLNodeTypeEntity: {
177            CFXMLEntityInfo *data = (CFXMLEntityInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
178            CFStringAppendCString(str, "<!ENTITY ", kCFStringEncodingASCII);
179            if (data->entityType == kCFXMLEntityTypeParameter) {
180                CFStringAppend(str, CFSTR("% "));
181            }
182            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
183            CFStringAppend(str, CFSTR(" "));
184            if (data->replacementText) {
185                appendQuotedString(str, data->replacementText);
186                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
187            } else {
188                appendExternalID(str, &(data->entityID));
189                if (data->notationName) {
190                    CFStringAppendFormat(str, NULL, CFSTR(" NDATA %@"), data->notationName);
191                }
192                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
193            }
194                break;
195        }
196        case kCFXMLNodeTypeEntityReference:
197        {
198            CFXMLEntityTypeCode entityType = ((CFXMLEntityReferenceInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->entityType;
199            if (entityType == kCFXMLEntityTypeParameter) {
200                CFStringAppendFormat(str, NULL, CFSTR("%%%@;"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
201            } else {
202                CFStringAppendFormat(str, NULL, CFSTR("&%@;"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
203            }
204            break;
205        }
206        case kCFXMLNodeTypeDocumentType:
207            CFStringAppendCString(str, "<!DOCTYPE ", kCFStringEncodingASCII);
208            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
209            if (CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree))) {
210                CFXMLExternalID *extID = &((CFXMLDocumentTypeInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->externalID;
211                appendExternalID(str, extID);
212            }
213                CFStringAppendCString(str, " [", kCFStringEncodingASCII);
214            break;
215        case kCFXMLNodeTypeWhitespace:
216            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
217            break;
218        case kCFXMLNodeTypeNotation: {
219            CFXMLNotationInfo *data = (CFXMLNotationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
220            CFStringAppendFormat(str, NULL, CFSTR("<!NOTATION %@ "), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
221            appendExternalID(str, &(data->externalID));
222                CFStringAppendCString(str, ">", kCFStringEncodingASCII);
223            break;
224        }
225        case kCFXMLNodeTypeElementTypeDeclaration:
226            CFStringAppendFormat(str, NULL, CFSTR("<!ELEMENT %@ %@>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)), ((CFXMLElementTypeDeclarationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->contentDescription);
227            break;
228        case kCFXMLNodeTypeAttributeListDeclaration: {
229            CFXMLAttributeListDeclarationInfo *attListData = (CFXMLAttributeListDeclarationInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree));
230            CFIndex idx;
231            CFStringAppendCString(str, "<!ATTLIST ", kCFStringEncodingASCII);
232            CFStringAppend(str, CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
233            for (idx = 0; idx < attListData->numberOfAttributes; idx ++) {
234                CFXMLAttributeDeclarationInfo *attr = &(attListData->attributes[idx]);
235                CFStringAppendFormat(str, NULL, CFSTR("\n\t%@ %@ %@"), attr->attributeName, attr->typeString, attr->defaultString);
236            }
237            CFStringAppendCString(str, ">", kCFStringEncodingASCII);
238            break;
239        }
240        default:
241            CFAssert1(false, __kCFLogAssertion, "Encountered unexpected XMLDataTypeID %d", CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree)));
242    }
243}
244
245static void _CFAppendXMLEpilog(CFMutableStringRef str, CFXMLTreeRef tree) {
246    CFXMLNodeTypeCode typeID = CFXMLNodeGetTypeCode(CFXMLTreeGetNode(tree));
247    if (typeID == kCFXMLNodeTypeElement) {
248        if (((CFXMLElementInfo *)CFXMLNodeGetInfoPtr(CFXMLTreeGetNode(tree)))->isEmpty) return;
249        CFStringAppendFormat(str, NULL, CFSTR("</%@>"), CFXMLNodeGetString(CFXMLTreeGetNode(tree)));
250    } else if (typeID == kCFXMLNodeTypeDocumentType) {
251        CFIndex len = CFStringGetLength(str);
252        if (CFStringHasSuffix(str, CFSTR(" ["))) {
253            // There were no in-line DTD elements
254            CFStringDelete(str, CFRangeMake(len-2, 2));
255        } else {
256            CFStringAppendCString(str, "]", kCFStringEncodingASCII);
257        }
258        CFStringAppendCString(str, ">", kCFStringEncodingASCII);
259    }
260}
261
262#pragma GCC diagnostic pop
263