ox 2.14.28 → 2.14.29
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 +4 -4
- data/CHANGELOG.md +103 -0
- data/ext/ox/base64.c +24 -17
- data/ext/ox/base64.h +2 -1
- data/ext/ox/buf.h +4 -7
- data/ext/ox/builder.c +277 -122
- data/ext/ox/cache.c +48 -13
- data/ext/ox/cache.h +2 -0
- data/ext/ox/dump.c +374 -221
- data/ext/ox/extconf.rb +3 -0
- data/ext/ox/gen_load.c +10 -8
- data/ext/ox/hash_load.c +7 -2
- data/ext/ox/helper.h +10 -6
- data/ext/ox/intern.c +6 -1
- data/ext/ox/obj_load.c +207 -47
- data/ext/ox/ox.c +179 -38
- data/ext/ox/ox.h +11 -1
- data/ext/ox/parse.c +249 -106
- data/ext/ox/sax.c +22 -18
- data/ext/ox/sax_as.c +42 -10
- data/ext/ox/sax_buf.c +23 -8
- data/ext/ox/sax_stack.h +13 -0
- data/ext/ox/time_conv.h +62 -0
- data/ext/ox/xml_check.h +58 -0
- data/ext/ox/xml_str.h +247 -0
- data/lib/ox/version.rb +1 -1
- metadata +5 -2
data/ext/ox/dump.c
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include <errno.h>
|
|
7
|
+
#include <stdbool.h>
|
|
7
8
|
#include <stdio.h>
|
|
8
9
|
#include <stdlib.h>
|
|
9
10
|
#include <string.h>
|
|
@@ -12,6 +13,9 @@
|
|
|
12
13
|
#include "base64.h"
|
|
13
14
|
#include "cache8.h"
|
|
14
15
|
#include "ox.h"
|
|
16
|
+
#include "time_conv.h"
|
|
17
|
+
#include "xml_check.h"
|
|
18
|
+
#include "xml_str.h"
|
|
15
19
|
|
|
16
20
|
#define USE_B64 0
|
|
17
21
|
#define MAX_DEPTH 1000
|
|
@@ -77,18 +81,25 @@ static int is_xml_friendly(const uchar *str, int len, const char *table);
|
|
|
77
81
|
|
|
78
82
|
static const char hex_chars[17] = "0123456789abcdef";
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
#define APPEND_CHARS(buffer, chars, size) \
|
|
85
|
+
{ \
|
|
86
|
+
memcpy(buffer, chars, size); \
|
|
87
|
+
buffer += size; \
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#ifdef HAVE_FAST_MEMCPY
|
|
91
|
+
#define APPEND_CHARS_SMALL(buffer, chars, size) \
|
|
92
|
+
{ \
|
|
93
|
+
fast_memcpy16(buffer, chars, size); \
|
|
94
|
+
buffer += size; \
|
|
95
|
+
}
|
|
96
|
+
#else
|
|
97
|
+
#define APPEND_CHARS_SMALL(buffer, chars, size) APPEND_CHARS(buffer, chars, size)
|
|
98
|
+
#endif
|
|
91
99
|
|
|
100
|
+
// Each table below maps a byte to the length of its escaped form, held as an ASCII digit so that
|
|
101
|
+
// xml_str_len() can sum the entries directly. The : character is equivalent to 10, used for replacement
|
|
102
|
+
// characters up to 10 characters long such as ''.
|
|
92
103
|
static const char xml_quote_chars[257] = "\
|
|
93
104
|
:::::::::11::1::::::::::::::::::\
|
|
94
105
|
11611151111111111111111111114141\
|
|
@@ -118,15 +129,6 @@ inline static int is_xml_friendly(const uchar *str, int len, const char *table)
|
|
|
118
129
|
return 1;
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
inline static size_t xml_str_len(const uchar *str, size_t len, const char *table) {
|
|
122
|
-
size_t size = 0;
|
|
123
|
-
|
|
124
|
-
for (; 0 < len; str++, len--) {
|
|
125
|
-
size += xml_friendly_chars[*str];
|
|
126
|
-
}
|
|
127
|
-
return size - len * (size_t)'0';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
132
|
inline static void dump_hex(uchar c, Out out) {
|
|
131
133
|
uchar d = (c >> 4) & 0x0F;
|
|
132
134
|
|
|
@@ -150,9 +152,10 @@ static Type obj_class_code(VALUE obj) {
|
|
|
150
152
|
return (is_xml_friendly((uchar *)StringValuePtr(obj), (int)RSTRING_LEN(obj), xml_element_chars)) ? StringCode
|
|
151
153
|
: String64Code;
|
|
152
154
|
case T_SYMBOL: {
|
|
153
|
-
|
|
155
|
+
volatile VALUE sym = rb_sym2str(obj);
|
|
154
156
|
|
|
155
|
-
return (is_xml_friendly((uchar *)sym, (int)
|
|
157
|
+
return (is_xml_friendly((uchar *)RSTRING_PTR(sym), (int)RSTRING_LEN(sym), xml_element_chars)) ? SymbolCode
|
|
158
|
+
: Symbol64Code;
|
|
156
159
|
}
|
|
157
160
|
case T_DATA: return (rb_cTime == clas) ? TimeCode : ((ox_date_class == clas) ? DateCode : 0);
|
|
158
161
|
case T_STRUCT: return (rb_cRange == clas) ? RangeCode : StructCode;
|
|
@@ -173,49 +176,74 @@ inline static void fill_indent(Out out, int cnt) {
|
|
|
173
176
|
memcpy(out->cur, out->opts->margin, out->opts->margin_len);
|
|
174
177
|
out->cur += out->opts->margin_len;
|
|
175
178
|
}
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
if (0 < cnt) {
|
|
180
|
+
memset(out->cur, ' ', cnt);
|
|
181
|
+
out->cur += cnt;
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
inline static void fill_value(Out out, const char *value, size_t len) {
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
out->cur += len;
|
|
187
|
+
if (16 < len) {
|
|
188
|
+
APPEND_CHARS(out->cur, value, len);
|
|
186
189
|
} else {
|
|
187
|
-
|
|
188
|
-
*out->cur++ = *value;
|
|
189
|
-
}
|
|
190
|
+
APPEND_CHARS_SMALL(out->cur, value, len);
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
inline static void fill_attr(Out out, char name, const char *value, size_t len) {
|
|
194
195
|
*out->cur++ = ' ';
|
|
195
196
|
*out->cur++ = name;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
memcpy(out->cur, value, len);
|
|
200
|
-
out->cur += len;
|
|
197
|
+
APPEND_CHARS_SMALL(out->cur, "=\"", 2);
|
|
198
|
+
if (16 < len) {
|
|
199
|
+
APPEND_CHARS(out->cur, value, len);
|
|
201
200
|
} else {
|
|
202
|
-
|
|
203
|
-
*out->cur++ = *value;
|
|
204
|
-
}
|
|
201
|
+
APPEND_CHARS_SMALL(out->cur, value, len);
|
|
205
202
|
}
|
|
206
203
|
*out->cur++ = '"';
|
|
207
204
|
}
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
static const char digits_table[] = "\
|
|
207
|
+
00010203040506070809\
|
|
208
|
+
10111213141516171819\
|
|
209
|
+
20212223242526272829\
|
|
210
|
+
30313233343536373839\
|
|
211
|
+
40414243444546474849\
|
|
212
|
+
50515253545556575859\
|
|
213
|
+
60616263646566676869\
|
|
214
|
+
70717273747576777879\
|
|
215
|
+
80818283848586878889\
|
|
216
|
+
90919293949596979899";
|
|
217
|
+
|
|
218
|
+
static char *ox_longlong_to_string(long long num, bool negative, char *b) {
|
|
219
|
+
while (100 <= num) {
|
|
220
|
+
unsigned idx = (unsigned)(num % 100) * 2;
|
|
221
|
+
|
|
222
|
+
*b-- = digits_table[idx + 1];
|
|
223
|
+
*b-- = digits_table[idx];
|
|
224
|
+
num /= 100;
|
|
225
|
+
}
|
|
226
|
+
if (num < 10) {
|
|
227
|
+
*b-- = (char)(num + '0');
|
|
228
|
+
} else {
|
|
229
|
+
*b-- = digits_table[num * 2 + 1];
|
|
230
|
+
*b-- = digits_table[num * 2];
|
|
231
|
+
}
|
|
232
|
+
if (negative) {
|
|
233
|
+
*b = '-';
|
|
234
|
+
} else {
|
|
235
|
+
b++;
|
|
236
|
+
}
|
|
237
|
+
return b;
|
|
238
|
+
}
|
|
211
239
|
|
|
240
|
+
inline static const char *ulong2str(ulong num, char *end) {
|
|
212
241
|
*end-- = '\0';
|
|
213
|
-
|
|
214
|
-
*
|
|
242
|
+
if (0 == num) {
|
|
243
|
+
*end = '0';
|
|
244
|
+
return end;
|
|
215
245
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return b;
|
|
246
|
+
return ox_longlong_to_string((long long)num, false, end);
|
|
219
247
|
}
|
|
220
248
|
|
|
221
249
|
static int check_circular(Out out, VALUE obj, Element e) {
|
|
@@ -290,9 +318,7 @@ static void dump_start(Out out, Element e) {
|
|
|
290
318
|
}
|
|
291
319
|
if (e->closed) {
|
|
292
320
|
if (out->opts->no_empty) {
|
|
293
|
-
|
|
294
|
-
*out->cur++ = '<';
|
|
295
|
-
*out->cur++ = '/';
|
|
321
|
+
APPEND_CHARS_SMALL(out->cur, "></", 3);
|
|
296
322
|
*out->cur++ = e->type;
|
|
297
323
|
} else {
|
|
298
324
|
*out->cur++ = '/';
|
|
@@ -309,8 +335,7 @@ static void dump_end(Out out, Element e) {
|
|
|
309
335
|
grow(out, size);
|
|
310
336
|
}
|
|
311
337
|
fill_indent(out, e->indent);
|
|
312
|
-
|
|
313
|
-
*out->cur++ = '/';
|
|
338
|
+
APPEND_CHARS_SMALL(out->cur, "</", 2);
|
|
314
339
|
*out->cur++ = e->type;
|
|
315
340
|
*out->cur++ = '>';
|
|
316
341
|
*out->cur = '\0';
|
|
@@ -320,13 +345,10 @@ inline static void dump_value(Out out, const char *value, size_t size) {
|
|
|
320
345
|
if (out->end - out->cur <= (long)size) {
|
|
321
346
|
grow(out, size);
|
|
322
347
|
}
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
out->cur += size;
|
|
348
|
+
if (16 < size) {
|
|
349
|
+
APPEND_CHARS(out->cur, value, size);
|
|
326
350
|
} else {
|
|
327
|
-
|
|
328
|
-
*out->cur++ = *value;
|
|
329
|
-
}
|
|
351
|
+
APPEND_CHARS_SMALL(out->cur, value, size);
|
|
330
352
|
}
|
|
331
353
|
*out->cur = '\0';
|
|
332
354
|
}
|
|
@@ -337,66 +359,88 @@ inline static void dump_str_value(Out out, const char *value, size_t size, const
|
|
|
337
359
|
if (out->end - out->cur <= (long)xsize) {
|
|
338
360
|
grow(out, xsize);
|
|
339
361
|
}
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
362
|
+
if (xsize == size) {
|
|
363
|
+
if (16 < size) {
|
|
364
|
+
APPEND_CHARS(out->cur, value, size);
|
|
343
365
|
} else {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
*out->cur++ = 't';
|
|
372
|
-
*out->cur++ = ';';
|
|
366
|
+
APPEND_CHARS_SMALL(out->cur, value, size);
|
|
367
|
+
}
|
|
368
|
+
*out->cur = '\0';
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const char *end = value + size;
|
|
372
|
+
|
|
373
|
+
while (value < end) {
|
|
374
|
+
/* Copy runs of pass-through bytes a word at a time. A word with no byte
|
|
375
|
+
* of interest is stored whole; on a hit the store is kept up to the
|
|
376
|
+
* first flagged byte and the rest is redone by the loops below. xsize
|
|
377
|
+
* was reserved for the whole escaped result so out->cur stays inside the
|
|
378
|
+
* buffer, and out->cur + 8 <= out->end keeps the wide store in bounds on
|
|
379
|
+
* its own.
|
|
380
|
+
*/
|
|
381
|
+
while (value + 8 <= end && out->cur + 8 <= out->end) {
|
|
382
|
+
uint64_t v;
|
|
383
|
+
uint64_t mask;
|
|
384
|
+
|
|
385
|
+
memcpy(&v, value, 8);
|
|
386
|
+
mask = xml_bytes_of_interest(v);
|
|
387
|
+
memcpy(out->cur, value, 8);
|
|
388
|
+
if (0 != mask) {
|
|
389
|
+
int n = xml_first_of_interest((const unsigned char *)value, mask);
|
|
390
|
+
|
|
391
|
+
value += n;
|
|
392
|
+
out->cur += n;
|
|
373
393
|
break;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
394
|
+
}
|
|
395
|
+
value += 8;
|
|
396
|
+
out->cur += 8;
|
|
397
|
+
}
|
|
398
|
+
/* Pass-through bytes the word loop could not take: the tail shorter than
|
|
399
|
+
* a word, and the '"' or '\'' the element table keeps that the predicate
|
|
400
|
+
* flags anyway.
|
|
401
|
+
*/
|
|
402
|
+
while (value < end) {
|
|
403
|
+
uchar c = (uchar)*value;
|
|
404
|
+
|
|
405
|
+
if (c < 0x20 || '"' == c || '\'' == c || '&' == c || '<' == c || '>' == c) {
|
|
379
406
|
break;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
407
|
+
}
|
|
408
|
+
*out->cur++ = *value++;
|
|
409
|
+
}
|
|
410
|
+
if (value < end) {
|
|
411
|
+
char c = *value++;
|
|
412
|
+
|
|
413
|
+
if ('1' == table[(uchar)c]) {
|
|
414
|
+
*out->cur++ = c;
|
|
415
|
+
} else {
|
|
416
|
+
switch (c) {
|
|
417
|
+
case '"': APPEND_CHARS_SMALL(out->cur, """, 6); break;
|
|
418
|
+
case '&': APPEND_CHARS_SMALL(out->cur, "&", 5); break;
|
|
419
|
+
case '\'': APPEND_CHARS_SMALL(out->cur, "'", 6); break;
|
|
420
|
+
case '<': APPEND_CHARS_SMALL(out->cur, "<", 4); break;
|
|
421
|
+
case '>': APPEND_CHARS_SMALL(out->cur, ">", 4); break;
|
|
422
|
+
default:
|
|
423
|
+
// Must be one of the invalid characters.
|
|
424
|
+
if (NotSet == out->opts->allow_invalid) {
|
|
425
|
+
rb_raise(ox_syntax_error_class, "'\\#x%02x' is not a valid XML character.", c);
|
|
426
|
+
}
|
|
427
|
+
if (Yes == out->opts->allow_invalid) {
|
|
428
|
+
// A character reference has to resolve to a Char, which #x0 is not.
|
|
429
|
+
// Reading � back ends the text at the NUL and drops the rest
|
|
430
|
+
// without an error, so the byte is left out instead.
|
|
431
|
+
if ('\0' != c) {
|
|
432
|
+
APPEND_CHARS_SMALL(out->cur, "�", 5);
|
|
433
|
+
dump_hex(c, out);
|
|
434
|
+
*out->cur++ = ';';
|
|
435
|
+
}
|
|
436
|
+
} else if ('\0' != *out->opts->inv_repl) {
|
|
437
|
+
// If the empty string then ignore. The first character of
|
|
438
|
+
// the replacement is the length.
|
|
439
|
+
memcpy(out->cur, out->opts->inv_repl + 1, (size_t)*out->opts->inv_repl);
|
|
440
|
+
out->cur += *out->opts->inv_repl;
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
398
443
|
}
|
|
399
|
-
break;
|
|
400
444
|
}
|
|
401
445
|
}
|
|
402
446
|
}
|
|
@@ -407,30 +451,27 @@ inline static void dump_num(Out out, VALUE obj) {
|
|
|
407
451
|
char buf[32];
|
|
408
452
|
char *b = buf + sizeof(buf) - 1;
|
|
409
453
|
long num = NUM2LONG(obj);
|
|
410
|
-
|
|
454
|
+
bool neg = false;
|
|
455
|
+
long size;
|
|
411
456
|
|
|
412
457
|
if (0 > num) {
|
|
413
|
-
neg =
|
|
458
|
+
neg = true;
|
|
414
459
|
num = -num;
|
|
415
460
|
}
|
|
416
461
|
*b-- = '\0';
|
|
417
462
|
if (0 < num) {
|
|
418
|
-
|
|
419
|
-
*b = (num % 10) + '0';
|
|
420
|
-
}
|
|
421
|
-
if (neg) {
|
|
422
|
-
*b = '-';
|
|
423
|
-
} else {
|
|
424
|
-
b++;
|
|
425
|
-
}
|
|
463
|
+
b = ox_longlong_to_string(num, neg, b);
|
|
426
464
|
} else {
|
|
427
465
|
*b = '0';
|
|
428
466
|
}
|
|
429
|
-
|
|
430
|
-
|
|
467
|
+
size = sizeof(buf) - (b - buf) - 1;
|
|
468
|
+
if (out->end - out->cur <= size) {
|
|
469
|
+
grow(out, size);
|
|
431
470
|
}
|
|
432
|
-
|
|
433
|
-
|
|
471
|
+
if (16 < size) {
|
|
472
|
+
APPEND_CHARS(out->cur, b, size);
|
|
473
|
+
} else {
|
|
474
|
+
APPEND_CHARS_SMALL(out->cur, b, size);
|
|
434
475
|
}
|
|
435
476
|
*out->cur = '\0';
|
|
436
477
|
}
|
|
@@ -443,22 +484,48 @@ static void dump_time_thin(Out out, VALUE obj) {
|
|
|
443
484
|
long nsec = ts.tv_nsec;
|
|
444
485
|
char *dot = b - 10;
|
|
445
486
|
long size;
|
|
487
|
+
bool neg = 0 > sec;
|
|
446
488
|
|
|
489
|
+
// tv_nsec is never negative, so a time before the epoch is a negative
|
|
490
|
+
// second count carrying a positive fraction. Writing those two out as they
|
|
491
|
+
// are would read as -1.5 for what is really -0.5, so move the fraction over
|
|
492
|
+
// and write the magnitude with a sign in front of it.
|
|
493
|
+
if (neg && 0 < nsec) {
|
|
494
|
+
sec++;
|
|
495
|
+
nsec = 1000000000L - nsec;
|
|
496
|
+
}
|
|
447
497
|
*b-- = '\0';
|
|
448
498
|
for (; dot < b; b--, nsec /= 10) {
|
|
449
499
|
*b = '0' + (nsec % 10);
|
|
450
500
|
}
|
|
451
501
|
*b-- = '.';
|
|
452
|
-
|
|
453
|
-
*b = '0'
|
|
502
|
+
if (0 == sec) {
|
|
503
|
+
*b-- = '0';
|
|
504
|
+
} else if (neg) {
|
|
505
|
+
// sec is left negative instead of negated so that the most negative
|
|
506
|
+
// time_t does not overflow. C truncates the quotient toward zero, so
|
|
507
|
+
// the remainder carries the sign with it.
|
|
508
|
+
for (; 0 != sec; b--, sec /= 10) {
|
|
509
|
+
*b = (char)('0' - sec % 10);
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
for (; 0 < sec; b--, sec /= 10) {
|
|
513
|
+
*b = '0' + (sec % 10);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (neg) {
|
|
517
|
+
*b-- = '-';
|
|
454
518
|
}
|
|
455
519
|
b++;
|
|
456
520
|
size = sizeof(buf) - (b - buf) - 1;
|
|
457
521
|
if (out->end - out->cur <= size) {
|
|
458
522
|
grow(out, size);
|
|
459
523
|
}
|
|
460
|
-
|
|
461
|
-
|
|
524
|
+
if (16 < size) {
|
|
525
|
+
APPEND_CHARS(out->cur, b, size);
|
|
526
|
+
} else {
|
|
527
|
+
APPEND_CHARS_SMALL(out->cur, b, size);
|
|
528
|
+
}
|
|
462
529
|
}
|
|
463
530
|
|
|
464
531
|
static void dump_date(Out out, VALUE obj) {
|
|
@@ -468,61 +535,76 @@ static void dump_date(Out out, VALUE obj) {
|
|
|
468
535
|
long size;
|
|
469
536
|
|
|
470
537
|
*b-- = '\0';
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
b++;
|
|
475
|
-
if ('\0' == *b) {
|
|
476
|
-
b--;
|
|
538
|
+
if (0 < jd) {
|
|
539
|
+
b = ox_longlong_to_string((long long)jd, false, b);
|
|
540
|
+
} else {
|
|
477
541
|
*b = '0';
|
|
478
542
|
}
|
|
479
543
|
size = sizeof(buf) - (b - buf) - 1;
|
|
480
544
|
if (out->end - out->cur <= size) {
|
|
481
545
|
grow(out, size);
|
|
482
546
|
}
|
|
483
|
-
|
|
484
|
-
|
|
547
|
+
if (16 < size) {
|
|
548
|
+
APPEND_CHARS(out->cur, b, size);
|
|
549
|
+
} else {
|
|
550
|
+
APPEND_CHARS_SMALL(out->cur, b, size);
|
|
551
|
+
}
|
|
485
552
|
}
|
|
486
553
|
|
|
487
554
|
static void dump_time_xsd(Out out, VALUE obj) {
|
|
488
|
-
struct
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
long
|
|
555
|
+
struct timespec ts = rb_time_timespec(obj);
|
|
556
|
+
time_t sec = ts.tv_sec;
|
|
557
|
+
long nsec = ts.tv_nsec;
|
|
558
|
+
long offset = NUM2LONG(rb_funcall2(obj, ox_utc_offset_id, 0, 0));
|
|
492
559
|
int tzhour, tzmin;
|
|
493
560
|
char tzsign = '+';
|
|
561
|
+
long long year;
|
|
562
|
+
int mon, day, hour, min, tsec;
|
|
563
|
+
char buf[64];
|
|
564
|
+
int cnt;
|
|
494
565
|
|
|
495
|
-
if (out->end - out->cur <= 33) {
|
|
496
|
-
grow(out, 33);
|
|
497
|
-
}
|
|
498
566
|
/* 2010-07-09T10:47:45.895826+09:00 */
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
567
|
+
// The offset comes from the Time rather than from localtime(), which has no
|
|
568
|
+
// tm_gmtoff in the Microsoft CRT and so wrote +00:00 next to a local wall
|
|
569
|
+
// clock there. Adding it before splitting gives that wall clock without a
|
|
570
|
+
// timezone database, so there is no platform branch and no NULL to check.
|
|
571
|
+
// xsd writes the offset as hh:mm, so one that is not a whole number of
|
|
572
|
+
// minutes -- Dublin was -00:25:21 until 1916 -- can not be written without
|
|
573
|
+
// moving the instant. UTC says the same thing and says it exactly.
|
|
574
|
+
if (0 != offset % 60) {
|
|
575
|
+
offset = 0;
|
|
576
|
+
}
|
|
577
|
+
if (0 > offset) {
|
|
502
578
|
tzsign = '-';
|
|
503
|
-
tzhour = (int)(
|
|
504
|
-
tzmin = (int)(
|
|
579
|
+
tzhour = (int)(offset / -3600);
|
|
580
|
+
tzmin = (int)(offset / -60) - (tzhour * 60);
|
|
505
581
|
} else {
|
|
506
|
-
tzhour = (int)(
|
|
507
|
-
tzmin = (int)(
|
|
582
|
+
tzhour = (int)(offset / 3600);
|
|
583
|
+
tzmin = (int)(offset / 60) - (tzhour * 60);
|
|
508
584
|
}
|
|
509
|
-
|
|
510
|
-
tzhour = 0;
|
|
511
|
-
tzmin = 0;
|
|
512
|
-
#endif
|
|
585
|
+
ox_civil_from_epoch((int64_t)sec + offset, &year, &mon, &day, &hour, &min, &tsec);
|
|
513
586
|
/* TBD replace with more efficient printer */
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
587
|
+
// A year outside four digits is longer than the sample above, so reserve
|
|
588
|
+
// what was actually formatted instead of a fixed 33. buf can not overflow:
|
|
589
|
+
// the largest time_t puts the year at 12 digits, and the rest of the format
|
|
590
|
+
// is a fixed 28 characters.
|
|
591
|
+
cnt = snprintf(buf,
|
|
592
|
+
sizeof(buf),
|
|
593
|
+
"%04lld-%02d-%02dT%02d:%02d:%02d.%06ld%c%02d:%02d",
|
|
594
|
+
year,
|
|
595
|
+
mon,
|
|
596
|
+
day,
|
|
597
|
+
hour,
|
|
598
|
+
min,
|
|
599
|
+
tsec,
|
|
600
|
+
nsec / 1000,
|
|
601
|
+
tzsign,
|
|
602
|
+
tzhour,
|
|
603
|
+
tzmin);
|
|
604
|
+
if (out->end - out->cur <= cnt) {
|
|
605
|
+
grow(out, cnt);
|
|
606
|
+
}
|
|
607
|
+
APPEND_CHARS(out->cur, buf, cnt);
|
|
526
608
|
}
|
|
527
609
|
|
|
528
610
|
static void dump_first_obj(VALUE obj, Out out) {
|
|
@@ -711,9 +793,10 @@ static void dump_obj(ID aid, VALUE obj, int depth, Out out) {
|
|
|
711
793
|
break;
|
|
712
794
|
}
|
|
713
795
|
case T_SYMBOL: {
|
|
714
|
-
|
|
796
|
+
volatile VALUE rsym = rb_sym2str(obj);
|
|
797
|
+
const char *sym = RSTRING_PTR(rsym);
|
|
715
798
|
|
|
716
|
-
cnt = (int)
|
|
799
|
+
cnt = (int)RSTRING_LEN(rsym);
|
|
717
800
|
#if USE_B64
|
|
718
801
|
if (is_xml_friendly((uchar *)sym, cnt)) {
|
|
719
802
|
e.type = SymbolCode;
|
|
@@ -789,18 +872,19 @@ static void dump_obj(ID aid, VALUE obj, int depth, Out out) {
|
|
|
789
872
|
}
|
|
790
873
|
clas = rb_obj_class(obj);
|
|
791
874
|
if (rb_cRange == clas) {
|
|
792
|
-
VALUE beg
|
|
793
|
-
VALUE end
|
|
794
|
-
|
|
795
|
-
int d2
|
|
875
|
+
VALUE beg;
|
|
876
|
+
VALUE end;
|
|
877
|
+
int excl;
|
|
878
|
+
int d2 = depth + 1;
|
|
796
879
|
|
|
880
|
+
rb_range_values(obj, &beg, &end, &excl);
|
|
797
881
|
e.type = RangeCode;
|
|
798
882
|
e.clas.len = 5;
|
|
799
883
|
e.clas.str = "Range";
|
|
800
884
|
out->w_start(out, &e);
|
|
801
885
|
dump_obj(ox_beg_id, beg, d2, out);
|
|
802
886
|
dump_obj(ox_end_id, end, d2, out);
|
|
803
|
-
dump_obj(ox_excl_id, excl, d2, out);
|
|
887
|
+
dump_obj(ox_excl_id, excl ? Qtrue : Qfalse, d2, out);
|
|
804
888
|
out->w_end(out, &e);
|
|
805
889
|
} else {
|
|
806
890
|
char num_buf[16];
|
|
@@ -1009,6 +1093,7 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
|
|
|
1009
1093
|
size_t size;
|
|
1010
1094
|
int indent;
|
|
1011
1095
|
|
|
1096
|
+
check_name_chars(name, (size_t)nlen, false);
|
|
1012
1097
|
if (0 > out->indent) {
|
|
1013
1098
|
indent = -1;
|
|
1014
1099
|
} else if (0 == out->indent) {
|
|
@@ -1017,6 +1102,11 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
|
|
|
1017
1102
|
indent = depth * out->indent;
|
|
1018
1103
|
}
|
|
1019
1104
|
size = indent + 4 + nlen + out->opts->margin_len;
|
|
1105
|
+
if (0 == depth && 0 < out->indent) {
|
|
1106
|
+
// The margin is written here and again by fill_indent(), so reserve it
|
|
1107
|
+
// twice.
|
|
1108
|
+
size += out->opts->margin_len;
|
|
1109
|
+
}
|
|
1020
1110
|
if (out->end - out->cur <= (long)size) {
|
|
1021
1111
|
grow(out, size);
|
|
1022
1112
|
}
|
|
@@ -1030,24 +1120,36 @@ static void dump_gen_element(VALUE obj, int depth, Out out) {
|
|
|
1030
1120
|
if (Qnil != attrs) {
|
|
1031
1121
|
rb_hash_foreach(attrs, dump_gen_attr, (VALUE)out);
|
|
1032
1122
|
}
|
|
1123
|
+
// The attribute loop runs Ruby, so both the reservation above and the name
|
|
1124
|
+
// pointer are spent: dump_gen_attr() calls rb_String() on each value, and
|
|
1125
|
+
// growing the name String there frees the buffer name points at. The type
|
|
1126
|
+
// check is not repeated: StringValuePtr() above left rname a String and a
|
|
1127
|
+
// VALUE does not change type, only where its bytes live.
|
|
1128
|
+
name = RSTRING_PTR(rname);
|
|
1129
|
+
nlen = RSTRING_LEN(rname);
|
|
1130
|
+
size = indent + 5 + nlen + out->opts->margin_len;
|
|
1131
|
+
if (out->end - out->cur <= (long)size) {
|
|
1132
|
+
grow(out, size);
|
|
1133
|
+
}
|
|
1033
1134
|
if (Qnil != nodes && 0 < RARRAY_LEN(nodes)) {
|
|
1034
1135
|
int do_indent;
|
|
1035
1136
|
|
|
1036
1137
|
*out->cur++ = '>';
|
|
1037
1138
|
do_indent = dump_gen_nodes(nodes, depth, out);
|
|
1139
|
+
// The children run Ruby as well.
|
|
1140
|
+
name = RSTRING_PTR(rname);
|
|
1141
|
+
nlen = RSTRING_LEN(rname);
|
|
1142
|
+
size = indent + 5 + nlen + out->opts->margin_len;
|
|
1038
1143
|
if (out->end - out->cur <= (long)size) {
|
|
1039
1144
|
grow(out, size);
|
|
1040
1145
|
}
|
|
1041
1146
|
if (do_indent) {
|
|
1042
1147
|
fill_indent(out, indent);
|
|
1043
1148
|
}
|
|
1044
|
-
|
|
1045
|
-
*out->cur++ = '/';
|
|
1149
|
+
APPEND_CHARS_SMALL(out->cur, "</", 2);
|
|
1046
1150
|
fill_value(out, name, nlen);
|
|
1047
1151
|
} else if (out->opts->no_empty) {
|
|
1048
|
-
|
|
1049
|
-
*out->cur++ = '<';
|
|
1050
|
-
*out->cur++ = '/';
|
|
1152
|
+
APPEND_CHARS_SMALL(out->cur, "></", 3);
|
|
1051
1153
|
fill_value(out, name, nlen);
|
|
1052
1154
|
} else {
|
|
1053
1155
|
*out->cur++ = '/';
|
|
@@ -1066,18 +1168,19 @@ static void dump_gen_instruct(VALUE obj, int depth, Out out) {
|
|
|
1066
1168
|
long clen = 0;
|
|
1067
1169
|
size_t size;
|
|
1068
1170
|
|
|
1171
|
+
check_unescaped(name, (size_t)nlen);
|
|
1069
1172
|
if (T_STRING == rb_type(rcontent)) {
|
|
1070
1173
|
content = StringValuePtr(rcontent);
|
|
1071
1174
|
clen = RSTRING_LEN(rcontent);
|
|
1072
|
-
|
|
1175
|
+
check_unescaped(content, (size_t)clen);
|
|
1176
|
+
size = 4 + nlen + clen;
|
|
1073
1177
|
} else {
|
|
1074
1178
|
size = 4 + nlen;
|
|
1075
1179
|
}
|
|
1076
1180
|
if (out->end - out->cur <= (long)size) {
|
|
1077
1181
|
grow(out, size);
|
|
1078
1182
|
}
|
|
1079
|
-
|
|
1080
|
-
*out->cur++ = '?';
|
|
1183
|
+
APPEND_CHARS_SMALL(out->cur, "<?", 2);
|
|
1081
1184
|
fill_value(out, name, nlen);
|
|
1082
1185
|
if (0 != content) {
|
|
1083
1186
|
if (' ' != *content) {
|
|
@@ -1087,9 +1190,8 @@ static void dump_gen_instruct(VALUE obj, int depth, Out out) {
|
|
|
1087
1190
|
} else if (Qnil != attrs) {
|
|
1088
1191
|
rb_hash_foreach(attrs, dump_gen_attr, (VALUE)out);
|
|
1089
1192
|
}
|
|
1090
|
-
|
|
1091
|
-
*out->cur
|
|
1092
|
-
*out->cur = '\0';
|
|
1193
|
+
APPEND_CHARS_SMALL(out->cur, "?>", 2);
|
|
1194
|
+
*out->cur = '\0';
|
|
1093
1195
|
}
|
|
1094
1196
|
|
|
1095
1197
|
static int dump_gen_nodes(VALUE obj, int depth, Out out) {
|
|
@@ -1131,31 +1233,34 @@ static int dump_gen_nodes(VALUE obj, int depth, Out out) {
|
|
|
1131
1233
|
}
|
|
1132
1234
|
|
|
1133
1235
|
static int dump_gen_attr(VALUE key, VALUE value, VALUE ov) {
|
|
1134
|
-
Out
|
|
1135
|
-
|
|
1136
|
-
const char
|
|
1137
|
-
size_t
|
|
1138
|
-
size_t
|
|
1236
|
+
Out out = (Out)ov;
|
|
1237
|
+
volatile VALUE kv = key;
|
|
1238
|
+
const char *ks;
|
|
1239
|
+
size_t klen;
|
|
1240
|
+
size_t size;
|
|
1139
1241
|
|
|
1140
1242
|
switch (rb_type(key)) {
|
|
1141
|
-
case T_SYMBOL:
|
|
1142
|
-
case T_STRING:
|
|
1143
|
-
default:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1243
|
+
case T_SYMBOL: kv = rb_sym2str(key); break;
|
|
1244
|
+
case T_STRING: break;
|
|
1245
|
+
default: kv = rb_String(key); break;
|
|
1246
|
+
}
|
|
1247
|
+
// Every arm above leaves kv a String, so no second type check is needed.
|
|
1248
|
+
ks = RSTRING_PTR(kv);
|
|
1249
|
+
klen = (size_t)RSTRING_LEN(kv);
|
|
1250
|
+
// Before the space, so a rescued raise leaves the element as it was. The
|
|
1251
|
+
// NUL this used to check for on its own is the low end of the same set.
|
|
1252
|
+
check_name_chars(ks, klen, true);
|
|
1253
|
+
if (!RB_TYPE_P(value, T_STRING)) {
|
|
1254
|
+
value = rb_String(value);
|
|
1255
|
+
}
|
|
1256
|
+
size = 4 + klen + RSTRING_LEN(value);
|
|
1151
1257
|
if (out->end - out->cur <= (long)size) {
|
|
1152
1258
|
grow(out, size);
|
|
1153
1259
|
}
|
|
1154
1260
|
*out->cur++ = ' ';
|
|
1155
1261
|
fill_value(out, ks, klen);
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
dump_str_value(out, StringValuePtr(value), RSTRING_LEN(value), xml_quote_chars);
|
|
1262
|
+
APPEND_CHARS_SMALL(out->cur, "=\"", 2);
|
|
1263
|
+
dump_str_value(out, RSTRING_PTR(value), RSTRING_LEN(value), xml_quote_chars);
|
|
1159
1264
|
*out->cur++ = '"';
|
|
1160
1265
|
|
|
1161
1266
|
return ST_CONTINUE;
|
|
@@ -1165,8 +1270,12 @@ static void
|
|
|
1165
1270
|
dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char *suf, size_t slen, Out out) {
|
|
1166
1271
|
volatile VALUE v = rb_attr_get(obj, ox_at_value_id);
|
|
1167
1272
|
const char *val;
|
|
1273
|
+
const char *end;
|
|
1274
|
+
const char *from;
|
|
1275
|
+
const char *split;
|
|
1168
1276
|
size_t vlen;
|
|
1169
1277
|
size_t size;
|
|
1278
|
+
size_t cdata_cnt = 0;
|
|
1170
1279
|
int indent;
|
|
1171
1280
|
|
|
1172
1281
|
if (T_STRING != rb_type(v)) {
|
|
@@ -1174,6 +1283,13 @@ dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char
|
|
|
1174
1283
|
}
|
|
1175
1284
|
val = StringValuePtr(v);
|
|
1176
1285
|
vlen = RSTRING_LEN(v);
|
|
1286
|
+
from = val;
|
|
1287
|
+
end = val + vlen;
|
|
1288
|
+
// Ox::Raw is documented as going out untouched, so it is the one value node
|
|
1289
|
+
// that is not checked. The rest are markup ox wrote the delimiters for.
|
|
1290
|
+
if (ox_raw_clas != rb_obj_class(obj)) {
|
|
1291
|
+
check_unescaped(val, vlen);
|
|
1292
|
+
}
|
|
1177
1293
|
if (0 > out->indent) {
|
|
1178
1294
|
indent = -1;
|
|
1179
1295
|
} else if (0 == out->indent) {
|
|
@@ -1182,33 +1298,36 @@ dump_gen_val_node(VALUE obj, int depth, const char *pre, size_t plen, const char
|
|
|
1182
1298
|
indent = depth * out->indent;
|
|
1183
1299
|
}
|
|
1184
1300
|
size = indent + plen + slen + vlen + out->opts->margin_len;
|
|
1301
|
+
// Only a CDATA section can be closed by its own value; see xml_cdata_end().
|
|
1302
|
+
if (9 == plen && 0 == strncmp("<![CDATA[", pre, 9)) {
|
|
1303
|
+
cdata_cnt = xml_cdata_end_cnt(val, end);
|
|
1304
|
+
size += cdata_cnt * CDATA_SPLIT_EXTRA;
|
|
1305
|
+
}
|
|
1185
1306
|
if (out->end - out->cur <= (long)size) {
|
|
1186
1307
|
grow(out, size);
|
|
1187
1308
|
}
|
|
1188
1309
|
fill_indent(out, indent);
|
|
1189
1310
|
fill_value(out, pre, plen);
|
|
1190
|
-
|
|
1311
|
+
// The ']]' stays in this section and the '>' opens the next one, so from
|
|
1312
|
+
// lands on the '>' each time round.
|
|
1313
|
+
while (0 < cdata_cnt && NULL != (split = xml_cdata_end(from, end))) {
|
|
1314
|
+
fill_value(out, from, (size_t)((split + 2) - from));
|
|
1315
|
+
fill_value(out, "]]><![CDATA[", CDATA_SPLIT_EXTRA);
|
|
1316
|
+
from = split + 2;
|
|
1317
|
+
}
|
|
1318
|
+
fill_value(out, from, (size_t)(end - from));
|
|
1191
1319
|
fill_value(out, suf, slen);
|
|
1192
1320
|
*out->cur = '\0';
|
|
1193
1321
|
}
|
|
1194
1322
|
|
|
1195
|
-
|
|
1323
|
+
// The dump traversal, run under rb_protect so the output buffer is freed even
|
|
1324
|
+
// when it raises (invalid character, oversized indent, deep-recursion circular
|
|
1325
|
+
// reference, ...). out->obj and out->opts carry everything it needs.
|
|
1326
|
+
static VALUE dump_obj_to_xml_body(VALUE outv) {
|
|
1327
|
+
Out out = (Out)outv;
|
|
1328
|
+
VALUE obj = out->obj;
|
|
1196
1329
|
VALUE clas = rb_obj_class(obj);
|
|
1197
1330
|
|
|
1198
|
-
out->w_time = (Yes == copts->xsd_date) ? dump_time_xsd : dump_time_thin;
|
|
1199
|
-
out->buf = ALLOC_N(char, 65336);
|
|
1200
|
-
out->end = out->buf + 65325; /* 10 less than end plus extra for possible errors */
|
|
1201
|
-
out->cur = out->buf;
|
|
1202
|
-
out->circ_cache = 0;
|
|
1203
|
-
out->circ_cnt = 0;
|
|
1204
|
-
out->opts = copts;
|
|
1205
|
-
out->obj = obj;
|
|
1206
|
-
*out->cur = '\0';
|
|
1207
|
-
if (Yes == copts->circular) {
|
|
1208
|
-
ox_cache8_new(&out->circ_cache);
|
|
1209
|
-
}
|
|
1210
|
-
out->indent = copts->indent;
|
|
1211
|
-
|
|
1212
1331
|
if (ox_document_clas == clas) {
|
|
1213
1332
|
dump_gen_doc(obj, -1, out);
|
|
1214
1333
|
} else if (ox_element_clas == clas) {
|
|
@@ -1229,9 +1348,39 @@ static void dump_obj_to_xml(VALUE obj, Options copts, Out out) {
|
|
|
1229
1348
|
if (0 <= out->indent) {
|
|
1230
1349
|
dump_value(out, "\n", 1);
|
|
1231
1350
|
}
|
|
1351
|
+
return Qnil;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
static void dump_obj_to_xml(VALUE obj, Options copts, Out out) {
|
|
1355
|
+
int state = 0;
|
|
1356
|
+
|
|
1357
|
+
out->w_time = (Yes == copts->xsd_date) ? dump_time_xsd : dump_time_thin;
|
|
1358
|
+
out->buf = ALLOC_N(char, 65336);
|
|
1359
|
+
out->end = out->buf + 65325; /* 10 less than end plus extra for possible errors */
|
|
1360
|
+
out->cur = out->buf;
|
|
1361
|
+
out->circ_cache = 0;
|
|
1362
|
+
out->circ_cnt = 0;
|
|
1363
|
+
out->opts = copts;
|
|
1364
|
+
out->obj = obj;
|
|
1365
|
+
*out->cur = '\0';
|
|
1366
|
+
if (Yes == copts->circular) {
|
|
1367
|
+
ox_cache8_new(&out->circ_cache);
|
|
1368
|
+
}
|
|
1369
|
+
out->indent = copts->indent;
|
|
1370
|
+
|
|
1371
|
+
// Run the traversal under rb_protect. On the normal path out->buf is handed
|
|
1372
|
+
// back to the caller; on a raise the longjmp would otherwise skip the free,
|
|
1373
|
+
// so release the buffer (and the circular cache) here before re-raising.
|
|
1374
|
+
rb_protect(dump_obj_to_xml_body, (VALUE)out, &state);
|
|
1375
|
+
|
|
1232
1376
|
if (Yes == copts->circular) {
|
|
1233
1377
|
ox_cache8_delete(out->circ_cache);
|
|
1234
1378
|
}
|
|
1379
|
+
if (0 != state) {
|
|
1380
|
+
xfree(out->buf);
|
|
1381
|
+
out->buf = NULL;
|
|
1382
|
+
rb_jump_tag(state);
|
|
1383
|
+
}
|
|
1235
1384
|
}
|
|
1236
1385
|
|
|
1237
1386
|
char *ox_write_obj_to_str(VALUE obj, Options copts) {
|
|
@@ -1248,11 +1397,15 @@ void ox_write_obj_to_file(VALUE obj, const char *path, Options copts) {
|
|
|
1248
1397
|
|
|
1249
1398
|
dump_obj_to_xml(obj, copts, &out);
|
|
1250
1399
|
size = out.cur - out.buf;
|
|
1251
|
-
if (0 == (f = fopen(path, "
|
|
1400
|
+
if (0 == (f = fopen(path, "wb"))) {
|
|
1401
|
+
xfree(out.buf);
|
|
1252
1402
|
rb_raise(rb_eIOError, "%s\n", strerror(errno));
|
|
1253
1403
|
}
|
|
1254
1404
|
if (size != fwrite(out.buf, 1, size, f)) {
|
|
1255
1405
|
int err = ferror(f);
|
|
1406
|
+
|
|
1407
|
+
fclose(f);
|
|
1408
|
+
xfree(out.buf);
|
|
1256
1409
|
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err));
|
|
1257
1410
|
}
|
|
1258
1411
|
xfree(out.buf);
|