DOMXpath и PHP: как обернуть кучу ‹li› внутри ‹ul›

У меня есть html-документ с этой не очень хорошей разметкой, без 'ul':

<p>Lorem</p>
<p>Ipsum...</p>
<li class='item'>...</li>
<li class='item'>...</li>
<li class='item'>...</li>
<div>...</div>

Теперь я пытаюсь «схватить» все li-элементы и обернуть их в ul-список, который я хотел бы разместить в том же месте, используя PHP и DOMXPath. Мне удается найти и «удалить» li-элементы:

$elements =  $xpath->query('//li[@class="item"]');

$wrapper = $document->createElement('ul');
foreach($elements as $child) {
    $wrapper->appendChild($child);
}

person chrney    schedule 25.11.2015    source источник
comment
Не знаю, как поставить ul в нужное место - думаю, это именно моя проблема.   -  person chrney    schedule 25.11.2015


Ответы (2)


Возможно, вы сможете получить parentNode первого <li>, а затем использовать метод insertBefore:

$html = <<<HTML
<p>Lorem</p>
<p>Ipsum...</p>
<li class='item'>...</li>
<li class='item'>...</li>
<li class='item'>...</li>
<div>...</div>
HTML;
$doc = new DOMDocument();
$doc->loadHTML($html);

$xpath = new DOMXpath($doc);

$elements = $xpath->query('//li[@class="item"]');

$wrapper = $doc->createElement('ul');
$elements->item(0)->parentNode->insertBefore(
    $wrapper, $elements->item(0)
);

foreach($elements as $child) {
    $wrapper->appendChild($child);
}

echo $doc->saveHTML();

Демо

person The fourth bird    schedule 25.11.2015

Вот что вам нужно. Возможно, вам придется настроить XPath-запрос для вашего реального HTML.

$document = new DOMDocument;

// We don't want to bother with white spaces
$document->preserveWhiteSpace = false;

$html = <<<EOT
<p>Lorem</p>
<p>Ipsum...</p>
<li class='item'>...</li>
<li class='item'>...</li>
<li class='item'>last...</li>
<div>...</div>
EOT;

$document->LoadHtml($html);

$xpath = new DOMXPath($document);

$elements =  $xpath->query('//li[@class="item"]');

// Saves a reference to the Node that is positioned right after our li's
$ref = $xpath->query('//li[@class="item"][last()]')->item(0)->nextSibling;

$wrapper = $document->createElement('ul');
foreach($elements as $child) {
    $wrapper->appendChild($child);
} 

$ref->parentNode->insertBefore($wrapper, $ref);

echo $document->saveHTML();

Пример выполнения: https://repl.it/B3UO/24

person Lucas Pottersky    schedule 25.11.2015
comment
Извините, приятель, у меня нет возможности точно узнать, какой элемент находится прямо перед li, как в вашем примере $div = $xpath-›query('//div')-›item(0) ; - person chrney; 26.11.2015
comment
Как я уже сказал, незнание вашего документа затрудняет отправку ответа. Вы можете настроить эту часть, чтобы сделать что-то вроде: $xpath->query('//li[last()]')->item(0)->next_sibling(); - person Lucas Pottersky; 26.11.2015
comment
Спасибо! Да, я забыл упомянуть, что эти ли могут быть не в определенном месте, хотя все они следуют друг за другом. Предложенное вами исправление дало мне «Вызов функции-члена next_sibling() при нулевом значении», вероятно, потому, что я удалил li, еще не вернув их обратно, таким образом, там, где нет li, верно? - person chrney; 26.11.2015
comment
Я обновил ответ. Теперь ты должен быть в порядке. $ref = $xpath->query('//li[@class="item"][last()]')->item(0)->nextSibling; - person Lucas Pottersky; 26.11.2015
comment
Это работало идеально, насколько я описал это. Опять же, li содержат a-теги с атрибутами (href) — их больше нет (a-тег и атрибуты). Есть идеи, как их сохранить? - person chrney; 26.11.2015
comment
Я добавил теги ‹a› в разметку, и, кажется, все работает нормально repl.it/B3UO/25 - person Lucas Pottersky; 26.11.2015
comment
Спасибо. Не уверен, почему это не работает для меня, но я просто возвращаю плоский узел. Не могли бы вы изменить свой пример, чтобы вы также проверяли материал next_sibling(), а не жестко запрограммированный div? - person chrney; 27.11.2015