/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012
* Jonathan Schleifer <js@webkeks.org>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#define OF_XML_PARSER_M
#include <string.h>
#include <sys/types.h>
#import "OFXMLParser.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFDataArray.h"
#import "OFXMLAttribute.h"
#import "OFStream.h"
#import "OFFile.h"
#import "OFInitializationFailedException.h"
#import "OFMalformedXMLException.h"
#import "OFUnboundNamespaceException.h"
#import "autorelease.h"
#import "macros.h"
typedef void (*state_function)(id, SEL, const char*, size_t*, size_t*);
static SEL selectors[OF_XMLPARSER_NUM_STATES];
static state_function lookupTable[OF_XMLPARSER_NUM_STATES];
static OF_INLINE void
cache_append(OFDataArray *cache, const char *string,
of_string_encoding_t encoding, size_t length)
{
if (OF_LIKELY(encoding == OF_STRING_ENCODING_UTF_8))
[cache addItemsFromCArray: string
count: length];
else {
void *pool = objc_autoreleasePoolPush();
OFString *tmp = [OFString stringWithCString: string
encoding: encoding
length: length];
[cache addItemsFromCArray: [tmp UTF8String]
count: [tmp UTF8StringLength]];
objc_autoreleasePoolPop(pool);
}
}
static OFString*
transform_string(OFDataArray *cache, size_t cut, BOOL unescape,
OFObject <OFStringXMLUnescapingDelegate> *delegate)
{
char *cArray;
size_t i, length;
BOOL hasEntities = NO;
OFMutableString *ret;
cArray = [cache cArray];
length = [cache count] - cut;
for (i = 0; i < length; i++) {
if (cArray[i] == '\r') {
if (i + 1 < length && cArray[i + 1] == '\n') {
[cache removeItemAtIndex: i];
cArray = [cache cArray];
i--;
length--;
} else
cArray[i] = '\n';
} else if (cArray[i] == '&')
hasEntities = YES;
}
ret = [OFMutableString stringWithUTF8String: cArray
length: length];
if (unescape && hasEntities)
return [ret stringByXMLUnescapingWithDelegate: delegate];
[ret makeImmutable];
return ret;
}
static OFString*
namespace_for_prefix(OFString *prefix, OFArray *namespaces)
{
OFDictionary **objects = [namespaces objects];
ssize_t i;
if (prefix == nil)
prefix = @"";
for (i = [namespaces count] - 1; i >= 0; i--) {
OFString *tmp;
if ((tmp = [objects[i] objectForKey: prefix]) != nil)
return tmp;
}
return nil;
}
static OF_INLINE void
resolve_attribute_namespace(OFXMLAttribute *attribute, OFArray *namespaces,
OFXMLParser *self)
{
OFString *attributeNS;
OFString *attributePrefix = attribute->ns;
if (attributePrefix == nil)
return;
attributeNS = namespace_for_prefix(attributePrefix, namespaces);
if ((attributePrefix != nil && attributeNS == nil))
@throw [OFUnboundNamespaceException
exceptionWithClass: [self class]
prefix: attributePrefix];
[attribute->ns release];
attribute->ns = [attributeNS retain];
}
@implementation OFXMLParser
+ (void)initialize
{
size_t i;
const SEL selectors_[] = {
@selector(OF_parseOutsideTagWithBuffer:i:last:),
@selector(OF_parseTagOpenedWithBuffer:i:last:),
@selector(OF_parseInProcessingInstructionsWithBuffer:i:last:),
@selector(OF_parseInTagNameWithBuffer:i:last:),
@selector(OF_parseInCloseTagNameWithBuffer:i:last:),
@selector(OF_parseInTagWithBuffer:i:last:),
@selector(OF_parseInAttributeNameWithBuffer:i:last:),
@selector(OF_parseExpectDelimiterWithBuffer:i:last:),
@selector(OF_parseInAttributeValueWithBuffer:i:last:),
@selector(OF_parseExpectCloseWithBuffer:i:last:),
@selector(OF_parseExpectSpaceOrCloseWithBuffer:i:last:),
@selector(OF_parseInExclamationMarkWithBuffer:i:last:),
@selector(OF_parseInCDATAOpeningWithBuffer:i:last:),
@selector(OF_parseInCDATA1WithBuffer:i:last:),
@selector(OF_parseInCDATA2WithBuffer:i:last:),
@selector(OF_parseInCommentOpeningWithBuffer:i:last:),
@selector(OF_parseInComment1WithBuffer:i:last:),
@selector(OF_parseInComment2WithBuffer:i:last:),
@selector(OF_parseInDoctypeWithBuffer:i:last:),
};
memcpy(selectors, selectors_, sizeof(selectors_));
for (i = 0; i < OF_XMLPARSER_NUM_STATES; i++) {
if (![self instancesRespondToSelector: selectors[i]])
@throw [OFInitializationFailedException
exceptionWithClass: self];
lookupTable[i] = (state_function)
[self instanceMethodForSelector: selectors[i]];
}
}
+ parser
{
return [[[self alloc] init] autorelease];
}
- init
{
self = [super init];
@try {
void *pool;
OFMutableDictionary *dict;
cache = [[OFBigDataArray alloc] init];
previous = [[OFMutableArray alloc] init];
namespaces = [[OFMutableArray alloc] init];
attributes = [[OFMutableArray alloc] init];
pool = objc_autoreleasePoolPush();
dict = [OFMutableDictionary dictionaryWithKeysAndObjects:
@"xml", @"http://www.w3.org/XML/1998/namespace",
@"xmlns", @"http://www.w3.org/2000/xmlns/", nil];
[namespaces addObject: dict];
acceptProlog = YES;
lineNumber = 1;
encoding = OF_STRING_ENCODING_UTF_8;
objc_autoreleasePoolPop(pool);
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[cache release];
[name release];
[prefix release];
[namespaces release];
[attributes release];
[attributeName release];
[attributePrefix release];
[previous release];
[super dealloc];
}
- (id <OFXMLParserDelegate>)delegate
{
return delegate;
}
- (void)setDelegate: (id <OFXMLParserDelegate>)delegate_
{
delegate = delegate_;
}
- (void)parseBuffer: (const char*)buffer
length: (size_t)length
{
size_t i, last = 0;
for (i = 0; i < length; i++) {
size_t j = i;
lookupTable[state](self, selectors[state], buffer, &i, &last);
/* Ensure we don't count this character twice */
if (i != j)
continue;
if (buffer[i] == '\r' || (buffer[i] == '\n' &&
!lastCarriageReturn))
lineNumber++;
lastCarriageReturn = (buffer[i] == '\r');
}
/* In OF_XMLPARSER_IN_TAG, there can be only spaces */
if (length - last > 0 && state != OF_XMLPARSER_IN_TAG)
cache_append(cache, buffer + last, encoding, length - last);
}
- (void)parseString: (OFString*)string
{
[self parseBuffer: [string UTF8String]
length: [string UTF8StringLength]];
}
- (void)parseStream: (OFStream*)stream
{
char *buffer = [self allocMemoryWithSize: of_pagesize];
@try {
while (![stream isAtEndOfStream]) {
size_t length = [stream readIntoBuffer: buffer
length: of_pagesize];
[self parseBuffer: buffer
length: length];
}
} @finally {
[self freeMemory: buffer];
}
}
- (void)parseFile: (OFString*)path
{
OFFile *file = [[OFFile alloc] initWithPath: path
mode: @"rb"];
@try {
[self parseStream: file];
} @finally {
[file release];
}
}
/*
* The following methods handle the different states of the parser. They are
* looked up in +[initialize] and put in a lookup table to speed things up.
* One dispatch for every character would be way too slow!
*/
/* Not in a tag */
- (void)OF_parseOutsideTagWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
size_t length;
if ((finishedParsing || [previous count] < 1) && buffer[*i] != ' ' &&
buffer[*i] != '\t' && buffer[*i] != '\n' && buffer[*i] != '\r' &&
buffer[*i] != '<')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
if (buffer[*i] != '<')
return;
if ((length = *i - *last) > 0)
cache_append(cache, buffer + *last, encoding, length);
if ([cache count] > 0) {
void *pool = objc_autoreleasePoolPush();
OFString *characters = transform_string(cache, 0, YES, self);
[delegate parser: self
foundCharacters: characters];
objc_autoreleasePoolPop(pool);
}
[cache removeAllItems];
*last = *i + 1;
state = OF_XMLPARSER_TAG_OPENED;
}
/* Tag was just opened */
- (void)OF_parseTagOpenedWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (finishedParsing && buffer[*i] != '!' && buffer[*i] != '?')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
switch (buffer[*i]) {
case '?':
*last = *i + 1;
state = OF_XMLPARSER_IN_PROCESSING_INSTRUCTIONS;
level = 0;
break;
case '/':
*last = *i + 1;
state = OF_XMLPARSER_IN_CLOSE_TAG_NAME;
acceptProlog = NO;
break;
case '!':
*last = *i + 1;
state = OF_XMLPARSER_IN_EXCLAMATIONMARK;
acceptProlog = NO;
break;
default:
state = OF_XMLPARSER_IN_TAG_NAME;
acceptProlog = NO;
(*i)--;
break;
}
}
/* <?xml […]?> */
- (BOOL)OF_parseXMLProcessingInstructions: (OFString*)pi
{
const char *cString;
size_t i, last, length;
int piState = 0;
OFString *attribute = nil;
OFMutableString *value = nil;
char piDelimiter = 0;
if (!acceptProlog)
return NO;
acceptProlog = NO;
pi = [pi substringWithRange: of_range(3, [pi length] - 3)];
pi = [pi stringByDeletingEnclosingWhitespaces];
cString = [pi UTF8String];
length = [pi UTF8StringLength];
for (i = last = 0; i < length; i++) {
switch (piState) {
case 0:
if (cString[i] == ' ' || cString[i] == '\t' ||
cString[i] == '\r' || cString[i] == '\n')
continue;
last = i;
piState = 1;
i--;
break;
case 1:
if (cString[i] != '=')
continue;
attribute = [OFString
stringWithUTF8String: cString + last
length: i - last];
last = i + 1;
piState = 2;
break;
case 2:
if (cString[i] != '\'' && cString[i] != '"')
return NO;
piDelimiter = cString[i];
last = i + 1;
piState = 3;
break;
case 3:
if (cString[i] != piDelimiter)
continue;
value = [OFMutableString
stringWithUTF8String: cString + last
length: i - last];
if ([attribute isEqual: @"version"])
if (![value hasPrefix: @"1."])
return NO;
if ([attribute isEqual: @"encoding"]) {
[value lowercase];
if ([value isEqual: @"utf-8"])
encoding = OF_STRING_ENCODING_UTF_8;
else if ([value isEqual: @"iso-8859-1"])
encoding =
OF_STRING_ENCODING_ISO_8859_1;
else if ([value isEqual: @"iso-8859-15"])
encoding =
OF_STRING_ENCODING_ISO_8859_15;
else if ([value isEqual: @"windows-1252"])
encoding =
OF_STRING_ENCODING_WINDOWS_1252;
else
return NO;
}
last = i + 1;
piState = 0;
break;
}
}
if (piState != 0)
return NO;
return YES;
}
/* Inside processing instructions */
- (void)OF_parseInProcessingInstructionsWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] == '?')
level = 1;
else if (level == 1 && buffer[*i] == '>') {
void *pool = objc_autoreleasePoolPush();
OFString *pi;
cache_append(cache, buffer + *last, encoding, *i - *last);
pi = transform_string(cache, 1, NO, nil);
if ([pi isEqual: @"xml"] || [pi hasPrefix: @"xml "] ||
[pi hasPrefix: @"xml\t"] || [pi hasPrefix: @"xml\r"] ||
[pi hasPrefix: @"xml\n"])
if (![self OF_parseXMLProcessingInstructions: pi])
@throw [OFMalformedXMLException
exceptionWithClass: [self class]
parser: self];
[delegate parser: self
foundProcessingInstructions: pi];
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
*last = *i + 1;
state = OF_XMLPARSER_OUTSIDE_TAG;
} else
level = 0;
}
/* Inside a tag, no name yet */
- (void)OF_parseInTagNameWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
const char *cacheCString, *tmp;
size_t length, cacheLength;
OFString *cacheString;
if (buffer[*i] != ' ' && buffer[*i] != '\t' && buffer[*i] != '\n' &&
buffer[*i] != '\r' && buffer[*i] != '>' && buffer[*i] != '/')
return;
if ((length = *i - *last) > 0)
cache_append(cache, buffer + *last, encoding, length);
pool = objc_autoreleasePoolPush();
cacheCString = [cache cArray];
cacheLength = [cache count];
cacheString = [OFString stringWithUTF8String: cacheCString
length: cacheLength];
if ((tmp = memchr(cacheCString, ':', cacheLength)) != NULL) {
name = [[OFString alloc]
initWithUTF8String: tmp + 1
length: cacheLength - (tmp - cacheCString) - 1];
prefix = [[OFString alloc]
initWithUTF8String: cacheCString
length: tmp - cacheCString];
} else {
name = [cacheString copy];
prefix = nil;
}
if (buffer[*i] == '>' || buffer[*i] == '/') {
OFString *ns;
ns = namespace_for_prefix(prefix, namespaces);
if (prefix != nil && ns == nil)
@throw [OFUnboundNamespaceException
exceptionWithClass: [self class]
prefix: prefix];
[delegate parser: self
didStartElement: name
withPrefix: prefix
namespace: ns
attributes: nil];
if (buffer[*i] == '/') {
[delegate parser: self
didEndElement: name
withPrefix: prefix
namespace: ns];
if ([previous count] == 0)
finishedParsing = YES;
} else
[previous addObject: cacheString];
[name release];
[prefix release];
name = prefix = nil;
state = (buffer[*i] == '/'
? OF_XMLPARSER_EXPECT_CLOSE
: OF_XMLPARSER_OUTSIDE_TAG);
} else
state = OF_XMLPARSER_IN_TAG;
if (buffer[*i] != '/')
[namespaces addObject: [OFMutableDictionary dictionary]];
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
*last = *i + 1;
}
/* Inside a close tag, no name yet */
- (void)OF_parseInCloseTagNameWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
const char *cacheCString, *tmp;
size_t length, cacheLength;
OFString *cacheString;
OFString *ns;
if (buffer[*i] != ' ' && buffer[*i] != '\t' && buffer[*i] != '\n' &&
buffer[*i] != '\r' && buffer[*i] != '>')
return;
if ((length = *i - *last) > 0)
cache_append(cache, buffer + *last, encoding, length);
pool = objc_autoreleasePoolPush();
cacheCString = [cache cArray];
cacheLength = [cache count];
cacheString = [OFString stringWithUTF8String: cacheCString
length: cacheLength];
if ((tmp = memchr(cacheCString, ':', cacheLength)) != NULL) {
name = [[OFString alloc]
initWithUTF8String: tmp + 1
length: cacheLength - (tmp - cacheCString) - 1];
prefix = [[OFString alloc]
initWithUTF8String: cacheCString
length: tmp - cacheCString];
} else {
name = [cacheString copy];
prefix = nil;
}
if (![[previous lastObject] isEqual: cacheString])
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
[previous removeLastObject];
[cache removeAllItems];
ns = namespace_for_prefix(prefix, namespaces);
if (prefix != nil && ns == nil)
@throw [OFUnboundNamespaceException
exceptionWithClass: [self class]
prefix: prefix];
[delegate parser: self
didEndElement: name
withPrefix: prefix
namespace: ns];
objc_autoreleasePoolPop(pool);
[namespaces removeLastObject];
[name release];
[prefix release];
name = prefix = nil;
*last = *i + 1;
state = (buffer[*i] == '>'
? OF_XMLPARSER_OUTSIDE_TAG
: OF_XMLPARSER_EXPECT_SPACE_OR_CLOSE);
if ([previous count] == 0)
finishedParsing = YES;
}
/* Inside a tag, name found */
- (void)OF_parseInTagWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
OFString *ns;
OFXMLAttribute **attributesObjects;
size_t j, attributesCount;
if (buffer[*i] != '>' && buffer[*i] != '/') {
if (buffer[*i] != ' ' && buffer[*i] != '\t' &&
buffer[*i] != '\n' && buffer[*i] != '\r') {
*last = *i;
state = OF_XMLPARSER_IN_ATTR_NAME;
(*i)--;
}
return;
}
attributesObjects = [attributes objects];
attributesCount = [attributes count];
ns = namespace_for_prefix(prefix, namespaces);
if (prefix != nil && ns == nil)
@throw [OFUnboundNamespaceException
exceptionWithClass: [self class]
prefix: prefix];
for (j = 0; j < attributesCount; j++)
resolve_attribute_namespace(attributesObjects[j], namespaces,
self);
pool = objc_autoreleasePoolPush();
[delegate parser: self
didStartElement: name
withPrefix: prefix
namespace: ns
attributes: attributes];
if (buffer[*i] == '/') {
[delegate parser: self
didEndElement: name
withPrefix: prefix
namespace: ns];
if ([previous count] == 0)
finishedParsing = YES;
[namespaces removeLastObject];
} else if (prefix != nil) {
OFString *str = [OFString stringWithFormat: @"%@:%@",
prefix, name];
[previous addObject: str];
} else
[previous addObject: name];
objc_autoreleasePoolPop(pool);
[name release];
[prefix release];
[attributes removeAllObjects];
name = prefix = nil;
*last = *i + 1;
state = (buffer[*i] == '/'
? OF_XMLPARSER_EXPECT_CLOSE
: OF_XMLPARSER_OUTSIDE_TAG);
}
/* Looking for attribute name */
- (void)OF_parseInAttributeNameWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
OFMutableString *cacheString;
const char *cacheCString, *tmp;
size_t length, cacheLength;
if (buffer[*i] != '=')
return;
if ((length = *i - *last) > 0)
cache_append(cache, buffer + *last, encoding, length);
pool = objc_autoreleasePoolPush();
cacheString = [OFMutableString stringWithUTF8String: [cache cArray]
length: [cache count]];
[cacheString deleteEnclosingWhitespaces];
/* Prevent a useless copy later */
[cacheString makeImmutable];
cacheCString = [cacheString UTF8String];
cacheLength = [cacheString UTF8StringLength];
if ((tmp = memchr(cacheCString, ':', cacheLength)) != NULL) {
attributeName = [[OFString alloc]
initWithUTF8String: tmp + 1
length: cacheLength - (tmp - cacheCString) - 1];
attributePrefix = [[OFString alloc]
initWithUTF8String: cacheCString
length: tmp - cacheCString];
} else {
attributeName = [cacheString copy];
attributePrefix = nil;
}
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
*last = *i + 1;
state = OF_XMLPARSER_EXPECT_DELIM;
}
/* Expecting delimiter */
- (void)OF_parseExpectDelimiterWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
*last = *i + 1;
if (buffer[*i] == ' ' || buffer[*i] == '\t' || buffer[*i] == '\n' ||
buffer[*i] == '\r')
return;
if (buffer[*i] != '\'' && buffer[*i] != '"')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
delimiter = buffer[*i];
state = OF_XMLPARSER_IN_ATTR_VALUE;
}
/* Looking for attribute value */
- (void)OF_parseInAttributeValueWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
OFString *attributeValue;
size_t length;
if (buffer[*i] != delimiter)
return;
if ((length = *i - *last) > 0)
cache_append(cache, buffer + *last, encoding, length);
pool = objc_autoreleasePoolPush();
attributeValue = transform_string(cache, 0, YES, self);
if (attributePrefix == nil && [attributeName isEqual: @"xmlns"])
[[namespaces lastObject] setObject: attributeValue
forKey: @""];
if ([attributePrefix isEqual: @"xmlns"])
[[namespaces lastObject] setObject: attributeValue
forKey: attributeName];
[attributes addObject:
[OFXMLAttribute attributeWithName: attributeName
namespace: attributePrefix
stringValue: attributeValue]];
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
[attributeName release];
[attributePrefix release];
attributeName = attributePrefix = nil;
*last = *i + 1;
state = OF_XMLPARSER_IN_TAG;
}
/* Expecting closing '>' */
- (void)OF_parseExpectCloseWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] == '>') {
*last = *i + 1;
state = OF_XMLPARSER_OUTSIDE_TAG;
} else
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
}
/* Expecting closing '>' or space */
- (void)OF_parseExpectSpaceOrCloseWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] == '>') {
*last = *i + 1;
state = OF_XMLPARSER_OUTSIDE_TAG;
} else if (buffer[*i] != ' ' && buffer[*i] != '\t' &&
buffer[*i] != '\n' && buffer[*i] != '\r')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
}
/* In <! */
- (void)OF_parseInExclamationMarkWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (finishedParsing && buffer[*i] != '-')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
if (buffer[*i] == '-')
state = OF_XMLPARSER_IN_COMMENT_OPENING;
else if (buffer[*i] == '[') {
state = OF_XMLPARSER_IN_CDATA_OPENING;
level = 0;
} else if (buffer[*i] == 'D') {
state = OF_XMLPARSER_IN_DOCTYPE;
level = 0;
} else
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
*last = *i + 1;
}
/* CDATA */
- (void)OF_parseInCDATAOpeningWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] != "CDATA["[level])
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
if (++level == 6) {
state = OF_XMLPARSER_IN_CDATA_1;
level = 0;
}
*last = *i + 1;
}
- (void)OF_parseInCDATA1WithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] == ']')
level++;
else
level = 0;
if (level == 2)
state = OF_XMLPARSER_IN_CDATA_2;
}
- (void)OF_parseInCDATA2WithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
OFString *CDATA;
if (buffer[*i] != '>') {
state = OF_XMLPARSER_IN_CDATA_1;
level = (buffer[*i] == ']' ? 1 : 0);
return;
}
pool = objc_autoreleasePoolPush();
cache_append(cache, buffer + *last, encoding, *i - *last);
CDATA = transform_string(cache, 2, NO, nil);
[delegate parser: self
foundCDATA: CDATA];
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
*last = *i + 1;
state = OF_XMLPARSER_OUTSIDE_TAG;
}
/* Comment */
- (void)OF_parseInCommentOpeningWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] != '-')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
*last = *i + 1;
state = OF_XMLPARSER_IN_COMMENT_1;
level = 0;
}
- (void)OF_parseInComment1WithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if (buffer[*i] == '-')
level++;
else
level = 0;
if (level == 2)
state = OF_XMLPARSER_IN_COMMENT_2;
}
- (void)OF_parseInComment2WithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
void *pool;
OFString *comment;
if (buffer[*i] != '>')
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
pool = objc_autoreleasePoolPush();
cache_append(cache, buffer + *last, encoding, *i - *last);
comment = transform_string(cache, 2, NO, nil);
[delegate parser: self
foundComment: comment];
objc_autoreleasePoolPop(pool);
[cache removeAllItems];
*last = *i + 1;
state = OF_XMLPARSER_OUTSIDE_TAG;
}
/* In <!DOCTYPE ...> */
- (void)OF_parseInDoctypeWithBuffer: (const char*)buffer
i: (size_t*)i
last: (size_t*)last
{
if ((level < 6 && buffer[*i] != "OCTYPE"[level]) ||
(level == 6 && buffer[*i] != ' ' && buffer[*i] != '\t' &&
buffer[*i] != '\n' && buffer[*i] != '\r'))
@throw [OFMalformedXMLException exceptionWithClass: [self class]
parser: self];
if (level < 7 || buffer[*i] == '<')
level++;
if (buffer[*i] == '>') {
if (level == 7)
state = OF_XMLPARSER_OUTSIDE_TAG;
else
level--;
}
*last = *i + 1;
}
- (size_t)lineNumber
{
return lineNumber;
}
- (BOOL)finishedParsing
{
return finishedParsing;
}
- (OFString*)string: (OFString*)string
containsUnknownEntityNamed: (OFString*)entity
{
return [delegate parser: self
foundUnknownEntityNamed: entity];
}
@end
@implementation OFObject (OFXMLParserDelegate)
- (void)parser: (OFXMLParser*)parser
foundProcessingInstructions: (OFString*)pi
{
}
- (void)parser: (OFXMLParser*)parser
didStartElement: (OFString*)name
withPrefix: (OFString*)prefix
namespace: (OFString*)ns
attributes: (OFArray*)attributes
{
}
- (void)parser: (OFXMLParser*)parser
didEndElement: (OFString*)name
withPrefix: (OFString*)prefix
namespace: (OFString*)ns
{
}
- (void)parser: (OFXMLParser*)parser
foundCharacters: (OFString*)characters
{
}
- (void)parser: (OFXMLParser*)parser
foundCDATA: (OFString*)CDATA
{
}
- (void)parser: (OFXMLParser*)parser
foundComment: (OFString*)comment
{
}
- (OFString*)parser: (OFXMLParser*)parser
foundUnknownEntityNamed: (OFString*)entity
{
return nil;
}
@end