285 lines
4.6 KiB
PHP
285 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace alveolata\xml;
|
|
|
|
require_once(DIR_ALVEOLATA . '/xml/abstract/interface.php');
|
|
require_once(DIR_ALVEOLATA . '/xml/concrete/comment/implementation.php');
|
|
require_once(DIR_ALVEOLATA . '/xml/concrete/text/implementation.php');
|
|
require_once(DIR_ALVEOLATA . '/xml/concrete/complex/implementation.php');
|
|
require_once(DIR_ALVEOLATA . '/xml/concrete/document/implementation.php');
|
|
|
|
|
|
/**
|
|
*/
|
|
function make_comment(
|
|
string $content
|
|
) : struct_comment
|
|
{
|
|
return (
|
|
new struct_comment(
|
|
$content
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function make_text(
|
|
string $content
|
|
) : struct_text
|
|
{
|
|
return (
|
|
new struct_text(
|
|
$content
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function make_complex(
|
|
string $name,
|
|
?array $options = null
|
|
) : struct_complex
|
|
{
|
|
$options = \array_merge(
|
|
[
|
|
'attributes' => [],
|
|
'children' => null,
|
|
],
|
|
($options ?? [])
|
|
);
|
|
return (
|
|
new struct_complex(
|
|
$name,
|
|
$options['attributes'],
|
|
$options['children']
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function make_document(
|
|
array $children
|
|
) : struct_document
|
|
{
|
|
return (
|
|
new struct_document(
|
|
$children
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function _get_logic(
|
|
$node
|
|
)
|
|
{
|
|
if ($node instanceof struct_text) {
|
|
return (new implementation_text($node));
|
|
}
|
|
else if ($node instanceof struct_comment) {
|
|
return (new implementation_comment($node));
|
|
}
|
|
else if ($node instanceof struct_complex) {
|
|
return (new implementation_complex($node));
|
|
}
|
|
else if ($node instanceof struct_document) {
|
|
return (new implementation_document($node));
|
|
}
|
|
else {
|
|
var_dump($node);
|
|
throw (new \Exception('unhandled struct'));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function find(
|
|
$node,
|
|
\Closure $predicate,
|
|
$options = null
|
|
) : array
|
|
{
|
|
$options = \array_merge(
|
|
[
|
|
'max_depth' => null,
|
|
],
|
|
($options ?? [])
|
|
);
|
|
return _get_logic($node)->find(fn($x, $y, $z) => find($x, $y, $z), $predicate, $options['max_depth']);
|
|
}
|
|
|
|
|
|
/**
|
|
* shortcut for xpath/css like navigation
|
|
*
|
|
* @param array $path {list<string>}
|
|
*/
|
|
function walk(
|
|
$node,
|
|
array $path
|
|
)
|
|
{
|
|
if (\count($path) <= 0) {
|
|
return $node;
|
|
}
|
|
else {
|
|
$hits = find(
|
|
$node,
|
|
fn($x) => (
|
|
($x instanceof struct_complex)
|
|
&&
|
|
($x->name === $path[0])
|
|
),
|
|
[
|
|
'max_depth' => 1,
|
|
]
|
|
);
|
|
if (\count($hits) <= 0) {
|
|
throw (new \Exception('not found: ' . $path[0]));
|
|
}
|
|
else if (\count($hits) >= 2) {
|
|
throw (new \Exception('ambiguous: ' . $path[0]));
|
|
}
|
|
else {
|
|
return walk(
|
|
$hits[0],
|
|
\array_slice($path, 1)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function transform(
|
|
$node,
|
|
\Closure $function
|
|
)
|
|
{
|
|
return _get_logic($node)->transform(fn($x, $y) => transform($x, $y), $function);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function to_raw(
|
|
$node
|
|
)
|
|
{
|
|
return _get_logic($node)->to_raw(fn($x) => to_raw($x));
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function to_string(
|
|
$node,
|
|
$options = null
|
|
)
|
|
{
|
|
$options = \array_merge(
|
|
[
|
|
'depth' => 0,
|
|
],
|
|
($options ?? [])
|
|
);
|
|
return _get_logic($node)->to_string(fn($x, $y) => to_string($x, $y), $options['depth']);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function _convert(
|
|
\DOMNode $doc
|
|
)
|
|
{
|
|
if ($doc instanceof \DOMComment) {
|
|
return make_comment($doc->data);
|
|
}
|
|
else if ($doc instanceof \DOMText) {
|
|
$content = \trim($doc->nodeValue);
|
|
return (
|
|
empty($content)
|
|
? null
|
|
: make_text($content)
|
|
);
|
|
}
|
|
else if ($doc instanceof \DOMElement) {
|
|
$attributes = [];
|
|
foreach ($doc->attributes as $attribute_raw) {
|
|
$attributes[$attribute_raw->name] = $attribute_raw->value;
|
|
}
|
|
$children = [];
|
|
foreach ($doc->childNodes as $child_raw) {
|
|
$child = _convert($child_raw);
|
|
if (\is_null($child)) {
|
|
// do nothing
|
|
}
|
|
else {
|
|
\array_push($children, $child);
|
|
}
|
|
}
|
|
return make_complex($doc->nodeName, ['attributes' => $attributes, 'children' => $children]);
|
|
}
|
|
else if ($doc instanceof \DOMDocument) {
|
|
$children = [];
|
|
foreach ($doc->childNodes as $child_raw) {
|
|
$child = _convert($child_raw);
|
|
if (\is_null($child)) {
|
|
// do nothing
|
|
}
|
|
else {
|
|
\array_push($children, $child);
|
|
}
|
|
}
|
|
return make_document($children);
|
|
}
|
|
else {
|
|
var_dump($doc);
|
|
throw (new \Exception('unhandled node type: ' . \strval($doc)));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @see https://www.php.net/manual/en/class.domdocument.php
|
|
* @see https://www.php.net/manual/en/domdocument.loadxml.php
|
|
* @see https://www.php.net/manual/en/libxml.constants.php
|
|
*/
|
|
function parse(
|
|
string $xml
|
|
)
|
|
{
|
|
$doc = new \DOMDocument();
|
|
$doc->loadXML(\sprintf('<_dummy>%s</_dummy>', $xml), 0);
|
|
$out = _convert($doc);
|
|
return (
|
|
(\count($out->children[0]->children) === 1)
|
|
? $out->children[0]->children[0]
|
|
: make_document($out->children[0]->children)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* just a shortcut
|
|
*/
|
|
function parse_raw(
|
|
string $xml
|
|
)
|
|
{
|
|
$node = parse($xml);
|
|
return to_raw($node);
|
|
}
|
|
|
|
?>
|