oj 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +17 -23
  3. data/README.md +74 -425
  4. data/ext/oj/buf.h +103 -0
  5. data/ext/oj/cache8.c +4 -0
  6. data/ext/oj/circarray.c +68 -0
  7. data/ext/oj/circarray.h +23 -0
  8. data/ext/oj/code.c +227 -0
  9. data/ext/oj/code.h +40 -0
  10. data/ext/oj/compat.c +243 -0
  11. data/ext/oj/custom.c +1097 -0
  12. data/ext/oj/dump.c +766 -1534
  13. data/ext/oj/dump.h +92 -0
  14. data/ext/oj/dump_compat.c +937 -0
  15. data/ext/oj/dump_leaf.c +254 -0
  16. data/ext/oj/dump_object.c +810 -0
  17. data/ext/oj/dump_rails.c +329 -0
  18. data/ext/oj/dump_strict.c +416 -0
  19. data/ext/oj/encode.h +51 -0
  20. data/ext/oj/err.c +57 -0
  21. data/ext/oj/err.h +70 -0
  22. data/ext/oj/extconf.rb +17 -7
  23. data/ext/oj/fast.c +213 -180
  24. data/ext/oj/hash.c +163 -0
  25. data/ext/oj/hash.h +46 -0
  26. data/ext/oj/hash_test.c +512 -0
  27. data/ext/oj/mimic_json.c +817 -0
  28. data/ext/oj/mimic_rails.c +806 -0
  29. data/ext/oj/mimic_rails.h +17 -0
  30. data/ext/oj/object.c +752 -0
  31. data/ext/oj/odd.c +230 -0
  32. data/ext/oj/odd.h +44 -0
  33. data/ext/oj/oj.c +1288 -929
  34. data/ext/oj/oj.h +240 -69
  35. data/ext/oj/parse.c +1014 -0
  36. data/ext/oj/parse.h +92 -0
  37. data/ext/oj/reader.c +223 -0
  38. data/ext/oj/reader.h +151 -0
  39. data/ext/oj/resolve.c +127 -0
  40. data/ext/oj/{cache.h → resolve.h} +6 -13
  41. data/ext/oj/rxclass.c +133 -0
  42. data/ext/oj/rxclass.h +27 -0
  43. data/ext/oj/saj.c +77 -175
  44. data/ext/oj/scp.c +224 -0
  45. data/ext/oj/sparse.c +911 -0
  46. data/ext/oj/stream_writer.c +301 -0
  47. data/ext/oj/strict.c +162 -0
  48. data/ext/oj/string_writer.c +480 -0
  49. data/ext/oj/val_stack.c +98 -0
  50. data/ext/oj/val_stack.h +188 -0
  51. data/lib/oj/active_support_helper.rb +41 -0
  52. data/lib/oj/bag.rb +6 -10
  53. data/lib/oj/easy_hash.rb +52 -0
  54. data/lib/oj/json.rb +172 -0
  55. data/lib/oj/mimic.rb +260 -5
  56. data/lib/oj/saj.rb +13 -10
  57. data/lib/oj/schandler.rb +142 -0
  58. data/lib/oj/state.rb +131 -0
  59. data/lib/oj/version.rb +1 -1
  60. data/lib/oj.rb +11 -23
  61. data/pages/Advanced.md +22 -0
  62. data/pages/Compatibility.md +25 -0
  63. data/pages/Custom.md +23 -0
  64. data/pages/Encoding.md +65 -0
  65. data/pages/JsonGem.md +79 -0
  66. data/pages/Modes.md +140 -0
  67. data/pages/Options.md +250 -0
  68. data/pages/Rails.md +60 -0
  69. data/pages/Security.md +20 -0
  70. data/test/_test_active.rb +76 -0
  71. data/test/_test_active_mimic.rb +96 -0
  72. data/test/_test_mimic_rails.rb +126 -0
  73. data/test/activesupport4/decoding_test.rb +105 -0
  74. data/test/activesupport4/encoding_test.rb +531 -0
  75. data/test/activesupport4/test_helper.rb +41 -0
  76. data/test/activesupport5/decoding_test.rb +125 -0
  77. data/test/activesupport5/encoding_test.rb +483 -0
  78. data/test/activesupport5/encoding_test_cases.rb +90 -0
  79. data/test/activesupport5/test_helper.rb +50 -0
  80. data/test/activesupport5/time_zone_test_helpers.rb +24 -0
  81. data/test/helper.rb +27 -0
  82. data/test/isolated/shared.rb +310 -0
  83. data/test/isolated/test_mimic_after.rb +13 -0
  84. data/test/isolated/test_mimic_alone.rb +12 -0
  85. data/test/isolated/test_mimic_as_json.rb +45 -0
  86. data/test/isolated/test_mimic_before.rb +13 -0
  87. data/test/isolated/test_mimic_define.rb +28 -0
  88. data/test/isolated/test_mimic_rails_after.rb +22 -0
  89. data/test/isolated/test_mimic_rails_before.rb +21 -0
  90. data/test/isolated/test_mimic_redefine.rb +15 -0
  91. data/test/json_gem/json_addition_test.rb +216 -0
  92. data/test/json_gem/json_common_interface_test.rb +143 -0
  93. data/test/json_gem/json_encoding_test.rb +109 -0
  94. data/test/json_gem/json_ext_parser_test.rb +20 -0
  95. data/test/json_gem/json_fixtures_test.rb +35 -0
  96. data/test/json_gem/json_generator_test.rb +383 -0
  97. data/test/json_gem/json_generic_object_test.rb +90 -0
  98. data/test/json_gem/json_parser_test.rb +470 -0
  99. data/test/json_gem/json_string_matching_test.rb +42 -0
  100. data/test/json_gem/test_helper.rb +18 -0
  101. data/test/perf_compat.rb +130 -0
  102. data/test/perf_fast.rb +9 -9
  103. data/test/perf_file.rb +64 -0
  104. data/test/{perf_obj.rb → perf_object.rb} +24 -10
  105. data/test/perf_scp.rb +151 -0
  106. data/test/perf_strict.rb +32 -113
  107. data/test/sample.rb +2 -3
  108. data/test/test_compat.rb +474 -0
  109. data/test/test_custom.rb +355 -0
  110. data/test/test_debian.rb +53 -0
  111. data/test/test_fast.rb +66 -16
  112. data/test/test_file.rb +237 -0
  113. data/test/test_gc.rb +49 -0
  114. data/test/test_hash.rb +29 -0
  115. data/test/test_null.rb +376 -0
  116. data/test/test_object.rb +1010 -0
  117. data/test/test_saj.rb +16 -16
  118. data/test/test_scp.rb +417 -0
  119. data/test/test_strict.rb +410 -0
  120. data/test/test_various.rb +815 -0
  121. data/test/test_writer.rb +308 -0
  122. data/test/tests.rb +9 -902
  123. data/test/tests_mimic.rb +14 -0
  124. data/test/tests_mimic_addition.rb +7 -0
  125. metadata +253 -38
  126. data/ext/oj/cache.c +0 -148
  127. data/ext/oj/foo.rb +0 -6
  128. data/ext/oj/load.c +0 -1049
  129. data/test/a.rb +0 -38
  130. data/test/perf1.rb +0 -64
  131. data/test/perf2.rb +0 -76
  132. data/test/perf_obj_old.rb +0 -213
  133. data/test/test_mimic.rb +0 -208
