替换 PHP 中的跨度,但保留内容

Replace span's in PHP but keep content inside

我有以下字符串:

<span style="font-size: 13px;">
   <span style="">
      <span style="">
         <span style="font-family: Roboto, sans-serif;">
            <span style="">
               Some text content
            </span>
         </span>
      </span>
   </span>
</span>

我想使用 PHP 将此字符串更改为以下内容:

<span style="font-size: 13px;">
   <span style="font-family: Roboto, sans-serif;">
      Some text content
   </span>
</span>

我不知道该怎么做,因为当我尝试使用 str_replace 替换 <span style=""> 时,我不知道如何替换 </span> 并保留里面的内容。我的下一个问题是,我不知道我的字符串中有多少 <span style="">。我的字符串中不仅有 1 个这样的块。

在此先感谢您的帮助,也许对我的愚蠢问题感到抱歉 - 我还在学习中。

这很容易通过适当的 HTML 解析器完成。 PHP 具有 DOMDocument,它可以将 X/HTML 解析为 文档对象模型 ,然后可以按照您的需要进行操作。

解决这个问题的诀窍是能够递归地遍历 DOM 树,找出每个节点,并替换你不想要的节点。为此,我通过在此处扩展 DOMDocument 编写了一个简短的辅助方法...

$html = <<<'HTML'
<span style="font-size: 13px;">
   <span style="">
      <span style="">
         <span style="font-family: Roboto, sans-serif;">
            <span style="">
               Some text content
            </span>
         </span>
      </span>
   </span>
</span>
HTML;

class MyDOMDocument extends DOMDocument {
    public function walk(DOMNode $node, $skipParent = false) {
        if (!$skipParent) {
            yield $node;
        }
        if ($node->hasChildNodes()) {
            foreach ($node->childNodes as $n) {
                yield from $this->walk($n);
            }
        }
    }
}

libxml_use_internal_errors(true);

$dom = new MyDOMDocument;
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

$keep = $remove = [];

foreach ($dom->walk($dom->childNodes->item(0)) as $node) {
    if ($node->nodeName !== "span") { // we only care about span nodes
        continue;
    }
    // we'll get rid of all span nodes that don't have the style attribute
    if (!$node->hasAttribute("style") || !strlen($node->getAttribute("style"))) {
        $remove[] = $node;
        foreach($node->childNodes as $child) {
            $keep[] = [$child, $node];
        }
    }
}

// you have to modify them one by one in reverse order to keep the inner nodes
foreach($keep as [$a, $b]) {
    $b->parentNode->insertBefore($a, $b);
}
foreach($remove as $a) {
    if ($a->parentNode) {
        $a->parentNode->removeChild($a);
    }
}

// Now we should have a rebuilt DOM tree with what we expect:
echo $dom->saveHTML();

输出:

<span style="font-size: 13px;">


         <span style="font-family: Roboto, sans-serif;">

               Some text content

         </span>


</span>

有关修改 HTML 文档的更通用方法,请查看 XSLT(可扩展样式表语言转换)。 PHP 有一个 XSLT 库。

然后您有一个 XML 文档,其中包含您的转换规则:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html" indent="yes"/>

    <!-- remove spans with empty styles -->
    <xsl:template match="*[@style and string-length(./@style) = 0]">
        <xsl:apply-templates />
    </xsl:template>

    <!-- catch all to copy any elements that aren't matched in other templates -->
    <xsl:template match="*">
        <xsl:copy select=".">
            <!-- copy the attributes of the element -->
            <xsl:copy-of select="@*" />
            <!-- continue applying templates to this element's children -->
            <xsl:apply-templates select="*" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

那么你的PHP:

$sourceHtml = new DOMDocument();
$sourceHtml->load('source.html');

$xsl = new DOMDocument();
$xsl->load('transform.xsl');

$xsltProcessor = new XSLTProcessor;
$xsltProcessor->importStyleSheet($xsl); // attach the xsl rules

echo $xsltProcessor->transformToXML($sourceHtml);

$transformedHtml = $xsltProcessor->transformToDoc($sourceHtml);
$transformedHtml->saveHTMLFile('transformed.html');

XSLT 对这种事情非常强大,您可以为 parent/sibling 关系设置各种规则,并相应地修改属性和内容。