diff --git a/README.md b/README.md index d987aa3..4081c9a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Helpers On top of the [built-in functionality in Handlebars](https://github.com/xp-forge/handlebars), this library includes the following essential helpers: * `encode`: Performs URL-encoding +* `json`: Performs JSON encoding, pretty-printing when given `format=true`. * `equals`: Tests arguments for equality * `contains`: Tests whether a string or array contains a certain value * `size`: Returns string length or array size diff --git a/composer.json b/composer.json index 2bfc13c..a78b21c 100755 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "xp-forge/frontend": "^7.0 | ^6.0", "xp-forge/handlebars": "^10.0 | ^9.3", "xp-forge/yaml": "^9.0 | ^8.0 | ^7.0 | ^6.0", + "xp-forge/json": "^6.0 | ^5.0", "php": ">=7.4.0" }, "require-dev" : { diff --git a/src/main/php/web/frontend/helpers/Essentials.class.php b/src/main/php/web/frontend/helpers/Essentials.class.php index 9b58f36..6c4b7e6 100755 --- a/src/main/php/web/frontend/helpers/Essentials.class.php +++ b/src/main/php/web/frontend/helpers/Essentials.class.php @@ -1,5 +1,9 @@ function($in, $context, $options) { return rawurlencode($options[0] ?? ''); }; + yield 'json' => function($in, $context, $options) { + static $marshalling, $format; + + $s= new StringOutput(($options['format'] ?? false) ? ($format??= new WrappedFormat(' ')) : null); + $s->write(($marshalling??= new Marshalling())->marshal($options[0] ?? null)); + return $s->bytes(); + }; yield 'equals' => function($in, $context, $options) { return (int)(($options[0] ?? null) === ($options[1] ?? null)); }; @@ -23,7 +34,7 @@ public function helpers() { yield 'size' => function($in, $context, $options) { if (!isset($options[0])) { return 0; - } else if ($options[0] instanceof \Countable || is_array($options[0])) { + } else if ($options[0] instanceof Countable || is_array($options[0])) { return sizeof($options[0]); } else { return strlen($options[0]); diff --git a/src/test/php/web/frontend/unittest/EssentialsTest.class.php b/src/test/php/web/frontend/unittest/EssentialsTest.class.php index 04eab04..761b1a5 100755 --- a/src/test/php/web/frontend/unittest/EssentialsTest.class.php +++ b/src/test/php/web/frontend/unittest/EssentialsTest.class.php @@ -1,9 +1,22 @@ ['World', true]]]; + yield [new class() { public $hello= ['World', true]; }]; + } + + /** @return iterable */ + private function iterables() { + yield [(function() { yield 1; yield 2; yield 3; })()]; + yield [new ArrayIterator([1, 2, 3])]; + } + #[Test] public function url_encode() { Assert::equals( @@ -12,6 +25,43 @@ public function url_encode() { ); } + #[Test] + public function json_string() { + Assert::equals( + 'let str = "He said \\"hello \\u4e16\\u754c!\\"\\n";', + $this->transform('let str = {{&json input}};', ['input' => 'He said "hello 世界!"'."\n"]) + ); + } + + #[Test, Values(from: 'objects')] + public function json_object($object) { + Assert::equals( + 'let obj = {"hello":["World",true]};', + $this->transform('let obj = {{&json input}};', ['input' => $object]) + ); + } + + #[Test, Values(from: 'iterables')] + public function json_iterable($iterable) { + Assert::equals( + 'let it = [1,2,3];', + $this->transform('let it = {{&json input}};', ['input' => $iterable]) + ); + } + + #[Test] + public function formatted_json() { + Assert::equals( + "{\n \"hello\": [\"World\", true]\n}", + $this->transform('{{&json input format=true}}', ['input' => ['hello' => ['World', true]]]) + ); + } + + #[Test, Values([['', '"<\\/script>"'], ['// END', '"\\/\\/ END"'], [['tag' => ''], '{"tag":"<\\/a>"}']])] + public function forward_slashes_escaped($input, $expected) { + Assert::equals($expected, $this->transform('{{&json input}}', ['input' => $input])); + } + #[Test, Values(['{{equals "A" "A"}}', '{{equals "A" a}}', '{{equals a a}}'])] public function are_equal($template) { Assert::equals('1', $this->transform($template, ['a' => 'A'])); @@ -44,7 +94,7 @@ public function size($expr, $expected) { 'test' => 'Test', 'numbers' => [1, 2, 3], 'sizes' => ['S' => 12.99, 'M' => 13.99], - 'count' => new class() implements \Countable { public function count(): int { return 1; } }, + 'count' => new class() implements Countable { public function count(): int { return 1; } }, 'empty' => [], ])); }