通过 PyParsing 只解析块的相关部分
Parse only relevant parts of block via PyParsing
我正在使用 PyParsing 来解析如下所示的文件:
abc_module (
.test_test1 ( test_test_real1 ),
.abc_test ( abc_test_real ),
.obs_test_one ( obs_test_one_real ),
.obs_test_two ( obs_test_two_real )
);
cfg_module (
.test_test2 ( test_test_real2 ),
.xyz_test ( xyz_test_real )
);
xyz_module (
.test_test2 ( test_test_real2 ),
.xyz_test ( xyz_test_real ),
.obs_test_three ( obs_test_three_real ),
.ahc_test ( ahc_test_real ),
.obs_test_four ( obs_test_four_real )
);
我正在尝试提取每个代码块的模块名称以及以“.obs”开头的可选行,因此我希望此示例文件的 ParseResult 是:
ParseResult = [["abc_module", ".obs_test_one", ".obs_test_two"], ["cfg_module"], ["xyz_module", ".obs_test_three", ".obs_test_four"]]
到目前为止我设法解析了代码块,但问题是我无法只提取 .obs 部分,我尝试使用 PyParsings“skipTo”方法,但我认为这不是正确的工具为此。
到目前为止我的代码:
keyword = Word(alphanums + '_' + ".")
sub_module_parser = keyword.setResultsName("module_name") + Suppress("(") + OneOrMore(
keyword + Suppress("(" + keyword + ")" + Optional(","))).setResultsName("obs_param")
tl;dr - 最好不要尝试在一行中编写解析器,而是退后一步,先编写一个计划。
我通常会使用某种 semi-formal 符号来编写 BNF,但我会尝试一些不同的东西。 BNF 不必是 super-rigorous,只要它能阐明您对文本结构的想法即可。
这是一个非常干净的结构,没有递归嵌套,所以在实际编写任何代码之前,真的很适合花几分钟时间写下 pyparsing 代码的外观计划。它还将帮助您按逻辑组编写解析器 - 将整个解析器写在一行中没有实际好处,只会让以后更难理解和维护。
首先,在您的文本中寻找一些合乎逻辑的重复组。例如,您的子模块如下所示:
(a name starting with a dot) "(" (an identifier) ")"
每个模块都包含一个列表,用逗号分隔。
模块本身如下所示:
(an identifier) "(" (some submodules separated by commas) ")" ";"
以这样的结构思考有助于避免一些无用的错误,例如将分隔逗号作为列表项的一部分。
这就是我们要使用的 BNF:
module IS (an identifier) "(" (some submodules separated by commas) ")" ";"
submodule IS (a name starting with a dot) "(" (an identifier) ")"
要转换为 pyparsing,我们需要从各个部分开始,并最终构建回整个模块。
首先是标点符号。它在解析时很有用,但之后会变得混乱,所以我们从解析结果中抑制它们:
LPAR = Suppress("(")
RPAR = Suppress(")")
SEMI = Suppress(";")
接下来是标识符。在定义诸如标识符之类的东西时,一个常见的错误是只定义一个包含所有可能找到的字母的单词,例如 Word(alphanums + '_')
。这将匹配示例文本中的所有标识符,但也会匹配“___”、“123”和“2_3_4”,我认为您不想匹配这些。 Word 有一个 two-argument 形式来定义作为有效起始字符的字符,然后是有效的正文字符。假设您的标识符必须以字母开头,然后是任何其他字符 - 这至少会过滤掉没有字母的整数或标识符:
identifier = Word(alphas, alphanums+"_")
我要偷懒了,用这个技巧定义子模块标识符为:
submodule_identifier = Word(".", alphanums+"_")
看看这对子模块的“以点开头的名称”有何影响。
我们现在可以定义一个子模块表达式:
submodule = submodule_identifier + LPAR + identifier + RPAR
模块现在是:
module = identifier + LPAR + delimited_list(submodule) + RPAR
是否可以有一个空模块?如果是这样,那么子模块列表应该包含在一个 Optional 中。
module = identifier + LPAR + Optional(delimited_list(submodule)) + RPAR + SEMI
差不多就可以了。这是完整的解析器,添加了组和名称以帮助处理解析结果:
LPAR = Suppress("(")
RPAR = Suppress(")")
SEMI = Suppress(";")
identifier = Word(alphas, alphanums + "_").setName("identifier")
submodule_identifier = Word(".", alphanums + "_").setName("submodule_identifier")
submodule = Group(submodule_identifier("name") + LPAR + identifier("value") + RPAR).setName("submodule")
submodule_list = delimited_list(submodule).setName("submodule_list")
module = Group(identifier("name") + LPAR + Group(Optional(submodule_list))("submodules") + RPAR + SEMI).setName("module")
这是使用 pyparsing 3 中的新铁路图功能的此解析器的图表。
module.create_diagram("module_railroad_diag.html")
你的解析器将输出这个结构:
print(module[...].parseString(sample).dump())
[['abc_module', [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
[0]:
['abc_module', [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
- name: 'abc_module'
- submodules: [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
[0]:
['.test_test1', 'test_test_real1']
- name: '.test_test1'
- value: 'test_test_real1'
[1]:
['.abc_test', 'abc_test_real']
- name: '.abc_test'
- value: 'abc_test_real'
[2]:
['.obs_test_one', 'obs_test_one_real']
- name: '.obs_test_one'
- value: 'obs_test_one_real'
[3]:
['.obs_test_two', 'obs_test_two_real']
- name: '.obs_test_two'
- value: 'obs_test_two_real'
[1]:
['cfg_module', [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real']]]
- name: 'cfg_module'
- submodules: [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real']]
[0]:
['.test_test2', 'test_test_real2']
- name: '.test_test2'
- value: 'test_test_real2'
[1]:
['.xyz_test', 'xyz_test_real']
- name: '.xyz_test'
- value: 'xyz_test_real'
[2]:
['xyz_module', [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real'], ...
- name: 'xyz_module'
- submodules: [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real'], ...
[0]:
['.test_test2', 'test_test_real2']
- name: '.test_test2'
- value: 'test_test_real2'
[1]:
['.xyz_test', 'xyz_test_real']
- name: '.xyz_test'
- value: 'xyz_test_real'
[2]:
['.obs_test_three', 'obs_test_three_real']
- name: '.obs_test_three'
- value: 'obs_test_three_real'
[3]:
['.ahc_test', 'ahc_test_real']
- name: '.ahc_test'
- value: 'ahc_test_real'
[4]:
['.obs_test_four', 'obs_test_four_real']
- name: '.obs_test_four'
- value: 'obs_test_four_real'
最后,您只想显示那些以“.obs”开头的子模块。这最好使用解析操作来完成,添加到子模块列表以过滤掉您想要的那些:
def obs_items_only(t):
return ParseResults(item for item in t if item.name.startswith(".obs"))
submodule_list.addParseAction(obs_items_only)
添加此过滤解析操作后,结果 trim 下降为:
[['abc_module', [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]], ...
[0]:
['abc_module', [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]]
- name: 'abc_module'
- submodules: [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]
[0]:
['.obs_test_one', 'obs_test_one_real']
- name: '.obs_test_one'
- value: 'obs_test_one_real'
[1]:
['.obs_test_two', 'obs_test_two_real']
- name: '.obs_test_two'
- value: 'obs_test_two_real'
[1]:
['cfg_module', []]
- name: 'cfg_module'
- submodules: []
[2]:
['xyz_module', [['.obs_test_three', 'obs_test_three_real'], ['.obs_test_four', 'obs_test_four_real']]]
- name: 'xyz_module'
- submodules: [['.obs_test_three', 'obs_test_three_real'], ['.obs_test_four', 'obs_test_four_real']]
[0]:
['.obs_test_three', 'obs_test_three_real']
- name: '.obs_test_three'
- value: 'obs_test_three_real'
[1]:
['.obs_test_four', 'obs_test_four_real']
- name: '.obs_test_four'
- value: 'obs_test_four_real'
OP 练习:使用此解析器,您将如何添加对也可以是整数的子模块值的支持,您可以将其定义为 Word(nums)
?
我正在使用 PyParsing 来解析如下所示的文件:
abc_module (
.test_test1 ( test_test_real1 ),
.abc_test ( abc_test_real ),
.obs_test_one ( obs_test_one_real ),
.obs_test_two ( obs_test_two_real )
);
cfg_module (
.test_test2 ( test_test_real2 ),
.xyz_test ( xyz_test_real )
);
xyz_module (
.test_test2 ( test_test_real2 ),
.xyz_test ( xyz_test_real ),
.obs_test_three ( obs_test_three_real ),
.ahc_test ( ahc_test_real ),
.obs_test_four ( obs_test_four_real )
);
我正在尝试提取每个代码块的模块名称以及以“.obs”开头的可选行,因此我希望此示例文件的 ParseResult 是:
ParseResult = [["abc_module", ".obs_test_one", ".obs_test_two"], ["cfg_module"], ["xyz_module", ".obs_test_three", ".obs_test_four"]]
到目前为止我设法解析了代码块,但问题是我无法只提取 .obs 部分,我尝试使用 PyParsings“skipTo”方法,但我认为这不是正确的工具为此。
到目前为止我的代码:
keyword = Word(alphanums + '_' + ".")
sub_module_parser = keyword.setResultsName("module_name") + Suppress("(") + OneOrMore(
keyword + Suppress("(" + keyword + ")" + Optional(","))).setResultsName("obs_param")
tl;dr - 最好不要尝试在一行中编写解析器,而是退后一步,先编写一个计划。
我通常会使用某种 semi-formal 符号来编写 BNF,但我会尝试一些不同的东西。 BNF 不必是 super-rigorous,只要它能阐明您对文本结构的想法即可。
这是一个非常干净的结构,没有递归嵌套,所以在实际编写任何代码之前,真的很适合花几分钟时间写下 pyparsing 代码的外观计划。它还将帮助您按逻辑组编写解析器 - 将整个解析器写在一行中没有实际好处,只会让以后更难理解和维护。
首先,在您的文本中寻找一些合乎逻辑的重复组。例如,您的子模块如下所示:
(a name starting with a dot) "(" (an identifier) ")"
每个模块都包含一个列表,用逗号分隔。
模块本身如下所示:
(an identifier) "(" (some submodules separated by commas) ")" ";"
以这样的结构思考有助于避免一些无用的错误,例如将分隔逗号作为列表项的一部分。
这就是我们要使用的 BNF:
module IS (an identifier) "(" (some submodules separated by commas) ")" ";"
submodule IS (a name starting with a dot) "(" (an identifier) ")"
要转换为 pyparsing,我们需要从各个部分开始,并最终构建回整个模块。
首先是标点符号。它在解析时很有用,但之后会变得混乱,所以我们从解析结果中抑制它们:
LPAR = Suppress("(")
RPAR = Suppress(")")
SEMI = Suppress(";")
接下来是标识符。在定义诸如标识符之类的东西时,一个常见的错误是只定义一个包含所有可能找到的字母的单词,例如 Word(alphanums + '_')
。这将匹配示例文本中的所有标识符,但也会匹配“___”、“123”和“2_3_4”,我认为您不想匹配这些。 Word 有一个 two-argument 形式来定义作为有效起始字符的字符,然后是有效的正文字符。假设您的标识符必须以字母开头,然后是任何其他字符 - 这至少会过滤掉没有字母的整数或标识符:
identifier = Word(alphas, alphanums+"_")
我要偷懒了,用这个技巧定义子模块标识符为:
submodule_identifier = Word(".", alphanums+"_")
看看这对子模块的“以点开头的名称”有何影响。
我们现在可以定义一个子模块表达式:
submodule = submodule_identifier + LPAR + identifier + RPAR
模块现在是:
module = identifier + LPAR + delimited_list(submodule) + RPAR
是否可以有一个空模块?如果是这样,那么子模块列表应该包含在一个 Optional 中。
module = identifier + LPAR + Optional(delimited_list(submodule)) + RPAR + SEMI
差不多就可以了。这是完整的解析器,添加了组和名称以帮助处理解析结果:
LPAR = Suppress("(")
RPAR = Suppress(")")
SEMI = Suppress(";")
identifier = Word(alphas, alphanums + "_").setName("identifier")
submodule_identifier = Word(".", alphanums + "_").setName("submodule_identifier")
submodule = Group(submodule_identifier("name") + LPAR + identifier("value") + RPAR).setName("submodule")
submodule_list = delimited_list(submodule).setName("submodule_list")
module = Group(identifier("name") + LPAR + Group(Optional(submodule_list))("submodules") + RPAR + SEMI).setName("module")
这是使用 pyparsing 3 中的新铁路图功能的此解析器的图表。
module.create_diagram("module_railroad_diag.html")
你的解析器将输出这个结构:
print(module[...].parseString(sample).dump())
[['abc_module', [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
[0]:
['abc_module', [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
- name: 'abc_module'
- submodules: [['.test_test1', 'test_test_real1'], ['.abc_test', 'abc_test_real'], ...
[0]:
['.test_test1', 'test_test_real1']
- name: '.test_test1'
- value: 'test_test_real1'
[1]:
['.abc_test', 'abc_test_real']
- name: '.abc_test'
- value: 'abc_test_real'
[2]:
['.obs_test_one', 'obs_test_one_real']
- name: '.obs_test_one'
- value: 'obs_test_one_real'
[3]:
['.obs_test_two', 'obs_test_two_real']
- name: '.obs_test_two'
- value: 'obs_test_two_real'
[1]:
['cfg_module', [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real']]]
- name: 'cfg_module'
- submodules: [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real']]
[0]:
['.test_test2', 'test_test_real2']
- name: '.test_test2'
- value: 'test_test_real2'
[1]:
['.xyz_test', 'xyz_test_real']
- name: '.xyz_test'
- value: 'xyz_test_real'
[2]:
['xyz_module', [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real'], ...
- name: 'xyz_module'
- submodules: [['.test_test2', 'test_test_real2'], ['.xyz_test', 'xyz_test_real'], ...
[0]:
['.test_test2', 'test_test_real2']
- name: '.test_test2'
- value: 'test_test_real2'
[1]:
['.xyz_test', 'xyz_test_real']
- name: '.xyz_test'
- value: 'xyz_test_real'
[2]:
['.obs_test_three', 'obs_test_three_real']
- name: '.obs_test_three'
- value: 'obs_test_three_real'
[3]:
['.ahc_test', 'ahc_test_real']
- name: '.ahc_test'
- value: 'ahc_test_real'
[4]:
['.obs_test_four', 'obs_test_four_real']
- name: '.obs_test_four'
- value: 'obs_test_four_real'
最后,您只想显示那些以“.obs”开头的子模块。这最好使用解析操作来完成,添加到子模块列表以过滤掉您想要的那些:
def obs_items_only(t):
return ParseResults(item for item in t if item.name.startswith(".obs"))
submodule_list.addParseAction(obs_items_only)
添加此过滤解析操作后,结果 trim 下降为:
[['abc_module', [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]], ...
[0]:
['abc_module', [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]]
- name: 'abc_module'
- submodules: [['.obs_test_one', 'obs_test_one_real'], ['.obs_test_two', 'obs_test_two_real']]
[0]:
['.obs_test_one', 'obs_test_one_real']
- name: '.obs_test_one'
- value: 'obs_test_one_real'
[1]:
['.obs_test_two', 'obs_test_two_real']
- name: '.obs_test_two'
- value: 'obs_test_two_real'
[1]:
['cfg_module', []]
- name: 'cfg_module'
- submodules: []
[2]:
['xyz_module', [['.obs_test_three', 'obs_test_three_real'], ['.obs_test_four', 'obs_test_four_real']]]
- name: 'xyz_module'
- submodules: [['.obs_test_three', 'obs_test_three_real'], ['.obs_test_four', 'obs_test_four_real']]
[0]:
['.obs_test_three', 'obs_test_three_real']
- name: '.obs_test_three'
- value: 'obs_test_three_real'
[1]:
['.obs_test_four', 'obs_test_four_real']
- name: '.obs_test_four'
- value: 'obs_test_four_real'
OP 练习:使用此解析器,您将如何添加对也可以是整数的子模块值的支持,您可以将其定义为 Word(nums)
?