CXMLDocument.m 8.77 KB
Newer Older
Pierre's avatar
Pierre committed
1 2 3 4 5
//
//  CXMLDocument.m
//  TouchCode
//
//  Created by Jonathan Wight on 03/07/08.
6
//  Copyright 2011 toxicsoftware.com. All rights reserved.
Pierre's avatar
Pierre committed
7
//
8 9
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
Pierre's avatar
Pierre committed
10
//
11 12
//     1. Redistributions of source code must retain the above copyright notice, this list of
//        conditions and the following disclaimer.
Pierre's avatar
Pierre committed
13
//
14 15 16
//     2. Redistributions in binary form must reproduce the above copyright notice, this list
//        of conditions and the following disclaimer in the documentation and/or other materials
//        provided with the distribution.
Pierre's avatar
Pierre committed
17
//
18 19 20 21 22 23 24 25 26 27 28 29 30
//  THIS SOFTWARE IS PROVIDED BY TOXICSOFTWARE.COM ``AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOXICSOFTWARE.COM OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of toxicsoftware.com.
Pierre's avatar
Pierre committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

#import "CXMLDocument.h"

#include <libxml/parser.h>

#import "CXMLNode_PrivateExtensions.h"
#import "CXMLElement.h"

#if TOUCHXMLUSETIDY
#import "CTidy.h"
#endif /* TOUCHXMLUSETIDY */

@implementation CXMLDocument

- (id)initWithXMLString:(NSString *)inString options:(NSUInteger)inOptions error:(NSError **)outError
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    {
    #pragma unused (inOptions)

    if ((self = [super init]) != NULL)
        {
        NSError *theError = NULL;

        #if TOUCHXMLUSETIDY
        if (inOptions & CXMLDocumentTidyHTML)
            {
            inString = [[CTidy tidy] tidyString:inString inputFormat:TidyFormat_HTML outputFormat:TidyFormat_XHTML diagnostics:NULL error:&theError];
            }
        else if (inOptions & CXMLDocumentTidyXML)
            {
            inString = [[CTidy tidy] tidyString:inString inputFormat:TidyFormat_XML outputFormat:TidyFormat_XML diagnostics:NULL error:&theError];
            }
        #endif

        xmlDocPtr theDoc = xmlParseDoc((xmlChar *)[inString UTF8String]);
        if (theDoc != NULL)
            {
            _node = (xmlNodePtr)theDoc;
            NSAssert(_node->_private == NULL, @"TODO");
69
            _node->_private = (__bridge void *)(self); // Note. NOT retained (TODO think more about _private usage)
70 71 72 73 74
            }
        else
            {
            xmlErrorPtr	theLastErrorPtr = xmlGetLastError();

75
                NSString* message = @(theLastErrorPtr ? theLastErrorPtr->message : "Unknown error");
76

77
            NSDictionary *theUserInfo = @{NSLocalizedDescriptionKey: message};
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
            theError = [NSError errorWithDomain:@"CXMLErrorDomain" code:1 userInfo:theUserInfo];

            xmlResetLastError();
            }

        if (outError)
            *outError = theError;

        if (theError != NULL)
            {
            self = NULL;
            }
        }
    return(self);
    }
Pierre's avatar
Pierre committed
93 94

- (id)initWithData:(NSData *)inData options:(NSUInteger)inOptions error:(NSError **)outError
95
    {
Pierre's avatar
Pierre committed
96
	return [self initWithData:inData encoding:NSUTF8StringEncoding options:inOptions error:outError];
97
    }
Pierre's avatar
Pierre committed
98 99

- (id)initWithData:(NSData *)inData encoding:(NSStringEncoding)encoding options:(NSUInteger)inOptions error:(NSError **)outError
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    {
    #pragma unused (inOptions)
    if ((self = [super init]) != NULL)
        {
        NSError *theError = NULL;

        #if TOUCHXMLUSETIDY
        if (inOptions & CXMLDocumentTidyHTML)
            {
            inData = [[CTidy tidy] tidyData:inData inputFormat:TidyFormat_HTML outputFormat:TidyFormat_XHTML diagnostics:NULL error:&theError];
            }
        else if (inOptions & CXMLDocumentTidyXML)
            {
            inData = [[CTidy tidy] tidyData:inData inputFormat:TidyFormat_XML outputFormat:TidyFormat_XML diagnostics:NULL error:&theError];
            }
        #endif

        if (theError == NULL)
            {
            xmlDocPtr theDoc = NULL;
            if (inData && inData.length > 0)
                {
                CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(encoding);
                CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc);
                const char *enc = CFStringGetCStringPtr(cfencstr, 0);
125
                theDoc = xmlReadMemory([inData bytes], (int)[inData length], NULL, enc, XML_PARSE_RECOVER | XML_PARSE_NOWARNING);
126 127 128 129 130
                }

            if (theDoc != NULL && xmlDocGetRootElement(theDoc) != NULL)
                {
                _node = (xmlNodePtr)theDoc;
131
                _node->_private = (__bridge void *)(self); // Note. NOT retained (TODO think more about _private usage)
132 133 134 135
                }
            else
                {
                xmlErrorPtr	theLastErrorPtr = xmlGetLastError();
136 137
                NSString* message = @(theLastErrorPtr ? theLastErrorPtr->message : "Unknown error");
                NSDictionary *theUserInfo = @{NSLocalizedDescriptionKey: message};
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
                theError = [NSError errorWithDomain:@"CXMLErrorDomain" code:1 userInfo:theUserInfo];

                xmlResetLastError();
                }
            }

        if (outError)
            *outError = theError;

        if (theError != NULL)
            {
            self = NULL;
            }
        }
    return(self);
    }
