From 3b81ffdd7e6f0fea71d90824ccb0d8020643f8a6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 19 Oct 2021 18:06:05 +0200 Subject: [PATCH] encoding moved to Node::toString() methods --- src/Neon/Encoder.php | 114 ++++++++++-------------------- src/Neon/Node.php | 3 + src/Neon/Node/ArrayItemNode.php | 40 +++++++++++ src/Neon/Node/ArrayNode.php | 16 +++++ src/Neon/Node/EntityChainNode.php | 6 ++ src/Neon/Node/EntityNode.php | 9 +++ src/Neon/Node/LiteralNode.php | 16 +++++ src/Neon/Node/StringNode.php | 17 +++++ 8 files changed, 143 insertions(+), 78 deletions(-) diff --git a/src/Neon/Encoder.php b/src/Neon/Encoder.php index 3d4466d9..be6fefa1 100644 --- a/src/Neon/Encoder.php +++ b/src/Neon/Encoder.php @@ -22,101 +22,59 @@ final class Encoder /** * Returns the NEON representation of a value. */ - public function encode($var, int $flags = 0): string + public function encode($val, int $flags = 0): string { - if ($var instanceof \DateTimeInterface) { - return $var->format('Y-m-d H:i:s O'); - - } elseif ($var instanceof Entity) { - return $this->encodeEntity($var); - - } elseif (is_object($var)) { - return $this->encodeObject($var, $flags); - - } elseif (is_array($var)) { - return $this->encodeArray($var, $flags); - - } elseif (is_string($var)) { - return $this->encodeString($var); - - } else { - return $this->encodeScalar($var); - } + $node = $this->valueToNode($val, (bool) ($flags & self::BLOCK)); + return $node->toString(); } - private function encodeEntity(Entity $var): string + public function valueToNode($val, bool $blockMode = false): Node { - if ($var->value === Neon::CHAIN) { - return implode('', array_map([$this, 'encode'], $var->attributes)); - } - return $this->encode($var->value) . '(' - . (is_array($var->attributes) ? substr($this->encode($var->attributes), 1, -1) : '') . ')'; - } + if ($val instanceof \DateTimeInterface) { + return new Node\LiteralNode($val); + } elseif ($val instanceof Entity && $val->value === Neon::CHAIN) { + $node = new Node\EntityChainNode; + foreach ($val->attributes as $entity) { + $node->chain[] = $this->valueToNode($entity, $blockMode); + } + return $node; - private function encodeObject($obj, int $flags): string - { - $var = []; - foreach ($obj as $k => $v) { - $var[$k] = $v; - } - return $this->encodeArray($var, $flags); - } + } elseif ($val instanceof Entity) { + return new Node\EntityNode( + $this->valueToNode($val->value), + $this->arrayToNodes((array) $val->attributes) + ); + } elseif (is_object($val) || is_array($val)) { + $node = new Node\ArrayNode($blockMode ? '' : null); + $node->items = $this->arrayToNodes($val, $blockMode); + return $node; - private function encodeArray(array $var, int $flags): string - { - $isList = !$var || array_keys($var) === range(0, count($var) - 1); - $s = ''; - if ($flags & self::BLOCK) { - if (count($var) === 0) { - return '[]'; - } - foreach ($var as $k => $v) { - $v = $this->encode($v, self::BLOCK); - $s .= ($isList ? '-' : $this->encode($k) . ':') - . (strpos($v, "\n") === false - ? ' ' . $v . "\n" - : "\n" . preg_replace('#^(?=.)#m', "\t", $v) . (substr($v, -2, 1) === "\n" ? '' : "\n")); - } - return $s; + } elseif (is_string($val) && Lexer::requiresDelimiters($val)) { + return new Node\StringNode($val); } else { - foreach ($var as $k => $v) { - $s .= ($isList ? '' : $this->encode($k) . ': ') . $this->encode($v) . ', '; - } - return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}'); + return new Node\LiteralNode($val); } } - private function encodeString(string $var): string + private function arrayToNodes($val, bool $blockMode = false): array { - if (!Lexer::requiresDelimiters($var)) { - return $var; - } - - $res = json_encode($var, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - if ($res === false) { - throw new Exception('Invalid UTF-8 sequence: ' . $var); - } - if (strpos($var, "\n") !== false) { - $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { - return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; - }, $res); - $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; + $res = []; + $counter = 0; + $hide = true; + foreach ($val as $k => $v) { + $res[] = $item = new Node\ArrayItemNode; + $item->key = $hide && $k === $counter ? null : self::valueToNode($k); + $item->value = self::valueToNode($v, $blockMode); + if ($hide && is_int($k)) { + $hide = $k === $counter; + $counter = max($k + 1, $counter); + } } return $res; } - - - private function encodeScalar($var): string - { - if (is_float($var)) { - $var = json_encode($var); - return strpos($var, '.') === false ? $var . '.0' : $var; - } - return json_encode($var); - } } diff --git a/src/Neon/Node.php b/src/Neon/Node.php index 8bbd2253..b047a8c3 100644 --- a/src/Neon/Node.php +++ b/src/Neon/Node.php @@ -24,6 +24,9 @@ abstract class Node abstract public function toValue(); + abstract public function toString(): string; + + /** @return self[] */ public function getSubNodes(): array { diff --git a/src/Neon/Node/ArrayItemNode.php b/src/Neon/Node/ArrayItemNode.php index c4b237a5..3286eeb0 100644 --- a/src/Neon/Node/ArrayItemNode.php +++ b/src/Neon/Node/ArrayItemNode.php @@ -43,12 +43,52 @@ public static function itemsToArray(array $items): array } + /** @param self[] $items */ + public static function itemsToInlineString(array $items): string + { + $res = ''; + foreach ($items as $item) { + $res .= ($res === '' ? '' : ', ') + . ($item->key ? $item->key->toString() . ': ' : '') + . $item->value->toString(); + } + return $res; + } + + + /** @param self[] $items */ + public static function itemsToBlockString(array $items): string + { + $res = ''; + foreach ($items as $item) { + $v = $item->value->toString(); + $res .= ($item->key ? $item->key->toString() . ':' : '-') + . (strpos($v, "\n") === false + ? ' ' . $v . "\n" + : "\n" . self::indent($v) . (substr($v, -2, 1) === "\n" ? '' : "\n")); + } + return $res; + } + + + private static function indent(string $s, string $indentation = "\t"): string + { + return preg_replace('#^(?=.)#m', $indentation, $s); + } + + public function toValue() { throw new \LogicException; } + public function toString(): string + { + throw new \LogicException; + } + + public function getSubNodes(): array { return $this->key ? [$this->key, $this->value] : [$this->value]; diff --git a/src/Neon/Node/ArrayNode.php b/src/Neon/Node/ArrayNode.php index cf0ff071..42404ac0 100644 --- a/src/Neon/Node/ArrayNode.php +++ b/src/Neon/Node/ArrayNode.php @@ -35,6 +35,22 @@ public function toValue(): array } + public function toString(): string + { + if ($this->indent === null) { + $isList = !array_filter($this->items, function ($item) { return $item->key; }); + $res = ArrayItemNode::itemsToInlineString($this->items); + return ($isList ? '[' : '{') . $res . ($isList ? ']' : '}'); + + } elseif (count($this->items) === 0) { + return '[]'; + + } else { + return ArrayItemNode::itemsToBlockString($this->items); + } + } + + public function getSubNodes(): array { return $this->items; diff --git a/src/Neon/Node/EntityChainNode.php b/src/Neon/Node/EntityChainNode.php index b4c91481..2e5fdff3 100644 --- a/src/Neon/Node/EntityChainNode.php +++ b/src/Neon/Node/EntityChainNode.php @@ -38,6 +38,12 @@ public function toValue(): Neon\Entity } + public function toString(): string + { + return implode('', array_map(function ($entity) { return $entity->toString(); }, $this->chain)); + } + + public function getSubNodes(): array { return $this->chain; diff --git a/src/Neon/Node/EntityNode.php b/src/Neon/Node/EntityNode.php index 6c0ff722..4bd0ba82 100644 --- a/src/Neon/Node/EntityNode.php +++ b/src/Neon/Node/EntityNode.php @@ -41,6 +41,15 @@ public function toValue(): Entity } + public function toString(): string + { + return $this->value->toString() + . '(' + . ($this->attributes ? ArrayItemNode::itemsToInlineString($this->attributes) : '') + . ')'; + } + + public function getSubNodes(): array { return array_merge([$this->value], $this->attributes); diff --git a/src/Neon/Node/LiteralNode.php b/src/Neon/Node/LiteralNode.php index a28d1be8..ae23bbd0 100644 --- a/src/Neon/Node/LiteralNode.php +++ b/src/Neon/Node/LiteralNode.php @@ -30,4 +30,20 @@ public function toValue() { return $this->value; } + + + public function toString(): string + { + if ($this->value instanceof \DateTimeInterface) { + return $this->value->format('Y-m-d H:i:s O'); + + } elseif (is_string($this->value)) { + return $this->value; + + } elseif (is_float($this->value)) { + $res = json_encode($this->value); + return strpos($res, '.') === false ? $res . '.0' : $res; + } + return json_encode($this->value); + } } diff --git a/src/Neon/Node/StringNode.php b/src/Neon/Node/StringNode.php index 73d3c0ef..ec4be166 100644 --- a/src/Neon/Node/StringNode.php +++ b/src/Neon/Node/StringNode.php @@ -9,6 +9,7 @@ namespace Nette\Neon\Node; +use Nette; use Nette\Neon\Node; @@ -30,4 +31,20 @@ public function toValue(): string { return $this->value; } + + + public function toString(): string + { + $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($res === false) { + throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); + } + if (strpos($this->value, "\n") !== false) { + $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { + return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; + }, $res); + $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; + } + return $res; + } }