Python 强制正则表达式在某些情况下失败

Python force regex to fail in certain conditions

在 python3 (3.9.6) 中,使用正则表达式 2.5.91 模块。

给定文件中的这些行:

1. <span>abc.</span>
2. <span>bcd</span>
3. <span>xyz.</span>
4. <span class="good">abc</span>
5. <span class="good">abc.</span>
6. <span class="good">xyz.</span>
7. <span id="whatever">def.</span>
8. <span id="whatever">xyz.</span>
9. <span class="good" id="whatever">ghi.</span>
10. <span class="bad" id="whatever">jkl.</span>
11. <span id="whatever" class="good">abc.</span>

我正在尝试创建一个正则表达式:

  1. 拒绝匹配任何带有 class、文本末尾没有句点或带有“xyz”的行。在文中。这意味着只有第 1 行和第 7 行应该匹配。
  2. 对于有效行,捕获 (id="whatever")(如果有)和跨度中的文本。

这不是一次处理一行;整个文件被读入一个变量,并且 regex.sub() 是 运行 使用捕获组一次对所有内容进行替换。 (更改超出了问题的范围。)因此,正则表达式必须在“坏”行上失败,因此子程序不会处理它们。

我能够成功地使用 *SKIP 和 *FAIL 使它在找到 class 时失败,如果它存在则捕获 id,但是当我尝试做同样的事情时事情就崩溃了如果它找到“xyz”。在文中。 IOW,这在好的线路上起作用并排除了带有 class.

的线路
newtext = regex.sub(r"""<span(?:(?:.*? class=".*?"(*SKIP)(*FAIL))?|( id="[a-z]+?")?)>([a-z]+\.)</span>""",r"""<span class="other"></span>""", text)

当我尝试添加另一个“|”时在文本部分如果找到“xyz.”就会失败,这会导致整个过程 always 失败,而不仅仅是当 xyz.被找到。 IOW,这根本不起作用,有或没有?在标签之间的文本部分中的第一组之后。

newtext = regex.sub(r"""<span(?:(?:.*? class=".*?"(*SKIP)(*FAIL))?|( id="[a-z]+?")?)>(?:(?:xyz\.(*SKIP)(*FAIL))|([a-z]+\.))</span>""",r"""<span class="other"></span>""", text)

诚然,我在 *SKIP/*FAIL 方面很弱,但我想当我第一个工作时我可能理解得足以让它工作,但第二个证明我错了。

那么,是否可以像上面那样在一个regex.sub中完成目标,即跳过该跳过的行,并添加一个class(在id前面,如果存在)到不存在的行? (我知道我现在没有对文本捕获组做任何事情,但我会的,它与这个问题无关。)

试试这个模式:^(?!<span[^<>]*class=)(?=<span(.*)>([^<>]*\.))(?!<span.*>xyz\.</span>).*$

参见正则表达式 demo

代码:

import re

pattern = "^(?!<span[^<>]*class=)(?=<span(.*)>([^<>]*\.))(?!<span.*>xyz\.</span>).*$"
text = """
<span>abc.</span>
<span>bcd</span>
<span>xyz.</span>
<span class="good">abc</span>
<span class="good">abc.</span>
<span class="good">xyz.</span>
<span id="whatever">def.</span>
<span id="whatever">xyz.</span>
<span class="good" id="whatever">ghi.</span>
<span class="bad" id="whatever">jkl.</span>
<span id="whatever" class="good">abc.</span>
"""
print(re.sub(pattern, "<span class=\"other\"\1>\2</span>", text, 0, re.MULTILINE))

输出:

<span class="other">abc.</span>
<span>bcd</span>
<span>xyz.</span>
<span class="good">abc</span>
<span class="good">abc.</span>xyz.
<span class="good">xyz.</span>
<span class="other" id="whatever">def.</span>
<span id="whatever">xyz.</span>
<span class="good" id="whatever">ghi.</span>
<span class="bad" id="whatever">jkl.</span>
<span id="whatever" class="good">abc.</span>

文件是否正确HTML?也许像这样尝试 html.parser.HTMLParser

from html import parser
from io import IOBase

class MyParser(parser.HTMLParser):
    """collects acceptable line-numbers/line-data"""
    
    def __init__(self, obj):
        """construct self, feed self"""
        super().__init__()
        self.data = list()
        if isinstance(obj, IOBase):
            line = obj.readline() # might need .decode("latin-1")
            while line is not "":
                self.feed(line)
                line = fp.readline() # .decode("latin-1")
            self.close()
        elif isinstance(obj, str):
            self.feed(obj)

    def handle_starttag(self, tag, attrs):
        """accept/reject tags"""
        attrs = dict(attrs)
        if tag == "span" and "class" not in attrs:
            data = self.get_starttag_text()
            if data.endswith(".") and "xyz." not in data:
                self.data.append((attrs.get("id"), data))


# usage would be like:
good_tags = MyParser(html_string) # a string or a file
for tag_id, text in good_tags.data:
    print(tag_id, text)

这种方法的好处是不需要复杂的 RE,甚至可以更改为使用简单的字符串方法,例如 endswith(如果您愿意,也可以使用更多 atomic/simpler RE)。构造函数被写出来接受文件指针或字符串对象。当文件内容被送入时,实例每次遇到标签都会调用handle_starttag方法,并执行其中的逻辑。在文件或字符串的末尾,所有需要的数据都应该在 data 属性中可用。