Pierre's avatar
Pierre committed
154 155

- (id)initWithContentsOfURL:(NSURL *)inURL options:(NSUInteger)inOptions error:(NSError **)outError
156
    {
Pierre's avatar
Pierre committed
157
	return [self initWithContentsOfURL:inURL encoding:NSUTF8StringEncoding options:inOptions error:outError];
158
    }
Pierre's avatar
Pierre committed
159 160

- (id)initWithContentsOfURL:(NSURL *)inURL encoding:(NSStringEncoding)encoding options:(NSUInteger)inOptions error:(NSError **)outError
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    {
    if (outError)
        *outError = NULL;

    NSData *theData = [NSData dataWithContentsOfURL:inURL options:NSUncachedRead error:outError];
    if (theData)
        {
        self = [self initWithData:theData encoding:encoding options:inOptions error:outError];
        }
    else
        {
        self = NULL;
        }

    return(self);
    }
Pierre's avatar
Pierre committed
177 178

- (void)dealloc
179 180
    {
    // Fix for #35 http://code.google.com/p/touchcode/issues/detail?id=35 -- clear up the node objects first (inside a pool so I _know_ they're cleared) and then freeing the document
Pierre's avatar
Pierre committed
181

182
    @autoreleasepool {
Pierre's avatar
Pierre committed
183

184 185 186 187
    for (CXMLNode *theNode in nodePool)
        {
        [theNode invalidate];
        }
Pierre's avatar
Pierre committed
188

189 190
    nodePool = NULL;

191
    }
192 193 194 195
    //
    xmlUnlinkNode(_node);
    xmlFreeDoc((xmlDocPtr)_node);
    _node = NULL;
Pierre's avatar
Pierre committed
196 197 198 199 200 201 202 203 204 205
}

//- (NSString *)characterEncoding;
//- (NSString *)version;
//- (BOOL)isStandalone;
//- (CXMLDocumentContentKind)documentContentKind;
//- (NSString *)MIMEType;
//- (CXMLDTD *)DTD;

- (CXMLElement *)rootElement
206 207 208 209
    {
    xmlNodePtr theLibXMLNode = xmlDocGetRootElement((xmlDocPtr)_node);
    return([CXMLNode nodeWithLibXMLNode:theLibXMLNode freeOnDealloc:NO]);
    }
Pierre's avatar
Pierre committed
210 211

- (NSData *)XMLData
212 213 214
    {
    return([self XMLDataWithOptions:0]);
    }
Pierre's avatar
Pierre committed
215 216

- (NSData *)XMLDataWithOptions:(NSUInteger)options
217 218 219 220 221
    {
    #pragma unused (options)
    xmlChar *theBuffer = NULL;
    int theBufferSize = 0;
    xmlDocDumpMemory((xmlDocPtr)self->_node, &theBuffer, &theBufferSize);
Pierre's avatar
Pierre committed
222

223
    NSData *theData = [NSData dataWithBytes:theBuffer length:theBufferSize];
Pierre's avatar
Pierre committed
224

225
    xmlFree(theBuffer);
Pierre's avatar
Pierre committed
226

227 228
    return(theData);
    }
Pierre's avatar
Pierre committed
229 230 231 232 233

//- (id)objectByApplyingXSLT:(NSData *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
//- (id)objectByApplyingXSLTString:(NSString *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
//- (id)objectByApplyingXSLTAtURL:(NSURL *)xsltURL arguments:(NSDictionary *)argument error:(NSError **)error;
- (id)XMLStringWithOptions:(NSUInteger)options
234 235 236 237 238 239
    {
    CXMLElement *theRoot = [self rootElement];
    NSMutableString *xmlString = [NSMutableString string];
    [xmlString appendString:[theRoot XMLStringWithOptions:options]];
    return xmlString;
    }
Pierre's avatar
Pierre committed
240 241

- (NSString *)description
242 243
    {
    NSAssert(_node != NULL, @"TODO");
Pierre's avatar
Pierre committed
244

245 246 247
    NSMutableString *result = [NSMutableString stringWithFormat:@"<%@ %p [%p]> ", NSStringFromClass([self class]), self, self->_node];
    xmlChar *xmlbuff;
    int buffersize;
Pierre's avatar
Pierre committed
248

249
    xmlDocDumpFormatMemory((xmlDocPtr)(self->_node), &xmlbuff, &buffersize, 1);
250
    NSString *dump = [[NSString alloc] initWithBytes:xmlbuff length:buffersize encoding:NSUTF8StringEncoding];
251
    xmlFree(xmlbuff);
Pierre's avatar
Pierre committed
252

253 254 255
    [result appendString:dump];
    return result;
    }
Pierre's avatar
Pierre committed
256

Pierre's avatar
Pierre committed
257
@end