使用 Pyparsing 解析自定义格式(花括号分隔)文本配置
Parsing a custom format (curly braces separated) text configuration with Pyparsing
我需要解析一些负载均衡器配置部分。这看起来很简单(至少对于人类而言)。
Config 由几个对象组成,它们的内容在花括号中,如下所示:
ltm rule ssl-header-insert {
when HTTP_REQUEST {
HTTP::header insert "X-SSL-Connection" "yes"
}
}
ltm rule some_redirect {
priority 1
when HTTP_REQUEST {
if { (not [class match [IP::remote_addr] equals addresses_group ]) }
{
HTTP::redirect "http://some.page.example.com"
TCP::close
event disable all
}
}
每个section/object的内容都是TCL代码所以会有花括号嵌套。我想要实现的是将其成对解析为:对象标识符(在 ltm rule
关键字之后)及其内容(大括号内的 tcl 代码)。
我查看了一些示例并进行了很多实验,但这确实让我很为难。我在 pyparsing 中进行了一些调试(这对我来说也有点混乱),我认为我无法以某种方式检测到右大括号,但无法弄清楚。
到目前为止我想到了什么:
from pyparsing import *
import json
list_sample = """ltm rule ssl-header-insert {
when HTTP_REQUEST {
HTTP::header insert "X-SSL-Connection" "yes"
}
}
ltm rule some_redirect {
priority 1
when HTTP_REQUEST {
if { (not [class match [IP::remote_addr] equals addresses_group ]) }
{
HTTP::redirect "http://some.page.example.com"
TCP::close
event disable all
}
}
}
ltm rule http_header_replace {
when HTTP_REQUEST {
HTTP::header replace Host some.host.example.com
}
}"""
ParserElement.defaultWhitespaceChars=(" \t")
NL = LineEnd()
END = StringEnd()
LBRACE, RBRACE = map(Suppress, '{}')
ANY_HEADER = Suppress("ltm rule ") + Word(alphas, alphanums + "_-")
END_MARK = Literal("ltm rule")
CONTENT_LINE = (~ANY_HEADER + (NotAny(RBRACE + FollowedBy(END_MARK)) + ~END + restOfLine) | (~ANY_HEADER + NotAny(RBRACE + FollowedBy(END)) + ~END + restOfLine)) | (~RBRACE + ~END + restOfLine)
ANY_HEADER.setName("HEADER").setDebug()
LBRACE.setName("LBRACE").setDebug()
RBRACE.setName("RBRACE").setDebug()
CONTENT_LINE.setName("LINE").setDebug()
template_defn = ZeroOrMore((ANY_HEADER + LBRACE +
Group(ZeroOrMore(CONTENT_LINE)) +
RBRACE))
template_defn.ignore(NL)
results = template_defn.parseString(list_sample).asList()
print("Raw print:")
print(results)
print("----------------------------------------------")
print("JSON pretty dump:")
print json.dumps(results, indent=2)
我在调试中看到一些匹配有效,但最后它失败了,结果是一个空列表。
旁注 - 我的 CONTENT_LINE
语法部分一般来说可能过于复杂,但到目前为止我还没有找到任何更简单的方法来覆盖它。
接下来要弄清楚如何在内容部分保留新行和制表符,因为我需要在输出中保持不变。但是看起来我必须首先使用 ignore()
函数(跳过新行)来解析多行文本,所以这是另一个挑战。
如果有人能帮助我找出问题所在,我将不胜感激。或者也许我应该采取其他方法?
我认为 nestedExpr('{', '}')
会有所帮助。这将处理嵌套的“{}”,并且包装在 originalTextFor
中将保留换行符和 spaces.
import pyparsing as pp
LTM, RULE = map(pp.Keyword, "ltm rule".split())
ident = pp.Word(pp.alphas, pp.alphanums+'-_')
ltm_rule_expr = pp.Group(LTM + RULE
+ ident('name')
+ pp.originalTextFor(pp.nestedExpr('{', '}'))('body'))
使用您的示例字符串(在添加缺失的尾随“}”之后):
for rule, _, _ in ltm_rule_expr.scanString(sample):
print(rule[0].name, rule[0].body.splitlines()[0:2])
给予
ssl-header-insert ['{', ' when HTTP_REQUEST {']
some_redirect ['{', ' priority 1']
dump()
也是列出返回的 ParseResults 内容的好方法:
for rule, _, _ in ltm_rule_expr.scanString(sample):
print(rule[0].dump())
print()
['ltm', 'rule', 'ssl-header-insert', '{\n when HTTP_REQUEST {\n HTTP::header insert "X-SSL-Connection" "yes"\n}\n}']
- body: '{\n when HTTP_REQUEST {\n HTTP::header insert "X-SSL-Connection" "yes"\n}\n}'
- name: 'ssl-header-insert'
['ltm', 'rule', 'some_redirect', '{\n priority 1\n\nwhen HTTP_REQUEST {\n\n if { (not [class match [IP::remote_addr] equals addresses_group ]) }\n {\n HTTP::redirect "http://some.page.example.com"\n TCP::close\n event disable all\n }\n}}']
- body: '{\n priority 1\n\nwhen HTTP_REQUEST {\n\n if { (not [class match [IP::remote_addr] equals addresses_group ]) }\n {\n HTTP::redirect "http://some.page.example.com"\n TCP::close\n event disable all\n }\n}}'
- name: 'some_redirect'
请注意,我将 'ltm'
和 'rule'
分解为单独的关键字表达式。这可以防止开发人员将有效代码编写为 ltm rule blah
,在 "ltm" 和 "rule" 之间有 > 1 space 的情况。这种事情经常发生,你永远不知道白色space会出现在哪里
我需要解析一些负载均衡器配置部分。这看起来很简单(至少对于人类而言)。
Config 由几个对象组成,它们的内容在花括号中,如下所示:
ltm rule ssl-header-insert {
when HTTP_REQUEST {
HTTP::header insert "X-SSL-Connection" "yes"
}
}
ltm rule some_redirect {
priority 1
when HTTP_REQUEST {
if { (not [class match [IP::remote_addr] equals addresses_group ]) }
{
HTTP::redirect "http://some.page.example.com"
TCP::close
event disable all
}
}
每个section/object的内容都是TCL代码所以会有花括号嵌套。我想要实现的是将其成对解析为:对象标识符(在 ltm rule
关键字之后)及其内容(大括号内的 tcl 代码)。
我查看了一些示例并进行了很多实验,但这确实让我很为难。我在 pyparsing 中进行了一些调试(这对我来说也有点混乱),我认为我无法以某种方式检测到右大括号,但无法弄清楚。
到目前为止我想到了什么:
from pyparsing import *
import json
list_sample = """ltm rule ssl-header-insert {
when HTTP_REQUEST {
HTTP::header insert "X-SSL-Connection" "yes"
}
}
ltm rule some_redirect {
priority 1
when HTTP_REQUEST {
if { (not [class match [IP::remote_addr] equals addresses_group ]) }
{
HTTP::redirect "http://some.page.example.com"
TCP::close
event disable all
}
}
}
ltm rule http_header_replace {
when HTTP_REQUEST {
HTTP::header replace Host some.host.example.com
}
}"""
ParserElement.defaultWhitespaceChars=(" \t")
NL = LineEnd()
END = StringEnd()
LBRACE, RBRACE = map(Suppress, '{}')
ANY_HEADER = Suppress("ltm rule ") + Word(alphas, alphanums + "_-")
END_MARK = Literal("ltm rule")
CONTENT_LINE = (~ANY_HEADER + (NotAny(RBRACE + FollowedBy(END_MARK)) + ~END + restOfLine) | (~ANY_HEADER + NotAny(RBRACE + FollowedBy(END)) + ~END + restOfLine)) | (~RBRACE + ~END + restOfLine)
ANY_HEADER.setName("HEADER").setDebug()
LBRACE.setName("LBRACE").setDebug()
RBRACE.setName("RBRACE").setDebug()
CONTENT_LINE.setName("LINE").setDebug()
template_defn = ZeroOrMore((ANY_HEADER + LBRACE +
Group(ZeroOrMore(CONTENT_LINE)) +
RBRACE))
template_defn.ignore(NL)
results = template_defn.parseString(list_sample).asList()
print("Raw print:")
print(results)
print("----------------------------------------------")
print("JSON pretty dump:")
print json.dumps(results, indent=2)
我在调试中看到一些匹配有效,但最后它失败了,结果是一个空列表。
旁注 - 我的 CONTENT_LINE
语法部分一般来说可能过于复杂,但到目前为止我还没有找到任何更简单的方法来覆盖它。
接下来要弄清楚如何在内容部分保留新行和制表符,因为我需要在输出中保持不变。但是看起来我必须首先使用 ignore()
函数(跳过新行)来解析多行文本,所以这是另一个挑战。
如果有人能帮助我找出问题所在,我将不胜感激。或者也许我应该采取其他方法?
我认为 nestedExpr('{', '}')
会有所帮助。这将处理嵌套的“{}”,并且包装在 originalTextFor
中将保留换行符和 spaces.
import pyparsing as pp
LTM, RULE = map(pp.Keyword, "ltm rule".split())
ident = pp.Word(pp.alphas, pp.alphanums+'-_')
ltm_rule_expr = pp.Group(LTM + RULE
+ ident('name')
+ pp.originalTextFor(pp.nestedExpr('{', '}'))('body'))
使用您的示例字符串(在添加缺失的尾随“}”之后):
for rule, _, _ in ltm_rule_expr.scanString(sample):
print(rule[0].name, rule[0].body.splitlines()[0:2])
给予
ssl-header-insert ['{', ' when HTTP_REQUEST {']
some_redirect ['{', ' priority 1']
dump()
也是列出返回的 ParseResults 内容的好方法:
for rule, _, _ in ltm_rule_expr.scanString(sample):
print(rule[0].dump())
print()
['ltm', 'rule', 'ssl-header-insert', '{\n when HTTP_REQUEST {\n HTTP::header insert "X-SSL-Connection" "yes"\n}\n}']
- body: '{\n when HTTP_REQUEST {\n HTTP::header insert "X-SSL-Connection" "yes"\n}\n}'
- name: 'ssl-header-insert'
['ltm', 'rule', 'some_redirect', '{\n priority 1\n\nwhen HTTP_REQUEST {\n\n if { (not [class match [IP::remote_addr] equals addresses_group ]) }\n {\n HTTP::redirect "http://some.page.example.com"\n TCP::close\n event disable all\n }\n}}']
- body: '{\n priority 1\n\nwhen HTTP_REQUEST {\n\n if { (not [class match [IP::remote_addr] equals addresses_group ]) }\n {\n HTTP::redirect "http://some.page.example.com"\n TCP::close\n event disable all\n }\n}}'
- name: 'some_redirect'
请注意,我将 'ltm'
和 'rule'
分解为单独的关键字表达式。这可以防止开发人员将有效代码编写为 ltm rule blah
,在 "ltm" 和 "rule" 之间有 > 1 space 的情况。这种事情经常发生,你永远不知道白色space会出现在哪里