如何在 html 文件中执行与标签无关的文本字符串搜索?

How to perform a tag-agnostic text string search in an html file?

我正在使用启用了 --xmlfilter 选项的 LanguageTool (LT) 来对 HTML 文件进行拼写检查。这会强制 LanguageTool 在 运行 拼写检查之前去除所有标签。

这也意味着所有报告的字符位置都是关闭的,因为 LT 没有 "see" 标签。

例如,如果我检查以下 HTML 片段:

<p>This is kin<b>d</b> o<i>f</i> a <b>stupid</b> question.</p>

LanguageTool 会将其视为纯文本句子:

    This is kind of a stupid question.

和returns以下消息:

<error category="Grammar" categoryid="GRAMMAR" context="                This is kind of a stupid question.    " contextoffset="24" errorlength="9" fromx="8" fromy="8" locqualityissuetype="grammar" msg="Don't include 'a' after a classification term. Use simply 'kind of'." offset="24" replacements="kind of" ruleId="KIND_OF_A" shortmsg="Grammatical problem" subId="1" tox="17" toy="8"/>

(在此特定示例中,LT 已标记 "kind of a.")

由于搜索字符串可能包含在标签中并且可能出现多次,因此我无法进行简单的索引搜索。

在 HTML 文件中可靠地定位任何给定文本字符串的最有效 Python 解决方案是什么? (LT returns 一个大概的字符位置,可能会偏离 10-30%,具体取决于标签的数量,以及标记词前后的词。)

即我需要进行忽略所有标签的搜索,但将它们包括在字符位置计数中。

在此特定示例中,我必须找到 "kind of a" 并找到字母 k 的位置:

kin<b>d</b> o<i>f</i>a

--xmlfilter 选项因此类问题而被弃用。正确的解决方案是自己删除标签但保留位置,以便您有一个映射来更正从 LT 返回的结果。当使用来自 Java 的 LT 时,AnnotatedText 支持这一点,但算法应该足够简单以移植它。 (完全披露:我是 LT 的维护者)

这可能不是最快的方法,但 pyparsing 可以识别大多数形式的 HTML 标签。下面的代码反转了典型的扫描,创建一个匹配任何单个字符的扫描器,然后配置扫描器以跳过 HTML 打开和关闭标签,以及常见的 HTML '&xxx;' 实体. pyparsing 的 scanString 方法 returns 生成匹配的标记、每个匹配项的开始和结束位置的生成器,因此很容易构建一个列表,将标记外的每个字符映射到其原始字符地点。从那里开始,剩下的几乎只是 ''.join 和索引到列表中。请参阅下面代码中的注释:

test = "<p>This &nbsp;is kin<b>d</b> o<i>f</i> a <b>stupid</b> question.</p>"

from pyparsing import Word, printables, anyOpenTag, anyCloseTag, commonHTMLEntity

non_tag_text = Word(printables+' ',  exact=1).leaveWhitespace()
non_tag_text.ignore(anyOpenTag | anyCloseTag | commonHTMLEntity)

# use scanString to get all characters outside of tags, and build list
# of (char,loc) tuples
char_locs = [(t[0], loc) for t,loc,endloc in non_tag_text.scanString(test)]

# imagine a world without HTML tags...
untagged = ''.join(ch for ch, loc in char_locs)

# look for our string in the untagged text, then index into the char,loc list
# to find the original location
search_str = 'kind of a'
orig_loc = char_locs[untagged.find(search_str)][1]

# print the test string, and mark where we found the matching text
print(test)
print(' '*orig_loc + '^')

"""
Should look like this:

<p>This &nbsp;is kin<b>d</b> o<i>f</i> a <b>stupid</b> question.</p>
                 ^
"""