From 4b22ed8250d3867b880d2e14eedbf421d8d19fa9 Mon Sep 17 00:00:00 2001 From: Jonathan Vollebregt Date: Thu, 22 Aug 2024 12:49:32 +0200 Subject: [PATCH] SimpleXMLElementPlugin: Rewrite and tests --- build/kint.phar | Bin 463468 -> 465101 bytes src/Parser/IteratorPlugin.php | 2 + src/Parser/SimpleXMLElementPlugin.php | 198 +++++---- src/Renderer/Rich/SimpleXMLElementPlugin.php | 11 +- src/Renderer/TextRenderer.php | 1 + src/Zval/SimpleXMLElementValue.php | 17 +- tests/Parser/SimpleXMLElementPluginTest.php | 429 +++++++++++++++++++ 7 files changed, 562 insertions(+), 96 deletions(-) create mode 100644 tests/Parser/SimpleXMLElementPluginTest.php diff --git a/build/kint.phar b/build/kint.phar index e69a8c07094c3360b2c543db44918e09a69a6465..3b9f0f31bdb91a1d794b083f3380f1f1b76f18dd 100644 GIT binary patch delta 3298 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 E58{EmcK`qY diff --git a/src/Parser/IteratorPlugin.php b/src/Parser/IteratorPlugin.php index fc8effa5d..688275a8a 100644 --- a/src/Parser/IteratorPlugin.php +++ b/src/Parser/IteratorPlugin.php @@ -33,6 +33,7 @@ use Kint\Zval\Value; use mysqli_result; use PDOStatement; +use SimpleXMLElement; use SplFileObject; use Traversable; @@ -53,6 +54,7 @@ class IteratorPlugin extends AbstractPlugin mysqli_result::class, PDOStatement::class, SplFileObject::class, + SimpleXMLElement::class, ]; public function getTypes(): array diff --git a/src/Parser/SimpleXMLElementPlugin.php b/src/Parser/SimpleXMLElementPlugin.php index de9871680..9f6b60455 100644 --- a/src/Parser/SimpleXMLElementPlugin.php +++ b/src/Parser/SimpleXMLElementPlugin.php @@ -27,7 +27,7 @@ namespace Kint\Parser; -use Kint\Zval\BlobValue; +use Kint\Utils; use Kint\Zval\Representation\Representation; use Kint\Zval\SimpleXMLElementValue; use Kint\Zval\Value; @@ -47,7 +47,7 @@ public function getTypes(): array public function getTriggers(): int { - return Parser::TRIGGER_SUCCESS; + return Parser::TRIGGER_SUCCESS | Parser::TRIGGER_DEPTH_LIMIT; } public function parse(&$var, Value &$o, int $trigger): void @@ -56,61 +56,95 @@ public function parse(&$var, Value &$o, int $trigger): void return; } + $x = new SimpleXMLElementValue($o->name, \get_class($var), \spl_object_hash($var), \spl_object_id($var)); + $x->transplant($o); + $x->size = null; + if (!self::$verbose) { - $o->removeRepresentation('properties'); - $o->removeRepresentation('iterator'); - $o->removeRepresentation('methods'); + $x->removeRepresentation('properties'); + $x->removeRepresentation('methods'); + $x->removeRepresentation('iterator'); + } + + /** + * @psalm-var null|array $x->value->contents + * Psalm bug #11052 + */ + if (\is_array($x->value->contents ?? null) && isset($x->value->contents[0])) { + $badattr = $x->value->contents[0]; + if ('@attributes' === $badattr->name && 'uninitialized' === $badattr->type) { + \array_shift($x->value->contents); + } } - // An invalid SimpleXMLElement can gum up the works with - // warnings if we call stuff children/attributes on it. - if (!$var) { - $o->size = null; + if ($trigger & Parser::TRIGGER_SUCCESS) { + if ($a = $this->getAttributeRepresentation($x, $var)) { + $x->addRepresentation($a, 0); + } - return; + if ($c = $this->getChildrenRepresentation($x, $var)) { + /** @psalm-var Value[] $c->contents */ + $x->size = \count($c->contents); + $x->addRepresentation($c, 0); + } } - $x = new SimpleXMLElementValue($o->name, \get_class($var), \spl_object_hash($var), \spl_object_id($var)); - $x->transplant($o); + // Even in DEPTH_LIMIT we need to check strings. With size === 0 we'll + // have no children or attributes so we can go ahead and replace Contents + if (!isset($c) && \strlen((string) $var)) { + $x->hints = \array_diff($x->hints, ['depth_limit']); - $namespaces = \array_merge([null], $var->getDocNamespaces()); + $base_obj = new Value($x->name); + $base_obj->depth = $x->depth + 1; + if (null !== $x->access_path) { + $x->access_path = '(string) '.$x->access_path; + $base_obj->access_path = $x->access_path; + } - // Attributes - $a = new Representation('Attributes'); - $a->contents = []; + $string = (string) $var; - $base_obj = new Value('base'); - $base_obj->depth = $x->depth; + $r = new Representation('toString'); + $r->implicit_label = true; + $r->contents = $this->getParser()->parse($string, $base_obj); - if (null !== $x->access_path) { - $base_obj->access_path = '(string) '.$x->access_path; + $x->addRepresentation($r, 0); } + $o = $x; + } + + protected function getAttributeRepresentation(SimpleXMLElementValue $element, SimpleXMLElement $var): ?Representation + { $parser = $this->getParser(); + $namespaces = \array_merge(['' => null], $var->getDocNamespaces()); - // Attributes are strings. If we're too deep set the - // depth limit to enable parsing them, but no deeper. - if ($parser->getDepthLimit() && $parser->getDepthLimit() - 2 < $base_obj->depth) { - $base_obj->depth = $parser->getDepthLimit() - 2; - } + // Attributes + $a = new Representation('Attributes'); + $a->contents = []; - $attribs = []; + $base_obj = new Value('base'); + $base_obj->access_path = $element->access_path; + $base_obj->depth = $element->depth; foreach ($namespaces as $nsAlias => $nsUrl) { - if ((bool) $nsAttribs = $var->attributes($nsUrl)) { + if ((bool) $nsAttribs = $var->attributes($nsAlias, true)) { $cleanAttribs = []; foreach ($nsAttribs as $name => $attrib) { $cleanAttribs[(string) $name] = $attrib; } - if (null === $nsUrl) { + if ('' === $nsAlias) { $obj = clone $base_obj; if (null !== $obj->access_path) { $obj->access_path .= '->attributes()'; } - $contents = $parser->parse($cleanAttribs, $obj)->value->contents ?? null; - $a->contents = \is_array($contents) ? $contents : []; + /** @psalm-var Value[] $cleanAttribs */ + $cleanAttribs = $parser->parse($cleanAttribs, $obj)->value->contents ?? []; + + foreach ($cleanAttribs as $attribute) { + $a->contents[] = $attribute; + } } else { $obj = clone $base_obj; if (null !== $obj->access_path) { @@ -129,34 +163,61 @@ public function parse(&$var, Value &$o, int $trigger): void } if ($a->contents) { - $x->addRepresentation($a, 0); + return $a; } + return null; + } + + protected function getChildrenRepresentation(SimpleXMLElementValue $element, SimpleXMLElement $var): ?Representation + { + $parser = $this->getParser(); + $namespaces = \array_merge(['' => null], $var->getDocNamespaces()); + // Children $c = new Representation('Children'); $c->contents = []; + // Alright kids, let's learn about SimpleXMLElement::children! + // children can take a namespace url or alias and provide a list of + // child nodes. This is great since just accessing the members through + // properties doesn't work on SimpleXMLElement when they have a + // namespace at all! + // + // Unfortunately SimpleXML decided to go the retarded route of + // categorizing elements by their tag name rather than by their local + // name (to put it in Dom terms) so if you have something like this: + // + // + // + // + // + // + // + // * children(null) will get the first 2 results + // * children('', true) will get the first 2 results + // * children('http://localhost/') will get the last 2 results + // * children('localhost', true) will get the last result + // + // So let's just give up and stick to aliases because fuck that mess! foreach ($namespaces as $nsAlias => $nsUrl) { - // This is doubling items because of the root namespace - // and the implicit namespace on its children. - $thisNs = $var->getNamespaces(); - if (isset($thisNs['']) && $thisNs[''] === $nsUrl) { - continue; - } - - if ((bool) $nsChildren = $var->children($nsUrl)) { + if ((bool) $nsChildren = $var->children($nsAlias, true)) { $nsap = []; foreach ($nsChildren as $name => $child) { $obj = new Value((string) $name); - $obj->depth = $x->depth + 1; - if (null !== $x->access_path) { - if (null === $nsUrl) { - $obj->access_path = $x->access_path.'->children()->'; + $obj->depth = $element->depth + 1; + if ('' !== $nsAlias) { + $obj->name = $nsAlias.':'.$name; + } + + if (null !== $element->access_path) { + if ('' === $nsAlias) { + $obj->access_path = $element->access_path.'->children()->'; } else { - $obj->access_path = $x->access_path.'->children('.\var_export($nsAlias, true).', true)->'; + $obj->access_path = $element->access_path.'->children('.\var_export($nsAlias, true).', true)->'; } - if (\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]+$/', (string) $name)) { + if (Utils::isValidPhpName((string) $name)) { $obj->access_path .= (string) $name; } else { $obj->access_path .= '{'.\var_export((string) $name, true).'}'; @@ -170,54 +231,15 @@ public function parse(&$var, Value &$o, int $trigger): void } } - $value = $parser->parse($child, $obj); - - if (null !== $value->access_path && 'string' === $value->type) { - $value->access_path = '(string) '.$value->access_path; - } - - $c->contents[] = $value; + $c->contents[] = $parser->parse($child, $obj); } } } - $x->size = \count($c->contents); - - if ($x->size) { - $x->addRepresentation($c, 0); - } else { - $x->size = null; - - if (\strlen((string) $var)) { - $base_obj = new BlobValue($x->name); - $base_obj->depth = $x->depth + 1; - if (null !== $x->access_path) { - $base_obj->access_path = '(string) '.$x->access_path; - } - - $value = (string) $var; - - $s = $parser->parse($value, $base_obj); - $srep = $s->getRepresentation('contents'); - $svalrep = $s->value && 'contents' == $s->value->getName() ? $s->value : null; - - if ($srep || $svalrep) { - $x->is_string_value = true; - $x->value = $srep ?: $svalrep; - - if ($srep) { - $x->replaceRepresentation($srep, 0); - } - } - - $reps = \array_reverse($s->getRepresentations()); - - foreach ($reps as $rep) { - $x->addRepresentation($rep, 0); - } - } + if ($c->contents) { + return $c; } - $o = $x; + return null; } } diff --git a/src/Renderer/Rich/SimpleXMLElementPlugin.php b/src/Renderer/Rich/SimpleXMLElementPlugin.php index e1dbbee74..5f1741eff 100644 --- a/src/Renderer/Rich/SimpleXMLElementPlugin.php +++ b/src/Renderer/Rich/SimpleXMLElementPlugin.php @@ -39,13 +39,16 @@ public function renderValue(Value $o): ?string return null; } - if (!$o->is_string_value || (bool) ($o->getRepresentation('attributes')->contents ?? false)) { + $r = $o->getRepresentation('tostring'); + + if (!isset($r->contents) || !$r->contents instanceof BlobValue) { return null; } - $b = new BlobValue($o->name); - $b->transplant($o); - $b->type = 'string'; + $b = clone $r->contents; + if ($r = $o->getRepresentation('attributes')) { + $b->addRepresentation($r, 1); + } $children = $this->renderer->renderChildren($b); $header = $this->renderer->renderHeader($o); diff --git a/src/Renderer/TextRenderer.php b/src/Renderer/TextRenderer.php index b101fada0..2c700b980 100644 --- a/src/Renderer/TextRenderer.php +++ b/src/Renderer/TextRenderer.php @@ -70,6 +70,7 @@ class TextRenderer extends AbstractRenderer Parser\EnumPlugin::class, Parser\IteratorPlugin::class, Parser\MicrotimePlugin::class, + Parser\SimpleXMLElementPlugin::class, Parser\StreamPlugin::class, Parser\TracePlugin::class, ]; diff --git a/src/Zval/SimpleXMLElementValue.php b/src/Zval/SimpleXMLElementValue.php index 650dbb831..10ed9e978 100644 --- a/src/Zval/SimpleXMLElementValue.php +++ b/src/Zval/SimpleXMLElementValue.php @@ -31,14 +31,23 @@ class SimpleXMLElementValue extends InstanceValue { public array $hints = ['object', 'simplexml_element']; - public bool $is_string_value = false; - public function getValueShort(): ?string { - if ($this->is_string_value && ($rep = $this->value) && 'contents' === $rep->getName() && 'string' === \gettype($rep->contents)) { - return '"'.$rep->contents.'"'; + if ($r = $this->getRepresentation('tostring')) { + /** @psalm-var object{contents: BlobValue, ...} $r */ + return $r->contents->getValueShort(); } return null; } + + public function getSize(): ?string + { + if ($r = $this->getRepresentation('tostring')) { + /** @psalm-var object{contents: BlobValue, ...} $r */ + return $r->contents->getSize(); + } + + return parent::getSize(); + } } diff --git a/tests/Parser/SimpleXMLElementPluginTest.php b/tests/Parser/SimpleXMLElementPluginTest.php new file mode 100644 index 000000000..922f198a7 --- /dev/null +++ b/tests/Parser/SimpleXMLElementPluginTest.php @@ -0,0 +1,429 @@ + + + + Text + + + String value + + String element + + + And string + + END; + + public const TEST_XML_NS = << + + + Text + + + String value + String element + And string + + END; + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::parse + * @covers \Kint\Parser\SimpleXMLElementPlugin::getAttributeRepresentation + * @covers \Kint\Parser\SimpleXMLElementPlugin::getChildrenRepresentation + */ + public function testParse() + { + $p = new Parser(); + $p->addPlugin(new ClassMethodsPlugin($p)); + $p->addPlugin(new SimpleXMLElementPlugin($p)); + $p->addPlugin(new IteratorPlugin($p)); // Just to make sure + + $v = \simplexml_load_string(self::TEST_XML); + $b = new Value('$v'); + $b->access_path = '$v'; + + SimpleXMLElementPlugin::$verbose = false; + $o = $p->parse($v, clone $b); + + $this->assertInstanceOf(SimpleXMLElementValue::class, $o); + $this->assertSame(SimpleXMLElement::class, $o->classname); + + // Properties + $this->assertSame('$v', $o->name); + $this->assertNull($o->getRepresentation('tostring')); + $this->assertSame(4, $o->size); + $this->assertSame('@attributes', $o->value->contents[0]->name); + $this->assertSame('viewBox', $o->value->contents[0]->value->contents[0]->name); + $this->assertSame('0 0 30 150', $o->value->contents[0]->value->contents[0]->value->contents); + $this->assertSame('$v->{\'@attributes\'}[\'viewBox\']', $o->value->contents[0]->value->contents[0]->access_path); + $this->assertSame(1, $o->value->contents[0]->size); + + $g = $o->value->contents[1]; + $this->assertSame('g', $g->name); + $this->assertSame('array', $g->type); + $this->assertSame('$v->g', $g->access_path); + $this->assertSame(2, $g->size); + + $attrib = $g->value->contents[0]->value->contents[0]; + $this->assertSame('@attributes', $attrib->name); + $this->assertSame('fill', $attrib->value->contents[1]->name); + $this->assertSame('$v->g[0]->{\'@attributes\'}[\'fill\']', $attrib->value->contents[1]->access_path); + $this->assertSame(2, $attrib->size); + + $inner = $g->value->contents[0]->value->contents[1]; + $this->assertSame('inner', $inner->name); + // Note that this is incorrect, and the access path will actually give a + // SimpleXMLElement, but even var_dump does this wrong because of the + // custom code around casting them to arrays so idc + $this->assertInstanceOf(BlobValue::class, $inner); + $this->assertSame('Text', $inner->value->contents); + $this->assertSame(4, $inner->size); + + $this->assertEmpty($g->value->contents[1]->value->contents); + $this->assertSame('$v->g[1]', $g->value->contents[1]->access_path); + $this->assertNull($g->value->contents[1]->size); + + $wrap = $o->value->contents[2]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $wrap); + $this->assertSame('wrap', $wrap->name); + $this->assertSame('$v->wrap', $wrap->access_path); + $this->assertSame(2, $wrap->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $wrap->value->contents[0]); + $this->assertSame('wrap', $wrap->value->contents[0]->name); + $this->assertSame('$v->wrap->wrap', $wrap->value->contents[0]->access_path); + $this->assertSame(1, $wrap->value->contents[0]->size); + $this->assertInstanceOf(BlobValue::class, $wrap->value->contents[0]->value->contents[0]); + $this->assertSame('text', $wrap->value->contents[0]->value->contents[0]->name); + $this->assertSame('String element', $wrap->value->contents[0]->value->contents[0]->value->contents); + $this->assertSame('$v->wrap->wrap->text', $wrap->value->contents[0]->value->contents[0]->access_path); + $this->assertSame(14, $wrap->value->contents[0]->value->contents[0]->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $wrap->value->contents[1]); + $this->assertSame('not-php-compatible', $wrap->value->contents[1]->name); + $this->assertSame('$v->wrap->{\'not-php-compatible\'}', $wrap->value->contents[1]->access_path); + $this->assertNull($wrap->value->contents[1]->size); + + $both = $o->value->contents[3]; + $this->assertSame('both', $both->name); + $this->assertSame('$v->both', $both->access_path); + $this->assertSame('string', $both->type); + $this->assertSame(10, $both->size); + + // Children + $r = $o->getRepresentation('children'); + $g1child = $r->contents[0]; + $this->assertInstanceOf(Representation::class, $r); + $this->assertCount(4, $r->contents); + $this->assertSame('g', $r->contents[0]->name); + $this->assertSame('$v->children()->g', $r->contents[0]->access_path); + $this->assertSame(1, $r->contents[0]->size); + $this->assertSame('g', $r->contents[1]->name); + $this->assertSame('$v->children()->g[1]', $r->contents[1]->access_path); + $this->assertNull($r->contents[1]->size); + $this->assertSame('wrap', $r->contents[2]->name); + $this->assertSame('$v->children()->wrap', $r->contents[2]->access_path); + $this->assertNull($r->contents[2]->getRepresentation('tostring')); + $this->assertSame(2, $r->contents[2]->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[2]); + + $incomp = $r->contents[2]->getRepresentation('children')->contents[1]; + $this->assertSame('not-php-compatible', $incomp->name); + $this->assertSame('$v->children()->wrap->children()->{\'not-php-compatible\'}', $incomp->access_path); + $this->assertNull($incomp->getRepresentation('tostring')); + $this->assertNull($incomp->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $incomp); + + $text = $r->contents[2] + ->getRepresentation('children')->contents[0] + ->getRepresentation('children')->contents[0]; + $this->assertSame('text', $text->name); + $this->assertSame('(string) $v->children()->wrap->children()->wrap->children()->text', $text->access_path); + $this->assertInstanceOf(Representation::class, $text->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $text->getRepresentation('tostring')->contents); + $this->assertNull($text->size); + $this->assertSame('14', $text->getSize()); + $this->assertSame(14, $text->getRepresentation('tostring')->contents->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $text); + $both = $r->contents[3]; + $this->assertSame('both', $both->name); + $this->assertSame('(string) $v->children()->both', $both->access_path); + $this->assertInstanceOf(Representation::class, $both->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $both->getRepresentation('tostring')->contents); + $this->assertNull($both->size); + $this->assertSame('10', $both->getSize()); + $this->assertSame(10, $both->getRepresentation('tostring')->contents->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $both); + + // Attributes + $r = $o->value->contents[1]->value->contents[0]->getRepresentation('attributes'); + $this->assertInstanceOf(Representation::class, $r); + $this->assertCount(2, $r->contents); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[0]); + $this->assertSame('stroke-width', $r->contents[0]->name); + $this->assertSame('(string) $v->g[0]->attributes()[\'stroke-width\']', $r->contents[0]->access_path); + $this->assertSame('2', $r->contents[0]->getRepresentation('tostring')->contents->value->contents); + $this->assertInstanceOf(Representation::class, $r->contents[0]->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $r->contents[0]->getRepresentation('tostring')->contents); + $this->assertNull($r->contents[0]->size); + $this->assertSame('1', $r->contents[0]->getSize()); + $this->assertSame(1, $r->contents[0]->getRepresentation('tostring')->contents->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[1]); + $this->assertSame('fill', $r->contents[1]->name); + $this->assertSame('(string) $v->g[0]->attributes()[\'fill\']', $r->contents[1]->access_path); + $this->assertSame('#FFF', $r->contents[1]->getRepresentation('tostring')->contents->value->contents); + $this->assertInstanceOf(Representation::class, $r->contents[1]->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $r->contents[1]->getRepresentation('tostring')->contents); + $this->assertNull($r->contents[1]->size); + $this->assertSame('4', $r->contents[1]->getSize()); + $this->assertSame(4, $r->contents[1]->getRepresentation('tostring')->contents->size); + + $r = $g1child->getRepresentation('attributes'); + $this->assertInstanceOf(Representation::class, $r); + $this->assertCount(2, $r->contents); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[0]); + $this->assertSame('stroke-width', $r->contents[0]->name); + $this->assertSame('(string) $v->children()->g->attributes()[\'stroke-width\']', $r->contents[0]->access_path); + $this->assertSame('2', $r->contents[0]->getRepresentation('tostring')->contents->value->contents); + $this->assertInstanceOf(Representation::class, $r->contents[0]->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $r->contents[0]->getRepresentation('tostring')->contents); + $this->assertNull($r->contents[0]->size); + $this->assertSame('1', $r->contents[0]->getSize()); + $this->assertSame(1, $r->contents[0]->getRepresentation('tostring')->contents->size); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[1]); + $this->assertSame('fill', $r->contents[1]->name); + $this->assertSame('(string) $v->children()->g->attributes()[\'fill\']', $r->contents[1]->access_path); + $this->assertSame('#FFF', $r->contents[1]->getRepresentation('tostring')->contents->value->contents); + $this->assertInstanceOf(Representation::class, $r->contents[1]->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $r->contents[1]->getRepresentation('tostring')->contents); + $this->assertNull($r->contents[1]->size); + $this->assertSame('4', $r->contents[1]->getSize()); + $this->assertSame(4, $r->contents[1]->getRepresentation('tostring')->contents->size); + + $r = $both->getRepresentation('attributes'); + $this->assertInstanceOf(Representation::class, $r); + $this->assertCount(1, $r->contents); + $this->assertInstanceOf(SimpleXMLElementValue::class, $r->contents[0]); + $this->assertSame('attribs', $r->contents[0]->name); + $this->assertSame('(string) $v->children()->both->attributes()[\'attribs\']', $r->contents[0]->access_path); + $this->assertSame('exist', $r->contents[0]->getRepresentation('tostring')->contents->value->contents); + $this->assertInstanceOf(Representation::class, $r->contents[0]->getRepresentation('tostring')); + $this->assertInstanceOf(BlobValue::class, $r->contents[0]->getRepresentation('tostring')->contents); + $this->assertNull($r->contents[0]->size); + $this->assertSame('5', $r->contents[0]->getSize()); + $this->assertSame(5, $r->contents[0]->getRepresentation('tostring')->contents->size); + } + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::parse + * @covers \Kint\Parser\SimpleXMLElementPlugin::getAttributeRepresentation + */ + public function testParseDepthLimit() + { + $p = new Parser(); + $p->addPlugin(new ClassMethodsPlugin($p)); + $p->addPlugin(new SimpleXMLElementPlugin($p)); + $p->addPlugin(new IteratorPlugin($p)); // Just to make sure + + $v = \simplexml_load_string(self::TEST_XML); + $b = new Value('$v'); + $b->access_path = '$v'; + + SimpleXMLElementPlugin::$verbose = false; + + $o = $p->parse($v, clone $b); + + $g1 = $o->getRepresentation('children')->contents[0]; + $inner = $g1->getRepresentation('children')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $inner); + $this->assertSame('inner', $inner->name); + $this->assertSame(2, $inner->depth); + $this->assertNotContains('depth_limit', $inner->hints); + $this->assertInstanceOf(Representation::class, $inner->getRepresentation('tostring')); + + $attr = $g1->getRepresentation('attributes')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $attr); + $this->assertSame('stroke-width', $attr->name); + $this->assertSame(2, $attr->depth); + $this->assertNotContains('depth_limit', $attr->hints); + $this->assertInstanceOf(Representation::class, $attr->getRepresentation('tostring')); + + $wrap = $o + ->getRepresentation('children')->contents[2] + ->getRepresentation('children')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $wrap); + $this->assertSame('wrap', $wrap->name); + $this->assertSame(2, $wrap->depth); + $this->assertNotContains('depth_limit', $wrap->hints); + $this->assertNull($wrap->getRepresentation('tostring')); + + $p->setDepthLimit(2); + $o = $p->parse($v, clone $b); + + $g1 = $o->getRepresentation('children')->contents[0]; + $inner = $g1->getRepresentation('children')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $inner); + $this->assertSame('inner', $inner->name); + $this->assertSame(2, $inner->depth); + $this->assertNotContains('depth_limit', $inner->hints); + $this->assertInstanceOf(Representation::class, $inner->getRepresentation('tostring')); + + $attr = $g1->getRepresentation('attributes')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $attr); + $this->assertSame('stroke-width', $attr->name); + $this->assertSame(2, $attr->depth); + $this->assertNotContains('depth_limit', $attr->hints); + $this->assertInstanceOf(Representation::class, $attr->getRepresentation('tostring')); + + $wrap = $o + ->getRepresentation('children')->contents[2] + ->getRepresentation('children')->contents[0]; + $this->assertInstanceOf(SimpleXMLElementValue::class, $wrap); + $this->assertSame('wrap', $wrap->name); + $this->assertSame(2, $wrap->depth); + $this->assertContains('depth_limit', $wrap->hints); + $this->assertNull($wrap->getRepresentation('tostring')); + } + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::parse + * @covers \Kint\Parser\SimpleXMLElementPlugin::getAttributeRepresentation + * @covers \Kint\Parser\SimpleXMLElementPlugin::getChildrenRepresentation + */ + public function testParseNamespaces() + { + $p = new Parser(); + $p->addPlugin(new ClassMethodsPlugin($p)); + $p->addPlugin(new SimpleXMLElementPlugin($p)); + $p->addPlugin(new IteratorPlugin($p)); // Just to make sure + + $v = \simplexml_load_string(self::TEST_XML_NS); + $b = new Value('$v'); + $b->access_path = '$v'; + + $o = $p->parse($v, clone $b); + + $r = $o->getRepresentation('children'); + $this->assertSame('g', $r->contents[0]->name); + $this->assertSame('$v->children()->g', $r->contents[0]->access_path); + $this->assertSame('wrap', $r->contents[1]->name); + $this->assertSame('$v->children()->wrap', $r->contents[1]->access_path); + $this->assertSame('both', $r->contents[2]->name); + $this->assertSame('(string) $v->children()->both', $r->contents[2]->access_path); + $this->assertSame('test:g', $r->contents[3]->name); + $this->assertSame('$v->children(\'test\', true)->g', $r->contents[3]->access_path); + + $inner = $r->contents[3]->getRepresentation('children')->contents[0]; + $this->assertSame('inner', $inner->name); + $this->assertSame('(string) $v->children(\'test\', true)->g->children()->inner', $inner->access_path); + + $attribs = $r->contents[2]->getRepresentation('attributes')->contents; + $this->assertSame('attribs', $attribs[0]->name); + $this->assertSame('(string) $v->children()->both->attributes()[\'attribs\']', $attribs[0]->access_path); + $this->assertSame('base', $attribs[0]->getRepresentation('tostring')->contents->value->contents); + $this->assertSame('test:attribs', $attribs[1]->name); + $this->assertSame('(string) $v->children()->both->attributes(\'test\', true)[\'attribs\']', $attribs[1]->access_path); + $this->assertSame('exists', $attribs[1]->getRepresentation('tostring')->contents->value->contents); + } + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::parse + */ + public function testParseVerbose() + { + $p = new Parser(); + $p->addPlugin(new ClassMethodsPlugin($p)); + $p->addPlugin(new SimpleXMLElementPlugin($p)); + $p->addPlugin(new IteratorPlugin($p)); // Just to make sure + + $v = \simplexml_load_string(self::TEST_XML); + $b = new Value('$v'); + $b->access_path = '$v'; + + SimpleXMLElementPlugin::$verbose = true; + $o = $p->parse($v, clone $b); + + $this->assertNotNull($o->getRepresentation('properties')); + $this->assertNotNull($o->getRepresentation('methods')); + $this->assertNotNull($o->getRepresentation('iterator')); + $this->assertCount(1, $o->getRepresentation('iterator')->contents); + $this->assertContains('blacklist', $o->getRepresentation('iterator')->contents[0]->hints); + } + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::parse + */ + public function testParseUnrelated() + { + $p = new Parser(); + $p->addPlugin(new ClassMethodsPlugin($p)); + $p->addPlugin(new SimpleXMLElementPlugin($p)); + $p->addPlugin(new IteratorPlugin($p)); // Just to make sure + + $v = new stdClass(); + $b = new Value('$v'); + $b->access_path = '$v'; + + SimpleXMLElementPlugin::$verbose = true; + $o = $p->parse($v, clone $b); + + $this->assertNotInstanceOf(SimpleXMLElementValue::class, $o); + } + + /** + * @covers \Kint\Parser\SimpleXMLElementPlugin::getTriggers + * @covers \Kint\Parser\SimpleXMLElementPlugin::getTypes + */ + public function testHooks() + { + $p = new SimpleXMLElementPlugin($this->createStub(Parser::class)); + + $this->assertSame(['object'], $p->getTypes()); + $this->assertSame(Parser::TRIGGER_SUCCESS | Parser::TRIGGER_DEPTH_LIMIT, $p->getTriggers()); + } +}