Skip to content

Commit 8fa58be

Browse files
committed
Add new XMLElementEntry as replacement for XMLNodeEntry
1 parent 2f920cb commit 8fa58be

File tree

10 files changed

+320
-8
lines changed

10 files changed

+320
-8
lines changed

src/core/etl/src/Flow/ETL/DSL/functions.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@
2020
use Flow\ETL\PHP\Type\Logical\List\ListElement;
2121
use Flow\ETL\PHP\Type\Logical\Map\{MapKey, MapValue};
2222
use Flow\ETL\PHP\Type\Logical\Structure\StructureElement;
23-
use Flow\ETL\PHP\Type\Logical\{DateTimeType, JsonType, ListType, MapType, StructureType, UuidType, XMLNodeType, XMLType};
23+
use Flow\ETL\PHP\Type\Logical\{DateTimeType,
24+
JsonType,
25+
ListType,
26+
MapType,
27+
StructureType,
28+
UuidType,
29+
XMLElementType,
30+
XMLNodeType,
31+
XMLType};
2432
use Flow\ETL\PHP\Type\Native\{ArrayType, CallableType, EnumType, NullType, ObjectType, ResourceType, ScalarType};
2533
use Flow\ETL\PHP\Type\{Type, TypeDetector};
2634
use Flow\ETL\Row\Factory\NativeEntryFactory;
@@ -268,6 +276,14 @@ function xml_node_entry(string $name, ?\DOMNode $value) : Entry\XMLNodeEntry
268276
return new Entry\XMLNodeEntry($name, $value);
269277
}
270278

279+
/**
280+
* @param array<int|string, mixed> $attributes
281+
*/
282+
function xml_element_entry(string $name, \DOMElement|string|null $value, array $attributes = []) : Entry\XMLElementEntry
283+
{
284+
return new Entry\XMLElementEntry($name, $value);
285+
}
286+
271287
function entries(Entry ...$entries) : Row\Entries
272288
{
273289
return new Row\Entries(...$entries);
@@ -357,6 +373,11 @@ function type_xml_node(bool $nullable = false) : XMLNodeType
357373
return new XMLNodeType($nullable);
358374
}
359375

