domDocument - определение позиции <br /> - PullRequest
4 голосов
/ 24 декабря 2011

Я использую domDocument для разбора некоторого HTML и хочу заменить разрывы на \ n.Однако у меня возникают проблемы с определением места, где на самом деле происходит разрыв в документе.

Учитывая следующий фрагмент HTML-кода из гораздо большего файла, который я читаю, используя $ dom-> loadHTMLFile ($ pFilename):

<p>Multiple-line paragraph<br />that has a close tag</p>

и мой код:

foreach ($dom->getElementsByTagName('*') as $domElement) {
    switch (strtolower($domElement->nodeName)) {
        case 'p' :
            $str = (string) $domElement->nodeValue;
            echo 'PARAGRAPH: ',$str,PHP_EOL;
            break;
        case 'br' :
            echo 'BREAK: ',PHP_EOL;
            break;
    }
}

Я получаю:

PARAGRAPH: Multiple-line paragraphthat has a close tag
BREAK:

Как определить позицию этого разрыва в абзаце и заменитьэто с \ n?

Или есть ли лучшая альтернатива, чем использование domDocument для анализа HTML, который может быть или не быть правильно сформированным?

Ответы [ 3 ]

8 голосов
/ 31 января 2012

Вы не можете получить позицию элемента, используя getElementsByTagName. Вы должны пройти childNodes каждого элемента и обрабатывать текстовые узлы и элементы отдельно.

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

function processElement(DOMNode $element){
    foreach($element->childNodes as $child){
        if($child instanceOf DOMText){
            echo $child->nodeValue,PHP_EOL;
        }elseif($child instanceOf DOMElement){
            switch($child->nodeName){
            case 'br':
                echo 'BREAK: ',PHP_EOL;
                break;
            case 'p':
                echo 'PARAGRAPH: ',PHP_EOL;
                processElement($child);
                echo 'END OF PARAGRAPH;',PHP_EOL;
                break;
            // etc.
            // other cases:
            default:
                processElement($child);
            }
        }
    }
}

$D = new DOMDocument;
$D->loadHTML('<p>Multiple-line paragraph<br />that has a close tag</p>');
processElement($D);

Будет выведено:

PARAGRAPH: 
Multiple-line paragraph
BREAK:
that has a close tag
END OF PARAGRAPH;
2 голосов
/ 31 января 2012

Поскольку вам не нужно иметь дело с дочерними узлами и другими вещами, почему бы просто не заменить вывод br?

$str = '<p>Multiple-line paragraph<br />that has<br>a close tag</p>';
echo preg_replace('/<br\s*\/?>/', "\n", $str);

:

<p>Multiple-line paragraph
that has
a close tag</p>

Alternative (используя Dom):

$str = '<p>Multiple-line<BR>paragraph<br />that<BR/>has<br>a close<Br>tag</p>';

$dom = new DomDocument();
$dom->loadHtml($str);

// using xpath here, because it will find every br-tag regardless
// of it being self-closing or not
$xpath = new DomXpath($dom);
foreach ($xpath->query('//br') as $br) {
  $br->parentNode->replaceChild($dom->createTextNode("\n"), $br);
}

// output whole html
echo $dom->saveHtml();

// or just the body child-nodes
$output = '';
foreach ($xpath->query('//body/*') as $bodyChild) {
  $output .= $dom->saveXml($bodyChild);
}

echo $output;
1 голос
/ 02 февраля 2012

Я написал простой класс, который не использует рекурсию и должен быть быстрее / потреблять меньше памяти, но в основном такая же примитивная идея, как у @Hrant Khachatrian's (перебирать все элементы и искать дочерние теги):

class DomScParser {

    public static function find(DOMNode &$parent_node, $tag_name) {
        //Check if we already got self-contained node
        if (!$parent_node->childNodes->length) {
            if ($parent_node->nodeName == $tag_name) {
                return $parent_node;
            }
        }
        //Initialize path array
        $dom_path = array($parent_node->firstChild);
        //Initialize found nodes array
        $found_dom_arr = array();
        //Iterate while we have elements in path
        while ($dom_path_size = count($dom_path)) {
            //Get last elemant in path
            $current_node = end($dom_path);
            //If it is an empty element - nothing to do here,
            //we should step back in our path.
            if (!$current_node) {
                array_pop($dom_path);
                continue;
            }

            if ($current_node->firstChild) {
                //If node has children - add it first child to end of path.
                //As we are looking for self-contained nodes without children,
                //this node is not what we are looking for - change corresponding
                //path elament to his sibling.
                $dom_path[] = $current_node->firstChild;
                $dom_path[$dom_path_size - 1] = $current_node->nextSibling;
            } else {
                //Check if we found correct node, if not - change corresponding
                //path elament to his sibling.
                if ($current_node->nodeName == $tag_name) {
                    $found_dom_arr[] = $current_node;
                }
                $dom_path[$dom_path_size - 1] = $current_node->nextSibling;
            }
        }
        return $found_dom_arr;
    }

    public static function replace(DOMNode &$parent_node, $search_tag_name, $replace_tag) {
        //Check if we got Node to replace found node or just some text.
        if (!$replace_tag instanceof DOMNode) {
            //Get DomDocument object
            if ($parent_node instanceof DOMDocument) {
                $dom = $parent_node;
            } else {
                $dom = $parent_node->ownerDocument;
            }
            $replace_tag=$dom->createTextNode($replace_tag);
        }
        $found_tags = self::find($parent_node, $search_tag_name);
        foreach ($found_tags AS &$found_tag) {
            $found_tag->parentNode->replaceChild($replace_tag->cloneNode(),$found_tag);
        }
    }

}

$D = new DOMDocument;
$D->loadHTML('<span>test1<br />test2</span>');
DomScParser::replace($D, 'br', "\n");

PS Также он не должен разбиваться на несколько вложенных тегов, так как не использует рекурсию.Пример HTML:

$html=str_repeat('<b>',100).'<br />'.str_repeat('</b>',100);
...