oj 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/ext/oj/buf.h +103 -0
- data/ext/oj/cache8.c +107 -0
- data/ext/oj/cache8.h +48 -0
- data/ext/oj/circarray.c +68 -0
- data/ext/oj/circarray.h +23 -0
- data/ext/oj/code.c +235 -0
- data/ext/oj/code.h +42 -0
- data/ext/oj/compat.c +299 -0
- data/ext/oj/custom.c +1191 -0
- data/ext/oj/dump.c +1252 -0
- data/ext/oj/dump.h +96 -0
- data/ext/oj/dump_compat.c +977 -0
- data/ext/oj/dump_leaf.c +252 -0
- data/ext/oj/dump_object.c +837 -0
- data/ext/oj/dump_strict.c +433 -0
- data/ext/oj/encode.h +45 -0
- data/ext/oj/err.c +57 -0
- data/ext/oj/err.h +70 -0
- data/ext/oj/extconf.rb +47 -0
- data/ext/oj/fast.c +1771 -0
- data/ext/oj/hash.c +163 -0
- data/ext/oj/hash.h +46 -0
- data/ext/oj/hash_test.c +512 -0
- data/ext/oj/mimic_json.c +878 -0
- data/ext/oj/object.c +771 -0
- data/ext/oj/odd.c +231 -0
- data/ext/oj/odd.h +44 -0
- data/ext/oj/oj.c +1704 -0
- data/ext/oj/oj.h +385 -0
- data/ext/oj/parse.c +1086 -0
- data/ext/oj/parse.h +111 -0
- data/ext/oj/rails.c +1493 -0
- data/ext/oj/rails.h +21 -0
- data/ext/oj/reader.c +231 -0
- data/ext/oj/reader.h +151 -0
- data/ext/oj/resolve.c +102 -0
- data/ext/oj/resolve.h +14 -0
- data/ext/oj/rxclass.c +147 -0
- data/ext/oj/rxclass.h +27 -0
- data/ext/oj/saj.c +714 -0
- data/ext/oj/scp.c +224 -0
- data/ext/oj/sparse.c +910 -0
- data/ext/oj/stream_writer.c +363 -0
- data/ext/oj/strict.c +212 -0
- data/ext/oj/string_writer.c +534 -0
- data/ext/oj/trace.c +79 -0
- data/ext/oj/trace.h +28 -0
- data/ext/oj/util.c +136 -0
- data/ext/oj/util.h +19 -0
- data/ext/oj/val_stack.c +118 -0
- data/ext/oj/val_stack.h +185 -0
- data/ext/oj/wab.c +631 -0
- data/lib/oj.rb +21 -0
- data/lib/oj/active_support_helper.rb +41 -0
- data/lib/oj/bag.rb +88 -0
- data/lib/oj/easy_hash.rb +52 -0
- data/lib/oj/error.rb +22 -0
- data/lib/oj/json.rb +176 -0
- data/lib/oj/mimic.rb +267 -0
- data/lib/oj/saj.rb +66 -0
- data/lib/oj/schandler.rb +142 -0
- data/lib/oj/state.rb +131 -0
- data/lib/oj/version.rb +5 -0
- data/pages/Advanced.md +22 -0
- data/pages/Compatibility.md +25 -0
- data/pages/Custom.md +23 -0
- data/pages/Encoding.md +65 -0
- data/pages/JsonGem.md +79 -0
- data/pages/Modes.md +155 -0
- data/pages/Options.md +283 -0
- data/pages/Rails.md +116 -0
- data/pages/Security.md +20 -0
- data/pages/WAB.md +13 -0
- data/test/_test_active.rb +76 -0
- data/test/_test_active_mimic.rb +96 -0
- data/test/_test_mimic_rails.rb +126 -0
- data/test/activerecord/result_test.rb +27 -0
- data/test/activesupport4/decoding_test.rb +108 -0
- data/test/activesupport4/encoding_test.rb +531 -0
- data/test/activesupport4/test_helper.rb +41 -0
- data/test/activesupport5/decoding_test.rb +125 -0
- data/test/activesupport5/encoding_test.rb +485 -0
- data/test/activesupport5/encoding_test_cases.rb +90 -0
- data/test/activesupport5/test_helper.rb +50 -0
- data/test/activesupport5/time_zone_test_helpers.rb +24 -0
- data/test/bar.rb +25 -0
- data/test/files.rb +29 -0
- data/test/foo.rb +167 -0
- data/test/helper.rb +26 -0
- data/test/isolated/shared.rb +308 -0
- data/test/isolated/test_mimic_after.rb +13 -0
- data/test/isolated/test_mimic_alone.rb +12 -0
- data/test/isolated/test_mimic_as_json.rb +45 -0
- data/test/isolated/test_mimic_before.rb +13 -0
- data/test/isolated/test_mimic_define.rb +28 -0
- data/test/isolated/test_mimic_rails_after.rb +22 -0
- data/test/isolated/test_mimic_rails_before.rb +21 -0
- data/test/isolated/test_mimic_redefine.rb +15 -0
- data/test/json_gem/json_addition_test.rb +216 -0
- data/test/json_gem/json_common_interface_test.rb +148 -0
- data/test/json_gem/json_encoding_test.rb +107 -0
- data/test/json_gem/json_ext_parser_test.rb +20 -0
- data/test/json_gem/json_fixtures_test.rb +35 -0
- data/test/json_gem/json_generator_test.rb +383 -0
- data/test/json_gem/json_generic_object_test.rb +90 -0
- data/test/json_gem/json_parser_test.rb +470 -0
- data/test/json_gem/json_string_matching_test.rb +42 -0
- data/test/json_gem/test_helper.rb +18 -0
- data/test/perf.rb +107 -0
- data/test/perf_compat.rb +130 -0
- data/test/perf_fast.rb +164 -0
- data/test/perf_file.rb +64 -0
- data/test/perf_object.rb +138 -0
- data/test/perf_saj.rb +109 -0
- data/test/perf_scp.rb +151 -0
- data/test/perf_simple.rb +287 -0
- data/test/perf_strict.rb +145 -0
- data/test/perf_wab.rb +131 -0
- data/test/sample.rb +54 -0
- data/test/sample/change.rb +14 -0
- data/test/sample/dir.rb +19 -0
- data/test/sample/doc.rb +36 -0
- data/test/sample/file.rb +48 -0
- data/test/sample/group.rb +16 -0
- data/test/sample/hasprops.rb +16 -0
- data/test/sample/layer.rb +12 -0
- data/test/sample/line.rb +20 -0
- data/test/sample/oval.rb +10 -0
- data/test/sample/rect.rb +10 -0
- data/test/sample/shape.rb +35 -0
- data/test/sample/text.rb +20 -0
- data/test/sample_json.rb +37 -0
- data/test/test_compat.rb +509 -0
- data/test/test_custom.rb +503 -0
- data/test/test_debian.rb +53 -0
- data/test/test_fast.rb +470 -0
- data/test/test_file.rb +239 -0
- data/test/test_gc.rb +49 -0
- data/test/test_hash.rb +29 -0
- data/test/test_integer_range.rb +73 -0
- data/test/test_null.rb +376 -0
- data/test/test_object.rb +1018 -0
- data/test/test_saj.rb +186 -0
- data/test/test_scp.rb +433 -0
- data/test/test_strict.rb +410 -0
- data/test/test_various.rb +741 -0
- data/test/test_wab.rb +307 -0
- data/test/test_writer.rb +380 -0
- data/test/tests.rb +24 -0
- data/test/tests_mimic.rb +14 -0
- data/test/tests_mimic_addition.rb +7 -0
- data/test/zoo.rb +13 -0
- metadata +359 -0
data/ext/oj/err.c
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
/* err.c
|
2
|
+
* Copyright (c) 2011, Peter Ohler
|
3
|
+
* All rights reserved.
|
4
|
+
*/
|
5
|
+
|
6
|
+
#include <stdarg.h>
|
7
|
+
|
8
|
+
#include "err.h"
|
9
|
+
|
10
|
+
void
|
11
|
+
oj_err_set(Err e, VALUE clas, const char *format, ...) {
|
12
|
+
va_list ap;
|
13
|
+
|
14
|
+
va_start(ap, format);
|
15
|
+
e->clas = clas;
|
16
|
+
vsnprintf(e->msg, sizeof(e->msg) - 1, format, ap);
|
17
|
+
va_end(ap);
|
18
|
+
}
|
19
|
+
|
20
|
+
void
|
21
|
+
oj_err_raise(Err e) {
|
22
|
+
rb_raise(e->clas, "%s", e->msg);
|
23
|
+
}
|
24
|
+
|
25
|
+
void
|
26
|
+
_oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char* file, int line) {
|
27
|
+
int n = 1;
|
28
|
+
int col = 1;
|
29
|
+
|
30
|
+
for (; json < current && '\n' != *current; current--) {
|
31
|
+
col++;
|
32
|
+
}
|
33
|
+
for (; json < current; current--) {
|
34
|
+
if ('\n' == *current) {
|
35
|
+
n++;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
oj_err_set(err, eclas, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line);
|
39
|
+
}
|
40
|
+
|
41
|
+
void
|
42
|
+
_oj_raise_error(const char *msg, const char *json, const char *current, const char* file, int line) {
|
43
|
+
struct _err err;
|
44
|
+
int n = 1;
|
45
|
+
int col = 1;
|
46
|
+
|
47
|
+
for (; json < current && '\n' != *current; current--) {
|
48
|
+
col++;
|
49
|
+
}
|
50
|
+
for (; json < current; current--) {
|
51
|
+
if ('\n' == *current) {
|
52
|
+
n++;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
oj_err_set(&err, oj_parse_error_class, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line);
|
56
|
+
rb_raise(err.clas, "%s", err.msg);
|
57
|
+
}
|
data/ext/oj/err.h
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
/* err.h
|
2
|
+
* Copyright (c) 2011, Peter Ohler
|
3
|
+
* All rights reserved.
|
4
|
+
*
|
5
|
+
* Redistribution and use in source and binary forms, with or without
|
6
|
+
* modification, are permitted provided that the following conditions are met:
|
7
|
+
*
|
8
|
+
* - Redistributions of source code must retain the above copyright notice, this
|
9
|
+
* list of conditions and the following disclaimer.
|
10
|
+
*
|
11
|
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer in the documentation
|
13
|
+
* and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* - Neither the name of Peter Ohler nor the names of its contributors may be
|
16
|
+
* used to endorse or promote products derived from this software without
|
17
|
+
* specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#ifndef OJ_ERR_H
|
32
|
+
#define OJ_ERR_H
|
33
|
+
|
34
|
+
#include "ruby.h"
|
35
|
+
// Needed to silence 2.4.0 warnings.
|
36
|
+
#ifndef NORETURN
|
37
|
+
# define NORETURN(x) x
|
38
|
+
#endif
|
39
|
+
|
40
|
+
#define set_error(err, eclas, msg, json, current) _oj_err_set_with_location(err, eclas, msg, json, current, FILE, LINE)
|
41
|
+
|
42
|
+
typedef struct _err {
|
43
|
+
VALUE clas;
|
44
|
+
char msg[128];
|
45
|
+
} *Err;
|
46
|
+
|
47
|
+
extern VALUE oj_parse_error_class;
|
48
|
+
|
49
|
+
extern void oj_err_set(Err e, VALUE clas, const char *format, ...);
|
50
|
+
extern void _oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char* file, int line);
|
51
|
+
|
52
|
+
NORETURN(extern void oj_err_raise(Err e));
|
53
|
+
|
54
|
+
#define raise_error(msg, json, current) _oj_raise_error(msg, json, current, __FILE__, __LINE__)
|
55
|
+
|
56
|
+
NORETURN(extern void _oj_raise_error(const char *msg, const char *json, const char *current, const char* file, int line));
|
57
|
+
|
58
|
+
|
59
|
+
inline static void
|
60
|
+
err_init(Err e) {
|
61
|
+
e->clas = Qnil;
|
62
|
+
*e->msg = '\0';
|
63
|
+
}
|
64
|
+
|
65
|
+
inline static int
|
66
|
+
err_has(Err e) {
|
67
|
+
return (Qnil != e->clas);
|
68
|
+
}
|
69
|
+
|
70
|
+
#endif /* OJ_ERR_H */
|
data/ext/oj/extconf.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
extension_name = 'oj'
|
5
|
+
dir_config(extension_name)
|
6
|
+
|
7
|
+
parts = RUBY_DESCRIPTION.split(' ')
|
8
|
+
type = parts[0]
|
9
|
+
type = type[4..-1] if type.start_with?('tcs-')
|
10
|
+
is_windows = RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/
|
11
|
+
platform = RUBY_PLATFORM
|
12
|
+
version = RUBY_VERSION.split('.')
|
13
|
+
puts ">>>>> Creating Makefile for #{type} version #{RUBY_VERSION} on #{platform} <<<<<"
|
14
|
+
|
15
|
+
dflags = {
|
16
|
+
'RUBY_TYPE' => type,
|
17
|
+
(type.upcase + '_RUBY') => nil,
|
18
|
+
'RUBY_VERSION' => RUBY_VERSION,
|
19
|
+
'RUBY_VERSION_MAJOR' => version[0],
|
20
|
+
'RUBY_VERSION_MINOR' => version[1],
|
21
|
+
'RUBY_VERSION_MICRO' => version[2],
|
22
|
+
'IS_WINDOWS' => is_windows ? 1 : 0,
|
23
|
+
'RSTRUCT_LEN_RETURNS_INTEGER_OBJECT' => ('ruby' == type && '2' == version[0] && '4' == version[1] && '1' >= version[2]) ? 1 : 0,
|
24
|
+
}
|
25
|
+
|
26
|
+
have_func('rb_time_timespec')
|
27
|
+
have_func('rb_ivar_count')
|
28
|
+
have_func('rb_ivar_foreach')
|
29
|
+
have_func('stpcpy')
|
30
|
+
have_func('rb_data_object_wrap')
|
31
|
+
|
32
|
+
dflags['OJ_DEBUG'] = true unless ENV['OJ_DEBUG'].nil?
|
33
|
+
|
34
|
+
dflags.each do |k,v|
|
35
|
+
if v.nil?
|
36
|
+
$CPPFLAGS += " -D#{k}"
|
37
|
+
else
|
38
|
+
$CPPFLAGS += " -D#{k}=#{v}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
$CPPFLAGS += ' -Wall'
|
43
|
+
#puts "*** $CPPFLAGS: #{$CPPFLAGS}"
|
44
|
+
|
45
|
+
create_makefile(File.join(extension_name, extension_name))
|
46
|
+
|
47
|
+
%x{make clean}
|
data/ext/oj/fast.c
ADDED
@@ -0,0 +1,1771 @@
|
|
1
|
+
/* fast.c
|
2
|
+
* Copyright (c) 2012, Peter Ohler
|
3
|
+
* All rights reserved.
|
4
|
+
*
|
5
|
+
* Redistribution and use in source and binary forms, with or without
|
6
|
+
* modification, are permitted provided that the following conditions are met:
|
7
|
+
*
|
8
|
+
* - Redistributions of source code must retain the above copyright notice, this
|
9
|
+
* list of conditions and the following disclaimer.
|
10
|
+
*
|
11
|
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer in the documentation
|
13
|
+
* and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* - Neither the name of Peter Ohler nor the names of its contributors may be
|
16
|
+
* used to endorse or promote products derived from this software without
|
17
|
+
* specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
31
|
+
#if !IS_WINDOWS
|
32
|
+
#include <sys/resource.h> // for getrlimit() on linux
|
33
|
+
#endif
|
34
|
+
#include <stdlib.h>
|
35
|
+
#include <stdio.h>
|
36
|
+
#include <string.h>
|
37
|
+
#include <math.h>
|
38
|
+
#include <errno.h>
|
39
|
+
|
40
|
+
#include "oj.h"
|
41
|
+
#include "encode.h"
|
42
|
+
|
43
|
+
// maximum to allocate on the stack, arbitrary limit
|
44
|
+
#define SMALL_XML 65536
|
45
|
+
#define MAX_STACK 100
|
46
|
+
//#define BATCH_SIZE (4096 / sizeof(struct _leaf) - 1)
|
47
|
+
#define BATCH_SIZE 100
|
48
|
+
|
49
|
+
typedef struct _batch {
|
50
|
+
struct _batch *next;
|
51
|
+
int next_avail;
|
52
|
+
struct _leaf leaves[BATCH_SIZE];
|
53
|
+
} *Batch;
|
54
|
+
|
55
|
+
typedef struct _doc {
|
56
|
+
Leaf data;
|
57
|
+
Leaf *where; // points to current location
|
58
|
+
Leaf where_path[MAX_STACK]; // points to head of path
|
59
|
+
char *json;
|
60
|
+
unsigned long size; // number of leaves/branches in the doc
|
61
|
+
VALUE self;
|
62
|
+
Batch batches;
|
63
|
+
struct _batch batch0;
|
64
|
+
} *Doc;
|
65
|
+
|
66
|
+
typedef struct _parseInfo {
|
67
|
+
char *str; /* buffer being read from */
|
68
|
+
char *s; /* current position in buffer */
|
69
|
+
Doc doc;
|
70
|
+
void *stack_min;
|
71
|
+
} *ParseInfo;
|
72
|
+
|
73
|
+
static void leaf_init(Leaf leaf, int type);
|
74
|
+
static Leaf leaf_new(Doc doc, int type);
|
75
|
+
static void leaf_append_element(Leaf parent, Leaf element);
|
76
|
+
static VALUE leaf_value(Doc doc, Leaf leaf);
|
77
|
+
static void leaf_fixnum_value(Leaf leaf);
|
78
|
+
static void leaf_float_value(Leaf leaf);
|
79
|
+
static VALUE leaf_array_value(Doc doc, Leaf leaf);
|
80
|
+
static VALUE leaf_hash_value(Doc doc, Leaf leaf);
|
81
|
+
|
82
|
+
static Leaf read_next(ParseInfo pi);
|
83
|
+
static Leaf read_obj(ParseInfo pi);
|
84
|
+
static Leaf read_array(ParseInfo pi);
|
85
|
+
static Leaf read_str(ParseInfo pi);
|
86
|
+
static Leaf read_num(ParseInfo pi);
|
87
|
+
static Leaf read_true(ParseInfo pi);
|
88
|
+
static Leaf read_false(ParseInfo pi);
|
89
|
+
static Leaf read_nil(ParseInfo pi);
|
90
|
+
static void next_non_white(ParseInfo pi);
|
91
|
+
static char* read_quoted_value(ParseInfo pi);
|
92
|
+
static void skip_comment(ParseInfo pi);
|
93
|
+
|
94
|
+
static VALUE protect_open_proc(VALUE x);
|
95
|
+
static VALUE parse_json(VALUE clas, char *json, bool given, bool allocated);
|
96
|
+
static void each_leaf(Doc doc, VALUE self);
|
97
|
+
static int move_step(Doc doc, const char *path, int loc);
|
98
|
+
static Leaf get_doc_leaf(Doc doc, const char *path);
|
99
|
+
static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path);
|
100
|
+
static void each_value(Doc doc, Leaf leaf);
|
101
|
+
|
102
|
+
static void doc_init(Doc doc);
|
103
|
+
static void doc_free(Doc doc);
|
104
|
+
static VALUE doc_open(VALUE clas, VALUE str);
|
105
|
+
static VALUE doc_open_file(VALUE clas, VALUE filename);
|
106
|
+
static VALUE doc_where(VALUE self);
|
107
|
+
static VALUE doc_local_key(VALUE self);
|
108
|
+
static VALUE doc_home(VALUE self);
|
109
|
+
static VALUE doc_type(int argc, VALUE *argv, VALUE self);
|
110
|
+
static VALUE doc_fetch(int argc, VALUE *argv, VALUE self);
|
111
|
+
static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self);
|
112
|
+
static VALUE doc_move(VALUE self, VALUE str);
|
113
|
+
static VALUE doc_each_child(int argc, VALUE *argv, VALUE self);
|
114
|
+
static VALUE doc_each_value(int argc, VALUE *argv, VALUE self);
|
115
|
+
static VALUE doc_dump(int argc, VALUE *argv, VALUE self);
|
116
|
+
static VALUE doc_size(VALUE self);
|
117
|
+
|
118
|
+
VALUE oj_doc_class = Qundef;
|
119
|
+
|
120
|
+
// This is only for CentOS 5.4 with Ruby 1.9.3-p0.
|
121
|
+
#ifndef HAVE_STPCPY
|
122
|
+
char *stpcpy(char *dest, const char *src) {
|
123
|
+
size_t cnt = strlen(src);
|
124
|
+
|
125
|
+
strcpy(dest, src);
|
126
|
+
|
127
|
+
return dest + cnt;
|
128
|
+
}
|
129
|
+
#endif
|
130
|
+
|
131
|
+
inline static void
|
132
|
+
next_non_white(ParseInfo pi) {
|
133
|
+
for (; 1; pi->s++) {
|
134
|
+
switch(*pi->s) {
|
135
|
+
case ' ':
|
136
|
+
case '\t':
|
137
|
+
case '\f':
|
138
|
+
case '\n':
|
139
|
+
case '\r':
|
140
|
+
break;
|
141
|
+
case '/':
|
142
|
+
skip_comment(pi);
|
143
|
+
break;
|
144
|
+
default:
|
145
|
+
return;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
inline static char*
|
151
|
+
ulong_fill(char *s, size_t num) {
|
152
|
+
char buf[32];
|
153
|
+
char *b = buf + sizeof(buf) - 1;
|
154
|
+
|
155
|
+
*b-- = '\0';
|
156
|
+
for (; 0 < num; num /= 10, b--) {
|
157
|
+
*b = (num % 10) + '0';
|
158
|
+
}
|
159
|
+
b++;
|
160
|
+
if ('\0' == *b) {
|
161
|
+
b--;
|
162
|
+
*b = '0';
|
163
|
+
}
|
164
|
+
for (; '\0' != *b; b++, s++) {
|
165
|
+
*s = *b;
|
166
|
+
}
|
167
|
+
return s;
|
168
|
+
}
|
169
|
+
|
170
|
+
inline static void
|
171
|
+
leaf_init(Leaf leaf, int type) {
|
172
|
+
leaf->next = 0;
|
173
|
+
leaf->rtype = type;
|
174
|
+
leaf->parent_type = T_NONE;
|
175
|
+
switch (type) {
|
176
|
+
case T_ARRAY:
|
177
|
+
case T_HASH:
|
178
|
+
leaf->elements = 0;
|
179
|
+
leaf->value_type = COL_VAL;
|
180
|
+
break;
|
181
|
+
case T_NIL:
|
182
|
+
leaf->value = Qnil;
|
183
|
+
leaf->value_type = RUBY_VAL;
|
184
|
+
break;
|
185
|
+
case T_TRUE:
|
186
|
+
leaf->value = Qtrue;
|
187
|
+
leaf->value_type = RUBY_VAL;
|
188
|
+
break;
|
189
|
+
case T_FALSE:
|
190
|
+
leaf->value = Qfalse;
|
191
|
+
leaf->value_type = RUBY_VAL;
|
192
|
+
break;
|
193
|
+
case T_FIXNUM:
|
194
|
+
case T_FLOAT:
|
195
|
+
case T_STRING:
|
196
|
+
default:
|
197
|
+
leaf->value_type = STR_VAL;
|
198
|
+
break;
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
inline static Leaf
|
203
|
+
leaf_new(Doc doc, int type) {
|
204
|
+
Leaf leaf;
|
205
|
+
|
206
|
+
if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) {
|
207
|
+
Batch b = ALLOC(struct _batch);
|
208
|
+
|
209
|
+
// Initializes all leaves with a NO_VAL value_type
|
210
|
+
memset(b, 0, sizeof(struct _batch));
|
211
|
+
b->next = doc->batches;
|
212
|
+
doc->batches = b;
|
213
|
+
b->next_avail = 0;
|
214
|
+
}
|
215
|
+
leaf = &doc->batches->leaves[doc->batches->next_avail];
|
216
|
+
doc->batches->next_avail++;
|
217
|
+
leaf_init(leaf, type);
|
218
|
+
|
219
|
+
return leaf;
|
220
|
+
}
|
221
|
+
|
222
|
+
inline static void
|
223
|
+
leaf_append_element(Leaf parent, Leaf element) {
|
224
|
+
if (0 == parent->elements) {
|
225
|
+
parent->elements = element;
|
226
|
+
element->next = element;
|
227
|
+
} else {
|
228
|
+
element->next = parent->elements->next;
|
229
|
+
parent->elements->next = element;
|
230
|
+
parent->elements = element;
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
static VALUE
|
235
|
+
leaf_value(Doc doc, Leaf leaf) {
|
236
|
+
if (RUBY_VAL != leaf->value_type) {
|
237
|
+
switch (leaf->rtype) {
|
238
|
+
case T_NIL:
|
239
|
+
leaf->value = Qnil;
|
240
|
+
break;
|
241
|
+
case T_TRUE:
|
242
|
+
leaf->value = Qtrue;
|
243
|
+
break;
|
244
|
+
case T_FALSE:
|
245
|
+
leaf->value = Qfalse;
|
246
|
+
break;
|
247
|
+
case T_FIXNUM:
|
248
|
+
leaf_fixnum_value(leaf);
|
249
|
+
break;
|
250
|
+
case T_FLOAT:
|
251
|
+
leaf_float_value(leaf);
|
252
|
+
break;
|
253
|
+
case T_STRING:
|
254
|
+
leaf->value = rb_str_new2(leaf->str);
|
255
|
+
leaf->value = oj_encode(leaf->value);
|
256
|
+
leaf->value_type = RUBY_VAL;
|
257
|
+
break;
|
258
|
+
case T_ARRAY:
|
259
|
+
return leaf_array_value(doc, leaf);
|
260
|
+
break;
|
261
|
+
case T_HASH:
|
262
|
+
return leaf_hash_value(doc, leaf);
|
263
|
+
break;
|
264
|
+
default:
|
265
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->rtype);
|
266
|
+
break;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
return leaf->value;
|
270
|
+
}
|
271
|
+
|
272
|
+
inline static Doc
|
273
|
+
self_doc(VALUE self) {
|
274
|
+
Doc doc = DATA_PTR(self);
|
275
|
+
|
276
|
+
if (0 == doc) {
|
277
|
+
rb_raise(rb_eIOError, "Document already closed or not open.");
|
278
|
+
}
|
279
|
+
return doc;
|
280
|
+
}
|
281
|
+
|
282
|
+
static void
|
283
|
+
skip_comment(ParseInfo pi) {
|
284
|
+
pi->s++; // skip first /
|
285
|
+
if ('*' == *pi->s) {
|
286
|
+
pi->s++;
|
287
|
+
for (; '\0' != *pi->s; pi->s++) {
|
288
|
+
if ('*' == *pi->s && '/' == *(pi->s + 1)) {
|
289
|
+
pi->s++;
|
290
|
+
return;
|
291
|
+
} else if ('\0' == *pi->s) {
|
292
|
+
raise_error("comment not terminated", pi->str, pi->s);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
} else if ('/' == *pi->s) {
|
296
|
+
for (; 1; pi->s++) {
|
297
|
+
switch (*pi->s) {
|
298
|
+
case '\n':
|
299
|
+
case '\r':
|
300
|
+
case '\f':
|
301
|
+
case '\0':
|
302
|
+
return;
|
303
|
+
default:
|
304
|
+
break;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
} else {
|
308
|
+
raise_error("invalid comment", pi->str, pi->s);
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
#ifdef RUBINIUS_RUBY
|
313
|
+
#define NUM_MAX 0x07FFFFFF
|
314
|
+
#else
|
315
|
+
#define NUM_MAX (FIXNUM_MAX >> 8)
|
316
|
+
#endif
|
317
|
+
|
318
|
+
|
319
|
+
static void
|
320
|
+
leaf_fixnum_value(Leaf leaf) {
|
321
|
+
char *s = leaf->str;
|
322
|
+
int64_t n = 0;
|
323
|
+
int neg = 0;
|
324
|
+
int big = 0;
|
325
|
+
|
326
|
+
if ('-' == *s) {
|
327
|
+
s++;
|
328
|
+
neg = 1;
|
329
|
+
} else if ('+' == *s) {
|
330
|
+
s++;
|
331
|
+
}
|
332
|
+
for (; '0' <= *s && *s <= '9'; s++) {
|
333
|
+
n = n * 10 + (*s - '0');
|
334
|
+
if (NUM_MAX <= n) {
|
335
|
+
big = 1;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
if (big) {
|
339
|
+
char c = *s;
|
340
|
+
|
341
|
+
*s = '\0';
|
342
|
+
leaf->value = rb_cstr_to_inum(leaf->str, 10, 0);
|
343
|
+
*s = c;
|
344
|
+
} else {
|
345
|
+
if (neg) {
|
346
|
+
n = -n;
|
347
|
+
}
|
348
|
+
leaf->value = rb_ll2inum(n);
|
349
|
+
}
|
350
|
+
leaf->value_type = RUBY_VAL;
|
351
|
+
}
|
352
|
+
|
353
|
+
static void
|
354
|
+
leaf_float_value(Leaf leaf) {
|
355
|
+
leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1));
|
356
|
+
leaf->value_type = RUBY_VAL;
|
357
|
+
}
|
358
|
+
|
359
|
+
static VALUE
|
360
|
+
leaf_array_value(Doc doc, Leaf leaf) {
|
361
|
+
volatile VALUE a = rb_ary_new();
|
362
|
+
|
363
|
+
if (0 != leaf->elements) {
|
364
|
+
Leaf first = leaf->elements->next;
|
365
|
+
Leaf e = first;
|
366
|
+
|
367
|
+
do {
|
368
|
+
rb_ary_push(a, leaf_value(doc, e));
|
369
|
+
e = e->next;
|
370
|
+
} while (e != first);
|
371
|
+
}
|
372
|
+
return a;
|
373
|
+
}
|
374
|
+
|
375
|
+
static VALUE
|
376
|
+
leaf_hash_value(Doc doc, Leaf leaf) {
|
377
|
+
volatile VALUE h = rb_hash_new();
|
378
|
+
|
379
|
+
if (0 != leaf->elements) {
|
380
|
+
Leaf first = leaf->elements->next;
|
381
|
+
Leaf e = first;
|
382
|
+
volatile VALUE key;
|
383
|
+
|
384
|
+
do {
|
385
|
+
key = rb_str_new2(e->key);
|
386
|
+
key = oj_encode(key);
|
387
|
+
rb_hash_aset(h, key, leaf_value(doc, e));
|
388
|
+
e = e->next;
|
389
|
+
} while (e != first);
|
390
|
+
}
|
391
|
+
return h;
|
392
|
+
}
|
393
|
+
|
394
|
+
static Leaf
|
395
|
+
read_next(ParseInfo pi) {
|
396
|
+
Leaf leaf = 0;
|
397
|
+
|
398
|
+
if ((void*)&leaf < pi->stack_min) {
|
399
|
+
rb_raise(rb_eSysStackError, "JSON is too deeply nested");
|
400
|
+
}
|
401
|
+
next_non_white(pi); // skip white space
|
402
|
+
switch (*pi->s) {
|
403
|
+
case '{':
|
404
|
+
leaf = read_obj(pi);
|
405
|
+
break;
|
406
|
+
case '[':
|
407
|
+
leaf = read_array(pi);
|
408
|
+
break;
|
409
|
+
case '"':
|
410
|
+
leaf = read_str(pi);
|
411
|
+
break;
|
412
|
+
case '+':
|
413
|
+
case '-':
|
414
|
+
case '0':
|
415
|
+
case '1':
|
416
|
+
case '2':
|
417
|
+
case '3':
|
418
|
+
case '4':
|
419
|
+
case '5':
|
420
|
+
case '6':
|
421
|
+
case '7':
|
422
|
+
case '8':
|
423
|
+
case '9':
|
424
|
+
leaf = read_num(pi);
|
425
|
+
break;
|
426
|
+
case 't':
|
427
|
+
leaf = read_true(pi);
|
428
|
+
break;
|
429
|
+
case 'f':
|
430
|
+
leaf = read_false(pi);
|
431
|
+
break;
|
432
|
+
case 'n':
|
433
|
+
leaf = read_nil(pi);
|
434
|
+
break;
|
435
|
+
case '\0':
|
436
|
+
default:
|
437
|
+
break; // returns 0
|
438
|
+
}
|
439
|
+
pi->doc->size++;
|
440
|
+
|
441
|
+
return leaf;
|
442
|
+
}
|
443
|
+
|
444
|
+
static Leaf
|
445
|
+
read_obj(ParseInfo pi) {
|
446
|
+
Leaf h = leaf_new(pi->doc, T_HASH);
|
447
|
+
char *end;
|
448
|
+
const char *key = 0;
|
449
|
+
Leaf val = 0;
|
450
|
+
|
451
|
+
pi->s++;
|
452
|
+
next_non_white(pi);
|
453
|
+
if ('}' == *pi->s) {
|
454
|
+
pi->s++;
|
455
|
+
return h;
|
456
|
+
}
|
457
|
+
while (1) {
|
458
|
+
next_non_white(pi);
|
459
|
+
key = 0;
|
460
|
+
val = 0;
|
461
|
+
if ('"' != *pi->s || 0 == (key = read_quoted_value(pi))) {
|
462
|
+
raise_error("unexpected character", pi->str, pi->s);
|
463
|
+
}
|
464
|
+
next_non_white(pi);
|
465
|
+
if (':' == *pi->s) {
|
466
|
+
pi->s++;
|
467
|
+
} else {
|
468
|
+
raise_error("invalid format, expected :", pi->str, pi->s);
|
469
|
+
}
|
470
|
+
if (0 == (val = read_next(pi))) {
|
471
|
+
//printf("*** '%s'\n", pi->s);
|
472
|
+
raise_error("unexpected character", pi->str, pi->s);
|
473
|
+
}
|
474
|
+
end = pi->s;
|
475
|
+
val->key = key;
|
476
|
+
val->parent_type = T_HASH;
|
477
|
+
leaf_append_element(h, val);
|
478
|
+
next_non_white(pi);
|
479
|
+
if ('}' == *pi->s) {
|
480
|
+
pi->s++;
|
481
|
+
*end = '\0';
|
482
|
+
break;
|
483
|
+
} else if (',' == *pi->s) {
|
484
|
+
pi->s++;
|
485
|
+
} else {
|
486
|
+
//printf("*** '%s'\n", pi->s);
|
487
|
+
raise_error("invalid format, expected , or } while in an object", pi->str, pi->s);
|
488
|
+
}
|
489
|
+
*end = '\0';
|
490
|
+
}
|
491
|
+
return h;
|
492
|
+
}
|
493
|
+
|
494
|
+
static Leaf
|
495
|
+
read_array(ParseInfo pi) {
|
496
|
+
Leaf a = leaf_new(pi->doc, T_ARRAY);
|
497
|
+
Leaf e;
|
498
|
+
char *end;
|
499
|
+
int cnt = 0;
|
500
|
+
|
501
|
+
pi->s++;
|
502
|
+
next_non_white(pi);
|
503
|
+
if (']' == *pi->s) {
|
504
|
+
pi->s++;
|
505
|
+
return a;
|
506
|
+
}
|
507
|
+
while (1) {
|
508
|
+
next_non_white(pi);
|
509
|
+
if (0 == (e = read_next(pi))) {
|
510
|
+
raise_error("unexpected character", pi->str, pi->s);
|
511
|
+
}
|
512
|
+
cnt++;
|
513
|
+
e->index = cnt;
|
514
|
+
e->parent_type = T_ARRAY;
|
515
|
+
leaf_append_element(a, e);
|
516
|
+
end = pi->s;
|
517
|
+
next_non_white(pi);
|
518
|
+
if (',' == *pi->s) {
|
519
|
+
pi->s++;
|
520
|
+
} else if (']' == *pi->s) {
|
521
|
+
pi->s++;
|
522
|
+
*end = '\0';
|
523
|
+
break;
|
524
|
+
} else {
|
525
|
+
raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s);
|
526
|
+
}
|
527
|
+
*end = '\0';
|
528
|
+
}
|
529
|
+
return a;
|
530
|
+
}
|
531
|
+
|
532
|
+
static Leaf
|
533
|
+
read_str(ParseInfo pi) {
|
534
|
+
Leaf leaf = leaf_new(pi->doc, T_STRING);
|
535
|
+
|
536
|
+
leaf->str = read_quoted_value(pi);
|
537
|
+
|
538
|
+
return leaf;
|
539
|
+
}
|
540
|
+
|
541
|
+
static Leaf
|
542
|
+
read_num(ParseInfo pi) {
|
543
|
+
char *start = pi->s;
|
544
|
+
int type = T_FIXNUM;
|
545
|
+
Leaf leaf;
|
546
|
+
|
547
|
+
if ('-' == *pi->s) {
|
548
|
+
pi->s++;
|
549
|
+
}
|
550
|
+
// digits
|
551
|
+
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
552
|
+
}
|
553
|
+
if ('.' == *pi->s) {
|
554
|
+
type = T_FLOAT;
|
555
|
+
pi->s++;
|
556
|
+
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
557
|
+
}
|
558
|
+
}
|
559
|
+
if ('e' == *pi->s || 'E' == *pi->s) {
|
560
|
+
pi->s++;
|
561
|
+
if ('-' == *pi->s || '+' == *pi->s) {
|
562
|
+
pi->s++;
|
563
|
+
}
|
564
|
+
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
565
|
+
}
|
566
|
+
}
|
567
|
+
leaf = leaf_new(pi->doc, type);
|
568
|
+
leaf->str = start;
|
569
|
+
|
570
|
+
return leaf;
|
571
|
+
}
|
572
|
+
|
573
|
+
static Leaf
|
574
|
+
read_true(ParseInfo pi) {
|
575
|
+
Leaf leaf = leaf_new(pi->doc, T_TRUE);
|
576
|
+
|
577
|
+
pi->s++;
|
578
|
+
if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) {
|
579
|
+
raise_error("invalid format, expected 'true'", pi->str, pi->s);
|
580
|
+
}
|
581
|
+
pi->s += 3;
|
582
|
+
|
583
|
+
return leaf;
|
584
|
+
}
|
585
|
+
|
586
|
+
static Leaf
|
587
|
+
read_false(ParseInfo pi) {
|
588
|
+
Leaf leaf = leaf_new(pi->doc, T_FALSE);
|
589
|
+
|
590
|
+
pi->s++;
|
591
|
+
if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) {
|
592
|
+
raise_error("invalid format, expected 'false'", pi->str, pi->s);
|
593
|
+
}
|
594
|
+
pi->s += 4;
|
595
|
+
|
596
|
+
return leaf;
|
597
|
+
}
|
598
|
+
|
599
|
+
static Leaf
|
600
|
+
read_nil(ParseInfo pi) {
|
601
|
+
Leaf leaf = leaf_new(pi->doc, T_NIL);
|
602
|
+
|
603
|
+
pi->s++;
|
604
|
+
if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) {
|
605
|
+
raise_error("invalid format, expected 'nil'", pi->str, pi->s);
|
606
|
+
}
|
607
|
+
pi->s += 3;
|
608
|
+
|
609
|
+
return leaf;
|
610
|
+
}
|
611
|
+
|
612
|
+
static uint32_t
|
613
|
+
read_4hex(ParseInfo pi, const char *h) {
|
614
|
+
uint32_t b = 0;
|
615
|
+
int i;
|
616
|
+
|
617
|
+
for (i = 0; i < 4; i++, h++) {
|
618
|
+
b = b << 4;
|
619
|
+
if ('0' <= *h && *h <= '9') {
|
620
|
+
b += *h - '0';
|
621
|
+
} else if ('A' <= *h && *h <= 'F') {
|
622
|
+
b += *h - 'A' + 10;
|
623
|
+
} else if ('a' <= *h && *h <= 'f') {
|
624
|
+
b += *h - 'a' + 10;
|
625
|
+
} else {
|
626
|
+
raise_error("invalid hex character", pi->str, pi->s);
|
627
|
+
}
|
628
|
+
}
|
629
|
+
return b;
|
630
|
+
}
|
631
|
+
|
632
|
+
static char*
|
633
|
+
unicode_to_chars(ParseInfo pi, char *t, uint32_t code) {
|
634
|
+
if (0x0000007F >= code) {
|
635
|
+
*t++ = (char)code;
|
636
|
+
} else if (0x000007FF >= code) {
|
637
|
+
*t++ = 0xC0 | (code >> 6);
|
638
|
+
*t++ = 0x80 | (0x3F & code);
|
639
|
+
} else if (0x0000FFFF >= code) {
|
640
|
+
*t++ = 0xE0 | (code >> 12);
|
641
|
+
*t++ = 0x80 | ((code >> 6) & 0x3F);
|
642
|
+
*t++ = 0x80 | (0x3F & code);
|
643
|
+
} else if (0x001FFFFF >= code) {
|
644
|
+
*t++ = 0xF0 | (code >> 18);
|
645
|
+
*t++ = 0x80 | ((code >> 12) & 0x3F);
|
646
|
+
*t++ = 0x80 | ((code >> 6) & 0x3F);
|
647
|
+
*t++ = 0x80 | (0x3F & code);
|
648
|
+
} else if (0x03FFFFFF >= code) {
|
649
|
+
*t++ = 0xF8 | (code >> 24);
|
650
|
+
*t++ = 0x80 | ((code >> 18) & 0x3F);
|
651
|
+
*t++ = 0x80 | ((code >> 12) & 0x3F);
|
652
|
+
*t++ = 0x80 | ((code >> 6) & 0x3F);
|
653
|
+
*t++ = 0x80 | (0x3F & code);
|
654
|
+
} else if (0x7FFFFFFF >= code) {
|
655
|
+
*t++ = 0xFC | (code >> 30);
|
656
|
+
*t++ = 0x80 | ((code >> 24) & 0x3F);
|
657
|
+
*t++ = 0x80 | ((code >> 18) & 0x3F);
|
658
|
+
*t++ = 0x80 | ((code >> 12) & 0x3F);
|
659
|
+
*t++ = 0x80 | ((code >> 6) & 0x3F);
|
660
|
+
*t++ = 0x80 | (0x3F & code);
|
661
|
+
} else {
|
662
|
+
raise_error("invalid Unicode character", pi->str, pi->s);
|
663
|
+
}
|
664
|
+
return t;
|
665
|
+
}
|
666
|
+
|
667
|
+
/* Assume the value starts immediately and goes until the quote character is
|
668
|
+
* reached again. Do not read the character after the terminating quote.
|
669
|
+
*/
|
670
|
+
static char*
|
671
|
+
read_quoted_value(ParseInfo pi) {
|
672
|
+
char *value = 0;
|
673
|
+
char *h = pi->s; // head
|
674
|
+
char *t = h; // tail
|
675
|
+
|
676
|
+
h++; // skip quote character
|
677
|
+
t++;
|
678
|
+
value = h;
|
679
|
+
for (; '"' != *h; h++, t++) {
|
680
|
+
if ('\0' == *h) {
|
681
|
+
pi->s = h;
|
682
|
+
raise_error("quoted string not terminated", pi->str, pi->s);
|
683
|
+
} else if ('\\' == *h) {
|
684
|
+
h++;
|
685
|
+
switch (*h) {
|
686
|
+
case 'n': *t = '\n'; break;
|
687
|
+
case 'r': *t = '\r'; break;
|
688
|
+
case 't': *t = '\t'; break;
|
689
|
+
case 'f': *t = '\f'; break;
|
690
|
+
case 'b': *t = '\b'; break;
|
691
|
+
case '"': *t = '"'; break;
|
692
|
+
case '/': *t = '/'; break;
|
693
|
+
case '\\': *t = '\\'; break;
|
694
|
+
case 'u': {
|
695
|
+
uint32_t code;
|
696
|
+
|
697
|
+
h++;
|
698
|
+
code = read_4hex(pi, h);
|
699
|
+
h += 3;
|
700
|
+
if (0x0000D800 <= code && code <= 0x0000DFFF) {
|
701
|
+
uint32_t c1 = (code - 0x0000D800) & 0x000003FF;
|
702
|
+
uint32_t c2;
|
703
|
+
|
704
|
+
h++;
|
705
|
+
if ('\\' != *h || 'u' != *(h + 1)) {
|
706
|
+
pi->s = h;
|
707
|
+
raise_error("invalid escaped character", pi->str, pi->s);
|
708
|
+
}
|
709
|
+
h += 2;
|
710
|
+
c2 = read_4hex(pi, h);
|
711
|
+
h += 3;
|
712
|
+
c2 = (c2 - 0x0000DC00) & 0x000003FF;
|
713
|
+
code = ((c1 << 10) | c2) + 0x00010000;
|
714
|
+
}
|
715
|
+
t = unicode_to_chars(pi, t, code);
|
716
|
+
t--;
|
717
|
+
break;
|
718
|
+
}
|
719
|
+
default:
|
720
|
+
pi->s = h;
|
721
|
+
raise_error("invalid escaped character", pi->str, pi->s);
|
722
|
+
break;
|
723
|
+
}
|
724
|
+
} else if (t != h) {
|
725
|
+
*t = *h;
|
726
|
+
}
|
727
|
+
}
|
728
|
+
*t = '\0'; // terminate value
|
729
|
+
pi->s = h + 1;
|
730
|
+
|
731
|
+
return value;
|
732
|
+
}
|
733
|
+
|
734
|
+
// doc support functions
|
735
|
+
inline static void
|
736
|
+
doc_init(Doc doc) {
|
737
|
+
memset(doc, 0, sizeof(struct _doc));
|
738
|
+
doc->where = doc->where_path;
|
739
|
+
doc->self = Qundef;
|
740
|
+
doc->batches = &doc->batch0;
|
741
|
+
}
|
742
|
+
|
743
|
+
static void
|
744
|
+
doc_free(Doc doc) {
|
745
|
+
if (0 != doc) {
|
746
|
+
Batch b;
|
747
|
+
|
748
|
+
while (0 != (b = doc->batches)) {
|
749
|
+
doc->batches = doc->batches->next;
|
750
|
+
if (&doc->batch0 != b) {
|
751
|
+
xfree(b);
|
752
|
+
}
|
753
|
+
}
|
754
|
+
//xfree(f);
|
755
|
+
}
|
756
|
+
}
|
757
|
+
|
758
|
+
static VALUE
|
759
|
+
protect_open_proc(VALUE x) {
|
760
|
+
ParseInfo pi = (ParseInfo)x;
|
761
|
+
|
762
|
+
pi->doc->data = read_next(pi); // parse
|
763
|
+
*pi->doc->where = pi->doc->data;
|
764
|
+
pi->doc->where = pi->doc->where_path;
|
765
|
+
if (rb_block_given_p()) {
|
766
|
+
return rb_yield(pi->doc->self); // caller processing
|
767
|
+
}
|
768
|
+
return Qnil;
|
769
|
+
}
|
770
|
+
|
771
|
+
static void
|
772
|
+
free_doc_cb(void *x) {
|
773
|
+
Doc doc = (Doc)x;
|
774
|
+
|
775
|
+
if (0 != doc) {
|
776
|
+
xfree(doc->json);
|
777
|
+
doc_free(doc);
|
778
|
+
}
|
779
|
+
}
|
780
|
+
|
781
|
+
static void
|
782
|
+
mark_leaf(Leaf leaf) {
|
783
|
+
switch (leaf->value_type) {
|
784
|
+
case COL_VAL:
|
785
|
+
if (NULL != leaf->elements) {
|
786
|
+
Leaf first = leaf->elements->next;
|
787
|
+
Leaf e = first;
|
788
|
+
|
789
|
+
do {
|
790
|
+
mark_leaf(e);
|
791
|
+
e = e->next;
|
792
|
+
} while (e != first);
|
793
|
+
}
|
794
|
+
break;
|
795
|
+
case RUBY_VAL:
|
796
|
+
rb_gc_mark(leaf->value);
|
797
|
+
break;
|
798
|
+
|
799
|
+
default:
|
800
|
+
break;
|
801
|
+
}
|
802
|
+
}
|
803
|
+
|
804
|
+
static void
|
805
|
+
mark_doc(void *ptr) {
|
806
|
+
if (NULL != ptr) {
|
807
|
+
Doc doc = (Doc)ptr;
|
808
|
+
|
809
|
+
rb_gc_mark(doc->self);
|
810
|
+
mark_leaf(doc->data);
|
811
|
+
}
|
812
|
+
}
|
813
|
+
|
814
|
+
static VALUE
|
815
|
+
parse_json(VALUE clas, char *json, bool given, bool allocated) {
|
816
|
+
struct _parseInfo pi;
|
817
|
+
volatile VALUE result = Qnil;
|
818
|
+
Doc doc;
|
819
|
+
int ex = 0;
|
820
|
+
volatile VALUE self;
|
821
|
+
|
822
|
+
// TBD are both needed? is stack allocation ever needed?
|
823
|
+
|
824
|
+
if (given) {
|
825
|
+
doc = ALLOCA_N(struct _doc, 1);
|
826
|
+
} else {
|
827
|
+
doc = ALLOC(struct _doc);
|
828
|
+
}
|
829
|
+
/* skip UTF-8 BOM if present */
|
830
|
+
if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) {
|
831
|
+
pi.str = json + 3;
|
832
|
+
} else {
|
833
|
+
pi.str = json;
|
834
|
+
}
|
835
|
+
pi.s = pi.str;
|
836
|
+
doc_init(doc);
|
837
|
+
pi.doc = doc;
|
838
|
+
#if IS_WINDOWS
|
839
|
+
pi.stack_min = (void*)((char*)&pi - (512 * 1024)); // assume a 1M stack and give half to ruby
|
840
|
+
#else
|
841
|
+
{
|
842
|
+
struct rlimit lim;
|
843
|
+
|
844
|
+
if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) {
|
845
|
+
pi.stack_min = (void*)((char*)&lim - (lim.rlim_cur / 4 * 3)); // let 3/4ths of the stack be used only
|
846
|
+
} else {
|
847
|
+
pi.stack_min = 0; // indicates not to check stack limit
|
848
|
+
}
|
849
|
+
}
|
850
|
+
#endif
|
851
|
+
// last arg is free func void* func(void*)
|
852
|
+
#ifdef HAVE_RB_DATA_OBJECT_WRAP
|
853
|
+
self = rb_data_object_wrap(clas, doc, mark_doc, free_doc_cb);
|
854
|
+
#else
|
855
|
+
self = rb_data_object_alloc(clas, doc, mark_doc, free_doc_cb);
|
856
|
+
#endif
|
857
|
+
doc->self = self;
|
858
|
+
doc->json = json;
|
859
|
+
DATA_PTR(doc->self) = doc;
|
860
|
+
result = rb_protect(protect_open_proc, (VALUE)&pi, &ex);
|
861
|
+
if (given || 0 != ex) {
|
862
|
+
DATA_PTR(doc->self) = NULL;
|
863
|
+
doc_free(pi.doc);
|
864
|
+
if (allocated && 0 != ex) { // will jump so caller will not free
|
865
|
+
xfree(json);
|
866
|
+
}
|
867
|
+
rb_gc_enable();
|
868
|
+
} else {
|
869
|
+
result = doc->self;
|
870
|
+
}
|
871
|
+
if (0 != ex) {
|
872
|
+
rb_jump_tag(ex);
|
873
|
+
}
|
874
|
+
return result;
|
875
|
+
}
|
876
|
+
|
877
|
+
static Leaf
|
878
|
+
get_doc_leaf(Doc doc, const char *path) {
|
879
|
+
Leaf leaf = *doc->where;
|
880
|
+
|
881
|
+
if (0 != doc->data && 0 != path) {
|
882
|
+
Leaf stack[MAX_STACK];
|
883
|
+
Leaf *lp;
|
884
|
+
|
885
|
+
if ('/' == *path) {
|
886
|
+
path++;
|
887
|
+
*stack = doc->data;
|
888
|
+
lp = stack;
|
889
|
+
} else if (doc->where == doc->where_path) {
|
890
|
+
*stack = doc->data;
|
891
|
+
lp = stack;
|
892
|
+
} else {
|
893
|
+
size_t cnt = doc->where - doc->where_path;
|
894
|
+
|
895
|
+
if (MAX_STACK <= cnt) {
|
896
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
897
|
+
}
|
898
|
+
memcpy(stack, doc->where_path, sizeof(Leaf) * (cnt + 1));
|
899
|
+
lp = stack + cnt;
|
900
|
+
}
|
901
|
+
return get_leaf(stack, lp, path);
|
902
|
+
}
|
903
|
+
return leaf;
|
904
|
+
}
|
905
|
+
|
906
|
+
static const char*
|
907
|
+
next_slash(const char *s) {
|
908
|
+
for (; '\0' != *s; s++) {
|
909
|
+
if ('\\' == *s) {
|
910
|
+
s++;
|
911
|
+
if ('\0' == *s) {
|
912
|
+
break;
|
913
|
+
}
|
914
|
+
} else if ('/' == *s) {
|
915
|
+
return s;
|
916
|
+
}
|
917
|
+
}
|
918
|
+
return NULL;
|
919
|
+
}
|
920
|
+
|
921
|
+
static bool
|
922
|
+
key_match(const char *pat, const char *key, int plen) {
|
923
|
+
for (; 0 < plen; plen--, pat++, key++) {
|
924
|
+
if ('\\' == *pat) {
|
925
|
+
plen--;
|
926
|
+
pat++;
|
927
|
+
}
|
928
|
+
if (*pat != *key) {
|
929
|
+
return false;
|
930
|
+
}
|
931
|
+
}
|
932
|
+
return '\0' == *key;
|
933
|
+
}
|
934
|
+
|
935
|
+
static Leaf
|
936
|
+
get_leaf(Leaf *stack, Leaf *lp, const char *path) {
|
937
|
+
Leaf leaf = *lp;
|
938
|
+
|
939
|
+
if (MAX_STACK <= lp - stack) {
|
940
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
941
|
+
}
|
942
|
+
if ('\0' != *path) {
|
943
|
+
if ('.' == *path && '.' == *(path + 1)) {
|
944
|
+
path += 2;
|
945
|
+
if ('/' == *path) {
|
946
|
+
path++;
|
947
|
+
}
|
948
|
+
if (stack < lp) {
|
949
|
+
leaf = get_leaf(stack, lp - 1, path);
|
950
|
+
} else {
|
951
|
+
return 0;
|
952
|
+
}
|
953
|
+
} else if (COL_VAL == leaf->value_type && 0 != leaf->elements) {
|
954
|
+
Leaf first = leaf->elements->next;
|
955
|
+
Leaf e = first;
|
956
|
+
int type = leaf->rtype;
|
957
|
+
|
958
|
+
leaf = 0;
|
959
|
+
if (T_ARRAY == type) {
|
960
|
+
int cnt = 0;
|
961
|
+
|
962
|
+
for (; '0' <= *path && *path <= '9'; path++) {
|
963
|
+
cnt = cnt * 10 + (*path - '0');
|
964
|
+
}
|
965
|
+
if ('/' == *path) {
|
966
|
+
path++;
|
967
|
+
}
|
968
|
+
do {
|
969
|
+
if (1 >= cnt) {
|
970
|
+
lp++;
|
971
|
+
*lp = e;
|
972
|
+
leaf = get_leaf(stack, lp, path);
|
973
|
+
break;
|
974
|
+
}
|
975
|
+
cnt--;
|
976
|
+
e = e->next;
|
977
|
+
} while (e != first);
|
978
|
+
} else if (T_HASH == type) {
|
979
|
+
const char *key = path;
|
980
|
+
const char *slash = next_slash(path);
|
981
|
+
int klen;
|
982
|
+
|
983
|
+
if (0 == slash) {
|
984
|
+
klen = (int)strlen(key);
|
985
|
+
path += klen;
|
986
|
+
} else {
|
987
|
+
klen = (int)(slash - key);
|
988
|
+
path += klen + 1;
|
989
|
+
}
|
990
|
+
do {
|
991
|
+
if (key_match(key, e->key, klen)) {
|
992
|
+
lp++;
|
993
|
+
*lp = e;
|
994
|
+
leaf = get_leaf(stack, lp, path);
|
995
|
+
break;
|
996
|
+
}
|
997
|
+
e = e->next;
|
998
|
+
} while (e != first);
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
}
|
1002
|
+
return leaf;
|
1003
|
+
}
|
1004
|
+
|
1005
|
+
static void
|
1006
|
+
each_leaf(Doc doc, VALUE self) {
|
1007
|
+
if (COL_VAL == (*doc->where)->value_type) {
|
1008
|
+
if (0 != (*doc->where)->elements) {
|
1009
|
+
Leaf first = (*doc->where)->elements->next;
|
1010
|
+
Leaf e = first;
|
1011
|
+
|
1012
|
+
doc->where++;
|
1013
|
+
if (MAX_STACK <= doc->where - doc->where_path) {
|
1014
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
1015
|
+
}
|
1016
|
+
do {
|
1017
|
+
*doc->where = e;
|
1018
|
+
each_leaf(doc, self);
|
1019
|
+
e = e->next;
|
1020
|
+
} while (e != first);
|
1021
|
+
doc->where--;
|
1022
|
+
}
|
1023
|
+
} else {
|
1024
|
+
rb_yield(self);
|
1025
|
+
}
|
1026
|
+
}
|
1027
|
+
|
1028
|
+
static int
|
1029
|
+
move_step(Doc doc, const char *path, int loc) {
|
1030
|
+
if (MAX_STACK <= doc->where - doc->where_path) {
|
1031
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
1032
|
+
}
|
1033
|
+
if ('\0' == *path) {
|
1034
|
+
loc = 0;
|
1035
|
+
} else {
|
1036
|
+
Leaf leaf;
|
1037
|
+
|
1038
|
+
if (0 == doc->where || 0 == (leaf = *doc->where)) {
|
1039
|
+
printf("*** Internal error at %s\n", path);
|
1040
|
+
return loc;
|
1041
|
+
}
|
1042
|
+
if ('.' == *path && '.' == *(path + 1)) {
|
1043
|
+
Leaf init = *doc->where;
|
1044
|
+
|
1045
|
+
path += 2;
|
1046
|
+
if (doc->where == doc->where_path) {
|
1047
|
+
return loc;
|
1048
|
+
}
|
1049
|
+
if ('/' == *path) {
|
1050
|
+
path++;
|
1051
|
+
}
|
1052
|
+
*doc->where = 0;
|
1053
|
+
doc->where--;
|
1054
|
+
loc = move_step(doc, path, loc + 1);
|
1055
|
+
if (0 != loc) {
|
1056
|
+
*doc->where = init;
|
1057
|
+
doc->where++;
|
1058
|
+
}
|
1059
|
+
} else if (COL_VAL == leaf->value_type && 0 != leaf->elements) {
|
1060
|
+
Leaf first = leaf->elements->next;
|
1061
|
+
Leaf e = first;
|
1062
|
+
|
1063
|
+
if (T_ARRAY == leaf->rtype) {
|
1064
|
+
int cnt = 0;
|
1065
|
+
|
1066
|
+
for (; '0' <= *path && *path <= '9'; path++) {
|
1067
|
+
cnt = cnt * 10 + (*path - '0');
|
1068
|
+
}
|
1069
|
+
if ('/' == *path) {
|
1070
|
+
path++;
|
1071
|
+
} else if ('\0' != *path) {
|
1072
|
+
return loc;
|
1073
|
+
}
|
1074
|
+
do {
|
1075
|
+
if (1 >= cnt) {
|
1076
|
+
doc->where++;
|
1077
|
+
*doc->where = e;
|
1078
|
+
loc = move_step(doc, path, loc + 1);
|
1079
|
+
if (0 != loc) {
|
1080
|
+
*doc->where = 0;
|
1081
|
+
doc->where--;
|
1082
|
+
}
|
1083
|
+
break;
|
1084
|
+
}
|
1085
|
+
cnt--;
|
1086
|
+
e = e->next;
|
1087
|
+
} while (e != first);
|
1088
|
+
} else if (T_HASH == leaf->rtype) {
|
1089
|
+
const char *key = path;
|
1090
|
+
const char *slash = next_slash(path);
|
1091
|
+
int klen;
|
1092
|
+
|
1093
|
+
if (0 == slash) {
|
1094
|
+
klen = (int)strlen(key);
|
1095
|
+
path += klen;
|
1096
|
+
} else {
|
1097
|
+
klen = (int)(slash - key);
|
1098
|
+
path += klen + 1;
|
1099
|
+
}
|
1100
|
+
do {
|
1101
|
+
if (key_match(key, e->key, klen)) {
|
1102
|
+
doc->where++;
|
1103
|
+
*doc->where = e;
|
1104
|
+
loc = move_step(doc, path, loc + 1);
|
1105
|
+
if (0 != loc) {
|
1106
|
+
*doc->where = 0;
|
1107
|
+
doc->where--;
|
1108
|
+
}
|
1109
|
+
break;
|
1110
|
+
}
|
1111
|
+
e = e->next;
|
1112
|
+
} while (e != first);
|
1113
|
+
}
|
1114
|
+
}
|
1115
|
+
}
|
1116
|
+
return loc;
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
static void
|
1120
|
+
each_value(Doc doc, Leaf leaf) {
|
1121
|
+
if (COL_VAL == leaf->value_type) {
|
1122
|
+
if (0 != leaf->elements) {
|
1123
|
+
Leaf first = leaf->elements->next;
|
1124
|
+
Leaf e = first;
|
1125
|
+
|
1126
|
+
do {
|
1127
|
+
each_value(doc, e);
|
1128
|
+
e = e->next;
|
1129
|
+
} while (e != first);
|
1130
|
+
}
|
1131
|
+
} else {
|
1132
|
+
rb_yield(leaf_value(doc, leaf));
|
1133
|
+
}
|
1134
|
+
}
|
1135
|
+
|
1136
|
+
// doc functions
|
1137
|
+
|
1138
|
+
/* @overload open(json) { |doc| ... } => Object
|
1139
|
+
*
|
1140
|
+
* Parses a JSON document String and then yields to the provided block if one
|
1141
|
+
* is given with an instance of the Oj::Doc as the single yield parameter. If
|
1142
|
+
* a block is not given then an Oj::Doc instance is returned and must be
|
1143
|
+
* closed with a call to the #close() method when no longer needed.
|
1144
|
+
*
|
1145
|
+
* @param [String] json JSON document string
|
1146
|
+
* @yieldparam [Oj::Doc] doc parsed JSON document
|
1147
|
+
* @yieldreturn [Object] returns the result of the yield as the result of the method call
|
1148
|
+
* @example
|
1149
|
+
* Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4
|
1150
|
+
* # or as an alternative
|
1151
|
+
* doc = Oj::Doc.open('[1,2,3]')
|
1152
|
+
* doc.size() #=> 4
|
1153
|
+
* doc.close()
|
1154
|
+
*/
|
1155
|
+
static VALUE
|
1156
|
+
doc_open(VALUE clas, VALUE str) {
|
1157
|
+
char *json;
|
1158
|
+
size_t len;
|
1159
|
+
volatile VALUE obj;
|
1160
|
+
int given = rb_block_given_p();
|
1161
|
+
int allocate;
|
1162
|
+
|
1163
|
+
Check_Type(str, T_STRING);
|
1164
|
+
len = (int)RSTRING_LEN(str) + 1;
|
1165
|
+
allocate = (SMALL_XML < len || !given);
|
1166
|
+
if (allocate) {
|
1167
|
+
json = ALLOC_N(char, len);
|
1168
|
+
} else {
|
1169
|
+
json = ALLOCA_N(char, len);
|
1170
|
+
}
|
1171
|
+
// It should not be necessaary to stop GC but if it is not stopped and a
|
1172
|
+
// large string is parsed that string is corrupted or freed during
|
1173
|
+
// parsing. I'm not sure what is going on exactly but disabling GC avoids
|
1174
|
+
// the issue.
|
1175
|
+
rb_gc_disable();
|
1176
|
+
memcpy(json, StringValuePtr(str), len);
|
1177
|
+
obj = parse_json(clas, json, given, allocate);
|
1178
|
+
rb_gc_enable();
|
1179
|
+
if (given && allocate) {
|
1180
|
+
xfree(json);
|
1181
|
+
}
|
1182
|
+
return obj;
|
1183
|
+
}
|
1184
|
+
|
1185
|
+
/* @overload open_file(filename) { |doc| ... } => Object
|
1186
|
+
*
|
1187
|
+
* Parses a JSON document from a file and then yields to the provided block if
|
1188
|
+
* one is given with an instance of the Oj::Doc as the single yield
|
1189
|
+
* parameter. If a block is not given then an Oj::Doc instance is returned and
|
1190
|
+
* must be closed with a call to the #close() method when no longer needed.
|
1191
|
+
*
|
1192
|
+
* @param [String] filename name of file that contains a JSON document
|
1193
|
+
* @yieldparam [Oj::Doc] doc parsed JSON document
|
1194
|
+
* @yieldreturn [Object] returns the result of the yield as the result of the method call
|
1195
|
+
* @example
|
1196
|
+
* File.open('array.json', 'w') { |f| f.write('[1,2,3]') }
|
1197
|
+
* Oj::Doc.open_file(filename) { |doc| doc.size() } #=> 4
|
1198
|
+
* # or as an alternative
|
1199
|
+
* doc = Oj::Doc.open_file(filename)
|
1200
|
+
* doc.size() #=> 4
|
1201
|
+
* doc.close()
|
1202
|
+
*/
|
1203
|
+
static VALUE
|
1204
|
+
doc_open_file(VALUE clas, VALUE filename) {
|
1205
|
+
char *path;
|
1206
|
+
char *json;
|
1207
|
+
FILE *f;
|
1208
|
+
size_t len;
|
1209
|
+
volatile VALUE obj;
|
1210
|
+
int given = rb_block_given_p();
|
1211
|
+
int allocate;
|
1212
|
+
|
1213
|
+
Check_Type(filename, T_STRING);
|
1214
|
+
path = StringValuePtr(filename);
|
1215
|
+
if (0 == (f = fopen(path, "r"))) {
|
1216
|
+
rb_raise(rb_eIOError, "%s", strerror(errno));
|
1217
|
+
}
|
1218
|
+
fseek(f, 0, SEEK_END);
|
1219
|
+
len = ftell(f);
|
1220
|
+
allocate = (SMALL_XML < len || !given);
|
1221
|
+
if (allocate) {
|
1222
|
+
json = ALLOC_N(char, len + 1);
|
1223
|
+
} else {
|
1224
|
+
json = ALLOCA_N(char, len + 1);
|
1225
|
+
}
|
1226
|
+
fseek(f, 0, SEEK_SET);
|
1227
|
+
if (len != fread(json, 1, len, f)) {
|
1228
|
+
fclose(f);
|
1229
|
+
rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")),
|
1230
|
+
"Failed to read %lu bytes from %s.", (unsigned long)len, path);
|
1231
|
+
}
|
1232
|
+
fclose(f);
|
1233
|
+
json[len] = '\0';
|
1234
|
+
rb_gc_disable();
|
1235
|
+
obj = parse_json(clas, json, given, allocate);
|
1236
|
+
rb_gc_enable();
|
1237
|
+
if (given && allocate) {
|
1238
|
+
xfree(json);
|
1239
|
+
}
|
1240
|
+
return obj;
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
static int
|
1244
|
+
esc_strlen(const char *s) {
|
1245
|
+
int cnt = 0;
|
1246
|
+
|
1247
|
+
for (; '\0' != *s; s++, cnt++) {
|
1248
|
+
if ('/' == *s) {
|
1249
|
+
cnt++;
|
1250
|
+
}
|
1251
|
+
}
|
1252
|
+
return cnt;
|
1253
|
+
}
|
1254
|
+
|
1255
|
+
static char*
|
1256
|
+
append_key(char *p, const char *key) {
|
1257
|
+
for (; '\0' != *key; p++, key++) {
|
1258
|
+
if ('/' == *key) {
|
1259
|
+
*p++ = '\\';
|
1260
|
+
}
|
1261
|
+
*p = *key;
|
1262
|
+
}
|
1263
|
+
return p;
|
1264
|
+
}
|
1265
|
+
|
1266
|
+
/* Document-method: parse
|
1267
|
+
* @see Oj::Doc.open
|
1268
|
+
*/
|
1269
|
+
|
1270
|
+
/* @overload where?() => String
|
1271
|
+
*
|
1272
|
+
* Returns a String that describes the absolute path to the current location
|
1273
|
+
* in the JSON document.
|
1274
|
+
*/
|
1275
|
+
static VALUE
|
1276
|
+
doc_where(VALUE self) {
|
1277
|
+
Doc doc = self_doc(self);
|
1278
|
+
|
1279
|
+
if (0 == *doc->where_path || doc->where == doc->where_path) {
|
1280
|
+
return oj_slash_string;
|
1281
|
+
} else {
|
1282
|
+
Leaf *lp;
|
1283
|
+
Leaf leaf;
|
1284
|
+
size_t size = 3; // leading / and terminating \0
|
1285
|
+
char *path;
|
1286
|
+
char *p;
|
1287
|
+
|
1288
|
+
for (lp = doc->where_path; lp <= doc->where; lp++) {
|
1289
|
+
leaf = *lp;
|
1290
|
+
if (T_HASH == leaf->parent_type) {
|
1291
|
+
size += esc_strlen((*lp)->key) + 1;
|
1292
|
+
} else if (T_ARRAY == leaf->parent_type) {
|
1293
|
+
size += ((*lp)->index < 100) ? 3 : 11;
|
1294
|
+
}
|
1295
|
+
}
|
1296
|
+
path = ALLOCA_N(char, size);
|
1297
|
+
p = path;
|
1298
|
+
for (lp = doc->where_path; lp <= doc->where; lp++) {
|
1299
|
+
leaf = *lp;
|
1300
|
+
if (T_HASH == leaf->parent_type) {
|
1301
|
+
p = append_key(p, (*lp)->key);
|
1302
|
+
} else if (T_ARRAY == leaf->parent_type) {
|
1303
|
+
p = ulong_fill(p, (*lp)->index);
|
1304
|
+
}
|
1305
|
+
*p++ = '/';
|
1306
|
+
}
|
1307
|
+
*--p = '\0';
|
1308
|
+
|
1309
|
+
return rb_str_new(path, p - path);
|
1310
|
+
}
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
/* @overload local_key() => String, Fixnum, nil
|
1314
|
+
*
|
1315
|
+
* Returns the final key to the current location.
|
1316
|
+
* @example
|
1317
|
+
* Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.local_key() } #=> 2
|
1318
|
+
* Oj::Doc.open('{"one":3}') { |doc| doc.move('/one'); doc.local_key() } #=> "one"
|
1319
|
+
* Oj::Doc.open('[1,2,3]') { |doc| doc.local_key() } #=> nil
|
1320
|
+
*/
|
1321
|
+
static VALUE
|
1322
|
+
doc_local_key(VALUE self) {
|
1323
|
+
Doc doc = self_doc(self);
|
1324
|
+
Leaf leaf = *doc->where;
|
1325
|
+
volatile VALUE key = Qnil;
|
1326
|
+
|
1327
|
+
if (T_HASH == leaf->parent_type) {
|
1328
|
+
key = rb_str_new2(leaf->key);
|
1329
|
+
key = oj_encode(key);
|
1330
|
+
} else if (T_ARRAY == leaf->parent_type) {
|
1331
|
+
key = LONG2NUM(leaf->index);
|
1332
|
+
}
|
1333
|
+
return key;
|
1334
|
+
}
|
1335
|
+
|
1336
|
+
/* @overload home() => nil
|
1337
|
+
*
|
1338
|
+
* Moves the document marker or location to the hoot or home position. The
|
1339
|
+
* same operation can be performed with a Oj::Doc.move('/').
|
1340
|
+
* @example
|
1341
|
+
* Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.home(); doc.where? } #=> '/'
|
1342
|
+
*/
|
1343
|
+
static VALUE
|
1344
|
+
doc_home(VALUE self) {
|
1345
|
+
Doc doc = self_doc(self);
|
1346
|
+
|
1347
|
+
*doc->where_path = doc->data;
|
1348
|
+
doc->where = doc->where_path;
|
1349
|
+
|
1350
|
+
return oj_slash_string;
|
1351
|
+
}
|
1352
|
+
|
1353
|
+
/* @overload type(path=nil) => Class
|
1354
|
+
*
|
1355
|
+
* Returns the Class of the data value at the location identified by the path
|
1356
|
+
* or the current location if the path is nil or not provided. This method
|
1357
|
+
* does not create the Ruby Object at the location specified so the overhead
|
1358
|
+
* is low.
|
1359
|
+
* @param [String] path path to the location to get the type of if provided
|
1360
|
+
* @example
|
1361
|
+
* Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array
|
1362
|
+
* Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum
|
1363
|
+
*/
|
1364
|
+
static VALUE
|
1365
|
+
doc_type(int argc, VALUE *argv, VALUE self) {
|
1366
|
+
Doc doc = self_doc(self);
|
1367
|
+
Leaf leaf;
|
1368
|
+
const char *path = 0;
|
1369
|
+
VALUE type = Qnil;
|
1370
|
+
|
1371
|
+
if (1 <= argc) {
|
1372
|
+
Check_Type(*argv, T_STRING);
|
1373
|
+
path = StringValuePtr(*argv);
|
1374
|
+
}
|
1375
|
+
if (0 != (leaf = get_doc_leaf(doc, path))) {
|
1376
|
+
switch (leaf->rtype) {
|
1377
|
+
case T_NIL: type = rb_cNilClass; break;
|
1378
|
+
case T_TRUE: type = rb_cTrueClass; break;
|
1379
|
+
case T_FALSE: type = rb_cFalseClass; break;
|
1380
|
+
case T_STRING: type = rb_cString; break;
|
1381
|
+
#ifdef RUBY_INTEGER_UNIFICATION
|
1382
|
+
case T_FIXNUM: type = rb_cInteger; break;
|
1383
|
+
#else
|
1384
|
+
case T_FIXNUM: type = rb_cFixnum; break;
|
1385
|
+
#endif
|
1386
|
+
case T_FLOAT: type = rb_cFloat; break;
|
1387
|
+
case T_ARRAY: type = rb_cArray; break;
|
1388
|
+
case T_HASH: type = rb_cHash; break;
|
1389
|
+
default: break;
|
1390
|
+
}
|
1391
|
+
}
|
1392
|
+
return type;
|
1393
|
+
}
|
1394
|
+
|
1395
|
+
/* @overload fetch(path=nil) => nil, true, false, Fixnum, Float, String, Array, Hash
|
1396
|
+
*
|
1397
|
+
* Returns the value at the location identified by the path or the current
|
1398
|
+
* location if the path is nil or not provided. This method will create and
|
1399
|
+
* return an Array or Hash if that is the type of Object at the location
|
1400
|
+
* specified. This is more expensive than navigating to the leaves of the JSON
|
1401
|
+
* document.
|
1402
|
+
* @param [String] path path to the location to get the type of if provided
|
1403
|
+
* @example
|
1404
|
+
* Oj::Doc.open('[1,2]') { |doc| doc.fetch() } #=> [1, 2]
|
1405
|
+
* Oj::Doc.open('[1,2]') { |doc| doc.fetch('/1') } #=> 1
|
1406
|
+
*/
|
1407
|
+
static VALUE
|
1408
|
+
doc_fetch(int argc, VALUE *argv, VALUE self) {
|
1409
|
+
Doc doc;
|
1410
|
+
Leaf leaf;
|
1411
|
+
volatile VALUE val = Qnil;
|
1412
|
+
const char *path = 0;
|
1413
|
+
|
1414
|
+
doc = self_doc(self);
|
1415
|
+
if (1 <= argc) {
|
1416
|
+
Check_Type(*argv, T_STRING);
|
1417
|
+
path = StringValuePtr(*argv);
|
1418
|
+
if (2 == argc) {
|
1419
|
+
val = argv[1];
|
1420
|
+
}
|
1421
|
+
}
|
1422
|
+
if (0 != (leaf = get_doc_leaf(doc, path))) {
|
1423
|
+
val = leaf_value(doc, leaf);
|
1424
|
+
}
|
1425
|
+
return val;
|
1426
|
+
}
|
1427
|
+
|
1428
|
+
/* @overload each_leaf(path=nil) => nil
|
1429
|
+
*
|
1430
|
+
* Yields to the provided block for each leaf node with the identified
|
1431
|
+
* location of the JSON document as the root. The parameter passed to the
|
1432
|
+
* block on yield is the Doc instance after moving to the child location.
|
1433
|
+
* @param [String] path if provided it identified the top of the branch to process the leaves of
|
1434
|
+
* @yieldparam [Doc] Doc at the child location
|
1435
|
+
* @example
|
1436
|
+
* Oj::Doc.open('[3,[2,1]]') { |doc|
|
1437
|
+
* result = {}
|
1438
|
+
* doc.each_leaf() { |d| result[d.where?] = d.fetch() }
|
1439
|
+
* result
|
1440
|
+
* }
|
1441
|
+
* #=> ["/1" => 3, "/2/1" => 2, "/2/2" => 1]
|
1442
|
+
*/
|
1443
|
+
static VALUE
|
1444
|
+
doc_each_leaf(int argc, VALUE *argv, VALUE self) {
|
1445
|
+
if (rb_block_given_p()) {
|
1446
|
+
Leaf save_path[MAX_STACK];
|
1447
|
+
Doc doc = self_doc(self);
|
1448
|
+
const char *path = 0;
|
1449
|
+
size_t wlen;
|
1450
|
+
|
1451
|
+
wlen = doc->where - doc->where_path;
|
1452
|
+
if (0 < wlen) {
|
1453
|
+
memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
|
1454
|
+
}
|
1455
|
+
if (1 <= argc) {
|
1456
|
+
Check_Type(*argv, T_STRING);
|
1457
|
+
path = StringValuePtr(*argv);
|
1458
|
+
if ('/' == *path) {
|
1459
|
+
doc->where = doc->where_path;
|
1460
|
+
path++;
|
1461
|
+
}
|
1462
|
+
if (0 != move_step(doc, path, 1)) {
|
1463
|
+
if (0 < wlen) {
|
1464
|
+
memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
|
1465
|
+
}
|
1466
|
+
return Qnil;
|
1467
|
+
}
|
1468
|
+
}
|
1469
|
+
each_leaf(doc, self);
|
1470
|
+
if (0 < wlen) {
|
1471
|
+
memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
|
1472
|
+
}
|
1473
|
+
}
|
1474
|
+
return Qnil;
|
1475
|
+
}
|
1476
|
+
|
1477
|
+
/* @overload move(path) => nil
|
1478
|
+
*
|
1479
|
+
* Moves the document marker to the path specified. The path can an absolute
|
1480
|
+
* path or a relative path.
|
1481
|
+
* @param [String] path path to the location to move to
|
1482
|
+
* @example
|
1483
|
+
* Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=> "/one/2"
|
1484
|
+
*/
|
1485
|
+
static VALUE
|
1486
|
+
doc_move(VALUE self, VALUE str) {
|
1487
|
+
Doc doc = self_doc(self);
|
1488
|
+
const char *path;
|
1489
|
+
int loc;
|
1490
|
+
|
1491
|
+
Check_Type(str, T_STRING);
|
1492
|
+
path = StringValuePtr(str);
|
1493
|
+
if ('/' == *path) {
|
1494
|
+
doc->where = doc->where_path;
|
1495
|
+
path++;
|
1496
|
+
}
|
1497
|
+
if (0 != (loc = move_step(doc, path, 1))) {
|
1498
|
+
rb_raise(rb_eArgError, "Failed to locate element %d of the path %s.", loc, path);
|
1499
|
+
}
|
1500
|
+
return Qnil;
|
1501
|
+
}
|
1502
|
+
|
1503
|
+
/* @overload each_child(path=nil) { |doc| ... } => nil
|
1504
|
+
*
|
1505
|
+
* Yields to the provided block for each immediate child node with the
|
1506
|
+
* identified location of the JSON document as the root. The parameter passed
|
1507
|
+
* to the block on yield is the Doc instance after moving to the child
|
1508
|
+
* location.
|
1509
|
+
* @param [String] path if provided it identified the top of the branch to process the chilren of
|
1510
|
+
* @yieldparam [Doc] Doc at the child location
|
1511
|
+
* @example
|
1512
|
+
* Oj::Doc.open('[3,[2,1]]') { |doc|
|
1513
|
+
* result = []
|
1514
|
+
* doc.each_value('/2') { |doc| result << doc.where? }
|
1515
|
+
* result
|
1516
|
+
* }
|
1517
|
+
* #=> ["/2/1", "/2/2"]
|
1518
|
+
*/
|
1519
|
+
static VALUE
|
1520
|
+
doc_each_child(int argc, VALUE *argv, VALUE self) {
|
1521
|
+
if (rb_block_given_p()) {
|
1522
|
+
Leaf save_path[MAX_STACK];
|
1523
|
+
Doc doc = self_doc(self);
|
1524
|
+
const char *path = 0;
|
1525
|
+
size_t wlen;
|
1526
|
+
|
1527
|
+
wlen = doc->where - doc->where_path;
|
1528
|
+
if (0 < wlen) {
|
1529
|
+
memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
|
1530
|
+
}
|
1531
|
+
if (1 <= argc) {
|
1532
|
+
Check_Type(*argv, T_STRING);
|
1533
|
+
path = StringValuePtr(*argv);
|
1534
|
+
if ('/' == *path) {
|
1535
|
+
doc->where = doc->where_path;
|
1536
|
+
path++;
|
1537
|
+
}
|
1538
|
+
if (0 != move_step(doc, path, 1)) {
|
1539
|
+
if (0 < wlen) {
|
1540
|
+
memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
|
1541
|
+
}
|
1542
|
+
return Qnil;
|
1543
|
+
}
|
1544
|
+
}
|
1545
|
+
if (COL_VAL == (*doc->where)->value_type && 0 != (*doc->where)->elements) {
|
1546
|
+
Leaf first = (*doc->where)->elements->next;
|
1547
|
+
Leaf e = first;
|
1548
|
+
|
1549
|
+
doc->where++;
|
1550
|
+
do {
|
1551
|
+
*doc->where = e;
|
1552
|
+
rb_yield(self);
|
1553
|
+
e = e->next;
|
1554
|
+
} while (e != first);
|
1555
|
+
}
|
1556
|
+
if (0 < wlen) {
|
1557
|
+
memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
|
1558
|
+
}
|
1559
|
+
}
|
1560
|
+
return Qnil;
|
1561
|
+
}
|
1562
|
+
|
1563
|
+
/* @overload each_value(path=nil) { |val| ... } => nil
|
1564
|
+
*
|
1565
|
+
* Yields to the provided block for each leaf value in the identified location
|
1566
|
+
* of the JSON document. The parameter passed to the block on yield is the
|
1567
|
+
* value of the leaf. Only those leaves below the element specified by the
|
1568
|
+
* path parameter are processed.
|
1569
|
+
* @param [String] path if provided it identified the top of the branch to process the leaf values of
|
1570
|
+
* @yieldparam [Object] val each leaf value
|
1571
|
+
* @example
|
1572
|
+
* Oj::Doc.open('[3,[2,1]]') { |doc|
|
1573
|
+
* result = []
|
1574
|
+
* doc.each_value() { |v| result << v }
|
1575
|
+
* result
|
1576
|
+
* }
|
1577
|
+
* #=> [3, 2, 1]
|
1578
|
+
*
|
1579
|
+
* Oj::Doc.open('[3,[2,1]]') { |doc|
|
1580
|
+
* result = []
|
1581
|
+
* doc.each_value('/2') { |v| result << v }
|
1582
|
+
* result
|
1583
|
+
* }
|
1584
|
+
* #=> [2, 1]
|
1585
|
+
*/
|
1586
|
+
static VALUE
|
1587
|
+
doc_each_value(int argc, VALUE *argv, VALUE self) {
|
1588
|
+
if (rb_block_given_p()) {
|
1589
|
+
Doc doc = self_doc(self);
|
1590
|
+
const char *path = 0;
|
1591
|
+
Leaf leaf;
|
1592
|
+
|
1593
|
+
if (1 <= argc) {
|
1594
|
+
Check_Type(*argv, T_STRING);
|
1595
|
+
path = StringValuePtr(*argv);
|
1596
|
+
}
|
1597
|
+
if (0 != (leaf = get_doc_leaf(doc, path))) {
|
1598
|
+
each_value(doc, leaf);
|
1599
|
+
}
|
1600
|
+
}
|
1601
|
+
return Qnil;
|
1602
|
+
}
|
1603
|
+
|
1604
|
+
/* @overload dump(path, filename)
|
1605
|
+
*
|
1606
|
+
* Dumps the document or nodes to a new JSON document. It uses the default
|
1607
|
+
* options for generating the JSON.
|
1608
|
+
* @param path [String] if provided it identified the top of the branch to dump to JSON
|
1609
|
+
* @param filename [String] if provided it is the filename to write the output to
|
1610
|
+
* @example
|
1611
|
+
* Oj::Doc.open('[3,[2,1]]') { |doc|
|
1612
|
+
* doc.dump('/2')
|
1613
|
+
* }
|
1614
|
+
* #=> "[2,1]"
|
1615
|
+
*/
|
1616
|
+
static VALUE
|
1617
|
+
doc_dump(int argc, VALUE *argv, VALUE self) {
|
1618
|
+
Doc doc = self_doc(self);
|
1619
|
+
Leaf leaf;
|
1620
|
+
const char *path = 0;
|
1621
|
+
const char *filename = 0;
|
1622
|
+
|
1623
|
+
if (1 <= argc) {
|
1624
|
+
if (Qnil != *argv) {
|
1625
|
+
Check_Type(*argv, T_STRING);
|
1626
|
+
path = StringValuePtr(*argv);
|
1627
|
+
}
|
1628
|
+
if (2 <= argc) {
|
1629
|
+
Check_Type(argv[1], T_STRING);
|
1630
|
+
filename = StringValuePtr(argv[1]);
|
1631
|
+
}
|
1632
|
+
}
|
1633
|
+
if (0 != (leaf = get_doc_leaf(doc, path))) {
|
1634
|
+
volatile VALUE rjson;
|
1635
|
+
|
1636
|
+
if (0 == filename) {
|
1637
|
+
char buf[4096];
|
1638
|
+
struct _out out;
|
1639
|
+
|
1640
|
+
out.buf = buf;
|
1641
|
+
out.end = buf + sizeof(buf) - 10;
|
1642
|
+
out.allocated = false;
|
1643
|
+
out.omit_nil = oj_default_options.dump_opts.omit_nil;
|
1644
|
+
oj_dump_leaf_to_json(leaf, &oj_default_options, &out);
|
1645
|
+
rjson = rb_str_new2(out.buf);
|
1646
|
+
if (out.allocated) {
|
1647
|
+
xfree(out.buf);
|
1648
|
+
}
|
1649
|
+
} else {
|
1650
|
+
oj_write_leaf_to_file(leaf, filename, &oj_default_options);
|
1651
|
+
rjson = Qnil;
|
1652
|
+
}
|
1653
|
+
return rjson;
|
1654
|
+
}
|
1655
|
+
return Qnil;
|
1656
|
+
}
|
1657
|
+
|
1658
|
+
/* @overload size() => Fixnum
|
1659
|
+
*
|
1660
|
+
* Returns the number of nodes in the JSON document where a node is any one of
|
1661
|
+
* the basic JSON components.
|
1662
|
+
* @return Returns the size of the JSON document.
|
1663
|
+
* @example
|
1664
|
+
* Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4
|
1665
|
+
*/
|
1666
|
+
static VALUE
|
1667
|
+
doc_size(VALUE self) {
|
1668
|
+
return ULONG2NUM(((Doc)DATA_PTR(self))->size);
|
1669
|
+
}
|
1670
|
+
|
1671
|
+
/* @overload close() => nil
|
1672
|
+
*
|
1673
|
+
* Closes an open document. No further calls to the document will be valid
|
1674
|
+
* after closing.
|
1675
|
+
* @example
|
1676
|
+
* doc = Oj::Doc.open('[1,2,3]')
|
1677
|
+
* doc.size() #=> 4
|
1678
|
+
* doc.close()
|
1679
|
+
*/
|
1680
|
+
static VALUE
|
1681
|
+
doc_close(VALUE self) {
|
1682
|
+
Doc doc = self_doc(self);
|
1683
|
+
|
1684
|
+
rb_gc_unregister_address(&doc->self);
|
1685
|
+
DATA_PTR(doc->self) = 0;
|
1686
|
+
if (0 != doc) {
|
1687
|
+
xfree(doc->json);
|
1688
|
+
doc_free(doc);
|
1689
|
+
xfree(doc);
|
1690
|
+
}
|
1691
|
+
return Qnil;
|
1692
|
+
}
|
1693
|
+
#if 0
|
1694
|
+
// hack to keep the doc generator happy
|
1695
|
+
Oj = rb_define_module("Oj");
|
1696
|
+
#endif
|
1697
|
+
|
1698
|
+
static VALUE
|
1699
|
+
doc_not_implemented(VALUE self) {
|
1700
|
+
rb_raise(rb_eNotImpError, "Not implemented.");
|
1701
|
+
return Qnil;
|
1702
|
+
}
|
1703
|
+
|
1704
|
+
/* Document-class: Oj::Doc
|
1705
|
+
*
|
1706
|
+
* The Doc class is used to parse and navigate a JSON document. The model it
|
1707
|
+
* employs is that of a document that while open can be navigated and values
|
1708
|
+
* extracted. Once the document is closed the document can not longer be
|
1709
|
+
* accessed. This allows the parsing and data extraction to be extremely fast
|
1710
|
+
* compared to other JSON parses.
|
1711
|
+
*
|
1712
|
+
* An Oj::Doc class is not created directly but the _open()_ class method is
|
1713
|
+
* used to open a document and the yield parameter to the block of the #open()
|
1714
|
+
* call is the Doc instance. The Doc instance can be moved across, up, and
|
1715
|
+
* down the JSON document. At each element the data associated with the
|
1716
|
+
* element can be extracted. It is also possible to just provide a path to the
|
1717
|
+
* data to be extracted and retrieve the data in that manner.
|
1718
|
+
*
|
1719
|
+
* For many of the methods a path is used to describe the location of an
|
1720
|
+
* element. Paths follow a subset of the XPath syntax. The slash ('/')
|
1721
|
+
* character is the separator. Each step in the path identifies the next
|
1722
|
+
* branch to take through the document. A JSON object will expect a key string
|
1723
|
+
* while an array will expect a positive index. A .. step indicates a move up
|
1724
|
+
* the JSON document.
|
1725
|
+
*
|
1726
|
+
* @example
|
1727
|
+
* json = %{[
|
1728
|
+
* {
|
1729
|
+
* "one" : 1,
|
1730
|
+
* "two" : 2
|
1731
|
+
* },
|
1732
|
+
* {
|
1733
|
+
* "three" : 3,
|
1734
|
+
* "four" : 4
|
1735
|
+
* }
|
1736
|
+
* ]}
|
1737
|
+
* # move and get value
|
1738
|
+
* Oj::Doc.open(json) do |doc|
|
1739
|
+
* doc.move('/1/two')
|
1740
|
+
* # doc location is now at the 'two' element of the hash that is the first element of the array.
|
1741
|
+
* doc.fetch()
|
1742
|
+
* end
|
1743
|
+
* #=> 2
|
1744
|
+
*
|
1745
|
+
* # Now try again using a path to Oj::Doc.fetch() directly and not using a block.
|
1746
|
+
* doc = Oj::Doc.open(json)
|
1747
|
+
* doc.fetch('/2/three') #=> 3
|
1748
|
+
* doc.close()
|
1749
|
+
*/
|
1750
|
+
void
|
1751
|
+
oj_init_doc() {
|
1752
|
+
oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject);
|
1753
|
+
rb_define_singleton_method(oj_doc_class, "open", doc_open, 1);
|
1754
|
+
rb_define_singleton_method(oj_doc_class, "open_file", doc_open_file, 1);
|
1755
|
+
rb_define_singleton_method(oj_doc_class, "parse", doc_open, 1);
|
1756
|
+
rb_define_method(oj_doc_class, "where?", doc_where, 0);
|
1757
|
+
rb_define_method(oj_doc_class, "local_key", doc_local_key, 0);
|
1758
|
+
rb_define_method(oj_doc_class, "home", doc_home, 0);
|
1759
|
+
rb_define_method(oj_doc_class, "type", doc_type, -1);
|
1760
|
+
rb_define_method(oj_doc_class, "fetch", doc_fetch, -1);
|
1761
|
+
rb_define_method(oj_doc_class, "each_leaf", doc_each_leaf, -1);
|
1762
|
+
rb_define_method(oj_doc_class, "move", doc_move, 1);
|
1763
|
+
rb_define_method(oj_doc_class, "each_child", doc_each_child, -1);
|
1764
|
+
rb_define_method(oj_doc_class, "each_value", doc_each_value, -1);
|
1765
|
+
rb_define_method(oj_doc_class, "dump", doc_dump, -1);
|
1766
|
+
rb_define_method(oj_doc_class, "size", doc_size, 0);
|
1767
|
+
rb_define_method(oj_doc_class, "close", doc_close, 0);
|
1768
|
+
|
1769
|
+
rb_define_method(oj_doc_class, "clone", doc_not_implemented, 0);
|
1770
|
+
rb_define_method(oj_doc_class, "dup", doc_not_implemented, 0);
|
1771
|
+
}
|