Многомерная итерация массива
Скажем, у вас есть следующий массив:
$nodes = array(
"parent node",
"parent node",
array(
"child node",
"child node",
array(
"grand child node",
"grand child node")));
Как бы вы решили преобразовать его в строку XML, чтобы он выглядел так:
<node>
<node>parent node</node>
<node>parent node</node>
<node>
<node>child node</node>
<node>child node</node>
<node>
<node>grand child node</node>
<node>grand child node</node>
</node>
</node>
</node>
Один из способов сделать это будет через рекурсивный метод, например:
function traverse($nodes)
{
echo "<node>";
foreach($nodes as $node)
{
if(is_array($node))
{
traverse($node);
}
else
{
echo "<node>$node</node>";
}
}
echo "</node>";
}
traverse($nodes);
Я ищу подход, который использует итерацию.
Ответы
Ответ 1
<?php
$nodes = array(
"parent node",
"parent node",
array(
"child node",
"child node",
array(
"grand child node",
"grand child node"
)
)
);
$s = '<node>';
$arr = $nodes;
while(count($arr) > 0)
{
$n = array_shift($arr);
if(is_array($n))
{
array_unshift($arr, null);
$arr = array_merge($n, $arr);
$s .= '<node>';
}
elseif(is_null($n))
$s .= '</node>';
else
$s .= '<node>'.$n.'</node>';
}
$s .= '</node>';
echo $s;
?>
Ответ 2
Вы можете использовать Iterator для итерации по массиву, а затем для получения нужного результата:
class TranformArrayIterator extends RecursiveIteratorIterator
{
protected function indent()
{
echo str_repeat("\t", $this->getDepth());
return $this;
}
public function beginIteration()
{
echo '<nodes>', PHP_EOL;
}
public function endIteration()
{
echo '</nodes>', PHP_EOL;
}
public function beginChildren()
{
$this->indent()->beginIteration();
}
public function endChildren()
{
$this->indent()->endIteration();
}
public function current()
{
return sprintf('%s<node>%s</node>%s',
str_repeat("\t", $this->getDepth() +1),
parent::current(),
PHP_EOL);
}
}
а затем соберите его следующим образом:
$iterator = new TranformArrayIterator(new RecursiveArrayIterator($nodes));
foreach($iterator as $val) {
echo $val;
}
выходы
<nodes>
<node>parent node</node>
<node>parent node</node>
<nodes>
<node>child node</node>
<node>child node</node>
<nodes>
<node>grand child node</node>
<node>grand child node</node>
</nodes>
</nodes>
</nodes>
Чтобы отключить $key
при использовании $key => $val
, добавьте это в TraverseArrayIterator
public function key()
{
return '';
}
Поскольку ваша цель состоит в создании XML, вы также можете передать XMLWriter в качестве соавтора в Iterator. Это позволяет больше контролировать созданный XML, а также гарантирует корректность вывода XML:
class TranformArrayIterator extends RecursiveIteratorIterator
{
private $xmlWriter;
public function __construct(
XmlWriter $xmlWriter,
Traversable $iterator,
$mode = RecursiveIteratorIterator::LEAVES_ONLY ,
$flags = 0)
{
$this->xmlWriter = $xmlWriter;
parent::__construct($iterator, $mode, $flags);
}
public function beginIteration()
{
$this->xmlWriter->startDocument('1.0', 'utf-8');
$this->beginChildren();
}
public function endIteration()
{
$this->xmlWriter->endDocument();
}
public function beginChildren()
{
$this->xmlWriter->startElement('nodes');
}
public function endChildren()
{
$this->xmlWriter->endElement();
}
public function current()
{
$this->xmlWriter->writeElement('node', parent::current());
}
}
Затем вы будете использовать его следующим образом:
$xmlWriter = new XmlWriter;
$xmlWriter->openUri('php://output');
$xmlWriter->setIndent(true);
$xmlWriter->setIndentString("\t");
$iterator = new TranformArrayIterator(
$xmlWriter,
new RecursiveArrayIterator($nodes)
);
и foreach
'через него выдаст тот же результат (но добавив пролог XML)