Почему DomDocument getElementsByTagName возвращает половину NodeList?

Я генерирую HTML-код с нестандартными тегами с помощью DomDocument, и результат такой:

/* Input HTML
  <div id="toobar_top">
    <widget id="flag_holder"></widget>
    <widget id="horizontal_menu"></widget>
  </div>
  <div id="header">
    <widget name="header"></widget>
  </div>
*/

Что я хочу сделать, так это «перевести» каждый виджет во что-то полезное... это простые заполнители с параметрами.

Извлечение функции из класса:

private function widgeter($doc) { //Give it an DomDocument HTML containing <widget> elements and will translate them into usable stuff
    $this->_widgetList = $doc->getElementsByTagName($this->_widgetTransformTo);
    foreach ($this->_widgetList as $widget) {
        $data = array();
        if ($widget->hasAttributes()) {
        foreach ($widget->attributes as $attribute) {
            $data[][$attribute->name] = $attribute->value;
            // @TODO: Implements Widget Transformation

        }
        }
        // Next 2 lines are just for debug
        $string = serialize($data);
        $newWidget = $doc->createElement('p', $string);
        $widget->parentNode->replaceChild($newWidget, $widget);
    }
    return $doc;
    }

затем, когда я сохраняю HTML(), я вижу $doc:

/* Output HTML
  <div id="toobar_top">
    <p>[{"id":"flag_holder"}]</p>
    <widget id="horizontal_menu"></widget>
  </div>
  <div id="header">
    <p>[{"id":"header"}]</p>
  </div>
*/

почему "horizontal_menu" не перевели?

Неважно, где находятся виджеты (я пробовал только с одним div со всеми виджетами и с div на виджет).

Я не могу понять это...


person MiPnamic    schedule 01.04.2011    source источник


Ответы (2)


Это происходит потому, что вы заменяете элементы в DOMNodeList, зацикливаясь на них. DOMNodeList не является массивом, поэтому foreach работает не с копией, а с самим объектом.

В основном, я думаю, что происходит следующее:

  • Вы заменяете первый экземпляр <widget> (элемент 0).
  • Указатель переместится к следующему элементу (элемент 1).
  • Элемент 0 был заменен и больше не существует.
  • Происходит смещение элемента: элемент 1 становится элементом 0, элемент 2 становится элементом 1.
  • Указатель по-прежнему указывает на элемент 1 (который изначально был элементом 2, эффективно пропуская узел).

Что вам нужно сделать, так это сохранить элементы в массиве, а затем изменить их, вместо того, чтобы зацикливаться на DOMNodeList:

$this->_widgetList = array();
foreach ($domNodeList as $node) {
   $this->_widgetList[] = $node;    
}

foreach ($this->_widgetList as $widget) {
   // do stuff
}
person netcoder    schedule 01.04.2011
comment
Именно то, что было объяснено @MiPnamic в чате. Вот, проголосуйте. :) - person salathe; 01.04.2011

чтобы избежать двух итераций, вы можете разобрать список элементов наоборот

$widgets = $doc->getElementsByTagName( 'widget' ); // get all elements

for( $i = $widget->length; $i > 0; $i-- ){
    $widget = $doc->getElementsByTagName( 'widget' )->item( $i - 1 );

    // do stuff whith the widget
}
person stef    schedule 09.07.2013