Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/codecov-report/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ runs:
directory: ./var/phpunit/coverage/clover

- name: Upload code coverage (pull request)
if: ${{ github.event_name == 'pull_request' && !cancelled() && inputs.php-version == '8.2' && inputs.dependencies == 'locked' }}
if: ${{ github.event_name == 'pull_request' && !cancelled() && inputs.dependencies == 'locked' }}
uses: codecov/codecov-action@v5
with:
token: ${{ inputs.token }}
directory: ./var/phpunit/coverage/clover
commit_parent: ${{ env.codecov_base_commit_sha }}

- name: Upload test results (pull request)
if: ${{ github.event_name == 'pull_request' && !cancelled() && inputs.php-version == '8.2' && inputs.dependencies == 'locked' }}
if: ${{ github.event_name == 'pull_request' && !cancelled() && inputs.dependencies == 'locked' }}
uses: codecov/test-results-action@v1
with:
token: ${{ inputs.token }}
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ parameters:

ignoreErrors:
-
message: '#Dom\\(HTMLDocument|HTMLElement|Element)#i'
message: '#Dom\\(CharacterData|HTMLDocument|HTMLElement|Element)#i'
identifier: class.notFound

includes:
Expand Down
55 changes: 55 additions & 0 deletions src/core/etl/src/Flow/ETL/Function/DOMElementNextSibling.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Function;

use function Flow\Types\DSL\type_instance_of;
use Dom\{CharacterData, HTMLElement};
use Flow\ETL\{Exception\InvalidArgumentException, FlowContext, Row};

final class DOMElementNextSibling extends ScalarFunctionChain
{
public function __construct(
private readonly ScalarFunction|\DOMNode|CharacterData|HTMLElement $element,
private readonly bool $allowOnlyElement,
) {
}

public function eval(Row $row, FlowContext $context) : \DOMNode|CharacterData|HTMLElement|null
{
$types = [
type_instance_of(\DOMNode::class),
];

if (\class_exists('\Dom\HTMLElement')) {
$types[] = type_instance_of(CharacterData::class);
$types[] = type_instance_of(HTMLElement::class);
}

$node = (new Parameter($this->element))->as(
$row,
$context,
...$types
);

if ($node instanceof \DOMDocument) {
$node = $node->documentElement;
}

if ($this->allowOnlyElement) {
if (!$node instanceof \DOMElement) {
return $context->functions()->invalidResult(new InvalidArgumentException('DOMElementNextSibling with option $allowOnlyElement requires DOMElement.'));
}

if ($node instanceof CharacterData) {
return $context->functions()->invalidResult(new InvalidArgumentException('DOMElementNextSibling with option $allowOnlyElement requires HTMLElement.'));
}

return $node->nextElementSibling;
}

/* @phpstan-ignore-next-line */
return $node->nextSibling;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Function;

use function Flow\Types\DSL\type_instance_of;
use Dom\{CharacterData, HTMLElement};
use Flow\ETL\{Exception\InvalidArgumentException, FlowContext, Row};

final class DOMElementPreviousSibling extends ScalarFunctionChain
{
public function __construct(
private readonly ScalarFunction|\DOMNode|CharacterData|HTMLElement $element,
private readonly bool $allowOnlyElement,
) {
}

public function eval(Row $row, FlowContext $context) : \DOMNode|CharacterData|HTMLElement|null
{
$types = [
type_instance_of(\DOMNode::class),
];

if (\class_exists('\Dom\HTMLElement')) {
$types[] = type_instance_of(CharacterData::class);
$types[] = type_instance_of(HTMLElement::class);
}

$node = (new Parameter($this->element))->as(
$row,
$context,
...$types
);

if ($node instanceof \DOMDocument) {
$node = $node->documentElement;
}

if ($this->allowOnlyElement) {
if (!$node instanceof \DOMElement) {
return $context->functions()->invalidResult(new InvalidArgumentException('DOMElementPreviousSibling with option $allowOnlyElement requires DOMElement.'));
}

if ($node instanceof CharacterData) {
return $context->functions()->invalidResult(new InvalidArgumentException('DOMElementPreviousSibling with option $allowOnlyElement requires HTMLElement.'));
}

return $node->previousElementSibling;
}

/* @phpstan-ignore-next-line */
return $node->previousSibling;
}
}
7 changes: 4 additions & 3 deletions src/core/etl/src/Flow/ETL/Function/DOMElementValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
namespace Flow\ETL\Function;

use function Flow\Types\DSL\{type_instance_of, type_list};
use Dom\HTMLElement;
use Dom\{CharacterData, HTMLElement};
use Flow\ETL\{FlowContext, Row};

final class DOMElementValue extends ScalarFunctionChain
{
public function __construct(private readonly ScalarFunction|\DOMNode|HTMLElement $node)
public function __construct(private readonly ScalarFunction|\DOMNode|CharacterData|HTMLElement $node)
{
}

Expand All @@ -22,6 +22,7 @@ public function eval(Row $row, FlowContext $context) : mixed
];

if (\class_exists('\Dom\HTMLElement')) {
$types[] = type_instance_of(CharacterData::class);
$types[] = type_instance_of(HTMLElement::class);
$types[] = type_list(type_instance_of(HTMLElement::class));
}
Expand All @@ -44,7 +45,7 @@ public function eval(Row $row, FlowContext $context) : mixed
return $node->nodeValue;
}

if ($node instanceof HTMLElement) {
if ($node instanceof CharacterData || $node instanceof HTMLElement) {
return $node->textContent;
}

Expand Down
10 changes: 10 additions & 0 deletions src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,21 @@ public function domElementAttributeValue(ScalarFunction|string $attribute) : DOM
return new DOMElementAttributeValue($this, $attribute);
}

public function domElementNextSibling(bool $allowOnlyElement = false) : DOMElementNextSibling
{
return new DOMElementNextSibling($this, $allowOnlyElement);
}

public function domElementParent() : DOMElementParent
{
return new DOMElementParent($this);
}

public function domElementPreviousSibling(bool $allowOnlyElement = false) : DOMElementPreviousSibling
{
return new DOMElementPreviousSibling($this, $allowOnlyElement);
}

public function domElementValue() : DOMElementValue
{
return new DOMElementValue($this);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Tests\Integration\Function;

use function Flow\ETL\DSL\{df, from_rows, html_element_entry, ref, row, rows, xml_element_entry};
use Flow\ETL\Tests\FlowTestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

final class DOMElementNextSiblingTest extends FlowTestCase
{
#[RequiresPhp('>= 8.4')]
public function test_dom_element_sibling_text_value() : void
{
$rows = df()
->read(from_rows(
rows(
row(
html_element_entry('html_element', '<article><section><h1>User Name</h1></section>01</article>')
)
)
))
->withEntry('user_details', ref('html_element')->htmlQuerySelector('section'))
->withEntry('user_name', ref('user_details')->htmlQuerySelector('h1')->domElementValue())
->withEntry('user_id', ref('user_details')->domElementNextSibling()->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => '01',
],
],
$rows->toArray()
);
}

#[RequiresPhp('>= 8.4')]
public function test_dom_element_sibling_text_value_when_only_element_is_allowed() : void
{
$rows = df()
->read(from_rows(
rows(
row(
html_element_entry('html_element', '<article><section><h1>User Name</h1></section>01</article>')
)
)
))
->withEntry('user_details', ref('html_element')->htmlQuerySelector('section'))
->withEntry('user_name', ref('user_details')->htmlQuerySelector('h1')->domElementValue())
->withEntry('user_id', ref('user_details')->domElementNextSibling(true)->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => null,
],
],
$rows->toArray()
);
}

