2016-08-03 24 views
4

この要件のために「エレガントな」パーサーをコーディングする際に問題があります。 (C朝食のようには見えないもの)。入力は文字列で、 '、'で区切られ、 '='で結合されたキー値のペアです。引用符付きの文字列からキー値のペアを抽出する

key1=value1,key2=value2 

私をだまし部分は、( ")の値を引用符で囲むことができますし、引用符内『』キーを終了していません。

key1=value1,key2="value2,still_value2" 

この最後の部分は、それがトリッキーな私がするために作られています私は、forループの範囲内のために頼る、分割またはre.splitを使用:(。

誰もがこれを行うにはきれいな方法を発揮することはできますか?

引用符は値にのみ起こると仮定してOKで、かつそこにいること白人ではないペースまたは非英数字で入力します。

+0

期待される出力を投稿できますか? –

+0

2番目の例の 'key2'の値に引用符が含まれているかどうかすなわち、あなたの例では、 'key2'は' 'value2、still_value2''または' '\" value2、still_value2 \ "" 'にマップされますか? – EvilTak

答えて

3
私はこの作業のために正規表現を使用しないことを助言する

、あなたが解析したい言語が規則的ではないからです。

複数のキー値のペアの文字列があります。これを解析する最良の方法は、パターンを一致させることではなく、適切にトークン化することです。

shlexと呼ばれるPython標準ライブラリには、POSIXシェルによって行われる解析を模倣し、必要に応じて簡単にカスタマイズできるレクサーの実装を提供するモジュールがあります。

from shlex import shlex 

def parse_kv_pairs(text, item_sep=",", value_sep="="): 
    """Parse key-value pairs from a shell-like text.""" 
    # initialize a lexer, in POSIX mode (to properly handle escaping) 
    lexer = shlex(text, posix=True) 
    # set ',' as whitespace for the lexer 
    # (the lexer will use this character to separate words) 
    lexer.whitespace = item_sep 
    # include '=' as a word character 
    # (this is done so that the lexer returns a list of key-value pairs) 
    # (if your option key or value contains any unquoted special character, you will need to add it here) 
    lexer.wordchars += value_sep 
    # then we separate option keys and values to build the resulting dictionary 
    # (maxsplit is required to make sure that '=' in value will not be a problem) 
    return dict(word.split(value_sep, maxsplit=1) for word in lexer) 

例の実行:

parse_kv_pairs(
    'key1=value1,key2=\'value2,still_value2,not_key1="not_value1"\'' 
) 

出力:

{'key1': 'value1', 'key2': 'value2,still_value2,not_key1="not_value1"'} 

EDIT:私は普通のshlexにこだわる理由というよりは、定期的に使用していることを追加するのを忘れ式(この場合は速い)は、gi後でより多くの入力を可能にする必要がある場合は、特に驚きは少ないと考えています。このようなキーと値のペアを正規表現で正しく解析する方法は決して見つけられませんでした。エンジンを騙す入力(例:A="B=\"1,2,3\"")が常に存在します。

このような入力を気にしない場合(あるいは言い換えれば、入力が正規言語の定義に従うことを保証できる場合)、正規表現は完全にうまくいきます。

EDIT2:splitmaxsplit引数を持って、それが分割/スライス/入社より使いはるかにきれいです。彼の音声入力のために@cdlaneに感謝します!

+1

私は 'shlex'は堅実な生産ソリューションだと信じています。これは手元の問題にどのように調整するかの良い例です。しかし、この答えは私のためにすべての優雅さを失う 'return'ステートメント - ' split() 'を同じデータで2回実行した後、' join() 'を使って余分な' split() 'の後にクリーンアップするだけです。辞書の理解? 'return dict(word.split(value_sep、maxsplit = 1)for lexerの単語の場合はどうでしょうか)' – cdlane

+0

はい、これは良い方法です。書くときに 'maxsplit'引数を忘れてしまいました。値で '='をサポートしています。あなたのアドバイスをありがとう、私は答えを編集します。 – pistache

2

私はそれがCの朝食の作品のように見える、それは非常にエレガントであることないことをわからない:)

data = {} 
original = 'key1=value1,key2="value2,still_value2"' 
converted = '' 

is_open = False 
for c in original: 
    if c == ',' and not is_open: 
     c = '\n' 
    elif c in ('"',"'"): 
     is_open = not is_open 
    converted += c 

for item in converted.split('\n'): 
    k, v = item.split('=') 
    data[k] = v 
5

Split a string, respect and preserve quotesからいくつかの正規表現の魔法を使用して、我々が行うことができます。

import re 

string = 'key1=value1,key2="value2,still_value2"' 

key_value_pairs = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', string) 

for key_value_pair in key_value_pairs: 
    key, value = key_value_pair.split("=") 

BioGeekで、推測しようとすると、私は正規表現を解釈することを意味します。Janne Karilaは、コンマで文字列を分割しますが、プロセス内で二重引用符(潜在的にカンマ)を尊重します。 2つの別々のオプションがあります:引用符を伴わない文字の実行。そして、それはだ場合を除き、二重引用符は、(バックスラッシュ)の実行を終了し、文字の二重引用符で囲まれたランはエスケープ:

(?:    # parenthesis for alternation (|), not memory 
[^\s,"]   # any 1 character except white space, comma or quote 
|    # or 
"(?:\\.|[^"])*" # a quoted string containing 0 or more characters 
       # other than quotes (unless escaped) 
)+    # one or more of the above 
+0

正規表現の仕組みについての説明を追加できますか? – BioGeek

+1

@BioGeek、私はあなたの要求に応じて、私が成功したかどうかを教えてくれました! – cdlane

+0

cdlane、説明のためにありがとう! – BioGeek

3

は、私は、この正規表現のソリューションを思い付いた:

import re 
match = re.findall(r'([^=]+)=(("[^"]+")|([^,]+)),?', 'key1=value1,key2=value2,key3="value3,stillvalue3",key4=value4') 

そして、これが「一致」を作ります:

for m in match: 
    key = m[0] 
    value = m[1] 

[('key1', 'value1', '', 'value1'), ('key2', 'value2', '', 'value2'), ('key3', '"value3,stillvalue3"', '"value3,stillvalue3"', ''), ('key4', 'value4', '', 'value4')] 

次に、キーと値を取得するために、ループのために作ることができますいくつかの他の回答に基づいて

1

、私は、次の解決策を考え出した:

import re 
import itertools 

data = 'key1=value1,key2="value2,still_value2"' 

# Based on Alan Moore's answer on http://stackoverflow.com/questions/2785755/how-to-split-but-ignore-separators-in-quoted-strings-in-python 
def split_on_non_quoted_equals(string): 
    return re.split('''=(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', string) 
def split_on_non_quoted_comma(string): 
    return re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', string) 

split1 = split_on_non_quoted_equals(data) 
split2 = map(lambda x: split_on_non_quoted_comma(x), split1) 

# 'Unpack' the sublists in to a single list. Based on Alex Martelli's answer on http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python 
flattened = [item for sublist in split2 for item in sublist] 

# Convert alternating elements of a list into keys and values of a dictionary. Based on Sven Marnach's answer on http://stackoverflow.com/questions/6900955/python-convert-list-to-dictionary 
d = dict(itertools.izip_longest(*[iter(flattened)] * 2, fillvalue="")) 

dをされた結果、以下の辞書:

{'key1': 'value1', 'key2': '"value2,still_value2"'} 
関連する問題