File Coverage

c/xmlwr.c
Criterion Covered Total %
statement 104 109 95.4
branch 79 94 84.0
condition n/a
subroutine n/a
pod n/a
total 183 203 90.1


line stmt bran cond sub pod time code
1             typedef struct fuxmlwr fuxmlwr;
2             struct fuxmlwr {
3             SV *self;
4             fuxmlwr *next, *prev;
5             fustr out;
6             };
7              
8             static fuxmlwr *fuxmlwr_tail = NULL;
9              
10 25           static SV *fuxmlwr_new(pTHX) {
11 25           fuxmlwr *wr = safemalloc(sizeof(*wr));
12 25           wr->next = NULL;
13 25           wr->prev = fuxmlwr_tail;
14 25 100         if (fuxmlwr_tail) fuxmlwr_tail->next = wr;
15 25           fuxmlwr_tail = wr;
16 25           fustr_init(&wr->out, NULL, SIZE_MAX);
17 25           return fu_selfobj(wr, "FU::XMLWriter");
18             }
19              
20 25           static void fuxmlwr_destroy(pTHX_ fuxmlwr *wr) {
21 25 50         if (fuxmlwr_tail == wr) fuxmlwr_tail = wr->next ? wr->next : wr->prev;
    50          
22 25 50         if (wr->next) wr->next->prev = wr->prev;
23 25 100         if (wr->prev) wr->prev->next = wr->next;
24 25 50         if (wr->out.sv) SvREFCNT_dec(wr->out.sv);
25 25           safefree(wr);
26 25           }
27              
28              
29 26           static void fuxmlwr_escape(pTHX_ fuxmlwr *wr, SV *sv) {
30 26 100         if (SvROK(sv) && !SvAMAGIC(sv)) fu_confess("Invalid attempt to output bare reference");
    50          
    100          
    50          
31              
32             STRLEN len;
33 24           const unsigned char *str = (unsigned char *)SvPV_const(sv, len);
34 24           const unsigned char *tmp, *end = str + len;
35 24           unsigned char x = 0;
36             unsigned char *buf;
37 24           int utf8 = SvUTF8(sv);
38              
39 33 50         while (str < end) {
40 33           tmp = str;
41 33 100         if (utf8) {
42 10 100         while (tmp < end) {
43 8           x = *tmp;
44 8 50         if (x == '<' || x == '&' || x == '"') break;
    50          
    50          
45 8           tmp++;
46             }
47             } else {
48 117 100         while (tmp < end) {
49 95           x = *tmp;
50 95 100         if (x == '<' || x == '&' || x == '"' || x >= 0x80) break;
    100          
    100          
    50          
51 86           tmp++;
52             }
53             }
54 33           fustr_write(&wr->out, (const char *)str, tmp-str);
55 33 100         if (tmp == end) return;
56 9           switch (x) {
57 3           case '<': fustr_write(&wr->out, "<", 4); break;
58 5           case '&': fustr_write(&wr->out, "&", 5); break;
59 1           case '"': fustr_write(&wr->out, """, 6); break;
60 0           default:
61 0           buf = (unsigned char *)fustr_write_buf(&wr->out, 2);
62 0           buf[0] = 0xc0 | (x >> 6);
63 0           buf[1] = 0x80 | (x & 0x3f);
64 0           break;
65             }
66 9           str = tmp + 1;
67             }
68             }
69              
70              
71 92           static int fuxmlwr_isnamechar(unsigned int x) {
72 92 100         return (x|32)-'a' < 26 || x-'0' < 10 || x == '_' || x == ':' || x == '-';
    100          
    50          
    50          
    100          
73             }
74              
75             // Validate a tag or attribute name. Pretty much /^[a-z0-9_:-]+$/i.
76             // This does not at all match with the XML and HTML standards, but this
77             // approach is simpler and catches the most important bugs anyway.
78 30           static void fuxmlwr_isname(const char *str) {
79 30           const char *x = str;
80 92 100         while (fuxmlwr_isnamechar(*x)) x++;
81 30 100         if (*x || x == str) fu_confess("Invalid tag or attribute name: '%s'", str);
    100          
82 23           }
83              
84              
85 27           static void fuxmlwr_tag(pTHX_ fuxmlwr *wr, I32 ax, I32 offset, I32 argc, int selfclose, const char *tagname, int tagnamelen) {
86             SV *key, *val;
87 27           const char *keys, *lastkey = NULL;
88 27           int isopen = 0;
89 27           dSP;
90              
91 27 100         if (!selfclose && ((argc - offset) & 1) == 0) fu_confess("Invalid number of arguments");
    100          
92 26           fustr_write_ch(&wr->out, '<');
93 26           fustr_write(&wr->out, tagname, tagnamelen);
94              
95 49 100         while (offset < argc-1) {
96 29           key = ST(offset);
97 29           offset++;
98 29           val = ST(offset);
99 29           offset++;
100              
101             // Don't even try to stringify attribute names; non-string keys are always a bug.
102 29 100         if (!SvPOK(key)) fu_confess("Non-string attribute");
103 27           keys = SvPVX(key);
104              
105 27           SvGETMAGIC(val);
106             /* TODO: Support boolean values */
107 27 100         if (keys[0] == '+' && keys[1] == 0) {
    50          
108 13 100         if (!SvOK(val)) {
109             // ignore
110 8 100         } else if (isopen) {
111 4           fustr_write_ch(&wr->out, ' ');
112 4           fuxmlwr_escape(aTHX_ wr, val);
113 4 100         } else if (lastkey) {
114 3           fustr_write_ch(&wr->out, ' ');
115 3           fustr_write(&wr->out, lastkey, strlen(lastkey));
116 3           fustr_write(&wr->out, "=\"", 2);
117 3           fuxmlwr_escape(aTHX_ wr, val);
118 3           isopen = 1;
119             } else {
120 1           fu_confess("Cannot use '+' as first attribute");
121             }
122             } else {
123 14 100         if (isopen) {
124 1           fustr_write_ch(&wr->out, '"');
125 1           isopen = 0;
126             }
127 14           fuxmlwr_isname(keys);
128 11 100         if (!SvOK(val)) {
129 4           lastkey = keys;
130             } else {
131 7           fustr_write_ch(&wr->out, ' ');
132 7           fustr_write(&wr->out, keys, SvCUR(key));
133 7           fustr_write(&wr->out, "=\"", 2);
134 7           fuxmlwr_escape(aTHX_ wr, val);
135 7           isopen = 1;
136             }
137             }
138             }
139              
140 20 100         if (isopen) fustr_write_ch(&wr->out, '"');
141              
142 20 100         if (offset < argc) {
143 19           val = ST(offset);
144 19           SvGETMAGIC(val);
145             } else
146 1           val = &PL_sv_undef;
147              
148 20 100         if (!SvOK(val)) { // undef
149 9           fustr_write(&wr->out, " />", 3);
150 11 100         } else if (SvROK(val) && strcmp(sv_reftype(SvRV(val), 0), "CODE") == 0) { // CODE ref
    100          
151 2           fustr_write_ch(&wr->out, '>');
152 2 50         PUSHMARK(SP);
153 2           call_sv(val, G_VOID|G_DISCARD|G_NOARGS);
154 2           fustr_write(&wr->out, "
155 2           fustr_write(&wr->out, tagname, tagnamelen);
156 2           fustr_write_ch(&wr->out, '>');
157             } else {
158 9           fustr_write_ch(&wr->out, '>');
159 9           fuxmlwr_escape(aTHX_ wr, val);
160 7           fustr_write(&wr->out, "
161 7           fustr_write(&wr->out, tagname, tagnamelen);
162 7           fustr_write_ch(&wr->out, '>');
163             }
164 18           }