File Coverage

XS.xs
Criterion Covered Total %
statement 1196 1255 95.3
branch 1264 2200 57.4
condition n/a
subroutine n/a
pod n/a
total 2460 3455 71.2


line stmt bran cond sub pod time code
1              
2             #define PERL_NO_GET_CONTEXT
3             #include "EXTERN.h"
4             #include "perl.h"
5             #include "XSUB.h"
6              
7             #ifndef av_count
8             #define av_count(av) (av_top_index(av) + 1)
9             #endif
10              
11             #include
12             #include
13             #include
14             #include
15             #include
16             #include
17              
18             /* ---- escape/format helpers ---- */
19              
20 11074           static inline int itoa_fast(char *buf, long val) {
21             char tmp[20];
22 11074           int len = 0, neg = 0;
23             unsigned long uval;
24 11074 100         if (val < 0) { neg = 1; uval = -(unsigned long)val; } else { uval = (unsigned long)val; }
25 41870 100         do { tmp[len++] = '0' + (uval % 10); uval /= 10; } while (uval);
26 11074 100         if (neg) tmp[len++] = '-';
27 52945 100         for (int i = 0; i < len; i++) buf[i] = tmp[len - 1 - i];
28 11074           return len;
29             }
30              
31 1006           static int itoa_comma(char *buf, long val) {
32             char digits[20];
33 1006           int dlen = 0, neg = 0;
34             unsigned long uval;
35 1006 100         if (val < 0) { neg = 1; uval = -(unsigned long)val; } else { uval = (unsigned long)val; }
36 2945 100         do { digits[dlen++] = '0' + (uval % 10); uval /= 10; } while (uval);
37 1006           int pos = 0;
38 1006 100         if (neg) buf[pos++] = '-';
39 3951 100         for (int i = dlen - 1; i >= 0; i--) {
40 2945           buf[pos++] = digits[i];
41 2945 100         if (i > 0 && i % 3 == 0) buf[pos++] = ',';
    100          
42             }
43 1006           return pos;
44             }
45              
46             /* lookup table for html_escape: 1 = special char */
47             static char html_special[256];
48             static char html_br_special[256];
49             static char json_special[256];
50             static int html_tables_inited = 0;
51              
52 4           static void init_html_tables(void) {
53 4 50         if (html_tables_inited) return;
54 4           memset(html_special, 0, 256);
55 4           html_special[(unsigned char)'&'] = 1;
56 4           html_special[(unsigned char)'<'] = 1;
57 4           html_special[(unsigned char)'>'] = 1;
58 4           html_special[(unsigned char)'"'] = 1;
59 4           html_special[(unsigned char)'\''] = 1;
60 4           memcpy(html_br_special, html_special, 256);
61 4           html_br_special[(unsigned char)'\n'] = 1;
62 4           memset(json_special, 0, 256);
63 4           json_special[(unsigned char)'"'] = 1;
64 4           json_special[(unsigned char)'\\'] = 1;
65 4           json_special[(unsigned char)'\b'] = 1;
66 4           json_special[(unsigned char)'\f'] = 1;
67 4           json_special[(unsigned char)'\n'] = 1;
68 4           json_special[(unsigned char)'\r'] = 1;
69 4           json_special[(unsigned char)'\t'] = 1;
70 132 100         for (int i = 0; i < 0x20; i++) json_special[i] = 1;
71 4           html_tables_inited = 1;
72             }
73              
74 15           static int html_escape(char *dst, const char *src, STRLEN slen) {
75 15           char *out = dst;
76 15           STRLEN i = 0;
77 52 100         while (i < slen) {
78             /* scan for run of non-special bytes */
79 41           STRLEN run = i;
80 242 100         while (run < slen && !html_special[(unsigned char)src[run]]) run++;
    100          
81 41 100         if (run > i) {
82 31           memcpy(out, src + i, run - i);
83 31           out += run - i;
84 31           i = run;
85             }
86 41 100         if (i >= slen) break;
87 37           switch (src[i]) {
88 2           case '&': memcpy(out, "&", 5); out += 5; break;
89 16           case '<': memcpy(out, "<", 4); out += 4; break;
90 16           case '>': memcpy(out, ">", 4); out += 4; break;
91 2           case '"': memcpy(out, """, 6); out += 6; break;
92 1           case '\'': memcpy(out, "'", 5); out += 5; break;
93             }
94 37           i++;
95             }
96 15           return (int)(out - dst);
97             }
98              
99 1           static int html_br_escape(char *dst, const char *src, STRLEN slen) {
100 1           char *out = dst;
101 1           STRLEN i = 0;
102 5 100         while (i < slen) {
103 4           STRLEN run = i;
104 17 50         while (run < slen && !html_br_special[(unsigned char)src[run]]) run++;
    100          
105 4 100         if (run > i) {
106 3           memcpy(out, src + i, run - i);
107 3           out += run - i;
108 3           i = run;
109             }
110 4 50         if (i >= slen) break;
111 4           switch (src[i]) {
112 0           case '&': memcpy(out, "&", 5); out += 5; break;
113 1           case '<': memcpy(out, "<", 4); out += 4; break;
114 1           case '>': memcpy(out, ">", 4); out += 4; break;
115 0           case '"': memcpy(out, """, 6); out += 6; break;
116 0           case '\'': memcpy(out, "'", 5); out += 5; break;
117 2           case '\n': memcpy(out, "
", 4); out += 4; break;
118             }
119 4           i++;
120             }
121 1           return (int)(out - dst);
122             }
123              
124 1           static int url_escape(char *dst, const char *src, STRLEN slen) {
125             static const char hex[] = "0123456789ABCDEF";
126 1           char *out = dst;
127 20 100         for (STRLEN i = 0; i < slen; i++) {
128 19           unsigned char c = (unsigned char)src[i];
129 19 100         if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
    50          
    100          
    50          
    100          
130 3 50         (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~')
    50          
    50          
    50          
    50          
131 16           *out++ = c;
132 3           else { *out++ = '%'; *out++ = hex[c >> 4]; *out++ = hex[c & 0xf]; }
133             }
134 1           return (int)(out - dst);
135             }
136              
137 3           static int json_escape(char *dst, const char *src, STRLEN slen) {
138             static const char hex[] = "0123456789abcdef";
139 3           char *out = dst;
140 30 100         for (STRLEN i = 0; i < slen; i++) {
141 27           unsigned char c = (unsigned char)src[i];
142 27           switch (c) {
143 4           case '"': *out++ = '\\'; *out++ = '"'; break;
144 0           case '\\': *out++ = '\\'; *out++ = '\\'; break;
145 0           case '\b': *out++ = '\\'; *out++ = 'b'; break;
146 0           case '\f': *out++ = '\\'; *out++ = 'f'; break;
147 1           case '\n': *out++ = '\\'; *out++ = 'n'; break;
148 0           case '\r': *out++ = '\\'; *out++ = 'r'; break;
149 0           case '\t': *out++ = '\\'; *out++ = 't'; break;
150 22           default:
151 22 100         if (c < 0x20) {
152 2           *out++ = '\\'; *out++ = 'u'; *out++ = '0'; *out++ = '0';
153 2           *out++ = hex[c >> 4]; *out++ = hex[c & 0xf];
154 20           } else *out++ = c;
155             }
156             }
157 3           return (int)(out - dst);
158             }
159              
160 1           static int hex_encode(char *dst, const char *src, STRLEN slen) {
161             static const char hex[] = "0123456789abcdef";
162 4 100         for (STRLEN i = 0; i < slen; i++) {
163 3           unsigned char c = (unsigned char)src[i];
164 3           dst[i*2] = hex[c >> 4]; dst[i*2+1] = hex[c & 0xf];
165             }
166 1           return (int)(slen * 2);
167             }
168              
169             static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
170             static const char b64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
171              
172 10           static int base64_encode_with(char *dst, const unsigned char *src, STRLEN slen, const char *alpha, int pad) {
173 10           char *out = dst;
174             STRLEN i;
175 14 100         for (i = 0; i + 2 < slen; i += 3) {
176 4           *out++ = alpha[src[i] >> 2];
177 4           *out++ = alpha[((src[i] & 3) << 4) | (src[i+1] >> 4)];
178 4           *out++ = alpha[((src[i+1] & 0xf) << 2) | (src[i+2] >> 6)];
179 4           *out++ = alpha[src[i+2] & 0x3f];
180             }
181 10 100         if (i < slen) {
182 9           *out++ = alpha[src[i] >> 2];
183 9 100         if (i + 1 < slen) {
184 6           *out++ = alpha[((src[i] & 3) << 4) | (src[i+1] >> 4)];
185 6           *out++ = alpha[((src[i+1] & 0xf) << 2)];
186             } else {
187 3           *out++ = alpha[((src[i] & 3) << 4)];
188 3 100         if (pad) *out++ = '=';
189             }
190 9 100         if (pad) *out++ = '=';
191             }
192 10           return (int)(out - dst);
193             }
194             #define base64_encode(d,s,l) base64_encode_with(d,s,l,b64,1)
195             #define base64url_encode(d,s,l) base64_encode_with(d,s,l,b64url,0)
196              
197             /* ---- compiled template structures ---- */
198              
199             enum xform_type {
200             XF_INT, XF_INT_COMMA, XF_FLOAT, XF_HTML, XF_HTML_BR,
201             XF_RAW, XF_URL, XF_JSON, XF_TRIM, XF_UC, XF_LC,
202             XF_PAD, XF_RPAD, XF_TRUNC, XF_DEFAULT,
203             XF_HEX, XF_BASE64, XF_BASE64URL, XF_COUNT, XF_BOOL, XF_DATE,
204             XF_SPRINTF, XF_REPLACE, XF_SUBSTR, XF_PLURAL,
205             XF_IF, XF_UNLESS, XF_MAP, XF_WRAP,
206             XF_NUMBER_SI, XF_BYTES_SI, XF_ELAPSED, XF_AGO,
207             XF_MASK, XF_ROWNUM, XF_COALESCE, XF_LENGTH
208             };
209             enum row_mode { ROW_ARRAY, ROW_HASH };
210              
211             typedef struct {
212             enum xform_type type;
213             int param_int;
214             char *param_str;
215             STRLEN param_str_len;
216             char *param_str2; /* bool falsy, replace new, plural plural-form, wrap suffix */
217             STRLEN param_str2_len;
218             int param_int2; /* substr length, map entry count */
219             /* map entries: stored as parallel arrays of keys and values */
220             char **map_keys; STRLEN *map_key_lens;
221             char **map_vals; STRLEN *map_val_lens;
222             int map_count;
223             } tpl_xform;
224              
225             typedef struct {
226             /* static text (if chain is NULL) */
227             char *static_data; STRLEN static_len;
228             /* field ref */
229             int col;
230             char *key; STRLEN key_len;
231             /* transform chain */
232             tpl_xform *chain;
233             int chain_len;
234             int is_rownum;
235             } tpl_op;
236              
237             typedef struct {
238             char *header; STRLEN header_len;
239             char *footer; STRLEN footer_len;
240             char *sep; STRLEN sep_len;
241             tpl_op *ops; int nops;
242             enum row_mode mode;
243             SSize_t last_row_count;
244             char escape_char; /* delimiter char, default '{' */
245             char *render_buf; STRLEN render_buf_alloc;
246             /* skip_if / skip_unless */
247             int skip_if_col; char *skip_if_key; STRLEN skip_if_key_len; int has_skip_if;
248             int skip_unless_col; char *skip_unless_key; STRLEN skip_unless_key_len; int has_skip_unless;
249             } tpl_compiled;
250              
251 206           static void tpl_free(tpl_compiled *t) {
252 206 100         if (t->header) free(t->header);
253 206 100         if (t->footer) free(t->footer);
254 206 100         if (t->sep) free(t->sep);
255 206 100         if (t->render_buf) free(t->render_buf);
256 206 100         if (t->skip_if_key) free(t->skip_if_key);
257 206 100         if (t->skip_unless_key) free(t->skip_unless_key);
258 474 100         for (int i = 0; i < t->nops; i++) {
259 268 100         if (t->ops[i].static_data) free(t->ops[i].static_data);
260 268 100         if (t->ops[i].key) free(t->ops[i].key);
261 268 100         if (t->ops[i].chain) {
262 457 100         for (int j = 0; j < t->ops[i].chain_len; j++) {
263 237           tpl_xform *x = &t->ops[i].chain[j];
264 237 100         if (x->param_str) free(x->param_str);
265 237 100         if (x->param_str2) free(x->param_str2);
266 237 100         if (x->map_keys) {
267 18 100         for (int k = 0; k < x->map_count; k++) { free(x->map_keys[k]); free(x->map_vals[k]); }
268 5           free(x->map_keys); free(x->map_vals); free(x->map_key_lens); free(x->map_val_lens);
269             }
270             }
271 220           free(t->ops[i].chain);
272             }
273             }
274 206 50         if (t->ops) free(t->ops);
275 206           free(t);
276 206           }
277              
278             /* parse "type" or "type:param" or "type:param1:param2" */
279 235           static tpl_xform parse_xform(const char *s, int len) {
280 235           tpl_xform x = {XF_RAW, 0, NULL, 0, NULL, 0};
281 235           const char *colon = memchr(s, ':', len);
282 235 100         int tlen = colon ? (int)(colon - s) : len;
283 235 100         const char *param = colon ? colon + 1 : NULL;
284 235 100         int plen = colon ? len - tlen - 1 : 0;
285              
286 235 100         if (tlen == 3 && memcmp(s, "int", 3) == 0) x.type = XF_INT;
    100          
287 206 100         else if (tlen == 9 && memcmp(s, "int_comma", 9) == 0) x.type = XF_INT_COMMA;
    100          
288 199 100         else if (tlen == 5 && memcmp(s, "float", 5) == 0) { x.type = XF_FLOAT; x.param_int = 2; }
    100          
289 195 100         else if (tlen == 4 && memcmp(s, "html", 4) == 0) x.type = XF_HTML;
    100          
290 175 100         else if (tlen == 7 && memcmp(s, "html_br", 7) == 0) x.type = XF_HTML_BR;
    100          
291 173 100         else if (tlen == 3 && memcmp(s, "raw", 3) == 0) x.type = XF_RAW;
    100          
292 141 100         else if (tlen == 3 && memcmp(s, "url", 3) == 0) x.type = XF_URL;
    100          
293 139 100         else if (tlen == 4 && memcmp(s, "json", 4) == 0) x.type = XF_JSON;
    100          
294 134 100         else if (tlen == 4 && memcmp(s, "trim", 4) == 0) x.type = XF_TRIM;
    100          
295 123 100         else if (tlen == 2 && memcmp(s, "uc", 2) == 0) x.type = XF_UC;
    100          
296 115 100         else if (tlen == 2 && memcmp(s, "lc", 2) == 0) x.type = XF_LC;
    100          
297 109 100         else if (tlen == 3 && memcmp(s, "pad", 3) == 0) x.type = XF_PAD;
    100          
298 105 100         else if (tlen == 4 && memcmp(s, "rpad", 4) == 0) x.type = XF_RPAD;
    100          
299 104 100         else if (tlen == 5 && memcmp(s, "trunc", 5) == 0) x.type = XF_TRUNC;
    100          
300 101 100         else if (tlen == 7 && memcmp(s, "default", 7) == 0) x.type = XF_DEFAULT;
    100          
301 96 100         else if (tlen == 3 && memcmp(s, "hex", 3) == 0) x.type = XF_HEX;
    100          
302 94 100         else if (tlen == 9 && memcmp(s, "base64url", 9) == 0) x.type = XF_BASE64URL;
    100          
303 90 100         else if (tlen == 6 && memcmp(s, "base64", 6) == 0) x.type = XF_BASE64;
    100          
304 86 100         else if (tlen == 5 && memcmp(s, "count", 5) == 0) x.type = XF_COUNT;
    100          
305 80 100         else if (tlen == 4 && memcmp(s, "bool", 4) == 0) x.type = XF_BOOL;
    100          
306 73 100         else if (tlen == 4 && memcmp(s, "date", 4) == 0) x.type = XF_DATE;
    100          
307 71 100         else if (tlen == 7 && memcmp(s, "sprintf", 7) == 0) x.type = XF_SPRINTF;
    100          
308 65 100         else if (tlen == 7 && memcmp(s, "replace", 7) == 0) x.type = XF_REPLACE;
    100          
309 60 100         else if (tlen == 6 && memcmp(s, "substr", 6) == 0) x.type = XF_SUBSTR;
    100          
310 56 100         else if (tlen == 6 && memcmp(s, "plural", 6) == 0) x.type = XF_PLURAL;
    100          
311 51 100         else if (tlen == 2 && memcmp(s, "if", 2) == 0) x.type = XF_IF;
    50          
312 47 100         else if (tlen == 6 && memcmp(s, "unless", 6) == 0) x.type = XF_UNLESS;
    100          
313 45 100         else if (tlen == 3 && memcmp(s, "map", 3) == 0) x.type = XF_MAP;
    100          
314 40 100         else if (tlen == 4 && memcmp(s, "wrap", 4) == 0) x.type = XF_WRAP;
    100          
315 36 100         else if (tlen == 9 && memcmp(s, "number_si", 9) == 0) x.type = XF_NUMBER_SI;
    50          
316 31 100         else if (tlen == 8 && memcmp(s, "bytes_si", 8) == 0) x.type = XF_BYTES_SI;
    100          
317 26 100         else if (tlen == 7 && memcmp(s, "elapsed", 7) == 0) x.type = XF_ELAPSED;
    50          
318 20 100         else if (tlen == 3 && memcmp(s, "ago", 3) == 0) x.type = XF_AGO;
    50          
319 16 100         else if (tlen == 4 && memcmp(s, "mask", 4) == 0) { x.type = XF_MASK; x.param_int = 4; }
    50          
320 9 100         else if (tlen == 6 && memcmp(s, "length", 6) == 0) x.type = XF_LENGTH;
    50          
321 6 100         else if (tlen == 8 && memcmp(s, "coalesce", 8) == 0) x.type = XF_COALESCE;
    50          
322              
323 235 100         if (param && plen == 0 && x.type == XF_DEFAULT) {
    100          
    50          
324 1           x.param_str = (char *)malloc(1);
325 1           x.param_str[0] = '\0';
326 1           x.param_str_len = 0;
327 234 100         } else if (param && plen > 0) {
    50          
328 65 100         if (x.type == XF_FLOAT || x.type == XF_PAD || x.type == XF_RPAD || x.type == XF_TRUNC || x.type == XF_MASK) {
    100          
    100          
    100          
    100          
329 15           x.param_int = 0;
330 33 100         for (int i = 0; i < plen; i++) x.param_int = x.param_int * 10 + (param[i] - '0');
331 50 100         } else if (x.type == XF_SPRINTF) {
332 6           x.param_str = (char *)malloc(plen + 1);
333 6           memcpy(x.param_str, param, plen);
334 6           x.param_str[plen] = '\0';
335 6           x.param_str_len = plen;
336 44 100         } else if (x.type == XF_REPLACE) {
337 5           const char *c2 = memchr(param, ':', plen);
338 5 50         if (c2) {
339 5           int p1len = (int)(c2 - param);
340 5           int p2len = plen - p1len - 1;
341 5 100         if (p1len > 0) {
342 4           x.param_str = (char *)malloc(p1len + 1);
343 4           memcpy(x.param_str, param, p1len);
344 4           x.param_str_len = p1len;
345 4           x.param_str2 = (char *)malloc(p2len + 1);
346 4           memcpy(x.param_str2, c2 + 1, p2len);
347 4           x.param_str2_len = p2len;
348             }
349             }
350 39 100         } else if (x.type == XF_SUBSTR) {
351 4           const char *c2 = memchr(param, ':', plen);
352 4           x.param_int = 0;
353 4 100         int p1len = c2 ? (int)(c2 - param) : plen;
354 10 100         for (int i = 0; i < p1len; i++) x.param_int = x.param_int * 10 + (param[i] - '0');
355 4           x.param_int2 = -1;
356 4 100         if (c2) {
357 1           x.param_int2 = 0;
358 1           int p2len = plen - p1len - 1;
359 2 100         for (int i = 0; i < p2len; i++) x.param_int2 = x.param_int2 * 10 + (c2[1+i] - '0');
360             }
361 35 100         } else if (x.type == XF_PLURAL) {
362 5           const char *c2 = memchr(param, ':', plen);
363 5 50         if (c2) {
364 5           int p1len = (int)(c2 - param);
365 5           int p2len = plen - p1len - 1;
366 5           x.param_str = (char *)malloc(p1len + 1);
367 5           memcpy(x.param_str, param, p1len);
368 5           x.param_str_len = p1len;
369 5           x.param_str2 = (char *)malloc(p2len + 1);
370 5           memcpy(x.param_str2, c2 + 1, p2len);
371 5           x.param_str2_len = p2len;
372             }
373 30 100         } else if (x.type == XF_IF || x.type == XF_UNLESS) {
    100          
374 6           x.param_str = (char *)malloc(plen + 1);
375 6           memcpy(x.param_str, param, plen);
376 6           x.param_str_len = plen;
377 24 100         } else if (x.type == XF_WRAP) {
378 4           const char *c2 = memchr(param, ':', plen);
379 4 100         if (c2) {
380 3           int p1len = (int)(c2 - param);
381 3           int p2len = plen - p1len - 1;
382 3           x.param_str = (char *)malloc(p1len + 1);
383 3           memcpy(x.param_str, param, p1len);
384 3           x.param_str_len = p1len;
385 3           x.param_str2 = (char *)malloc(p2len + 1);
386 3           memcpy(x.param_str2, c2 + 1, p2len);
387 3           x.param_str2_len = p2len;
388             } else {
389 1           x.param_str = (char *)malloc(plen + 1);
390 1           memcpy(x.param_str, param, plen);
391 1           x.param_str_len = plen;
392             }
393 20 100         } else if (x.type == XF_MAP) {
394 5           int cnt = 1;
395 96 100         for (int i = 0; i < plen; i++) if (param[i] == ':') cnt++;
    100          
396 5           x.map_keys = (char **)calloc(cnt, sizeof(char *));
397 5           x.map_vals = (char **)calloc(cnt, sizeof(char *));
398 5           x.map_key_lens = (STRLEN *)calloc(cnt, sizeof(STRLEN));
399 5           x.map_val_lens = (STRLEN *)calloc(cnt, sizeof(STRLEN));
400 5           x.map_count = 0;
401 5           const char *p2 = param, *pe = param + plen;
402 18 100         while (p2 < pe) {
403 13           const char *next = memchr(p2, ':', pe - p2);
404 13 100         if (!next) next = pe;
405 13           const char *eq = memchr(p2, '=', next - p2);
406 13 50         if (eq) {
407 13           int kl = (int)(eq - p2), vl = (int)(next - eq - 1);
408 13           int idx = x.map_count++;
409 13           x.map_keys[idx] = (char *)malloc(kl + 1); memcpy(x.map_keys[idx], p2, kl); x.map_key_lens[idx] = kl;
410 13           x.map_vals[idx] = (char *)malloc(vl + 1); memcpy(x.map_vals[idx], eq + 1, vl); x.map_val_lens[idx] = vl;
411             }
412 13           p2 = next + 1;
413             }
414 15 100         } else if (x.type == XF_COALESCE) {
415 5           x.param_str = (char *)malloc(plen + 1);
416 5           memcpy(x.param_str, param, plen);
417 5           x.param_str[plen] = '\0';
418 5           x.param_str_len = plen;
419 10 100         } else if (x.type == XF_DEFAULT || x.type == XF_DATE) {
    100          
420 5           x.param_str = (char *)malloc(plen + 1);
421 5           memcpy(x.param_str, param, plen);
422 5           x.param_str[plen] = '\0';
423 5           x.param_str_len = plen;
424 5 50         } else if (x.type == XF_BOOL) {
425 5           const char *c2 = memchr(param, ':', plen);
426 5 50         if (c2) {
427 5           int p1len = (int)(c2 - param);
428 5           int p2len = plen - p1len - 1;
429 5           x.param_str = (char *)malloc(p1len + 1);
430 5           memcpy(x.param_str, param, p1len);
431 5           x.param_str_len = p1len;
432 5           x.param_str2 = (char *)malloc(p2len + 1);
433 5           memcpy(x.param_str2, c2 + 1, p2len);
434 5           x.param_str2_len = p2len;
435             } else {
436 0           x.param_str = (char *)malloc(plen + 1);
437 0           memcpy(x.param_str, param, plen);
438 0           x.param_str_len = plen;
439 0           x.param_str2 = NULL; x.param_str2_len = 0;
440             }
441             }
442             }
443 235           return x;
444             }
445              
446             /* Parse "{field:type1:p1|type2:p2}" into chain */
447 222           static void parse_field_spec(const char *spec, int spec_len,
448             tpl_op *op, enum row_mode *mode) {
449             /* split field name from transform chain at first : or | */
450 222           const char *sep = NULL;
451 493 100         for (int i = 0; i < spec_len; i++) {
452 487 100         if (spec[i] == ':' || spec[i] == '|') { sep = spec + i; break; }
    50          
453             }
454              
455 222           const char *field = spec;
456 222 100         int field_len = sep ? (int)(sep - spec) : spec_len;
457              
458             /* check for row number placeholder {#} */
459 222 100         if (field_len == 1 && field[0] == '#') {
    100          
460 5           op->is_rownum = 1;
461             /* parse transform chain if any */
462 5 100         if (!sep) {
463 3           op->chain = (tpl_xform *)malloc(sizeof(tpl_xform));
464 3           op->chain[0] = (tpl_xform){XF_RAW, 0, NULL, 0};
465 3           op->chain_len = 1;
466 3           return;
467             }
468 2           goto parse_chain;
469             }
470              
471             /* detect array vs hash mode */
472 217           int is_num = 1, is_neg = 0, start_idx = 0;
473 217 50         if (field_len > 0 && field[0] == '-') { is_neg = 1; start_idx = 1; }
    100          
474 415 100         for (int i = start_idx; i < field_len; i++)
475 217 50         if (field[i] < '0' || field[i] > '9') { is_num = 0; break; }
    100          
476              
477 217 100         if (is_num && field_len > start_idx) {
    50          
478 198           op->col = 0;
479 396 100         for (int i = start_idx; i < field_len; i++) op->col = op->col * 10 + (field[i] - '0');
480 198 100         if (is_neg) op->col = -op->col;
481             } else {
482 19           *mode = ROW_HASH;
483 19           op->key = (char *)malloc(field_len + 1);
484 19           memcpy(op->key, field, field_len);
485 19           op->key[field_len] = '\0';
486 19           op->key_len = field_len;
487             }
488              
489             /* parse transform chain */
490 217 100         if (!sep) {
491 3           op->chain = (tpl_xform *)malloc(sizeof(tpl_xform));
492 3           op->chain[0] = (tpl_xform){XF_RAW, 0, NULL, 0};
493 3           op->chain_len = 1;
494 3           return;
495             }
496              
497 214           parse_chain:;
498 216           const char *xforms_start = spec + field_len;
499 216 50         if (*xforms_start == ':' || *xforms_start == '|') xforms_start++;
    0          
500 216           int xforms_len = spec_len - (int)(xforms_start - spec);
501              
502             /* count pipes to size the chain */
503 216           int nxforms = 1;
504 1724 100         for (int i = 0; i < xforms_len; i++) if (xforms_start[i] == '|') nxforms++;
    100          
505              
506 216           op->chain = (tpl_xform *)malloc(nxforms * sizeof(tpl_xform));
507 216           op->chain_len = 0;
508              
509 216           const char *p = xforms_start;
510 216           const char *xend = xforms_start + xforms_len;
511 451 100         while (p < xend) {
512 235           const char *pipe = memchr(p, '|', xend - p);
513 235 100         if (!pipe) pipe = xend;
514 235           op->chain[op->chain_len++] = parse_xform(p, (int)(pipe - p));
515 235           p = pipe + 1;
516             }
517              
518             /* count/coalesce operate on the raw field value (array/hash size, or the
519             first truthy field), so they are only meaningful as the first transform.
520             Mid-chain they would silently emit "0"/nothing; reject at compile time. */
521 233 100         for (int i = 1; i < op->chain_len; i++) {
522 19 100         if (op->chain[i].type == XF_COUNT)
523 1           croak("Text::Stencil: 'count' must be the first transform in a chain");
524 18 100         if (op->chain[i].type == XF_COALESCE)
525 1           croak("Text::Stencil: 'coalesce' must be the first transform in a chain");
526             }
527             }
528              
529 208           static tpl_compiled *tpl_compile(pTHX_ const char *header, STRLEN hlen,
530             const char *row, STRLEN rlen,
531             const char *footer, STRLEN flen,
532             const char *sep, STRLEN slen,
533             char esc_char) {
534 208           tpl_compiled *t = (tpl_compiled *)calloc(1, sizeof(tpl_compiled));
535 208 100         if (hlen) { t->header = (char *)malloc(hlen); memcpy(t->header, header, hlen); } t->header_len = hlen;
536 208 100         if (flen) { t->footer = (char *)malloc(flen); memcpy(t->footer, footer, flen); } t->footer_len = flen;
537 208 100         if (slen) { t->sep = (char *)malloc(slen); memcpy(t->sep, sep, slen); t->sep_len = slen; }
538 208           t->mode = ROW_ARRAY;
539 208 100         t->escape_char = esc_char ? esc_char : '{';
540              
541 208           int cap = 16;
542 208           t->ops = (tpl_op *)calloc(cap, sizeof(tpl_op));
543 208           t->nops = 0;
544              
545 208 100         char close_char = (t->escape_char == '{') ? '}' : t->escape_char;
546 208 100         if (t->escape_char == '[') close_char = ']';
547 208 50         if (t->escape_char == '(') close_char = ')';
548 208 100         if (t->escape_char == '<') close_char = '>';
549              
550 208           const char *p = row, *end = row + rlen;
551 431 100         while (p < end) {
552 242           const char *brace = memchr(p, t->escape_char, end - p);
553 242 100         if (!brace) brace = end;
554 242 100         if (brace > p) {
555 45 50         if (t->nops >= cap) { cap *= 2; t->ops = (tpl_op *)realloc(t->ops, cap * sizeof(tpl_op)); if (!t->ops) croak("realloc"); memset(&t->ops[cap/2], 0, (cap/2)*sizeof(tpl_op)); }
    0          
556 45           tpl_op *op = &t->ops[t->nops++];
557 45           memset(op, 0, sizeof(*op));
558 45           op->static_data = (char *)malloc(brace - p);
559 45           memcpy(op->static_data, p, brace - p);
560 45           op->static_len = brace - p;
561             }
562 242 100         if (brace >= end) break;
563              
564             /* doubled escape char (e.g. {{ ) = literal */
565 225 50         if (brace + 1 < end && brace[1] == t->escape_char) {
    100          
566 3 50         if (t->nops >= cap) { cap *= 2; t->ops = (tpl_op *)realloc(t->ops, cap * sizeof(tpl_op)); if (!t->ops) croak("realloc"); memset(&t->ops[cap/2], 0, (cap/2)*sizeof(tpl_op)); }
    0          
567 3           tpl_op *op = &t->ops[t->nops++];
568 3           memset(op, 0, sizeof(*op));
569 3           op->static_data = (char *)malloc(1);
570 3           op->static_data[0] = t->escape_char;
571 3           op->static_len = 1;
572 3           p = brace + 2;
573 3           continue;
574             }
575              
576 222           const char *close = memchr(brace + 1, close_char, end - brace - 1);
577 222 50         if (!close) break;
578              
579 222 50         if (t->nops >= cap) { cap *= 2; t->ops = (tpl_op *)realloc(t->ops, cap * sizeof(tpl_op)); if (!t->ops) croak("realloc"); memset(&t->ops[cap/2], 0, (cap/2)*sizeof(tpl_op)); }
    0          
580 222           tpl_op *op = &t->ops[t->nops++];
581 222           memset(op, 0, sizeof(*op));
582 222           parse_field_spec(brace + 1, (int)(close - brace - 1), op, &t->mode);
583 220           p = close + 1;
584             }
585 206           return t;
586             }
587              
588             /* ---- render ---- */
589              
590             #define BUF_ENSURE(need) do { if (pos + (need) > alloc) { alloc = (pos + (need)) * 2; buf = (char *)realloc(buf, alloc); if (!buf) croak("realloc"); } } while(0)
591             #define BUF_WRITE(s, l) do { BUF_ENSURE(l); memcpy(buf + pos, s, l); pos += l; } while(0)
592              
593             /* reusable buffer macros for render_buf */
594             #define RBUF_INIT(t, est) do { \
595             if (t->render_buf && t->render_buf_alloc >= (est)) { \
596             buf = t->render_buf; alloc = t->render_buf_alloc; \
597             } else { \
598             alloc = (est); buf = (char *)realloc(t->render_buf, alloc); \
599             if (!buf) croak("realloc"); \
600             t->render_buf = buf; t->render_buf_alloc = alloc; \
601             } \
602             /* Detach the reusable buffer for the render's duration. The live buffer \
603             lives only in the local `buf`; a BUF_ENSURE/render_field realloc can \
604             move it, and a render path can croak (e.g. a row callback dies). \
605             Leaving t->render_buf NULL until RBUF_FINISH guarantees DESTROY and the \
606             next render never free/realloc a stale pointer. A croak mid-render \
607             loses the in-flight buffer -- a bounded, error-path-only leak traded \
608             for memory safety. */ \
609             t->render_buf = NULL; t->render_buf_alloc = 0; \
610             pos = 0; \
611             } while(0)
612             #define RBUF_FINISH(t) do { t->render_buf = buf; t->render_buf_alloc = alloc; } while(0)
613              
614 10566           static inline SV *fetch_field(pTHX_ SV *row_sv, tpl_op *op, enum row_mode mode) {
615 10566 100         if (mode == ROW_HASH) {
616 110 50         if (!SvROK(row_sv) || SvTYPE(SvRV(row_sv)) != SVt_PVHV) return NULL;
    50          
617 110           SV **sv = hv_fetch((HV *)SvRV(row_sv), op->key, op->key_len, 0);
618 110 50         return sv ? *sv : NULL;
619             } else {
620 10456 50         if (!SvROK(row_sv) || SvTYPE(SvRV(row_sv)) != SVt_PVAV) return NULL;
    50          
621 10456           AV *av = (AV *)SvRV(row_sv);
622 10456           SV **ary = AvARRAY(av);
623 10456 50         SSize_t top = av_top_index(av);
624 10456           int col = op->col;
625 10456 100         if (col < 0) col = (int)(top + 1) + col;
626 10456 50         if (col >= 0 && col <= (int)top) return ary[col];
    50          
627 0           return NULL;
628             }
629             }
630              
631             /* Apply a single transform, writing result to buf+pos or tmp */
632 1495           static void apply_xform(tpl_xform *xf, const char *src, STRLEN slen,
633             char **bufp, STRLEN *posp, STRLEN *allocp,
634             char **tmpp, STRLEN *tmp_lenp, STRLEN *tmp_allocp,
635             int to_output) {
636 1495           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
637 1495           char *tmp = *tmpp; STRLEN tmp_len = *tmp_lenp; STRLEN tmp_alloc = *tmp_allocp;
638              
639             /* macro to write to either output or temp */
640             #define OUT_ENSURE(n) do { \
641             if (to_output) { BUF_ENSURE(n); } \
642             else { if ((n) > tmp_alloc || !tmp) { tmp_alloc = (n) < 1 ? 1 : (STRLEN)(n) * 2; tmp = (char *)realloc(tmp, tmp_alloc); if (!tmp) croak("realloc"); } } \
643             } while(0)
644             #define OUT_PTR (to_output ? buf + pos : tmp)
645              
646 1495           switch (xf->type) {
647 0           case XF_INT: {
648 0 0         OUT_ENSURE(20);
    0          
    0          
    0          
    0          
    0          
649 0           long v = 0; int neg = 0;
650 0 0         for (STRLEN i = 0; i < slen; i++) {
651 0 0         if (src[i] == '-') neg = 1;
652 0 0         else if (src[i] >= '0' && src[i] <= '9') v = v * 10 + (src[i] - '0');
    0          
653             }
654 0 0         if (neg) v = -v;
655 0 0         int w = itoa_fast(OUT_PTR, v);
656 0 0         if (to_output) pos += w; else tmp_len = w;
657 0           break;
658             }
659 1001           case XF_INT_COMMA: {
660 1001 50         OUT_ENSURE(28);
    50          
    0          
    0          
    0          
    0          
661 1001           long v = 0; int neg = 0;
662 3895 100         for (STRLEN i = 0; i < slen; i++) {
663 2894 50         if (src[i] == '-') neg = 1;
664 2894 50         else if (src[i] >= '0' && src[i] <= '9') v = v * 10 + (src[i] - '0');
    50          
665             }
666 1001 50         if (neg) v = -v;
667 1001 50         int w = itoa_comma(OUT_PTR, v);
668 1001 50         if (to_output) pos += w; else tmp_len = w;
669 1001           break;
670             }
671 0           case XF_FLOAT: {
672 0 0         OUT_ENSURE(64);
    0          
    0          
    0          
    0          
    0          
673 0           double fv = 0;
674 0 0         { char tb[64]; int tl = slen < 63 ? (int)slen : 63; memcpy(tb, src, tl); tb[tl] = 0; fv = atof(tb); }
675 0 0         int w = snprintf(OUT_PTR, 64, "%.*f", xf->param_int, fv);
676 0 0         if (w > 63) w = 63;
677 0 0         if (to_output) pos += w; else tmp_len = w;
678 0           break;
679             }
680 32           case XF_HTML: {
681 32 50         OUT_ENSURE(slen * 6);
    100          
    50          
    0          
    0          
    0          
    0          
682 32           int needs_escape = 0;
683 100689 100         for (STRLEN i = 0; i < slen; i++)
684 100672 100         if (html_special[(unsigned char)src[i]]) { needs_escape = 1; break; }
685 32 100         if (!needs_escape) {
686 17 50         memcpy(OUT_PTR, src, slen);
687 17 50         if (to_output) pos += slen; else tmp_len = slen;
688             } else {
689 15 50         int w = html_escape(OUT_PTR, src, slen);
690 15 50         if (to_output) pos += w; else tmp_len = w;
691             }
692 32           break;
693             }
694 1           case XF_HTML_BR: {
695 1 50         OUT_ENSURE(slen * 6);
    50          
    0          
    0          
    0          
    0          
    0          
696 1           int needs_escape = 0;
697 6 50         for (STRLEN i = 0; i < slen; i++)
698 6 100         if (html_br_special[(unsigned char)src[i]]) { needs_escape = 1; break; }
699 1 50         if (!needs_escape) {
700 0 0         memcpy(OUT_PTR, src, slen);
701 0 0         if (to_output) pos += slen; else tmp_len = slen;
702             } else {
703 1 50         int w = html_br_escape(OUT_PTR, src, slen);
704 1 50         if (to_output) pos += w; else tmp_len = w;
705             }
706 1           break;
707             }
708 1           case XF_URL: {
709 1 50         OUT_ENSURE(slen * 3);
    50          
    0          
    0          
    0          
    0          
    0          
710 1 50         int w = url_escape(OUT_PTR, src, slen);
711 1 50         if (to_output) pos += w; else tmp_len = w;
712 1           break;
713             }
714 4           case XF_JSON: {
715 4 50         OUT_ENSURE(slen * 6);
    50          
    0          
    0          
    0          
    0          
    0          
716 4           int needs_escape = 0;
717 25 100         for (STRLEN i = 0; i < slen; i++)
718 24 100         if (json_special[(unsigned char)src[i]]) { needs_escape = 1; break; }
719 4 100         if (!needs_escape) {
720 1 50         memcpy(OUT_PTR, src, slen);
721 1 50         if (to_output) pos += slen; else tmp_len = slen;
722             } else {
723 3 50         int w = json_escape(OUT_PTR, src, slen);
724 3 50         if (to_output) pos += w; else tmp_len = w;
725             }
726 4           break;
727             }
728 9           case XF_TRIM: {
729 9           const char *s = src; STRLEN l = slen;
730 21 50         while (l > 0 && (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) { s++; l--; }
    100          
    50          
    50          
    50          
731 22 50         while (l > 0 && (s[l-1] == ' ' || s[l-1] == '\t' || s[l-1] == '\n' || s[l-1] == '\r')) l--;
    100          
    50          
    100          
    50          
732 9 100         OUT_ENSURE(l);
    50          
    0          
    100          
    50          
    50          
    50          
733 9 100         memcpy(OUT_PTR, s, l);
734 9 100         if (to_output) pos += l; else tmp_len = l;
735 9           break;
736             }
737 6           case XF_UC: {
738 6 100         OUT_ENSURE(slen);
    50          
    0          
    50          
    0          
    50          
    50          
739 34 100         for (STRLEN i = 0; i < slen; i++) OUT_PTR[i] = toupper((unsigned char)src[i]);
    100          
740 6 100         if (to_output) pos += slen; else tmp_len = slen;
741 6           break;
742             }
743 5           case XF_LC: {
744 5 100         OUT_ENSURE(slen);
    50          
    0          
    100          
    50          
    50          
    50          
745 38 100         for (STRLEN i = 0; i < slen; i++) OUT_PTR[i] = tolower((unsigned char)src[i]);
    100          
746 5 100         if (to_output) pos += slen; else tmp_len = slen;
747 5           break;
748             }
749 4           case XF_PAD: {
750 4           int w = xf->param_int;
751 4 50         OUT_ENSURE(w > (int)slen ? w : slen);
    100          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
752 4           int pad = w - (int)slen;
753 4 100         if (pad > 0) { memset(OUT_PTR, ' ', pad); memcpy(OUT_PTR + pad, src, slen); }
    50          
    50          
754 1 50         else memcpy(OUT_PTR, src, slen);
755 4 100         int total = pad > 0 ? w : (int)slen;
756 4 50         if (to_output) pos += total; else tmp_len = total;
757 4           break;
758             }
759 1           case XF_RPAD: {
760 1           int w = xf->param_int;
761 1 50         OUT_ENSURE(w > (int)slen ? w : slen);
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
762 1 50         memcpy(OUT_PTR, src, slen);
763 1           int pad = w - (int)slen;
764 1 50         if (pad > 0) memset(OUT_PTR + slen, ' ', pad);
    50          
765 1 50         int total = pad > 0 ? w : (int)slen;
766 1 50         if (to_output) pos += total; else tmp_len = total;
767 1           break;
768             }
769 3           case XF_TRUNC: {
770 3           int mx = xf->param_int;
771 3 100         if ((int)slen <= mx) {
772 1 50         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
773 1 50         if (to_output) pos += slen; else tmp_len = slen;
774             } else {
775 2           int tl = mx > 3 ? mx - 3 : 0;
776 2 100         OUT_ENSURE(tl + 3);
    50          
    0          
    50          
    0          
    50          
    50          
777 2 100         memcpy(OUT_PTR, src, tl);
778 2 100         memcpy(OUT_PTR + tl, "...", 3);
779 2 100         if (to_output) pos += tl + 3; else tmp_len = tl + 3;
780             }
781 3           break;
782             }
783 1           case XF_HEX: {
784 1 50         OUT_ENSURE(slen * 2);
    50          
    0          
    0          
    0          
    0          
    0          
785 1 50         int w = hex_encode(OUT_PTR, src, slen);
786 1 50         if (to_output) pos += w; else tmp_len = w;
787 1           break;
788             }
789 7           case XF_BASE64: {
790 7 50         OUT_ENSURE(((slen + 2) / 3) * 4);
    50          
    0          
    0          
    0          
    0          
    0          
791 7 50         int w = base64_encode(OUT_PTR, (const unsigned char *)src, slen);
792 7 50         if (to_output) pos += w; else tmp_len = w;
793 7           break;
794             }
795 3           case XF_BASE64URL: {
796 3 50         OUT_ENSURE(((slen + 2) / 3) * 4);
    50          
    0          
    0          
    0          
    0          
    0          
797 3 50         int w = base64url_encode(OUT_PTR, (const unsigned char *)src, slen);
798 3 50         if (to_output) pos += w; else tmp_len = w;
799 3           break;
800             }
801 0           case XF_COUNT: {
802             /* unreachable: count must be the first transform (compile-time croak
803             otherwise) and is handled in render_field before apply_xform; kept
804             for -Wswitch completeness */
805 0 0         OUT_ENSURE(12);
    0          
    0          
    0          
    0          
    0          
806 0 0         int w = itoa_fast(OUT_PTR, xf->param_int);
807 0 0         if (to_output) pos += w; else tmp_len = w;
808 0           break;
809             }
810 7           case XF_BOOL: {
811 7           int truthy = (slen > 0);
812 7 100         if (slen == 1 && src[0] == '0') truthy = 0;
    100          
813             const char *val; STRLEN vlen;
814 7 100         if (truthy) {
815 3 100         val = xf->param_str ? xf->param_str : "true";
816 3 100         vlen = xf->param_str ? xf->param_str_len : 4;
817             } else {
818 4 100         val = xf->param_str2 ? xf->param_str2 : "false";
819 4 100         vlen = xf->param_str2 ? xf->param_str2_len : 5;
820             }
821 7 50         OUT_ENSURE(vlen); memcpy(OUT_PTR, val, vlen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
822 7 50         if (to_output) pos += vlen; else tmp_len = vlen;
823 7           break;
824             }
825 2           case XF_DATE: {
826 2           time_t epoch = 0;
827 13 100         for (STRLEN i = 0; i < slen; i++) {
828 11 50         if (src[i] >= '0' && src[i] <= '9') epoch = epoch * 10 + (src[i] - '0');
    50          
829             }
830             struct tm tm;
831 2           gmtime_r(&epoch, &tm);
832 2 100         const char *fmt = xf->param_str ? xf->param_str : "%Y-%m-%d %H:%M:%S";
833 2 50         OUT_ENSURE(256);
    50          
    0          
    0          
    0          
    0          
834 2 50         int w = (int)strftime(OUT_PTR, 256, fmt, &tm);
835 2 50         if (to_output) pos += w; else tmp_len = w;
836 2           break;
837             }
838 6           case XF_SPRINTF: {
839 6 50         OUT_ENSURE(256);
    50          
    0          
    0          
    0          
    0          
840 6 50         if (xf->param_str && xf->param_str_len > 0) {
    50          
841 6           char last = xf->param_str[xf->param_str_len - 1];
842 6 100         if (last != 'd' && last != 'i' && last != 'x' && last != 'X' &&
    50          
    100          
    50          
    50          
843 4 50         last != 'o' && last != 'u' && last != 'f' && last != 'e' &&
    100          
    50          
    50          
844 3 100         last != 'g' && last != 's') {
845 1 50         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
846 1 50         if (to_output) pos += slen; else tmp_len = slen;
847 2           break;
848             }
849             char fmtbuf[64];
850 5           int fmtlen = snprintf(fmtbuf, sizeof(fmtbuf), "%s%s",
851 5 50         xf->param_str[0] == '%' ? "" : "%", xf->param_str);
852 5 50         if (fmtlen >= (int)sizeof(fmtbuf)) {
853 0 0         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
854 0 0         if (to_output) pos += slen; else tmp_len = slen;
855 0           break;
856             }
857 5           int pct_count = 0; int has_star = 0;
858 26 100         for (int fi = 0; fi < fmtlen && fmtbuf[fi]; fi++) {
    50          
859 21 100         if (fmtbuf[fi] == '%') { if (fi + 1 < fmtlen && fmtbuf[fi+1] == '%') fi++; else pct_count++; }
    50          
    50          
860 15 50         else if (fmtbuf[fi] == '*') has_star = 1;
861             }
862 5 100         if (pct_count != 1 || has_star) {
    50          
863 1 50         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
864 1 50         if (to_output) pos += slen; else tmp_len = slen;
865 1           break;
866             }
867             int w;
868 4 100         if (last == 'd' || last == 'i' || last == 'x' || last == 'X' || last == 'o' || last == 'u') {
    50          
    100          
    50          
    50          
    50          
869 2           long lv = 0; int neg = 0;
870 7 100         for (STRLEN i = 0; i < slen; i++) {
871 5 50         if (src[i] == '-') neg = 1;
872 5 50         else if (src[i] >= '0' && src[i] <= '9') lv = lv * 10 + (src[i] - '0');
    50          
873             }
874 2 50         if (neg) lv = -lv;
875 2 50         w = snprintf(OUT_PTR, 256, fmtbuf, lv);
876 2 100         } else if (last == 'f' || last == 'e' || last == 'g') {
    50          
    50          
877 1 50         char tb[64]; int tl = slen < 63 ? (int)slen : 63;
878 1           memcpy(tb, src, tl); tb[tl] = 0;
879 1 50         w = snprintf(OUT_PTR, 256, fmtbuf, atof(tb));
880             } else {
881 1 50         char tb[256]; int tl = slen < 255 ? (int)slen : 255;
882 1           memcpy(tb, src, tl); tb[tl] = 0;
883 1 50         w = snprintf(OUT_PTR, 256, fmtbuf, tb);
884             }
885 4 50         if (w > 255) w = 255;
886 4 50         if (to_output) pos += w; else tmp_len = w;
887             }
888 4           break;
889             }
890 5           case XF_REPLACE: {
891 5 100         if (!xf->param_str) {
892 1 50         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
893 1 50         if (to_output) pos += slen; else tmp_len = slen;
894 1           break;
895             }
896 4           const char *needle = xf->param_str;
897 4           STRLEN nlen = xf->param_str_len;
898 4 50         const char *repl = xf->param_str2 ? xf->param_str2 : "";
899 4 50         STRLEN rlen = xf->param_str2 ? xf->param_str2_len : 0;
900 4 50         OUT_ENSURE(slen * (rlen + 1));
    50          
    0          
    0          
    0          
    0          
    0          
901 4 50         char *out = OUT_PTR;
902 4           STRLEN opos = 0;
903 4           STRLEN i = 0;
904 23 100         while (i < slen) {
905 19 100         if (i + nlen <= slen && memcmp(src + i, needle, nlen) == 0) {
    100          
906 7           memcpy(out + opos, repl, rlen); opos += rlen;
907 7           i += nlen;
908             } else {
909 12           out[opos++] = src[i++];
910             }
911             }
912 4 50         if (to_output) pos += opos; else tmp_len = opos;
913 4           break;
914             }
915 4           case XF_SUBSTR: {
916 4           int start = xf->param_int;
917 4           int maxlen = xf->param_int2;
918 4 100         if (start < 0 || start >= (int)slen) {
    100          
919 2 50         if (!to_output) tmp_len = 0;
920 2           break;
921             }
922 2           const char *s = src + start;
923 2           STRLEN l = slen - start;
924 2 100         if (maxlen >= 0 && (int)l > maxlen) l = maxlen;
    50          
925 2 50         OUT_ENSURE(l); memcpy(OUT_PTR, s, l);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
926 2 50         if (to_output) pos += l; else tmp_len = l;
927 2           break;
928             }
929 5           case XF_PLURAL: {
930 5           long v = 0;
931 5 50         STRLEN pi = (slen > 0 && src[0] == '-') ? 1 : 0;
    100          
932 10 100         for (STRLEN i = pi; i < slen; i++)
933 5 50         if (src[i] >= '0' && src[i] <= '9') v = v * 10 + (src[i] - '0');
    50          
934 5 100         if (pi) v = -v; /* preserve sign: -1 is plural, and itoa keeps the '-' */
935             const char *form; STRLEN flen;
936 5 100         if (v == 1) {
937 2 50         form = xf->param_str ? xf->param_str : ""; flen = xf->param_str_len;
938             } else {
939 3 50         form = xf->param_str2 ? xf->param_str2 : "s"; flen = xf->param_str2 ? xf->param_str2_len : 1;
    50          
940             }
941 5 50         OUT_ENSURE(20 + 1 + flen);
    50          
    0          
    0          
    0          
    0          
    0          
942 5 50         int nw = itoa_fast(OUT_PTR, v);
943 5 50         OUT_PTR[nw] = ' ';
944 5 50         memcpy(OUT_PTR + nw + 1, form, flen);
945 5           int total = nw + 1 + (int)flen;
946 5 50         if (to_output) pos += total; else tmp_len = total;
947 5           break;
948             }
949 4           case XF_IF: {
950 4 100         int truthy = (slen > 0 && !(slen == 1 && src[0] == '0'));
    50          
    100          
951 4 100         if (truthy && xf->param_str) {
    50          
952 1 50         OUT_ENSURE(xf->param_str_len);
    50          
    0          
    0          
    0          
    0          
    0          
953 1 50         memcpy(OUT_PTR, xf->param_str, xf->param_str_len);
954 1 50         if (to_output) pos += xf->param_str_len; else tmp_len = xf->param_str_len;
955             } else {
956 3 50         if (!to_output) tmp_len = 0;
957             }
958 4           break;
959             }
960 2           case XF_UNLESS: {
961 2 100         int truthy = (slen > 0 && !(slen == 1 && src[0] == '0'));
    50          
    50          
962 2 100         if (!truthy && xf->param_str) {
    50          
963 1 50         OUT_ENSURE(xf->param_str_len);
    50          
    0          
    0          
    0          
    0          
    0          
964 1 50         memcpy(OUT_PTR, xf->param_str, xf->param_str_len);
965 1 50         if (to_output) pos += xf->param_str_len; else tmp_len = xf->param_str_len;
966             } else {
967 1 50         if (!to_output) tmp_len = 0;
968             }
969 2           break;
970             }
971 5           case XF_MAP: {
972 5           const char *val = src; STRLEN vlen = slen;
973 12 100         for (int mi = 0; mi < xf->map_count; mi++) {
974 10 50         if ((xf->map_key_lens[mi] == slen && memcmp(xf->map_keys[mi], src, slen) == 0) ||
    100          
975 7 50         (xf->map_key_lens[mi] == 1 && xf->map_keys[mi][0] == '*')) {
    100          
976 5           val = xf->map_vals[mi]; vlen = xf->map_val_lens[mi];
977 5 50         if (xf->map_key_lens[mi] != 1 || xf->map_keys[mi][0] != '*') break;
    100          
978             }
979             }
980 5 50         OUT_ENSURE(vlen); memcpy(OUT_PTR, val, vlen);
    50          
    0          
    0          
    0          
    0          
    0          
    50          
981 5 50         if (to_output) pos += vlen; else tmp_len = vlen;
982 5           break;
983             }
984 4           case XF_WRAP: {
985 6 100         if (slen > 0 && xf->param_str) {
    50          
986 2 100         STRLEN plen2 = xf->param_str_len + slen + (xf->param_str2 ? xf->param_str2_len : 0);
987 2 50         OUT_ENSURE(plen2);
    50          
    0          
    0          
    0          
    0          
    0          
988 2 50         memcpy(OUT_PTR, xf->param_str, xf->param_str_len);
989 2           int wpos = (int)xf->param_str_len;
990 2 50         memcpy(OUT_PTR + wpos, src, slen); wpos += slen;
991 2 100         if (xf->param_str2) { memcpy(OUT_PTR + wpos, xf->param_str2, xf->param_str2_len); wpos += xf->param_str2_len; }
    50          
992 2 50         if (to_output) pos += wpos; else tmp_len = wpos;
993             } else {
994 2 50         if (!to_output) tmp_len = 0;
995             }
996 4           break;
997             }
998 4           case XF_NUMBER_SI: {
999 4           double v = 0;
1000 4 50         { char tb[64]; int tl = slen < 63 ? (int)slen : 63; memcpy(tb, src, tl); tb[tl] = 0; v = atof(tb); }
1001 4 50         OUT_ENSURE(32);
    50          
    0          
    0          
    0          
    0          
1002             int w;
1003 4 50         if (v >= 1e15 || v <= -1e15) w = snprintf(OUT_PTR, 32, "%.1fP", v / 1e15);
    50          
    0          
1004 4 50         else if (v >= 1e12 || v <= -1e12) w = snprintf(OUT_PTR, 32, "%.1fT", v / 1e12);
    50          
    0          
1005 4 50         else if (v >= 1e9 || v <= -1e9) w = snprintf(OUT_PTR, 32, "%.1fG", v / 1e9);
    50          
    0          
1006 4 100         else if (v >= 1e6 || v <= -1e6) w = snprintf(OUT_PTR, 32, "%.1fM", v / 1e6);
    50          
    50          
1007 3 100         else if (v >= 1e3 || v <= -1e3) w = snprintf(OUT_PTR, 32, "%.1fK", v / 1e3);
    100          
    50          
1008 1 50         else w = snprintf(OUT_PTR, 32, "%.0f", v);
1009 4 50         if (w > 31) w = 31;
1010 4 50         if (to_output) pos += w; else tmp_len = w;
1011 4           break;
1012             }
1013 4           case XF_BYTES_SI: {
1014 4           double v = 0;
1015 4 50         { char tb[64]; int tl = slen < 63 ? (int)slen : 63; memcpy(tb, src, tl); tb[tl] = 0; v = atof(tb); }
1016 4 50         OUT_ENSURE(32);
    50          
    0          
    0          
    0          
    0          
1017             int w;
1018 4 100         if (v >= 1099511627776.0) w = snprintf(OUT_PTR, 32, "%.1f TB", v / 1099511627776.0);
    50          
1019 3 100         else if (v >= 1073741824.0) w = snprintf(OUT_PTR, 32, "%.1f GB", v / 1073741824.0);
    50          
1020 2 50         else if (v >= 1048576.0) w = snprintf(OUT_PTR, 32, "%.1f MB", v / 1048576.0);
    0          
1021 2 100         else if (v >= 1024.0) w = snprintf(OUT_PTR, 32, "%.1f KB", v / 1024.0);
    50          
1022 1 50         else w = snprintf(OUT_PTR, 32, "%.0f B", v);
1023 4 50         if (w > 31) w = 31;
1024 4 50         if (to_output) pos += w; else tmp_len = w;
1025 4           break;
1026             }
1027 5           case XF_ELAPSED: {
1028 5           long v = 0;
1029 22 100         for (STRLEN i = 0; i < slen; i++)
1030 17 50         if (src[i] >= '0' && src[i] <= '9') v = v * 10 + (src[i] - '0');
    50          
1031 5 50         OUT_ENSURE(64);
    50          
    0          
    0          
    0          
    0          
1032 5           int w = 0;
1033 5 100         if (v >= 86400 && w < 63) { int n = snprintf(OUT_PTR + w, 64 - w, "%ldd ", v / 86400); if (n > 0) w += n; v %= 86400; }
    50          
    50          
    50          
1034 5 100         if (v >= 3600 && w < 63) { int n = snprintf(OUT_PTR + w, 64 - w, "%ldh ", v / 3600); if (n > 0) w += n; v %= 3600; }
    50          
    50          
    50          
1035 5 100         if (v >= 60 && w < 63) { int n = snprintf(OUT_PTR + w, 64 - w, "%ldm ", v / 60); if (n > 0) w += n; v %= 60; }
    50          
    50          
    50          
1036 5 50         if (w < 63) { int n = snprintf(OUT_PTR + w, 64 - w, "%lds", v); if (n > 0) w += n; }
    50          
    50          
1037 5 50         if (w > 63) w = 63;
1038 5 50         if (to_output) pos += w; else tmp_len = w;
1039 5           break;
1040             }
1041 3           case XF_AGO: {
1042 3           time_t now = time(NULL);
1043 3           time_t epoch = 0;
1044 33 100         for (STRLEN i = 0; i < slen; i++)
1045 30 50         if (src[i] >= '0' && src[i] <= '9') epoch = epoch * 10 + (src[i] - '0');
    50          
1046 3           long diff = (long)(now - epoch);
1047 3 50         OUT_ENSURE(32);
    50          
    0          
    0          
    0          
    0          
1048             int w;
1049 3 100         if (diff < 0) w = snprintf(OUT_PTR, 32, "in the future");
    50          
1050 2 50         else if (diff < 60) w = snprintf(OUT_PTR, 32, "%lds ago", diff);
    0          
1051 2 100         else if (diff < 3600) w = snprintf(OUT_PTR, 32, "%ldm ago", diff / 60);
    50          
1052 1 50         else if (diff < 86400) w = snprintf(OUT_PTR, 32, "%ldh ago", diff / 3600);
    50          
1053 0 0         else if (diff < 2592000) w = snprintf(OUT_PTR, 32, "%ldd ago", diff / 86400);
    0          
1054 0 0         else if (diff < 31536000) w = snprintf(OUT_PTR, 32, "%ldmo ago", diff / 2592000);
    0          
1055 0 0         else w = snprintf(OUT_PTR, 32, "%ldy ago", diff / 31536000);
1056 3 50         if (w > 31) w = 31;
1057 3 50         if (to_output) pos += w; else tmp_len = w;
1058 3           break;
1059             }
1060 6           case XF_MASK: {
1061 6           int keep = xf->param_int;
1062 6 100         OUT_ENSURE(slen);
    50          
    0          
    50          
    0          
    50          
    50          
1063 6           int mask_len = (int)slen - keep;
1064 6 100         if (mask_len > 0) memset(OUT_PTR, '*', mask_len);
    100          
1065 6 100         if (keep > 0) {
1066 5           int start = mask_len > 0 ? mask_len : 0;
1067 5           int copy = keep > (int)slen ? (int)slen : keep;
1068 5 100         memcpy(OUT_PTR + start, src + slen - copy, copy);
1069             }
1070 6           int total = (int)slen;
1071 6 100         if (to_output) pos += total; else tmp_len = total;
1072 6           break;
1073             }
1074 3           case XF_LENGTH: {
1075 3 50         OUT_ENSURE(20);
    50          
    0          
    0          
    0          
    0          
1076 3 50         int w = itoa_fast(OUT_PTR, (long)slen);
1077 3 50         if (to_output) pos += w; else tmp_len = w;
1078 3           break;
1079             }
1080 348           case XF_COALESCE: /* handled in render_field, fallthrough to raw */
1081             case XF_ROWNUM: /* should not appear in chain; rownum is handled by render_field */
1082             case XF_DEFAULT:
1083             case XF_RAW: {
1084 348 50         OUT_ENSURE(slen); memcpy(OUT_PTR, src, slen);
    100          
    50          
    0          
    0          
    0          
    0          
    50          
1085 348 50         if (to_output) pos += slen; else tmp_len = slen;
1086 348           break;
1087             }
1088             }
1089              
1090             #undef OUT_ENSURE
1091             #undef OUT_PTR
1092 1495           *bufp = buf; *posp = pos; *allocp = alloc;
1093 1495           *tmpp = tmp; *tmp_lenp = tmp_len; *tmp_allocp = tmp_alloc;
1094 1495           }
1095              
1096 11571           static void render_field(pTHX_ tpl_op *op, SV *row_sv, enum row_mode mode,
1097             char **bufp, STRLEN *posp, STRLEN *allocp,
1098             SSize_t row_idx) {
1099 11571           const char *src = NULL; STRLEN slen = 0;
1100 11571           int use_default = 0;
1101 11571           char rownum_buf[20]; int rownum_len = 0;
1102              
1103 11571 100         if (op->is_rownum) {
1104 1010           rownum_len = itoa_fast(rownum_buf, (long)row_idx);
1105 1010           src = rownum_buf; slen = rownum_len;
1106 1010           use_default = 1; /* skip fetch_field path, go straight to chain */
1107             }
1108              
1109 11571           SV *sv = NULL;
1110 11571 100         if (!op->is_rownum) {
1111 10561           sv = fetch_field(aTHX_ row_sv, op, mode);
1112              
1113             /* handle default transform */
1114 10561 50         if (!sv || !SvOK(sv)) {
    100          
1115 46 100         for (int i = 0; i < op->chain_len; i++) {
1116 27 100         if (op->chain[i].type == XF_DEFAULT && op->chain[i].param_str) {
    50          
1117 4           src = op->chain[i].param_str;
1118 4           slen = op->chain[i].param_str_len;
1119 4           use_default = 1;
1120 4           break;
1121             }
1122 23 100         if (op->chain[i].type == XF_COALESCE) {
1123 1           use_default = 1; /* coalesce will handle it */
1124 1           break;
1125             }
1126 22 100         if (op->chain[i].type == XF_BOOL || op->chain[i].type == XF_IF ||
    100          
1127 20 50         op->chain[i].type == XF_UNLESS || op->chain[i].type == XF_MAP ||
    50          
1128 20 100         op->chain[i].type == XF_WRAP) {
1129 3           src = ""; slen = 0;
1130 3           use_default = 1;
1131 3           break;
1132             }
1133             }
1134 11570 100         if (!use_default) return;
1135             }
1136             }
1137              
1138             /* handle coalesce: try fallback fields, then literal default */
1139 11552 100         if (!op->is_rownum && op->chain_len > 0 && op->chain[0].type == XF_COALESCE) {
    50          
    100          
1140 6           int primary_ok = 0;
1141 6 50         if (sv && SvOK(sv)) {
    100          
1142             STRLEN plen;
1143 5           const char *pstr = SvPV_nomg(sv, plen);
1144 5 100         if (plen > 0) { primary_ok = 1; src = pstr; slen = plen; use_default = 0; }
1145             }
1146 6 100         if (!primary_ok && !op->chain[0].param_str) return;
    50          
1147 6 100         if (!primary_ok && op->chain[0].param_str) {
    50          
1148 5           const char *params = op->chain[0].param_str;
1149 5           STRLEN params_len = op->chain[0].param_str_len;
1150 5           const char *p = params, *pe = params + params_len;
1151 5           const char *last_param = NULL; STRLEN last_param_len = 0;
1152             /* find last param (the literal default) */
1153 5           const char *tp = params;
1154 10 50         while (tp < pe) {
1155 10           const char *next = memchr(tp, ':', pe - tp);
1156 10 100         if (!next) { last_param = tp; last_param_len = pe - tp; break; }
1157 5           last_param = tp; last_param_len = next - tp;
1158 5           tp = next + 1;
1159             }
1160             /* try each fallback field (all params except the last) */
1161 5           int found = 0;
1162 5           p = params;
1163 7 50         while (p < pe) {
1164 7           const char *next = memchr(p, ':', pe - p);
1165 7 100         STRLEN seg_len = next ? (STRLEN)(next - p) : (STRLEN)(pe - p);
1166 7 100         if (!next && p == last_param) break; /* this is the literal default */
    50          
1167             /* check if this is not the last param */
1168 5 50         if (p != last_param || next) {
    0          
1169             /* try to fetch this field from the row */
1170 5           tpl_op tmp_op = {0};
1171 5 100         if (mode == ROW_HASH) {
1172 4           tmp_op.key = (char *)p; tmp_op.key_len = seg_len;
1173             } else {
1174 1           int is_neg = 0, si = 0;
1175 1 50         if (seg_len > 0 && p[0] == '-') { is_neg = 1; si = 1; }
    50          
1176 1           int is_num = 1;
1177 2 100         for (STRLEN fi = si; fi < seg_len; fi++)
1178 1 50         if (p[fi] < '0' || p[fi] > '9') { is_num = 0; break; }
    50          
1179 1 50         if (is_num && seg_len > (STRLEN)si) {
    50          
1180 1           tmp_op.col = 0;
1181 2 100         for (STRLEN fi = si; fi < seg_len; fi++) tmp_op.col = tmp_op.col * 10 + (p[fi] - '0');
1182 1 50         if (is_neg) tmp_op.col = -tmp_op.col;
1183             } else {
1184 3 0         if (!next) break; /* non-numeric in array mode = treat as literal default */
1185 0           p = next + 1; continue;
1186             }
1187             }
1188 5           SV *fallback = fetch_field(aTHX_ row_sv, &tmp_op, mode);
1189 5 50         if (fallback && SvOK(fallback)) {
    100          
1190             STRLEN flen;
1191 4           const char *fstr = SvPV_nomg(fallback, flen);
1192 4 100         if (flen > 0) { sv = fallback; src = fstr; slen = flen; use_default = 0; found = 1; break; }
1193             }
1194             }
1195 2 50         if (!next) break;
1196 2           p = next + 1;
1197             }
1198 5 100         if (!found) {
1199 2           src = last_param; slen = last_param_len;
1200 2           use_default = 1;
1201             }
1202             }
1203             }
1204              
1205             /* handle count type: count elements of array/hash ref */
1206 11552 100         if (!use_default && !op->is_rownum && op->chain_len > 0 && op->chain[0].type == XF_COUNT) {
    50          
    50          
    100          
1207 3           int cnt = 0;
1208 3 50         if (sv && SvROK(sv)) {
    100          
1209 2           SV *inner = SvRV(sv);
1210 2 100         if (SvTYPE(inner) == SVt_PVAV) cnt = (int)av_count((AV *)inner);
1211 1 50         else if (SvTYPE(inner) == SVt_PVHV) cnt = (int)HvUSEDKEYS((HV *)inner);
    50          
1212             }
1213             char cbuf[12];
1214 3           int clen = itoa_fast(cbuf, cnt);
1215 3 50         if (op->chain_len == 1) {
1216 3           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
1217 3 50         BUF_ENSURE(clen); memcpy(buf + pos, cbuf, clen); pos += clen;
    0          
1218 3           *bufp = buf; *posp = pos; *allocp = alloc;
1219 3           return;
1220             }
1221 0           src = cbuf; slen = clen;
1222             }
1223              
1224             /* get initial string value */
1225 11549 100         if (!use_default && !op->is_rownum && !(op->chain_len > 0 &&
    50          
    50          
1226 10530 50         (op->chain[0].type == XF_COUNT || op->chain[0].type == XF_COALESCE))) {
    100          
1227             /* for int/float types as first transform, use numeric conversion */
1228 10526 50         if (op->chain_len > 0 && (op->chain[0].type == XF_INT || op->chain[0].type == XF_INT_COMMA)) {
    100          
    100          
1229             char ibuf[28]; /* itoa_comma needs up to 26 ("-9,223,372,036,854,775,808"); itoa_fast 20 */
1230 10058 100         int ilen = (op->chain[0].type == XF_INT) ? itoa_fast(ibuf, SvIV_nomg(sv)) : itoa_comma(ibuf, SvIV_nomg(sv));
1231 10058 50         if (op->chain_len == 1) {
1232 10058           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
1233 10058 50         BUF_ENSURE(ilen); memcpy(buf + pos, ibuf, ilen); pos += ilen;
    0          
1234 10058           *bufp = buf; *posp = pos; *allocp = alloc;
1235 10058           return;
1236             }
1237 0           src = ibuf; slen = ilen;
1238 468 50         } else if (op->chain_len > 0 && op->chain[0].type == XF_FLOAT) {
    100          
1239             char fbuf[64];
1240 3           int flen = snprintf(fbuf, 64, "%.*f", op->chain[0].param_int, SvNV_nomg(sv));
1241 3 50         if (flen > 63) flen = 63;
1242 3 50         if (op->chain_len == 1) {
1243 3           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
1244 3 50         BUF_ENSURE(flen); memcpy(buf + pos, fbuf, flen); pos += flen;
    0          
1245 3           *bufp = buf; *posp = pos; *allocp = alloc;
1246 3           return;
1247             }
1248 0           src = fbuf; slen = flen;
1249             } else {
1250 465           src = SvPV_nomg(sv, slen);
1251             }
1252             }
1253              
1254             /* single transform fast path (most common) */
1255 1488           int start = 0;
1256 1488 100         if (!use_default && !op->is_rownum && op->chain_len > 0 &&
    50          
    50          
1257 469 50         (op->chain[0].type == XF_INT || op->chain[0].type == XF_INT_COMMA ||
    50          
1258 469 50         op->chain[0].type == XF_FLOAT || op->chain[0].type == XF_COUNT ||
    50          
1259 469 100         op->chain[0].type == XF_COALESCE))
1260 4           start = 1;
1261              
1262 1488 100         if (op->chain_len - start == 0) {
1263 4           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
1264 4 50         BUF_ENSURE(slen); memcpy(buf + pos, src, slen); pos += slen;
    0          
1265 4           *bufp = buf; *posp = pos; *allocp = alloc;
1266 4           return;
1267             }
1268              
1269 1484 100         if (op->chain_len - start == 1) {
1270 1475           tpl_xform *xf = &op->chain[start];
1271 1475 100         if (xf->type == XF_DEFAULT) {
1272 3           char *buf = *bufp; STRLEN pos = *posp; STRLEN alloc = *allocp;
1273 3 50         BUF_ENSURE(slen); memcpy(buf + pos, src, slen); pos += slen;
    0          
1274 3           *bufp = buf; *posp = pos; *allocp = alloc;
1275             } else {
1276 1472           char *tmp = NULL; STRLEN tmp_len = 0, tmp_alloc = 0;
1277 1472           apply_xform(xf, src, slen, bufp, posp, allocp, &tmp, &tmp_len, &tmp_alloc, 1);
1278 1472 50         if (tmp) free(tmp);
1279             }
1280 1475           return;
1281             }
1282              
1283             /* chain: apply transforms with ping-pong buffers */
1284 9           char *tmp_a = NULL, *tmp_b = NULL;
1285 9           STRLEN tmp_a_len = 0, tmp_a_alloc = 0, tmp_b_len = 0, tmp_b_alloc = 0;
1286 9           const char *cur = src; STRLEN cur_len = slen;
1287 9           int use_a = 1;
1288              
1289 34 100         for (int i = start; i < op->chain_len; i++) {
1290 25 100         if (op->chain[i].type == XF_DEFAULT) continue;
1291 23           int is_last = 1;
1292 23 100         for (int k = i + 1; k < op->chain_len; k++)
1293 14 50         if (op->chain[k].type != XF_DEFAULT) { is_last = 0; break; }
1294              
1295 23 100         if (is_last) {
1296 9           char *dummy = NULL; STRLEN dummy_len = 0, dummy_alloc = 0;
1297 9           apply_xform(&op->chain[i], cur, cur_len, bufp, posp, allocp, &dummy, &dummy_len, &dummy_alloc, 1);
1298 9 50         if (dummy) free(dummy);
1299             } else {
1300 14 100         if (use_a) {
1301 10           tmp_a_len = 0;
1302 10           apply_xform(&op->chain[i], cur, cur_len, bufp, posp, allocp, &tmp_a, &tmp_a_len, &tmp_a_alloc, 0);
1303 10           cur = tmp_a; cur_len = tmp_a_len;
1304 10           use_a = 0;
1305             } else {
1306 4           tmp_b_len = 0;
1307 4           apply_xform(&op->chain[i], cur, cur_len, bufp, posp, allocp, &tmp_b, &tmp_b_len, &tmp_b_alloc, 0);
1308 4           cur = tmp_b; cur_len = tmp_b_len;
1309 4           use_a = 1;
1310             }
1311             }
1312             }
1313 9 100         if (tmp_a) free(tmp_a);
1314 9 100         if (tmp_b) free(tmp_b);
1315             }
1316              
1317             /* check if a field value in a row is truthy */
1318 31           static int is_field_truthy(pTHX_ SV *row_sv, tpl_compiled *t, int is_skip_if) {
1319             int col; char *key; STRLEN key_len;
1320 31 100         if (is_skip_if) { col = t->skip_if_col; key = t->skip_if_key; key_len = t->skip_if_key_len; }
1321 13           else { col = t->skip_unless_col; key = t->skip_unless_key; key_len = t->skip_unless_key_len; }
1322              
1323 31           SV *field = NULL;
1324 31 100         if (key) {
1325 6 50         if (SvROK(row_sv) && SvTYPE(SvRV(row_sv)) == SVt_PVHV) {
    50          
1326 6           SV **sv = hv_fetch((HV *)SvRV(row_sv), key, key_len, 0);
1327 6 50         if (sv) field = *sv;
1328             }
1329             } else {
1330 25 50         if (SvROK(row_sv) && SvTYPE(SvRV(row_sv)) == SVt_PVAV) {
    50          
1331 25           AV *av = (AV *)SvRV(row_sv);
1332 25           SV **ary = AvARRAY(av);
1333 25 50         SSize_t top = av_top_index(av);
1334 25 100         if (col < 0) col = (int)(top + 1) + col;
1335 25 50         if (col >= 0 && col <= (int)top) field = ary[col];
    50          
1336             }
1337             }
1338 31 50         if (!field || !SvOK(field)) return 0;
    100          
1339             STRLEN flen;
1340 29           const char *fstr = SvPV(field, flen);
1341 29 100         if (flen == 0) return 0;
1342 18 100         if (flen == 1 && fstr[0] == '0') return 0;
    100          
1343 13           return 1;
1344             }
1345              
1346 11543           static int should_skip_row(pTHX_ SV *row_sv, tpl_compiled *t) {
1347 11543 100         if (t->has_skip_if && is_field_truthy(aTHX_ row_sv, t, 1)) return 1;
    100          
1348 11536 100         if (t->has_skip_unless && !is_field_truthy(aTHX_ row_sv, t, 0)) return 1;
    100          
1349 11529           return 0;
1350             }
1351              
1352 185           static SV *tpl_render(pTHX_ tpl_compiled *t, AV *rows) {
1353 185           SSize_t nrows = av_count(rows);
1354 185           t->last_row_count = nrows;
1355             STRLEN alloc, pos;
1356             char *buf;
1357 185 100         RBUF_INIT(t, t->header_len + t->footer_len + nrows * 300 + 1);
    50          
    50          
1358              
1359 185 50         BUF_WRITE(t->header, t->header_len);
    0          
1360 185           int first = 1;
1361 11406 100         for (SSize_t i = 0; i < nrows; i++) {
1362 11221           SV **rowref = av_fetch(rows, i, 0);
1363 11221 50         if (!rowref) continue;
1364 11221 100         if (should_skip_row(aTHX_ *rowref, t)) continue;
1365 11211 100         if (!first && t->sep_len) BUF_WRITE(t->sep, t->sep_len);
    100          
    50          
    0          
1366 11211           first = 0;
1367 22532 100         for (int j = 0; j < t->nops; j++) {
1368 11321           tpl_op *op = &t->ops[j];
1369 11321 100         if (op->static_data)
1370 87 50         BUF_WRITE(op->static_data, op->static_len);
    0          
1371             else
1372 11234           render_field(aTHX_ op, *rowref, t->mode, &buf, &pos, &alloc, i);
1373             }
1374             }
1375 185 50         BUF_WRITE(t->footer, t->footer_len);
    0          
1376 185           RBUF_FINISH(t);
1377 185           return newSVpvn_utf8(buf, pos, 1);
1378             }
1379              
1380             /* ---- sorted render ---- */
1381              
1382             typedef struct { SV *sv; const char **keys; STRLEN *key_lens; } sort_entry;
1383              
1384             static int sort_nsort;
1385             static int sort_numeric;
1386              
1387 37           static int sort_cmp_multi(const sort_entry *ea, const sort_entry *eb) {
1388 39 50         for (int k = 0; k < sort_nsort; k++) {
1389 39 100         if (sort_numeric) {
1390             char ba[64], bb[64];
1391 5 50         int la = ea->key_lens[k] < 63 ? (int)ea->key_lens[k] : 63;
1392 5 50         int lb = eb->key_lens[k] < 63 ? (int)eb->key_lens[k] : 63;
1393 5           memcpy(ba, ea->keys[k], la); ba[la] = 0;
1394 5           memcpy(bb, eb->keys[k], lb); bb[lb] = 0;
1395 5           double da = atof(ba), db = atof(bb);
1396 8 100         if (da < db) return -1;
1397 3 50         if (da > db) return 1;
1398             } else {
1399 34           STRLEN minlen = ea->key_lens[k] < eb->key_lens[k] ? ea->key_lens[k] : eb->key_lens[k];
1400 34           int r = memcmp(ea->keys[k], eb->keys[k], minlen);
1401 34 100         if (r) return r;
1402 2 50         if (ea->key_lens[k] != eb->key_lens[k])
1403 0 0         return ea->key_lens[k] < eb->key_lens[k] ? -1 : 1;
1404             }
1405             }
1406 0           return 0;
1407             }
1408              
1409 24           static int sort_cmp_asc(const void *a, const void *b) {
1410 24           return sort_cmp_multi((const sort_entry *)a, (const sort_entry *)b);
1411             }
1412              
1413 13           static int sort_cmp_desc(const void *a, const void *b) {
1414 13           return sort_cmp_multi((const sort_entry *)b, (const sort_entry *)a);
1415             }
1416              
1417             /* libc free() as a SAVEDESTRUCTOR_X callback. The sort scratch is malloc'd,
1418             not Newx'd, so SAVEFREEPV/Safefree would mismatch on DEBUGGING perls. */
1419 39           static void ts_free(pTHX_ void *p) { free(p); }
1420              
1421 14           static SV *tpl_render_sorted(pTHX_ tpl_compiled *t, AV *rows,
1422             int *sort_cols, const char **sort_keys, STRLEN *sort_key_lens,
1423             int nsort, int descending, int numeric) {
1424 14           SSize_t nrows = av_count(rows);
1425 14           t->last_row_count = nrows;
1426              
1427 14 100         sort_entry *entries = nrows > 0 ? (sort_entry *)malloc(nrows * sizeof(sort_entry)) : NULL;
1428 14 100         if (nrows > 0 && !entries) croak("malloc");
    50          
1429 14 100         const char **all_keys = nrows > 0 ? (const char **)calloc(nrows * nsort, sizeof(char *)) : NULL;
1430 14 100         STRLEN *all_lens = nrows > 0 ? (STRLEN *)calloc(nrows * nsort, sizeof(STRLEN)) : NULL;
1431             /* Free the sort scratch on scope exit, so a croak mid-render (e.g. an OOM
1432             realloc in BUF_ENSURE) frees it on the unwind instead of leaking it;
1433             LEAVE frees it on the normal path. */
1434 14           ENTER;
1435 14 100         if (entries) SAVEDESTRUCTOR_X(ts_free, entries);
1436 14 100         if (all_keys) SAVEDESTRUCTOR_X(ts_free, all_keys);
1437 14 100         if (all_lens) SAVEDESTRUCTOR_X(ts_free, all_lens);
1438             /* checked after the SAVEDESTRUCTORs so the unwind frees entries; an
1439             unchecked NULL here would segfault at entries[i].keys below */
1440 14 100         if (nrows > 0 && (!all_keys || !all_lens)) croak("calloc");
    50          
    50          
1441 53 100         for (SSize_t i = 0; i < nrows; i++) {
1442 39           SV **rowref = av_fetch(rows, i, 0);
1443 39 50         entries[i].sv = rowref ? *rowref : &PL_sv_undef;
1444 39           entries[i].keys = all_keys + i * nsort;
1445 39           entries[i].key_lens = all_lens + i * nsort;
1446 84 100         for (int k = 0; k < nsort; k++) {
1447 45           entries[i].keys[k] = ""; entries[i].key_lens[k] = 0;
1448 45 50         if (rowref && SvROK(*rowref)) {
    50          
1449 45           SV *field = NULL;
1450 45 100         if (sort_keys) {
1451 21 50         if (SvTYPE(SvRV(*rowref)) == SVt_PVHV) {
1452 21           SV **sv = hv_fetch((HV *)SvRV(*rowref), sort_keys[k], sort_key_lens[k], 0);
1453 21 50         if (sv) field = *sv;
1454             }
1455             } else {
1456 24 50         if (SvTYPE(SvRV(*rowref)) == SVt_PVAV) {
1457 24           SV **sv = av_fetch((AV *)SvRV(*rowref), sort_cols[k], 0);
1458 24 50         if (sv) field = *sv;
1459             }
1460             }
1461 45 50         if (field) entries[i].keys[k] = SvPV(field, entries[i].key_lens[k]);
1462             }
1463             }
1464             }
1465              
1466 14           sort_nsort = nsort;
1467 14           sort_numeric = numeric;
1468 14 100         int (*cmp)(const void *, const void *) = descending ? sort_cmp_desc : sort_cmp_asc;
1469 14           qsort(entries, nrows, sizeof(sort_entry), cmp);
1470              
1471             STRLEN alloc, pos;
1472             char *buf;
1473 14 100         RBUF_INIT(t, t->header_len + t->footer_len + nrows * 300 + 1);
    50          
    50          
1474              
1475 14 50         BUF_WRITE(t->header, t->header_len);
    0          
1476 14           int first = 1;
1477 53 100         for (SSize_t i = 0; i < nrows; i++) {
1478 39           SV *row_sv = entries[i].sv;
1479 39 100         if (should_skip_row(aTHX_ row_sv, t)) continue;
1480 38 100         if (!first && t->sep_len) BUF_WRITE(t->sep, t->sep_len);
    100          
    50          
    0          
1481 38           first = 0;
1482 106 100         for (int j = 0; j < t->nops; j++) {
1483 68           tpl_op *op = &t->ops[j];
1484 68 100         if (op->static_data) BUF_WRITE(op->static_data, op->static_len);
    50          
    0          
1485 53           else render_field(aTHX_ op, row_sv, t->mode, &buf, &pos, &alloc, i);
1486             }
1487             }
1488 14 50         BUF_WRITE(t->footer, t->footer_len);
    0          
1489 14           RBUF_FINISH(t);
1490 14           SV *result = newSVpvn_utf8(buf, pos, 1);
1491 14           LEAVE; /* runs the SAVEDESTRUCTOR_X frees for entries/all_keys/all_lens */
1492 14           return result;
1493             }
1494              
1495 5           static SV *tpl_render_one(pTHX_ tpl_compiled *t, SV *row_sv) {
1496 5 100         if (should_skip_row(aTHX_ row_sv, t))
1497 1           return newSVpvn_utf8("", 0, 1);
1498             STRLEN alloc, pos;
1499             char *buf;
1500 4 50         RBUF_INIT(t, t->header_len + t->footer_len + 512);
    0          
    50          
1501 4 50         BUF_WRITE(t->header, t->header_len);
    0          
1502 13 100         for (int j = 0; j < t->nops; j++) {
1503 9           tpl_op *op = &t->ops[j];
1504 9 100         if (op->static_data) BUF_WRITE(op->static_data, op->static_len);
    50          
    0          
1505 5           else render_field(aTHX_ op, row_sv, t->mode, &buf, &pos, &alloc, 0);
1506             }
1507 4 50         BUF_WRITE(t->footer, t->footer_len);
    0          
1508 4           RBUF_FINISH(t);
1509 4           return newSVpvn_utf8(buf, pos, 1);
1510             }
1511              
1512 2           static void tpl_render_to_fh(pTHX_ tpl_compiled *t, AV *rows, PerlIO *fh) {
1513 2           SSize_t nrows = av_count(rows);
1514 2           t->last_row_count = nrows;
1515 2           STRLEN alloc = t->header_len + t->footer_len + nrows * 300 + 1;
1516 2           char *buf = (char *)malloc(alloc);
1517 2 50         if (!buf) croak("malloc");
1518 2           STRLEN pos = 0;
1519 2 50         BUF_WRITE(t->header, t->header_len);
    0          
1520 2           int first = 1;
1521 205 100         for (SSize_t i = 0; i < nrows; i++) {
1522 203           SV **rowref = av_fetch(rows, i, 0);
1523 203 50         if (!rowref) continue;
1524 203 50         if (should_skip_row(aTHX_ *rowref, t)) continue;
1525 203 100         if (!first && t->sep_len) BUF_WRITE(t->sep, t->sep_len);
    50          
    50          
    0          
1526 203           first = 0;
1527 406 100         for (int j = 0; j < t->nops; j++) {
1528 203           tpl_op *op = &t->ops[j];
1529 203 50         if (op->static_data) BUF_WRITE(op->static_data, op->static_len);
    0          
    0          
1530 203           else render_field(aTHX_ op, *rowref, t->mode, &buf, &pos, &alloc, i);
1531             }
1532 203 100         if (pos > 65536) { PerlIO_write(fh, buf, pos); pos = 0; }
1533             }
1534 2 50         BUF_WRITE(t->footer, t->footer_len);
    0          
1535 2 50         if (pos) PerlIO_write(fh, buf, pos);
1536 2           free(buf);
1537 2           }
1538              
1539 8           static SV *tpl_render_cb(pTHX_ tpl_compiled *t, SV *cb, PerlIO *fh) {
1540             STRLEN alloc, pos;
1541             char *buf;
1542 8           int use_fh = (fh != NULL);
1543              
1544 8 100         if (use_fh) {
1545 3           alloc = 65536;
1546 3           buf = (char *)malloc(alloc);
1547 3 50         if (!buf) croak("malloc");
1548             } else {
1549 5 50         RBUF_INIT(t, 4096);
    0          
    50          
1550             }
1551 8           pos = 0;
1552              
1553 8 50         BUF_WRITE(t->header, t->header_len);
    0          
1554 8           SSize_t row_idx = 0;
1555 8           int first = 1;
1556 8           t->last_row_count = 0;
1557              
1558 75           while (1) {
1559 83           dSP;
1560 83           ENTER; SAVETMPS;
1561 83 50         PUSHMARK(SP);
1562 83           PUTBACK;
1563 83           int count = call_sv(cb, G_SCALAR | G_EVAL);
1564 83           SPAGAIN;
1565 83 50         if (SvTRUE(ERRSV)) {
    100          
1566             /* The row callback died: free our render buffer (the fh path's
1567             own malloc, or the detached render_buf) before propagating, so
1568             a dying callback can't leak it. */
1569 2 50         SV *err = newSVsv(ERRSV);
1570 2 50         PUTBACK; FREETMPS; LEAVE;
1571 2           free(buf);
1572 2           croak_sv(sv_2mortal(err));
1573             }
1574 81           SV *row_sv = NULL;
1575 81 50         if (count > 0) row_sv = POPs;
1576 81 50         if (!row_sv || !SvOK(row_sv) || !SvROK(row_sv)) {
    100          
    50          
1577 6 50         PUTBACK; FREETMPS; LEAVE;
1578 6           break;
1579             }
1580 75           SvREFCNT_inc_simple_void_NN(row_sv);
1581 75 50         PUTBACK; FREETMPS; LEAVE;
1582              
1583 75 100         if (!should_skip_row(aTHX_ row_sv, t)) {
1584 73 100         if (!first && t->sep_len) BUF_WRITE(t->sep, t->sep_len);
    100          
    50          
    0          
1585 73           first = 0;
1586 282 100         for (int j = 0; j < t->nops; j++) {
1587 209           tpl_op *op = &t->ops[j];
1588 209 100         if (op->static_data) BUF_WRITE(op->static_data, op->static_len);
    100          
    50          
1589 76           else render_field(aTHX_ op, row_sv, t->mode, &buf, &pos, &alloc, row_idx);
1590             }
1591 73 100         if (use_fh && pos > 65536) { PerlIO_write(fh, buf, pos); pos = 0; }
    50          
1592             }
1593 75           SvREFCNT_dec(row_sv);
1594 75           row_idx++;
1595 75           t->last_row_count = row_idx;
1596             }
1597              
1598 6 50         BUF_WRITE(t->footer, t->footer_len);
    0          
1599              
1600 6 100         if (use_fh) {
1601 2 50         if (pos) PerlIO_write(fh, buf, pos);
1602 2           free(buf);
1603 2           return &PL_sv_undef;
1604             } else {
1605 4           RBUF_FINISH(t);
1606 4           return newSVpvn_utf8(buf, pos, 1);
1607             }
1608             }
1609              
1610             /* columns introspection */
1611 2           static AV *tpl_columns(pTHX_ tpl_compiled *t) {
1612 2           AV *cols = newAV();
1613 8 100         for (int i = 0; i < t->nops; i++) {
1614 6           tpl_op *op = &t->ops[i];
1615 6 100         if (op->chain && !op->is_rownum) {
    50          
1616 4 100         if (op->key)
1617 2           av_push(cols, newSVpvn(op->key, op->key_len));
1618             else
1619 2           av_push(cols, newSViv(op->col));
1620             }
1621             }
1622 2           return cols;
1623             }
1624              
1625              
1626             MODULE = Text::Stencil PACKAGE = Text::Stencil
1627              
1628             BOOT:
1629 4           init_html_tables();
1630              
1631             SV *
1632             new(class, ...)
1633             const char *class
1634             CODE:
1635             {
1636 206           const char *header = "", *row = "", *footer = "", *sep = "";
1637 206           STRLEN hlen = 0, rlen = 0, flen = 0, slen = 0;
1638 206           char esc = 0;
1639 206           SV *skip_if_sv = NULL, *skip_unless_sv = NULL;
1640             /* shorthand: Text::Stencil->new($row_template) */
1641 206 100         if (items == 2 && SvPOK(ST(1))) {
    50          
1642 2           row = SvPV(ST(1), rlen);
1643             } else {
1644 204 50         if (items % 2 == 0) croak("Odd number of arguments");
1645 474 100         for (int i = 1; i < items; i += 2) {
1646 270           const char *key = SvPV_nolen(ST(i));
1647 270           SV *val = ST(i + 1);
1648 270 100         if (strcmp(key, "header") == 0) header = SvPV(val, hlen);
1649 260 100         else if (strcmp(key, "row") == 0) row = SvPV(val, rlen);
1650 56 100         else if (strcmp(key, "footer") == 0) footer = SvPV(val, flen);
1651 46 100         else if (strcmp(key, "separator") == 0) sep = SvPV(val, slen);
1652 16 100         else if (strcmp(key, "escape_char") == 0) { STRLEN el; const char *ev = SvPV(val, el); if (el) esc = ev[0]; }
    50          
1653 11 100         else if (strcmp(key, "skip_if") == 0) skip_if_sv = val;
1654 4 50         else if (strcmp(key, "skip_unless") == 0) skip_unless_sv = val;
1655             }
1656             }
1657 206           tpl_compiled *t = tpl_compile(aTHX_ header, hlen, row, rlen, footer, flen, sep, slen, esc);
1658 204 100         if (skip_if_sv) {
1659 7           t->has_skip_if = 1;
1660 7 100         if (SvIOK(skip_if_sv) || looks_like_number(skip_if_sv)) {
    50          
1661 6           t->skip_if_col = SvIV(skip_if_sv);
1662             } else {
1663             STRLEN kl;
1664 1           const char *ks = SvPV(skip_if_sv, kl);
1665 1           t->skip_if_key = (char *)malloc(kl + 1);
1666 1           memcpy(t->skip_if_key, ks, kl);
1667 1           t->skip_if_key[kl] = '\0';
1668 1           t->skip_if_key_len = kl;
1669             }
1670             }
1671 204 100         if (skip_unless_sv) {
1672 4           t->has_skip_unless = 1;
1673 4 100         if (SvIOK(skip_unless_sv) || looks_like_number(skip_unless_sv)) {
    50          
1674 3           t->skip_unless_col = SvIV(skip_unless_sv);
1675             } else {
1676             STRLEN kl;
1677 1           const char *ks = SvPV(skip_unless_sv, kl);
1678 1           t->skip_unless_key = (char *)malloc(kl + 1);
1679 1           memcpy(t->skip_unless_key, ks, kl);
1680 1           t->skip_unless_key[kl] = '\0';
1681 1           t->skip_unless_key_len = kl;
1682             }
1683             }
1684 204           SV *obj = newSViv(PTR2IV(t));
1685 204           SV *ref = newRV_noinc(obj);
1686 204           sv_bless(ref, gv_stashpv(class, GV_ADD));
1687 204           RETVAL = ref;
1688             }
1689             OUTPUT:
1690             RETVAL
1691              
1692             SV *
1693             render(self, rows)
1694             SV *self
1695             AV *rows
1696             CODE:
1697             {
1698 185           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1699 185           RETVAL = tpl_render(aTHX_ t, rows);
1700             }
1701             OUTPUT:
1702             RETVAL
1703              
1704             SV *
1705             render_sorted(self, rows, sort_by, ...)
1706             SV *self
1707             AV *rows
1708             SV *sort_by
1709             CODE:
1710             {
1711 14           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1712 14           int descending = 0, numeric = 0;
1713 14 100         if (items > 3 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVHV) {
    50          
    50          
1714 5           HV *opts = (HV *)SvRV(ST(3));
1715             SV **sv;
1716 5           sv = hv_fetchs(opts, "descending", 0);
1717 5 100         if (sv && SvTRUE(*sv)) descending = 1;
    50          
1718 5           sv = hv_fetchs(opts, "numeric", 0);
1719 5 100         if (sv && SvTRUE(*sv)) numeric = 1;
    50          
1720             }
1721 17 100         if (SvROK(sort_by) && SvTYPE(SvRV(sort_by)) == SVt_PVAV) {
    50          
1722 3           AV *sort_av = (AV *)SvRV(sort_by);
1723 3           int nsort = (int)av_count(sort_av);
1724 3 50         if (nsort == 0) {
1725 0           RETVAL = tpl_render(aTHX_ t, rows);
1726             } else {
1727 3           SV **first = av_fetch(sort_av, 0, 0);
1728 3 50         int use_keys = first && !SvIOK(*first) && !looks_like_number(*first);
    100          
    50          
1729 3 100         if (use_keys) {
1730 2           const char **skeys = (const char **)malloc(nsort * sizeof(char *));
1731 2           STRLEN *sklens = (STRLEN *)malloc(nsort * sizeof(STRLEN));
1732 5 100         for (int i = 0; i < nsort; i++) {
1733 3           SV **el = av_fetch(sort_av, i, 0);
1734 3 50         skeys[i] = el ? SvPV(*el, sklens[i]) : "";
1735 3 50         if (!el) sklens[i] = 0;
1736             }
1737 2           RETVAL = tpl_render_sorted(aTHX_ t, rows, NULL, skeys, sklens, nsort, descending, numeric);
1738 2           free(skeys); free(sklens);
1739             } else {
1740 1           int *scols = (int *)malloc(nsort * sizeof(int));
1741 3 100         for (int i = 0; i < nsort; i++) {
1742 2           SV **el = av_fetch(sort_av, i, 0);
1743 2 50         scols[i] = el ? (int)SvIV(*el) : 0;
1744             }
1745 1           RETVAL = tpl_render_sorted(aTHX_ t, rows, scols, NULL, NULL, nsort, descending, numeric);
1746 1           free(scols);
1747             }
1748             }
1749 18 100         } else if (SvIOK(sort_by) || looks_like_number(sort_by)) {
    50          
1750 7           int col = (int)SvIV(sort_by);
1751 7           RETVAL = tpl_render_sorted(aTHX_ t, rows, &col, NULL, NULL, 1, descending, numeric);
1752             } else {
1753             STRLEN klen;
1754 4           const char *key = SvPV(sort_by, klen);
1755 4 50         if (klen > 1 && key[0] == '-') { key++; klen--; descending = 1; }
    100          
1756 4           RETVAL = tpl_render_sorted(aTHX_ t, rows, NULL, &key, &klen, 1, descending, numeric);
1757             }
1758             }
1759             OUTPUT:
1760             RETVAL
1761              
1762             SV *
1763             render_one(self, row)
1764             SV *self
1765             SV *row
1766             CODE:
1767             {
1768 5           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1769 5           RETVAL = tpl_render_one(aTHX_ t, row);
1770             }
1771             OUTPUT:
1772             RETVAL
1773              
1774             void
1775             render_to_fh(self, fh, rows)
1776             SV *self
1777             PerlIO *fh
1778             AV *rows
1779             CODE:
1780             {
1781 2           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1782 2           tpl_render_to_fh(aTHX_ t, rows, fh);
1783             }
1784              
1785             SV *
1786             render_cb(self, cb, ...)
1787             SV *self
1788             SV *cb
1789             CODE:
1790             {
1791 8           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1792 8 50         if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV)
    50          
1793 0           croak("render_cb: second argument must be a coderef");
1794 8           PerlIO *fh = NULL;
1795 8 100         if (items > 2) {
1796 3           fh = IoIFP(sv_2io(ST(2)));
1797             }
1798 8           RETVAL = tpl_render_cb(aTHX_ t, cb, fh);
1799             }
1800             OUTPUT:
1801             RETVAL
1802              
1803             AV *
1804             columns(self)
1805             SV *self
1806             CODE:
1807             {
1808 2           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1809 2           RETVAL = tpl_columns(aTHX_ t);
1810             }
1811             OUTPUT:
1812             RETVAL
1813              
1814             IV
1815             row_count(self)
1816             SV *self
1817             CODE:
1818             {
1819 2           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1820 2 50         RETVAL = (IV)t->last_row_count;
1821             }
1822             OUTPUT:
1823             RETVAL
1824              
1825             SV *
1826             clone(self, ...)
1827             SV *self
1828             CODE:
1829             {
1830 2           tpl_compiled *orig = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1831 2 50         if (items % 2 == 0) croak("Odd number of arguments");
1832 2           const char *row = NULL; STRLEN rlen = 0;
1833 2           const char *sep = NULL; STRLEN slen = 0;
1834 4 100         for (int i = 1; i < items; i += 2) {
1835 2           const char *key = SvPV_nolen(ST(i));
1836 2           SV *val = ST(i + 1);
1837 2 50         if (strcmp(key, "row") == 0) row = SvPV(val, rlen);
1838 0 0         else if (strcmp(key, "separator") == 0) sep = SvPV(val, slen);
1839             }
1840 2 50         if (!row) croak("clone requires 'row' argument");
1841 2 50         tpl_compiled *t = tpl_compile(aTHX_
1842 2           orig->header, orig->header_len,
1843             row, rlen,
1844 2           orig->footer, orig->footer_len,
1845             sep ? sep : orig->sep, sep ? slen : orig->sep_len,
1846 2 50         orig->escape_char);
1847             /* copy skip conditions from original */
1848 2           t->has_skip_if = orig->has_skip_if;
1849 2           t->skip_if_col = orig->skip_if_col;
1850 2 50         if (orig->skip_if_key) {
1851 0           t->skip_if_key = (char *)malloc(orig->skip_if_key_len + 1);
1852 0           memcpy(t->skip_if_key, orig->skip_if_key, orig->skip_if_key_len + 1);
1853 0           t->skip_if_key_len = orig->skip_if_key_len;
1854             }
1855 2           t->has_skip_unless = orig->has_skip_unless;
1856 2           t->skip_unless_col = orig->skip_unless_col;
1857 2 50         if (orig->skip_unless_key) {
1858 0           t->skip_unless_key = (char *)malloc(orig->skip_unless_key_len + 1);
1859 0           memcpy(t->skip_unless_key, orig->skip_unless_key, orig->skip_unless_key_len + 1);
1860 0           t->skip_unless_key_len = orig->skip_unless_key_len;
1861             }
1862 2           SV *obj = newSViv(PTR2IV(t));
1863 2           SV *ref = newRV_noinc(obj);
1864 2           sv_bless(ref, SvSTASH(SvRV(self)));
1865 2           RETVAL = ref;
1866             }
1867             OUTPUT:
1868             RETVAL
1869              
1870             void
1871             DESTROY(self)
1872             SV *self
1873             CODE:
1874             {
1875 206           tpl_compiled *t = INT2PTR(tpl_compiled *, SvIV(SvRV(self)));
1876 206           tpl_free(t);
1877             }