376+
function type_xml_element(bool $nullable = false) : XMLElementType
377+
{
378+
return new XMLElementType($nullable);
379+
}
380+
360381
function type_uuid(bool $nullable = false) : UuidType
361382
{
362383
return new UuidType($nullable);
@@ -1072,6 +1093,11 @@ function xml_node_schema(string $name, bool $nullable = false, ?Schema\Metadata
10721093
return Definition::xml_node($name, $nullable, $metadata);
10731094
}
10741095

1096+
function xml_element_schema(string $name, bool $nullable = false, ?Schema\Metadata $metadata = null) : Definition
1097+
{
1098+
return Definition::xml_element($name, $nullable, $metadata);
1099+
}
1100+
10751101
function struct_schema(string $name, StructureType $type, ?Schema\Metadata $metadata = null) : Definition
10761102
{
10771103
return Definition::structure($name, $type, $metadata);

src/core/etl/src/Flow/ETL/Formatter/ASCII/ASCIIValue.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private function stringValue() : string
8989
if ($val instanceof Entry) {
9090
$this->stringValue = $val->toString();
9191

92-
if ($val instanceof Entry\XMLEntry || $val instanceof Entry\XMLNodeEntry) {
92+
if ($val instanceof Entry\XMLEntry || $val instanceof Entry\XMLElementEntry || $val instanceof Entry\XMLNodeEntry) {
9393
$this->stringValue = \str_replace("\n", '', $this->stringValue);
9494
}
9595

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\PHP\Type\Logical;
6+
7+
use Flow\ETL\Exception\InvalidArgumentException;
8+
use Flow\ETL\PHP\Type\Native\NullType;
9+
use Flow\ETL\PHP\Type\Type;
10+
11+
final class XMLElementType implements LogicalType
12+
{
13+
public function __construct(private readonly bool $nullable)
14+
{
15+
}
16+
17+
public static function fromArray(array $data) : self
18+
{
19+
return new self($data['nullable'] ?? false);
20+
}
21+
22+
public function isEqual(Type $type) : bool
23+
{
24+
return $type instanceof self;
25+
}
26+
27+
public function isValid(mixed $value) : bool
28+
{
29+
if ($this->nullable && $value === null) {
30+
return true;
31+
}
32+
33+
if ($value instanceof \DOMElement) {
34+
return true;
35+
}
36+
37+
return false;
38+
}
39+
40+
public function makeNullable(bool $nullable) : self
41+
{
42+
return new self($nullable);
43+
}
44+
45+
public function merge(Type $type) : self
46+
{
47+
if ($type instanceof NullType) {
48+
return $this->makeNullable(true);
49+
}
50+
51+
if (!$type instanceof self) {
52+
throw new InvalidArgumentException('Cannot merge different types, ' . $this->toString() . ' and ' . $type->toString());
53+
}
54+
55+
return new self($this->nullable || $type->nullable());
56+
}
57+
58+
public function normalize() : array
59+
{
60+
return [
61+
'type' => 'xml_element',
62+
'nullable' => $this->nullable,
63+
];
64+
}
65+
66+
public function nullable() : bool
67+
{
68+
return $this->nullable;
69+
}
70+
71+
public function toString() : string
72+
{
73+
return ($this->nullable ? '?' : '') . 'xml_element';
74+
}
75+
}

src/core/etl/src/Flow/ETL/Partition.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@
55
namespace Flow\ETL;
66

77
use Flow\ETL\Exception\InvalidArgumentException;
8-
use Flow\ETL\Row\Entry\{ArrayEntry, DateTimeEntry, JsonEntry, ListEntry, MapEntry, ObjectEntry, StructureEntry, XMLEntry, XMLNodeEntry};
8+
use Flow\ETL\Row\Entry\{ArrayEntry,
9+
DateTimeEntry,
10+
JsonEntry,
11+
ListEntry,
12+
MapEntry,
13+
ObjectEntry,
14+
StructureEntry,
15+
XMLElementEntry,
16+
XMLEntry,
17+
XMLNodeEntry};
918
use Flow\ETL\Row\{EntryReference, Reference};
1019

1120
final class Partition
@@ -73,7 +82,7 @@ public static function valueFromRow(Reference $ref, Row $row) : mixed
7382

7483
return match ($entry::class) {
7584
DateTimeEntry::class => $entry->value()?->format('Y-m-d'),
76-
XMLEntry::class, XMLNodeEntry::class, JsonEntry::class, ObjectEntry::class, ListEntry::class, StructureEntry::class, MapEntry::class, ArrayEntry::class => throw new InvalidArgumentException($entry::class . ' can\'t be used as a partition'),
85+
XMLEntry::class, XMLElementEntry::class, XMLNodeEntry::class, JsonEntry::class, ObjectEntry::class, ListEntry::class, StructureEntry::class, MapEntry::class, ArrayEntry::class => throw new InvalidArgumentException($entry::class . ' can\'t be used as a partition'),
7786
default => $entry->toString(),
7887
};
7988
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Row\Entry;
6+
7+
use function Flow\ETL\DSL\type_xml_element;
8+
use Flow\ETL\Exception\InvalidArgumentException;
9+
use Flow\ETL\PHP\Type\Logical\XMLElementType;
10+
use Flow\ETL\PHP\Type\Type;
11+
use Flow\ETL\Row\Schema\Definition;
12+
use Flow\ETL\Row\{Entry, Reference};
13+
14+
/**
15+
* @implements Entry<?\DOMElement>
16+
*/
17+
final class XMLElementEntry implements Entry
18+
{
19+
use EntryRef;
20+
21+
private readonly XMLElementType $type;
22+
23+
private readonly ?\DOMElement $value;
24+
25+
/**
26+
* @param array<int|string, mixed> $attributes
27+
*/
28+
public function __construct(private readonly string $name, \DOMElement|string|null $value, array $attributes = [])
29+
{
30+
if (\is_string($value)) {
31+
$this->value = (new \DOMDocument())->createElement($value);
32+
} elseif ($value instanceof \DOMElement) {
33+
$this->value = (new \DOMDocument())->createElement($value->tagName, $value->nodeValue ?: '');
34+
} else {
35+
$this->value = $value;
36+
}
37+
38+
if (null !== $this->value) {
39+
foreach ($attributes as $name => $attribute) {
40+
if (!is_scalar($attribute)) {
41+
throw InvalidArgumentException::because('Attribute name must be a scalar, given: ' . \gettype($attribute));
42+
}
43+
44+
$this->value->setAttribute((string) $name, (string) $attribute);
45+
}
46+
}
47+
48+
$this->type = type_xml_element($this->value === null);
49+
}
50+
51+
public function __serialize() : array
52+
{
53+
return [
54+
'name' => $this->name,
55+
'value' => $this->value === null ? null : \base64_encode(\gzcompress($this->toString()) ?: ''),
56+
'type' => $this->type,
57+
];
58+
}
59+
60+
public function __toString() : string
61+
{
62+
if ($this->value === null) {
63+
return '';
64+
}
65+
66+
/* @phpstan-ignore-next-line */
67+
return $this->value->ownerDocument->saveXML($this->value);
68+
}
69+
70+
public function __unserialize(array $data) : void
71+
{
72+
$this->name = $data['name'];
73+
$this->type = $data['type'];
74+
75+
if ($data['value'] === null) {
76+
$this->value = null;
77+
78+
return;
79+
}
80+
81+
$element = \gzuncompress(\base64_decode($data['value'], true) ?: '') ?: '';
82+
83+
$domDocument = new \DOMDocument();
84+
@$domDocument->loadXML($element);
85+
86+
/* @phpstan-ignore-next-line */
87+
$this->value = (new \DOMDocument())->createElement($domDocument->documentElement->tagName, $domDocument->documentElement->nodeValue);
88+
}
89+
90+
public function definition() : Definition
91+
{
92+
return Definition::xml_element($this->ref(), $this->type->nullable());
93+
}
94+
95+
public function is(Reference|string $name) : bool
96+
{
97+
if ($name instanceof Reference) {
98+
return $this->name === $name->name();
99+
}
100+
101+
return $this->name === $name;
102+
}
103+
104+
public function isEqual(Entry $entry) : bool
105+
{
106+
if (!$entry instanceof self || !$this->is($entry->name())) {
107+
return false;
108+
}
109+
110+
if (!$this->type->isEqual($entry->type)) {
111+
return false;
112+
}
113+
114+
return $this->value?->C14N() === $entry->value?->C14N();
115+
}
116+
117+
public function map(callable $mapper) : Entry
118+
{
119+
return new self($this->name, $mapper($this->value()));
120+
}
121+
122+
public function name() : string
123+
{
124+
return $this->name;
125+
}
126+
127+
public function rename(string $name) : Entry
128+
{
129+
return new self($name, $this->value);
130+
}
131+
132+
public function toString() : string
133+
{
134+
if ($this->value === null) {
135+
return '';
136+
}
137+
138+
/* @phpstan-ignore-next-line */
139+
return $this->value->ownerDocument->saveXML($this->value);
140+
}
141+
142+
public function type() : Type
143+
{
144+
return $this->type;
145+
}
146+
147+
public function value() : ?\DOMElement
148+
{
149+
return $this->value;
150+
}
151+
}

src/core/etl/src/Flow/ETL/Row/Entry/XMLNodeEntry.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
/**
1414
* @implements Entry<?\DOMNode>
15+
*
16+
* @deprecated Please use XMLElementEntry
1517
*/
1618
final class XMLNodeEntry implements Entry
1719
{

src/core/etl/src/Flow/ETL/Row/Schema/Definition.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@
44

55
namespace Flow\ETL\Row\Schema;
66

7-
use function Flow\ETL\DSL\{type_array, type_boolean, type_datetime, type_enum, type_float, type_int, type_json, type_list, type_string, type_uuid, type_xml, type_xml_node};
7+
use function Flow\ETL\DSL\{type_array,
8+
type_boolean,
9+
type_datetime,
10+
type_enum,
11+
type_float,
12+
type_int,
13+
type_json,
14+
type_list,
15+
type_string,
16+
type_uuid,
17+
type_xml,
18+
type_xml_element,
19+
type_xml_node};
820
use Flow\ETL\Exception\{InvalidArgumentException, RuntimeException};
921
use Flow\ETL\PHP\Type\Logical\{ListType, MapType, StructureType};
1022
use Flow\ETL\PHP\Type\Native\ObjectType;
@@ -107,6 +119,7 @@ public static function fromArray(array $definition) : self
107119
'uuid' => UuidEntry::class,
108120
'xml' => XMLEntry::class,
109121
'xml_node' => XMLNodeEntry::class,
122+
'xml_element' => Entry\XMLElementEntry::class,
110123
default => throw new InvalidArgumentException(\sprintf('Unknown entry type "%s"', \json_encode($definition['type']))),
111124
},
112125
TypeFactory::fromArray($definition['type']),
@@ -179,6 +192,11 @@ public static function xml(string|Reference $entry, bool $nullable = false, ?Met
179192
return new self($entry, XMLEntry::class, type_xml($nullable), $metadata);
180193
}
181194

195+
public static function xml_element(string|Reference $entry, bool $nullable = false, ?Metadata $metadata = null) : self
196+
{
197+
return new self($entry, Entry\XMLElementEntry::class, type_xml_element($nullable), $metadata);
198+
}
199+
182200
public static function xml_node(string|Reference $entry, bool $nullable = false, ?Metadata $metadata = null) : self
183201
{
184202
return new self($entry, XMLNodeEntry::class, type_xml_node($nullable), $metadata);

src/core/etl/src/Flow/ETL/Transformer/OrderEntries/TypePriorities.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ final class TypePriorities
2727
Entry\ObjectEntry::class => 12,
2828
Entry\StructureEntry::class => 13,
2929
Entry\XMLEntry::class => 14,
30-
Entry\XMLNodeEntry::class => 15,
30+
Entry\XMLElementEntry::class => 15,
31+
Entry\XMLNodeEntry::class => 16,
3132
];
3233

3334
/**

0 commit comments

Comments
 (0)