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
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ jobs:
uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main
cs:
uses: innmind/github-workflows/.github/workflows/cs.yml@main
with:
php-version: '8.2'
12 changes: 12 additions & 0 deletions .github/workflows/extensive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Extensive CI

on:
push:
tags:
- '*'
paths:
- '.github/workflows/extensive.yml'

jobs:
blackbox:
uses: innmind/github-workflows/.github/workflows/extensive.yml@main
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [Unreleased]

### Changed

- Requires PHP `8.4`
- Requires `innmind/xml:~9.0`

## 7.0.0 - 2025-06-07

### Added
Expand Down
4 changes: 4 additions & 0 deletions blackbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
};

Application::new($argv)
->when(
\getenv('BLACKBOX_SET_SIZE') !== false,
static fn(Application $app) => $app->scenariiPerProof((int) \getenv('BLACKBOX_SET_SIZE')),
)
->when(
\getenv('ENABLE_COVERAGE') !== false,
static fn(Application $app) => $app
Expand Down
15 changes: 7 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@
"issues": "http://github.com/Innmind/Html/issues"
},
"require": {
"php": "~8.2",
"innmind/xml": "~8.0",
"innmind/filesystem": "~8.1",
"innmind/immutable": "~5.16",
"innmind/validation": "~2.0",
"symfony/dom-crawler": ">=6.3 <7.0.7",
"innmind/url": "~4.0"
"php": "~8.4",
"innmind/xml": "~9.0",
"innmind/filesystem": "~9.0",
"innmind/immutable": "~6.0",
"innmind/validation": "~3.0",
"innmind/url": "~5.0"
},
"autoload": {
"psr-4": {
Expand All @@ -35,7 +34,7 @@
},
"require-dev": {
"innmind/black-box": "^6.4.1",
"innmind/static-analysis": "^1.2.1",
"innmind/static-analysis": "~1.3",
"innmind/coding-standard": "~2.0"
}
}
21 changes: 10 additions & 11 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
};
use Innmind\Filesystem\File\Content;
use Innmind\Immutable\Attempt;
use Symfony\Component\DomCrawler\Crawler;

