Skip to content

Commit

Permalink
SimpleXMLElementPlugin: Rewrite and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jnvsor committed Aug 23, 2024
1 parent ca55a57 commit 4b22ed8
Show file tree
Hide file tree
Showing 7 changed files with 562 additions and 96 deletions.
Binary file modified build/kint.phar
Binary file not shown.
2 changes: 2 additions & 0 deletions src/Parser/IteratorPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Kint\Zval\Value;
use mysqli_result;
use PDOStatement;
use SimpleXMLElement;
use SplFileObject;
use Traversable;

Expand All @@ -53,6 +54,7 @@ class IteratorPlugin extends AbstractPlugin
mysqli_result::class,
PDOStatement::class,
SplFileObject::class,
SimpleXMLElement::class,
];

public function getTypes(): array
Expand Down
198 changes: 110 additions & 88 deletions src/Parser/SimpleXMLElementPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<Value> $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) {
Expand All @@ -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:
//
// <root xmlns:localhost="http://localhost/">
// <tag />
// <tag xmlns="http://localhost/" />
// <localhost:tag />
// </root>
//
// * 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).'}';
Expand All @@ -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;
}
}
11 changes: 7 additions & 4 deletions src/Renderer/Rich/SimpleXMLElementPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/Renderer/TextRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
Expand Down
17 changes: 13 additions & 4 deletions src/Zval/SimpleXMLElementValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Loading

0 comments on commit 4b22ed8

Please sign in to comment.