Skip to content

Commit 592f2dd

Browse files
committed
Simplify XMLConverter codebase to reduce BC break
1 parent bd0c1dc commit 592f2dd

File tree

4 files changed

+111
-82
lines changed

4 files changed

+111
-82
lines changed

docs/9.0/converter/xml.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ title: Converting a CSV into a XML DOMDocument object
77

88
The `XMLConverter` converts a CSV records collection into a PHP `DOMDocument`.
99

10+
<p class="message-info">Starting with version <code>9.22.0</code>, support for
11+
returning <code>\Dom\XmlDocument</code> instances is added</p>
12+
1013
## Settings
1114

1215
Prior to converting your records collection into XML, you may wish to configure the element and its associated attribute names. To do so, `XMLConverter` provides methods to set up these settings.
@@ -57,10 +60,23 @@ This method allow to apply a callback prior to converting your collection indivi
5760
This callback allows you to specify how each item will be converted. The formatter should
5861
return an associative array suitable for conversion.
5962

63+
### XMLConverter::xmlClass
64+
65+
<p class="message-info">New feature introduced in version <code>9.22.0</code></p>
66+
67+
```php
68+
public XMLConverter::xmlClass(string $xmlClass): self
69+
```
70+
71+
This method allow to specify the return type when calling the `convert` method.
72+
The accepted values are `DomDocument` or `Dom\XmlDocument` class names any other
73+
value will trigger a `ValueError` from the API. To avoid BC break the default
74+
value if this method is **NEVER** call is `DomDocument`.
75+
6076
## Conversion
6177

6278
```php
63-
public XMLConverter::convert(iterable $records): DOMDocument
79+
public XMLConverter::convert(iterable $records): DOMDocument|\Dom\XmlDocument
6480
```
6581

6682
The `XMLConverter::convert` accepts an `iterable` which represents the records collection and returns a `DOMDocument` object.
@@ -120,7 +136,7 @@ echo htmlentities($dom->saveXML());
120136
<p class="message-info">New feature introduced in version <code>9.3.0</code></p>
121137

122138
```php
123-
public XMLConverter::import(iterable $records, DOMDocument $doc): DOMElement
139+
public XMLConverter::import(iterable $records, DOMDocument|\Dom\XMLDocument $doc): DOMElement|\Dom\Element
124140
```
125141

126142
Instead of converting your tabular data into a full XML document you can now import it into an already existing `DOMDocument` object.
@@ -129,7 +145,7 @@ To do so, you need to specify which document the data should be imported into us
129145
This method takes two arguments:
130146

131147
- the tabular data as defined for the `XMLConverter::convert` method;
132-
- a `DOMDocument` object to import the data into;
148+
- a `DOMDocument` or a `\Dom\XmlDocument` object to import the data into;
133149

134150
Note that the resulting `DOMElement` is attached to the given `DOMDocument` object but not yet included in the document tree.
135151
To include it, you still need to call a DOM insertion method like `appendChild` or `insertBefore` with a node that *is* currently in the document tree.

src/HTMLConverterTest.php

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,10 @@ public function testToHTML(): void
2929
->setHeaderOffset(0)
3030
;
3131

32-
$stmt = Statement::create()
32+
$records = Statement::create()
3333
->offset(3)
3434
->limit(5)
35-
;
36-
37-
$records = $stmt->process($csv);
35+
->process($csv);
3836

3937
$converter = HTMLConverter::create()
4038
->table('table-csv-data', 'test')
@@ -58,12 +56,10 @@ public function testToHTMLWithTHeadTableSection(): void
5856
->setHeaderOffset(0)
5957
;
6058

61-
$stmt = Statement::create()
59+
$records = Statement::create()
6260
->offset(3)
6361
->limit(5)
64-
;
65-
66-
$records = $stmt->process($csv);
62+
->process($csv);
6763

6864
$converter = HTMLConverter::create()
6965
->table('table-csv-data', 'test')
@@ -89,12 +85,10 @@ public function testToHTMLWithTFootTableSection(): void
8985
->setHeaderOffset(0)
9086
;
9187

