Skip to content

Commit 599c7f3

Browse files
committed
Add XMLConverter::supportsHeader and allow the instance to use header values a cells name
1 parent 8a7d6c9 commit 599c7f3

File tree

5 files changed

+106
-22
lines changed

5 files changed

+106
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ All Notable changes to `Csv` will be documented in this file
1313
- `ResultSet::createFromRdbms`
1414
- `RdbmsResult` class to allow converting RDBMS result into `ResultSet`
1515
- `TabularData` interface
16+
- `XMLConverter::supportsHeader`
1617

1718
### Deprecated
1819

@@ -30,6 +31,7 @@ All Notable changes to `Csv` will be documented in this file
3031
- `XMLConverter::$formatter` visibility it should not be public.
3132
- `XMLConverter` internal rewritten to take advantage of PHP8.4 new dom classes
3233
- `HTMLConverter` internal rewritten to take advantage of PHP8.4 new dom classes
34+
- `XMLConverter::fieldElement` now has a `nullable` field element to allow using headers names as cell names.
3335

3436
### Removed
3537

docs/9.0/converter/xml.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,25 @@ This method sets the XML record name and optionally the attribute name for the r
4040
### XMLConverter::fieldElement
4141

4242
```php
43-
public XMLConverter::fieldElement(string $node_name, string $fieldname_attribute_name = ''): self
43+
public XMLConverter::fieldElement(?string $node_name, string $fieldname_attribute_name = ''): self
4444
```
4545

46-
This method sets the XML field name and optionally the attribute name for the field name value.
46+
This method sets the XML field name and optionally the attribute name for the field name value. If the field name
47+
is `null` then the converter will use the CSV header names as field name.
4748

4849
<p class="message-info">The default field element name is <code>cell</code>.</p>
4950
<p class="message-info">The default attribute name is an empty string.</p>
51+
<p class="message-notice">The field element can be <code>null</code> since version <code>9.22.0</code>.</p>
52+
53+
If the field name is invalid an exception will be thrown. If you opt-in to use the record names as field element name,
54+
the exception will only be thrown on XML conversion. If you want to check if the CSV current header are usable as
55+
field name value you can use the new `XMLConverter::supportsHeader` method which returns `false` if at least on
56+
of the header name is invalid.
57+
58+
```php
59+
XMLConverter::supportsHeader(['foo', 'bar', '1']); // returns false
60+
XMLConverter::supportsHeader(['foo', 'bar', 'foo']); // returns true
61+
```
5062

5163
### XMLConverter::formatter
5264

src/HTMLConverter.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,6 @@ public static function create(): self
4747
return new self();
4848
}
4949

50-
/**
51-
* DEPRECATION WARNING! This method will be removed in the next major point release.
52-
*
53-
* @throws DOMException
54-
* @see HTMLConverter::create()
55-
* @deprecated since version 9.7.0
56-
*/
5750
public function __construct()
5851
{
5952
}

src/XMLConverter.php

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use DOMException;
2323
use Exception;
2424
use RuntimeException;
25+
use Throwable;
2526
use ValueError;
2627

2728
use function class_exists;
@@ -40,7 +41,7 @@ class XMLConverter
4041
/** XML Node name. */
4142
protected string $record_name = 'row';
4243
/** XML Item name. */
43-
protected string $field_name = 'cell';
44+
protected ?string $field_name = 'cell';
4445
/** XML column attribute name. */
4546
protected string $column_attr = '';
4647
/** XML offset attribute name. */
@@ -68,12 +69,6 @@ public static function create(): self
6869
return new self();
6970
}
7071