public function test_xml_sibling_element_value() : void
{
$dom = new \DOMDocument();
$dom->loadXML('<user><name>User Name</name><number>01</number></user>');

$rows = df()
->read(
from_rows(
rows(
row(
xml_element_entry('xml_element', $dom->getElementsByTagName('name')->item(0))
)
)
)
)
->withEntry('user_name', ref('xml_element')->domElementValue())
->withEntry('user_id', ref('xml_element')->domElementNextSibling()->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => '01',
],
],
$rows->toArray()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Flow\ETL\Tests\Integration\Function;

use function Flow\ETL\DSL\{df, from_rows, html_element_entry, ref, row, rows, xml_element_entry};
use Flow\ETL\Tests\FlowTestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

final class DOMElementPreviousSiblingTest extends FlowTestCase
{
#[RequiresPhp('>= 8.4')]
public function test_dom_element_sibling_text_value() : void
{
$rows = df()
->read(from_rows(
rows(
row(
html_element_entry('html_element', '<article>01<section><h1>User Name</h1></section></article>')
)
)
))
->withEntry('user_details', ref('html_element')->htmlQuerySelector('section'))
->withEntry('user_name', ref('user_details')->htmlQuerySelector('h1')->domElementValue())
->withEntry('user_id', ref('user_details')->domElementPreviousSibling()->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => '01',
],
],
$rows->toArray()
);
}

#[RequiresPhp('>= 8.4')]
public function test_dom_element_sibling_text_value_when_only_element_is_allowed() : void
{
$rows = df()
->read(from_rows(
rows(
row(
html_element_entry('html_element', '<article>01<section><h1>User Name</h1></section></article>')
)
)
))
->withEntry('user_details', ref('html_element')->htmlQuerySelector('section'))
->withEntry('user_name', ref('user_details')->htmlQuerySelector('h1')->domElementValue())
->withEntry('user_id', ref('user_details')->domElementPreviousSibling(true)->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => null,
],
],
$rows->toArray()
);
}

public function test_xml_sibling_element_value() : void
{
$dom = new \DOMDocument();
$dom->loadXML('<user><name>User Name</name><number>01</number></user>');

$rows = df()
->read(
from_rows(
rows(
row(
xml_element_entry('xml_element', $dom->getElementsByTagName('number')->item(0))
)
)
)
)
->withEntry('user_id', ref('xml_element')->domElementValue())
->withEntry('user_name', ref('xml_element')->domElementPreviousSibling()->domElementValue())
->select('user_name', 'user_id')
->fetch();

self::assertSame(
[
[
'user_name' => 'User Name',
'user_id' => '01',
],
],
$rows->toArray()
);
}
}
2 changes: 1 addition & 1 deletion web/landing/resources/api.json

Large diffs are not rendered by default.