diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c90094..e647cb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Full changelog for PHP Quill Renderer +## v3.02.0 - 2018-05-13 + +* Add initial support for loading multiple deltas, very basic and not yet supported through the API, next version. +* Custom attributes can be added to base insert and compound delta. The base insert adds a span around the insert +text; the compound delta adds the attributes to the outer HTML tag. +* Added code coverage reporting via coveralls.io. +* Increased test coverage, test for thrown exceptions and removed redundant method in Delta class. + ## v3.01.0 - 2018-05-10 * `Parser::load()` wasn't resetting the deltas array, thanks [tominventisbe](https://github.com/tominventisbe). diff --git a/README.md b/README.md index e434679..34b591e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ with version 3 and are unlikely to ever be updated, the v3 is so much more flexi The easiest way to use the renderer is via composer. ```composer require deanblackborough/php-quill-renderer```, alternatively you can include the classes in my src/ directory in your library or app. -## Usage +## Usage via API ``` try { $quill = new \DBlackborough\Quill\Render($quill_json, 'HTML'); @@ -40,6 +40,9 @@ try { ``` ## Usage, direct, parse and then render + +### If you use this method, it will change in the next release, I need to add a load() method to the Renderer class, the API will not change. + ``` $parser = new \DBlackborough\Quill\Parser\Html(); $parser->load($quill_json); diff --git a/src/Delta/Html/Compound.php b/src/Delta/Html/Compound.php index 3130d57..38f3981 100644 --- a/src/Delta/Html/Compound.php +++ b/src/Delta/Html/Compound.php @@ -13,8 +13,19 @@ */ class Compound extends Delta { + /** + * @var array Array of HTML tags + */ private $tags; + /** + * @var array An array of element attributes + */ + private $element_attributes; + + /** + * @var string The generated HTML fragment + */ private $html; /** @@ -27,6 +38,7 @@ public function __construct(string $insert) $this->insert = $insert; $this->tags = []; + $this->element_attributes = []; $this->html = ''; } @@ -58,7 +70,7 @@ private function tags(): void break; default: - // Ignore tags not found + $this->element_attributes[$attribute] = $value; break; } } @@ -88,8 +100,17 @@ public function render(): string { $this->tags(); - foreach ($this->tags as $tag) { - $this->html .= "<{$tag}>"; + $element_attributes = ''; + foreach ($this->element_attributes as $attribute => $value) { + $element_attributes .= "{$attribute}=\"{$value}\" "; + } + + foreach ($this->tags as $i => $tag) { + if ($i === 0 && strlen($element_attributes) > 0) { + $this->html .= "<{$tag} " . rtrim($element_attributes) . '>'; + } else { + $this->html .= "<{$tag}>"; + } } $this->html .= $this->insert; diff --git a/src/Delta/Html/Delta.php b/src/Delta/Html/Delta.php index 39217da..4c7aaa7 100644 --- a/src/Delta/Html/Delta.php +++ b/src/Delta/Html/Delta.php @@ -107,20 +107,6 @@ public function getInsert(): string return $this->insert; } - /** - * If the delta is a child, is it the only child - * - * @return boolean - */ - public function isOnlyChild(): bool - { - if ($this->isFirstChild() === true && $this->isLastChild() === true) { - return true; - } else { - return false; - } - } - /** * If the delta is a child, what type of tag is the parent * diff --git a/src/Delta/Html/Insert.php b/src/Delta/Html/Insert.php index 959c580..1afd9a6 100644 --- a/src/Delta/Html/Insert.php +++ b/src/Delta/Html/Insert.php @@ -34,6 +34,21 @@ public function __construct(string $insert, array $attributes = []) */ public function render(): string { - return $this->insert; + $add_span = false; + if (count($this->attributes) > 0) { + $add_span = true; + } + + if ($add_span === false) { + return $this->insert; + } else { + $html = 'attributes as $attribute => $value) { + $html .= " {$attribute}=\"{$value}\""; + } + $html .= ">{$this->insert}"; + + return $html; + } } } diff --git a/src/Parser/Html.php b/src/Parser/Html.php index c0cfb2c..82f7db9 100644 --- a/src/Parser/Html.php +++ b/src/Parser/Html.php @@ -130,7 +130,7 @@ public function parse(): bool break; default: - // Write to errors array? Throw exception? + $this->deltas[] = new Insert($quill['insert'], $quill['attributes']); break; } } @@ -185,6 +185,30 @@ public function parse(): bool } } + /** + * Parse multiple deltas + * + * @return boolean Return true if all the deltas could be parsed ready for the renderer + */ + public function parseMultiple() : bool + { + $results = []; + foreach ($this->quill_json_stack as $index => $quill_json) { + $this->quill_json = $quill_json; + $this->deltas = []; + $results[$index] = $this->parse(); + if ($results[$index] === true) { + $this->deltas_stack[$index] = $this->deltas(); + } + } + + if (in_array(false, $results) === false) { + return true; + } else { + return false; + } + } + /** * Return the array of delta objects * @@ -194,4 +218,21 @@ public function deltas(): array { return $this->deltas; } + + /** + * Return a specific delta array of delta objects + * + * @param string $index Index of the deltas array you want + * + * @return array + * @throwns \OutOfRangeException + */ + public function deltasByIndex(string $index): array + { + if (array_key_exists($index, $this->deltas_stack) === true) { + return $this->deltas_stack[$index]; + } else { + throw new \OutOfRangeException('Deltas array does not exist for the given index: ' . $index); + } + } } diff --git a/src/Parser/Parse.php b/src/Parser/Parse.php index 8c17688..ba6604c 100644 --- a/src/Parser/Parse.php +++ b/src/Parser/Parse.php @@ -13,12 +13,19 @@ abstract class Parse { /** - * The initial quill json array after it has been decoded + * The initial quill json string after it has been json decoded * * @var array */ protected $quill_json; + /** + * An array of json decoded quill strings + * + * @var array + */ + protected $quill_json_stack; + /** * Deltas array after parsing, array of Delta objects * @@ -26,6 +33,13 @@ abstract class Parse */ protected $deltas; + /** + * Deltas stack array after parsing, array of Delta objects index by user defined index + * + * @var array + */ + protected $deltas_stack; + /** * Is the json array a valid json array? * @@ -38,11 +52,11 @@ abstract class Parse */ public function __construct() { - $this->deltas = []; + $this->quill_json = null; } /** - * Load the deltas, check the json is valid and then save to the $quill_json property + * Load the deltas, checks the json is valid and then save to the $quill_json property * * @param string $quill_json Quill json string * @@ -61,6 +75,33 @@ public function load(string $quill_json): bool } } + /** + * Load multiple deltas + * + * @param array An array of $quill json, returnable via array index + * + * @return boolean + */ + public function loadMultiple(array $quill_json): bool + { + $this->deltas_stack = []; + + foreach ($quill_json as $index => $json) { + $json_stack_value = json_decode($json, true); + + if (is_array($json_stack_value) === true && count($json_stack_value) > 0) { + $this->quill_json_stack[$index] = $json_stack_value; + } + } + + if (count($quill_json) === count($this->quill_json_stack)) { + $this->valid = true; + return true; + } else { + return false; + } + } + /** * Loop through the deltas and generate the contents array * @@ -74,4 +115,14 @@ abstract public function parse(): bool; * @return array */ abstract public function deltas(): array; + + /** + * Return a specific delta array of delta objects + * + * @param string $index Index of the deltas array you want + * + * @return array + * @throwns \OutOfRangeException + */ + abstract public function deltasByIndex(string $index): array; } diff --git a/src/Render.php b/src/Render.php index 32dd245..4954cd6 100644 --- a/src/Render.php +++ b/src/Render.php @@ -42,14 +42,14 @@ public function __construct(string $deltas, string $format = 'HTML') $this->parser = new Parser\Html(); break; default: - throw new \Exception('No renderer found for requested format: "' . $format . '"'); + throw new \InvalidArgumentException('Requested $format not supported, formats supported, [HTML]'); break; } $this->format = $format; if ($this->parser->load($deltas) === false) { - throw new \Exception('Failed to load deltas json'); + throw new \RuntimeException('Failed to load/parse deltas json'); } } @@ -75,6 +75,10 @@ public function parserLoaded(): bool */ public function render(): string { + if ($this->parser === null) { + throw new \BadMethodCallException('No parser loaded'); + } + if ($this->parser->parse() !== true) { throw new \Exception('Failed to parse the supplied deltas object'); } @@ -84,7 +88,7 @@ public function render(): string $this->renderer = new Renderer\Html($this->parser->deltas()); break; default: - throw new \Exception('No renderer found for requested format: "' . $this->format . '"'); + // Never should be reached break; } diff --git a/src/Renderer/Html.php b/src/Renderer/Html.php index 3706970..8aab63b 100644 --- a/src/Renderer/Html.php +++ b/src/Renderer/Html.php @@ -25,6 +25,7 @@ class Html extends Render * Renderer constructor. * * @param array $deltas Delta objects array + * @deprecated Loading the delta in the constructor is going to be removed in the next version, to support multiple deltas I am going to add a load method */ public function __construct(array $deltas) { diff --git a/tests/attributes/html/TypographyTest.php b/tests/attributes/html/TypographyTest.php index 1001d0f..c4fb8bb 100644 --- a/tests/attributes/html/TypographyTest.php +++ b/tests/attributes/html/TypographyTest.php @@ -12,19 +12,22 @@ final class TypographyTest extends \PHPUnit\Framework\TestCase { private $delta_bold = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"bold":true},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; + private $delta_bold_with_attributes = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"bold":true, "class":"bold_attributes"},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; private $delta_italic = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"italic":true},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; private $delta_strike = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"strike":true},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; private $delta_sub_script = '{"ops":[{"insert":"Lorem ipsum dolor sit"},{"attributes":{"script":"sub"},"insert":"x"},{"insert":" amet, consectetur adipiscing elit. Pellentesque at elit dapibus risus molestie rhoncus dapibus eu nulla. Vestibulum at eros id augue cursus egestas."}]}'; private $delta_super_script = '{"ops":[{"insert":"Lorem ipsum dolor sit"},{"attributes":{"script":"super"},"insert":"x"},{"insert":" amet, consectetur adipiscing elit. Pellentesque at elit dapibus risus molestie rhoncus dapibus eu nulla. Vestibulum at eros id augue cursus egestas."}]}'; private $delta_underline = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"underline":true},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; + private $delta_single_attribute = '{"ops":[{"insert":"Lorem ipsum dolor sit amet "},{"attributes":{"class":"custom_class"},"insert":"sollicitudin"},{"insert":" quam, nec auctor eros felis elementum quam. Fusce vel mollis enim."}]}'; private $expected_bold = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; + private $expected_bold_with_attributes = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; private $expected_italic = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; private $expected_strike = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; private $expected_sub_script = '