/**
* @psalm-immutable
Expand All @@ -26,20 +25,20 @@ private function __construct(private Translator $translate)
*/
public function __invoke(Content $html): Attempt
{
/** @psalm-suppress ImpureMethodCall */
$firstNode = (new Crawler($html->toString(), useHtml5Parser: false))->getNode(0);
$content = $html->toString();

if (!$firstNode instanceof \DOMNode) {
/** @var Attempt<Document|Node|Element|Custom> */
return Attempt::error(new \RuntimeException('Failed to parse html content'));
if ($content === '') {
return Attempt::error(new \RuntimeException('Empty content'));
}

/** @psalm-suppress RedundantCondition */
while ($firstNode->parentNode instanceof \DOMNode) {
$firstNode = $firstNode->parentNode;
try {
return ($this->translate)(\Dom\HTMLDocument::createFromString(
$content,
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
));
} catch (\Throwable $e) {
return Attempt::error($e);
}

return ($this->translate)($firstNode);
}

public static function new(): self
Expand Down
31 changes: 7 additions & 24 deletions src/Translator.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ private function __construct(
}

/**
* @psalm-suppress UndefinedClass Since the package still supports PHP 8.2
*
* @return Attempt<Document|Element|Custom|Node>
*/
public function __invoke(\DOMNode|\Dom\Node $node): Attempt
public function __invoke(\Dom\Node $node): Attempt
{
return $this
->buildDocument($node)
Expand All @@ -59,11 +57,9 @@ public static function new(): self
}

/**
* @psalm-suppress UndefinedClass Since the package still supports PHP 8.2
*
* @return Attempt<Element|Custom|Node>
*/
private function child(\DOMNode|\Dom\Node $node): Attempt
private function child(\Dom\Node $node): Attempt
{
/** @var Attempt<Element|Custom|Node> Psalm doesn't understand the filter */
return ($this->translate)($node)
Expand All @@ -74,39 +70,27 @@ private function child(\DOMNode|\Dom\Node $node): Attempt
->or(Instance::of(Node::class)),
static fn() => new \RuntimeException('Invalid document node'),
)
->match(
Attempt::result(...),
Attempt::error(...),
);
->attempt(static fn($e) => $e);
}

/**
* @psalm-suppress UndefinedClass Since the package still supports PHP 8.2
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
* @psalm-suppress UndefinedPropertyFetch
*
* @return Attempt<Document>
*/
private function buildDocument(\DOMNode|\Dom\Node $node): Attempt
private function buildDocument(\Dom\Node $node): Attempt
{
/** @var Sequence<Node|Element|Custom> */
$children = Sequence::of();

return Maybe::just($node)
->keep(
Instance::of(\DOMDocument::class)->or(
Instance::of(\Dom\Document::class),
),
)
->keep(Instance::of(\Dom\Document::class))
->attempt(static fn() => new \RuntimeException('Not a document'))
->flatMap(
fn($document) => Sequence::of(...\array_values(\iterator_to_array($document->childNodes)))
->keep(
Instance::of(\DOMNode::class)->or(
Instance::of(\Dom\Node::class),
),
)
->keep(Instance::of(\Dom\Node::class))
->exclude(static fn($child) => $child->nodeType === \XML_DOCUMENT_TYPE_NODE)
->sink($children)
->attempt(
Expand All @@ -129,11 +113,10 @@ private function buildDocument(\DOMNode|\Dom\Node $node): Attempt
/**
* @psalm-pure
* @psalm-suppress ImpurePropertyFetch
* @psalm-suppress UndefinedClass Since the package still supports PHP 8.2
*
* @return Maybe<Type>
*/
private static function buildDoctype(\DOMDocumentType|\Dom\DocumentType $type): Maybe
private static function buildDoctype(\Dom\DocumentType $type): Maybe
{
/** @psalm-suppress MixedArgument */
return Type::maybe(
Expand Down
8 changes: 4 additions & 4 deletions tests/DocumentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ public function testPrependChild()
$this->assertInstanceOf(Document::class, $document2);
$this->assertSame($document->type(), $document2->type());
$this->assertNotSame($document->children(), $document2->children());
$this->assertCount(3, $document->children());
$this->assertCount(4, $document2->children());
$this->assertSame(3, $document->children()->size());
$this->assertSame(4, $document2->children()->size());
$this->assertSame(
$node,
$document2->children()->get(0)->match(
Expand Down Expand Up @@ -127,8 +127,8 @@ public function testAppendChild()
$this->assertInstanceOf(Document::class, $document2);
$this->assertSame($document->type(), $document2->type());
$this->assertNotSame($document->children(), $document2->children());
$this->assertCount(3, $document->children());
$this->assertCount(4, $document2->children());
$this->assertSame(3, $document->children()->size());
$this->assertSame(4, $document2->children()->size());
$this->assertEquals(
$document->children()->get(0),
$document2->children()->get(0),
Expand Down
2 changes: 1 addition & 1 deletion tests/Element/ScriptTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function testInterface()
'<script>foo</script>'."\n",
$script->asContent()->toString(),
);
$this->assertCount(1, $script->children());
$this->assertSame(1, $script->children()->size());
}

public function testWithAttributes()
Expand Down
11 changes: 7 additions & 4 deletions tests/ReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ public function testReadSimple()
static fn($node) => $node,
static fn() => null,
);

// For some reason the \Dom\HTMLDocument API doesn't respect the
// indentation of the input document.
$space = ' ';
$expected = <<<HTML
<!DOCTYPE html>
<html>
<head/>
<html><head/>
<body>
foo
</body>
</html>
$space
</body></html>
HTML;

$this->assertInstanceOf(Document::class, $node);
Expand Down
20 changes: 12 additions & 8 deletions tests/Translator/NodeTranslator/ATranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ class ATranslatorTest extends TestCase
{
public function testTranslate()
{
$dom = new \DOMDocument;
$dom->loadHTML('<a href="/" class="whatever">foo</a>');
$dom = \Dom\HTMLDocument::createFromString(
'<a href="/" class="whatever">foo</a>',
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
);

$a = Translator::new()(
$dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0),
$dom->childNodes->item(0),
)->match(
static fn($a) => $a,
static fn() => null,
Expand All @@ -27,21 +29,23 @@ public function testTranslate()
$this->assertInstanceOf(A::class, $a);
$this->assertSame('/', $a->href()->toString());
$a = $a->normalize();
$this->assertCount(2, $a->attributes());
$this->assertSame(2, $a->attributes()->size());
$this->assertSame('whatever', $a->attribute('class')->match(
static fn($attribute) => $attribute->value(),
static fn() => null,
));
$this->assertCount(1, $a->children());
$this->assertSame(1, $a->children()->size());
}

public function testReturnNothingWhenMissingHrefAttribute()
{
$dom = new \DOMDocument;
$dom->loadHTML('<a class="whatever">foo</a>');
$dom = \Dom\HTMLDocument::createFromString(
'<a class="whatever">foo</a>',
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
);

$result = Translator::new()(
$dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0),
$dom->childNodes->item(0),
)
->maybe()
->keep(Instance::of(A::class));
Expand Down
18 changes: 11 additions & 7 deletions tests/Translator/NodeTranslator/BaseTranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ class BaseTranslatorTest extends TestCase
{
public function testTranslate()
{
$dom = new \DOMDocument;
$dom->loadHTML('<base href="/" target="_blank"/>');
$dom = \Dom\HTMLDocument::createFromString(
'<base href="/" target="_blank"/>',
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
);

$base = Translator::new()(
$dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0),
$dom->childNodes->item(0),
)->match(
static fn($base) => $base,
static fn() => null,
Expand All @@ -27,7 +29,7 @@ public function testTranslate()
$this->assertInstanceOf(Base::class, $base);
$this->assertSame('/', $base->href()->toString());
$base = $base->normalize();
$this->assertCount(2, $base->attributes());
$this->assertSame(2, $base->attributes()->size());
$this->assertSame('_blank', $base->attribute('target')->match(
static fn($attribute) => $attribute->value(),
static fn() => null,
Expand All @@ -36,11 +38,13 @@ public function testTranslate()

public function testReturnNothingWhenMissingHrefAttribute()
{
$dom = new \DOMDocument;
$dom->loadHTML('<base/>');
$dom = \Dom\HTMLDocument::createFromString(
'<base/>',
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
);

$result = Translator::new()(
$dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0),
$dom->childNodes->item(0),
)
->maybe()
->keep(Instance::of(Base::class));
Expand Down
20 changes: 12 additions & 8 deletions tests/Translator/NodeTranslator/DocumentTranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class DocumentTranslatorTest extends TestCase
{
public function testTranslate()
{
$document = new \DOMDocument;
$document->loadHtml('<!DOCTYPE html><body></body>');
$document = \Dom\HTMLDocument::createFromString('<!DOCTYPE html><body></body>');

$node = Translator::new()(
$document,
Expand All @@ -25,11 +24,12 @@ public function testTranslate()

$this->assertInstanceOf(Document::class, $node);
$this->assertSame('html', $node->type()->name());
$this->assertCount(1, $node->children());
$this->assertSame(1, $node->children()->size());
$this->assertSame(
<<<HTML
<!DOCTYPE html>
<html>
<head/>
<body/>
</html>

Expand All @@ -40,8 +40,10 @@ public function testTranslate()

public function testTranslateWithoutDoctype()
{
$document = new \DOMDocument;
$document->loadHtml('<!--foo-->');
$document = \Dom\HTMLDocument::createFromString(
'<!--foo-->',
\LIBXML_NOERROR,
);

$node = Translator::new()(
$document,
Expand All @@ -51,15 +53,17 @@ public function testTranslateWithoutDoctype()
);

$this->assertSame(
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
'<!DOCTYPE html>',
$node->type()->toString(),
);
}

public function testTranslateWithoutChildren()
{
$document = new \DOMDocument;
$document->loadHtml('<!DOCTYPE html>');
$document = \Dom\HTMLDocument::createFromString(
'<!DOCTYPE html>',
\LIBXML_HTML_NOIMPLIED | \LIBXML_NOERROR,
);

$node = Translator::new()(
$document,
Expand Down
Loading