/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#import "ObjFW.h"
#import "ObjFWTest.h"
@interface OFXMLParserTests: OTTestCase <OFXMLParserDelegate>
{
int _i;
}
@end
enum EventType {
eventTypeProcessingInstruction,
eventTypeTagOpen,
eventTypeTagClose,
eventTypeString,
eventTypeCDATA,
eventTypeComment
};
@implementation OFXMLParserTests
- (void)parser: (OFXMLParser *)parser
didCreateEvent: (enum EventType)type
name: (OFString *)name
prefix: (OFString *)prefix
namespace: (OFString *)namespace
attributes: (OFArray *)attrs
string: (OFString *)string
{
switch (_i++) {
case 0:
OTAssertEqual(type, eventTypeProcessingInstruction);
OTAssertEqualObjects(name, @"xml");
OTAssertEqualObjects(string, @"version='1.0'");
break;
case 1:
OTAssertEqual(type, eventTypeProcessingInstruction);
OTAssertEqualObjects(name, @"p?i");
OTAssertNil(string);
break;
case 2:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"root");
OTAssertNil(prefix);
OTAssertNil(namespace);
OTAssertEqual(attrs.count, 0);
break;
case 3:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n\n ");
break;
case 4:
OTAssertEqual(type, eventTypeCDATA);
OTAssertEqualObjects(string, @"f<]]]oo]");
OTAssertEqual(parser.lineNumber, 3);
break;
case 5:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"bar");
OTAssertNil(prefix);
OTAssertNil(namespace);
OTAssertNil(attrs);
break;
case 6:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"bar");
OTAssertNil(prefix);
OTAssertNil(namespace);
OTAssertNil(attrs);
break;
case 7:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 8:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"foobar");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
OTAssertEqualObjects(attrs, [OFArray arrayWithObject:
[OFXMLAttribute attributeWithName: @"xmlns"
stringValue: @"urn:objfw:test:"
@"foobar"]]);
break;
case 9:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 10:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"qux");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
OTAssertEqualObjects(attrs, [OFArray arrayWithObject:
[OFXMLAttribute attributeWithName: @"foo"
namespace: @"http://www.w3.org/"
@"2000/xmlns/"
stringValue: @"urn:objfw:test:foo"]]);
break;
case 11:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 12:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"bla");
OTAssertEqualObjects(prefix, @"foo");
OTAssertEqualObjects(namespace, @"urn:objfw:test:foo");
OTAssertEqualObjects(attrs, ([OFArray arrayWithObjects:
[OFXMLAttribute attributeWithName: @"bla"
namespace: @"urn:objfw:test:foo"
stringValue: @"bla"],
[OFXMLAttribute attributeWithName: @"blafoo"
stringValue: @"foo"], nil]));
break;
case 13:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 14:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"blup");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
OTAssertEqualObjects(attrs, ([OFArray arrayWithObjects:
[OFXMLAttribute attributeWithName: @"qux"
namespace: @"urn:objfw:test:foo"
stringValue: @"asd"],
[OFXMLAttribute attributeWithName: @"quxqux"
stringValue: @"test"], nil]));
break;
case 15:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"blup");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
break;
case 16:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 17:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"bla");
OTAssertEqualObjects(prefix, @"bla");
OTAssertEqualObjects(namespace, @"urn:objfw:test:bla");
OTAssertEqualObjects(attrs, ([OFArray arrayWithObjects:
[OFXMLAttribute attributeWithName: @"bla"
namespace: @"http://www.w3.org/"
@"2000/xmlns/"
stringValue: @"urn:objfw:test:bla"],
[OFXMLAttribute attributeWithName: @"qux"
stringValue: @"qux"],
[OFXMLAttribute attributeWithName: @"foo"
namespace: @"urn:objfw:test:bla"
stringValue: @"blafoo"], nil]));
break;
case 18:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"bla");
OTAssertEqualObjects(prefix, @"bla");
OTAssertEqualObjects(namespace, @"urn:objfw:test:bla");
break;
case 19:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 20:
OTAssertEqual(type, eventTypeTagOpen);
OTAssertEqualObjects(name, @"abc");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:abc");
OTAssertEqualObjects(attrs, ([OFArray arrayWithObjects:
[OFXMLAttribute attributeWithName: @"xmlns"
stringValue: @"urn:objfw:test:abc"],
[OFXMLAttribute attributeWithName: @"abc"
stringValue: @"abc"],
[OFXMLAttribute attributeWithName: @"abc"
namespace: @"urn:objfw:test:foo"
stringValue: @"abc"], nil]));
break;
case 21:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"abc");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:abc");
break;
case 22:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 23:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"bla");
OTAssertEqualObjects(prefix, @"foo");
OTAssertEqualObjects(namespace, @"urn:objfw:test:foo");
break;
case 24:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 25:
OTAssertEqual(type, eventTypeComment);
OTAssertEqualObjects(string, @" commänt ");
break;
case 26:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 27:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"qux");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
break;
case 28:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n ");
break;
case 29:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"foobar");
OTAssertNil(prefix);
OTAssertEqualObjects(namespace, @"urn:objfw:test:foobar");
break;
case 30:
OTAssertEqual(type, eventTypeString);
OTAssertEqualObjects(string, @"\n");
break;
case 31:
OTAssertEqual(type, eventTypeTagClose);
OTAssertEqualObjects(name, @"root");
OTAssertNil(prefix);
OTAssertNil(namespace);
break;
}
}
- (void)parser: (OFXMLParser *)parser
foundProcessingInstructionWithTarget: (OFString *)target
text: (OFString *)text
{
[self parser: parser
didCreateEvent: eventTypeProcessingInstruction
name: target
prefix: nil
namespace: nil
attributes: nil
string: text];
}
- (void)parser: (OFXMLParser *)parser
didStartElement: (OFString *)name
prefix: (OFString *)prefix
namespace: (OFString *)namespace
attributes: (OFArray *)attrs
{
[self parser: parser
didCreateEvent: eventTypeTagOpen
name: name
prefix: prefix
namespace: namespace
attributes: attrs
string: nil];
}
- (void)parser: (OFXMLParser *)parser
didEndElement: (OFString *)name
prefix: (OFString *)prefix
namespace: (OFString *)namespace
{
[self parser: parser
didCreateEvent: eventTypeTagClose
name: name
prefix: prefix
namespace: namespace
attributes: nil
string: nil];
}
- (void)parser: (OFXMLParser *)parser foundCharacters: (OFString *)string
{
[self parser: parser
didCreateEvent: eventTypeString
name: nil
prefix: nil
namespace: nil
attributes: nil
string: string];
}
- (void)parser: (OFXMLParser *)parser foundCDATA: (OFString *)CDATA
{
[self parser: parser
didCreateEvent: eventTypeCDATA
name: nil
prefix: nil
namespace: nil
attributes: nil
string: CDATA];
}
- (void)parser: (OFXMLParser *)parser foundComment: (OFString *)comment
{
[self parser: parser
didCreateEvent: eventTypeComment
name: nil
prefix: nil
namespace: nil
attributes: nil
string: comment];
}
- (OFString *)parser: (OFXMLParser *)parser
foundUnknownEntityNamed: (OFString *)entity
{
if ([entity isEqual: @"foo"])
return @"foobar";
return nil;
}
- (void)testParser
{
static const char *string = "\xEF\xBB\xBF<?xml version='1.0'?><?p?i?>"
"<!DOCTYPE foo><root>\r\r"
" <![CDATA[f<]]]oo]]]><bar/>\n"
" <foobar xmlns='urn:objfw:test:foobar'>\r\n"
" <qux xmlns:foo='urn:objfw:test:foo'>\n"
" <foo:bla foo:bla = 'bla' blafoo='foo'>\n"
" <blup foo:qux='asd' quxqux='test'/>\n"
" <bla:bla\r\rxmlns:bla\r=\t\"urn:objfw:test:bla\" qux='qux'\r\n"
" bla:foo='blafoo'/>\n"
" <abc xmlns='urn:objfw:test:abc' abc='abc' foo:abc='abc'/>\n"
" </foo:bla>\n"
" <!-- commänt -->\n"
" </qux>\n"
" </foobar>\n"
"</root>";
OFXMLParser *parser;
size_t j, length;
parser = [OFXMLParser parser];
parser.delegate = self;
/* Simulate a stream where we only get chunks */
length = strlen(string);
for (j = 0; j < length; j+= 2) {
if (parser.hasFinishedParsing)
abort();
if (j + 2 > length)
[parser parseBuffer: string + j length: 1];
else
[parser parseBuffer: string + j length: 2];
}
OTAssertEqual(_i, 32);
OTAssertEqual(parser.lineNumber, 18);
OTAssertTrue(parser.hasFinishedParsing);
/* Parsing whitespaces after the document */
[parser parseString: @" \t\r\n "];
/* Parsing comments after the document */
[parser parseString: @" \t<!-- foo -->\r<!--bar-->\n "];
/* Detection of junk after the document */
OTAssertThrowsSpecific([parser parseString: @"a"],
OFMalformedXMLException);
OTAssertThrowsSpecific([parser parseString: @"<!["],
OFMalformedXMLException);
}
- (void)testDetectionOfInvalidXMLProcessingInstructions
{
OFXMLParser *parser;
parser = [OFXMLParser parser];
OTAssertThrowsSpecific([parser parseString: @"<?xml version='2.0'?>"],
OFMalformedXMLException);
parser = [OFXMLParser parser];
OTAssertThrowsSpecific([parser parseString: @"<x><?xml?></x>"],
OFMalformedXMLException);
}
- (void)testDetectionOfInvalidEncoding
{
OFXMLParser *parser = [OFXMLParser parser];
OTAssertThrowsSpecific(
[parser parseString: @"<?xml encoding='UTF-7'?>"],
OFInvalidEncodingException);
}
@end