ox 2.14.14 → 2.14.17

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.
data/ext/ox/sax.c CHANGED
@@ -55,7 +55,7 @@ static char read_text(SaxDrive dr);
55
55
  static char read_jump(SaxDrive dr, const char *pat);
56
56
  static char read_attrs(SaxDrive dr, char c, char termc, char term2, int is_xml, int eq_req, Hint h);
57
57
  static char read_name_token(SaxDrive dr);
58
- static char read_quoted_value(SaxDrive dr);
58
+ static char read_quoted_value(SaxDrive dr, bool inst);
59
59
 
60
60
  static void hint_clear_empty(SaxDrive dr);
61
61
  static Nv hint_try_close(SaxDrive dr, const char *name);
@@ -469,7 +469,7 @@ DONE:
469
469
  Nv sp;
470
470
 
471
471
  for (sp = dr->stack.tail - 1; dr->stack.head <= sp; sp--) {
472
- snprintf(msg, sizeof(msg) - 1, "%selement '%s' not closed", EL_MISMATCH, sp->name);
472
+ snprintf(msg, sizeof(msg) - 1, "%selement '%s' not closed", EL_MISMATCH, nv_name(sp));
473
473
  ox_sax_drive_error_at(dr, msg, dr->buf.pos, dr->buf.line, dr->buf.col);
474
474
  end_element_cb(dr, sp->val, dr->buf.pos, dr->buf.line, dr->buf.col, sp->hint);
475
475
  }
@@ -761,17 +761,19 @@ CB:
761
761
  * code.
762
762
  */