data/ext/oj/fast.c CHANGED
@@ -38,6 +38,7 @@
38
38
  #include <errno.h>
39
39
 
40
40
  #include "oj.h"
41
+ #include "encode.h"
41
42
 
42
43
  // maximum to allocate on the stack, arbitrary limit
43
44
  #define SMALL_XML 65536
@@ -146,23 +147,6 @@ next_non_white(ParseInfo pi) {
146
147
  }
147
148
  }
148
149
 
149
- inline static void
150
- next_white(ParseInfo pi) {
151
- for (; 1; pi->s++) {
152
- switch(*pi->s) {
153
- case ' ':
154
- case '\t':
155
- case '\f':
156
- case '\n':
157
- case '\r':
158
- case '\0':
159
- return;
160
- default:
161
- break;
162
- }
163
- }
164
- }
165
-
166
150
  inline static char*
167
151
  ulong_fill(char *s, size_t num) {
168
152
  char buf[32];
@@ -186,7 +170,7 @@ ulong_fill(char *s, size_t num) {
186
170
  inline static void
187
171
  leaf_init(Leaf leaf, int type) {
188
172
  leaf->next = 0;
189
- leaf->type = type;
173
+ leaf->rtype = type;
190
174
  leaf->parent_type = T_NONE;
191
175
  switch (type) {
192
176
  case T_ARRAY:
@@ -222,6 +206,8 @@ leaf_new(Doc doc, int type) {
222
206
  if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) {
223
207
  Batch b = ALLOC(struct _Batch);
224
208
 
209
+ // Initializes all leaves with a NO_VAL value_type
210
+ memset(b, 0, sizeof(struct _Batch));
225
211
  b->next = doc->batches;
226
212
  doc->batches = b;
227
213
  b->next_avail = 0;
@@ -248,7 +234,7 @@ leaf_append_element(Leaf parent, Leaf element) {
248
234
  static VALUE
249
235
  leaf_value(Doc doc, Leaf leaf) {
250
236
  if (RUBY_VAL != leaf->value_type) {
251
- switch (leaf->type) {
237
+ switch (leaf->rtype) {
252
238
  case T_NIL:
253
239
  leaf->value = Qnil;
254
240
  break;
@@ -266,9 +252,7 @@ leaf_value(Doc doc, Leaf leaf) {
266
252
  break;
267
253
  case T_STRING:
268
254
  leaf->value = rb_str_new2(leaf->str);
269
- #if HAS_ENCODING_SUPPORT
270
- rb_enc_associate(leaf->value, oj_utf8_encoding);
271
- #endif
255
+ leaf->value = oj_encode(leaf->value);
272
256
  leaf->value_type = RUBY_VAL;
273
257
  break;
274
258
  case T_ARRAY:
@@ -278,7 +262,7 @@ leaf_value(Doc doc, Leaf leaf) {
278
262
  return leaf_hash_value(doc, leaf);
279
263
  break;
280
264
  default:
281
- rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->type);
265
+ rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->rtype);
282
266
  break;
283
267
  }
284
268
  }
@@ -361,84 +345,16 @@ leaf_fixnum_value(Leaf leaf) {
361
345
  if (neg) {
362
346
  n = -n;
363
347
  }
364
- leaf->value = LONG2NUM(n);
348
+ leaf->value = rb_ll2inum(n);
365
349
  }
366
350
  leaf->value_type = RUBY_VAL;
367
351
  }
368
352
 
369
- #ifdef JRUBY_RUBY
370
- static void
371
- leaf_float_value(Leaf leaf) {
372
- char *s = leaf->str;
373
- int64_t n = 0;
374
- long a = 0;
375
- long div = 1;
376
- long e = 0;
377
- int neg = 0;
378
- int eneg = 0;
379
- int big = 0;
380
-
381
- if ('-' == *s) {
382
- s++;
383
- neg = 1;
384
- } else if ('+' == *s) {
385
- s++;
386
- }
387
- for (; '0' <= *s && *s <= '9'; s++) {
388
- n = n * 10 + (*s - '0');
389
- if (NUM_MAX <= n) {
390
- big = 1;
391
- }
392
- }
393
- if (big) {
394
- char c = *s;
395
-
396
- *s = '\0';
397
- leaf->value = rb_cstr_to_inum(leaf->str, 10, 0);
398
- *s = c;
399
- } else {
400
- double d;
401
-
402
- if ('.' == *s) {
403
- s++;
404
- for (; '0' <= *s && *s <= '9'; s++) {
405
- a = a * 10 + (*s - '0');
406
- div *= 10;
407
- }
408
- }
409
- if ('e' == *s || 'E' == *s) {
410
- s++;
411
- if ('-' == *s) {
412
- s++;
413
- eneg = 1;
414
- } else if ('+' == *s) {
415
- s++;
416
- }
417
- for (; '0' <= *s && *s <= '9'; s++) {
418
- e = e * 10 + (*s - '0');
419
- }
420
- }
421
- d = (double)n + (double)a / (double)div;
422
- if (neg) {
423
- d = -d;
424
- }
425
- if (0 != e) {
426
- if (eneg) {
427
- e = -e;
428
- }
429
- d *= pow(10.0, e);
430
- }
431
- leaf->value = rb_float_new(d);
432
- }
433
- leaf->value_type = RUBY_VAL;
434
- }
435
- #else
436
353
  static void
437
354
  leaf_float_value(Leaf leaf) {
438
355
  leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1));
439
356
  leaf->value_type = RUBY_VAL;
440
357
  }
441
- #endif
442
358
 
443
359
  static VALUE
444
360
  leaf_array_value(Doc doc, Leaf leaf) {
@@ -467,9 +383,7 @@ leaf_hash_value(Doc doc, Leaf leaf) {
467
383
 
468
384
  do {
469
385
  key = rb_str_new2(e->key);
470
- #if HAS_ENCODING_SUPPORT
471
- rb_enc_associate(key, oj_utf8_encoding);
472
- #endif
386
+ key = oj_encode(key);
473
387
  rb_hash_aset(h, key, leaf_value(doc, e));
474
388
  e = e->next;
475
389
  } while (e != first);
@@ -695,33 +609,59 @@ read_nil(ParseInfo pi) {
695
609
  return leaf;
696
610
  }
697
611
 
698
- static char
699
- read_hex(ParseInfo pi, char *h) {
700
- uint8_t b = 0;
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
+ }
701
631
 
702
- if ('0' <= *h && *h <= '9') {
703
- b = *h - '0';
704
- } else if ('A' <= *h && *h <= 'F') {
705
- b = *h - 'A' + 10;
706
- } else if ('a' <= *h && *h <= 'f') {
707
- b = *h - 'a' + 10;
708
- } else {
709
- pi->s = h;
710
- raise_error("invalid hex character", pi->str, pi->s);
711
- }
712
- h++;
713
- b = b << 4;
714
- if ('0' <= *h && *h <= '9') {
715
- b += *h - '0';
716
- } else if ('A' <= *h && *h <= 'F') {
717
- b += *h - 'A' + 10;
718
- } else if ('a' <= *h && *h <= 'f') {
719
- b += *h - 'a' + 10;
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);
720
661
  } else {
721
- pi->s = h;
722
- raise_error("invalid hex character", pi->str, pi->s);
662
+ raise_error("invalid Unicode character", pi->str, pi->s);
723
663
  }
724
- return (char)b;
664
+ return t;
725
665
  }
726
666
 
727
667
  /* Assume the value starts immediately and goes until the quote character is
@@ -751,16 +691,31 @@ read_quoted_value(ParseInfo pi) {
751
691
  case '"': *t = '"'; break;
752
692
  case '/': *t = '/'; break;
753
693
  case '\\': *t = '\\'; break;
754
- case 'u':
694
+ case 'u': {
695
+ uint32_t code;
696
+
755
697
  h++;
756
- *t = read_hex(pi, h);
757
- h += 2;
758
- if ('\0' != *t) {
759
- t++;
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;
760
714
  }
761
- *t = read_hex(pi, h);
762
- h++;
715
+ t = unicode_to_chars(pi, t, code);
716
+ t--;
763
717
  break;
718
+ }
764
719
  default:
765
720
  pi->s = h;
766
721
  raise_error("invalid escaped character", pi->str, pi->s);
@@ -779,15 +734,10 @@ read_quoted_value(ParseInfo pi) {
779
734
  // doc support functions
780
735
  inline static void
781
736
  doc_init(Doc doc) {
737
+ memset(doc, 0, sizeof(struct _Doc));
782
738
  doc->where = doc->where_path;
783
- *doc->where = 0;
784
- doc->data = 0;
785
739
  doc->self = Qundef;
786
- doc->size = 0;
787
- doc->json = 0;
788
740
  doc->batches = &doc->batch0;
789
- doc->batch0.next = 0;
790
- doc->batch0.next_avail = 0;
791
741
  }
792
742
 
793
743
  static void
@@ -863,7 +813,11 @@ parse_json(VALUE clas, char *json, int given, int allocated) {
863
813
  }
864
814
  #endif
865
815
  // last arg is free func void* func(void*)
816
+ #if HAS_DATA_OBJECT_WRAP
817
+ doc->self = rb_data_object_wrap(clas, doc, 0, free_doc_cb);
818
+ #else
866
819
  doc->self = rb_data_object_alloc(clas, doc, 0, free_doc_cb);
820
+ #endif
867
821
  rb_gc_register_address(&doc->self);
868
822
  doc->json = json;
869
823
  DATA_PTR(doc->self) = doc;
@@ -902,7 +856,10 @@ get_doc_leaf(Doc doc, const char *path) {
902
856
  } else {
903
857
  size_t cnt = doc->where - doc->where_path;
904
858
 
905
- memcpy(stack, doc->where_path, sizeof(Leaf) * cnt);
859
+ if (MAX_STACK <= cnt) {
860
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
861
+ }
862
+ memcpy(stack, doc->where_path, sizeof(Leaf) * (cnt + 1));
906
863
  lp = stack + cnt;
907
864
  }
908
865
  return get_leaf(stack, lp, path);
@@ -910,6 +867,35 @@ get_doc_leaf(Doc doc, const char *path) {
910
867
  return leaf;
911
868
  }
912
869
 
870
+ static const char*
871
+ next_slash(const char *s) {
872
+ for (; '\0' != *s; s++) {
873
+ if ('\\' == *s) {
874
+ s++;
875
+ if ('\0' == *s) {
876
+ break;
877
+ }
878
+ } else if ('/' == *s) {
879
+ return s;
880
+ }
881
+ }
882
+ return NULL;
883
+ }
884
+
885
+ static bool
886
+ key_match(const char *pat, const char *key, int plen) {
887
+ for (; 0 < plen; plen--, pat++, key++) {
888
+ if ('\\' == *pat) {
889
+ plen--;
890
+ pat++;
891
+ }
892
+ if (*pat != *key) {
893
+ return false;
894
+ }
895
+ }
896
+ return '\0' == *key;
897
+ }
898
+
913
899
  static Leaf
914
900
  get_leaf(Leaf *stack, Leaf *lp, const char *path) {
915
901
  Leaf leaf = *lp;
@@ -931,7 +917,7 @@ get_leaf(Leaf *stack, Leaf *lp, const char *path) {
931
917
  } else if (COL_VAL == leaf->value_type && 0 != leaf->elements) {
932
918
  Leaf first = leaf->elements->next;
933
919
  Leaf e = first;
934
- int type = leaf->type;
920
+ int type = leaf->rtype;
935
921
 
936
922
  leaf = 0;
937
923
  if (T_ARRAY == type) {
@@ -955,7 +941,7 @@ get_leaf(Leaf *stack, Leaf *lp, const char *path) {
955
941
  } while (e != first);
956
942
  } else if (T_HASH == type) {
957
943
  const char *key = path;
958
- const char *slash = strchr(path, '/');
944
+ const char *slash = next_slash(path);
959
945
  int klen;
960
946
 
961
947
  if (0 == slash) {
@@ -966,7 +952,7 @@ get_leaf(Leaf *stack, Leaf *lp, const char *path) {
966
952
  path += klen + 1;
967
953
  }
968
954
  do {
969
- if (0 == strncmp(key, e->key, klen) && '\0' == e->key[klen]) {
955
+ if (key_match(key, e->key, klen)) {
970
956
  lp++;
971
957
  *lp = e;
972
958
  leaf = get_leaf(stack, lp, path);
@@ -988,11 +974,15 @@ each_leaf(Doc doc, VALUE self) {
988
974
  Leaf e = first;
989
975
 
990
976
  doc->where++;
977
+ if (MAX_STACK <= doc->where - doc->where_path) {
978
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
979
+ }
991
980
  do {
992
981
  *doc->where = e;
993
982
  each_leaf(doc, self);
994
983
  e = e->next;
995
984
  } while (e != first);
985
+ doc->where--;
996
986
  }
997
987
  } else {
998
988
  rb_yield(self);
@@ -1034,7 +1024,7 @@ move_step(Doc doc, const char *path, int loc) {
1034
1024
  Leaf first = leaf->elements->next;
1035
1025
  Leaf e = first;
1036
1026
 
1037
- if (T_ARRAY == leaf->type) {
1027
+ if (T_ARRAY == leaf->rtype) {
1038
1028
  int cnt = 0;
1039
1029
 
1040
1030
  for (; '0' <= *path && *path <= '9'; path++) {
@@ -1059,9 +1049,9 @@ move_step(Doc doc, const char *path, int loc) {
1059
1049
  cnt--;
1060
1050
  e = e->next;
1061
1051
  } while (e != first);
1062
- } else if (T_HASH == leaf->type) {
1052
+ } else if (T_HASH == leaf->rtype) {
1063
1053
  const char *key = path;
1064
- const char *slash = strchr(path, '/');
1054
+ const char *slash = next_slash(path);
1065
1055
  int klen;
1066
1056
 
1067
1057
  if (0 == slash) {
@@ -1072,7 +1062,7 @@ move_step(Doc doc, const char *path, int loc) {
1072
1062
  path += klen + 1;
1073
1063
  }
1074
1064
  do {
1075
- if (0 == strncmp(key, e->key, klen) && '\0' == e->key[klen]) {
1065
+ if (key_match(key, e->key, klen)) {
1076
1066
  doc->where++;
1077
1067
  *doc->where = e;
1078
1068
  loc = move_step(doc, path, loc + 1);
@@ -1109,14 +1099,14 @@ each_value(Doc doc, Leaf leaf) {
1109
1099
 
1110
1100
  // doc functions
1111
1101
 
1112
- /* call-seq: open(json) { |doc| ... } => Object
1102
+ /* @overload open(json) { |doc| ... } => Object
1113
1103
  *
1114
1104
  * Parses a JSON document String and then yields to the provided block if one
1115
1105
  * is given with an instance of the Oj::Doc as the single yield parameter. If
1116
1106
  * a block is not given then an Oj::Doc instance is returned and must be
1117
1107
  * closed with a call to the #close() method when no longer needed.
1118
1108
  *
1119
- * @param [String] json JSON document string
1109
+ * @param [String] json JSON document string
1120
1110
  * @yieldparam [Oj::Doc] doc parsed JSON document
1121
1111
  * @yieldreturn [Object] returns the result of the yield as the result of the method call
1122
1112
  * @example
@@ -1150,14 +1140,14 @@ doc_open(VALUE clas, VALUE str) {
1150
1140
  return obj;
1151
1141
  }
1152
1142
 
1153
- /* call-seq: open_file(filename) { |doc| ... } => Object
1143
+ /* @overload open_file(filename) { |doc| ... } => Object
1154
1144
  *
1155
1145
  * Parses a JSON document from a file and then yields to the provided block if
1156
1146
  * one is given with an instance of the Oj::Doc as the single yield
1157
1147
  * parameter. If a block is not given then an Oj::Doc instance is returned and
1158
1148
  * must be closed with a call to the #close() method when no longer needed.
1159
1149
  *
1160
- * @param [String] filename name of file that contains a JSON document
1150
+ * @param [String] filename name of file that contains a JSON document
1161
1151
  * @yieldparam [Oj::Doc] doc parsed JSON document
1162
1152
  * @yieldreturn [Object] returns the result of the yield as the result of the method call
1163
1153
  * @example
@@ -1206,11 +1196,34 @@ doc_open_file(VALUE clas, VALUE filename) {
1206
1196
  return obj;
1207
1197
  }
1208
1198
 
1199
+ static int
1200
+ esc_strlen(const char *s) {
1201
+ int cnt = 0;
1202
+
1203
+ for (; '\0' != *s; s++, cnt++) {
1204
+ if ('/' == *s) {
1205
+ cnt++;
1206
+ }
1207
+ }
1208
+ return cnt;
1209
+ }
1210
+
1211
+ static char*
1212
+ append_key(char *p, const char *key) {
1213
+ for (; '\0' != *key; p++, key++) {
1214
+ if ('/' == *key) {
1215
+ *p++ = '\\';
1216
+ }
1217
+ *p = *key;
1218
+ }
1219
+ return p;
1220
+ }
1221
+
1209
1222
  /* Document-method: parse
1210
1223
  * @see Oj::Doc.open
1211
1224
  */
1212
1225
 
1213
- /* call-seq: where?() => String
1226
+ /* @overload where?() => String
1214
1227
  *
1215
1228
  * Returns a String that describes the absolute path to the current location
1216
1229
  * in the JSON document.
@@ -1231,7 +1244,7 @@ doc_where(VALUE self) {
1231
1244
  for (lp = doc->where_path; lp <= doc->where; lp++) {
1232
1245
  leaf = *lp;
1233
1246
  if (T_HASH == leaf->parent_type) {
1234
- size += strlen((*lp)->key) + 1;
1247
+ size += esc_strlen((*lp)->key) + 1;
1235
1248
  } else if (T_ARRAY == leaf->parent_type) {
1236
1249
  size += ((*lp)->index < 100) ? 3 : 11;
1237
1250
  }
@@ -1241,7 +1254,7 @@ doc_where(VALUE self) {
1241
1254
  for (lp = doc->where_path; lp <= doc->where; lp++) {
1242
1255
  leaf = *lp;
1243
1256
  if (T_HASH == leaf->parent_type) {
1244
- p = stpcpy(p, (*lp)->key);
1257
+ p = append_key(p, (*lp)->key);
1245
1258
  } else if (T_ARRAY == leaf->parent_type) {
1246
1259
  p = ulong_fill(p, (*lp)->index);
1247
1260
  }
@@ -1252,7 +1265,7 @@ doc_where(VALUE self) {
1252
1265
  }
1253
1266
  }
1254
1267
 
1255
- /* call-seq: local_key() => String, Fixnum, nil
1268
+ /* @overload local_key() => String, Fixnum, nil
1256
1269
  *
1257
1270
  * Returns the final key to the current location.
1258
1271
  * @example
@@ -1268,16 +1281,14 @@ doc_local_key(VALUE self) {
1268
1281
 
1269
1282
  if (T_HASH == leaf->parent_type) {
1270
1283
  key = rb_str_new2(leaf->key);
1271
- #if HAS_ENCODING_SUPPORT
1272
- rb_enc_associate(key, oj_utf8_encoding);
1273
- #endif
1284
+ key = oj_encode(key);
1274
1285
  } else if (T_ARRAY == leaf->parent_type) {
1275
1286
  key = LONG2NUM(leaf->index);
1276
1287
  }
1277
1288
  return key;
1278
1289
  }
1279
1290
 
1280
- /* call-seq: home() => nil
1291
+ /* @overload home() => nil
1281
1292
  *
1282
1293
  * Moves the document marker or location to the hoot or home position. The
1283
1294
  * same operation can be performed with a Oj::Doc.move('/').
@@ -1294,13 +1305,13 @@ doc_home(VALUE self) {
1294
1305
  return oj_slash_string;
1295
1306
  }
1296
1307
 
1297
- /* call-seq: type(path=nil) => Class
1308
+ /* @overload type(path=nil) => Class
1298
1309
  *
1299
1310
  * Returns the Class of the data value at the location identified by the path
1300
1311
  * or the current location if the path is nil or not provided. This method
1301
1312
  * does not create the Ruby Object at the location specified so the overhead
1302
1313
  * is low.
1303
- * @param [String] path path to the location to get the type of if provided
1314
+ * @param [String] path path to the location to get the type of if provided
1304
1315
  * @example
1305
1316
  * Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array
1306
1317
  * Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum
@@ -1317,12 +1328,16 @@ doc_type(int argc, VALUE *argv, VALUE self) {
1317
1328
  path = StringValuePtr(*argv);
1318
1329
  }
1319
1330
  if (0 != (leaf = get_doc_leaf(doc, path))) {
1320
- switch (leaf->type) {
1331
+ switch (leaf->rtype) {
1321
1332
  case T_NIL: type = rb_cNilClass; break;
1322
1333
  case T_TRUE: type = rb_cTrueClass; break;
1323
1334
  case T_FALSE: type = rb_cFalseClass; break;
1324
1335
  case T_STRING: type = rb_cString; break;
1336
+ #ifdef RUBY_INTEGER_UNIFICATION
1337
+ case T_FIXNUM: type = rb_cInteger; break;
1338
+ #else
1325
1339
  case T_FIXNUM: type = rb_cFixnum; break;
1340
+ #endif
1326
1341
  case T_FLOAT: type = rb_cFloat; break;
1327
1342
  case T_ARRAY: type = rb_cArray; break;
1328
1343
  case T_HASH: type = rb_cHash; break;
@@ -1332,14 +1347,14 @@ doc_type(int argc, VALUE *argv, VALUE self) {
1332
1347
  return type;
1333
1348
  }
1334
1349
 
1335
- /* call-seq: fetch(path=nil) => nil, true, false, Fixnum, Float, String, Array, Hash
1350
+ /* @overload fetch(path=nil) => nil, true, false, Fixnum, Float, String, Array, Hash
1336
1351
  *
1337
1352
  * Returns the value at the location identified by the path or the current
1338
1353
  * location if the path is nil or not provided. This method will create and
1339
1354
  * return an Array or Hash if that is the type of Object at the location
1340
1355
  * specified. This is more expensive than navigating to the leaves of the JSON
1341
1356
  * document.
1342
- * @param [String] path path to the location to get the type of if provided
1357
+ * @param [String] path path to the location to get the type of if provided
1343
1358
  * @example
1344
1359
  * Oj::Doc.open('[1,2]') { |doc| doc.fetch() } #=> [1, 2]
1345
1360
  * Oj::Doc.open('[1,2]') { |doc| doc.fetch('/1') } #=> 1
@@ -1365,12 +1380,12 @@ doc_fetch(int argc, VALUE *argv, VALUE self) {
1365
1380
  return val;
1366
1381
  }
1367
1382
 
1368
- /* call-seq: each_leaf(path=nil) => nil
1383
+ /* @overload each_leaf(path=nil) => nil
1369
1384
  *
1370
1385
  * Yields to the provided block for each leaf node with the identified
1371
1386
  * location of the JSON document as the root. The parameter passed to the
1372
1387
  * block on yield is the Doc instance after moving to the child location.
1373
- * @param [String] path if provided it identified the top of the branch to process the leaves of
1388
+ * @param [String] path if provided it identified the top of the branch to process the leaves of
1374
1389
  * @yieldparam [Doc] Doc at the child location
1375
1390
  * @example
1376
1391
  * Oj::Doc.open('[3,[2,1]]') { |doc|
@@ -1390,7 +1405,7 @@ doc_each_leaf(int argc, VALUE *argv, VALUE self) {
1390
1405
 
1391
1406
  wlen = doc->where - doc->where_path;
1392
1407
  if (0 < wlen) {
1393
- memcpy(save_path, doc->where_path, sizeof(Leaf) * wlen);
1408
+ memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
1394
1409
  }
1395
1410
  if (1 <= argc) {
1396
1411
  Check_Type(*argv, T_STRING);
@@ -1401,24 +1416,24 @@ doc_each_leaf(int argc, VALUE *argv, VALUE self) {
1401
1416
  }
1402
1417
  if (0 != move_step(doc, path, 1)) {
1403
1418
  if (0 < wlen) {
1404
- memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
1419
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1405
1420
  }
1406
1421
  return Qnil;
1407
1422
  }
1408
1423
  }
1409
1424
  each_leaf(doc, self);
1410
1425
  if (0 < wlen) {
1411
- memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
1426
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1412
1427
  }
1413
1428
  }
1414
1429
  return Qnil;
1415
1430
  }
1416
1431
 
1417
- /* call-seq: move(path) => nil
1432
+ /* @overload move(path) => nil
1418
1433
  *
1419
1434
  * Moves the document marker to the path specified. The path can an absolute
1420
1435
  * path or a relative path.
1421
- * @param [String] path path to the location to move to
1436
+ * @param [String] path path to the location to move to
1422
1437
  * @example
1423
1438
  * Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=> "/one/2"
1424
1439
  */
@@ -1440,13 +1455,13 @@ doc_move(VALUE self, VALUE str) {
1440
1455
  return Qnil;
1441
1456
  }
1442
1457
 
1443
- /* call-seq: each_child(path=nil) { |doc| ... } => nil
1458
+ /* @overload each_child(path=nil) { |doc| ... } => nil
1444
1459
  *
1445
1460
  * Yields to the provided block for each immediate child node with the
1446
1461
  * identified location of the JSON document as the root. The parameter passed
1447
1462
  * to the block on yield is the Doc instance after moving to the child
1448
1463
  * location.
1449
- * @param [String] path if provided it identified the top of the branch to process the chilren of
1464
+ * @param [String] path if provided it identified the top of the branch to process the chilren of
1450
1465
  * @yieldparam [Doc] Doc at the child location
1451
1466
  * @example
1452
1467
  * Oj::Doc.open('[3,[2,1]]') { |doc|
@@ -1466,7 +1481,7 @@ doc_each_child(int argc, VALUE *argv, VALUE self) {
1466
1481
 
1467
1482
  wlen = doc->where - doc->where_path;
1468
1483
  if (0 < wlen) {
1469
- memcpy(save_path, doc->where_path, sizeof(Leaf) * wlen);
1484
+ memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
1470
1485
  }
1471
1486
  if (1 <= argc) {
1472
1487
  Check_Type(*argv, T_STRING);
@@ -1477,7 +1492,7 @@ doc_each_child(int argc, VALUE *argv, VALUE self) {
1477
1492
  }
1478
1493
  if (0 != move_step(doc, path, 1)) {
1479
1494
  if (0 < wlen) {
1480
- memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
1495
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1481
1496
  }
1482
1497
  return Qnil;
1483
1498
  }
@@ -1494,19 +1509,19 @@ doc_each_child(int argc, VALUE *argv, VALUE self) {
1494
1509
  } while (e != first);
1495
1510
  }
1496
1511
  if (0 < wlen) {
1497
- memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen);
1512
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1498
1513
  }
1499
1514
  }
1500
1515
  return Qnil;
1501
1516
  }
1502
1517
 
1503
- /* call-seq: each_value(path=nil) { |val| ... } => nil
1518
+ /* @overload each_value(path=nil) { |val| ... } => nil
1504
1519
  *
1505
1520
  * Yields to the provided block for each leaf value in the identified location
1506
1521
  * of the JSON document. The parameter passed to the block on yield is the
1507
1522
  * value of the leaf. Only those leaves below the element specified by the
1508
1523
  * path parameter are processed.
1509
- * @param [String] path if provided it identified the top of the branch to process the leaf values of
1524
+ * @param [String] path if provided it identified the top of the branch to process the leaf values of
1510
1525
  * @yieldparam [Object] val each leaf value
1511
1526
  * @example
1512
1527
  * Oj::Doc.open('[3,[2,1]]') { |doc|
@@ -1541,12 +1556,12 @@ doc_each_value(int argc, VALUE *argv, VALUE self) {
1541
1556
  return Qnil;
1542
1557
  }
1543
1558
 
1544
- /* call-seq: dump(path=nil) => String
1559
+ /* @overload dump(path, filename)
1545
1560
  *
1546
1561
  * Dumps the document or nodes to a new JSON document. It uses the default
1547
1562
  * options for generating the JSON.
1548
- * @param [String] path if provided it identified the top of the branch to dump to JSON
1549
- * @param [String] filename if provided it is the filename to write the output to
1563
+ * @param path [String] if provided it identified the top of the branch to dump to JSON
1564
+ * @param filename [String] if provided it is the filename to write the output to
1550
1565
  * @example
1551
1566
  * Oj::Doc.open('[3,[2,1]]') { |doc|
1552
1567
  * doc.dump('/2')
@@ -1571,13 +1586,21 @@ doc_dump(int argc, VALUE *argv, VALUE self) {
1571
1586
  }
1572
1587
  }
1573
1588
  if (0 != (leaf = get_doc_leaf(doc, path))) {
1574
- char *json;
1575
1589
  VALUE rjson;
1576
1590
 
1577
1591
  if (0 == filename) {
1578
- json = oj_write_leaf_to_str(leaf, &oj_default_options);
1579
- rjson = rb_str_new2(json);
1580
- xfree(json);
1592
+ char buf[4096];
1593
+ struct _Out out;
1594
+
1595
+ out.buf = buf;
1596
+ out.end = buf + sizeof(buf) - 10;
1597
+ out.allocated = 0;
1598
+ out.omit_nil = oj_default_options.dump_opts.omit_nil;
1599
+ oj_dump_leaf_to_json(leaf, &oj_default_options, &out);
1600
+ rjson = rb_str_new2(out.buf);
1601
+ if (out.allocated) {
1602
+ xfree(out.buf);
1603
+ }
1581
1604
  } else {
1582
1605
  oj_write_leaf_to_file(leaf, filename, &oj_default_options);
1583
1606
  rjson = Qnil;
@@ -1587,7 +1610,7 @@ doc_dump(int argc, VALUE *argv, VALUE self) {
1587
1610
  return Qnil;
1588
1611
  }
1589
1612
 
1590
- /* call-seq: size() => Fixnum
1613
+ /* @overload size() => Fixnum
1591
1614
  *
1592
1615
  * Returns the number of nodes in the JSON document where a node is any one of
1593
1616
  * the basic JSON components.
@@ -1600,7 +1623,7 @@ doc_size(VALUE self) {
1600
1623
  return ULONG2NUM(((Doc)DATA_PTR(self))->size);
1601
1624
  }
1602
1625
 
1603
- /* call-seq: close() => nil
1626
+ /* @overload close() => nil
1604
1627
  *
1605
1628
  * Closes an open document. No further calls to the document will be valid
1606
1629
  * after closing.
@@ -1618,6 +1641,7 @@ doc_close(VALUE self) {
1618
1641
  if (0 != doc) {
1619
1642
  xfree(doc->json);
1620
1643
  doc_free(doc);
1644
+ xfree(doc);
1621
1645
  }
1622
1646
  return Qnil;
1623
1647
  }
@@ -1626,6 +1650,12 @@ doc_close(VALUE self) {
1626
1650
  Oj = rb_define_module("Oj");
1627
1651
  #endif
1628
1652
 
1653
+ static VALUE
1654
+ doc_not_implemented(VALUE self) {
1655
+ rb_raise(rb_eNotImpError, "Not implemented.");
1656
+ return Qnil;
1657
+ }
1658
+
1629
1659
  /* Document-class: Oj::Doc
1630
1660
  *
1631
1661
  * The Doc class is used to parse and navigate a JSON document. The model it
@@ -1690,4 +1720,7 @@ oj_init_doc() {
1690
1720
  rb_define_method(oj_doc_class, "dump", doc_dump, -1);
1691
1721
  rb_define_method(oj_doc_class, "size", doc_size, 0);
1692
1722
  rb_define_method(oj_doc_class, "close", doc_close, 0);
1723
+
1724
+ rb_define_method(oj_doc_class, "clone", doc_not_implemented, 0);
1725
+ rb_define_method(oj_doc_class, "dup", doc_not_implemented, 0);
1693
1726
  }