通过 PHP 选择性地替换某些 HTML 标签,同时保留一些
Selectivly replace certain HTML tags via PHP while keeping some
我有一个包含 html 代码的表单,由于我无法禁用 MCE 编辑器的自动格式化功能,该代码偶尔会有点乱。
我已经用 PHP 做了一些简单的替换,但还有一些我不太确定。
删除 ALL <span>
标签,例如 <span style="font-family: inherit; font-weight: inherit; line-height: 1.3;">
包括它们的 </span>
对应但 NOT这些标签内的内容。
例如:<span style="font-family: inherit; font-weight: inherit; line-height: 1.3;">Whosebug</span>
会变成 Whosebug
唯一应该不删除的是看起来像:
<span class="MainLink" style="font-weight: bold"><a href="https://website.com/" style="color: #2f82de; text-decoration: none">link name</a></span>
所以基本上任何封装 <a href...
link.
关于如何做到这一点的任何想法,我假设我需要使用正则表达式来做到这一点,但可能有 easier/better 方式。
试试这个:
$output = preg_replace('/<span[^>]*>(?!<a[ >])|(?<!\/a>)<\/span>/', '', $input);
这个正则表达式有 两个部分:
- 它会删除后面没有
<a
的任何 <span>
。
- 它会删除前面没有
/a>
的任何 </span>
。
注意:此解决方案是对问题的快速修复,并假定有效 HTML。在某些情况下这可能无法正常工作,但 OP 可能不会有任何这些情况(例如 span
内的自关闭 a
标签)。请参阅 Regex101,了解 所考虑的场景演示。
要执行此操作,您需要一个解析器,而不是正则表达式(另请参阅 The Famous Answer 关于此)
从这个例子开始 DOMDocument
和 DOMXpath
:
$dom = new DOMDocument();
libxml_use_internal_errors(1);
$dom->formatOutput = True;
$dom->loadHTML( $html );
$xpath = new DOMXPath( $dom );
while( $node = $xpath->query( '//span[not(contains(@class,"MainLink"))]' )->item(0) )
{
$fragment = $dom->createDocumentFragment();
while( $node->childNodes->length )
{
$fragment->appendChild( $node->childNodes->item(0) );
}
$node->parentNode->replaceChild( $fragment, $node );
}
echo $dom->saveHTML();
这一行:
while( $node = $xpath->query( '//span[not(contains(@class,"leave"))]' )->item(0) )
你在 class
属性中搜索不包含“leave”的每个 <span>
节点:如果找到此模式,则执行循环 (->item(0)
)。
然后您创建一个新的 DOMDocumentFragment
,一个特殊的临时节点,您可以在其中添加所有子节点:
while( $node->childNodes->length )
{
$fragment->appendChild( $node->childNodes->item(0) );
}
将所有节点子节点移动到新片段后,用片段替换空 <span>
节点。
其他对您有帮助的有用的 XPath:
//span[not(a)]
: select 所有 <span>
节点后面没有 <a>
子节点;
//span[not(contains(@class,"leave")) and not(contains(@class,"yes"))]
: select 所有 <span>
节点在 class
属性中没有“离开”或“是”。
已编辑以切换捕获组
我总是觉得做这样的事情真的很棘手,因为往往有太多无法预料的情况需要处理,否则他们会回来咬我。
也就是说,这种正则表达式的挑战通常很有趣。
我可能会尝试这样的事情:
(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)
在此处进行操作:https://regex101.com/r/qY8pL5/3
它所做的是首先尝试匹配 span 标签的开始,并找到开始标签最有可能结束的位置。这被放入一个非捕获组中,因此它可以被丢弃。接下来它确保接下来的两个字符不是锚标记,因为不应剥离包裹锚的跨度。下一部分是一个捕获组,它尽可能懒惰地捕获每个字符,直到它到达结束 span 标记。结束跨度标记也收集在非捕获组中,因此可以将其丢弃。
这将匹配独立的 span 标签和包裹在锚点中的 span 标签。它不会匹配包裹锚点的 span 标签。
在 php 中,您可以这样实现它:
$final_string = preg_replace('/(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)/', '', $string);
第一个参数是我们的正则表达式,第二个参数是我们想要用来替换我们的正则表达式匹配的内容 - 在这种情况下第一个(并且在这种情况下只是)捕获组被保留 - 最后我们通过我们希望匹配的字符串。
请注意,@fusion3k 是迄今为止最好的答案,它提供了进行任何类型的真正 HTML 解析的综合方法。
我有一个包含 html 代码的表单,由于我无法禁用 MCE 编辑器的自动格式化功能,该代码偶尔会有点乱。
我已经用 PHP 做了一些简单的替换,但还有一些我不太确定。
删除 ALL <span>
标签,例如 <span style="font-family: inherit; font-weight: inherit; line-height: 1.3;">
包括它们的 </span>
对应但 NOT这些标签内的内容。
例如:<span style="font-family: inherit; font-weight: inherit; line-height: 1.3;">Whosebug</span>
会变成 Whosebug
唯一应该不删除的是看起来像:
<span class="MainLink" style="font-weight: bold"><a href="https://website.com/" style="color: #2f82de; text-decoration: none">link name</a></span>
所以基本上任何封装 <a href...
link.
关于如何做到这一点的任何想法,我假设我需要使用正则表达式来做到这一点,但可能有 easier/better 方式。
试试这个:
$output = preg_replace('/<span[^>]*>(?!<a[ >])|(?<!\/a>)<\/span>/', '', $input);
这个正则表达式有 两个部分:
- 它会删除后面没有
<a
的任何<span>
。 - 它会删除前面没有
/a>
的任何</span>
。
注意:此解决方案是对问题的快速修复,并假定有效 HTML。在某些情况下这可能无法正常工作,但 OP 可能不会有任何这些情况(例如 span
内的自关闭 a
标签)。请参阅 Regex101,了解 所考虑的场景演示。
要执行此操作,您需要一个解析器,而不是正则表达式(另请参阅 The Famous Answer 关于此)
从这个例子开始 DOMDocument
和 DOMXpath
:
$dom = new DOMDocument();
libxml_use_internal_errors(1);
$dom->formatOutput = True;
$dom->loadHTML( $html );
$xpath = new DOMXPath( $dom );
while( $node = $xpath->query( '//span[not(contains(@class,"MainLink"))]' )->item(0) )
{
$fragment = $dom->createDocumentFragment();
while( $node->childNodes->length )
{
$fragment->appendChild( $node->childNodes->item(0) );
}
$node->parentNode->replaceChild( $fragment, $node );
}
echo $dom->saveHTML();
这一行:
while( $node = $xpath->query( '//span[not(contains(@class,"leave"))]' )->item(0) )
你在 class
属性中搜索不包含“leave”的每个 <span>
节点:如果找到此模式,则执行循环 (->item(0)
)。
然后您创建一个新的 DOMDocumentFragment
,一个特殊的临时节点,您可以在其中添加所有子节点:
while( $node->childNodes->length )
{
$fragment->appendChild( $node->childNodes->item(0) );
}
将所有节点子节点移动到新片段后,用片段替换空 <span>
节点。
其他对您有帮助的有用的 XPath:
//span[not(a)]
: select 所有<span>
节点后面没有<a>
子节点;//span[not(contains(@class,"leave")) and not(contains(@class,"yes"))]
: select 所有<span>
节点在class
属性中没有“离开”或“是”。
已编辑以切换捕获组
我总是觉得做这样的事情真的很棘手,因为往往有太多无法预料的情况需要处理,否则他们会回来咬我。
也就是说,这种正则表达式的挑战通常很有趣。
我可能会尝试这样的事情:
(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)
在此处进行操作:https://regex101.com/r/qY8pL5/3
它所做的是首先尝试匹配 span 标签的开始,并找到开始标签最有可能结束的位置。这被放入一个非捕获组中,因此它可以被丢弃。接下来它确保接下来的两个字符不是锚标记,因为不应剥离包裹锚的跨度。下一部分是一个捕获组,它尽可能懒惰地捕获每个字符,直到它到达结束 span 标记。结束跨度标记也收集在非捕获组中,因此可以将其丢弃。
这将匹配独立的 span 标签和包裹在锚点中的 span 标签。它不会匹配包裹锚点的 span 标签。
在 php 中,您可以这样实现它:
$final_string = preg_replace('/(?:<span[^>]*?>)(?!<a)(.*?)(?:<\/span>)/', '', $string);
第一个参数是我们的正则表达式,第二个参数是我们想要用来替换我们的正则表达式匹配的内容 - 在这种情况下第一个(并且在这种情况下只是)捕获组被保留 - 最后我们通过我们希望匹配的字符串。
请注意,@fusion3k 是迄今为止最好的答案,它提供了进行任何类型的真正 HTML 解析的综合方法。