<?php
class Tree
{
const FORMAT_TEXT = 0;
const FORMAT_HTML = 1;
protected $rootIds;
protected $template;
/** @var array Template, indexed by nodes ids */
protected $nodes;
protected $adjacencyList;
public function __construct($template)
{
$this->setTemplate($template);
}
public function setTemplate($template)
{
$this->template = $template;
$this->nodes = $this->getNodes($template);
$this->rootIds = $this->getRootIds($template);
$this->adjacencyList = $this->buildAdjacencyList($template);
}
public function format($format)
{
$output = '';
foreach ($this->rootIds as $rootId) {
$this->walkTree($this->nodes[$rootId], $output, $format);
}
return $output;
}
public function __toString()
{
$this->format(static::FORMAT_TEXT);
}
protected function buildAdjacencyList($template)
{
$adjacencyList = [];
foreach ($template as $node) {
if ($node['parentId'] !== null) {
if (isset($adjacencyList[$node['parentId']])) { $adjacencyList[$node['parentId']][] = $node['id'];
} else {
$adjacencyList[$node['parentId']] = [$node['id']];
}
}
}
return $adjacencyList;
}
protected function getNodes($template)
{
$nodes = [];
foreach ($template as $node) {
$nodes[$node['id']] = $node;
}
return $nodes;
}
protected function getRootIds($template)
{
$rootIds = [];
foreach ($template as $node) {
if ($node['parentId'] === null) {
$rootIds[] = $node['id'];
}
}
if ($rootIds !== []) {
return $rootIds;
} else {
throw new Exception('Parent node do not exists.');
}
}
protected function walkTree($node, &$output, $format, $depth = 0)
{
switch ($format) {
case Tree::FORMAT_TEXT:
$output .= $this->formatNodeAsText($node['text'], $depth);
break;
case Tree::FORMAT_HTML:
$output .= $this->formatNodeAsHtml($node['text'], $depth);
break;
default:
$output .= $this->formatNodeAsText($node['text'], $depth);
}
$depth += 1;
if (isset($this->adjacencyList[$node['id']])) { foreach ($this->adjacencyList[$node['id']] as $childId) {
$this->walkTree($this->nodes[$childId], $output, $format, $depth);
}
}
}
protected function formatNodeAsText($nodeText, $depth)
{
return str_repeat(' ', $depth) . $nodeText . "\r\n"; }
protected function formatNodeAsHtml($nodeText, $depth)
{
return '<p class="offset-' . $depth . '">' . $nodeText . '</p>';
}
}
array("id" => 1, "parentId" => null, "text" => "Первая строка"), array("id" => 2, "parentId" => 3, "text" => "Вторая строка"), array("id" => 3, "parentId" => 1, "text" => "Третья строка"), array("id" => 4, "parentId" => 1, "text" => "Четвертая строка"),
array("id" => 5, "parentId" => null, "text" => "Пятая строка"), array("id" => 6, "parentId" => 5, "text" => "Шестая строка") );
$tree = new Tree($template);
echo $tree->format(Tree::FORMAT_TEXT);
PD9waHAKZXJyb3JfcmVwb3J0aW5nKC0xKTsKCmNsYXNzIFRyZWUKewogICAgY29uc3QgRk9STUFUX1RFWFQgPSAwOwogICAgY29uc3QgRk9STUFUX0hUTUwgPSAxOwoKICAgIHByb3RlY3RlZCAkcm9vdElkczsKICAgIAogICAgcHJvdGVjdGVkICR0ZW1wbGF0ZTsKICAgIAogICAgLyoqIEB2YXIgYXJyYXkgVGVtcGxhdGUsIGluZGV4ZWQgYnkgbm9kZXMgaWRzICovCiAgICBwcm90ZWN0ZWQgJG5vZGVzOwogICAgCiAgICBwcm90ZWN0ZWQgJGFkamFjZW5jeUxpc3Q7CiAgICAKICAgIAogICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KCR0ZW1wbGF0ZSkKICAgIHsKICAgICAgICAkdGhpcy0+c2V0VGVtcGxhdGUoJHRlbXBsYXRlKTsKICAgIH0KICAgIAogICAgcHVibGljIGZ1bmN0aW9uIHNldFRlbXBsYXRlKCR0ZW1wbGF0ZSkKICAgIHsKICAgICAgICAkdGhpcy0+dGVtcGxhdGUgPSAkdGVtcGxhdGU7CiAgICAgICAgJHRoaXMtPm5vZGVzID0gJHRoaXMtPmdldE5vZGVzKCR0ZW1wbGF0ZSk7CiAgICAgICAgJHRoaXMtPnJvb3RJZHMgPSAkdGhpcy0+Z2V0Um9vdElkcygkdGVtcGxhdGUpOwogICAgICAgICR0aGlzLT5hZGphY2VuY3lMaXN0ID0gJHRoaXMtPmJ1aWxkQWRqYWNlbmN5TGlzdCgkdGVtcGxhdGUpOwogICAgfQogICAgCiAgICBwdWJsaWMgZnVuY3Rpb24gZm9ybWF0KCRmb3JtYXQpCiAgICB7CiAgICAgICAgJG91dHB1dCA9ICcnOwogICAgICAgIAogICAgICAgIGZvcmVhY2ggKCR0aGlzLT5yb290SWRzIGFzICRyb290SWQpIHsKICAgICAgICAgICAgJHRoaXMtPndhbGtUcmVlKCR0aGlzLT5ub2Rlc1skcm9vdElkXSwgJG91dHB1dCwgJGZvcm1hdCk7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiAkb3V0cHV0OwogICAgfQogICAgCiAgICBwdWJsaWMgZnVuY3Rpb24gX190b1N0cmluZygpCiAgICB7CiAgICAgICAgJHRoaXMtPmZvcm1hdChzdGF0aWM6OkZPUk1BVF9URVhUKTsKICAgIH0KICAgIAogICAgcHJvdGVjdGVkIGZ1bmN0aW9uIGJ1aWxkQWRqYWNlbmN5TGlzdCgkdGVtcGxhdGUpCiAgICB7CiAgICAgICAgJGFkamFjZW5jeUxpc3QgPSBbXTsKICAgICAgICAKICAgICAgICBmb3JlYWNoICgkdGVtcGxhdGUgYXMgJG5vZGUpIHsKICAgICAgICAgICAgaWYgKCRub2RlWydwYXJlbnRJZCddICE9PSBudWxsKSB7CiAgICAgICAgICAgICAgICBpZiAoaXNzZXQoJGFkamFjZW5jeUxpc3RbJG5vZGVbJ3BhcmVudElkJ11dKSkgewogICAgICAgICAgICAgICAgICAgICRhZGphY2VuY3lMaXN0WyRub2RlWydwYXJlbnRJZCddXVtdID0gJG5vZGVbJ2lkJ107CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICRhZGphY2VuY3lMaXN0WyRub2RlWydwYXJlbnRJZCddXSA9IFskbm9kZVsnaWQnXV07CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgCiAgICAgICAgcmV0dXJuICRhZGphY2VuY3lMaXN0OwogICAgfQogICAgCiAgICBwcm90ZWN0ZWQgZnVuY3Rpb24gZ2V0Tm9kZXMoJHRlbXBsYXRlKQogICAgewogICAgICAgICRub2RlcyA9IFtdOwogICAgICAgIAogICAgICAgIGZvcmVhY2ggKCR0ZW1wbGF0ZSBhcyAkbm9kZSkgewogICAgICAgICAgICAkbm9kZXNbJG5vZGVbJ2lkJ11dID0gJG5vZGU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiAkbm9kZXM7CiAgICB9CiAgICAKICAgIHByb3RlY3RlZCBmdW5jdGlvbiBnZXRSb290SWRzKCR0ZW1wbGF0ZSkKICAgIHsKICAgICAgICAkcm9vdElkcyA9IFtdOwogICAgICAgIAogICAgICAgIGZvcmVhY2ggKCR0ZW1wbGF0ZSBhcyAkbm9kZSkgewogICAgICAgICAgICBpZiAoJG5vZGVbJ3BhcmVudElkJ10gPT09IG51bGwpIHsKICAgICAgICAgICAgICAgICRyb290SWRzW10gPSAkbm9kZVsnaWQnXTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAKICAgICAgICBpZiAoJHJvb3RJZHMgIT09IFtdKSB7CiAgICAgICAgICAgIHJldHVybiAkcm9vdElkczsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICB0aHJvdyBuZXcgRXhjZXB0aW9uKCdQYXJlbnQgbm9kZSBkbyBub3QgZXhpc3RzLicpOwogICAgICAgIH0KICAgIH0KCiAgICBwcm90ZWN0ZWQgZnVuY3Rpb24gd2Fsa1RyZWUoJG5vZGUsICYkb3V0cHV0LCAkZm9ybWF0LCAkZGVwdGggPSAwKQogICAgewogICAgICAgIHN3aXRjaCAoJGZvcm1hdCkgewogICAgICAgICAgICBjYXNlIFRyZWU6OkZPUk1BVF9URVhUOgogICAgICAgICAgICAgICAgJG91dHB1dCAuPSAkdGhpcy0+Zm9ybWF0Tm9kZUFzVGV4dCgkbm9kZVsndGV4dCddLCAkZGVwdGgpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgVHJlZTo6Rk9STUFUX0hUTUw6CiAgICAgICAgICAgICAgICAkb3V0cHV0IC49ICR0aGlzLT5mb3JtYXROb2RlQXNIdG1sKCRub2RlWyd0ZXh0J10sICRkZXB0aCk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgICRvdXRwdXQgLj0gJHRoaXMtPmZvcm1hdE5vZGVBc1RleHQoJG5vZGVbJ3RleHQnXSwgJGRlcHRoKTsKICAgICAgICB9CiAgICAgICAgJGRlcHRoICs9IDE7CiAgICAgICAgCiAgICAgICAgaWYgKGlzc2V0KCR0aGlzLT5hZGphY2VuY3lMaXN0WyRub2RlWydpZCddXSkpIHsKICAgICAgICAJZm9yZWFjaCAoJHRoaXMtPmFkamFjZW5jeUxpc3RbJG5vZGVbJ2lkJ11dIGFzICRjaGlsZElkKSB7CiAgICAgICAgICAgIAkkdGhpcy0+d2Fsa1RyZWUoJHRoaXMtPm5vZGVzWyRjaGlsZElkXSwgJG91dHB1dCwgJGZvcm1hdCwgJGRlcHRoKTsKICAgICAgICAJfQogICAgICAgIH0KICAgIH0KICAgIAogICAgcHJvdGVjdGVkIGZ1bmN0aW9uIGZvcm1hdE5vZGVBc1RleHQoJG5vZGVUZXh0LCAkZGVwdGgpCiAgICB7CiAgICAgICAgcmV0dXJuIHN0cl9yZXBlYXQoJyAgICAnLCAkZGVwdGgpIC4gJG5vZGVUZXh0IC4gIlxyXG4iOwogICAgfQogICAgCiAgICBwcm90ZWN0ZWQgZnVuY3Rpb24gZm9ybWF0Tm9kZUFzSHRtbCgkbm9kZVRleHQsICRkZXB0aCkKICAgIHsKICAgICAgICByZXR1cm4gJzxwIGNsYXNzPSJvZmZzZXQtJyAuICRkZXB0aCAuICciPicgLiAkbm9kZVRleHQgLiAnPC9wPic7CiAgICB9Cn0KCiR0ZW1wbGF0ZSA9IGFycmF5KAogICAgYXJyYXkoImlkIiA9PiAxLCAicGFyZW50SWQiID0+IG51bGwsICJ0ZXh0IiA9PiAi0J/QtdGA0LLQsNGPINGB0YLRgNC+0LrQsCIpLCAKICAgIGFycmF5KCJpZCIgPT4gMiwgInBhcmVudElkIiA9PiAzLCAgICAidGV4dCIgPT4gItCS0YLQvtGA0LDRjyDRgdGC0YDQvtC60LAiKSwgCiAgICBhcnJheSgiaWQiID0+IDMsICJwYXJlbnRJZCIgPT4gMSwgICAgInRleHQiID0+ICLQotGA0LXRgtGM0Y8g0YHRgtGA0L7QutCwIiksCiAgICBhcnJheSgiaWQiID0+IDQsICJwYXJlbnRJZCIgPT4gMSwgICAgInRleHQiID0+ICLQp9C10YLQstC10YDRgtCw0Y8g0YHRgtGA0L7QutCwIiksCiAgICAKICAgIGFycmF5KCJpZCIgPT4gNSwgInBhcmVudElkIiA9PiBudWxsLCAidGV4dCIgPT4gItCf0Y/RgtCw0Y8g0YHRgtGA0L7QutCwIiksIAogICAgYXJyYXkoImlkIiA9PiA2LCAicGFyZW50SWQiID0+IDUsICAgICJ0ZXh0IiA9PiAi0KjQtdGB0YLQsNGPINGB0YLRgNC+0LrQsCIpCik7CgokdHJlZSA9IG5ldyBUcmVlKCR0ZW1wbGF0ZSk7CmVjaG8gJHRyZWUtPmZvcm1hdChUcmVlOjpGT1JNQVRfVEVYVCk7