установить теги в html используя domdocument и preg_replace_callback - PullRequest
1 голос
/ 07 июля 2019

Я пытаюсь заменить слова из моего терминологического словаря на якорь (html), чтобы он получил всплывающую подсказку. Я сделал замену, но я не могу вернуть ее обратно в объект DomDocument.

Я сделал рекурсивную функцию, которая выполняет итерацию DOM, выполняет итерацию каждого дочернего узла, ищет слово в моем словаре и заменяет его на якорь.

Я использовал это с обычным preg_match в HTML, но это просто сталкивается с проблемами ... когда HTML становится сложным

Рекурсивная функция:

$terms = array(
   'example'=>'explanation about example'
);

function iterate_html($doc, $original_doc = null)
    {
    global $terms;

        if(is_null($original_doc)) {
            self::iterate_html($doc, $doc);
        }

        foreach($doc->childNodes as $childnode)
        {
            $children = $childnode->childNodes;
            if($children) {
                self::iterate_html($childnode);
            } else {

                $regexes = '~\b' . implode('\b|\b',array_keys($terms)) . '\b~i';
                $new_nodevalue = preg_replace_callback($regexes, function($matches) {
                    $doc = new DOMDocument();

                    $anchor = $doc->createElement('a', $matches[0]);
                    $anchor->setAttribute('class', 'text-info');
                    $anchor->setAttribute('data-toggle', 'tooltip');
                    $anchor->setAttribute('data-original-title', $terms[strtolower($matches[0])]);

                    return $doc->saveXML($anchor);

                }, $childnode->nodeValue);



                $dom = new DOMDocument();
                $template = $dom->createDocumentFragment();
                $template->appendXML($new_nodevalue);

                $original_doc->importNode($template->childNodes, true);
                $childnode->parentNode->replaceChild($template, $childnode);
            }
        }
    }

echo iterate_html('this is just some example text.');

Я ожидаю, что результат будет:

this is just some <a class="text-info" data-toggle="tooltip" data-original-title="explanation about example">example</a> text

1 Ответ

0 голосов
/ 07 июля 2019

Я не думаю, что создание рекурсивной функции для обхода DOM полезно, когда вы можете использовать запрос XPath. Кроме того, я не уверен, что preg_replace_callback является адаптированной функцией для этого случая. Я предпочитаю использовать preg_split. Вот пример:

$html = 'this is just some example text.';

$terms = array(
   'example'=>'explanation about example'
);

// sort by reverse order of key size
// (to be sure that the longest string always wins instead of the first in the pattern)

uksort($terms, function ($a, $b) {
    $diff = mb_strlen($b) - mb_strlen($a);

    return ($diff) ? $diff : strcmp($a, $b);
});

// build the pattern inside a capture group (to have delimiters in the results with the PREG_SPLIT_DELIM_CAPTURE option)
$pattern = '~\b(' . implode('|', array_map(function($i) { return preg_quote($i, '~'); }, array_keys($terms))) . ')\b~i';

// prevent eventual html errors to be displayed
$libxmlInternalErrors = libxml_use_internal_errors(true);

// determine if the html string have a root html element already, if not add a fake root.
$dom = new DOMDocument;
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$fakeRootElement = false;

if ( $dom->documentElement->nodeName !== 'html' ) {
    $dom->loadHTML("<div>$html</div>", LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
    $fakeRootElement = true;
}

libxml_use_internal_errors($libxmlInternalErrors);

// find all text nodes (not already included in a link or between other unwanted tags)
$xp = new DOMXPath($dom);
$textNodes = $xp->query('//text()[not(ancestor::a)][not(ancestor::style)][not(ancestor::script)]');

// replacement
foreach ($textNodes as $textNode) {
    $parts = preg_split($pattern, $textNode->nodeValue, -1, PREG_SPLIT_DELIM_CAPTURE);
    $fragment = $dom->createDocumentFragment();
    foreach ($parts as $k=>$part) {
        if ($k&1) {
            $anchor = $dom->createElement('a', $part);
            $anchor->setAttribute('class', 'text-info');
            $anchor->setAttribute('data-toggle', 'tooltip');
            $anchor->setAttribute('data-original-title', $terms[strtolower($part)]);
            $fragment->appendChild($anchor);
        } else {
            $fragment->appendChild($dom->createTextNode($part));
        }
    }
    $textNode->parentNode->replaceChild($fragment, $textNode);
}


// building of the result string
$result = '';

if ( $fakeRootElement ) {
    foreach ($dom->documentElement->childNodes as $childNode) {
        $result .= $dom->saveHTML($childNode);
    }
} else {
    $result = $dom->saveHTML();
}

echo $result;

демо

Не стесняйтесь помещать это в одну или несколько функций / методов, но имейте в виду, что этот вид редактирования имеет неотделимый вес и должен использоваться каждый раз, когда редактируется HTML (а не каждый раз, когда HTML отображается ).

...