71-
/**
72-
* DEPRECATION WARNING! This method will be removed in the next major point release.
73-
*
74-
* @deprecated since version 9.7.0
75-
* @see XMLConverter::create()
76-
*/
7772
public function __construct()
7873
{
7974
}
@@ -86,7 +81,7 @@ public function __construct()
8681
public function rootElement(string $node_name): self
8782
{
8883
$clone = clone $this;
89-
$clone->root_name = $this->filterElementName($node_name);
84+
$clone->root_name = (string) $this->filterElementName($node_name);
9085

9186
return $clone;
9287
}
@@ -99,7 +94,7 @@ public function rootElement(string $node_name): self
9994
public function recordElement(string $node_name, string $record_offset_attribute_name = ''): self
10095
{
10196
$clone = clone $this;
102-
$clone->record_name = $this->filterElementName($node_name);
97+
$clone->record_name = (string) $this->filterElementName($node_name);
10398
$clone->offset_attr = $this->filterAttributeName($record_offset_attribute_name);
10499

105100
return $clone;
@@ -110,7 +105,7 @@ public function recordElement(string $node_name, string $record_offset_attribute
110105
*
111106
* @throws DOMException
112107
*/
113-
public function fieldElement(string $node_name, string $fieldname_attribute_name = ''): self
108+
public function fieldElement(?string $node_name, string $fieldname_attribute_name = ''): self
114109
{
115110
$clone = clone $this;
116111
$clone->field_name = $this->filterElementName($node_name);
@@ -203,11 +198,12 @@ protected function recordToElement(DOMDocument|XMLDocument $document, array $rec
203198
*/
204199
protected function fieldToElement(DOMDocument|XMLDocument $document, string $value, int|string $node_name): DOMElement|Element
205200
{
206-
$item = $document->createElement($this->field_name);
201+
$node_name = (string) $node_name;
202+
$item = $document->createElement($this->field_name ?? $node_name);
207203
$item->appendChild($document->createTextNode($value));
208204

209205
if ('' !== $this->column_attr) {
210-
$item->setAttribute($this->column_attr, (string) $node_name);
206+
$item->setAttribute($this->column_attr, $node_name);
211207
}
212208

213209
return $item;
@@ -218,8 +214,12 @@ protected function fieldToElement(DOMDocument|XMLDocument $document, string $val
218214
*
219215
* @throws DOMException If the Element name is invalid
220216
*/
221-
protected function filterElementName(string $value): string
217+
protected function filterElementName(?string $value): ?string
222218
{
219+
if (null === $value) {
220+
return null;
221+
}
222+
223223
return self::newXmlDocument(XMLDocument::class)->createElement($value)->tagName;
224224
}
225225

@@ -259,4 +259,18 @@ public function convert(iterable $records): DOMDocument
259259

260260
return $document;
261261
}
262+
263+
public static function supportsHeader(array $header): bool
264+
{
265+
$document = self::newXmlDocument(XMLDocument::class);
266+
foreach ($header as $header_value) {
267+
try {
268+
$res = $document->createElement($header_value)->tagName;
269+
} catch (Throwable) {
270+
return false;
271+
}
272+
}
273+
274+
return [] !== $header;
275+
}
262276
}

src/XMLConverterTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
use Dom\XMLDocument;
1717
use DOMDocument;
1818
use DOMException;
19+
use PHPUnit\Framework\Attributes\DataProvider;
1920
use PHPUnit\Framework\Attributes\Group;
21+
use PHPUnit\Framework\Attributes\Test;
2022
use PHPUnit\Framework\TestCase;
23+
use Throwable;
2124

2225
use function array_map;
2326
use function class_exists;
@@ -145,4 +148,64 @@ public function testToXMLWithFormatter(): void
145148

146149
self::assertStringContainsString('ABEL', (string) $dom->saveXML());
147150
}
151+
152+
#[Test]
153+
public function it_can_use_the_document_header_as_cell_name(): void
154+
{
155+
$csv = Reader::createFromPath(__DIR__.'/../test_files/prenoms.csv', 'r')
156+
->setDelimiter(';')
157+
->setHeaderOffset(0)
158+
->slice(0, 2);
159+
160+
$converter = XMLConverter::create()
161+
->rootElement('stats')
162+
->recordElement('per_name', 'offset')
163+
->fieldElement(null);
164+
165+
/** @var DOMDocument|XMLDocument $dom */
166+
$dom = class_exists(XMLDocument::class) ? XMLDocument::createEmpty() : new DOMDocument(encoding: 'UTF-8');
167+
$dom->appendChild($converter->import($csv, $dom));
168+
$dom->formatOutput = true;
169+
170+
$generatedXml = (string) $dom->saveXML();
171+
172+
self::assertStringContainsString('<per_name offset="1">', $generatedXml);
173+
self::assertStringContainsString('<nombre>55</nombre>', $generatedXml);
174+
self::assertStringContainsString('<sexe>M</sexe>', $generatedXml);
175+
self::assertStringContainsString('<annee>2004</annee>', $generatedXml);
176+
}
177+
178+
#[Test]
179+
public function it_will_trigger_an_exception_if_the_header_is_invalid(): void
180+
{
181+
$csv = Reader::createFromPath(__DIR__.'/../test_files/prenoms.csv', 'r')
182+
->setDelimiter(';')
183+
->slice(0, 2);
184+
185+
$converter = XMLConverter::create()
186+
->rootElement('stats')
187+
->recordElement('per_name', 'offset')
188+
->fieldElement(null);
189+
190+
/** @var DOMDocument|XMLDocument $dom */
191+
$dom = class_exists(XMLDocument::class) ? XMLDocument::createEmpty() : new DOMDocument(encoding: 'UTF-8');
192+
193+
$this->expectException(Throwable::class);
194+
$converter->import($csv, $dom);
195+
}
196+
197+
#[Test]
198+
#[DataProvider('provideHeader')]
199+
public function it_will_tell_if_it_can_use_a_specific_header(array $header, bool $expected): void
200+
{
201+
self::assertSame($expected, XMLConverter::supportsHeader($header));
202+
}
203+
204+
public static function provideHeader(): iterable
205+
{
206+
yield 'no header' => ['header' => [], 'expected' => false];
207+
yield 'header with name value' => ['header' => ['foo', 'bar'], 'expected' => true];
208+
yield 'header with numeric index' => ['header' => [1, 'bar'], 'expected' => false];
209+
yield 'header with string as numeric index' => ['header' => ['1', 'bar'], 'expected' => false];
210+
}
148211
}

0 commit comments

Comments
 (0)