Lorem ipsum dolor sitx amet, consectetur adipiscing elit. Pellentesque at elit dapibus risus molestie rhoncus dapibus eu nulla. Vestibulum at eros id augue cursus egestas.

'; private $expected_super_script = '

Lorem ipsum dolor sitx amet, consectetur adipiscing elit. Pellentesque at elit dapibus risus molestie rhoncus dapibus eu nulla. Vestibulum at eros id augue cursus egestas.

'; private $expected_underline = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; - + private $expected_single_attribute = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; /** * Test bold attribute @@ -46,6 +49,26 @@ public function testBold() $this->assertEquals($this->expected_bold, $result, __METHOD__ . ' - Bold attribute failure'); } + /** + * Test bold attribute with additional custom attribute + * + * @return void + * @throws \Exception + */ + public function testBoldWithAttribute() + { + $result = null; + + try { + $quill = new QuillRender($this->delta_bold_with_attributes); + $result = $quill->render(); + } catch (\Exception $e) { + $this->fail(__METHOD__ . 'failure, ' . $e->getMessage()); + } + + $this->assertEquals($this->expected_bold_with_attributes, $result, __METHOD__ . ' - Bold attribute with attributes failure'); + } + /** * Test italic attribute * @@ -143,4 +166,24 @@ public function testUnderline() $this->assertEquals($this->expected_underline, $result, __METHOD__ . ' - Underline attribute failure'); } + + /** + * Test a single 'unknown' attribute, should create a span + * + * @return void + * @throws \Exception + */ + public function testSingleAttribute() + { + $result = null; + + try { + $quill = new QuillRender($this->delta_single_attribute); + $result = $quill->render(); + } catch (\Exception $e) { + $this->fail(__METHOD__ . 'failure, ' . $e->getMessage()); + } + + $this->assertEquals($this->expected_single_attribute, $result, __METHOD__ . ' - Single attribute failure'); + } } diff --git a/tests/html/CompoundTest.php b/tests/html/CompoundTest.php index 68b87a3..be19d46 100644 --- a/tests/html/CompoundTest.php +++ b/tests/html/CompoundTest.php @@ -23,6 +23,20 @@ final class CompoundTest extends \PHPUnit\Framework\TestCase }, "insert":"quam sapien " }, + { + "attributes":{ + "bold":true, + "script":"sub" + }, + "insert":"quam sapien " + }, + { + "attributes":{ + "bold":true, + "underline":true + }, + "insert":"quam sapien " + }, { "attributes":{ "strike":true @@ -82,7 +96,7 @@ final class CompoundTest extends \PHPUnit\Framework\TestCase ] }'; - private $expected_multiple_attributes = "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed efficitur nibh tempor augue lobortis, nec eleifend velit venenatis. Nullam fringilla dui eget lectus mattis tincidunt. Donec sollicitudin, lacus sed luctus ultricies, quam sapien sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim. Sed ac augue tincidunt, cursus urna a, tempus ipsum. Donec pretium fermentum erat a elementum. In est odio, mattis sed dignissim sed, porta ac nisl. Nunc et tellus imperdiet turpis placerat tristique nec quis justo. Aenean nisi libero, auctor a laoreet sed, fermentum vel massa. Etiam ultricies leo eget purus tempor dapibus. Integer ac sapien eros. Suspendisse convallis ex.

