入力データは適切にフォーマットされているため、非常に単純な再帰的子孫パーサーを作成できます。再帰はあまり必要ありません。または、単純なスタックを使用します。
$props = array_filter(array_map('trim', explode("\n", $prop)));
$stack = [$node = $xml = new SimpleXMLElement('<root/>')];
foreach ($props as $str)
{
if ($str === '}') {
array_pop($stack);
$node = end($stack);
continue;
}
if (preg_match('~^(\w+) {$~', $str, $matches)) {
$node = $stack[] = $node->addChild($matches[1]);
continue;
}
if (preg_match('~^(\w+):\s*(.*)$~', $str, $matches)) {
$node->addChild($matches[1], htmlspecialchars($matches[2]));
continue;
}
throw new UnexpectedValueException(sprintf('Unable to parse: "%s"', $str));
}
$xml->asXML('php://output');
2番目の例(以前は欠落していた)の出力は(美化されています):
<?xml version="1.0"?>
<root>
<button>
<large>
<bond>
<width>10px;</width>
<height>10px;</height>
</bond>
<style>
<background>
<color>#ffffff;</color>
<image>url(www.px.com/aui.png) -10px;</image>
<another>
<width>100px;</width>
<height>100px;</height>
</another>
</background>
</style>
</large>
<small>
<bond>
<width>10px;</width>
<height>10px;</height>
</bond>
<style>
<color>#fff;</color>
<border>1px solid #000;</border>
</style>
</small>
</button>
</root>
ここでは XML を使用することをお勧めします。これは、重複キーを持つことができない配列よりも構造をより適切に表現できるためです。
スタックの代わりに再帰関数呼び出しを使用することも可能です。しかし、これには入力ストリームを巻き戻しのないイテレータでラップして壊れないようにする必要があります(または使用してarray_shift
いますが、私はあまり好きではありません):
$parse = function($p, SimpleXMLElement $t) use (&$parse) {
foreach($p as $s) {
if ($s === '}') {
break;
}
if (preg_match('~^([^ ]+) {$~', $s, $m)) {
$p->next();
$parse($p, $t->addChild($m[1]));
continue;
}
if (preg_match('~^([^:]+):\s*(.*)$~', $s, $m)) {
$n = $t->addChild($m[1], htmlspecialchars($m[2]));
continue;
}
}
};
$props = array_filter(array_map('trim', explode("\n", $prop)));
$xml = new SimpleXMLElement('<root/>');
$parse(new NoRewindIterator(new ArrayIterator($props)), $xml);
$xml->asXML('php://output');