PHP + regex查找并替换< a>源代码中某个类中的任何标记

hlswsv35  于 2023-04-07  发布在  PHP
关注(0)|答案(1)|浏览(121)

我有一个PHP变量中的HTML代码,我需要替换包含在另一个标签中的每个链接,该标签具有"obfuscate"类,例如:

<div class="obfuscate foobar">
    <strong>
        <a href="https://example.com" class="randomclass" target="_BLANK">test</a>
    </strong>
</div>

我需要将<a>标记替换为<span>,它继承了原始标记的所有内容,添加了"akn-obf-link"类,并在"data-o"属性下通过base64_encode()传递了一个模糊链接,如果链接具有target _blank或"0",则"data-b"属性的值为"1"
在上面的示例中,<a>标记应转换为:

<span class="akn-obf-link randomclass" data-o="aHR0cHM6Ly9leGFtcGxlLmNvbQ==" data-b="1">test</span>

我已经有一个代码,当<a>标签本身有"obfuscate"类时,如果这可能有帮助:

$result = preg_replace_callback('#<a[^>]+((?<=\s)href=(\"|\')([^\"\']*)(\'|\")[^>]+(?<=\s)class=(\"|\')[^\'\"]*(?<!\w|-)obfuscate(?!\w|-)[^\'\"]*(\"|\')|(?<=\s)class=(\"|\')[^\'\"]*(?<!\w|-)obfuscate(?!\w|-)[^\'\"]*(\"|\')[^>]+(?<=\s)href=(\"|\')([^\"\']*)(\'|\"))[^>]*>(.*)<\/a>#miUs', function($matches) {
        preg_match('#<a[^>]+(?<=\s)class=[\"|\\\']([^\\\'\"]+)[\"|\\\']#imUs',$matches[0],$matches_classes);
        $classes = trim(preg_replace('/\s+/',' ',str_replace('obfuscate','',$matches_classes[1])));
        return '<span class="akn-obf-link'.($classes?' '.$classes:'').'" data-o="'.base64_encode($matches[3]?:$matches[10]).'" data-b="'.((strpos(strtolower($matches[0]),'_blank')!==false)?'1':'0').'">'.$matches[12].'</span>';
    }, $code);

我需要相同的,但每当标签是在另一个标签,有"obfuscate"类。

p3rjfoxz

p3rjfoxz1#

试图用正则表达式解决这个问题将是痛苦和不安全的,原因在Stackoverflow上讨论了很多次。
通常,如果<div class="obfuscate">包含一些子<div>标记,会发生什么?

<div class="obfuscate foobar">
    <div>Something</div>
    <strong>
        <a href="https://example.com" class="randomclass" target="_BLANK">test</a>
    </strong>
</div>

这意味着你必须在正则表达式中处理递归,因为this regex将不起作用

~<\s*div\s+
# The mandatory class anywhere in the tag:
(?=[^>]*\bclass="(?<class>[^>]*?)")
# The rest of the attributes:
[^>]*>
# The content of the <div>, in an ungreedy way:
(.*?)
# The closing </div> tag:
<\s*/\s*div\s*>~gsx

作为we can see here,它不能捕获div的全部内容。你需要一个平衡良好的正则表达式来解决这个问题。
好的,让我们假设你有一个class="..."属性,就像在一部很好的老浪漫电影中一样,带有漂亮的双引号。我们假设你没有子div。这意味着你可以捕获内部的HTML,然后用一个相对复杂的模式来查找所有<a>标签,就像下面这样:

~# Declaration of all regex sub-routines:
(?(DEFINE)
# This sub-routine will match an attribute value with or without the quotes around it.
# So it will match "https://example.com" or 'https://example.com' (example with href)
# but also match my-class-name if we had something like <div class=my-class-name>
(?<attr_value_with_delim>(?:(?<delimiter>["']).*?(?:\k<delimiter>)|[^"'=<>\s]+))
)

# The regex pattern starts here:
# Match an opening <a> tag.
<\s*a\s+
# All the attributes are optional as <a name="my-anchor"></a> is allowed.
# But you can remove the ? at the end if you want to make them mandatory.
# You may also add other attributes such as hreflang, type, data-*, etc.
(?=[^>]*\bhref\s*=\s*(?<href>\g<attr_value_with_delim>))?
(?=[^>]*\bid\s*=\s*(?<id>\g<attr_value_with_delim>))?
(?=[^>]*\bclass\s*=\s*(?<class>\g<attr_value_with_delim>))?
(?=[^>]*\bname\s*=\s*(?<name>\g<attr_value_with_delim>))?
(?=[^>]*\btarget\s*=\s*(?<target>\g<attr_value_with_delim>))?
(?=[^>]*\btitle\s*=\s*(?<title>\g<attr_value_with_delim>))?
(?=[^>]*\bdownload\s*=\s*(?<download>\g<attr_value_with_delim>))?
(?=[^>]*\brel\s*=\s*(?<rel>\g<attr_value_with_delim>))?
[^>]*>
(.*?)
<\s*/\s*a\s*>~isxg

我在这里做的:https://regex101.com/r/ZSx69l/2
我想处理带双引号、单引号和不带引号的属性。我试图捕获不带引号的值,但没有找到正确的方法。没关系,因为使用preg_replace_callback()函数,您可以使用trim(..., '"\'')或正则表达式删除引号。然后您就可以计算base64并将其重写为所需的输出。
但这真的能解决所有格式错误的HTML代码吗?可能不会。

我会坚持使用PHP's DOMDocument,因为它有一些安全的东西。现在到处都安装了它,与bug的风险相比,执行时间并不重要。

您可能不需要解析HTML页面的全部内容,而需要使用防弹正则表达式来获取所需内容。

相关问题