Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/json/decoder.py @ 68:5028fdace37b
planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author | jpayne |
---|---|
date | Tue, 18 Mar 2025 16:23:26 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
1 """Implementation of JSONDecoder | |
2 """ | |
3 import re | |
4 | |
5 from json import scanner | |
6 try: | |
7 from _json import scanstring as c_scanstring | |
8 except ImportError: | |
9 c_scanstring = None | |
10 | |
11 __all__ = ['JSONDecoder', 'JSONDecodeError'] | |
12 | |
13 FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL | |
14 | |
15 NaN = float('nan') | |
16 PosInf = float('inf') | |
17 NegInf = float('-inf') | |
18 | |
19 | |
20 class JSONDecodeError(ValueError): | |
21 """Subclass of ValueError with the following additional properties: | |
22 | |
23 msg: The unformatted error message | |
24 doc: The JSON document being parsed | |
25 pos: The start index of doc where parsing failed | |
26 lineno: The line corresponding to pos | |
27 colno: The column corresponding to pos | |
28 | |
29 """ | |
30 # Note that this exception is used from _json | |
31 def __init__(self, msg, doc, pos): | |
32 lineno = doc.count('\n', 0, pos) + 1 | |
33 colno = pos - doc.rfind('\n', 0, pos) | |
34 errmsg = '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) | |
35 ValueError.__init__(self, errmsg) | |
36 self.msg = msg | |
37 self.doc = doc | |
38 self.pos = pos | |
39 self.lineno = lineno | |
40 self.colno = colno | |
41 | |
42 def __reduce__(self): | |
43 return self.__class__, (self.msg, self.doc, self.pos) | |
44 | |
45 | |
46 _CONSTANTS = { | |
47 '-Infinity': NegInf, | |
48 'Infinity': PosInf, | |
49 'NaN': NaN, | |
50 } | |
51 | |
52 | |
53 STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) | |
54 BACKSLASH = { | |
55 '"': '"', '\\': '\\', '/': '/', | |
56 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', | |
57 } | |
58 | |
59 def _decode_uXXXX(s, pos): | |
60 esc = s[pos + 1:pos + 5] | |
61 if len(esc) == 4 and esc[1] not in 'xX': | |
62 try: | |
63 return int(esc, 16) | |
64 except ValueError: | |
65 pass | |
66 msg = "Invalid \\uXXXX escape" | |
67 raise JSONDecodeError(msg, s, pos) | |
68 | |
69 def py_scanstring(s, end, strict=True, | |
70 _b=BACKSLASH, _m=STRINGCHUNK.match): | |
71 """Scan the string s for a JSON string. End is the index of the | |
72 character in s after the quote that started the JSON string. | |
73 Unescapes all valid JSON string escape sequences and raises ValueError | |
74 on attempt to decode an invalid string. If strict is False then literal | |
75 control characters are allowed in the string. | |
76 | |
77 Returns a tuple of the decoded string and the index of the character in s | |
78 after the end quote.""" | |
79 chunks = [] | |
80 _append = chunks.append | |
81 begin = end - 1 | |
82 while 1: | |
83 chunk = _m(s, end) | |
84 if chunk is None: | |
85 raise JSONDecodeError("Unterminated string starting at", s, begin) | |
86 end = chunk.end() | |
87 content, terminator = chunk.groups() | |
88 # Content is contains zero or more unescaped string characters | |
89 if content: | |
90 _append(content) | |
91 # Terminator is the end of string, a literal control character, | |
92 # or a backslash denoting that an escape sequence follows | |
93 if terminator == '"': | |
94 break | |
95 elif terminator != '\\': | |
96 if strict: | |
97 #msg = "Invalid control character %r at" % (terminator,) | |
98 msg = "Invalid control character {0!r} at".format(terminator) | |
99 raise JSONDecodeError(msg, s, end) | |
100 else: | |
101 _append(terminator) | |
102 continue | |
103 try: | |
104 esc = s[end] | |
105 except IndexError: | |
106 raise JSONDecodeError("Unterminated string starting at", | |
107 s, begin) from None | |
108 # If not a unicode escape sequence, must be in the lookup table | |
109 if esc != 'u': | |
110 try: | |
111 char = _b[esc] | |
112 except KeyError: | |
113 msg = "Invalid \\escape: {0!r}".format(esc) | |
114 raise JSONDecodeError(msg, s, end) | |
115 end += 1 | |
116 else: | |
117 uni = _decode_uXXXX(s, end) | |
118 end += 5 | |
119 if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u': | |
120 uni2 = _decode_uXXXX(s, end + 1) | |
121 if 0xdc00 <= uni2 <= 0xdfff: | |
122 uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) | |
123 end += 6 | |
124 char = chr(uni) | |
125 _append(char) | |
126 return ''.join(chunks), end | |
127 | |
128 | |
129 # Use speedup if available | |
130 scanstring = c_scanstring or py_scanstring | |
131 | |
132 WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) | |
133 WHITESPACE_STR = ' \t\n\r' | |
134 | |
135 | |
136 def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, | |
137 memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR): | |
138 s, end = s_and_end | |
139 pairs = [] | |
140 pairs_append = pairs.append | |
141 # Backwards compatibility | |
142 if memo is None: | |
143 memo = {} | |
144 memo_get = memo.setdefault | |
145 # Use a slice to prevent IndexError from being raised, the following | |
146 # check will raise a more specific ValueError if the string is empty | |
147 nextchar = s[end:end + 1] | |
148 # Normally we expect nextchar == '"' | |
149 if nextchar != '"': | |
150 if nextchar in _ws: | |
151 end = _w(s, end).end() | |
152 nextchar = s[end:end + 1] | |
153 # Trivial empty object | |
154 if nextchar == '}': | |
155 if object_pairs_hook is not None: | |
156 result = object_pairs_hook(pairs) | |
157 return result, end + 1 | |
158 pairs = {} | |
159 if object_hook is not None: | |
160 pairs = object_hook(pairs) | |
161 return pairs, end + 1 | |
162 elif nextchar != '"': | |
163 raise JSONDecodeError( | |
164 "Expecting property name enclosed in double quotes", s, end) | |
165 end += 1 | |
166 while True: | |
167 key, end = scanstring(s, end, strict) | |
168 key = memo_get(key, key) | |
169 # To skip some function call overhead we optimize the fast paths where | |
170 # the JSON key separator is ": " or just ":". | |
171 if s[end:end + 1] != ':': | |
172 end = _w(s, end).end() | |
173 if s[end:end + 1] != ':': | |
174 raise JSONDecodeError("Expecting ':' delimiter", s, end) | |
175 end += 1 | |
176 | |
177 try: | |
178 if s[end] in _ws: | |
179 end += 1 | |
180 if s[end] in _ws: | |
181 end = _w(s, end + 1).end() | |
182 except IndexError: | |
183 pass | |
184 | |
185 try: | |
186 value, end = scan_once(s, end) | |
187 except StopIteration as err: | |
188 raise JSONDecodeError("Expecting value", s, err.value) from None | |
189 pairs_append((key, value)) | |
190 try: | |
191 nextchar = s[end] | |
192 if nextchar in _ws: | |
193 end = _w(s, end + 1).end() | |
194 nextchar = s[end] | |
195 except IndexError: | |
196 nextchar = '' | |
197 end += 1 | |
198 | |
199 if nextchar == '}': | |
200 break | |
201 elif nextchar != ',': | |
202 raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) | |
203 end = _w(s, end).end() | |
204 nextchar = s[end:end + 1] | |
205 end += 1 | |
206 if nextchar != '"': | |
207 raise JSONDecodeError( | |
208 "Expecting property name enclosed in double quotes", s, end - 1) | |
209 if object_pairs_hook is not None: | |
210 result = object_pairs_hook(pairs) | |
211 return result, end | |
212 pairs = dict(pairs) | |
213 if object_hook is not None: | |
214 pairs = object_hook(pairs) | |
215 return pairs, end | |
216 | |
217 def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): | |
218 s, end = s_and_end | |
219 values = [] | |
220 nextchar = s[end:end + 1] | |
221 if nextchar in _ws: | |
222 end = _w(s, end + 1).end() | |
223 nextchar = s[end:end + 1] | |
224 # Look-ahead for trivial empty array | |
225 if nextchar == ']': | |
226 return values, end + 1 | |
227 _append = values.append | |
228 while True: | |
229 try: | |
230 value, end = scan_once(s, end) | |
231 except StopIteration as err: | |
232 raise JSONDecodeError("Expecting value", s, err.value) from None | |
233 _append(value) | |
234 nextchar = s[end:end + 1] | |
235 if nextchar in _ws: | |
236 end = _w(s, end + 1).end() | |
237 nextchar = s[end:end + 1] | |
238 end += 1 | |
239 if nextchar == ']': | |
240 break | |
241 elif nextchar != ',': | |
242 raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) | |
243 try: | |
244 if s[end] in _ws: | |
245 end += 1 | |
246 if s[end] in _ws: | |
247 end = _w(s, end + 1).end() | |
248 except IndexError: | |
249 pass | |
250 | |
251 return values, end | |
252 | |
253 | |
254 class JSONDecoder(object): | |
255 """Simple JSON <http://json.org> decoder | |
256 | |
257 Performs the following translations in decoding by default: | |
258 | |
259 +---------------+-------------------+ | |
260 | JSON | Python | | |
261 +===============+===================+ | |
262 | object | dict | | |
263 +---------------+-------------------+ | |
264 | array | list | | |
265 +---------------+-------------------+ | |
266 | string | str | | |
267 +---------------+-------------------+ | |
268 | number (int) | int | | |
269 +---------------+-------------------+ | |
270 | number (real) | float | | |
271 +---------------+-------------------+ | |
272 | true | True | | |
273 +---------------+-------------------+ | |
274 | false | False | | |
275 +---------------+-------------------+ | |
276 | null | None | | |
277 +---------------+-------------------+ | |
278 | |
279 It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as | |
280 their corresponding ``float`` values, which is outside the JSON spec. | |
281 | |
282 """ | |
283 | |
284 def __init__(self, *, object_hook=None, parse_float=None, | |
285 parse_int=None, parse_constant=None, strict=True, | |
286 object_pairs_hook=None): | |
287 """``object_hook``, if specified, will be called with the result | |
288 of every JSON object decoded and its return value will be used in | |
289 place of the given ``dict``. This can be used to provide custom | |
290 deserializations (e.g. to support JSON-RPC class hinting). | |
291 | |
292 ``object_pairs_hook``, if specified will be called with the result of | |
293 every JSON object decoded with an ordered list of pairs. The return | |
294 value of ``object_pairs_hook`` will be used instead of the ``dict``. | |
295 This feature can be used to implement custom decoders. | |
296 If ``object_hook`` is also defined, the ``object_pairs_hook`` takes | |
297 priority. | |
298 | |
299 ``parse_float``, if specified, will be called with the string | |
300 of every JSON float to be decoded. By default this is equivalent to | |
301 float(num_str). This can be used to use another datatype or parser | |
302 for JSON floats (e.g. decimal.Decimal). | |
303 | |
304 ``parse_int``, if specified, will be called with the string | |
305 of every JSON int to be decoded. By default this is equivalent to | |
306 int(num_str). This can be used to use another datatype or parser | |
307 for JSON integers (e.g. float). | |
308 | |
309 ``parse_constant``, if specified, will be called with one of the | |
310 following strings: -Infinity, Infinity, NaN. | |
311 This can be used to raise an exception if invalid JSON numbers | |
312 are encountered. | |
313 | |
314 If ``strict`` is false (true is the default), then control | |
315 characters will be allowed inside strings. Control characters in | |
316 this context are those with character codes in the 0-31 range, | |
317 including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``. | |
318 """ | |
319 self.object_hook = object_hook | |
320 self.parse_float = parse_float or float | |
321 self.parse_int = parse_int or int | |
322 self.parse_constant = parse_constant or _CONSTANTS.__getitem__ | |
323 self.strict = strict | |
324 self.object_pairs_hook = object_pairs_hook | |
325 self.parse_object = JSONObject | |
326 self.parse_array = JSONArray | |
327 self.parse_string = scanstring | |
328 self.memo = {} | |
329 self.scan_once = scanner.make_scanner(self) | |
330 | |
331 | |
332 def decode(self, s, _w=WHITESPACE.match): | |
333 """Return the Python representation of ``s`` (a ``str`` instance | |
334 containing a JSON document). | |
335 | |
336 """ | |
337 obj, end = self.raw_decode(s, idx=_w(s, 0).end()) | |
338 end = _w(s, end).end() | |
339 if end != len(s): | |
340 raise JSONDecodeError("Extra data", s, end) | |
341 return obj | |
342 | |
343 def raw_decode(self, s, idx=0): | |
344 """Decode a JSON document from ``s`` (a ``str`` beginning with | |
345 a JSON document) and return a 2-tuple of the Python | |
346 representation and the index in ``s`` where the document ended. | |
347 | |
348 This can be used to decode a JSON document from a string that may | |
349 have extraneous data at the end. | |
350 | |
351 """ | |
352 try: | |
353 obj, end = self.scan_once(s, idx) | |
354 except StopIteration as err: | |
355 raise JSONDecodeError("Expecting value", s, err.value) from None | |
356 return obj, end |