Index: src/OFXMLParser.h ================================================================== --- src/OFXMLParser.h +++ src/OFXMLParser.h @@ -16,10 +16,12 @@ @class OFXMLParser; @class OFArray; @class OFMutableArray; #if defined(OF_HAVE_PROPERTIES) && defined(OF_HAVE_BLOCKS) +typedef void (^of_xml_parser_processing_instructions_block_t)( + OFXMLParser *parser, OFString *pi); typedef void (^of_xml_parser_element_start_block_t)(OFXMLParser *parser, OFString *name, OFString *prefix, OFString *ns, OFArray *attrs); typedef void (^of_xml_parser_element_end_block_t)(OFXMLParser *parser, OFString *name, OFString *prefix, OFString *ns); typedef void (^of_xml_parser_string_block_t)(OFXMLParser *parser, @@ -30,10 +32,19 @@ /** * \brief A protocol that needs to be implemented by delegates for OFXMLParser. */ @protocol OFXMLParserDelegate +/** + * This callback is called when the XML parser found processing instructions. + * + * \param parser The parser which found processing instructions + * \param pi The processing instructions + */ +- (void)parser: (OFXMLParser*)parser + foundProcessingInstructions: (OFString*)pi; + /** * This callback is called when the XML parser found the start of a new tag. * * \param parser The parser which found a new tag * \param name The name of the tag which just started @@ -114,11 +125,11 @@ { id delegate; enum { OF_XMLPARSER_OUTSIDE_TAG, OF_XMLPARSER_TAG_OPENED, - OF_XMLPARSER_IN_PROLOG, + OF_XMLPARSER_IN_PROCESSING_INSTRUCTIONS, OF_XMLPARSER_IN_TAG_NAME, OF_XMLPARSER_IN_CLOSE_TAG_NAME, OF_XMLPARSER_IN_TAG, OF_XMLPARSER_IN_ATTR_NAME, OF_XMLPARSER_EXPECT_DELIM, @@ -143,10 +154,12 @@ OFString *attrName; OFString *attrPrefix; char delim; OFMutableArray *previous; #if defined(OF_HAVE_PROPERTIES) && defined(OF_HAVE_BLOCKS) + of_xml_parser_processing_instructions_block_t + processingInstructionsHandler; of_xml_parser_element_start_block_t elementStartHandler; of_xml_parser_element_end_block_t elementEndHandler; of_xml_parser_string_block_t charactersHandler; of_xml_parser_string_block_t CDATAHandler; of_xml_parser_string_block_t commentHandler; @@ -156,10 +169,12 @@ } #ifdef OF_HAVE_PROPERTIES @property (retain) id delegate; # ifdef OF_HAVE_BLOCKS +@property (copy) of_xml_parser_processing_instructions_block_t + processingInstructionsHandler; @property (copy) of_xml_parser_element_start_block_t elementStartHandler; @property (copy) of_xml_parser_element_end_block_t elementEndHandler; @property (copy) of_xml_parser_string_block_t charactersHandler; @property (copy) of_xml_parser_string_block_t CDATAHandler; @property (copy) of_xml_parser_string_block_t commentHandler; @@ -183,10 +198,23 @@ * \param delegate The delegate to use */ - (void)setDelegate: (id )delegate; #if defined(OF_HAVE_PROPERTIES) && defined(OF_HAVE_BLOCKS) +/** + * \return The processing instructions handler + */ +- (of_xml_parser_processing_instructions_block_t)processingInstructionsHandler; + +/** + * Sets the processing instructions handler. + * + * \param block A processing instructions handler + */ +- (void)setProcessingInstructionsHandler: + (of_xml_parser_processing_instructions_block_t)block; + /** * \return The element start handler */ - (of_xml_parser_element_start_block_t)elementStartHandler; Index: src/OFXMLParser.m ================================================================== --- src/OFXMLParser.m +++ src/OFXMLParser.m @@ -78,22 +78,23 @@ attr->ns = [attr_ns retain]; } @implementation OFXMLParser #if defined(OF_HAVE_PROPERTIES) && defined(OF_HAVE_BLOCKS) -@synthesize elementStartHandler, elementEndHandler, charactersHandler; -@synthesize CDATAHandler, commentHandler, unknownEntityHandler; +@synthesize processingInstructionsHandler, elementStartHandler; +@synthesize elementEndHandler, charactersHandler, CDATAHandler, commentHandler; +@synthesize unknownEntityHandler; #endif + (void)initialize { size_t i; const SEL sels[] = { @selector(_parseOutsideTagWithBuffer:i:last:), @selector(_parseTagOpenedWithBuffer:i:last:), - @selector(_parseInPrologWithBuffer:i:last:), + @selector(_parseInProcessingInstructionsWithBuffer:i:last:), @selector(_parseInTagNameWithBuffer:i:last:), @selector(_parseInCloseTagNameWithBuffer:i:last:), @selector(_parseInTagWithBuffer:i:last:), @selector(_parseInAttributeNameWithBuffer:i:last:), @selector(_parseExpectDelimiterWithBuffer:i:last:), @@ -283,11 +284,12 @@ last: (size_t*)last { switch (buf[*i]) { case '?': *last = *i + 1; - state = OF_XMLPARSER_IN_PROLOG; + state = OF_XMLPARSER_IN_PROCESSING_INSTRUCTIONS; + level = 0; break; case '/': *last = *i + 1; state = OF_XMLPARSER_IN_CLOSE_TAG_NAME; break; @@ -301,18 +303,40 @@ break; } } /* Inside prolog */ -- (void)_parseInPrologWithBuffer: (const char*)buf - i: (size_t*)i - last: (size_t*)last +- (void)_parseInProcessingInstructionsWithBuffer: (const char*)buf + i: (size_t*)i + last: (size_t*)last { - *last = *i + 1; - if (buf[*i] == '?') - state = OF_XMLPARSER_EXPECT_CLOSE; + level = 1; + else if (level == 1 && buf[*i] == '>') { + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFMutableString *pi; + size_t len; + + [cache appendCStringWithoutUTF8Checking: buf + *last + length: *i - *last]; + pi = [[cache mutableCopy] autorelease]; + len = [pi length]; + + [pi removeCharactersFromIndex: len - 1 + toIndex: len]; + + [delegate parser: self + foundProcessingInstructions: pi]; + + [pool release]; + + [cache setToCString: ""]; + + *last = *i + 1; + state = OF_XMLPARSER_OUTSIDE_TAG; + } else + level = 0; } /* Inside a tag, no name yet */ - (void)_parseInTagNameWithBuffer: (const char*)buf i: (size_t*)i @@ -857,10 +881,15 @@ 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*)attrs Index: tests/OFXMLParserTests.m ================================================================== --- tests/OFXMLParserTests.m +++ tests/OFXMLParserTests.m @@ -24,10 +24,11 @@ static OFString *module = @"OFXMLParser"; static int i = 0; enum event_type { + PROCESSING_INSTRUCTIONS, TAG_START, TAG_END, STRING, CDATA, COMMENT @@ -38,53 +39,60 @@ name: (OFString*)name prefix: (OFString*)prefix namespace: (OFString*)ns attributes: (OFArray*)attrs string: (OFString*)string - comment: (OFString*)comment { OFString *msg; i++; msg = [OFString stringWithFormat: @"Parsing part #%d", i]; switch (i) { case 1: + TEST(msg, et == PROCESSING_INSTRUCTIONS && + [string isEqual: @"xml version='1.0'"]) + break; + case 2: + TEST(msg, et == PROCESSING_INSTRUCTIONS && + [string isEqual: @"p?i"]) + break; + case 3: TEST(msg, et == TAG_START && [name isEqual: @"root"] && prefix == nil && ns == nil && [attrs count] == 0) break; - case 2: + case 4: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 3: + case 5: TEST(msg, et == CDATA && [string isEqual: @"f<]]]oo"]) break; - case 4: + case 6: TEST(msg, et == TAG_START && [name isEqual: @"bar"] && prefix == nil && ns == nil && attrs == nil) break; - case 5: + case 7: TEST(msg, et == TAG_END && [name isEqual: @"bar"] && prefix == nil && ns == nil && attrs == nil) break; - case 6: + case 8: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 7: + case 9: TEST(msg, et == TAG_START && [name isEqual: @"foobar"] && prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"] && [attrs count] == 1 && /* xmlns attr */ [[[attrs objectAtIndex: 0] name] isEqual: @"xmlns"] && [[attrs objectAtIndex: 0] namespace] == nil && [[[attrs objectAtIndex: 0] stringValue] isEqual: @"urn:objfw:test:foobar"]) break; - case 8: + case 10: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 9: + case 11: TEST(msg, et == TAG_START && [name isEqual: @"qux"] && prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"] && [attrs count] == 1 && /* xmlns:foo attr */ [[[attrs objectAtIndex: 0] name] isEqual: @"foo"] && @@ -91,14 +99,14 @@ [[[attrs objectAtIndex: 0] namespace] isEqual: @"http://www.w3.org/2000/xmlns/"] && [[[attrs objectAtIndex: 0] stringValue] isEqual: @"urn:objfw:test:foo"]) break; - case 10: + case 12: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 11: + case 13: TEST(msg, et == TAG_START && [name isEqual: @"bla"] && [prefix isEqual: @"foo"] && [ns isEqual: @"urn:objfw:test:foo"] && [attrs count] == 2 && /* foo:bla attr */ @@ -109,14 +117,14 @@ /* blafoo attr */ [[[attrs objectAtIndex: 1] name] isEqual: @"blafoo"] && [[attrs objectAtIndex: 1] namespace] == nil && [[[attrs objectAtIndex: 1] stringValue] isEqual: @"foo"]) break; - case 12: + case 14: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 13: + case 15: TEST(msg, et == TAG_START && [name isEqual: @"blup"] && prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"] && [attrs count] == 2 && /* foo:qux attr */ [[[attrs objectAtIndex: 0] name] isEqual: @"qux"] && @@ -126,18 +134,18 @@ /* quxqux attr */ [[[attrs objectAtIndex: 1] name] isEqual: @"quxqux"] && [[attrs objectAtIndex: 1] namespace] == nil && [[[attrs objectAtIndex: 1] stringValue] isEqual: @"test"]) break; - case 14: + case 16: TEST(msg, et == TAG_END && [name isEqual: @"blup"] && prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"]) break; - case 15: + case 17: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 16: + case 18: TEST(msg, et == TAG_START && [name isEqual: @"bla"] && [prefix isEqual: @"bla"] && [ns isEqual: @"urn:objfw:test:bla"] && [attrs count] == 3 && /* xmlns:bla attr */ [[[attrs objectAtIndex: 0] name] isEqual: @"bla"] && @@ -153,19 +161,19 @@ [[[attrs objectAtIndex: 2] name] isEqual: @"foo"] && [[[attrs objectAtIndex: 2] namespace] isEqual: @"urn:objfw:test:bla"] && [[[attrs objectAtIndex: 2] stringValue] isEqual: @"blafoo"]) break; - case 17: + case 19: TEST(msg, et == TAG_END && [name isEqual: @"bla"] && [prefix isEqual: @"bla"] && [ns isEqual: @"urn:objfw:test:bla"]) break; - case 18: + case 20: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; - case 19: + case 21: TEST(msg, et == TAG_START && [name isEqual: @"abc"] && prefix == nil && [ns isEqual: @"urn:objfw:test:abc"] && [attrs count] == 3 && /* xmlns attr */ [[[attrs objectAtIndex: 0] name] isEqual: @"xmlns"] && @@ -180,51 +188,62 @@ [[[attrs objectAtIndex: 2] name] isEqual: @"abc"] && [[[attrs objectAtIndex: 2] namespace] isEqual: @"urn:objfw:test:foo"] && [[[attrs objectAtIndex: 2] stringValue] isEqual: @"abc"]) break; - case 20: + case 22: TEST(msg, et == TAG_END && [name isEqual: @"abc"] && prefix == nil && [ns isEqual: @"urn:objfw:test:abc"]) break; - case 21: - TEST(msg, et == STRING && [string isEqual: @"\n "]) - break; - case 22: - TEST(msg, et == TAG_END && [name isEqual: @"bla"] && - [prefix isEqual: @"foo"] && - [ns isEqual: @"urn:objfw:test:foo"]) - break; case 23: TEST(msg, et == STRING && [string isEqual: @"\n "]) break; case 24: - TEST(msg, et == COMMENT && [comment isEqual: @" commänt "]) + TEST(msg, et == TAG_END && [name isEqual: @"bla"] && + [prefix isEqual: @"foo"] && + [ns isEqual: @"urn:objfw:test:foo"]) break; case 25: - TEST(msg, et == STRING && [string isEqual: @"\n "]) + TEST(msg, et == STRING && [string isEqual: @"\n "]) break; case 26: - TEST(msg, et == TAG_END && [name isEqual: @"qux"] && - prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"]) + TEST(msg, et == COMMENT && [string isEqual: @" commänt "]) break; case 27: - TEST(msg, et == STRING && [string isEqual: @"\n "]) + TEST(msg, et == STRING && [string isEqual: @"\n "]) break; case 28: - TEST(msg, et == TAG_END && [name isEqual: @"foobar"] && + TEST(msg, et == TAG_END && [name isEqual: @"qux"] && prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"]) break; case 29: - TEST(msg, et == STRING && [string isEqual: @"\n"]) + TEST(msg, et == STRING && [string isEqual: @"\n "]) break; case 30: + TEST(msg, et == TAG_END && [name isEqual: @"foobar"] && + prefix == nil && [ns isEqual: @"urn:objfw:test:foobar"]) + break; + case 31: + TEST(msg, et == STRING && [string isEqual: @"\n"]) + break; + case 32: TEST(msg, et == TAG_END && [name isEqual: @"root"] && prefix == nil && ns == nil); break; } } + +- (void)parser: (OFXMLParser*)parser + foundProcessingInstructions: (OFString*)pi +{ + [self parserCallbackWithEventType: PROCESSING_INSTRUCTIONS + name: nil + prefix: nil + namespace: nil + attributes: nil + string: pi]; +} - (void)parser: (OFXMLParser*)parser didStartElement: (OFString*)name withPrefix: (OFString*)prefix namespace: (OFString*)ns @@ -233,12 +252,11 @@ [self parserCallbackWithEventType: TAG_START name: name prefix: prefix namespace: ns attributes: attrs - string: nil - comment: nil]; + string: nil]; } - (void)parser: (OFXMLParser*)parser didEndElement: (OFString*)name withPrefix: (OFString*)prefix @@ -247,12 +265,11 @@ [self parserCallbackWithEventType: TAG_END name: name prefix: prefix namespace: ns attributes: nil - string: nil - comment: nil]; + string: nil]; } - (void)parser: (OFXMLParser*)parser foundCharacters: (OFString*)string { @@ -259,12 +276,11 @@ [self parserCallbackWithEventType: STRING name: nil prefix: nil namespace: nil attributes: nil - string: string - comment: nil]; + string: string]; } - (void)parser: (OFXMLParser*)parser foundCDATA: (OFString*)cdata { @@ -271,12 +287,11 @@ [self parserCallbackWithEventType: CDATA name: nil prefix: nil namespace: nil attributes: nil - string: cdata - comment: nil]; + string: cdata]; } - (void)parser: (OFXMLParser*)parser foundComment: (OFString*)comment { @@ -283,12 +298,11 @@ [self parserCallbackWithEventType: COMMENT name: nil prefix: nil namespace: nil attributes: nil - string: nil - comment: comment]; + string: comment]; } - (OFString*)parser: (OFXMLParser*)parser foundUnknownEntityNamed: (OFString*)entity { @@ -300,11 +314,12 @@ - (void)XMLParserTests { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFXMLParser *parser; - const char *str = "<<>>>>\n" + const char *str = "" + "<<>>>>\n" " \n" " \n" " \n" " \n" " \n" @@ -332,10 +347,10 @@ else [parser parseBuffer: str + j withSize: 2]; } - TEST(@"Checking if everything was parsed", i == 30) + TEST(@"Checking if everything was parsed", i == 32) [pool drain]; } @end