92-
$stmt = Statement::create()
88+
$records = Statement::create()
9389
->offset(3)
9490
->limit(5)
95-
;
96-
97-
$records = $stmt->process($csv);
91+
->process($csv);
9892

9993
$converter = HTMLConverter::create()
10094
->table('table-csv-data', 'test')
@@ -120,12 +114,10 @@ public function testToHTMLWithBothTableHeaderSection(): void
120114
->setHeaderOffset(0)
121115
;
122116

123-
$stmt = Statement::create()
117+
$records = Statement::create()
124118
->offset(3)
125119
->limit(5)
126-
;
127-
128-
$records = $stmt->process($csv);
120+
->process($csv);
129121

130122
$converter = HTMLConverter::create()
131123
->table('table-csv-data', 'test')
@@ -154,24 +146,19 @@ public function testToHTMLWithFormatter(): void
154146
{
155147
$csv = Reader::createFromPath(__DIR__.'/../test_files/prenoms.csv', 'r')
156148
->setDelimiter(';')
157-
->setHeaderOffset(0)
158-
;
149+
->setHeaderOffset(0);
159150

160-
$stmt = Statement::create()
151+
$records = Statement::create()
161152
->offset(3)
162153
->limit(5)
163-
;
164-
165-
$records = $stmt->process($csv);
154+
->process($csv);
166155

167156
$converter = HTMLConverter::create()
168157
->table('table-csv-data', 'test')
169158
->td('title')
170159
->tr('data-record-offset')
171160
->formatter(fn (array $record, int|string $key): array => array_map(strtoupper(...), $record));
172-
;
173161

174-
$html = $converter->convert($records);
175-
self::assertStringContainsString('ABEL', $html);
162+
self::assertStringContainsString('ABEL', $converter->convert($records));
176163
}
177164
}

src/XMLConverter.php

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
use DOMElement;
2121
use DOMException;
2222
use Exception;
23+
use RuntimeException;
24+
use ValueError;
2325

2426
use function class_exists;
2527
use function extension_loaded;
28+
use function in_array;
2629

2730
/**
2831
* Converts tabular data into a DOMDocument object.
@@ -41,10 +44,23 @@ class XMLConverter
4144
protected string $offset_attr = '';
4245
/** @var ?Closure(array, array-key): array */
4346
protected ?Closure $formatter = null;
47+
/** @var class-string */
48+
protected string $xmlClass = DomDocument::class;
4449

45-
private static function supportsModerDom(): bool
50+
/**
51+
* @param class-string $xmlClass
52+
*
53+
* @throws RuntimeException If the extension is not present
54+
* @throws ValueError If the XML class used is invalid
55+
*/
56+
private static function newXmlDocument(string $xmlClass = DOMDocument::class): DOMDocument|XMLDocument
4657
{
47-
return extension_loaded('dom') && class_exists(XMLDocument::class);
58+
return match (true) {
59+
!extension_loaded('dom') => throw new RuntimeException('The DOM extension is not loaded.'),
60+
!in_array($xmlClass, [XMLDocument::class , DOMDocument::class], true) => throw new ValueError('The xml class is invalid.'),
61+
XMLDocument::class === $xmlClass && class_exists(XMLDocument::class) => XMLDocument::createEmpty(),
62+
default => new DOMDocument(encoding: 'UTF-8'),
63+
};
4864
}
4965

5066
public static function create(): self
@@ -67,15 +83,10 @@ public function __construct()
6783
*/
6884
public function convert(iterable $records): DOMDocument|XMLDocument
6985
{
70-
if (null !== $this->formatter) {
71-
$records = MapIterator::fromIterable($records, $this->formatter);
72-
}
86+
$document = self::newXmlDocument($this->xmlClass);
87+
$document->appendChild($this->import($records, $document));
7388

74-
$doc = self::supportsModerDom() ? XMLDocument::createEmpty() : new DOMDocument(version:'1.0', encoding:'UTF-8');
75-
$node = $this->import($records, $doc);
76-
$doc->appendChild($node);
77-
78-
return $doc;
89+
return $document;
7990
}
8091

