1 # -*- coding: utf-8 -*- |
|
2 """ |
|
3 sphinx.util.jsdump |
|
4 ~~~~~~~~~~~~~~~~~~ |
|
5 |
|
6 This module implements a simple JavaScript serializer. |
|
7 Uses the basestring encode function from simplejson. |
|
8 |
|
9 :copyright: 2008 by Armin Ronacher, Bob Ippolito, Georg Brandl. |
|
10 :license: BSD. |
|
11 """ |
|
12 |
|
13 import re |
|
14 |
|
15 _str_re = re.compile(r'"(\\\\|\\"|[^"])*"') |
|
16 _int_re = re.compile(r'\d+') |
|
17 _name_re = re.compile(r'[a-zA-Z]\w*') |
|
18 _nameonly_re = re.compile(r'[a-zA-Z]\w*$') |
|
19 |
|
20 # escape \, ", control characters and everything outside ASCII |
|
21 ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') |
|
22 ESCAPE_DICT = { |
|
23 '\\': '\\\\', |
|
24 '"': '\\"', |
|
25 '\b': '\\b', |
|
26 '\f': '\\f', |
|
27 '\n': '\\n', |
|
28 '\r': '\\r', |
|
29 '\t': '\\t', |
|
30 } |
|
31 |
|
32 ESCAPED = re.compile(r'\\u.{4}|\\.') |
|
33 |
|
34 |
|
35 def encode_string(s): |
|
36 def replace(match): |
|
37 s = match.group(0) |
|
38 try: |
|
39 return ESCAPE_DICT[s] |
|
40 except KeyError: |
|
41 n = ord(s) |
|
42 if n < 0x10000: |
|
43 return '\\u%04x' % (n,) |
|
44 else: |
|
45 # surrogate pair |
|
46 n -= 0x10000 |
|
47 s1 = 0xd800 | ((n >> 10) & 0x3ff) |
|
48 s2 = 0xdc00 | (n & 0x3ff) |
|
49 return '\\u%04x\\u%04x' % (s1, s2) |
|
50 return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' |
|
51 |
|
52 def decode_string(s): |
|
53 return ESCAPED.sub(lambda m: eval('u"'+m.group()+'"'), s) |
|
54 |
|
55 |
|
56 reswords = set("""\ |
|
57 abstract else instanceof switch |
|
58 boolean enum int synchronized |
|
59 break export interface this |
|
60 byte extends long throw |
|
61 case false native throws |
|
62 catch final new transient |
|
63 char finally null true |
|
64 class float package try |
|
65 const for private typeof |
|
66 continue function protected var |
|
67 debugger goto public void |
|
68 default if return volatile |
|
69 delete implements short while |
|
70 do import static with |
|
71 double in super""".split()) |
|
72 |
|
73 def dumps(obj, key=False): |
|
74 if key: |
|
75 if not isinstance(obj, basestring): |
|
76 obj = str(obj) |
|
77 if _nameonly_re.match(obj) and obj not in reswords: |
|
78 return obj # return it as a bare word |
|
79 else: |
|
80 return encode_string(obj) |
|
81 if obj is None: |
|
82 return 'null' |
|
83 elif obj is True or obj is False: |
|
84 return obj and 'true' or 'false' |
|
85 elif isinstance(obj, (int, long, float)): |
|
86 return str(obj) |
|
87 elif isinstance(obj, dict): |
|
88 return '{%s}' % ','.join('%s:%s' % ( |
|
89 dumps(key, True), |
|
90 dumps(value) |
|
91 ) for key, value in obj.iteritems()) |
|
92 elif isinstance(obj, (tuple, list, set)): |
|
93 return '[%s]' % ','.join(dumps(x) for x in obj) |
|
94 elif isinstance(obj, basestring): |
|
95 return encode_string(obj) |
|
96 raise TypeError(type(obj)) |
|
97 |
|
98 def dump(obj, f): |
|
99 f.write(dumps(obj)) |
|
100 |
|
101 |
|
102 def loads(x): |
|
103 """Loader that can read the JS subset the indexer produces.""" |
|
104 nothing = object() |
|
105 i = 0 |
|
106 n = len(x) |
|
107 stack = [] |
|
108 obj = nothing |
|
109 key = False |
|
110 keys = [] |
|
111 while i < n: |
|
112 c = x[i] |
|
113 if c == '{': |
|
114 obj = {} |
|
115 stack.append(obj) |
|
116 key = True |
|
117 keys.append(nothing) |
|
118 i += 1 |
|
119 elif c == '[': |
|
120 obj = [] |
|
121 stack.append(obj) |
|
122 key = False |
|
123 keys.append(nothing) |
|
124 i += 1 |
|
125 elif c in '}]': |
|
126 if key: |
|
127 if keys[-1] is not nothing: |
|
128 raise ValueError("unfinished dict") |
|
129 # empty dict |
|
130 key = False |
|
131 oldobj = stack.pop() |
|
132 keys.pop() |
|
133 if stack: |
|
134 obj = stack[-1] |
|
135 if isinstance(obj, dict): |
|
136 if keys[-1] is nothing: |
|
137 raise ValueError("invalid key object", oldobj) |
|
138 obj[keys[-1]] = oldobj |
|
139 else: |
|
140 obj.append(oldobj) |
|
141 else: |
|
142 break |
|
143 i += 1 |
|
144 elif c == ',': |
|
145 if key: |
|
146 raise ValueError("multiple keys") |
|
147 if isinstance(obj, dict): |
|
148 key = True |
|
149 i += 1 |
|
150 elif c == ':': |
|
151 if not isinstance(obj, dict): |
|
152 raise ValueError("colon in list") |
|
153 i += 1 |
|
154 if not key: |
|
155 raise ValueError("multiple values") |
|
156 key = False |
|
157 else: |
|
158 m = _str_re.match(x, i) |
|
159 if m: |
|
160 y = decode_string(m.group()[1:-1]) |
|
161 else: |
|
162 m = _int_re.match(x, i) |
|
163 if m: |
|
164 y = int(m.group()) |
|
165 else: |
|
166 m = _name_re.match(x, i) |
|
167 if m: |
|
168 y = m.group() |
|
169 if y == 'true': |
|
170 y = True |
|
171 elif y == 'false': |
|
172 y = False |
|
173 elif y == 'null': |
|
174 y = None |
|
175 elif not key: |
|
176 raise ValueError("bareword as value") |
|
177 else: |
|
178 raise ValueError("read error at pos %d" % i) |
|
179 i = m.end() |
|
180 if isinstance(obj, dict): |
|
181 if key: |
|
182 keys[-1] = y |
|
183 else: |
|
184 obj[keys[-1]] = y |
|
185 key = False |
|
186 else: |
|
187 obj.append(y) |
|
188 if obj is nothing: |
|
189 raise ValueError("nothing loaded from string") |
|
190 return obj |
|
191 |
|
192 def load(f): |
|
193 return loads(f.read()) |
|