2016-08-03 3 views
4

У меня возникла проблема с кодированием «элегантного» парсера для этого требования. (Тот, который не похож на кусок C завтрак). Ввод представляет собой строку, пары ключевых значений, разделенные символом «,» и соединенные с «=».Извлечение пар значений из строки с кавычками

key1=value1,key2=value2 

Часть обманывая меня значение может быть указано ("), так и внутри кавычек„“не заканчивается ключ.

key1=value1,key2="value2,still_value2" 

Эта последняя часть сделала это сложно для меня использовать раскол или re.split, прибегая к для I в диапазоне для петель :(.

Можно ли продемонстрировать чистый способ сделать это?

это нормально предполагать котировки бывают только в значениях, и что нет белых темп или не буквенно-цифровые символы.

+0

Можете ли вы отправить ожидаемый выход? –

+0

Будет ли значение 'key2' во втором примере содержать кавычки или нет? т. е. в вашем примере, 'key2' соответствует значению' 'value2, still_value2 "или" "\" value2, still_value2 \ "" '? – EvilTak

ответ

3

Я бы не советовал использовать регулярные выражения для решения этой задачи, потому что язык, который вы хотите разобрать, не является регулярным.

У вас есть символьная строка из нескольких пар ключей. Лучший способ разобрать это - это не совпадение с шаблонами на нем, но для его правильной маркировки.

В стандартной библиотеке Python имеется модуль, называемый shlex, который имитирует разбор, выполняемый оболочками 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 ves вам меньше сюрпризов, особенно если вам нужно позже включить более возможные входы. Я никогда не нашел, как правильно разбирать такие пары ключ-значение с регулярными выражениями, всегда будут входы (например: A="B=\"1,2,3\""), которые обманут двигатель.

Если вы не заботитесь о таких входах (или, по-другому, если вы можете убедиться, что ваш ввод соответствует определению обычного языка), регулярные выражения отлично.

EDIT2:split имеет maxsplit аргумент, что гораздо чище, чем расщепление/нарезка/присоединения. Спасибо @cdlane за его звук!

+1

Я считаю, что «shlex» - это надежное производственное решение, и это хороший пример того, как настроить его на проблему. Тем не менее, этот ответ теряет всю элегантность для меня в его 'return' statement -' split() 'одни и те же данные дважды, а затем' join() 'для очистки после чрезмерного' split() ', чтобы вы могли использовать понимание словаря? Как насчет 'return dict (word.split (value_sep, maxsplit = 1) для слова в 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("=") 

Per BioGeek, моя попытка угадать, я имею в виду интерпретировать регулярное выражение Janne Karila: шаблон разбивает строки на запятые, но в этом процессе рассматриваются двойные кавычки (возможно, с запятыми). Он имеет два отдельных варианта: пробеги символов, которые не включают кавычки; и двойные кавычки пробеги символов, где двойные кавычки заканчивают бег, если это не (обратный слэш) спаслись:

(?:    # 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') 

И это делает «матч»:

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

Затем вы можете сделать цикл, чтобы получить ключи и значения:

for m in match: 
    key = m[0] 
    value = m[1] 
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"'} 
Смежные вопросы