"; + private $expected_multiple_attributes = "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed efficitur nibh tempor augue lobortis, nec eleifend velit venenatis. Nullam fringilla dui eget lectus mattis tincidunt. Donec sollicitudin, lacus sed luctus ultricies, quam sapien quam sapien quam sapien sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim. Sed ac augue tincidunt, cursus urna a, tempus ipsum. Donec pretium fermentum erat a elementum. In est odio, mattis sed dignissim sed, porta ac nisl. Nunc et tellus imperdiet turpis placerat tristique nec quis justo. Aenean nisi libero, auctor a laoreet sed, fermentum vel massa. Etiam ultricies leo eget purus tempor dapibus. Integer ac sapien eros. Suspendisse convallis ex.

"; private $expected_multiple_unknown_attributes_image = '

Text 1 assumenda Text 2.

Text 3.

'; /** @@ -106,7 +120,7 @@ public function testMultipleAttributes() } /** - * Test a delta with multiple unknown attributes ona an image, attributes should be included as is + * Test a delta with multiple unknown attributes on an image, attributes should be included as is * * @return void * @throws \Exception diff --git a/tests/html/MultipleTest.php b/tests/html/MultipleTest.php new file mode 100644 index 0000000..a1dbcd5 --- /dev/null +++ b/tests/html/MultipleTest.php @@ -0,0 +1,35 @@ +Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; + private $expected_two = '

