From cbdcc9bbd60e32b50695ad908bf535f126901488 Mon Sep 17 00:00:00 2001 From: Christian Harvey Date: Fri, 12 Jul 2019 10:16:02 +0100 Subject: [PATCH] Fixes #344. Added the ability to add headers and footers to the table output of HTMLConverter. --- composer.json | 3 +- src/HTMLConverter.php | 77 ++++++++++++++++++++++++++++++++----- src/XMLConverter.php | 29 ++++++++++++-- tests/HTMLConverterTest.php | 53 +++++++++++++++++++++++++ tests/XMLConverterTest.php | 42 ++++++++++++++++++++ 5 files changed, 190 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index ab75fd2b..5c5067f8 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ }, "require": { "php" : ">=7.0.10", - "ext-mbstring" : "*" + "ext-mbstring" : "*", + "ext-dom" : "*" }, "require-dev": { "ext-curl" : "*", diff --git a/src/HTMLConverter.php b/src/HTMLConverter.php index 27b7c644..bac4c79f 100644 --- a/src/HTMLConverter.php +++ b/src/HTMLConverter.php @@ -58,19 +58,45 @@ public function __construct() /** * Convert an Record collection into a DOMDocument. * - * @param array|Traversable $records the tabular data collection + * @param array|Traversable $records The tabular data collection + * @param array $headerRecord An optional array of headers to output to the table using `` and `` elements + * @param array $footerRecord An optional array of footers to output to the table using `` and `` elements + * + * @return string */ - public function convert($records): string + public function convert($records, array $headerRecord = [], array $footerRecord = []): string { - /** @var DOMDocument $doc */ - $doc = $this->xml_converter->convert($records); + if ([] === $headerRecord && [] === $footerRecord) { + /** @var DOMDocument $doc */ + $doc = $this->xml_converter->convert($records); + + /** @var DOMElement $table */ + $table = $doc->getElementsByTagName('table')->item(0); + $this->styleTableElement($table); + + return $doc->saveHTML($table); + }; + + $doc = new DOMDocument('1.0'); + + $tbody = $this->xml_converter->rootElement('tbody')->import($records, $doc); + $table = $doc->createElement('table'); + $this->styleTableElement($table); + if (!empty($headerRecord)) { + $table->appendChild( + $this->createRecordRow('thead', 'th', $headerRecord, $doc) + ); + } + $table->appendChild($tbody); + if (!empty($footerRecord)) { + $table->appendChild( + $this->createRecordRow('tfoot', 'th', $footerRecord, $doc) + ); + } - /** @var DOMElement $table */ - $table = $doc->getElementsByTagName('table')->item(0); - $table->setAttribute('class', $this->class_name); - $table->setAttribute('id', $this->id_value); + $doc->appendChild($table); - return $doc->saveHTML($table); + return $doc->saveHTML(); } /** @@ -111,4 +137,37 @@ public function td(string $fieldname_attribute_name): self return $clone; } + + /** + * Create a DOMElement representing a single record of data + * + * @param string $recordTagName + * @param string $fieldTagName + * @param array $record + * @param DOMDocument $doc + * + * @return DOMElement + */ + private function createRecordRow(string $recordTagName, string $fieldTagName, array $record, DOMDocument $doc) : DOMElement + { + $node = $this->xml_converter->rootElement($recordTagName)->fieldElement($fieldTagName)->import([$record], $doc); + + /** @var DOMElement $element */ + foreach ($node->getElementsByTagName($fieldTagName) as $element) { + $element->setAttribute('scope', 'col'); + } + + return $node; + } + + /** + * Style the table dom element + * + * @param DOMElement $table_element + */ + private function styleTableElement(DOMElement $table_element) + { + $table_element->setAttribute('class', $this->class_name); + $table_element->setAttribute('id', $this->id_value); + } } diff --git a/src/XMLConverter.php b/src/XMLConverter.php index c7b2fdec..8745f0ca 100644 --- a/src/XMLConverter.php +++ b/src/XMLConverter.php @@ -80,11 +80,34 @@ class XMLConverter ]; /** - * Convert an Record collection into a DOMDocument. + * Convert a Record collection into a DOMDocument. * * @param array|Traversable $records the CSV records collection + * + * @return DOMDocument */ public function convert($records): DOMDocument + { + if (!is_iterable($records)) { + throw new TypeError(sprintf('%s() expects argument passed to be iterable, %s given', __METHOD__, gettype($records))); + } + $doc = new DOMDocument('1.0'); + $doc->appendChild( + $this->import($records, $doc) + ); + return $doc; + } + + /** + * Create a new DOMElement related to the given DOMDocument. + * + * **DOES NOT** attach to the DOMDocument + * + * @param iterable $records + * + * @return DOMElement + */ + public function import($records, DOMDocument $doc): DOMElement { if (!is_iterable($records)) { throw new TypeError(sprintf('%s() expects argument passed to be iterable, %s given', __METHOD__, gettype($records))); @@ -92,15 +115,13 @@ public function convert($records): DOMDocument $field_encoder = $this->encoder['field']['' !== $this->column_attr]; $record_encoder = $this->encoder['record']['' !== $this->offset_attr]; - $doc = new DOMDocument('1.0'); $root = $doc->createElement($this->root_name); foreach ($records as $offset => $record) { $node = $this->$record_encoder($doc, $record, $field_encoder, $offset); $root->appendChild($node); } - $doc->appendChild($root); - return $doc; + return $root; } /** diff --git a/tests/HTMLConverterTest.php b/tests/HTMLConverterTest.php index a609765c..3e1dd48a 100644 --- a/tests/HTMLConverterTest.php +++ b/tests/HTMLConverterTest.php @@ -54,6 +54,59 @@ public function testToHTML() self::assertContains('', $html); self::assertContains('', $html); + self::assertNotContains('', $html); + self::assertNotContains('', $html); + } + + /** + * @covers ::__construct + * @covers ::table + * @covers ::tr + * @covers ::td + * @covers ::convert + */ + public function testToHTMLWithHeaders() + { + $csv = Reader::createFromPath(__DIR__.'/data/prenoms.csv', 'r') + ->setDelimiter(';') + ->setHeaderOffset(0) + ; + + $stmt = (new Statement()) + ->offset(3) + ->limit(5) + ; + + $records = $stmt->process($csv); + + $converter = (new HTMLConverter()) + ->table('table-csv-data', 'test') + ->td('title') + ->tr('data-record-offset') + ; + + $html = $converter->convert($records, $records->getHeader()); + self::assertContains('
', $html); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertNotContains('', $html); + + $html = $converter->convert($records, [], $records->getHeader()); + self::assertContains('
prenoms
', $html); + self::assertContains('', $html); + self::assertNotContains('', $html); + self::assertContains('', $html); + self::assertContains('', $html); + + $html = $converter->convert($records, $records->getHeader(), $records->getHeader()); + self::assertContains('
prenoms
', $html); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertContains('', $html); + self::assertNotContains('', $html); + self::assertNotContains('', $html); } /** diff --git a/tests/XMLConverterTest.php b/tests/XMLConverterTest.php index 8c957090..0c5f5651 100644 --- a/tests/XMLConverterTest.php +++ b/tests/XMLConverterTest.php @@ -12,6 +12,7 @@ namespace LeagueTest\Csv; use DOMDocument; +use DOMElement; use DOMException; use League\Csv\Reader; use League\Csv\Statement; @@ -90,4 +91,45 @@ public function testXmlElementTriggersTypeError() self::expectException(TypeError::class); (new XMLConverter())->convert('foo'); } + + /** + * @covers ::rootElement + * @covers ::recordElement + * @covers ::fieldElement + * @covers ::import + * @covers ::recordToElement + * @covers ::recordToElementWithAttribute + * @covers ::fieldToElement + * @covers ::fieldToElementWithAttribute + * @covers ::filterAttributeName + * @covers ::filterElementName + */ + public function testImport() + { + $csv = Reader::createFromPath(__DIR__.'/data/prenoms.csv', 'r') + ->setDelimiter(';') + ->setHeaderOffset(0) + ; + + $stmt = (new Statement()) + ->offset(3) + ->limit(5) + ; + + $records = $stmt->process($csv); + + $converter = (new XMLConverter()) + ->rootElement('csv') + ->recordElement('record', 'offset') + ->fieldElement('field', 'name') + ; + + $doc = new DOMDocument('1.0'); + $element = $converter->import($records, $doc); + + self::assertInstanceOf(DOMDocument::class, $doc); + self::assertCount(0, $doc->childNodes); + self::assertInstanceOf(DOMElement::class, $element); + self::assertCount(5, $element->childNodes); + } }