<?php
/* This class is part of the XP framework
*
* $Id$
*/
uses(
'unittest.TestCase',
'xml.parser.XMLParser',
'xml.parser.ParserCallback',
'xml.parser.InputSource'
);
/**
* TestCase
*
* @see xp://xml.parser.ParserCallback
* @see xp://xml.parser.XMLParser
*/
abstract class AbstractXMLParserTest extends TestCase {
const NAME = 0;
const ATTR = 1;
const CHLD = 2;
protected
$parser = NULL;
/**
* Sets up test case
*
*/
public function setUp() {
$this->parser= new XMLParser();
}
/**
* Tears down test case
*
*/
public function tearDown() {
delete($this->parser);
}
/**
* Returns an XML document by prepending the XML declaration to
* the given string and returning it.
*
* @param string str
* @param bool decl default TRUE
* @return xml.parser.InputSource XML the source XML
*/
protected abstract function source($str, $decl= TRUE);
/**
* Creates a new callback
*
* @return xml.parser.ParserCallback
*/
protected function newCallback() {
return newinstance('xml.parser.ParserCallback', array(), '{
protected
$pointer = array();
public
$tree = NULL,
$elements = array(),
$encoding = NULL;
public function onStartElement($parser, $name, $attrs) {
$this->elements[]= $name;
array_unshift($this->pointer, array($name, $attrs, array()));
}
public function onEndElement($parser, $name) {
$e= array_shift($this->pointer);
if (empty($this->pointer)) {
$this->tree= $e;
} else {
$this->pointer[0][AbstractXMLParserTest::CHLD][]= $e;
}
}
public function onCData($parser, $cdata) {
$this->pointer[0][AbstractXMLParserTest::CHLD][]= trim($cdata);
}
public function onDefault($parser, $data) {
$this->pointer[0][AbstractXMLParserTest::CHLD][]= trim($data);
}
public function onBegin($instance) {
$this->encoding= $instance->getEncoding();
}
public function onError($instance, $exception) {
}
public function onFinish($instance) {
}
}');
}
/**
* Test
*
*/
#[@test]
public function withoutDeclaration() {
$this->assertTrue($this->parser->parse($this->source('<root/>', TRUE)));
}
/**
* Test parsing an empty string
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function emptyString() {
$this->parser->parse($this->source('', FALSE));
}
/**
* Test
*
*/
#[@test]
public function withDeclaration() {
$this->assertTrue($this->parser->parse($this->source('<root/>')));
}
/**
* Test tree parsing
*
*/
#[@test]
public function tree() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<book>
<author><name>Timm</name></author>
<chapter id="1">
<title>Introduction</title>
<paragraph>
This is where it all started.
</paragraph>
</chapter>
</book>'));
$this->assertEquals('book', $callback->tree[self::NAME]);
$this->assertEquals(array(), $callback->tree[self::ATTR]);
with ($author= $callback->tree[self::CHLD][1]); {
$this->assertEquals('author', $author[self::NAME]);
$this->assertEquals(array(), $author[self::ATTR]);
with ($name= $author[self::CHLD][0]); {
$this->assertEquals('name', $name[self::NAME]);
$this->assertEquals(array(), $name[self::ATTR]);
$this->assertEquals(array('Timm'), $name[self::CHLD]);
}
}
with ($chapter= $callback->tree[self::CHLD][3]); {
$this->assertEquals('chapter', $chapter[self::NAME]);
$this->assertEquals(array('id' => '1'), $chapter[self::ATTR]);
with ($title= $chapter[self::CHLD][1]); {
$this->assertEquals('title', $title[self::NAME]);
$this->assertEquals(array(), $title[self::ATTR]);
$this->assertEquals(array('Introduction'), $title[self::CHLD]);
}
with ($paragraph= $chapter[self::CHLD][3]); {
$this->assertEquals('paragraph', $paragraph[self::NAME]);
$this->assertEquals(array(), $paragraph[self::ATTR]);
$this->assertEquals(array('This is where it all started.'), $paragraph[self::CHLD]);
}
}
}
/**
* Test reusability, that is, a parser can be reused after calling parse()
* on it.
*
*/
#[@test]
public function reusable() {
for ($i= 0; $i < 4; $i++) {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<run id="'.$i.'"/>'));
$this->assertEquals(
array('run', array('id' => (string)$i), array()),
$callback->tree,
'Run #'.$i
);
}
}
/**
* Test error is thrown late in the process
*
*/
#[@test]
public function errorOccursLate() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
try {
$this->parser->parse($this->source('<doc><h1>Title</h1><p>Text</p><img></doc>'));
$this->fail('Parsed without problems', NULL, 'xml.XMLFormatException');
} catch (XMLFormatException $expected) {
$this->assertEquals(NULL, $callback->tree, 'Tree only set if entire doc parsed');
$this->assertEquals(4, sizeof($callback->elements));
$this->assertEquals('doc', $callback->elements[0]);
$this->assertEquals('h1', $callback->elements[1]);
$this->assertEquals('p', $callback->elements[2]);
$this->assertEquals('img', $callback->elements[3]);
}
}
/**
* Test a document without a root node
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function withoutRoot() {
$this->parser->parse($this->source(''));
}
/**
* Test an unclosed tag
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function unclosedTag() {
$this->parser->parse($this->source('<a>'));
}
/**
* Test an unclosed attribute
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function unclosedAttribute() {
$this->parser->parse($this->source('<a href="http://>Click</a>'));
}
/**
* Test an unclosed comment
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function unclosedComment() {
$this->parser->parse($this->source('<doc><!-- Comment</doc>'));
}
/**
* Test an incorrectly closed comment
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function incorrectlyClosedComment() {
$this->parser->parse($this->source('<doc><!-- Comment ></doc>'));
}
/**
* Test a malformed comment
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function malformedComment() {
$this->parser->parse($this->source('<doc><! Comment --></doc>'));
}
/**
* Test an unclosed PI ("processing instruction")
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function unclosedProcessingInstruction() {
$this->parser->parse($this->source('<doc><?php echo "1"; </doc>'));
}
/**
* Test attribute redefinition
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function attributeRedefinition() {
$this->parser->parse($this->source('<a id="1" id="2"/>'));
}
/**
* Test quotes inside attributes
*
*/
#[@test]
public function quotesInsideAttributes() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<n id="\'1\'" t=\'"_new"\' q="'""/>'));
$this->assertEquals(
array('n', array('id' => "'1'", 't' => '"_new"', 'q' => '\'"'), array()),
$callback->tree
);
}
/**
* Test unquoted greater sign inside an attribute
*
*/
#[@test]
public function greaterSignInAttribute() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<a id=">"/>'));
$this->assertEquals(
array('a', array('id' => '>'), array()),
$callback->tree
);
}
/**
* Test unquoted smaller sign inside an attribute
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function smallerSignInAttribute() {
$this->parser->parse($this->source('<a id="<"/>'));
}
/**
* Test CDATA
*
*/
#[@test]
public function cdataSection() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc>CDATA [<![CDATA[ <&> ]]>]</doc>
'));
$this->assertEquals(array(
'doc', array(), array(
'CDATA [', '<&>', ']'
)
), $callback->tree);
}
/**
* Test processing instructions
*
*/
#[@test]
public function processingInstruction() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<doc><?php echo "1"; ?></doc>'));
$this->assertEquals(array(
'doc', array(), array(
'<?php echo "1"; ?>'
)
), $callback->tree);
}
/**
* Test comments
*
*/
#[@test]
public function comment() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('<doc><!-- Comment --></doc>'));
$this->assertEquals(array(
'doc', array(), array(
'<!-- Comment -->'
)
), $callback->tree);
}
/**
* Test nested CDATA is not allowed
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function nestedCdata() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc><![CDATA[ <![CDATA[ ]]> ]]></doc>
'));
}
/**
* Test predefined entities
*
*/
#[@test]
public function predefinedEntities() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc>"3 < 5 '&' 5 > 3"</doc>
'));
$this->assertEquals(array(
'doc', array(), array(
'"', '3', '<', '5', "'", '&', "'", '5', '>', '3', '"'
)
), $callback->tree);
}
/**
* Test hex entity
*
*/
#[@test]
public function hexEntity() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc>© 2001-2009 the XP team</doc>
'));
$this->assertEquals(array(
'doc', array(), array(
'©', '2001-2009 the XP team'
)
), $callback->tree);
}
/**
* Test umlauts returned in iso-8859-1
*
*/
#[@test]
public function iso88591Conversion() {
$this->parser->setEncoding('iso-8859-1');
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc>The übercoder returns</doc>
'));
$this->assertEquals('iso-8859-1', $callback->encoding);
$this->assertEquals(array(
'doc', array(), array(
'The', 'übercoder returns'
)
), $callback->tree);
}
/**
* Test umlauts returned in UTF-8
*
*/
#[@test]
public function utf8Conversion() {
$this->parser->setEncoding('utf-8');
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<doc>The übercoder returns</doc>
'));
$this->assertEquals('utf-8', $callback->encoding);
$this->assertEquals(array(
'doc', array(), array(
'The', 'übercoder returns'
)
), $callback->tree);
}
/**
* Test undeclared entity
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function undeclaredEntity() {
$this->parser->parse($this->source('<doc> </doc>'));
}
/**
* Test undeclared entity inside an attribute
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function undeclaredEntityInAttribute() {
$this->parser->parse($this->source('<doc><a href=" "/></doc>'));
}
/**
* Test double root node
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function doubleRoot() {
$this->parser->parse($this->source('<doc/><doc/>'));
}
/**
* Test DOCTYPE section without content
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function docTypeWithoutContent() {
$this->parser->parse($this->source('<!DOCTYPE doc ]>'));
}
/**
* Test entity declared via ENTITY
*
*/
#[@test]
public function characterEntity() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [ <!ENTITY copy "©"> ]>
<doc>Copyright: ©</doc>
'));
$this->assertEquals(array(
'doc', array(), array(
'Copyright:', '©'
)
), $callback->tree);
}
/**
* Test entity declared via ENTITY
*
*/
#[@test]
public function entity() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [ <!ENTITY copyright "2009 The XP team"> ]>
<doc>Copyright: ©right;</doc>
'));
$this->assertEquals(array(
'doc', array(), array(
'Copyright:', '©right;'
)
), $callback->tree);
}
/**
* Test entity declared via ENTITY and used inside an attribute
*
*/
#[@test]
public function entityInAttribute() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [ <!ENTITY copyright "2009 The XP team"> ]>
<doc><book copyright="Copyright ©right;"/></doc>
'));
$this->assertEquals(array(
'doc', array(), array(
array('book', array('copyright' => 'Copyright 2009 The XP team'), array()),
)
), $callback->tree);
}
/**
* Test entity expansion
*
*/
#[@test]
public function entityExpansion() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [
<!ENTITY year "2009">
<!ENTITY copyright "&year; The XP team">
]>
<doc><book copyright="Copyright ©right;"/></doc>
'));
$this->assertEquals(array(
'doc', array(), array(
array('book', array('copyright' => 'Copyright 2009 The XP team'), array()),
)
), $callback->tree);
}
/**
* Test external entity declared via ENTITY
*
*/
#[@test]
public function externalEntity() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [ <!ENTITY copyright SYSTEM "http://xp-framework.net/copyright.txt" > ]>
<doc>Copyright: ©right;</doc>
'));
$this->assertEquals(array(
'doc', array(), array('Copyright:'),
), $callback->tree);
}
/**
* Test external entity declared via ENTITY and used inside an
* attribute raises a XML_ERR_ENTITY_IS_EXTERNAL
*
*/
#[@test, @expect('xml.XMLFormatException')]
public function externalEntityInAttribute() {
$callback= $this->newCallback();
$this->parser->setCallback($callback);
$this->parser->parse($this->source('
<!DOCTYPE doc [ <!ENTITY copyright SYSTEM "http://xp-framework.net/copyright.txt" > ]>
<doc><book copyright="Copyright ©right;"/></doc>
'));
}
}
?>