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
24 changes: 24 additions & 0 deletions docs/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,30 @@ $xml = $mapper($someNode);
$xml = $doc->stringifyNode($someNode);
```

#### to_unsafe_legacy_document

Converts a `Dom\XMLDocument` (PHP 8.4+) into a legacy `DOMDocument` via an XML round-trip.
This is useful when interoperating with libraries that still expect the legacy `DOMDocument` type.

The `documentURI` is preserved on the resulting `DOMDocument`.

**Caveat:** Line numbers in the resulting `DOMDocument` may differ from the original because
the new DOM's `saveXML()` can reformat the output (e.g., collapsing multi-line opening tags
into single lines).

```php
use VeeWee\Xml\Dom\Document;
use function VeeWee\Xml\Dom\Mapper\to_unsafe_legacy_document;

$doc = Document::fromXmlFile('some.xml');

// Using the convenience method on Document:
$legacyDoc = $doc->toUnsafeLegacyDocument();

// Or using the mapper function directly:
$legacyDoc = $doc->map(to_unsafe_legacy_document());
```

#### xslt_template

Allows you to map an XML document based on an [XSLT template](xslt.md).
Expand Down
14 changes: 14 additions & 0 deletions src/Xml/Dom/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
use Dom\Node;
use Dom\XMLDocument;
use Dom\XPath as DOMXPath;
use DOMDocument;
use VeeWee\Xml\Dom\Traverser\Traverser;
use VeeWee\Xml\Dom\Traverser\Visitor;
use VeeWee\Xml\ErrorHandling\Issue\IssueCollection;
use VeeWee\Xml\Exception\RuntimeException;
use function Psl\Vec\map;
use function VeeWee\Xml\Dom\Locator\document_element;
use function VeeWee\Xml\Dom\Mapper\to_unsafe_legacy_document;
use function VeeWee\Xml\Dom\Mapper\xml_string;
use function VeeWee\Xml\Internal\configure;

Expand Down Expand Up @@ -101,6 +103,18 @@ public function toUnsafeDocument(): XMLDocument
return $this->document;
}

/**
* Converts this document into a legacy DOMDocument via an XML round-trip.
*
* The documentURI is preserved. Note that line numbers may differ from the original
* because the new DOM's saveXML() can reformat the output (e.g., collapsing
* multi-line opening tags into single lines).
*/
public function toUnsafeLegacyDocument(): DOMDocument
{
return $this->map(to_unsafe_legacy_document());
}

/**
* @template T
* @param callable(XMLDocument): T $locator
Expand Down
46 changes: 46 additions & 0 deletions src/Xml/Dom/Mapper/to_unsafe_legacy_document.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Mapper;

use Closure;
use Dom\XMLDocument;
use DOMDocument;
use function VeeWee\Xml\ErrorHandling\disallow_issues;
use function VeeWee\Xml\ErrorHandling\disallow_libxml_false_returns;

/**
* Converts a Dom\XMLDocument (PHP 8.4+) into a legacy DOMDocument.
*
* This performs an XML round-trip: the new DOM's saveXML() output is loaded
* into a legacy DOMDocument via loadXML(). The documentURI is preserved.
*
* Caveat: line numbers in the resulting DOMDocument may differ from the original
* because the new DOM's saveXML() can reformat the output (e.g., collapsing
* multi-line opening tags into single lines).
*
* @return Closure(XMLDocument): DOMDocument
*/
function to_unsafe_legacy_document(): Closure
{
return static fn (XMLDocument $document): DOMDocument => disallow_issues(
static function () use ($document): DOMDocument {
$xml = xml_string()($document);

$legacy = new DOMDocument();
disallow_libxml_false_returns(
$legacy->loadXML($xml),
'Unable to load XML into legacy DOMDocument'
);

// documentURI must be set AFTER loadXML() because loadXML() resets it.
$documentUri = $document->documentURI;
if ($documentUri !== '') {
$legacy->documentURI = $documentUri;
}

return $legacy;
}
);
}
1 change: 1 addition & 0 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
'Xml\Dom\Manipulator\Xmlns\rename' => __DIR__.'/Xml/Dom/Manipulator/Xmlns/rename.php',
'Xml\Dom\Manipulator\Xmlns\rename_element_namespace' => __DIR__.'/Xml/Dom/Manipulator/Xmlns/rename_element_namespace.php',
'Xml\Dom\Manipulator\append' => __DIR__.'/Xml/Dom/Manipulator/append.php',
'Xml\Dom\Mapper\to_unsafe_legacy_document' => __DIR__.'/Xml/Dom/Mapper/to_unsafe_legacy_document.php',
'Xml\Dom\Mapper\xml_string' => __DIR__.'/Xml/Dom/Mapper/xml_string.php',
'Xml\Dom\Mapper\xslt_template' => __DIR__.'/Xml/Dom/Mapper/xslt_template.php',
'Xml\Dom\Predicate\is_attribute' => __DIR__.'/Xml/Dom/Predicate/is_attribute.php',
Expand Down
63 changes: 63 additions & 0 deletions tests/Xml/Dom/Mapper/ToLegacyDocumentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace VeeWee\Tests\Xml\Mapper;

use DOMDocument;
use PHPUnit\Framework\TestCase;
use VeeWee\Xml\Dom\Document;
use function VeeWee\Xml\Dom\Mapper\to_unsafe_legacy_document;

final class ToLegacyDocumentTest extends TestCase
{
public function test_it_can_convert_to_legacy_document(): void
{
$doc = Document::fromXmlString('<root><item>hello</item></root>');
$legacy = $doc->toUnsafeLegacyDocument();

static::assertInstanceOf(DOMDocument::class, $legacy);
static::assertSame('hello', $legacy->getElementsByTagName('item')->item(0)->textContent);
}

public function test_it_preserves_document_uri(): void
{
$doc = Document::fromXmlString(
'<root/>',
\VeeWee\Xml\Dom\Configurator\document_uri('/some/path/file.xml')
);
$legacy = $doc->toUnsafeLegacyDocument();

static::assertSame('/some/path/file.xml', $legacy->documentURI);
}

public function test_it_preserves_namespaces(): void
{
$xml = '<root xmlns:ns="http://example.com"><ns:item>value</ns:item></root>';
$doc = Document::fromXmlString($xml);
$legacy = $doc->toUnsafeLegacyDocument();

$items = $legacy->getElementsByTagNameNS('http://example.com', 'item');
static::assertSame(1, $items->length);
static::assertSame('value', $items->item(0)->textContent);
}

public function test_it_can_be_used_as_mapper(): void
{
$doc = Document::fromXmlString('<root/>');
$legacy = $doc->map(to_unsafe_legacy_document());

static::assertInstanceOf(DOMDocument::class, $legacy);
static::assertSame('root', $legacy->documentElement->localName);
}

public function test_it_preserves_xml_declaration(): void
{
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<root/>';
$doc = Document::fromXmlString($xml);
$legacy = $doc->toUnsafeLegacyDocument();

static::assertSame('1.0', $legacy->xmlVersion);
static::assertSame('UTF-8', $legacy->encoding);
}
}