rosavox/lib/alveolata/xml/functions.php
2025-05-23 07:33:29 +00:00

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);
}
?>