From c3df371998e33c5625f85a698e4a82807804aecf Mon Sep 17 00:00:00 2001 From: nikic Date: Sat, 3 Dec 2011 15:15:20 +0100 Subject: [PATCH] Cover, fix and cleanup XML unserializer --- lib/PHPParser/NodeVisitorAbstract.php | 3 + lib/PHPParser/Unserializer/XML.php | 146 +++++++++------- test/PHPParser/Tests/Unserializer/XMLTest.php | 162 +++++++++--------- 3 files changed, 169 insertions(+), 142 deletions(-) diff --git a/lib/PHPParser/NodeVisitorAbstract.php b/lib/PHPParser/NodeVisitorAbstract.php index d7724f4..75ae698 100644 --- a/lib/PHPParser/NodeVisitorAbstract.php +++ b/lib/PHPParser/NodeVisitorAbstract.php @@ -1,5 +1,8 @@ read(); + return $this->read($this->reader->depth); } - protected function read() { - $depth = $this->reader->depth; - while ($this->reader->read() && $depth <= $this->reader->depth) { + protected function read($depthLimit, $throw = true, &$nodeFound = null) { + $nodeFound = true; + while ($this->reader->read() && $depthLimit < $this->reader->depth) { if (XMLReader::ELEMENT !== $this->reader->nodeType) { continue; } if ('node' === $this->reader->prefix) { - $className = 'PHPParser_Node_' . $this->reader->localName; - - // create the node without calling it's constructor - $node = unserialize( - sprintf('O:%d:"%s":0:{}', strlen($className), $className) - ); - - $line = $this->reader->getAttribute('line'); - $node->setLine(null !== $line ? $line : -1); - - $docComment = $this->reader->getAttribute('docComment'); - $node->setDocComment($docComment); - - $depth2 = $this->reader->depth; - while ($this->reader->read() && $depth2 < $this->reader->depth) { - if (XMLReader::ELEMENT !== $this->reader->nodeType) { - continue; - } - - if ('subNode' !== $this->reader->prefix) { - throw new Exception('Expected sub node'); - } - - $subNodeName = $this->reader->localName; - $subNodeContent = $this->read(); - - $node->$subNodeName = $subNodeContent; - } - - return $node; + return $this->readNode(); } elseif ('scalar' === $this->reader->prefix) { - if ('array' === $this->reader->localName) { - $array = array(); - while ($node = $this->read()) { - $array[] = $node; - } - return $array; - } elseif ('string' === $this->reader->localName) { - return $this->readText(); - } elseif ('int' === $this->reader->localName) { - return (int) $this->readText(); - } elseif ('float' === $this->reader->localName) { - return (float) $this->readText(); - } elseif ('false' === $this->reader->localName - || 'true' === $this->reader->localName - || 'null' === $this->reader->localName - ) { - if ($this->reader->hasValue) { - throw new Exception('false, true and null nodes cannot have a value'); - } - - return constant($this->reader->localName); - } else { - throw new Exception('Unexpected scalar type'); - } + return $this->readScalar(); } else { - throw new Exception('Unexpected node type'); + throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name)); } } + + $nodeFound = false; + if ($throw) { + throw new DomainException('Expected node or scalar'); + } } - protected function readText() { - if (!$this->reader->read() || XMLReader::TEXT !== $this->reader->nodeType) { - throw new Exception('Expected text node'); + protected function readNode() + { + $className = 'PHPParser_Node_' . $this->reader->localName; + + // create the node without calling it's constructor + $node = unserialize( + sprintf('O:%d:"%s":0:{}', strlen($className), $className) + ); + + $line = $this->reader->getAttribute('line'); + $node->setLine(null !== $line ? $line : -1); + + $docComment = $this->reader->getAttribute('docComment'); + $node->setDocComment($docComment); + + $depthLimit = $this->reader->depth; + while ($this->reader->read() && $depthLimit < $this->reader->depth) { + if (XMLReader::ELEMENT !== $this->reader->nodeType) { + continue; + } + + if ('subNode' !== $this->reader->prefix) { + throw new DomainException( + sprintf('Expected sub node, got node of type "%s"', $this->reader->name) + ); + } + + $subNodeName = $this->reader->localName; + $subNodeContent = $this->read($this->reader->depth); + + $node->$subNodeName = $subNodeContent; } - return $this->reader->value; + return $node; + } + + protected function readScalar() { + switch ($name = $this->reader->localName) { + case 'array': + $depth = $this->reader->depth; + $array = array(); + while (true) { + $node = $this->read($depth, false, $nodeFound); + if (!$nodeFound) { + break; + } + $array[] = $node; + } + return $array; + case 'string': + return $this->reader->readString(); + case 'int': + $text = $this->reader->readString(); + if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) { + throw new DomainException(sprintf('"%s" is not a valid integer', $text)); + } + return $int; + case 'float': + $text = $this->reader->readString(); + if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) { + throw new DomainException(sprintf('"%s" is not a valid float', $text)); + } + return $float; + case 'true': + case 'false': + case 'null': + if (!$this->reader->isEmptyElement) { + throw new DomainException(sprintf('"%s" scalar must be empty', $name)); + } + return constant($name); + default: + throw new DomainException(sprintf('Unknown scalar type "%s"', $name)); + } } } diff --git a/test/PHPParser/Tests/Unserializer/XMLTest.php b/test/PHPParser/Tests/Unserializer/XMLTest.php index ac92e5f..40ebc2b 100644 --- a/test/PHPParser/Tests/Unserializer/XMLTest.php +++ b/test/PHPParser/Tests/Unserializer/XMLTest.php @@ -2,93 +2,54 @@ class PHPParser_Tests_Unserializer_XMLTest extends PHPUnit_Framework_TestCase { - /** - * @covers PHPParser_Unserializer_XML - */ - public function testUnserialize() { + public function testNode() { $xml = << - - - - - - - - - - a - - - - - 0 - - - - - - - - - - - - - b - - - - - 1 - - - - - - - - - - - - - - - - - - - - Foo - - - - - - - - - functionName - - - + + + Test + + XML; - $code = <<<'CODE' -/** doc comment */ -function functionName(&$a = 0, $b = 1.0) -{ - echo 'Foo'; -} -CODE; $unserializer = new PHPParser_Unserializer_XML; - $prettyPrinter = new PHPParser_PrettyPrinter_Zend; + $this->assertEquals( + new PHPParser_Node_Scalar_String('Test', 1, '/** doc comment */'), + $unserializer->unserialize($xml) + ); + } - $stmts = $unserializer->unserialize($xml); - $this->assertEquals($code, $prettyPrinter->prettyPrint($stmts), '', 0, 10, true); + public function testScalars() { + $xml = << + + + + + test + + + 1 + 1 + 1.5 + + + + + +XML; + $result = array( + array(), array(), + 'test', '', '', + 1, + 1, 1.5, + true, false, null + ); + + $unserializer = new PHPParser_Unserializer_XML; + $this->assertEquals($result, $unserializer->unserialize($xml)); } /** @@ -104,4 +65,47 @@ XML; $unserializer = new PHPParser_Unserializer_XML; $unserializer->unserialize($xml); } + + /** + * @dataProvider provideTestErrors + * @expectedException DomainException + * @expectedExceptionMessage false, true and null scalars must be empty elements + */ + public function testErrors($xml, $errorMsg) { + $this->setExpectedException('DomainException', $errorMsg); + + $xml = << + + $xml + +XML; + + $unserializer = new PHPParser_Unserializer_XML; + $unserializer->unserialize($xml); + } + + public function provideTestErrors() { + return array( + array('test', '"true" scalar must be empty'), + array('test', '"false" scalar must be empty'), + array('test', '"null" scalar must be empty'), + array('bar', 'Unknown scalar type "foo"'), + array('x', '"x" is not a valid int'), + array('x', '"x" is not a valid float'), + array('', 'Expected node or scalar'), + array('test', 'Unexpected node of type "foo:bar"'), + array( + 'test', + 'Expected sub node, got node of type "foo:bar"' + ), + array( + '', + 'Expected node or scalar' + ), + ); + } } \ No newline at end of file