Python:RegexをBNFまたはPYPARSINGに置き換えます
質問
私は比較的単純なテキストを解析しています。各行はゲームユニットを説明しています。解析技術についての知識はほとんどないので、次のアドホックソリューションを使用しました。
class Unit:
# rules is an ordered dictionary of tagged regex that is intended to be applied in the given order
# the group named V would correspond to the value (if any) for that particular tag
rules = (
('Level', r'Lv. (?P<V>\d+)'),
('DPS', r'DPS: (?P<V>\d+)'),
('Type', r'(?P<V>Tank|Infantry|Artillery'),
#the XXX will be expanded into a list of valid traits
#note: (XXX| )* wouldn't work; it will match the first space it finds,
#and stop at that if it's in front of something other than a trait
('Traits', r'(?P<V>(XXX)(XXX| )*)'),
# flavor text, if any, ends with a dot
('FlavorText', r'(?P<V>.*\."?$)'),
)
rules = collections.OrderedDict(rules)
traits = '|'.join('All-Terrain', 'Armored', 'Anti-Aircraft', 'Motorized')
rules['Traits'] = re.sub('XXX', effects, rules['Traits'])
for x in rules:
rules[x] = re.sub('<V>', '<'+x+'>', rules[x])
rules[x] = re.compile(rules[x])
def __init__(self, data)
# data looks like this:
# Lv. 5 Tank DPS: 55 Motorized Armored
for field, regex in Item.rules.items():
data = regex.sub(self.parse, data, 1)
if data:
raise ParserError('Could not parse part of the input: ' + data)
def parse(self, m):
if len(m.groupdict()) != 1:
Exception('Expected a single named group')
field, value = m.groupdict().popitem()
setattr(self, field, value)
return ''
正常に動作しますが、正規表現の限界に達したと感じています。具体的には、特性の場合、値は最終的には、後のポイントでリストに分割して変換する必要がある文字列になります。その後の関数は(「電動」、「装甲」)に変更されました。
このコードを変換して、EBNFまたはPyparsingの文法などを使用するか、そのようなものを使用することを考えています。私の目標は次のとおりです。
- このコードをよりきれいにし、エラーが発生しやすくなります
- 値のリストを使用したケースの醜い扱いを避けます(最初にRegex内部で交換する必要があり、後で結果を後処理して文字列をリストに変換します)
何を使用するかについて、そしてコードを書き換える方法についてのあなたの提案は何ですか?
PS乱雑を避けるためにコードの一部をスキップしました。プロセスにエラーを導入した場合、申し訳ありません - 元のコードは機能します:)
解決
私はパイピングのためのコーチングガイドを書き始めましたが、あなたのルールを見ると、それらはEBNFを扱うことなく、パピング要素自体に非常に簡単に翻訳するので、簡単なサンプルを調理しました。
from pyparsing import Word, nums, oneOf, Group, OneOrMore, Regex, Optional
integer = Word(nums)
level = "Lv." + integer("Level")
dps = "DPS:" + integer("DPS")
type_ = oneOf("Tank Infantry Artillery")("Type")
traits = Group(OneOrMore(oneOf("All-Terrain Armored Anti-Aircraft Motorized")))("Traits")
flavortext = Regex(r".*\.$")("FlavorText")
rule = (Optional(level) & Optional(dps) & Optional(type_) &
Optional(traits) & Optional(flavortext))
正規表現を既存のパイパーズ文法にどのように落とすことができるかを確認できるように、正規表現の例を含めました。の構成 rule
'&'演算子を使用すると、個々のアイテムが任意の順序で見つけることができることを意味します(したがって、文法は、あなた自身のコードでそれを行う代わりに、すべてのルールを反復的に処理します)。 PYPARSINGでは、オペレーターのオーバーロードを使用して、シンプルなパーサーから複雑なパーサーを構築します。および「^」の代替案(最初の試合または最長試合)など。
解析された結果がどのように見えるかを以下に示します - regexenで名前付きグループを使用したように、結果名を追加したことに注意してください。
data = "Lv. 5 Tank DPS: 55 Motorized Armored"
parsed_data = rule.parseString(data)
print parsed_data.dump()
print parsed_data.DPS
print parsed_data.Type
print ' '.join(parsed_data.Traits)
印刷:
['Lv.', '5', 'Tank', 'DPS:', '55', ['Motorized', 'Armored']]
- DPS: 55
- Level: 5
- Traits: ['Motorized', 'Armored']
- Type: Tank
55
Tank
Motorized Armored
ウィキに立ち寄って、他の例をご覧ください。 Easy_installをインストールしてPyparsingをインストールできますが、SourceForgeからソース配信をダウンロードすると、追加のドキュメントがたくさんあります。