Python - Multiple choice markup parsing
-
14-04-2021 - |
Question
Consider this text:
Would you like to have responses to your questions sent to you via email ?
I'm going to propose multiple choices for several words by marking up them like that:
Would you like [to get]|[having]|g[to have] responses to your questions sent [up to]|g[to]|[on] you via email ?
The choices are bracketted and separated by pipes
The good choice is preceded by a g
I would like to parse this sentence to get the text formatted like that:
Would you like __ responses to your questions sent __ you via email ?
With a list like:
[
[
{"to get":0},
{"having":0},
{"to have":1},
],
[
{"up to":0},
{"to":1},
{"on":0},
],
]
Is my markup design ok ?
How to regex the sentence to get the needed result and generate the list ?
edit: User oriented markup language needed
Solution
I will suggest my solution too:
Would you like {to get|having|+to have} responses to your questions sent {up to|+to|on} you via email ?
def extract_choices(text):
choices = []
def callback(match):
variants = match.group().strip('{}')
choices.append(dict(
(v.lstrip('+'), v.startswith('+'))
for v in variants.split('|')
))
return '___'
text = re.sub('{.*?}', callback, text)
return text, choices
Lets try it:
>>> t = 'Would you like {to get|having|+to have} responses to your questions sent {up to|+to|on} you via email?'
>>> pprint.pprint(extract_choices(t))
... ('Would you like ___ responses to your questions sent ___ you via email?',
... [{'having': False, 'to get': False, 'to have': True},
... {'on': False, 'to': True, 'up to': False}])
OTHER TIPS
I would add some grouping parentheses {}
, and output not list of list of dicts, but list of dicts.
Code:
import re
s = 'Would you like {[to get]|[having]|g[to have]} responses to your questions sent {[up to]|g[to]|[on]} you via email ?'
def variants_to_dict(variants):
dct = {}
for is_good, s in variants:
dct[s] = 1 if is_good == 'g' else 0
return dct
def question_to_choices(s):
choices_re = re.compile(r'{[^}]+}')
variants_re = re.compile(r'''\|?(g?)
\[
([^\]]+)
\]
''', re.VERBOSE)
choices_list = []
for choices in choices_re.findall(s):
choices_list.append(variants_to_dict(variants_re.findall(choices)))
return choices_re.sub('___', s), choices_list
question, choices = question_to_choices(s)
print question
print choices
Output:
Would you like ___ responses to your questions sent ___ you via email ?
[{'to have': 1, 'to get': 0, 'having': 0}, {'to': 1, 'up to': 0, 'on': 0}]
Rough parsing implementation using regular expressions:
import re
s = "Would you like [to get]|[having]|g[to have] responses to your questions sent [up to]|g[to]|[on] you via email ?" # pattern string
choice_groups = re.compile(r"((?:g?\[[^\]]+\]\|?)+)") # regex to get choice groups
choices = re.compile(r"(g?)\[([^\]]+)\]") # regex to extract choices within each group
# now, use the regexes to parse the string:
groups = choice_groups.findall(s)
# returns: ['[to get]|[having]|g[to have]', '[up to]|g[to]|[on]']
# parse each group to extract possible choices, along with if they are good
group_choices = [choices.findall(group) for group in groups]
# will contain [[('', 'to get'), ('', 'having'), ('g', 'to have')], [('', 'up to'), ('g', 'to'), ('', 'on')]]
# finally, substitute each choice group to form a template
template = choice_groups.sub('___', s)
# template is "Would you like ___ responses to your questions sent ___ you via email ?"
Parsing this to suit your format should be pretty easy now. Good luck :)
I also think that for this task xml is much more appropriate because there are already a lot of tools available that will make parsing much easier and less error-prone.
Anyway, if you decide to use your design, I'd do something like this:
import re
question_str = ("Would you like [to get]|[having]|g[to have] "
"responses to your questions sent "
"[up to]|g[to]|[on] you via email ?")
def option_to_dict(option_str):
if option_str.startswith('g'):
name = option_str.lstrip('g')
value = 1
else:
name = option_str
value = 0
name = name.strip('[]')
return {name: value}
regex = re.compile('g?\[[^]]+\](\|g?\[[^]]+\])*')
options = [[option_to_dict(option_str)
for option_str in match.group(0).split('|')]
for match in regex.finditer(question_str)]
print options
question = regex.sub('___', question_str)
print question
Example output:
[[{'to get': 0}, {'having': 0}, {'to have': 1}], [{'up to': 0}, {'to': 1}, {'on': 0}]]
Would you like ___ responses to your questions sent ___ you via email ?
Note: Regarding the design, I think it would be better to have a mark to set start/end of the whole set of options (not just one for single options).