1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
import re
import typing
from . import get_compat_mode
from .utils import get_args, get_kwargs
def parse_attributes(attrs: str, output_dict: typing.Dict) -> None:
"""Update a dictionary with name/value attributes from the attrs string.
The attrs string is a comma separated list of values and keyword name=value
pairs. Values must precede keywords and are named '1','2'... The entire
attributes list is named '0'. If keywords are specified string values must
be quoted. Examples:
attrs: ''
output_dict: {}
attrs: 'hello,world'
output_dict: {'0': 'hello,world', '1': 'hello', '2': 'world',}
attrs: '"hello", planet="earth"'
output_dict: {'0': '"hello", planet="earth"', '1': 'hello' 'planet': 'earth', }
"""
if not attrs:
return
output_dict['0'] = attrs
# Replace line separators with spaces so line spanning works.
s = re.sub(r'\s', ' ', attrs)
d = legacy_parse(s) if get_compat_mode() == 1 else future_parse(s)
output_dict.update(d)
assert len(d) > 0
def future_parse(s: str) -> dict:
d = {}
key = ''
value = ''
count = 1
quote = None
in_quotes = False
had_quotes = False
def add_value():
nonlocal count, d, key, value
key = key.strip()
value = value.strip()
if had_quotes:
value = value[1:-1]
if not value and not had_quotes:
value = None
if key:
d[key] = value if value else ''
key = ''
else:
d["{}".format(count)] = value
count += 1
value = ''
for i in range(len(s)):
char = s[i]
if char == ',' and not in_quotes:
add_value()
had_quotes = False
elif value and char == '=' and not in_quotes:
key = value
value = ''
elif not in_quotes and (char == '"' or char == "'") \
and (i == 0 or s[i - 1] != '\\'):
in_quotes = True
quote = char
value += char
elif in_quotes and char == quote and (i == 0 or s[i - 1] != '\\'):
in_quotes = False
had_quotes = True
quote = None
value += char
elif char == '\\' and i < len(s) - 1 and (s[i + 1] == '"' or s[i + 1] == "'"):
pass
else:
value += char
if key and key[0] == '=' and not value:
value = key + "="
key = ""
if not value and s.rstrip()[-1] == ',':
value = ' '
if had_quotes or value or key:
add_value()
return d
def legacy_parse(s: str) -> dict:
d = {}
try:
d.update(get_args(s))
d.update(get_kwargs(s))
for v in list(d.values()):
if not (isinstance(v, str)
or isinstance(v, int) or isinstance(v, float) or v is None):
raise Exception
except Exception:
s = s.replace('"', '\\"')
s = s.split(',')
s = ['"' + x.strip() + '"' for x in s]
s = ','.join(s)
try:
d = {}
d.update(get_args(s))
d.update(get_kwargs(s))
except Exception:
return # If there's a syntax error leave with {0}=attrs.
for k in list(d.keys()): # Drop any empty positional arguments.
if d[k] == '':
del d[k]
return d
|