Как извлечь структурированный текст из HTML-списка в PHP?

У меня есть эта строка:

<ul> <li id="1">Page 1</li> <li id="2">Page 2 <ul> <li id="3">Sub Page A</li> <li id="4">Sub Page B</li> <li id="5">Sub Page C <ul> <li id="6">Sub Sub Page I</li> </ul> </li> </ul> </li> <li id="7">Page 3 <ul> <li id="8">Sub Page D</li> </ul> </li> <li id="9">Page 4</li> </ul> 

и я хочу взорвать каждую информацию с помощью PHP и сделать это так:

 ---------------------------------- | ID | ORDER | PARENT | CHILDREN | ---------------------------------- | 1 | 1 | 0 | 0 | | 2 | 2 | 0 | 3,4,5 | | 3 | 1 | 2 | 0 | | 4 | 2 | 2 | 0 | | 5 | 3 | 2 | 6 | | 6 | 1 | 5 | 0 | | 7 | 3 | 0 | 8 | | 8 | 1 | 7 | 0 | | 9 | 4 | 0 | 0 | ---------------------------------- 

Для получения дополнительной информации это то, что для меня означает этот список:

ID 1 является 1-й (1) и имеет 0 родителей и 0 детей,

ID 2 – 2-й (Страница 2) и имеет 0 идентификаторов родителей и детей 3,4,5,

ID 3 является 1-й (подстраница A) и имеет родительский идентификатор 2 и 0 детей,

ID 4 – 2-я (Субстраница B) и имеет родительский идентификатор 2 и 0 детей,

Идентификатор 5 – третий (подстраница C) и имеет родительский идентификатор 2 и идентификатор детей 6,

ID 6 – 1-я (под-страница I) и имеет родительский идентификатор 5 и 0 детей,

ID 7 – 3-й (Страница 3) и имеет 0 родителей и детей ID 8,

ID 8 – 1-я (Суб-страница I) и имеет родительский идентификатор 7 и 0 детей,

ID 9 – четвертый (Страница 4) и имеет 0 родителей и 0 детей.

Если это слишком сложно, может ли кто-нибудь сказать, как получить эту информацию из этой строки другим методом?

Это не «строка», это HTML. Вам нужно использовать парсер HTML, например DOMDocument или simple_html_dom .

См. Примеры по адресу http://htmlparsing.com/php.html.

Здесь вы можете разделить проблему. Единственное, что нужно было бы проанализировать HTML, это проще всего сделать с DOMDocument и DOMXpath здесь. Это выполняется некоторое сопоставление в контексте результата другого выражения / запроса xpath. Звучит, может быть, немного сложно, но это не так. В более упрощенном варианте вы можете найти это в предыдущем ответе, чтобы получить родительский элемент через xpath и все дочерние элементы .

В вашем случае это немного усложняет, некоторый псевдокод. Я добавил ярлык, потому что он делает вещи более заметными для демонстрационных целей:

 foreach //li :: ID := string(./@id) ParentID := string(./ancestor::li[1]/@id) Label := normalize-space(./text()[1]) 

Как показано на рисунке, это возвращает только голые данные. У вас также есть Орден и дети. Обычно список детей не нужен (я все равно его здесь). То, что похоже на значение Order и значение Children, – это то, что они извлекаются из контекста.

Например, при перемещении n-лидера в личном порядке порядок каждого ребенка может быть пронумерован, если счетчик хранится на каждый ParentID.

Подобно Детям, как счетчик, эта ценность должна быть построена, итерации по списку. Только в самом конце доступно правильное значение для каждого списка.

Таким образом, эти два значения находятся в контексте, я создаю этот контекст в виде массива с ключом ParentID: $parents . За каждый идентификатор он будет содержать две записи: 0 содержит счетчик для Order и 1, содержащий массив, чтобы содержать идентификаторы детей (если они есть).

Примечание. Технически это не совсем правильно. Заказ и дети должны быть выражены и в чистом xpath, но я просто не делал этого в этом примере, чтобы показать, как добавить свой собственный не-xpath-контекст, например, если вы хотите использовать другой порядок или обработку детей.

Достаточно с теорией. Учитывая стандартную настройку:

 $doc = new DOMDocument(); $doc->loadHTML($html); $xp = new DOMXPath($doc); 

Указанное отображение, вкл. его контекст может быть написан как анонимная функция:

 $parents = []; $map = function (DOMElement $li) use ($xp, &$parents) { $id = (int)$xp->evaluate('string(./@id)', $li); $parentId = (int)$xp->evaluate('string(./ancestor::li[1]/@id)', $li); $label = $xp->evaluate('normalize-space(./text()[1])', $li); isset($parents[$parentId][0]) ? $parents[$parentId][0]++ : ($parents[$parentId][0] = 1); $order = $parents[$parentId][0]; $parents[$parentId][1][] = $id; isset($parents[$id][1]) || $parents[$id][1] = []; return array($id, $label, $order, $parentId, &$parents[$id][1]); }; 

