oj 2.0.0 → 3.0.0

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.
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
  }