彻底解析所有匹配项的文件
Exhaustively parse file for all matches
我有一个使用 pyparsing 解析一些日志文件的语法,但 运行 遇到了一个问题,即只返回第一个匹配项。有没有办法确保我得到详尽的匹配项?这是一些代码:
from pyparsing import Literal, Optional, oneOf, OneOrMore, ParserElement, Regex, restOfLine, Suppress, ZeroOrMore
ParserElement.setDefaultWhitespaceChars(' ')
dt = Regex(r'''\d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 20\d\d \d\d:\d\d:\d\d\,\d{3}''')
# TODO maybe add a parse action to make a datetime object out of the dt capture group
log_level = Suppress('[') + oneOf("INFO DEBUG ERROR WARN TRACE") + Suppress(']')
package_name = Regex(r'''(com|org|net)\.(\w+\.)+\w+''')
junk_data = Optional(Regex('\(.*?\)'))
guid = Regex('[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}')
first_log_line = dt.setResultsName('datetime') + \
log_level('log_level') + \
guid('guid') + \
junk_data('junk') + \
package_name('package_name') + \
Suppress(':') + \
restOfLine('message') + \
Suppress('\n')
additional_log_lines = Suppress('\t') + package_name + restOfLine
log_entry = (first_log_line + Optional(ZeroOrMore(additional_log_lines)))
log_batch = OneOrMore(log_entry)
在我看来,最后两行相当于
log_entry := first_log_line | first_log_line additional_log_lines
additional_log_lines := additional_log_line | additional_log_line additional_log_lines
log_batch := log_entry | log_entry log_batch
或者类似的东西。我在想这个错误吗?当我执行 print(log_batch.parseString(data).dump())
时,我只看到一个与所有预期标记的匹配项。
因此,有一种解决方法似乎可以解决问题。无论出于何种原因,scanString
确实 适当地遍历了它们,因此我可以非常简单地在生成器中获取我的匹配项:
matches = (m for m, _, _ in log_batch.scanString(data))
仍然不确定为什么 parseString
没有 详尽地工作,并且仍然有点担心我误解了有关 pyparsing 的一些东西,所以更多的指针是欢迎到这里。
您的 scanString
行为是一个强有力的线索。假设我写了一个表达式来匹配一个或多个项目,并且错误地定义了我的表达式,以至于我的列表中的第二个项目不匹配。然后 OneOrMore(expr)
会失败,而 expr.scanString
会 "succeed",因为它会给我 更多 匹配,但仍然会忽略我可能拥有的匹配想要,但解析错误。
import pyparsing as pp
data = "AAA _AB BBB CCC"
expr = pp.Word(pp.alphas)
print(pp.OneOrMore(expr).parseString(data))
给出:
['AAA']
乍一看,OneOrMore
似乎失败了,而 scanString
显示了更多匹配项:
['AAA']
['AB'] <- really wanted '_AB' here
['BBB']
['CCC']
这是一个使用 scanString
的循环,它不打印匹配项,而是打印匹配项之间的间隙以及它们的开始位置:
# loop to find non-matching parts in data
last_end = 0
for t,s,e in expr.scanString(data):
gap = data[last_end:s]
print(s, ':', repr(gap))
last_end = e
给予:
0 : ''
5 : ' _' <-- AHA!!
8 : ' '
12 : ' '
这是另一种可视化方式。
# print markers where each match begins in input string
markers = [' ']*len(data)
for t,s,e in expr.scanString(data):
markers[s] = '^'
print(data)
print(''.join(markers))
打印:
AAA _AB BBB CCC
^ ^ ^ ^
您的代码会稍微复杂一些,因为您的数据跨越多行,但是使用 pyparsing
的 line
、lineno
和 col
方法,您可以做类似的事情。
我有一个使用 pyparsing 解析一些日志文件的语法,但 运行 遇到了一个问题,即只返回第一个匹配项。有没有办法确保我得到详尽的匹配项?这是一些代码:
from pyparsing import Literal, Optional, oneOf, OneOrMore, ParserElement, Regex, restOfLine, Suppress, ZeroOrMore
ParserElement.setDefaultWhitespaceChars(' ')
dt = Regex(r'''\d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 20\d\d \d\d:\d\d:\d\d\,\d{3}''')
# TODO maybe add a parse action to make a datetime object out of the dt capture group
log_level = Suppress('[') + oneOf("INFO DEBUG ERROR WARN TRACE") + Suppress(']')
package_name = Regex(r'''(com|org|net)\.(\w+\.)+\w+''')
junk_data = Optional(Regex('\(.*?\)'))
guid = Regex('[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}')
first_log_line = dt.setResultsName('datetime') + \
log_level('log_level') + \
guid('guid') + \
junk_data('junk') + \
package_name('package_name') + \
Suppress(':') + \
restOfLine('message') + \
Suppress('\n')
additional_log_lines = Suppress('\t') + package_name + restOfLine
log_entry = (first_log_line + Optional(ZeroOrMore(additional_log_lines)))
log_batch = OneOrMore(log_entry)
在我看来,最后两行相当于
log_entry := first_log_line | first_log_line additional_log_lines
additional_log_lines := additional_log_line | additional_log_line additional_log_lines
log_batch := log_entry | log_entry log_batch
或者类似的东西。我在想这个错误吗?当我执行 print(log_batch.parseString(data).dump())
时,我只看到一个与所有预期标记的匹配项。
因此,有一种解决方法似乎可以解决问题。无论出于何种原因,scanString
确实 适当地遍历了它们,因此我可以非常简单地在生成器中获取我的匹配项:
matches = (m for m, _, _ in log_batch.scanString(data))
仍然不确定为什么 parseString
没有 详尽地工作,并且仍然有点担心我误解了有关 pyparsing 的一些东西,所以更多的指针是欢迎到这里。
您的 scanString
行为是一个强有力的线索。假设我写了一个表达式来匹配一个或多个项目,并且错误地定义了我的表达式,以至于我的列表中的第二个项目不匹配。然后 OneOrMore(expr)
会失败,而 expr.scanString
会 "succeed",因为它会给我 更多 匹配,但仍然会忽略我可能拥有的匹配想要,但解析错误。
import pyparsing as pp
data = "AAA _AB BBB CCC"
expr = pp.Word(pp.alphas)
print(pp.OneOrMore(expr).parseString(data))
给出:
['AAA']
乍一看,OneOrMore
似乎失败了,而 scanString
显示了更多匹配项:
['AAA']
['AB'] <- really wanted '_AB' here
['BBB']
['CCC']
这是一个使用 scanString
的循环,它不打印匹配项,而是打印匹配项之间的间隙以及它们的开始位置:
# loop to find non-matching parts in data
last_end = 0
for t,s,e in expr.scanString(data):
gap = data[last_end:s]
print(s, ':', repr(gap))
last_end = e
给予:
0 : ''
5 : ' _' <-- AHA!!
8 : ' '
12 : ' '
这是另一种可视化方式。
# print markers where each match begins in input string
markers = [' ']*len(data)
for t,s,e in expr.scanString(data):
markers[s] = '^'
print(data)
print(''.join(markers))
打印:
AAA _AB BBB CCC
^ ^ ^ ^
您的代码会稍微复杂一些,因为您的数据跨越多行,但是使用 pyparsing
的 line
、lineno
和 col
方法,您可以做类似的事情。