Skip to content

Commit

Permalink
Add new XMLElementEntry as replacement for XMLNodeEntry
Browse files Browse the repository at this point in the history
  • Loading branch information
stloyd committed May 6, 2024
1 parent 2f920cb commit 8fa58be
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 8 deletions.
28 changes: 27 additions & 1 deletion src/core/etl/src/Flow/ETL/DSL/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@
use Flow\ETL\PHP\Type\Logical\List\ListElement;
use Flow\ETL\PHP\Type\Logical\Map\{MapKey, MapValue};
use Flow\ETL\PHP\Type\Logical\Structure\StructureElement;
use Flow\ETL\PHP\Type\Logical\{DateTimeType, JsonType, ListType, MapType, StructureType, UuidType, XMLNodeType, XMLType};
use Flow\ETL\PHP\Type\Logical\{DateTimeType,
JsonType,
ListType,
MapType,
StructureType,
UuidType,
XMLElementType,
XMLNodeType,
XMLType};
use Flow\ETL\PHP\Type\Native\{ArrayType, CallableType, EnumType, NullType, ObjectType, ResourceType, ScalarType};
use Flow\ETL\PHP\Type\{Type, TypeDetector};
use Flow\ETL\Row\Factory\NativeEntryFactory;
Expand Down Expand Up @@ -268,6 +276,14 @@ function xml_node_entry(string $name, ?\DOMNode $value) : Entry\XMLNodeEntry
return new Entry\XMLNodeEntry($name, $value);

Check failure on line 276 in src/core/etl/src/Flow/ETL/DSL/functions.php

View workflow job for this annotation

GitHub Actions / Static Analyze (locked, 8.1, ubuntu-latest)

DeprecatedClass

src/core/etl/src/Flow/ETL/DSL/functions.php:276:12: DeprecatedClass: Flow\ETL\Row\Entry\XMLNodeEntry is marked deprecated (see https://psalm.dev/098)
}

/**
* @param array<int|string, mixed> $attributes
*/
function xml_element_entry(string $name, \DOMElement|string|null $value, array $attributes = []) : Entry\XMLElementEntry
{
return new Entry\XMLElementEntry($name, $value);
}

function entries(Entry ...$entries) : Row\Entries
{
return new Row\Entries(...$entries);
Expand Down Expand Up @@ -357,6 +373,11 @@ function type_xml_node(bool $nullable = false) : XMLNodeType
return new XMLNodeType($nullable);
}

function type_xml_element(bool $nullable = false) : XMLElementType
{
return new XMLElementType($nullable);
}

