【问题标题】:How would I modify a HTML string without touching the HTML elements?如何在不触及 HTML 元素的情况下修改 HTML 字符串?
【发布时间】:2020-10-05 10:13:00
【问题描述】:

假设我有这个字符串:

$test = '<p>You are such a <strong class="Stack">helpful</strong> Stack Exchange user.</p>';

然后我天真地用“Flack”替换任何“Stack”实例,我会得到这个:

$test = '<p>You are such a <strong class="Flack">helpful</strong> Flack Exchange user.</p>';

显然,我不想要这个。我只想改变实际的“内容”——而不是 HTML 部分。我想要这个:

$test = '<p>You are such a <strong class="Stack">helpful</strong> Flack Exchange user.</p>';

为此,必须进行某种智能解析。首先检测并从字符串中挑选出 HTML 元素,然后对“纯”内容字符串进行字符串替换操作,然后以某种方式将 HTML 元素原封不动地放回正确的位置。

我的大脑已经为此苦苦挣扎了很长一段时间,但我找不到任何合理的解决方案,既不老套又不容易出错。

令我震惊的是,这可能作为 PHP 内置的功能而存在。是这样吗?或者有什么方法可以让我以稳健和理智的方式完成这项工作?

我宁愿不尝试将所有 HTML 部分替换为 ____DO_NOT_TOUCH_1________DO_NOT_TOUCH_2____ 等。这似乎不是正确的方法。

【问题讨论】:

  • 使用 DOM 解析器,然后递归遍历所有节点,并且仅在实际文本节点上进行替换。

标签: php html string parsing


【解决方案1】:

您可以按照@04FS 的建议使用以下递归函数:

function replaceText(DOMNode $node, string $search, string $replace) {
    if($node->hasChildNodes()) {
        foreach($node->childNodes as $child) {
            if ($child->nodeType == XML_TEXT_NODE) {
                $child->textContent = str_replace($search, $replace, $child->textContent);   
            } else {
                replaceText($child, $search, $replace);     
            }
        }
    }
}

由于DOMDocument 也是DOMNode,所以您可以直接将其用作函数参数:

$html =
    '<div class="foo">
        <span class="foo">foo</span>
        <span class="foo">foo</span>
        foo
    </div>';

$doc = new DOMDocument();
$doc->loadXML($html); // alternatively loadHTML(), will throw an error on invalid HTML tags

replaceText($doc, 'foo', 'bar');

echo $doc->saveXML();
// or
echo $doc->saveXML($doc->firstChild);
// ... to get rid of the leading XML version tag

会输出

<div class="foo">
    <span class="foo">bar</span>
    <span class="foo">bar</span>
    bar
</div>

奖励:当您想要 str_replace 属性值时

function replaceTextInAttribute(DOMNode $node, string $attribute_name, string $search, string $replace) {
    if ($node->hasAttributes()) {
        foreach ($node->attributes as $attr) {
            if($attr->nodeName === $attribute_name) {
                $attr->nodeValue = str_replace($search, $replace, $attr->nodeValue);
            }
        }   
    }
    if($node->hasChildNodes()) {
        foreach($node->childNodes as $child) {
            replaceTextInAttribute($child, $attribute_name, $search, $replace);     
        }
    }
}

奖励 2: 使功能更具可扩展性

function modifyText(DOMNode $node, callable $userFunc) {
    if($node->hasChildNodes()) {
        foreach($node->childNodes as $child) {
            if ($child->nodeType == XML_TEXT_NODE) {
                $child->textContent = $userFunc($child->textContent);   
            } else {
                modifyText($child, $userFunc);     
            }
        }
    }
}

modifyText(
    $doc, 
    function(string $string) {
        return strtoupper(str_replace('foo', 'bar', $string));
    }
);

echo $doc->saveXML($doc->firstChild);

会输出

<div class="foo">
    <span class="foo">BAR</span>
    <span class="foo">BAR</span>
    BAR
</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-19
    • 2011-10-30
    • 2011-06-25
    • 1970-01-01
    • 2021-03-10
    • 1970-01-01
    • 2021-09-22
    • 2018-03-21
    相关资源
    最近更新 更多