Lorem ipsum dolor sit amet sollicitudin quam, nec auctor eros felis elementum quam. Fusce vel mollis enim.

'; + + public function testLoadingMultipleDeltasAndRendering() + { + $quill_json = [ + 'one' => $this->delta_one, + 'two' => $this->delta_two + ]; + + $parser = new \DBlackborough\Quill\Parser\Html(); + $parser->loadMultiple($quill_json); + $parser->parseMultiple(); + + $renderer = new \DBlackborough\Quill\Renderer\Html($parser->deltasByIndex('one')); + $this->assertEquals($renderer->render(), $this->expected_one); + + $renderer = new \DBlackborough\Quill\Renderer\Html($parser->deltasByIndex('two')); + $this->assertEquals($renderer->render(), $this->expected_two); + } +} diff --git a/tests/html/StockTest.php b/tests/html/StockTest.php index a80a6d2..23de42e 100644 --- a/tests/html/StockTest.php +++ b/tests/html/StockTest.php @@ -13,6 +13,7 @@ final class StockTest extends \PHPUnit\Framework\TestCase { private $delta_null_insert = '{"ops":[{"insert":"Heading 1"},{"insert":null},{"attributes":{"header":1},"insert":"\n"}]}'; private $delta_header = '{"ops":[{"insert":"Heading 1"},{"attributes":{"header":1},"insert":"\n"}]}'; + private $delta_header_invalid = '{"ops":[{"insert":"Heading 1"},{"attributes":{"header":1},"insert":"\n"}}'; private $expected_null_insert = "

Heading 1

"; private $expected_header = '

Heading 1

'; @@ -69,4 +70,30 @@ public function testMultipleInstancesInScript() $this->assertEquals($this->expected_header, $result, __METHOD__ . ' Multiple load calls failure'); } + + /** + * Test to see if an exception is thrown when an invalid parser is requested + * + * @return void + * @throws \Exception + */ + public function testExceptionThrownForInvalidParser() + { + $this->expectException(\InvalidArgumentException::class); + + $quill = new QuillRender($this->delta_header, 'UNKNOWN'); + } + + /** + * Test to see if an exception is thrown when attempting to parse an invalid json string + * + * @return void + * @throws \Exception + */ + public function testExceptionThrownForInvalidJson() + { + $this->expectException(\RuntimeException::class); + + $quill = new QuillRender($this->delta_header_invalid, 'HTML'); + } }