function type_uuid(bool $nullable = false) : UuidType
{
return new UuidType($nullable);
Expand Down Expand Up @@ -1072,6 +1093,11 @@ function xml_node_schema(string $name, bool $nullable = false, ?Schema\Metadata
return Definition::xml_node($name, $nullable, $metadata);
}

function xml_element_schema(string $name, bool $nullable = false, ?Schema\Metadata $metadata = null) : Definition
{
return Definition::xml_element($name, $nullable, $metadata);
}

function struct_schema(string $name, StructureType $type, ?Schema\Metadata $metadata = null) : Definition
{
return Definition::structure($name, $type, $metadata);
Expand Down
2 changes: 1 addition & 1 deletion src/core/etl/src/Flow/ETL/Formatter/ASCII/ASCIIValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private function stringValue() : string
if ($val instanceof Entry) {
$this->stringValue = $val->toString();

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

Expand Down
75 changes: 75 additions & 0 deletions src/core/etl/src/Flow/ETL/PHP/Type/Logical/XMLElementType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\PHP\Type\Logical;

use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\PHP\Type\Native\NullType;
use Flow\ETL\PHP\Type\Type;

final class XMLElementType implements LogicalType
{
public function __construct(private readonly bool $nullable)
{
}

public static function fromArray(array $data) : self
{
return new self($data['nullable'] ?? false);
}

public function isEqual(Type $type) : bool
{
return $type instanceof self;
}

public function isValid(mixed $value) : bool
{
if ($this->nullable && $value === null) {
return true;
}

if ($value instanceof \DOMElement) {
return true;
}

return false;
}

public function makeNullable(bool $nullable) : self
{
return new self($nullable);
}

public function merge(Type $type) : self
{
if ($type instanceof NullType) {
return $this->makeNullable(true);
}

if (!$type instanceof self) {
throw new InvalidArgumentException('Cannot merge different types, ' . $this->toString() . ' and ' . $type->toString());
}

return new self($this->nullable || $type->nullable());
}

public function normalize() : array
{
return [
'type' => 'xml_element',
'nullable' => $this->nullable,
];
}

public function nullable() : bool
{
return $this->nullable;
}

public function toString() : string
{
return ($this->nullable ? '?' : '') . 'xml_element';
}
}
13 changes: 11 additions & 2 deletions src/core/etl/src/Flow/ETL/Partition.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
namespace Flow\ETL;

use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\Row\Entry\{ArrayEntry, DateTimeEntry, JsonEntry, ListEntry, MapEntry, ObjectEntry, StructureEntry, XMLEntry, XMLNodeEntry};
use Flow\ETL\Row\Entry\{ArrayEntry,
DateTimeEntry,
JsonEntry,
ListEntry,
MapEntry,
ObjectEntry,
StructureEntry,
XMLElementEntry,
XMLEntry,
XMLNodeEntry};
use Flow\ETL\Row\{EntryReference, Reference};

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

return match ($entry::class) {
DateTimeEntry::class => $entry->value()?->format('Y-m-d'),
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'),
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'),

Check failure on line 85 in src/core/etl/src/Flow/ETL/Partition.php

View workflow job for this annotation

GitHub Actions / Static Analyze (locked, 8.1, ubuntu-latest)

DeprecatedClass

src/core/etl/src/Flow/ETL/Partition.php:85:54: DeprecatedClass: Class Flow\ETL\Row\Entry\XMLNodeEntry is deprecated (see https://psalm.dev/098)
default => $entry->toString(),
};
}
Expand Down
151 changes: 151 additions & 0 deletions src/core/etl/src/Flow/ETL/Row/Entry/XMLElementEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Row\Entry;

use function Flow\ETL\DSL\type_xml_element;
use Flow\ETL\Exception\InvalidArgumentException;
use Flow\ETL\PHP\Type\Logical\XMLElementType;
use Flow\ETL\PHP\Type\Type;
use Flow\ETL\Row\Schema\Definition;
use Flow\ETL\Row\{Entry, Reference};

/**
* @implements Entry<?\DOMElement>
*/
final class XMLElementEntry implements Entry
{
use EntryRef;

private readonly XMLElementType $type;

private readonly ?\DOMElement $value;

/**
* @param array<int|string, mixed> $attributes
*/
public function __construct(private readonly string $name, \DOMElement|string|null $value, array $attributes = [])
{
if (\is_string($value)) {
$this->value = (new \DOMDocument())->createElement($value);
} elseif ($value instanceof \DOMElement) {
$this->value = (new \DOMDocument())->createElement($value->tagName, $value->nodeValue ?: '');
} else {
$this->value = $value;
}

if (null !== $this->value) {
foreach ($attributes as $name => $attribute) {
if (!is_scalar($attribute)) {
throw InvalidArgumentException::because('Attribute name must be a scalar, given: ' . \gettype($attribute));
}

$this->value->setAttribute((string) $name, (string) $attribute);
}
}

$this->type = type_xml_element($this->value === null);
}

public function __serialize() : array
{
return [
'name' => $this->name,
'value' => $this->value === null ? null : \base64_encode(\gzcompress($this->toString()) ?: ''),
'type' => $this->type,
];
}

public function __toString() : string
{
if ($this->value === null) {
return '';
}

/* @phpstan-ignore-next-line */
return $this->value->ownerDocument->saveXML($this->value);
}

public function __unserialize(array $data) : void
{
$this->name = $data['name'];
$this->type = $data['type'];

if ($data['value'] === null) {
$this->value = null;

return;
}

$element = \gzuncompress(\base64_decode($data['value'], true) ?: '') ?: '';

$domDocument = new \DOMDocument();
@$domDocument->loadXML($element);

/* @phpstan-ignore-next-line */
$this->value = (new \DOMDocument())->createElement($domDocument->documentElement->tagName, $domDocument->documentElement->nodeValue);
}

public function definition() : Definition
{
return Definition::xml_element($this->ref(), $this->type->nullable());
}

public function is(Reference|string $name) : bool
{
if ($name instanceof Reference) {
return $this->name === $name->name();
}

return $this->name === $name;
}

public function isEqual(Entry $entry) : bool
{
if (!$entry instanceof self || !$this->is($entry->name())) {
return false;
}

if (!$this->type->isEqual($entry->type)) {
return false;
}

return $this->value?->C14N() === $entry->value?->C14N();
}

public function map(callable $mapper) : Entry
{
return new self($this->name, $mapper($this->value()));
}

public function name() : string
{
return $this->name;
}

public function rename(string $name) : Entry
{
return new self($name, $this->value);
}

public function toString() : string
{
if ($this->value === null) {
return '';
}

/* @phpstan-ignore-next-line */
return $this->value->ownerDocument->saveXML($this->value);
}

public function type() : Type
{
return $this->type;
}

public function value() : ?\DOMElement
{
return $this->value;
}
}
2 changes: 2 additions & 0 deletions src/core/etl/src/Flow/ETL/Row/Entry/XMLNodeEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

/**
* @implements Entry<?\DOMNode>
*
* @deprecated Please use XMLElementEntry
*/
final class XMLNodeEntry implements Entry
{
Expand Down
20 changes: 19 additions & 1 deletion src/core/etl/src/Flow/ETL/Row/Schema/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@

namespace Flow\ETL\Row\Schema;

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};
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_element,
type_xml_node};
use Flow\ETL\Exception\{InvalidArgumentException, RuntimeException};
use Flow\ETL\PHP\Type\Logical\{ListType, MapType, StructureType};
use Flow\ETL\PHP\Type\Native\ObjectType;
Expand Down Expand Up @@ -107,6 +119,7 @@ public static function fromArray(array $definition) : self
'uuid' => UuidEntry::class,
'xml' => XMLEntry::class,
'xml_node' => XMLNodeEntry::class,

Check failure on line 121 in src/core/etl/src/Flow/ETL/Row/Schema/Definition.php

View workflow job for this annotation

GitHub Actions / Static Analyze (locked, 8.1, ubuntu-latest)

DeprecatedClass

src/core/etl/src/Flow/ETL/Row/Schema/Definition.php:121:31: DeprecatedClass: Class Flow\ETL\Row\Entry\XMLNodeEntry is deprecated (see https://psalm.dev/098)
'xml_element' => Entry\XMLElementEntry::class,
default => throw new InvalidArgumentException(\sprintf('Unknown entry type "%s"', \json_encode($definition['type']))),
},
TypeFactory::fromArray($definition['type']),
Expand Down Expand Up @@ -179,6 +192,11 @@ public static function xml(string|Reference $entry, bool $nullable = false, ?Met
return new self($entry, XMLEntry::class, type_xml($nullable), $metadata);
}

public static function xml_element(string|Reference $entry, bool $nullable = false, ?Metadata $metadata = null) : self
{
return new self($entry, Entry\XMLElementEntry::class, type_xml_element($nullable), $metadata);
}

public static function xml_node(string|Reference $entry, bool $nullable = false, ?Metadata $metadata = null) : self
{
return new self($entry, XMLNodeEntry::class, type_xml_node($nullable), $metadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ final class TypePriorities
Entry\ObjectEntry::class => 12,
Entry\StructureEntry::class => 13,
Entry\XMLEntry::class => 14,
Entry\XMLNodeEntry::class => 15,
Entry\XMLElementEntry::class => 15,
Entry\XMLNodeEntry::class => 16,

Check failure on line 31 in src/core/etl/src/Flow/ETL/Transformer/OrderEntries/TypePriorities.php

View workflow job for this annotation

GitHub Actions / Static Analyze (locked, 8.1, ubuntu-latest)

DeprecatedClass

src/core/etl/src/Flow/ETL/Transformer/OrderEntries/TypePriorities.php:31:9: DeprecatedClass: Class Flow\ETL\Row\Entry\XMLNodeEntry is deprecated (see https://psalm.dev/098)
];

/**
Expand Down
Loading

0 comments on commit 8fa58be

Please sign in to comment.