yajl-ruby 1.0.0-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of yajl-ruby might be problematic. Click here for more details.

Files changed (152) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG.md +327 -0
  4. data/Gemfile +3 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +362 -0
  7. data/Rakefile +2 -0
  8. data/benchmark/encode.rb +72 -0
  9. data/benchmark/encode_json_and_marshal.rb +42 -0
  10. data/benchmark/encode_json_and_yaml.rb +53 -0
  11. data/benchmark/http.rb +32 -0
  12. data/benchmark/parse.rb +94 -0
  13. data/benchmark/parse_json_and_marshal.rb +50 -0
  14. data/benchmark/parse_json_and_yaml.rb +55 -0
  15. data/benchmark/parse_stream.rb +54 -0
  16. data/benchmark/subjects/item.json +1 -0
  17. data/benchmark/subjects/ohai.json +1216 -0
  18. data/benchmark/subjects/ohai.marshal_dump +0 -0
  19. data/benchmark/subjects/ohai.yml +975 -0
  20. data/benchmark/subjects/twitter_search.json +1 -0
  21. data/benchmark/subjects/twitter_stream.json +430 -0
  22. data/benchmark/subjects/unicode.json +1 -0
  23. data/examples/encoding/chunked_encoding.rb +27 -0
  24. data/examples/encoding/one_shot.rb +13 -0
  25. data/examples/encoding/to_an_io.rb +12 -0
  26. data/examples/http/twitter_search_api.rb +12 -0
  27. data/examples/http/twitter_stream_api.rb +26 -0
  28. data/examples/parsing/from_file.rb +14 -0
  29. data/examples/parsing/from_stdin.rb +9 -0
  30. data/examples/parsing/from_string.rb +13 -0
  31. data/ext/yajl/api/yajl_common.h +89 -0
  32. data/ext/yajl/api/yajl_gen.h +161 -0
  33. data/ext/yajl/api/yajl_parse.h +196 -0
  34. data/ext/yajl/api/yajl_version.h +23 -0
  35. data/ext/yajl/extconf.rb +7 -0
  36. data/ext/yajl/yajl.c +164 -0
  37. data/ext/yajl/yajl_alloc.c +65 -0
  38. data/ext/yajl/yajl_alloc.h +50 -0
  39. data/ext/yajl/yajl_buf.c +119 -0
  40. data/ext/yajl/yajl_buf.h +73 -0
  41. data/ext/yajl/yajl_bytestack.h +85 -0
  42. data/ext/yajl/yajl_encode.c +201 -0
  43. data/ext/yajl/yajl_encode.h +52 -0
  44. data/ext/yajl/yajl_ext.c +905 -0
  45. data/ext/yajl/yajl_ext.h +135 -0
  46. data/ext/yajl/yajl_gen.c +344 -0
  47. data/ext/yajl/yajl_lex.c +748 -0
  48. data/ext/yajl/yajl_lex.h +135 -0
  49. data/ext/yajl/yajl_parser.c +450 -0
  50. data/ext/yajl/yajl_parser.h +82 -0
  51. data/ext/yajl/yajl_version.c +7 -0
  52. data/lib/yajl.rb +75 -0
  53. data/lib/yajl/1.8/yajl.so +0 -0
  54. data/lib/yajl/1.9/yajl.so +0 -0
  55. data/lib/yajl/bzip2.rb +11 -0
  56. data/lib/yajl/bzip2/stream_reader.rb +31 -0
  57. data/lib/yajl/bzip2/stream_writer.rb +14 -0
  58. data/lib/yajl/deflate.rb +6 -0
  59. data/lib/yajl/deflate/stream_reader.rb +43 -0
  60. data/lib/yajl/deflate/stream_writer.rb +20 -0
  61. data/lib/yajl/gzip.rb +6 -0
  62. data/lib/yajl/gzip/stream_reader.rb +30 -0
  63. data/lib/yajl/gzip/stream_writer.rb +13 -0
  64. data/lib/yajl/http_stream.rb +212 -0
  65. data/lib/yajl/json_gem.rb +15 -0
  66. data/lib/yajl/json_gem/encoding.rb +51 -0
  67. data/lib/yajl/json_gem/parsing.rb +26 -0
  68. data/lib/yajl/version.rb +3 -0
  69. data/lib/yajl/yajl.rb +2 -0
  70. data/spec/encoding/encoding_spec.rb +271 -0
  71. data/spec/global/global_spec.rb +54 -0
  72. data/spec/http/fixtures/http.bzip2.dump +0 -0
  73. data/spec/http/fixtures/http.chunked.dump +11 -0
  74. data/spec/http/fixtures/http.deflate.dump +0 -0
  75. data/spec/http/fixtures/http.error.dump +12 -0
  76. data/spec/http/fixtures/http.gzip.dump +0 -0
  77. data/spec/http/fixtures/http.html.dump +1220 -0
  78. data/spec/http/fixtures/http.raw.dump +1226 -0
  79. data/spec/http/http_delete_spec.rb +98 -0
  80. data/spec/http/http_error_spec.rb +32 -0
  81. data/spec/http/http_get_spec.rb +109 -0
  82. data/spec/http/http_post_spec.rb +123 -0
  83. data/spec/http/http_put_spec.rb +105 -0
  84. data/spec/http/http_stream_options_spec.rb +27 -0
  85. data/spec/json_gem_compatibility/compatibility_spec.rb +203 -0
  86. data/spec/parsing/active_support_spec.rb +64 -0
  87. data/spec/parsing/chunked_spec.rb +96 -0
  88. data/spec/parsing/fixtures/fail.15.json +1 -0
  89. data/spec/parsing/fixtures/fail.16.json +1 -0
  90. data/spec/parsing/fixtures/fail.17.json +1 -0
  91. data/spec/parsing/fixtures/fail.26.json +1 -0
  92. data/spec/parsing/fixtures/fail11.json +1 -0
  93. data/spec/parsing/fixtures/fail12.json +1 -0
  94. data/spec/parsing/fixtures/fail13.json +1 -0
  95. data/spec/parsing/fixtures/fail14.json +1 -0
  96. data/spec/parsing/fixtures/fail19.json +1 -0
  97. data/spec/parsing/fixtures/fail20.json +1 -0
  98. data/spec/parsing/fixtures/fail21.json +1 -0
  99. data/spec/parsing/fixtures/fail22.json +1 -0
  100. data/spec/parsing/fixtures/fail23.json +1 -0
  101. data/spec/parsing/fixtures/fail24.json +1 -0
  102. data/spec/parsing/fixtures/fail25.json +1 -0
  103. data/spec/parsing/fixtures/fail27.json +2 -0
  104. data/spec/parsing/fixtures/fail28.json +2 -0
  105. data/spec/parsing/fixtures/fail3.json +1 -0
  106. data/spec/parsing/fixtures/fail4.json +1 -0
  107. data/spec/parsing/fixtures/fail5.json +1 -0
  108. data/spec/parsing/fixtures/fail6.json +1 -0
  109. data/spec/parsing/fixtures/fail9.json +1 -0
  110. data/spec/parsing/fixtures/pass.array.json +6 -0
  111. data/spec/parsing/fixtures/pass.codepoints_from_unicode_org.json +1 -0
  112. data/spec/parsing/fixtures/pass.contacts.json +1 -0
  113. data/spec/parsing/fixtures/pass.db100.xml.json +1 -0
  114. data/spec/parsing/fixtures/pass.db1000.xml.json +1 -0
  115. data/spec/parsing/fixtures/pass.dc_simple_with_comments.json +11 -0
  116. data/spec/parsing/fixtures/pass.deep_arrays.json +1 -0
  117. data/spec/parsing/fixtures/pass.difficult_json_c_test_case.json +1 -0
  118. data/spec/parsing/fixtures/pass.difficult_json_c_test_case_with_comments.json +1 -0
  119. data/spec/parsing/fixtures/pass.doubles.json +1 -0
  120. data/spec/parsing/fixtures/pass.empty_array.json +1 -0
  121. data/spec/parsing/fixtures/pass.empty_string.json +1 -0
  122. data/spec/parsing/fixtures/pass.escaped_bulgarian.json +4 -0
  123. data/spec/parsing/fixtures/pass.escaped_foobar.json +1 -0
  124. data/spec/parsing/fixtures/pass.item.json +1 -0
  125. data/spec/parsing/fixtures/pass.json-org-sample1.json +23 -0
  126. data/spec/parsing/fixtures/pass.json-org-sample2.json +11 -0
  127. data/spec/parsing/fixtures/pass.json-org-sample3.json +26 -0
  128. data/spec/parsing/fixtures/pass.json-org-sample4-nows.json +88 -0
  129. data/spec/parsing/fixtures/pass.json-org-sample4.json +89 -0
  130. data/spec/parsing/fixtures/pass.json-org-sample5.json +27 -0
  131. data/spec/parsing/fixtures/pass.map-spain.xml.json +1 -0
  132. data/spec/parsing/fixtures/pass.ns-invoice100.xml.json +1 -0
  133. data/spec/parsing/fixtures/pass.ns-soap.xml.json +1 -0
  134. data/spec/parsing/fixtures/pass.numbers-fp-4k.json +6 -0
  135. data/spec/parsing/fixtures/pass.numbers-fp-64k.json +61 -0
  136. data/spec/parsing/fixtures/pass.numbers-int-4k.json +11 -0
  137. data/spec/parsing/fixtures/pass.numbers-int-64k.json +154 -0
  138. data/spec/parsing/fixtures/pass.twitter-search.json +1 -0
  139. data/spec/parsing/fixtures/pass.twitter-search2.json +1 -0
  140. data/spec/parsing/fixtures/pass.unicode.json +3315 -0
  141. data/spec/parsing/fixtures/pass.yelp.json +1 -0
  142. data/spec/parsing/fixtures/pass1.json +56 -0
  143. data/spec/parsing/fixtures/pass2.json +1 -0
  144. data/spec/parsing/fixtures/pass3.json +6 -0
  145. data/spec/parsing/fixtures_spec.rb +40 -0
  146. data/spec/parsing/one_off_spec.rb +85 -0
  147. data/spec/rcov.opts +3 -0
  148. data/spec/spec_helper.rb +16 -0
  149. data/tasks/compile.rake +35 -0
  150. data/tasks/rspec.rake +16 -0
  151. data/yajl-ruby.gemspec +24 -0
  152. metadata +335 -0
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright 2010, Lloyd Hilaiel.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions are
6
+ * met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ *
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in
13
+ * the documentation and/or other materials provided with the
14
+ * distribution.
15
+ *
16
+ * 3. Neither the name of Lloyd Hilaiel nor the names of its
17
+ * contributors may be used to endorse or promote products derived
18
+ * from this software without specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ /*
34
+ * A header only implementation of a simple stack of bytes, used in YAJL
35
+ * to maintain parse state.
36
+ */
37
+
38
+ #ifndef __YAJL_BYTESTACK_H__
39
+ #define __YAJL_BYTESTACK_H__
40
+
41
+ #include "api/yajl_common.h"
42
+
43
+ #define YAJL_BS_INC 128
44
+
45
+ typedef struct yajl_bytestack_t
46
+ {
47
+ unsigned char * stack;
48
+ unsigned int size;
49
+ unsigned int used;
50
+ yajl_alloc_funcs * yaf;
51
+ } yajl_bytestack;
52
+
53
+ /* initialize a bytestack */
54
+ #define yajl_bs_init(obs, _yaf) { \
55
+ (obs).stack = NULL; \
56
+ (obs).size = 0; \
57
+ (obs).used = 0; \
58
+ (obs).yaf = (_yaf); \
59
+ } \
60
+
61
+
62
+ /* initialize a bytestack */
63
+ #define yajl_bs_free(obs) \
64
+ if ((obs).stack) (obs).yaf->free((obs).yaf->ctx, (obs).stack);
65
+
66
+ #define yajl_bs_current(obs) \
67
+ (assert((obs).used > 0), (obs).stack[(obs).used - 1])
68
+
69
+ #define yajl_bs_push(obs, byte) { \
70
+ if (((obs).size - (obs).used) == 0) { \
71
+ (obs).size += YAJL_BS_INC; \
72
+ (obs).stack = (obs).yaf->realloc((obs).yaf->ctx,\
73
+ (void *) (obs).stack, (obs).size);\
74
+ } \
75
+ (obs).stack[((obs).used)++] = (byte); \
76
+ }
77
+
78
+ /* removes the top item of the stack, returns nothing */
79
+ #define yajl_bs_pop(obs) { ((obs).used)--; }
80
+
81
+ #define yajl_bs_set(obs, byte) \
82
+ (obs).stack[((obs).used) - 1] = (byte);
83
+
84
+
85
+ #endif
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Copyright 2010, Lloyd Hilaiel.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions are
6
+ * met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ *
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in
13
+ * the documentation and/or other materials provided with the
14
+ * distribution.
15
+ *
16
+ * 3. Neither the name of Lloyd Hilaiel nor the names of its
17
+ * contributors may be used to endorse or promote products derived
18
+ * from this software without specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ #include "yajl_encode.h"
34
+
35
+ #include <assert.h>
36
+ #include <stdlib.h>
37
+ #include <string.h>
38
+ #include <stdio.h>
39
+
40
+ static void CharToHex(unsigned char c, char * hexBuf)
41
+ {
42
+ const char * hexchar = "0123456789ABCDEF";
43
+ hexBuf[0] = hexchar[c >> 4];
44
+ hexBuf[1] = hexchar[c & 0x0F];
45
+ }
46
+
47
+ void
48
+ yajl_string_encode(yajl_buf buf, const unsigned char * str,
49
+ unsigned int len, unsigned int htmlSafe)
50
+ {
51
+ yajl_string_encode2((const yajl_print_t) &yajl_buf_append, buf, str, len, htmlSafe);
52
+ }
53
+
54
+ void
55
+ yajl_string_encode2(const yajl_print_t print,
56
+ void * ctx,
57
+ const unsigned char * str,
58
+ unsigned int len,
59
+ unsigned int htmlSafe)
60
+ {
61
+ unsigned int beg = 0;
62
+ unsigned int end = 0;
63
+ char hexBuf[7];
64
+ hexBuf[0] = '\\'; hexBuf[1] = 'u'; hexBuf[2] = '0'; hexBuf[3] = '0';
65
+ hexBuf[6] = 0;
66
+
67
+ while (end < len) {
68
+ const char * escaped = NULL;
69
+ switch (str[end]) {
70
+ case '\r': escaped = "\\r"; break;
71
+ case '\n': escaped = "\\n"; break;
72
+ case '\\': escaped = "\\\\"; break;
73
+ /* case '/': escaped = "\\/"; break; */
74
+ case '"': escaped = "\\\""; break;
75
+ case '\f': escaped = "\\f"; break;
76
+ case '\b': escaped = "\\b"; break;
77
+ case '\t': escaped = "\\t"; break;
78
+ case '/':
79
+ if (htmlSafe) {
80
+ escaped = "\\/";
81
+ }
82
+ break;
83
+ default:
84
+ if ((unsigned char) str[end] < 32) {
85
+ CharToHex(str[end], hexBuf + 4);
86
+ escaped = hexBuf;
87
+ }
88
+ break;
89
+ }
90
+ if (escaped != NULL) {
91
+ print(ctx, (const char *) (str + beg), end - beg);
92
+ print(ctx, escaped, (unsigned int)strlen(escaped));
93
+ beg = ++end;
94
+ } else {
95
+ ++end;
96
+ }
97
+ }
98
+ print(ctx, (const char *) (str + beg), end - beg);
99
+ }
100
+
101
+ static void hexToDigit(unsigned int * val, const unsigned char * hex)
102
+ {
103
+ unsigned int i;
104
+ for (i=0;i<4;i++) {
105
+ unsigned char c = hex[i];
106
+ if (c >= 'A') c = (c & ~0x20) - 7;
107
+ c -= '0';
108
+ assert(!(c & 0xF0));
109
+ *val = (*val << 4) | c;
110
+ }
111
+ }
112
+
113
+ static void Utf32toUtf8(unsigned int codepoint, char * utf8Buf)
114
+ {
115
+ if (codepoint < 0x80) {
116
+ utf8Buf[0] = (char) codepoint;
117
+ utf8Buf[1] = 0;
118
+ } else if (codepoint < 0x0800) {
119
+ utf8Buf[0] = (char) ((codepoint >> 6) | 0xC0);
120
+ utf8Buf[1] = (char) ((codepoint & 0x3F) | 0x80);
121
+ utf8Buf[2] = 0;
122
+ } else if (codepoint < 0x10000) {
123
+ utf8Buf[0] = (char) ((codepoint >> 12) | 0xE0);
124
+ utf8Buf[1] = (char) (((codepoint >> 6) & 0x3F) | 0x80);
125
+ utf8Buf[2] = (char) ((codepoint & 0x3F) | 0x80);
126
+ utf8Buf[3] = 0;
127
+ } else if (codepoint < 0x200000) {
128
+ utf8Buf[0] =(char)((codepoint >> 18) | 0xF0);
129
+ utf8Buf[1] =(char)(((codepoint >> 12) & 0x3F) | 0x80);
130
+ utf8Buf[2] =(char)(((codepoint >> 6) & 0x3F) | 0x80);
131
+ utf8Buf[3] =(char)((codepoint & 0x3F) | 0x80);
132
+ utf8Buf[4] = 0;
133
+ } else {
134
+ utf8Buf[0] = '?';
135
+ utf8Buf[1] = 0;
136
+ }
137
+ }
138
+
139
+ void yajl_string_decode(yajl_buf buf, const unsigned char * str,
140
+ unsigned int len)
141
+ {
142
+ unsigned int beg = 0;
143
+ unsigned int end = 0;
144
+
145
+ while (end < len) {
146
+ if (str[end] == '\\') {
147
+ char utf8Buf[5];
148
+ const char * unescaped = "?";
149
+ yajl_buf_append(buf, str + beg, end - beg);
150
+ switch (str[++end]) {
151
+ case 'r': unescaped = "\r"; break;
152
+ case 'n': unescaped = "\n"; break;
153
+ case '\\': unescaped = "\\"; break;
154
+ case '/': unescaped = "/"; break;
155
+ case '"': unescaped = "\""; break;
156
+ case 'f': unescaped = "\f"; break;
157
+ case 'b': unescaped = "\b"; break;
158
+ case 't': unescaped = "\t"; break;
159
+ case 'u': {
160
+ unsigned int codepoint = 0;
161
+ hexToDigit(&codepoint, str + ++end);
162
+ end+=3;
163
+ /* check if this is a surrogate */
164
+ if ((codepoint & 0xFC00) == 0xD800) {
165
+ end++;
166
+ if (str[end] == '\\' && str[end + 1] == 'u') {
167
+ unsigned int surrogate = 0;
168
+ hexToDigit(&surrogate, str + end + 2);
169
+ codepoint =
170
+ (((codepoint & 0x3F) << 10) |
171
+ ((((codepoint >> 6) & 0xF) + 1) << 16) |
172
+ (surrogate & 0x3FF));
173
+ end += 5;
174
+ } else {
175
+ unescaped = "?";
176
+ break;
177
+ }
178
+ }
179
+
180
+ Utf32toUtf8(codepoint, utf8Buf);
181
+ unescaped = utf8Buf;
182
+
183
+ if (codepoint == 0) {
184
+ yajl_buf_append(buf, unescaped, 1);
185
+ beg = ++end;
186
+ continue;
187
+ }
188
+
189
+ break;
190
+ }
191
+ default:
192
+ assert("this should never happen" == NULL);
193
+ }
194
+ yajl_buf_append(buf, unescaped, (unsigned int)strlen(unescaped));
195
+ beg = ++end;
196
+ } else {
197
+ end++;
198
+ }
199
+ }
200
+ yajl_buf_append(buf, str + beg, end - beg);
201
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Copyright 2010, Lloyd Hilaiel.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions are
6
+ * met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ *
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in
13
+ * the documentation and/or other materials provided with the
14
+ * distribution.
15
+ *
16
+ * 3. Neither the name of Lloyd Hilaiel nor the names of its
17
+ * contributors may be used to endorse or promote products derived
18
+ * from this software without specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ #ifndef __YAJL_ENCODE_H__
34
+ #define __YAJL_ENCODE_H__
35
+
36
+ #include "yajl_buf.h"
37
+ #include "api/yajl_gen.h"
38
+
39
+ void yajl_string_encode2(const yajl_print_t printer,
40
+ void * ctx,
41
+ const unsigned char * str,
42
+ unsigned int length,
43
+ unsigned int htmlSafe);
44
+
45
+ void yajl_string_encode(yajl_buf buf, const unsigned char * str,
46
+ unsigned int length,
47
+ unsigned int htmlSafe);
48
+
49
+ void yajl_string_decode(yajl_buf buf, const unsigned char * str,
50
+ unsigned int length);
51
+
52
+ #endif
@@ -0,0 +1,905 @@
1
+ /*
2
+ * Copyright (c) 2008-2011 Brian Lopez - http://github.com/brianmario
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining
5
+ * a copy of this software and associated documentation files (the
6
+ * "Software"), to deal in the Software without restriction, including
7
+ * without limitation the rights to use, copy, modify, merge, publish,
8
+ * distribute, sublicense, and/or sell copies of the Software, and to
9
+ * permit persons to whom the Software is furnished to do so, subject to
10
+ * the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be
13
+ * included in all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ #include "yajl_ext.h"
25
+
26
+ #define YAJL_RB_TO_JSON \
27
+ VALUE rb_encoder, cls; \
28
+ rb_scan_args(argc, argv, "01", &rb_encoder); \
29
+ cls = rb_obj_class(rb_encoder); \
30
+ if (rb_encoder == Qnil || cls != cEncoder) { \
31
+ rb_encoder = rb_yajl_encoder_new(0, NULL, cEncoder); \
32
+ } \
33
+ return rb_yajl_encoder_encode(1, &self, rb_encoder); \
34
+
35
+ /* Helpers for building objects */
36
+ inline void yajl_check_and_fire_callback(void * ctx) {
37
+ yajl_parser_wrapper * wrapper;
38
+ GetParser((VALUE)ctx, wrapper);
39
+
40
+ /* No need to do any of this if the callback isn't even setup */
41
+ if (wrapper->parse_complete_callback != Qnil) {
42
+ int len = RARRAY_LEN(wrapper->builderStack);
43
+ if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
44
+ rb_funcall(wrapper->parse_complete_callback, intern_call, 1, rb_ary_pop(wrapper->builderStack));
45
+ }
46
+ } else {
47
+ int len = RARRAY_LEN(wrapper->builderStack);
48
+ if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
49
+ wrapper->objectsFound++;
50
+ if (wrapper->objectsFound > 1) {
51
+ rb_raise(cParseError, "%s", "Found multiple JSON objects in the stream but no block or the on_parse_complete callback was assigned to handle them.");
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ inline void yajl_set_static_value(void * ctx, VALUE val) {
58
+ yajl_parser_wrapper * wrapper;
59
+ VALUE lastEntry, hash;
60
+ int len;
61
+
62
+ GetParser((VALUE)ctx, wrapper);
63
+
64
+ len = RARRAY_LEN(wrapper->builderStack);
65
+ if (len > 0) {
66
+ lastEntry = rb_ary_entry(wrapper->builderStack, len-1);
67
+ switch (TYPE(lastEntry)) {
68
+ case T_ARRAY:
69
+ rb_ary_push(lastEntry, val);
70
+ if (TYPE(val) == T_HASH || TYPE(val) == T_ARRAY) {
71
+ rb_ary_push(wrapper->builderStack, val);
72
+ }
73
+ break;
74
+ case T_HASH:
75
+ rb_hash_aset(lastEntry, val, Qnil);
76
+ rb_ary_push(wrapper->builderStack, val);
77
+ break;
78
+ case T_STRING:
79
+ case T_SYMBOL:
80
+ hash = rb_ary_entry(wrapper->builderStack, len-2);
81
+ if (TYPE(hash) == T_HASH) {
82
+ rb_hash_aset(hash, lastEntry, val);
83
+ rb_ary_pop(wrapper->builderStack);
84
+ if (TYPE(val) == T_HASH || TYPE(val) == T_ARRAY) {
85
+ rb_ary_push(wrapper->builderStack, val);
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ } else {
91
+ rb_ary_push(wrapper->builderStack, val);
92
+ }
93
+ }
94
+
95
+ static void yajl_encoder_wrapper_free(void * wrapper) {
96
+ yajl_encoder_wrapper * w = wrapper;
97
+ if (w) {
98
+ if (w->indentString) {
99
+ free(w->indentString);
100
+ }
101
+ yajl_gen_free(w->encoder);
102
+ free(w);
103
+ }
104
+ }
105
+
106
+ static void yajl_encoder_wrapper_mark(void * wrapper) {
107
+ yajl_encoder_wrapper * w = wrapper;
108
+ if (w) {
109
+ rb_gc_mark(w->on_progress_callback);
110
+ rb_gc_mark(w->terminator);
111
+ }
112
+ }
113
+
114
+ void yajl_encode_part(void * wrapper, VALUE obj, VALUE io) {
115
+ VALUE str, outBuff, otherObj;
116
+ yajl_encoder_wrapper * w = wrapper;
117
+ yajl_gen_status status;
118
+ int idx = 0;
119
+ const unsigned char * buffer;
120
+ const char * cptr;
121
+ unsigned int len;
122
+ VALUE keys, entry, keyStr;
123
+
124
+ if (io != Qnil || w->on_progress_callback != Qnil) {
125
+ status = yajl_gen_get_buf(w->encoder, &buffer, &len);
126
+ if (len >= WRITE_BUFSIZE) {
127
+ outBuff = rb_str_new((const char *)buffer, len);
128
+ if (io != Qnil) {
129
+ rb_io_write(io, outBuff);
130
+ } else if (w->on_progress_callback != Qnil) {
131
+ rb_funcall(w->on_progress_callback, intern_call, 1, outBuff);
132
+ }
133
+ yajl_gen_clear(w->encoder);
134
+ }
135
+ }
136
+
137
+ switch (TYPE(obj)) {
138
+ case T_HASH:
139
+ status = yajl_gen_map_open(w->encoder);
140
+
141
+ /* TODO: itterate through keys in the hash */
142
+ keys = rb_funcall(obj, intern_keys, 0);
143
+ for(idx=0; idx<RARRAY_LEN(keys); idx++) {
144
+ entry = rb_ary_entry(keys, idx);
145
+ keyStr = rb_funcall(entry, intern_to_s, 0); /* key must be a string */
146
+ /* the key */
147
+ yajl_encode_part(w, keyStr, io);
148
+ /* the value */
149
+ yajl_encode_part(w, rb_hash_aref(obj, entry), io);
150
+ }
151
+
152
+ status = yajl_gen_map_close(w->encoder);
153
+ break;
154
+ case T_ARRAY:
155
+ status = yajl_gen_array_open(w->encoder);
156
+ for(idx=0; idx<RARRAY_LEN(obj); idx++) {
157
+ otherObj = rb_ary_entry(obj, idx);
158
+ yajl_encode_part(w, otherObj, io);
159
+ }
160
+ status = yajl_gen_array_close(w->encoder);
161
+ break;
162
+ case T_NIL:
163
+ status = yajl_gen_null(w->encoder);
164
+ break;
165
+ case T_TRUE:
166
+ status = yajl_gen_bool(w->encoder, 1);
167
+ break;
168
+ case T_FALSE:
169
+ status = yajl_gen_bool(w->encoder, 0);
170
+ break;
171
+ case T_FIXNUM:
172
+ case T_FLOAT:
173
+ case T_BIGNUM:
174
+ str = rb_funcall(obj, intern_to_s, 0);
175
+ cptr = RSTRING_PTR(str);
176
+ len = RSTRING_LEN(str);
177
+ if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) {
178
+ rb_raise(cEncodeError, "'%s' is an invalid number", cptr);
179
+ }
180
+ status = yajl_gen_number(w->encoder, cptr, len);
181
+ break;
182
+ case T_STRING:
183
+ cptr = RSTRING_PTR(obj);
184
+ len = RSTRING_LEN(obj);
185
+ status = yajl_gen_string(w->encoder, (const unsigned char *)cptr, len);
186
+ break;
187
+ default:
188
+ if (rb_respond_to(obj, intern_to_json)) {
189
+ str = rb_funcall(obj, intern_to_json, 0);
190
+ Check_Type(str, T_STRING);
191
+ cptr = RSTRING_PTR(str);
192
+ len = RSTRING_LEN(str);
193
+ status = yajl_gen_number(w->encoder, cptr, len);
194
+ } else {
195
+ str = rb_funcall(obj, intern_to_s, 0);
196
+ Check_Type(str, T_STRING);
197
+ cptr = RSTRING_PTR(str);
198
+ len = RSTRING_LEN(str);
199
+ status = yajl_gen_string(w->encoder, (const unsigned char *)cptr, len);
200
+ }
201
+ break;
202
+ }
203
+ }
204
+
205
+ void yajl_parser_wrapper_free(void * wrapper) {
206
+ yajl_parser_wrapper * w = wrapper;
207
+ if (w) {
208
+ yajl_free(w->parser);
209
+ free(w);
210
+ }
211
+ }
212
+
213
+ void yajl_parser_wrapper_mark(void * wrapper) {
214
+ yajl_parser_wrapper * w = wrapper;
215
+ if (w) {
216
+ rb_gc_mark(w->builderStack);
217
+ rb_gc_mark(w->parse_complete_callback);
218
+ }
219
+ }
220
+
221
+ void yajl_parse_chunk(const unsigned char * chunk, unsigned int len, yajl_handle parser) {
222
+ yajl_status stat;
223
+
224
+ stat = yajl_parse(parser, chunk, len);
225
+
226
+ if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
227
+ unsigned char * str = yajl_get_error(parser, 1, chunk, len);
228
+ VALUE errobj = rb_exc_new2(cParseError, (const char*) str);
229
+ yajl_free_error(parser, str);
230
+ rb_exc_raise(errobj);
231
+ }
232
+ }
233
+
234
+ /* YAJL Callbacks */
235
+ static int yajl_found_null(void * ctx) {
236
+ yajl_set_static_value(ctx, Qnil);
237
+ yajl_check_and_fire_callback(ctx);
238
+ return 1;
239
+ }
240
+
241
+ static int yajl_found_boolean(void * ctx, int boolean) {
242
+ yajl_set_static_value(ctx, boolean ? Qtrue : Qfalse);
243
+ yajl_check_and_fire_callback(ctx);
244
+ return 1;
245
+ }
246
+
247
+ static int yajl_found_number(void * ctx, const char * numberVal, unsigned int numberLen) {
248
+ char buf[numberLen+1];
249
+ buf[numberLen] = 0;
250
+ memcpy(buf, numberVal, numberLen);
251
+
252
+ if (memchr(buf, '.', numberLen) ||
253
+ memchr(buf, 'e', numberLen) ||
254
+ memchr(buf, 'E', numberLen)) {
255
+ yajl_set_static_value(ctx, rb_float_new(strtod(buf, NULL)));
256
+ } else {
257
+ yajl_set_static_value(ctx, rb_cstr2inum(buf, 10));
258
+ }
259
+ return 1;
260
+ }
261
+
262
+ static int yajl_found_string(void * ctx, const unsigned char * stringVal, unsigned int stringLen) {
263
+ VALUE str = rb_str_new((const char *)stringVal, stringLen);
264
+ #ifdef HAVE_RUBY_ENCODING_H
265
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
266
+ rb_enc_associate(str, utf8Encoding);
267
+ if (default_internal_enc) {
268
+ str = rb_str_export_to_enc(str, default_internal_enc);
269
+ }
270
+ #endif
271
+ yajl_set_static_value(ctx, str);
272
+ yajl_check_and_fire_callback(ctx);
273
+ return 1;
274
+ }
275
+
276
+ static int yajl_found_hash_key(void * ctx, const unsigned char * stringVal, unsigned int stringLen) {
277
+ yajl_parser_wrapper * wrapper;
278
+ VALUE keyStr;
279
+ #ifdef HAVE_RUBY_ENCODING_H
280
+ rb_encoding *default_internal_enc;
281
+ #endif
282
+ GetParser((VALUE)ctx, wrapper);
283
+ #ifdef HAVE_RUBY_ENCODING_H
284
+ default_internal_enc = rb_default_internal_encoding();
285
+ #endif
286
+
287
+ if (wrapper->symbolizeKeys) {
288
+ char buf[stringLen+1];
289
+ memcpy(buf, stringVal, stringLen);
290
+ buf[stringLen] = 0;
291
+ VALUE stringEncoded = rb_str_new2(buf);
292
+ #ifdef HAVE_RUBY_ENCODING_H
293
+ rb_enc_associate(stringEncoded, rb_utf8_encoding());
294
+ #endif
295
+
296
+ yajl_set_static_value(ctx, ID2SYM(rb_to_id(stringEncoded)));
297
+ } else {
298
+ keyStr = rb_str_new((const char *)stringVal, stringLen);
299
+ #ifdef HAVE_RUBY_ENCODING_H
300
+ rb_enc_associate(keyStr, utf8Encoding);
301
+ if (default_internal_enc) {
302
+ keyStr = rb_str_export_to_enc(keyStr, default_internal_enc);
303
+ }
304
+ #endif
305
+ yajl_set_static_value(ctx, keyStr);
306
+ }
307
+ yajl_check_and_fire_callback(ctx);
308
+ return 1;
309
+ }
310
+
311
+ static int yajl_found_start_hash(void * ctx) {
312
+ yajl_parser_wrapper * wrapper;
313
+ GetParser((VALUE)ctx, wrapper);
314
+ wrapper->nestedHashLevel++;
315
+ yajl_set_static_value(ctx, rb_hash_new());
316
+ return 1;
317
+ }
318
+
319
+ static int yajl_found_end_hash(void * ctx) {
320
+ yajl_parser_wrapper * wrapper;
321
+ GetParser((VALUE)ctx, wrapper);
322
+ wrapper->nestedHashLevel--;
323
+ if (RARRAY_LEN(wrapper->builderStack) > 1) {
324
+ rb_ary_pop(wrapper->builderStack);
325
+ }
326
+ yajl_check_and_fire_callback(ctx);
327
+ return 1;
328
+ }
329
+
330
+ static int yajl_found_start_array(void * ctx) {
331
+ yajl_parser_wrapper * wrapper;
332
+ GetParser((VALUE)ctx, wrapper);
333
+ wrapper->nestedArrayLevel++;
334
+ yajl_set_static_value(ctx, rb_ary_new());
335
+ return 1;
336
+ }
337
+
338
+ static int yajl_found_end_array(void * ctx) {
339
+ yajl_parser_wrapper * wrapper;
340
+ GetParser((VALUE)ctx, wrapper);
341
+ wrapper->nestedArrayLevel--;
342
+ if (RARRAY_LEN(wrapper->builderStack) > 1) {
343
+ rb_ary_pop(wrapper->builderStack);
344
+ }
345
+ yajl_check_and_fire_callback(ctx);
346
+ return 1;
347
+ }
348
+
349
+
350
+ /* Ruby Interface */
351
+
352
+ /*
353
+ * Document-class: Yajl::Parser
354
+ *
355
+ * This class contains methods for parsing JSON directly from an IO object.
356
+ * The only basic requirment currently is that the IO object respond to #read(len) and #eof?
357
+ * The IO is parsed until a complete JSON object has been read and a ruby object will be returned.
358
+ */
359
+
360
+ /*
361
+ * Document-method: new
362
+ *
363
+ * call-seq: new([:symbolize_keys => true, [:allow_comments => false[, :check_utf8 => false]]])
364
+ *
365
+ * :symbolize_keys will turn hash keys into Ruby symbols, defaults to false.
366
+ *
367
+ * :allow_comments will turn on/off the check for comments inside the JSON stream, defaults to true.
368
+ *
369
+ * :check_utf8 will validate UTF8 characters found in the JSON stream, defaults to true.
370
+ */
371
+ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
372
+ yajl_parser_wrapper * wrapper;
373
+ yajl_parser_config cfg;
374
+ VALUE opts, obj;
375
+ int allowComments = 1, checkUTF8 = 1, symbolizeKeys = 0;
376
+
377
+ /* Scan off config vars */
378
+ if (rb_scan_args(argc, argv, "01", &opts) == 1) {
379
+ Check_Type(opts, T_HASH);
380
+
381
+ if (rb_hash_aref(opts, sym_allow_comments) == Qfalse) {
382
+ allowComments = 0;
383
+ }
384
+ if (rb_hash_aref(opts, sym_check_utf8) == Qfalse) {
385
+ checkUTF8 = 0;
386
+ }
387
+ if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
388
+ symbolizeKeys = 1;
389
+ }
390
+ }
391
+ cfg = (yajl_parser_config){allowComments, checkUTF8};
392
+
393
+ obj = Data_Make_Struct(klass, yajl_parser_wrapper, yajl_parser_wrapper_mark, yajl_parser_wrapper_free, wrapper);
394
+ wrapper->parser = yajl_alloc(&callbacks, &cfg, NULL, (void *)obj);
395
+ wrapper->nestedArrayLevel = 0;
396
+ wrapper->nestedHashLevel = 0;
397
+ wrapper->objectsFound = 0;
398
+ wrapper->symbolizeKeys = symbolizeKeys;
399
+ wrapper->builderStack = rb_ary_new();
400
+ wrapper->parse_complete_callback = Qnil;
401
+ rb_obj_call_init(obj, 0, 0);
402
+ return obj;
403
+ }
404
+
405
+ /*
406
+ * Document-method: initialize
407
+ *
408
+ * call-seq: new([:symbolize_keys => true, [:allow_comments => false[, :check_utf8 => false]]])
409
+ *
410
+ * :symbolize_keys will turn hash keys into Ruby symbols, defaults to false.
411
+ *
412
+ * :allow_comments will turn on/off the check for comments inside the JSON stream, defaults to true.
413
+ *
414
+ * :check_utf8 will validate UTF8 characters found in the JSON stream, defaults to true.
415
+ */
416
+ static VALUE rb_yajl_parser_init(int argc, VALUE * argv, VALUE self) {
417
+ return self;
418
+ }
419
+
420
+ /*
421
+ * Document-method: parse
422
+ *
423
+ * call-seq:
424
+ * parse(input, buffer_size=8092)
425
+ * parse(input, buffer_size=8092) { |obj| ... }
426
+ *
427
+ * +input+ can either be a string or an IO to parse JSON from
428
+ *
429
+ * +buffer_size+ is the size of chunk that will be parsed off the input (if it's an IO) for each loop of the parsing process.
430
+ * 8092 is a good balance between the different types of streams (off disk, off a socket, etc...), but this option
431
+ * is here so the caller can better tune their parsing depending on the type of stream being passed.
432
+ * A larger read buffer will perform better for files off disk, where as a smaller size may be more efficient for
433
+ * reading off of a socket directly.
434
+ *
435
+ * If a block was passed, it's called when an object has been parsed off the stream. This is especially
436
+ * usefull when parsing a stream of multiple JSON objects.
437
+ *
438
+ * NOTE: you can optionally assign the +on_parse_complete+ callback, and it will be called the same way the optional
439
+ * block is for this method.
440
+ */
441
+ static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
442
+ yajl_status stat;
443
+ yajl_parser_wrapper * wrapper;
444
+ VALUE rbufsize, input, blk;
445
+ unsigned int len;
446
+ const char * cptr;
447
+
448
+ GetParser(self, wrapper);
449
+
450
+ /* setup our parameters */
451
+ rb_scan_args(argc, argv, "11&", &input, &rbufsize, &blk);
452
+ if (NIL_P(rbufsize)) {
453
+ rbufsize = INT2FIX(READ_BUFSIZE);
454
+ } else {
455
+ Check_Type(rbufsize, T_FIXNUM);
456
+ }
457
+ if (!NIL_P(blk)) {
458
+ rb_yajl_parser_set_complete_cb(self, blk);
459
+ }
460
+
461
+ if (TYPE(input) == T_STRING) {
462
+ cptr = RSTRING_PTR(input);
463
+ len = RSTRING_LEN(input);
464
+ yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
465
+ } else if (rb_respond_to(input, intern_io_read)) {
466
+ VALUE parsed = rb_str_new(0, FIX2LONG(rbufsize));
467
+ while (rb_funcall(input, intern_io_read, 2, rbufsize, parsed) != Qnil) {
468
+ cptr = RSTRING_PTR(parsed);
469
+ len = RSTRING_LEN(parsed);
470
+ yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
471
+ }
472
+ } else {
473
+ rb_raise(cParseError, "input must be a string or IO");
474
+ }
475
+
476
+ /* parse any remaining buffered data */
477
+ stat = yajl_parse_complete(wrapper->parser);
478
+
479
+ if (wrapper->parse_complete_callback != Qnil) {
480
+ yajl_check_and_fire_callback((void *)self);
481
+ return Qnil;
482
+ }
483
+
484
+ return rb_ary_pop(wrapper->builderStack);
485
+ }
486
+
487
+ /*
488
+ * Document-method: parse_chunk
489
+ *
490
+ * call-seq: parse_chunk(string_chunk)
491
+ *
492
+ * +string_chunk+ can be a partial or full JSON string to push on the parser.
493
+ *
494
+ * This method will throw an exception if the +on_parse_complete+ callback hasn't been assigned yet.
495
+ * The +on_parse_complete+ callback assignment is required so the user can handle objects that have been
496
+ * parsed off the stream as they're found.
497
+ */
498
+ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
499
+ yajl_parser_wrapper * wrapper;
500
+ unsigned int len;
501
+
502
+ GetParser(self, wrapper);
503
+ if (NIL_P(chunk)) {
504
+ rb_raise(cParseError, "Can't parse a nil string.");
505
+ }
506
+
507
+ if (wrapper->parse_complete_callback != Qnil) {
508
+ const char * cptr = RSTRING_PTR(chunk);
509
+ len = RSTRING_LEN(chunk);
510
+ yajl_parse_chunk((const unsigned char*)cptr, len, wrapper->parser);
511
+ } else {
512
+ rb_raise(cParseError, "The on_parse_complete callback isn't setup, parsing useless.");
513
+ }
514
+
515
+ return Qnil;
516
+ }
517
+
518
+ /*
519
+ * Document-method: on_parse_complete=
520
+ *
521
+ * call-seq: on_parse_complete = Proc.new { |obj| ... }
522
+ *
523
+ * This callback setter allows you to pass a Proc/lambda or any other object that responds to #call.
524
+ *
525
+ * It will pass a single parameter, the ruby object built from the last parsed JSON object
526
+ */
527
+ static VALUE rb_yajl_parser_set_complete_cb(VALUE self, VALUE callback) {
528
+ yajl_parser_wrapper * wrapper;
529
+ GetParser(self, wrapper);
530
+ wrapper->parse_complete_callback = callback;
531
+ return Qnil;
532
+ }
533
+
534
+ /*
535
+ * Document-class: Yajl::Encoder
536
+ *
537
+ * This class contains methods for encoding a Ruby object into JSON, streaming it's output into an IO object.
538
+ * The IO object need only respond to #write(str)
539
+ * The JSON stream created is written to the IO in chunks, as it's being created.
540
+ */
541
+
542
+ static unsigned char * defaultIndentString = (unsigned char *)" ";
543
+ /*
544
+ * Document-method: new
545
+ *
546
+ * call-seq: initialize([:pretty => false[, :indent => ' '][, :terminator => "\n"]])
547
+ *
548
+ * :pretty will enable/disable beautifying or "pretty priting" the output string.
549
+ *
550
+ * :indent is the character(s) used to indent the output string.
551
+ *
552
+ * :terminator allows you to specify a character to be used as the termination character after a full JSON string has been generated by
553
+ * the encoder. This would be especially useful when encoding in chunks (via a block or callback during the encode process), to be able to
554
+ * determine when the last chunk of the current encode is sent.
555
+ * If you specify this option to be nil, it will be ignored if encoding directly to an IO or simply returning a string. But if a block is used,
556
+ * the encoder will still pass it - I hope that makes sense ;).
557
+ */
558
+ static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
559
+ yajl_encoder_wrapper * wrapper;
560
+ yajl_gen_config cfg;
561
+ VALUE opts, obj, indent;
562
+ unsigned char *indentString = NULL, *actualIndent = NULL;
563
+ int beautify = 0, htmlSafe = 0;
564
+
565
+ /* Scan off config vars */
566
+ if (rb_scan_args(argc, argv, "01", &opts) == 1) {
567
+ Check_Type(opts, T_HASH);
568
+
569
+ if (rb_hash_aref(opts, sym_pretty) == Qtrue) {
570
+ beautify = 1;
571
+ indent = rb_hash_aref(opts, sym_indent);
572
+ if (indent != Qnil) {
573
+ #ifdef HAVE_RUBY_ENCODING_H
574
+ indent = rb_str_export_to_enc(indent, utf8Encoding);
575
+ #endif
576
+ Check_Type(indent, T_STRING);
577
+ indentString = (unsigned char*)malloc(RSTRING_LEN(indent)+1);
578
+ memcpy(indentString, RSTRING_PTR(indent), RSTRING_LEN(indent));
579
+ indentString[RSTRING_LEN(indent)] = '\0';
580
+ actualIndent = indentString;
581
+ }
582
+ }
583
+ if (rb_hash_aref(opts, sym_html_safe) == Qtrue) {
584
+ htmlSafe = 1;
585
+ }
586
+ }
587
+ if (!indentString) {
588
+ indentString = defaultIndentString;
589
+ }
590
+ cfg = (yajl_gen_config){beautify, (const char *)indentString, htmlSafe};
591
+
592
+ obj = Data_Make_Struct(klass, yajl_encoder_wrapper, yajl_encoder_wrapper_mark, yajl_encoder_wrapper_free, wrapper);
593
+ wrapper->indentString = actualIndent;
594
+ wrapper->encoder = yajl_gen_alloc(&cfg, NULL);
595
+ wrapper->on_progress_callback = Qnil;
596
+ if (opts != Qnil && rb_funcall(opts, intern_has_key, 1, sym_terminator) == Qtrue) {
597
+ wrapper->terminator = rb_hash_aref(opts, sym_terminator);
598
+ #ifdef HAVE_RUBY_ENCODING_H
599
+ if (TYPE(wrapper->terminator) == T_STRING) {
600
+ wrapper->terminator = rb_str_export_to_enc(wrapper->terminator, utf8Encoding);
601
+ }
602
+ #endif
603
+ } else {
604
+ wrapper->terminator = 0;
605
+ }
606
+ rb_obj_call_init(obj, 0, 0);
607
+ return obj;
608
+ }
609
+
610
+ /*
611
+ * Document-method: initialize
612
+ *
613
+ * call-seq: initialize([:pretty => false[, :indent => ' '][, :terminator => "\n"]])
614
+ *
615
+ * :pretty will enable/disable beautifying or "pretty priting" the output string.
616
+ *
617
+ * :indent is the character(s) used to indent the output string.
618
+ *
619
+ * :terminator allows you to specify a character to be used as the termination character after a full JSON string has been generated by
620
+ * the encoder. This would be especially useful when encoding in chunks (via a block or callback during the encode process), to be able to
621
+ * determine when the last chunk of the current encode is sent.
622
+ * If you specify this option to be nil, it will be ignored if encoding directly to an IO or simply returning a string. But if a block is used,
623
+ * the encoder will still pass it - I hope that makes sense ;).
624
+ */
625
+ static VALUE rb_yajl_encoder_init(int argc, VALUE * argv, VALUE self) {
626
+ return self;
627
+ }
628
+
629
+ /*
630
+ * Document-method: encode
631
+ *
632
+ * call-seq: encode(obj[, io[, &block]])
633
+ *
634
+ * +obj+ is the Ruby object to encode to JSON
635
+ *
636
+ * +io+ is an optional IO used to stream the encoded JSON string to.
637
+ * If +io+ isn't specified, this method will return the resulting JSON string. If +io+ is specified, this method returns nil
638
+ *
639
+ * If an optional block is passed, it's called when encoding is complete and passed the resulting JSON string
640
+ *
641
+ * It should be noted that you can reuse an instance of this class to continue encoding multiple JSON
642
+ * to the same stream. Just continue calling this method, passing it the same IO object with new/different
643
+ * ruby objects to encode. This is how streaming is accomplished.
644
+ */
645
+ static VALUE rb_yajl_encoder_encode(int argc, VALUE * argv, VALUE self) {
646
+ yajl_encoder_wrapper * wrapper;
647
+ const unsigned char * buffer;
648
+ unsigned int len;
649
+ VALUE obj, io, blk, outBuff;
650
+
651
+ GetEncoder(self, wrapper);
652
+
653
+ rb_scan_args(argc, argv, "11&", &obj, &io, &blk);
654
+
655
+ if (blk != Qnil) {
656
+ wrapper->on_progress_callback = blk;
657
+ }
658
+
659
+ /* begin encode process */
660
+ yajl_encode_part(wrapper, obj, io);
661
+
662
+ /* just make sure we output the remaining buffer */
663
+ yajl_gen_get_buf(wrapper->encoder, &buffer, &len);
664
+ outBuff = rb_str_new((const char *)buffer, len);
665
+ #ifdef HAVE_RUBY_ENCODING_H
666
+ rb_enc_associate(outBuff, utf8Encoding);
667
+ #endif
668
+ yajl_gen_clear(wrapper->encoder);
669
+
670
+ if (io != Qnil) {
671
+ rb_io_write(io, outBuff);
672
+ if (wrapper->terminator != 0 && wrapper->terminator != Qnil) {
673
+ rb_io_write(io, wrapper->terminator);
674
+ }
675
+ return Qnil;
676
+ } else if (blk != Qnil) {
677
+ rb_funcall(blk, intern_call, 1, outBuff);
678
+ if (wrapper->terminator != 0) {
679
+ rb_funcall(blk, intern_call, 1, wrapper->terminator);
680
+ }
681
+ return Qnil;
682
+ } else {
683
+ if (wrapper->terminator != 0 && wrapper->terminator != Qnil) {
684
+ rb_str_concat(outBuff, wrapper->terminator);
685
+ }
686
+ return outBuff;
687
+ }
688
+ return Qnil;
689
+ }
690
+
691
+ /*
692
+ * Document-method: on_progress
693
+ *
694
+ * call-seq: on_progress = Proc.new {|str| ...}
695
+ *
696
+ * This callback setter allows you to pass a Proc/lambda or any other object that responds to #call.
697
+ *
698
+ * It will pass the caller a chunk of the encode buffer after it's reached it's internal max buffer size (defaults to 8kb).
699
+ * For example, encoding a large object that would normally result in 24288 bytes of data will result in 3 calls to this callback (assuming the 8kb default encode buffer).
700
+ */
701
+ static VALUE rb_yajl_encoder_set_progress_cb(VALUE self, VALUE callback) {
702
+ yajl_encoder_wrapper * wrapper;
703
+ GetEncoder(self, wrapper);
704
+ wrapper->on_progress_callback = callback;
705
+ return Qnil;
706
+ }
707
+
708
+
709
+ /* JSON Gem compatibility */
710
+
711
+ /*
712
+ * Document-class: Hash
713
+ */
714
+ /*
715
+ * Document-method: to_json
716
+ *
717
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
718
+ *
719
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
720
+ *
721
+ * Encodes an instance of Hash to JSON
722
+ */
723
+ static VALUE rb_yajl_json_ext_hash_to_json(int argc, VALUE * argv, VALUE self) {
724
+ YAJL_RB_TO_JSON;
725
+ }
726
+
727
+ /*
728
+ * Document-class: Array
729
+ */
730
+ /*
731
+ * Document-method: to_json
732
+ *
733
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
734
+ *
735
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
736
+ *
737
+ * Encodes an instance of Array to JSON
738
+ */
739
+ static VALUE rb_yajl_json_ext_array_to_json(int argc, VALUE * argv, VALUE self) {
740
+ YAJL_RB_TO_JSON;
741
+ }
742
+
743
+ /*
744
+ * Document-class: Fixnum
745
+ */
746
+ /*
747
+ * Document-method: to_json
748
+ *
749
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
750
+ *
751
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
752
+ *
753
+ * Encodes an instance of Fixnum to JSON
754
+ */
755
+ static VALUE rb_yajl_json_ext_fixnum_to_json(int argc, VALUE * argv, VALUE self) {
756
+ YAJL_RB_TO_JSON;
757
+ }
758
+
759
+ /*
760
+ * Document-class: Float
761
+ */
762
+ /*
763
+ * Document-method: to_json
764
+ *
765
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
766
+ *
767
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
768
+ *
769
+ * Encodes an instance of Float to JSON
770
+ */
771
+ static VALUE rb_yajl_json_ext_float_to_json(int argc, VALUE * argv, VALUE self) {
772
+ YAJL_RB_TO_JSON;
773
+ }
774
+
775
+ /*
776
+ * Document-class: String
777
+ */
778
+ /*
779
+ * Document-method: to_json
780
+ *
781
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
782
+ *
783
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
784
+ *
785
+ * Encodes an instance of TrueClass to JSON
786
+ */
787
+ static VALUE rb_yajl_json_ext_string_to_json(int argc, VALUE * argv, VALUE self) {
788
+ YAJL_RB_TO_JSON;
789
+ }
790
+
791
+ /*
792
+ * Document-class: TrueClass
793
+ */
794
+ /*
795
+ * Document-method: to_json
796
+ *
797
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
798
+ *
799
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
800
+ *
801
+ * Encodes an instance of TrueClass to JSON
802
+ */
803
+ static VALUE rb_yajl_json_ext_true_to_json(int argc, VALUE * argv, VALUE self) {
804
+ YAJL_RB_TO_JSON;
805
+ }
806
+
807
+ /*
808
+ * Document-class: FalseClass
809
+ */
810
+ /*
811
+ * Document-method: to_json
812
+ *
813
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
814
+ *
815
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
816
+ *
817
+ * Encodes an instance of FalseClass to JSON
818
+ */
819
+ static VALUE rb_yajl_json_ext_false_to_json(int argc, VALUE * argv, VALUE self) {
820
+ YAJL_RB_TO_JSON;
821
+ }
822
+
823
+ /*
824
+ * Document-class: NilClass
825
+ */
826
+ /*
827
+ * Document-method: to_json
828
+ *
829
+ * call-seq: to_json(encoder=Yajl::Encoder.new)
830
+ *
831
+ * +encoder+ is an existing Yajl::Encoder used to encode JSON
832
+ *
833
+ * Encodes an instance of NilClass to JSON
834
+ */
835
+ static VALUE rb_yajl_json_ext_nil_to_json(int argc, VALUE * argv, VALUE self) {
836
+ YAJL_RB_TO_JSON;
837
+ }
838
+
839
+ /*
840
+ * Document-class: Yajl::Encoder
841
+ */
842
+ /*
843
+ * Document-method: enable_json_gem_compatability
844
+ *
845
+ * call-seq: enable_json_gem_compatability
846
+ *
847
+ * Enables the JSON gem compatibility API
848
+ */
849
+ static VALUE rb_yajl_encoder_enable_json_gem_ext(VALUE klass) {
850
+ rb_define_method(rb_cHash, "to_json", rb_yajl_json_ext_hash_to_json, -1);
851
+ rb_define_method(rb_cArray, "to_json", rb_yajl_json_ext_array_to_json, -1);
852
+ rb_define_method(rb_cFixnum, "to_json", rb_yajl_json_ext_fixnum_to_json, -1);
853
+ rb_define_method(rb_cFloat, "to_json", rb_yajl_json_ext_float_to_json, -1);
854
+ rb_define_method(rb_cString, "to_json", rb_yajl_json_ext_string_to_json, -1);
855
+ rb_define_method(rb_cTrueClass, "to_json", rb_yajl_json_ext_true_to_json, -1);
856
+ rb_define_method(rb_cFalseClass, "to_json", rb_yajl_json_ext_false_to_json, -1);
857
+ rb_define_method(rb_cNilClass, "to_json", rb_yajl_json_ext_nil_to_json, -1);
858
+ return Qnil;
859
+ }
860
+
861
+
862
+ /* Ruby Extension initializer */
863
+ void Init_yajl() {
864
+ mYajl = rb_define_module("Yajl");
865
+
866
+ cParseError = rb_define_class_under(mYajl, "ParseError", rb_eStandardError);
867
+ cEncodeError = rb_define_class_under(mYajl, "EncodeError", rb_eStandardError);
868
+
869
+ cParser = rb_define_class_under(mYajl, "Parser", rb_cObject);
870
+ rb_define_singleton_method(cParser, "new", rb_yajl_parser_new, -1);
871
+ rb_define_method(cParser, "initialize", rb_yajl_parser_init, -1);
872
+ rb_define_method(cParser, "parse", rb_yajl_parser_parse, -1);
873
+ rb_define_method(cParser, "parse_chunk", rb_yajl_parser_parse_chunk, 1);
874
+ rb_define_method(cParser, "<<", rb_yajl_parser_parse_chunk, 1);
875
+ rb_define_method(cParser, "on_parse_complete=", rb_yajl_parser_set_complete_cb, 1);
876
+
877
+ cEncoder = rb_define_class_under(mYajl, "Encoder", rb_cObject);
878
+ rb_define_singleton_method(cEncoder, "new", rb_yajl_encoder_new, -1);
879
+ rb_define_method(cEncoder, "initialize", rb_yajl_encoder_init, -1);
880
+ rb_define_method(cEncoder, "encode", rb_yajl_encoder_encode, -1);
881
+ rb_define_method(cEncoder, "on_progress=", rb_yajl_encoder_set_progress_cb, 1);
882
+
883
+ rb_define_singleton_method(cEncoder, "enable_json_gem_compatability", rb_yajl_encoder_enable_json_gem_ext, 0);
884
+
885
+ intern_io_read = rb_intern("read");
886
+ intern_call = rb_intern("call");
887
+ intern_keys = rb_intern("keys");
888
+ intern_to_s = rb_intern("to_s");
889
+ intern_to_json = rb_intern("to_json");
890
+ intern_to_sym = rb_intern("to_sym");
891
+ intern_has_key = rb_intern("has_key?");
892
+ intern_as_json = rb_intern("as_json");
893
+
894
+ sym_allow_comments = ID2SYM(rb_intern("allow_comments"));
895
+ sym_check_utf8 = ID2SYM(rb_intern("check_utf8"));
896
+ sym_pretty = ID2SYM(rb_intern("pretty"));
897
+ sym_indent = ID2SYM(rb_intern("indent"));
898
+ sym_html_safe = ID2SYM(rb_intern("html_safe"));
899
+ sym_terminator = ID2SYM(rb_intern("terminator"));
900
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
901
+
902
+ #ifdef HAVE_RUBY_ENCODING_H
903
+ utf8Encoding = rb_utf8_encoding();
904
+ #endif
905
+ }