Как вы можете видеть, сначала он содержит поиск значений, подобных псевдокоду, а во второй части – обработку значений контекста. Это просто инициализировать контекст для ID / ParentID, если он еще не существует.

Это сопоставление должно быть применено:

 $result = []; foreach ($xp->query('//li') as $li) { list($id) = $array = $map($li); $result[$id] = $array; } 

Который сделает $result содержит список элементов и $parents – данные контекста. В качестве ссылки используется значение «Дети», которое нужно сейчас взорвать, тогда ссылки можно удалить:

 foreach ($parents as &$parent) { $parent[1] = implode(',', $parent[1]); } unset($parent, $parents); 

Это затем приводит к $result result, который может быть выведен:

 echo '+----+----------------+-------+--------+----------+ | ID | LABEL | ORDER | PARENT | CHILDREN | +----+----------------+-------+--------+----------+ '; foreach ($result as $line) { vprintf("| %' 2d | %' -14s | %' 2d | %' 2d | %-8s |\n", $line); } echo '+----+----------------+-------+--------+----------+ '; 

Что тогда выглядит:

 +----+----------------+-------+--------+----------+ | ID | LABEL | ORDER | PARENT | CHILDREN | +----+----------------+-------+--------+----------+ | 1 | Page 1 | 1 | 0 | | | 2 | Page 2 | 2 | 0 | 3,4,5 | | 3 | Sub Page A | 1 | 2 | | | 4 | Sub Page B | 2 | 2 | | | 5 | Sub Page C | 3 | 2 | 6 | | 6 | Sub Sub Page I | 1 | 5 | | | 7 | Page 3 | 3 | 0 | 8 | | 8 | Sub Page D | 1 | 7 | | | 9 | Page 4 | 4 | 0 | | +----+----------------+-------+--------+----------+ 

Вы можете найти Демо онлайн здесь .

Я оставляю второй ответ, потому что на этот раз это демонстрирует, как это сделать с единственным отображением (в псевдокоде):

 foreach //li :: ID := string(./@id) ParentID := string(./ancestor::li[1]/@id) Label := normalize-space(./text()[1]) Order := count(./preceding-sibling::li)+1 Children := implode(",", ./ul/li/@id) 

Поскольку это может быть сделано для каждого узла li независимо от того, в каком порядке, это может быть идеальным совпадением для Iterator , здесь текущая функция:

 public function current() { return [ 'ID' => $this->evaluate('number(./@id)'), 'label' => $this->evaluate('normalize-space(./text()[1])'), 'order' => $this->evaluate('count(./preceding-sibling::li)+1'), 'parentID' => $this->evaluate('number(concat("0", ./ancestor::li[1]/@id))'), 'children' => $this->implodeNodes(',', './ul/li/@id'), ]; } 

Полный пример ( демонстрационный ) вывод и код:

 +----+----------------+-------+--------+----------+ | ID | LABEL | ORDER | PARENT | CHILDREN | +----+----------------+-------+--------+----------+ | 1 | Page 1 | 1 | 0 | | | 2 | Page 2 | 2 | 0 | 3,4,5 | | 3 | Sub Page A | 1 | 2 | | | 4 | Sub Page B | 2 | 2 | | | 5 | Sub Page C | 3 | 2 | 6 | | 6 | Sub Sub Page I | 1 | 5 | | | 7 | Page 3 | 3 | 0 | 8 | | 8 | Sub Page D | 1 | 7 | | | 9 | Page 4 | 4 | 0 | | +----+----------------+-------+--------+----------+ class HtmlListIterator extends IteratorIterator { private $xpath; public function __construct($html) { $doc = new DOMDocument(); $doc->loadHTML($html); $this->xpath = new DOMXPath($doc); parent::__construct($this->xpath->query('//li')); } private function evaluate($expression) { return $this->xpath->evaluate($expression, parent::current()); } private function implodeNodes($glue, $expression) { return implode( $glue, array_map(function ($a) { return $a->nodeValue; }, iterator_to_array($this->evaluate($expression, parent::current()))) ); } public function current() { return [ 'ID' => $this->evaluate('number(./@id)'), 'label' => $this->evaluate('normalize-space(./text()[1])'), 'order' => $this->evaluate('count(./preceding-sibling::li)+1'), 'parentID' => $this->evaluate('number(concat("0", ./ancestor::li[1]/@id))'), 'children' => $this->implodeNodes(',', './ul/li/@id'), ]; } } print_result(new HtmlListIterator($html)); function print_result($result) { echo '+----+----------------+-------+--------+----------+ | ID | LABEL | ORDER | PARENT | CHILDREN | +----+----------------+-------+--------+----------+ '; foreach ($result as $line) { vprintf("| %' 2d | %' -14s | %' 2d | %' 2d | %-8s |\n", $line); } echo '+----+----------------+-------+--------+----------+ '; }