import of github code used for the hackathon
[github-barcamp-201407] / ijson-1.1 / ijson / backends / yajl2.py
1 '''
2 Wrapper for YAJL C library version 2.x.
3 '''
4
5 from ctypes import Structure, c_uint, c_ubyte, c_int, c_long, c_double, \
6                    c_void_p, c_char_p, CFUNCTYPE, POINTER, byref, string_at, cast , \
7                    cdll, util, c_char
8 from decimal import Decimal
9
10 from ijson import common, backends
11 from ijson.compat import b2s
12
13
14 yajl = backends.find_yajl(2)
15
16 yajl.yajl_alloc.restype = POINTER(c_char)
17 yajl.yajl_get_error.restype = POINTER(c_char)
18
19 C_EMPTY = CFUNCTYPE(c_int, c_void_p)
20 C_INT = CFUNCTYPE(c_int, c_void_p, c_int)
21 C_LONG = CFUNCTYPE(c_int, c_void_p, c_long)
22 C_DOUBLE = CFUNCTYPE(c_int, c_void_p, c_double)
23 C_STR = CFUNCTYPE(c_int, c_void_p, POINTER(c_ubyte), c_uint)
24
25
26 def number(value):
27     '''
28     Helper function casting a string that represents any Javascript number
29     into appropriate Python value: either int or Decimal.
30     '''
31     try:
32         return int(value)
33     except ValueError:
34         return Decimal(value)
35
36 _callback_data = [
37     # Mapping of JSON parser events to callback C types and value converters.
38     # Used to define the Callbacks structure and actual callback functions
39     # inside the parse function.
40     ('null', C_EMPTY, lambda: None),
41     ('boolean', C_INT, lambda v: bool(v)),
42     # "integer" and "double" aren't actually yielded by yajl since "number"
43     # takes precedence if defined
44     ('integer', C_LONG, lambda v, l: int(string_at(v, l))),
45     ('double', C_DOUBLE, lambda v, l: float(string_at(v, l))),
46     ('number', C_STR, lambda v, l: number(b2s(string_at(v, l)))),
47     ('string', C_STR, lambda v, l: string_at(v, l).decode('utf-8')),
48     ('start_map', C_EMPTY, lambda: None),
49     ('map_key', C_STR, lambda v, l: b2s(string_at(v, l))),
50     ('end_map', C_EMPTY, lambda: None),
51     ('start_array', C_EMPTY, lambda: None),
52     ('end_array', C_EMPTY, lambda: None),
53 ]
54
55 class Callbacks(Structure):
56     _fields_ = [(name, type) for name, type, func in _callback_data]
57
58 YAJL_OK = 0
59 YAJL_CANCELLED = 1
60 YAJL_INSUFFICIENT_DATA = 2
61 YAJL_ERROR = 3
62
63 # constants defined in yajl_parse.h
64 YAJL_ALLOW_COMMENTS = 1
65 YAJL_MULTIPLE_VALUES = 8
66
67
68 def basic_parse(f, allow_comments=False, buf_size=64 * 1024,
69                 multiple_values=False):
70     '''
71     Iterator yielding unprefixed events.
72
73     Parameters:
74
75     - f: a readable file-like object with JSON input
76     - allow_comments: tells parser to allow comments in JSON input
77     - buf_size: a size of an input buffer
78     - multiple_values: allows the parser to parse multiple JSON objects
79     '''
80     events = []
81
82     def callback(event, func_type, func):
83         def c_callback(context, *args):
84             events.append((event, func(*args)))
85             return 1
86         return func_type(c_callback)
87
88     callbacks = Callbacks(*[callback(*data) for data in _callback_data])
89     handle = yajl.yajl_alloc(byref(callbacks), None, None)
90     if allow_comments:
91         yajl.yajl_config(handle, YAJL_ALLOW_COMMENTS, 1)
92     if multiple_values:
93         yajl.yajl_config(handle, YAJL_MULTIPLE_VALUES, 1)
94     try:
95         while True:
96             buffer = f.read(buf_size)
97             if buffer:
98                 result = yajl.yajl_parse(handle, buffer, len(buffer))
99             else:
100                 result = yajl.yajl_complete_parse(handle)
101             if result == YAJL_ERROR:
102                 perror = yajl.yajl_get_error(handle, 1, buffer, len(buffer))
103                 error = cast(perror, c_char_p).value
104                 yajl.yajl_free_error(handle, perror)
105                 raise common.JSONError(error)
106             if not buffer and not events:
107                 if result == YAJL_INSUFFICIENT_DATA:
108                     raise common.IncompleteJSONError()
109                 break
110
111             for event in events:
112                 yield event
113             events = []
114     finally:
115         yajl.yajl_free(handle)
116
117 def parse(file, **kwargs):
118     '''
119     Backend-specific wrapper for ijson.common.parse.
120     '''
121     return common.parse(basic_parse(file, **kwargs))
122
123 def items(file, prefix):
124     '''
125     Backend-specific wrapper for ijson.common.items.
126     '''
127     return common.items(parse(file), prefix)

Benjamin Mako Hill || Want to submit a patch?