763
763
  static char read_element_start(SaxDrive dr) {
764
- const char *ename = 0;
764
+ const char *ename = NULL;
765
+ char ebuf[128];
765
766
  size_t nlen;
766
- volatile VALUE name = Qnil;
767
+ volatile VALUE name = Qnil;
767
768
  char c;
768
- int closed;
769
769
  long pos = (long)(dr->buf.pos);
770
770
  long line = (long)(dr->buf.line);
771
771
  long col = (long)(dr->buf.col);
772
772
  Hint h = NULL;
773
773
  int stackless = 0;
774
774
  Nv parent = stack_peek(&dr->stack);
775
+ bool closed;
776
+ bool efree = false;
775
777
 
776
778
  if ('\0' == (c = read_name_token(dr))) {
777
779
  return '\0';
@@ -827,7 +829,7 @@ static char read_element_start(SaxDrive dr) {
827
829
  if (0 != top_nv) {
828
830
  char msg[256];
829
831
 
830
- if (!h->nest && NestOverlay != h->overlay && 0 == strcasecmp(top_nv->name, h->name)) {
832
+ if (!h->nest && NestOverlay != h->overlay && nv_same_name(top_nv, h->name, true)) {
831
833
  snprintf(msg,
832
834
  sizeof(msg) - 1,
833
835
  "%s%s can not be nested in a %s document, closing previous.",
@@ -844,7 +846,7 @@ static char read_element_start(SaxDrive dr) {
844
846
  int ok = 0;
845
847
 
846
848
  for (p = h->parents; 0 != *p; p++) {
847
- if (0 == strcasecmp(*p, top_nv->name)) {
849
+ if (nv_same_name(top_nv, *p, true)) {
848
850
  ok = 1;
849
851
  break;
850
852
  }
@@ -855,7 +857,7 @@ static char read_element_start(SaxDrive dr) {
855
857
  "%s%s can not be a child of a %s in a %s document.",
856
858
  INV_ELEMENT,
857
859
  h->name,
858
- top_nv->name,
860
+ nv_name(top_nv),
859
861
  dr->options.hints->name);
860
862
  ox_sax_drive_error(dr, msg);
861
863
  }
@@ -864,6 +866,16 @@ static char read_element_start(SaxDrive dr) {
864
866
  }
865
867
  }
866
868
  name = str2sym(dr, dr->buf.str, nlen, &ename);
869
+ if (NULL == ename) {
870
+ if (sizeof(ebuf) <= nlen) {
871
+ ename = ox_strndup(dr->buf.str, nlen);
872
+ efree = true;
873
+ } else {
874
+ memcpy(ebuf, dr->buf.str, nlen);
875
+ ebuf[nlen] = '\0';
876
+ ename = ebuf;
877
+ }
878
+ }
867
879
  if (dr->has_start_element && 0 >= dr->blocked &&
868
880
  (NULL == h || ActiveOverlay == h->overlay || NestOverlay == h->overlay)) {
869
881
  VALUE args[1];
@@ -875,9 +887,9 @@ static char read_element_start(SaxDrive dr) {
875
887
  rb_funcall2(dr->handler, ox_start_element_id, 1, args);
876
888
  }
877
889
  if ('/' == c) {
878
- closed = 1;
890
+ closed = true;
879
891
  } else if ('>' == c) {
880
- closed = 0;
892
+ closed = false;
881
893
  } else {
882
894
  buf_protect(&dr->buf);
883
895
  c = read_attrs(dr, c, '/', '>', 0, 0, h);
@@ -906,11 +918,14 @@ static char read_element_start(SaxDrive dr) {
906
918
  } else {
907
919
  stack_push(&dr->stack, ename, nlen, name, h);
908
920
  }
921
+ if (efree) {
922
+ free((char *)ename);
923
+ }
909
924
  if ('>' != c) {
910
925
  ox_sax_drive_error(dr, WRONG_CHAR "element not closed");
911
926
  return c;
912
927
  }
913
- dr->buf.str = 0;
928
+ dr->buf.str = NULL;
914
929
 
915
930
  return buf_get(&dr->buf);
916
931
  }
@@ -919,7 +934,7 @@ static Nv stack_rev_find(SaxDrive dr, const char *name) {
919
934
  Nv nv;
920
935
 
921
936
  for (nv = dr->stack.tail - 1; dr->stack.head <= nv; nv--) {
922
- if (0 == (dr->options.smart ? strcasecmp(name, nv->name) : strcmp(name, nv->name))) {
937
+ if (nv_same_name(nv, name, dr->options.smart)) {
923
938
  return nv;
924
939
  }
925
940
  }
@@ -944,7 +959,7 @@ static char read_element_end(SaxDrive dr) {
944
959
  // c should be > and current is one past so read another char
945
960
  c = buf_get(&dr->buf);
946
961
  nv = stack_peek(&dr->stack);
947
- if (0 != nv && 0 == (dr->options.smart ? strcasecmp(dr->buf.str, nv->name) : strcmp(dr->buf.str, nv->name))) {
962
+ if (0 != nv && nv_same_name(nv, dr->buf.str, dr->options.smart)) {
948
963
  name = nv->val;
949
964
  h = nv->hint;
950
965
  stack_pop(&dr->stack);
@@ -997,7 +1012,7 @@ static char read_element_end(SaxDrive dr) {
997
1012
  "%selement '%s' close does not match '%s' open",
998
1013
  EL_MISMATCH,
999
1014
  dr->buf.str,
1000
- nv->name);
1015
+ nv_name(nv));
1001
1016
  ox_sax_drive_error_at(dr, msg, pos, line, col);
1002
1017
  for (nv = stack_pop(&dr->stack); match < nv; nv = stack_pop(&dr->stack)) {
1003
1018
  end_element_cb(dr, nv->val, pos, line, col, nv->hint);
@@ -1204,6 +1219,7 @@ static char read_attrs(SaxDrive dr, char c, char termc, char term2, int is_xml,
1204
1219
  c = buf_next_non_white(&dr->buf);
1205
1220
  }
1206
1221
  if ('=' != c) {
1222
+ // TBD allow in smart mode
1207
1223
  if (eq_req) {
1208
1224
  dr->err = 1;
1209
1225
  return c;
@@ -1215,7 +1231,7 @@ static char read_attrs(SaxDrive dr, char c, char termc, char term2, int is_xml,
1215
1231
  pos = dr->buf.pos + 1;
1216
1232
  line = dr->buf.line;
1217
1233
  col = dr->buf.col + 1;
1218
- c = read_quoted_value(dr);
1234
+ c = read_quoted_value(dr, '?' == termc);
1219
1235
  attr_value = dr->buf.str;
1220
1236
 
1221
1237
  if (is_encoding) {
@@ -1282,10 +1298,11 @@ static char read_name_token(SaxDrive dr) {
1282
1298
  return '\0';
1283
1299
  }
1284
1300
 
1285
- /* The character after the quote or if there is no quote, the character after the word is returned. dr->buf.tail is one
1286
- * past that. dr->buf.str will point to the token which will be '\0' terminated.
1301
+ /* The character after the quote or if there is no quote, the character after
1302
+ * the word is returned. dr->buf.tail is one past that. dr->buf.str will point
1303
+ * to the token which will be '\0' terminated.
1287
1304
  */
1288
- static char read_quoted_value(SaxDrive dr) {
1305
+ static char read_quoted_value(SaxDrive dr, bool inst) {
1289
1306
  char c;
1290
1307
 
1291
1308
  c = buf_get(&dr->buf);
@@ -1309,19 +1326,27 @@ static char read_quoted_value(SaxDrive dr) {
1309
1326
  }
1310
1327
  // not quoted, look for something that terminates the string
1311
1328
  dr->buf.str = dr->buf.tail - 1;
1312
- ox_sax_drive_error(dr, WRONG_CHAR "attribute value not in quotes");
1329
+ // TBD if smart or html then no error
1330
+ if (!(dr->options.smart && ox_hints_html() != dr->options.hints)) {
1331
+ ox_sax_drive_error(dr, WRONG_CHAR "attribute value not in quotes");
1332
+ }
1313
1333
  while ('\0' != (c = buf_get(&dr->buf))) {
1314
1334
  switch (c) {
1315
1335
  case ' ':
1316
1336
  // case '/':
1317
1337
  case '>':
1318
- case '?': // for instructions
1319
1338
  case '\t':
1320
1339
  case '\n':
1321
1340
  case '\r':
1322
1341
  *(dr->buf.tail - 1) = '\0'; /* terminate value */
1323
1342
  // dr->buf.tail is in the correct position, one after the word terminator
1324
1343
  return c;
1344
+ case '?': // for instructions
1345
+ if (inst) {
1346
+ *(dr->buf.tail - 1) = '\0'; /* terminate value */
1347
+ return c;
1348
+ }
1349
+ break;
1325
1350
  default: break;
1326
1351
  }
1327
1352
  }
@@ -1465,18 +1490,18 @@ int ox_sax_collapse_special(SaxDrive dr, char *str, long pos, long line, long co
1465
1490
  break;
1466
1491
  }
1467
1492
  case '\r':
1468
- s++;
1493
+ s++;
1469
1494
  if ('\n' == *s) {
1470
- continue;
1471
- }
1472
- line++;
1473
- col = 1;
1495
+ continue;
1496
+ }
1497
+ line++;
1498
+ col = 1;
1474
1499
  *b++ = '\n';
1475
- break;
1476
- case '\n':
1477
- line++;
1478
- col = 0;
1479
- // fall through
1500
+ break;
1501
+ case '\n':
1502
+ line++;
1503
+ col = 0;
1504
+ // fall through
1480
1505
  default:
1481
1506
  col++;
1482
1507
  *b++ = *s++;
@@ -1512,7 +1537,7 @@ static Nv hint_try_close(SaxDrive dr, const char *name) {
1512
1537
  return 0;
1513
1538
  }
1514
1539
  for (nv = stack_peek(&dr->stack); 0 != nv; nv = stack_peek(&dr->stack)) {
1515
- if (0 == strcasecmp(name, nv->name)) {
1540
+ if (nv_same_name(nv, name, true)) {
1516
1541
  stack_pop(&dr->stack);
1517
1542
  return nv;
1518
1543
  }
data/ext/ox/sax.h CHANGED
@@ -20,7 +20,7 @@ typedef struct _saxOptions {
20
20
  SkipMode skip;
21
21
  char strip_ns[64];
22
22
  Hints hints;
23
- } * SaxOptions;
23
+ } *SaxOptions;
24
24
 
25
25
  typedef struct _saxDrive {
26
26
  struct _buf buf;
@@ -52,7 +52,7 @@ typedef struct _saxDrive {
52
52
  bool has_start_element;
53
53
  bool has_end_element;
54
54
 
55
- } * SaxDrive;
55
+ } *SaxDrive;
56
56
 
57
57
  extern void ox_collapse_return(char *str);
58
58
  extern void ox_sax_parse(VALUE handler, VALUE io, SaxOptions options);
data/ext/ox/sax_as.c CHANGED
@@ -3,27 +3,26 @@
3
3
  * All rights reserved.
4
4
  */
5
5
 
6
- #include <stdlib.h>
7
6
  #include <errno.h>
8
7
  #include <stdio.h>
8
+ #include <stdlib.h>
9
9
  #include <strings.h>
10
10
  #include <sys/types.h>
11
11
  #if HAVE_SYS_UIO_H
12
12
  #include <sys/uio.h>
13
13
  #endif
14
- #include <unistd.h>
15
14
  #include <time.h>
15
+ #include <unistd.h>
16
16
 
17
+ #include "ox.h"
17
18
  #include "ruby.h"
18
19
  #include "ruby/version.h"
19
- #include "ox.h"
20
20
  #include "sax.h"
21
21
 
22
- static VALUE
23
- parse_double_time(const char *text) {
24
- long v = 0;
25
- long v2 = 0;
26
- const char *dot = 0;
22
+ static VALUE parse_double_time(const char *text) {
23
+ long v = 0;
24
+ long v2 = 0;
25
+ const char *dot = 0;
27
26
  char c;
28
27
 
29
28
  for (; '.' != *text; text++) {
@@ -44,101 +43,86 @@ parse_double_time(const char *text) {
44
43
  for (; text - dot <= 9; text++) {
45
44
  v2 *= 10;
46
45
  }
47
- #if HAVE_RB_TIME_NANO_NEW
48
46
  return rb_time_nano_new(v, v2);
49
- #else
50
- return rb_time_new(v, v2 / 1000);
51
- #endif
52
47
  }
53
48
 
54
49
  typedef struct _tp {
55
- int cnt;
56
- char end;
57
- char alt;
50
+ int cnt;
51
+ char end;
52
+ char alt;
58
53
  } *Tp;
59
54
 
60
- static VALUE
61
- parse_xsd_time(const char *text) {
62
- long cargs[10];
63
- long *cp = cargs;
64
- long v;
65
- int i;
66
- char c = '\0';
67
- struct _tp tpa[10] = { { 4, '-', '-' },
68
- { 2, '-', '-' },
69
- { 2, 'T', ' ' },
70
- { 2, ':', ':' },
71
- { 2, ':', ':' },
72
- { 2, '.', '.' },
73
- { 9, '+', '-' },
74
- { 2, ':', ':' },
75
- { 2, '\0', '\0' },
76
- { 0, '\0', '\0' } };
77
- Tp tp = tpa;
78
- struct tm tm;
55
+ static VALUE parse_xsd_time(const char *text) {
56
+ long cargs[10];
57
+ long *cp = cargs;
58
+ long v;
59
+ int i;
60
+ char c = '\0';
61
+ struct _tp tpa[10] = {{4, '-', '-'},
62
+ {2, '-', '-'},
63
+ {2, 'T', ' '},
64
+ {2, ':', ':'},
65
+ {2, ':', ':'},
66
+ {2, '.', '.'},
67
+ {9, '+', '-'},
68
+ {2, ':', ':'},
69
+ {2, '\0', '\0'},
70
+ {0, '\0', '\0'}};
71
+ Tp tp = tpa;
72
+ struct tm tm;
79
73
 
80
74
  memset(cargs, 0, sizeof(cargs));
81
75
  for (; 0 != tp->cnt; tp++) {
82
- for (i = tp->cnt, v = 0; 0 < i ; text++, i--) {
76
+ for (i = tp->cnt, v = 0; 0 < i; text++, i--) {
83
77
  c = *text;
84
78
  if (c < '0' || '9' < c) {
85
79
  if ('\0' == c || tp->end == c || tp->alt == c) {
86
80
  break;
87
81
  }
88
- return Qnil;
82
+ return Qnil;
89
83
  }
90
84
  v = 10 * v + (long)(c - '0');
91
85
  }
92
- if ('\0' == c) {
93
- break;
94
- }
86
+ if ('\0' == c) {
87
+ break;
88
+ }
95
89
  c = *text++;
96
90
  if (tp->end != c && tp->alt != c) {
97
- return Qnil;
91
+ return Qnil;
98
92
  }
99
93
  *cp++ = v;
100
94
  }
101
95
  tm.tm_year = (int)cargs[0] - 1900;
102
- tm.tm_mon = (int)cargs[1] - 1;
96
+ tm.tm_mon = (int)cargs[1] - 1;
103
97
  tm.tm_mday = (int)cargs[2];
104
98
  tm.tm_hour = (int)cargs[3];
105
- tm.tm_min = (int)cargs[4];
106
- tm.tm_sec = (int)cargs[5];
107
- #if HAVE_RB_TIME_NANO_NEW
99
+ tm.tm_min = (int)cargs[4];
100
+ tm.tm_sec = (int)cargs[5];
108
101
  return rb_time_nano_new(mktime(&tm), cargs[6]);
109
- #else
110
- return rb_time_new(mktime(&tm), cargs[6] / 1000);
111
- #endif
112
102
  }
113
103
 
114
104
  /* call-seq: as_s()
115
105
  *
116
106
  * *return* value as an String.
117
107
  */
118
- static VALUE
119
- sax_value_as_s(VALUE self) {
120
- SaxDrive dr = DATA_PTR(self);
121
- VALUE rs;
108
+ static VALUE sax_value_as_s(VALUE self) {
109
+ SaxDrive dr = DATA_PTR(self);
110
+ VALUE rs;
122
111
 
123
112
  if ('\0' == *dr->buf.str) {
124
- return Qnil;
113
+ return Qnil;
125
114
  }
126
115
  if (dr->options.convert_special) {
127
- ox_sax_collapse_special(dr, dr->buf.str, dr->buf.pos, dr->buf.line, dr->buf.col);
116
+ ox_sax_collapse_special(dr, dr->buf.str, dr->buf.pos, dr->buf.line, dr->buf.col);
128
117
  }
129
118
  switch (dr->options.skip) {
130
- case CrSkip:
131
- buf_collapse_return(dr->buf.str);
132
- break;
133
- case SpcSkip:
134
- buf_collapse_white(dr->buf.str);
135
- break;
136
- default:
137
- break;
119
+ case CrSkip: buf_collapse_return(dr->buf.str); break;
120
+ case SpcSkip: buf_collapse_white(dr->buf.str); break;
121
+ default: break;
138
122
  }
139
123
  rs = rb_str_new2(dr->buf.str);
140
124
  if (0 != dr->encoding) {
141
- rb_enc_associate(rs, dr->encoding);
125
+ rb_enc_associate(rs, dr->encoding);
142
126
  }
143
127
  return rs;
144
128
  }
@@ -147,12 +131,11 @@ sax_value_as_s(VALUE self) {
147
131
  *
148
132
  * *return* value as an Symbol.
149
133
  */
150
- static VALUE
151
- sax_value_as_sym(VALUE self) {
152
- SaxDrive dr = DATA_PTR(self);
134
+ static VALUE sax_value_as_sym(VALUE self) {
135
+ SaxDrive dr = DATA_PTR(self);
153
136
 
154
137
  if ('\0' == *dr->buf.str) {
155
- return Qnil;
138
+ return Qnil;
156
139
  }
157
140
  return str2sym(dr, dr->buf.str, strlen(dr->buf.str), 0);
158
141
  }
@@ -161,12 +144,11 @@ sax_value_as_sym(VALUE self) {
161
144
  *
162
145
  * *return* value as an Float.
163
146
  */
164
- static VALUE
165
- sax_value_as_f(VALUE self) {
166
- SaxDrive dr = DATA_PTR(self);
147
+ static VALUE sax_value_as_f(VALUE self) {
148
+ SaxDrive dr = DATA_PTR(self);
167
149
 
168
150
  if ('\0' == *dr->buf.str) {
169
- return Qnil;
151
+ return Qnil;
170
152
  }
171
153
  return rb_float_new(strtod(dr->buf.str, 0));
172
154
  }
@@ -175,31 +157,30 @@ sax_value_as_f(VALUE self) {
175
157
  *
176
158
  * *return* value as an Fixnum.
177
159
  */
178
- static VALUE
179
- sax_value_as_i(VALUE self) {
180
- SaxDrive dr = DATA_PTR(self);
181
- const char *s = dr->buf.str;
182
- long n = 0;
183
- int neg = 0;
160
+ static VALUE sax_value_as_i(VALUE self) {
161
+ SaxDrive dr = DATA_PTR(self);
162
+ const char *s = dr->buf.str;
163
+ long n = 0;
164
+ int neg = 0;
184
165
 
185
166
  if ('\0' == *s) {
186
- return Qnil;
167
+ return Qnil;
187
168
  }
188
169
  if ('-' == *s) {
189
- neg = 1;
190
- s++;
170
+ neg = 1;
171
+ s++;
191
172
  } else if ('+' == *s) {
192
- s++;
173
+ s++;
193
174
  }
194
175
  for (; '\0' != *s; s++) {
195
- if ('0' <= *s && *s <= '9') {
196
- n = n * 10 + (*s - '0');
197
- } else {
198
- rb_raise(ox_arg_error_class, "Not a valid Fixnum.\n");
199
- }
176
+ if ('0' <= *s && *s <= '9') {
177
+ n = n * 10 + (*s - '0');
178
+ } else {
179
+ rb_raise(ox_arg_error_class, "Not a valid Fixnum.\n");
180
+ }
200
181
  }
201
182
  if (neg) {
202
- n = -n;
183
+ n = -n;
203
184
  }
204
185
  return LONG2NUM(n);
205
186
  }
@@ -208,22 +189,20 @@ sax_value_as_i(VALUE self) {
208
189
  *
209
190
  * *return* value as an Time.
210
191
  */
211
- static VALUE
212
- sax_value_as_time(VALUE self) {
213
- SaxDrive dr = DATA_PTR(self);
214
- const char *str = dr->buf.str;
192
+ static VALUE sax_value_as_time(VALUE self) {
193
+ SaxDrive dr = DATA_PTR(self);
194
+ const char *str = dr->buf.str;
215
195
  VALUE t;
216
196
 
217
197
  if ('\0' == *str) {
218
- return Qnil;
198
+ return Qnil;
219
199
  }
220
- if (Qnil == (t = parse_double_time(str)) &&
221
- Qnil == (t = parse_xsd_time(str))) {
222
- VALUE args[1];
200
+ if (Qnil == (t = parse_double_time(str)) && Qnil == (t = parse_xsd_time(str))) {
201
+ VALUE args[1];
223
202
 
224
203
  /*printf("**** time parse\n"); */
225
204
  *args = rb_str_new2(str);
226
- t = rb_funcall2(ox_time_class, ox_parse_id, 1, args);
205
+ t = rb_funcall2(ox_time_class, ox_parse_id, 1, args);
227
206
  }
228
207
  return t;
229
208
  }
@@ -232,8 +211,7 @@ sax_value_as_time(VALUE self) {
232
211
  *
233
212
  * *return* value as an boolean.
234
213
  */
235
- static VALUE
236
- sax_value_as_bool(VALUE self) {
214
+ static VALUE sax_value_as_bool(VALUE self) {
237
215
  return (0 == strcasecmp("true", ((SaxDrive)DATA_PTR(self))->buf.str)) ? Qtrue : Qfalse;
238
216
  }
239
217
 
@@ -241,8 +219,7 @@ sax_value_as_bool(VALUE self) {
241
219
  *
242
220
  * *return* true if the value is empty.
243
221
  */
244
- static VALUE
245
- sax_value_empty(VALUE self) {
222
+ static VALUE sax_value_empty(VALUE self) {
246
223
  return ('\0' == *((SaxDrive)DATA_PTR(self))->buf.str) ? Qtrue : Qfalse;
247
224
  }
248
225
 
@@ -251,15 +228,14 @@ sax_value_empty(VALUE self) {
251
228
  * Values in the SAX callbacks. They can be converted to various different
252
229
  * types. with the _as_x()_ methods.
253
230
  */
254
- void
255
- ox_sax_define() {
231
+ void ox_sax_define() {
256
232
  #if 0
257
233
  ox = rb_define_module("Ox");
258
234
  #if RUBY_API_VERSION_CODE >= 30200
259
235
  sax_module = rb_define_class_under(ox, "Sax", rb_cObject);
260
236
  #endif
261
237
  #endif
262
- VALUE sax_module = rb_const_get_at(Ox, rb_intern("Sax"));
238
+ VALUE sax_module = rb_const_get_at(Ox, rb_intern("Sax"));
263
239
 
264
240
  ox_sax_value_class = rb_define_class_under(sax_module, "Value", rb_cObject);
265
241
  #if RUBY_API_VERSION_CODE >= 30200