8192
/**
@@ -111,6 +122,10 @@ public function download(iterable $records, ?string $filename = null, string $en
111122
*/
112123
public function import(iterable $records, DOMDocument|XMLDocument $doc): DOMElement|Element
113124
{
125+
if (null !== $this->formatter) {
126+
$records = MapIterator::fromIterable($records, $this->formatter);
127+
}
128+
114129
$root = $doc->createElement($this->root_name);
115130
foreach ($records as $offset => $record) {
116131
$root->appendChild($this->recordToElement($doc, $record, $offset));
@@ -123,12 +138,11 @@ public function import(iterable $records, DOMDocument|XMLDocument $doc): DOMElem
123138
* Converts a CSV record into a DOMElement and
124139
* adds its offset as DOMElement attribute.
125140
*/
126-
protected function recordToElement(DOMDocument|XMLDocument $doc, array $record, int $offset): DOMElement|Element
141+
protected function recordToElement(DOMDocument|XMLDocument $document, array $record, int $offset): DOMElement|Element
127142
{
128-
$node = $doc->createElement($this->record_name);
143+
$node = $document->createElement($this->record_name);
129144
foreach ($record as $node_name => $value) {
130-
$item = $this->fieldToElement($doc, (string) $value, $node_name);
131-
$node->appendChild($item);
145+
$node->appendChild($this->fieldToElement($document, (string) $value, $node_name));
132146
}
133147

134148
if ('' !== $this->offset_attr) {
@@ -144,10 +158,10 @@ protected function recordToElement(DOMDocument|XMLDocument $doc, array $record,
144158
* Converts the CSV item into a DOMElement and adds the item offset
145159
* as attribute to the returned DOMElement
146160
*/
147-
protected function fieldToElement(DOMDocument|XMLDocument $doc, string $value, int|string $node_name): DOMElement|Element
161+
protected function fieldToElement(DOMDocument|XMLDocument $document, string $value, int|string $node_name): DOMElement|Element
148162
{
149-
$item = $doc->createElement($this->field_name);
150-
$item->appendChild($doc->createTextNode($value));
163+
$item = $document->createElement($this->field_name);
164+
$item->appendChild($document->createTextNode($value));
151165

152166
if ('' !== $this->column_attr) {
153167
$item->setAttribute($this->column_attr, (string) $node_name);
@@ -183,31 +197,62 @@ public function formatter(?callable $formatter): self
183197
}
184198

185199
/**
186-
* Filters XML element name.
200+
* XML Record element setter.
187201
*
188-
* @throws DOMException If the Element name is invalid
202+
* @throws DOMException
189203
*/
190-
protected function filterElementName(string $value): string
204+
public function recordElement(string $node_name, string $record_offset_attribute_name = ''): self
191205
{
192-
$document = self::supportsModerDom() ? XmlDocument::createEmpty() : new DOMDocument('1.0');
206+
$clone = clone $this;
207+
$clone->record_name = $this->filterElementName($node_name);
208+
$clone->offset_attr = $this->filterAttributeName($record_offset_attribute_name);
193209

194-
return $document->createElement($value)->tagName;
210+
return $clone;
195211
}
196212

197213
/**
198-
* XML Record element setter.
214+
* XML Field element setter.
199215
*
200216
* @throws DOMException
201217
*/
202-
public function recordElement(string $node_name, string $record_offset_attribute_name = ''): self
218+
public function fieldElement(string $node_name, string $fieldname_attribute_name = ''): self
203219
{
204220
$clone = clone $this;
205-
$clone->record_name = $this->filterElementName($node_name);
206-
$clone->offset_attr = $this->filterAttributeName($record_offset_attribute_name);
221+
$clone->field_name = $this->filterElementName($node_name);
222+
$clone->column_attr = $this->filterAttributeName($fieldname_attribute_name);
223+
224+
return $clone;
225+
}
226+
227+
/**
228+
* @param class-string $xmlClass
229+
*/
230+
public function xmlClass(string $xmlClass): self
231+
{
232+
if (!in_array($xmlClass, [XMLDocument::class , DOMDocument::class], true)) {
233+
throw new ValueError('The xml class is invalid.');
234+
}
235+
236+
if ($this->xmlClass === $xmlClass) {
237+
return $this;
238+
}
239+
240+
$clone = clone $this;
241+
$clone->xmlClass = $xmlClass;
207242

208243
return $clone;
209244
}
210245

246+
/**
247+
* Filters XML element name.
248+
*
249+
* @throws DOMException If the Element name is invalid
250+
*/
251+
protected function filterElementName(string $value): string
252+
{
253+
return self::newXmlDocument(XMLDocument::class)->createElement($value)->tagName;
254+
}
255+
211256
/**
212257
* Filters XML attribute name.
213258
*
@@ -221,24 +266,9 @@ protected function filterAttributeName(string $value): string
221266
return $value;
222267
}
223268

224-
$document = self::supportsModerDom() ? XmlDocument::createEmpty() : new DOMDocument('1.0');
225-
$element = $document->createElement('foo');
269+
$element = self::newXmlDocument(XMLDocument::class)->createElement('foo');
226270
$element->setAttribute($value, 'foo');
227271

228272
return $value;
229273
}
230-
231-
/**
232-
* XML Field element setter.
233-
*
234-
* @throws DOMException
235-
*/
236-
public function fieldElement(string $node_name, string $fieldname_attribute_name = ''): self
237-
{
238-
$clone = clone $this;
239-
$clone->field_name = $this->filterElementName($node_name);
240-
$clone->column_attr = $this->filterAttributeName($fieldname_attribute_name);
241-
242-
return $clone;
243-
}
244274
}

src/XMLConverterTest.php

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313

1414
namespace League\Csv;
1515

16+
use Dom\XMLDocument;
1617
use DOMDocument;
17-
use DOMElement;
1818
use DOMException;
1919
use PHPUnit\Framework\Attributes\Group;
2020
use PHPUnit\Framework\TestCase;
2121

2222
use function array_map;
23+
use function class_exists;
2324

2425
#[Group('converter')]
2526
final class XMLConverterTest extends TestCase
@@ -44,24 +45,19 @@ public function testToXML(): void
4445
->fieldElement('field', 'name')
4546
;
4647

47-
$dom = $converter->convert($records);
48+
$dom = $converter->convert($records, XMLDocument::class);
4849
$record_list = $dom->getElementsByTagName('record');
49-
50-
/** @var DOMElement $record_node */
5150
$record_node = $record_list->item(0);
52-
5351
$field_list = $dom->getElementsByTagName('field');
54-
55-
/** @var DOMElement $field_node */
5652
$field_node = $field_list->item(0);
5753

58-
/** @var DOMElement $baseTag */
59-
$baseTag = $dom->documentElement;
60-
61-
self::assertSame('csv', $baseTag->tagName);
54+
self::assertNotNull($dom->documentElement);
55+
self::assertSame('csv', $dom->documentElement->tagName);
6256
self::assertEquals(5, $record_list->length);
57+
self::assertNotNull($record_node);
6358
self::assertTrue($record_node->hasAttribute('offset'));
6459
self::assertEquals(20, $field_list->length);
60+
self::assertNotNull($field_node);
6561
self::assertTrue($field_node->hasAttribute('name'));
6662
}
6763

@@ -93,7 +89,7 @@ public function testImport(): void
9389
->fieldElement('field', 'name')
9490
;
9591

96-
$doc = new DOMDocument('1.0');
92+
$doc = class_exists(XMLDocument::class) ? XMLDocument::createEmpty() : new DOMDocument('1.0');
9793
$element = $converter->import($records, $doc);
9894

9995
self::assertCount(0, $doc->childNodes);

0 commit comments

Comments
 (0)