ObjFW  Artifact [c5cbe08edf]

Artifact c5cbe08edfe04a518e83c11f81e9610e37cce537966dd8b1d0f0106642ece1b2:

  • File src/OFXMLParser.m — part of check-in [3d16a30f41] at 2013-06-22 12:12:36 on branch trunk — Rework exceptions.

    This mostly removes the argument for the class in which the exception
    occurred. As backtraces were recently added for all platforms, the
    passed class does not give any extra information on where the exception
    occurred anymore.

    This also removes a few other arguments which were not too helpful. In
    the past, the idea was to pass as many arguments as possible so that it
    is easier to find the origin of the exception. However, as backtraces
    are a much better way to find the origin, those are not useful anymore
    and just make the exception more cumbersome to use. The rule is now to
    only pass arguments that might help in recovering from the exception or
    provide information that is otherwise not easily accessible. (user: js, size: 23947) [annotate] [blame] [check-ins using]


/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013
 *   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"

#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 "OFSystemInfo.h"

#import "OFInitializationFailedException.h"
#import "OFMalformedXMLException.h"
#import "OFUnboundPrefixException.h"

#import "autorelease.h"
#import "macros.h"

typedef void (*state_function)(id, SEL);
static SEL selectors[OF_XMLPARSER_NUM_STATES];
static state_function lookupTable[OF_XMLPARSER_NUM_STATES];

static OF_INLINE void
buffer_append(OFDataArray *buffer, const char *string,
    of_string_encoding_t encoding, size_t length)
{
	if (OF_LIKELY(encoding == OF_STRING_ENCODING_UTF_8))
		[buffer addItems: string
			   count: length];
	else {
		void *pool = objc_autoreleasePoolPush();
		OFString *tmp = [OFString stringWithCString: string
						   encoding: encoding
						     length: length];
		[buffer addItems: [tmp UTF8String]
			   count: [tmp UTF8StringLength]];
		objc_autoreleasePoolPop(pool);
	}
}

static OFString*
transform_string(OFDataArray *buffer, size_t cut, bool unescape,
    id <OFStringXMLUnescapingDelegate> delegate)
{
	char *items;
	size_t i, length;
	bool hasEntities = false;
	OFString *ret;

	items = [buffer items];
	length = [buffer count] - cut;

	for (i = 0; i < length; i++) {
		if (items[i] == '\r') {
			if (i + 1 < length && items[i + 1] == '\n') {
				[buffer removeItemAtIndex: i];
				items = [buffer items];

				i--;
				length--;
			} else
				items[i] = '\n';
		} else if (items[i] == '&')
			hasEntities = true;
	}

	ret = [OFString stringWithUTF8String: items
				      length: length];

	if (unescape && hasEntities)
		return [ret stringByXMLUnescapingWithDelegate: delegate];

	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->_namespace;

	if (attributePrefix == nil)
		return;

	attributeNS = namespace_for_prefix(attributePrefix, namespaces);

	if ((attributePrefix != nil && attributeNS == nil))
		@throw [OFUnboundPrefixException
		    exceptionWithPrefix: attributePrefix
				 parser: self];

	[attribute->_namespace release];
	attribute->_namespace = [attributeNS retain];
}

@implementation OFXMLParser
+ (void)initialize
{
	size_t i;

	const SEL selectors_[OF_XMLPARSER_NUM_STATES] = {
		@selector(OF_inByteOrderMarkState),
		@selector(OF_outsideTagState),
		@selector(OF_tagOpenedState),
		@selector(OF_inProcessingInstructionsState),
		@selector(OF_inTagNameState),
		@selector(OF_inCloseTagNameState),
		@selector(OF_inTagState),
		@selector(OF_inAttributeNameState),
		@selector(OF_expectDelimiterState),
		@selector(OF_inAttributeValueState),
		@selector(OF_expectCloseState),
		@selector(OF_expectSpaceOrCloseState),
		@selector(OF_inExclamationMarkState),
		@selector(OF_inCDATAOpeningState),
		@selector(OF_inCDATAState1),
		@selector(OF_inCDATAState2),
		@selector(OF_inCommentOpeningState),
		@selector(OF_inCommentState1),
		@selector(OF_inCommentState2),
		@selector(OF_inDOCTYPEState)
	};
	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]];
	}
}

