merged unescapeHTML branch; removed lxml dependency
[youtube-dl] / youtube_dl / trivialjson.py
1 """trivialjson (https://github.com/phihag/trivialjson)"""
2
3 import re
4 def loads(s):
5         s = s.decode('UTF-8')
6         def raiseError(msg, i):
7                 raise ValueError(msg + ' at position ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]))
8         def skipSpace(i, expectMore=True):
9                 while i < len(s) and s[i] in ' \t\r\n':
10                         i += 1
11                 if expectMore:
12                         if i >= len(s):
13                                 raiseError('Premature end', i)
14                 return i
15         def decodeEscape(match):
16                 esc = match.group(1)
17                 _STATIC = {
18                         '"': '"',
19                         '\\': '\\',
20                         '/': '/',
21                         'b': unichr(0x8),
22                         'f': unichr(0xc),
23                         'n': '\n',
24                         'r': '\r',
25                         't': '\t',
26                 }
27                 if esc in _STATIC:
28                         return _STATIC[esc]
29                 if esc[0] == 'u':
30                         if len(esc) == 1+4:
31                                 return unichr(int(esc[1:5], 16))
32                         if len(esc) == 5+6 and esc[5:7] == '\\u':
33                                 hi = int(esc[1:5], 16)
34                                 low = int(esc[7:11], 16)
35                                 return unichr((hi - 0xd800) * 0x400 + low - 0xdc00 + 0x10000)
36                 raise ValueError('Unknown escape ' + str(esc))
37         def parseString(i):
38                 i += 1
39                 e = i
40                 while True:
41                         e = s.index('"', e)
42                         bslashes = 0
43                         while s[e-bslashes-1] == '\\':
44                                 bslashes += 1
45                         if bslashes % 2 == 1:
46                                 e += 1
47                                 continue
48                         break
49                 rexp = re.compile(r'\\(u[dD][89aAbB][0-9a-fA-F]{2}\\u[0-9a-fA-F]{4}|u[0-9a-fA-F]{4}|.|$)')
50                 stri = rexp.sub(decodeEscape, s[i:e])
51                 return (e+1,stri)
52         def parseObj(i):
53                 i += 1
54                 res = {}
55                 i = skipSpace(i)
56                 if s[i] == '}': # Empty dictionary
57                         return (i+1,res)
58                 while True:
59                         if s[i] != '"':
60                                 raiseError('Expected a string object key', i)
61                         i,key = parseString(i)
62                         i = skipSpace(i)
63                         if i >= len(s) or s[i] != ':':
64                                 raiseError('Expected a colon', i)
65                         i,val = parse(i+1)
66                         res[key] = val
67                         i = skipSpace(i)
68                         if s[i] == '}':
69                                 return (i+1, res)
70                         if s[i] != ',':
71                                 raiseError('Expected comma or closing curly brace', i)
72                         i = skipSpace(i+1)
73         def parseArray(i):
74                 res = []
75                 i = skipSpace(i+1)
76                 if s[i] == ']': # Empty array
77                         return (i+1,res)
78                 while True:
79                         i,val = parse(i)
80                         res.append(val)
81                         i = skipSpace(i) # Raise exception if premature end
82                         if s[i] == ']':
83                                 return (i+1, res)
84                         if s[i] != ',':
85                                 raiseError('Expected a comma or closing bracket', i)
86                         i = skipSpace(i+1)
87         def parseDiscrete(i):
88                 for k,v in {'true': True, 'false': False, 'null': None}.items():
89                         if s.startswith(k, i):
90                                 return (i+len(k), v)
91                 raiseError('Not a boolean (or null)', i)
92         def parseNumber(i):
93                 mobj = re.match('^(-?(0|[1-9][0-9]*)(\.[0-9]*)?([eE][+-]?[0-9]+)?)', s[i:])
94                 if mobj is None:
95                         raiseError('Not a number', i)
96                 nums = mobj.group(1)
97                 if '.' in nums or 'e' in nums or 'E' in nums:
98                         return (i+len(nums), float(nums))
99                 return (i+len(nums), int(nums))
100         CHARMAP = {'{': parseObj, '[': parseArray, '"': parseString, 't': parseDiscrete, 'f': parseDiscrete, 'n': parseDiscrete}
101         def parse(i):
102                 i = skipSpace(i)
103                 i,res = CHARMAP.get(s[i], parseNumber)(i)
104                 i = skipSpace(i, False)
105                 return (i,res)
106         i,res = parse(0)
107         if i < len(s):
108                 raise ValueError('Extra data at end of input (index ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]) + ')')
109         return res