+ (instancetype)parser
{
	return [[[self alloc] init] autorelease];
}

- init
{
	self = [super init];

	@try {
		void *pool;
		OFMutableDictionary *dict;

		_buffer = [[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 = true;
		_lineNumber = 1;
		_encoding = OF_STRING_ENCODING_UTF_8;
		_depthLimit = 32;

		objc_autoreleasePoolPop(pool);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_buffer 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;
}

- (size_t)depthLimit
{
	return _depthLimit;
}

- (void)setDepthLimit: (size_t)depthLimit
{
	_depthLimit = depthLimit;
}

- (void)parseBuffer: (const char*)buffer
	     length: (size_t)length
{
	_data = buffer;

	for (_i = _last = 0; _i < length; _i++) {
		size_t j = _i;

		lookupTable[_state](self, selectors[_state]);

		/* Ensure we don't count this character twice */
		if (_i != j)
			continue;

		if (_data[_i] == '\r' || (_data[_i] == '\n' &&
		    !_lastCarriageReturn))
			_lineNumber++;

		_lastCarriageReturn = (_data[_i] == '\r');
	}

	/* In OF_XMLPARSER_IN_TAG, there can be only spaces */
	if (length - _last > 0 && _state != OF_XMLPARSER_IN_TAG)
		buffer_append(_buffer, _data + _last, _encoding,
		    length - _last);
}

- (void)parseString: (OFString*)string
{
	[self parseBuffer: [string UTF8String]
		   length: [string UTF8StringLength]];
}

- (void)parseStream: (OFStream*)stream
{
	size_t pageSize = [OFSystemInfo pageSize];
	char *buffer = [self allocMemoryWithSize: pageSize];

	@try {
		while (![stream isAtEndOfStream]) {
			size_t length = [stream readIntoBuffer: buffer
							length: 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!
 */

- (void)OF_inByteOrderMarkState
{
	if (_data[_i] != "\xEF\xBB\xBF"[_level]) {
		if (_level == 0) {
			_state = OF_XMLPARSER_OUTSIDE_TAG;
			_i--;
			return;
		}

		@throw [OFMalformedXMLException exceptionWithParser: self];
	}

	if (_level++ == 2)
		_state = OF_XMLPARSER_OUTSIDE_TAG;

	_last = _i + 1;
}

/* Not in a tag */
- (void)OF_outsideTagState
{
	size_t length;

	if ((_finishedParsing || [_previous count] < 1) && _data[_i] != ' ' &&
	    _data[_i] != '\t' && _data[_i] != '\n' && _data[_i] != '\r' &&
	    _data[_i] != '<')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	if (_data[_i] != '<')
		return;

	if ((length = _i - _last) > 0)
		buffer_append(_buffer, _data + _last, _encoding, length);

	if ([_buffer count] > 0) {
		void *pool = objc_autoreleasePoolPush();
		OFString *characters = transform_string(_buffer, 0, true, self);

		if ([_delegate respondsToSelector:
		    @selector(parser:foundCharacters:)])
			[_delegate parser: self
			  foundCharacters: characters];

		objc_autoreleasePoolPop(pool);
	}

	[_buffer removeAllItems];

	_last = _i + 1;
	_state = OF_XMLPARSER_TAG_OPENED;
}

/* Tag was just opened */
- (void)OF_tagOpenedState
{
	if (_finishedParsing && _data[_i] != '!' && _data[_i] != '?')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	switch (_data[_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 = false;
		break;
	case '!':
		_last = _i + 1;
		_state = OF_XMLPARSER_IN_EXCLAMATIONMARK;
		_acceptProlog = false;
		break;
	default:
		if (_depthLimit > 0 && [_previous count] >= _depthLimit)
			@throw [OFMalformedXMLException
			    exceptionWithParser: self];

		_state = OF_XMLPARSER_IN_TAG_NAME;
		_acceptProlog = false;
		_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;
	bool hasVersion = false;

	if (!_acceptProlog)
		return false;

	_acceptProlog = false;

	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
			    stringWithCString: cString + last
				     encoding: _encoding
				       length: i - last];
			last = i + 1;
			PIState = 2;

			break;
		case 2:
			if (cString[i] != '\'' && cString[i] != '"')
				return false;

			piDelimiter = cString[i];
			last = i + 1;
			PIState = 3;

			break;
		case 3:
			if (cString[i] != piDelimiter)
				continue;

			value = [OFMutableString
			    stringWithCString: cString + last
				     encoding: _encoding
				       length: i - last];

			if ([attribute isEqual: @"version"]) {
				if (![value hasPrefix: @"1."])
					return false;

				hasVersion = true;
			}

			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 false;
			}

			last = i + 1;
			PIState = 0;

			break;
		}
	}

	if (PIState != 0 || !hasVersion)
		return false;

	return true;
}

/* Inside processing instructions */
- (void)OF_inProcessingInstructionsState
{
	if (_data[_i] == '?')
		_level = 1;
	else if (_level == 1 && _data[_i] == '>') {
		void *pool = objc_autoreleasePoolPush();
		OFString *PI;

		buffer_append(_buffer, _data + _last, _encoding, _i - _last);
		PI = transform_string(_buffer, 1, false, 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
				    exceptionWithParser: self];

		if ([_delegate respondsToSelector:
		    @selector(parser:foundProcessingInstructions:)])
			[_delegate		 parser: self
			    foundProcessingInstructions: PI];

		objc_autoreleasePoolPop(pool);

		[_buffer removeAllItems];

		_last = _i + 1;
		_state = OF_XMLPARSER_OUTSIDE_TAG;
	} else
		_level = 0;
}

/* Inside a tag, no name yet */
- (void)OF_inTagNameState
{
	void *pool;
	const char *bufferCString, *tmp;
	size_t length, bufferLength;
	OFString *bufferString;

	if (_data[_i] != ' ' && _data[_i] != '\t' && _data[_i] != '\n' &&
	    _data[_i] != '\r' && _data[_i] != '>' && _data[_i] != '/')
		return;

	if ((length = _i - _last) > 0)
		buffer_append(_buffer, _data + _last, _encoding, length);

	pool = objc_autoreleasePoolPush();

	bufferCString = [_buffer items];
	bufferLength = [_buffer count];
	bufferString = [OFString stringWithUTF8String: bufferCString
					       length: bufferLength];

	if ((tmp = memchr(bufferCString, ':', bufferLength)) != NULL) {
		_name = [[OFString alloc]
		    initWithUTF8String: tmp + 1
				length: bufferLength -
					(tmp - bufferCString) - 1];
		_prefix = [[OFString alloc]
		    initWithUTF8String: bufferCString
				length: tmp - bufferCString];
	} else {
		_name = [bufferString copy];
		_prefix = nil;
	}

	if (_data[_i] == '>' || _data[_i] == '/') {
		OFString *namespace;

		namespace = namespace_for_prefix(_prefix, _namespaces);

		if (_prefix != nil && namespace == nil)
			@throw [OFUnboundPrefixException
			    exceptionWithPrefix: _prefix
					 parser: self];

		if ([_delegate respondsToSelector: @selector(parser:
		    didStartElement:prefix:namespace:attributes:)])
			[_delegate parser: self
			  didStartElement: _name
				   prefix: _prefix
				namespace: namespace
			       attributes: nil];

		if (_data[_i] == '/') {
			if ([_delegate respondsToSelector:
			    @selector(parser:didEndElement:prefix:namespace:)])
				[_delegate parser: self
				    didEndElement: _name
					   prefix: _prefix
					namespace: namespace];

			if ([_previous count] == 0)
				_finishedParsing = true;
		} else
			[_previous addObject: bufferString];

		[_name release];
		[_prefix release];
		_name = _prefix = nil;

		_state = (_data[_i] == '/'
		    ? OF_XMLPARSER_EXPECT_CLOSE
		    : OF_XMLPARSER_OUTSIDE_TAG);
	} else
		_state = OF_XMLPARSER_IN_TAG;

	if (_data[_i] != '/')
		[_namespaces addObject: [OFMutableDictionary dictionary]];

	objc_autoreleasePoolPop(pool);

	[_buffer removeAllItems];
	_last = _i + 1;
}

/* Inside a close tag, no name yet */
- (void)OF_inCloseTagNameState
{
	void *pool;
	const char *bufferCString, *tmp;
	size_t length, bufferLength;
	OFString *bufferString, *namespace;

	if (_data[_i] != ' ' && _data[_i] != '\t' && _data[_i] != '\n' &&
	    _data[_i] != '\r' && _data[_i] != '>')
		return;

	if ((length = _i - _last) > 0)
		buffer_append(_buffer, _data + _last, _encoding, length);

	pool = objc_autoreleasePoolPush();

	bufferCString = [_buffer items];
	bufferLength = [_buffer count];
	bufferString = [OFString stringWithUTF8String: bufferCString
					       length: bufferLength];

	if ((tmp = memchr(bufferCString, ':', bufferLength)) != NULL) {
		_name = [[OFString alloc]
		    initWithUTF8String: tmp + 1
				length: bufferLength -
					(tmp - bufferCString) - 1];
		_prefix = [[OFString alloc]
		    initWithUTF8String: bufferCString
				length: tmp - bufferCString];
	} else {
		_name = [bufferString copy];
		_prefix = nil;
	}

	if (![[_previous lastObject] isEqual: bufferString])
		@throw [OFMalformedXMLException exceptionWithParser: self];

	[_previous removeLastObject];

	[_buffer removeAllItems];

	namespace = namespace_for_prefix(_prefix, _namespaces);
	if (_prefix != nil && namespace == nil)
		@throw [OFUnboundPrefixException exceptionWithPrefix: _prefix
							      parser: self];

	if ([_delegate respondsToSelector:
	    @selector(parser:didEndElement:prefix:namespace:)])
		[_delegate parser: self
		    didEndElement: _name
			   prefix: _prefix
			namespace: namespace];

	objc_autoreleasePoolPop(pool);

	[_namespaces removeLastObject];
	[_name release];
	[_prefix release];
	_name = _prefix = nil;

	_last = _i + 1;
	_state = (_data[_i] == '>'
	    ? OF_XMLPARSER_OUTSIDE_TAG
	    : OF_XMLPARSER_EXPECT_SPACE_OR_CLOSE);

	if ([_previous count] == 0)
		_finishedParsing = true;
}

/* Inside a tag, name found */
- (void)OF_inTagState
{
	void *pool;
	OFString *namespace;
	OFXMLAttribute **attributesObjects;
	size_t j, attributesCount;

	if (_data[_i] != '>' && _data[_i] != '/') {
		if (_data[_i] != ' ' && _data[_i] != '\t' &&
		    _data[_i] != '\n' && _data[_i] != '\r') {
			_last = _i;
			_state = OF_XMLPARSER_IN_ATTR_NAME;
			_i--;
		}

		return;
	}

	attributesObjects = [_attributes objects];
	attributesCount = [_attributes count];

	namespace = namespace_for_prefix(_prefix, _namespaces);

	if (_prefix != nil && namespace == nil)
		@throw [OFUnboundPrefixException exceptionWithPrefix: _prefix
							      parser: self];

	for (j = 0; j < attributesCount; j++)
		resolve_attribute_namespace(attributesObjects[j], _namespaces,
		    self);

	pool = objc_autoreleasePoolPush();

	if ([_delegate respondsToSelector:
	    @selector(parser:didStartElement:prefix:namespace:attributes:)])
		[_delegate parser: self
		  didStartElement: _name
			   prefix: _prefix
			namespace: namespace
		       attributes: _attributes];

	if (_data[_i] == '/') {
		if ([_delegate respondsToSelector:
		    @selector(parser:didEndElement:prefix:namespace:)])
			[_delegate parser: self
			    didEndElement: _name
				   prefix: _prefix
				namespace: namespace];

		if ([_previous count] == 0)
			_finishedParsing = true;

		[_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 = (_data[_i] == '/'
	    ? OF_XMLPARSER_EXPECT_CLOSE
	    : OF_XMLPARSER_OUTSIDE_TAG);
}

/* Looking for attribute name */
- (void)OF_inAttributeNameState
{
	void *pool;
	OFMutableString *bufferString;
	const char *bufferCString, *tmp;
	size_t length, bufferLength;

	if (_data[_i] != '=')
		return;

	if ((length = _i - _last) > 0)
		buffer_append(_buffer, _data + _last, _encoding, length);

	pool = objc_autoreleasePoolPush();

	bufferString = [OFMutableString stringWithUTF8String: [_buffer items]
						      length: [_buffer count]];
	[bufferString deleteEnclosingWhitespaces];
	/* Prevent a useless copy later */
	[bufferString makeImmutable];

	bufferCString = [bufferString UTF8String];
	bufferLength = [bufferString UTF8StringLength];

	if ((tmp = memchr(bufferCString, ':', bufferLength)) != NULL) {
		_attributeName = [[OFString alloc]
		    initWithUTF8String: tmp + 1
				length: bufferLength -
					(tmp - bufferCString) - 1];
		_attributePrefix = [[OFString alloc]
		    initWithUTF8String: bufferCString
				length: tmp - bufferCString];
	} else {
		_attributeName = [bufferString copy];
		_attributePrefix = nil;
	}

	objc_autoreleasePoolPop(pool);

	[_buffer removeAllItems];

	_last = _i + 1;
	_state = OF_XMLPARSER_EXPECT_DELIMITER;
}

/* Expecting delimiter */
- (void)OF_expectDelimiterState
{
	_last = _i + 1;

	if (_data[_i] == ' ' || _data[_i] == '\t' || _data[_i] == '\n' ||
	    _data[_i] == '\r')
		return;

	if (_data[_i] != '\'' && _data[_i] != '"')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	_delimiter = _data[_i];
	_state = OF_XMLPARSER_IN_ATTR_VALUE;
}

/* Looking for attribute value */
- (void)OF_inAttributeValueState
{
	void *pool;
	OFString *attributeValue;
	size_t length;

	if (_data[_i] != _delimiter)
		return;

	if ((length = _i - _last) > 0)
		buffer_append(_buffer, _data + _last, _encoding, length);

	pool = objc_autoreleasePoolPush();
	attributeValue = transform_string(_buffer, 0, true, 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);

	[_buffer removeAllItems];
	[_attributeName release];
	[_attributePrefix release];
	_attributeName = _attributePrefix = nil;

	_last = _i + 1;
	_state = OF_XMLPARSER_IN_TAG;
}

/* Expecting closing '>' */
- (void)OF_expectCloseState
{
	if (_data[_i] == '>') {
		_last = _i + 1;
		_state = OF_XMLPARSER_OUTSIDE_TAG;
	} else
		@throw [OFMalformedXMLException exceptionWithParser: self];
}

/* Expecting closing '>' or space */
- (void)OF_expectSpaceOrCloseState
{
	if (_data[_i] == '>') {
		_last = _i + 1;
		_state = OF_XMLPARSER_OUTSIDE_TAG;
	} else if (_data[_i] != ' ' && _data[_i] != '\t' &&
	    _data[_i] != '\n' && _data[_i] != '\r')
		@throw [OFMalformedXMLException exceptionWithParser: self];
}

/* In <! */
- (void)OF_inExclamationMarkState
{
	if (_finishedParsing && _data[_i] != '-')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	if (_data[_i] == '-')
		_state = OF_XMLPARSER_IN_COMMENT_OPENING;
	else if (_data[_i] == '[') {
		_state = OF_XMLPARSER_IN_CDATA_OPENING;
		_level = 0;
	} else if (_data[_i] == 'D') {
		_state = OF_XMLPARSER_IN_DOCTYPE;
		_level = 0;
	} else
		@throw [OFMalformedXMLException exceptionWithParser: self];

	_last = _i + 1;
}

/* CDATA */
- (void)OF_inCDATAOpeningState
{
	if (_data[_i] != "CDATA["[_level])
		@throw [OFMalformedXMLException exceptionWithParser: self];

	if (++_level == 6) {
		_state = OF_XMLPARSER_IN_CDATA_1;
		_level = 0;
	}

	_last = _i + 1;
}

- (void)OF_inCDATAState1
{
	if (_data[_i] == ']')
		_level++;
	else
		_level = 0;

	if (_level == 2)
		_state = OF_XMLPARSER_IN_CDATA_2;
}

- (void)OF_inCDATAState2
{
	void *pool;
	OFString *CDATA;

	if (_data[_i] != '>') {
		_state = OF_XMLPARSER_IN_CDATA_1;
		_level = (_data[_i] == ']' ? 1 : 0);

		return;
	}

	pool = objc_autoreleasePoolPush();

	buffer_append(_buffer, _data + _last, _encoding, _i - _last);
	CDATA = transform_string(_buffer, 2, false, nil);

	if ([_delegate respondsToSelector: @selector(parser:foundCDATA:)])
		[_delegate parser: self
		       foundCDATA: CDATA];

	objc_autoreleasePoolPop(pool);

	[_buffer removeAllItems];

	_last = _i + 1;
	_state = OF_XMLPARSER_OUTSIDE_TAG;
}

/* Comment */
- (void)OF_inCommentOpeningState
{
	if (_data[_i] != '-')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	_last = _i + 1;
	_state = OF_XMLPARSER_IN_COMMENT_1;
	_level = 0;
}

- (void)OF_inCommentState1
{
	if (_data[_i] == '-')
		_level++;
	else
		_level = 0;

	if (_level == 2)
		_state = OF_XMLPARSER_IN_COMMENT_2;
}

- (void)OF_inCommentState2
{
	void *pool;
	OFString *comment;

	if (_data[_i] != '>')
		@throw [OFMalformedXMLException exceptionWithParser: self];

	pool = objc_autoreleasePoolPush();

	buffer_append(_buffer, _data + _last, _encoding, _i - _last);
	comment = transform_string(_buffer, 2, false, nil);

	if ([_delegate respondsToSelector: @selector(parser:foundComment:)])
		[_delegate parser: self
		     foundComment: comment];

	objc_autoreleasePoolPop(pool);

	[_buffer removeAllItems];

	_last = _i + 1;
	_state = OF_XMLPARSER_OUTSIDE_TAG;
}

/* In <!DOCTYPE ...> */
- (void)OF_inDOCTYPEState
{
	if ((_level < 6 && _data[_i] != "OCTYPE"[_level]) ||
	    (_level == 6 && _data[_i] != ' ' && _data[_i] != '\t' &&
	    _data[_i] != '\n' && _data[_i] != '\r'))
		@throw [OFMalformedXMLException exceptionWithParser: self];

	_level++;

	if (_level > 6 && _data[_i] == '>')
		_state = OF_XMLPARSER_OUTSIDE_TAG;

	_last = _i + 1;
}

- (size_t)lineNumber
{
	return _lineNumber;
}

- (bool)finishedParsing
{
	return _finishedParsing;
}

-	   (OFString*)string: (OFString*)string
  containsUnknownEntityNamed: (OFString*)entity
{
	if ([_delegate respondsToSelector:
	    @selector(parser:foundUnknownEntityNamed:)])
		return [_delegate parser: self
		 foundUnknownEntityNamed: entity];

	return nil;
}
@end