]> mj.ucw.cz Git - libucw.git/blob - lib/xml.c
XML: Backuped unfinished XML parser.
[libucw.git] / lib / xml.c
1 /*
2  *      UCW Library -- A simple XML parser
3  *
4  *      (c) 2007 Pavel Charvat <pchar@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 /* TODO:
11  * - various character encodings
12  * - iface
13  * - stack-like memory handling where possible
14  */
15
16 #define LOCAL_DEBUG
17
18 #include "lib/lib.h"
19 #include "lib/mempool.h"
20 #include "lib/fastbuf.h"
21 #include "lib/ff-utf8.h"
22 #include "lib/chartype.h"
23 #include "lib/unicode.h"
24 #include "lib/xml.h"
25 #include "lib/hashfunc.h"
26 #include "lib/stkstring.h"
27 #include "charset/unicat.h"
28
29 #include <setjmp.h>
30
31 /*** Error handling ***/
32
33 static void NONRET
34 xml_throw(struct xml_context *ctx)
35 {
36   ASSERT(ctx->err_code && ctx->throw_buf);
37   longjmp(*(jmp_buf *)ctx->throw_buf, ctx->err_code);
38 }
39
40 static void
41 xml_warn(struct xml_context *ctx, const char *format, ...)
42 {
43   if (ctx->h_warn)
44     {
45       va_list args;
46       va_start(args, format);
47       ctx->err_msg = stk_vprintf(format, args);
48       ctx->err_code = XML_ERR_WARN;
49       va_end(args);
50       ctx->h_warn(ctx);
51       ctx->err_msg = NULL;
52       ctx->err_code = XML_ERR_OK;
53     }
54 }
55
56 static void
57 xml_error(struct xml_context *ctx, const char *format, ...)
58 {
59   if (ctx->h_error)
60     {
61       va_list args;
62       va_start(args, format);
63       ctx->err_msg = stk_vprintf(format, args);
64       ctx->err_code = XML_ERR_ERROR;
65       va_end(args);
66       ctx->h_error(ctx);
67       ctx->err_msg = NULL;
68       ctx->err_code = XML_ERR_OK;
69     }
70 }
71
72 static void NONRET
73 xml_fatal(struct xml_context *ctx, const char *format, ...)
74 {
75   va_list args;
76   va_start(args, format);
77   ctx->err_msg = mp_vprintf(ctx->pool, format, args);
78   ctx->err_code = XML_ERR_FATAL;
79   ctx->state = XML_STATE_FATAL;
80   va_end(args);
81   if (ctx->h_fatal)
82     ctx->h_fatal(ctx);
83   xml_throw(ctx);
84 }
85
86 /*** Charecter categorization ***/
87
88 #include "obj/lib/xml-ucat.h"
89
90 static inline uns
91 xml_char_cat(uns c)
92 {
93   if (c < 0x10000)
94     return 1U << xml_char_tab2[(c & 0xff) + xml_char_tab1[c >> 8]];
95   else if (likely(c < 0x110000))
96     return 1U << xml_char_tab3[c >> 16];
97   else
98     return 1;
99 }
100
101 /*** Reading of document/external entities ***/
102
103 static void NONRET
104 xml_eof(struct xml_context *ctx)
105 {
106   ctx->err_msg = "Unexpected EOF";
107   ctx->err_code = XML_ERR_EOF;
108   xml_throw(ctx);
109 }
110
111 static void NONRET
112 xml_fatal_nested(struct xml_context *ctx)
113 {
114   xml_fatal(ctx, "Entity is not tested correctly");
115 }
116
117 static inline void
118 xml_inc_depth(struct xml_context *ctx)
119 {
120   ctx->depth++;
121 }
122
123 static inline void
124 xml_dec_depth(struct xml_context *ctx)
125 {
126   if (unlikely(!ctx->depth))
127     xml_fatal_nested(ctx);
128   ctx->depth--;
129 }
130
131 static void
132 xml_push_source(struct xml_context *ctx, struct fastbuf *fb, uns flags)
133 {
134   DBG("XML: xml_push_source");
135   struct xml_source *osrc = ctx->sources;
136   if (osrc)
137     {
138       osrc->bptr = ctx->bptr;
139       osrc->bstop = ctx->bstop;
140       osrc->depth = ctx->depth;
141     }
142   struct xml_source *src = mp_alloc(ctx->pool, sizeof(*src));
143   src->next = osrc;
144   src->flags = flags;
145   src->fb = fb;
146   ctx->depth = 0;
147   ctx->sources = src;
148   ctx->bstop = ctx->bptr = src->buf;
149   if (flags & XML_SRC_SURROUND)
150     {
151       *ctx->bptr++ = 0x20;
152       *ctx->bptr++ = xml_char_cat(0x20);
153     }
154 }
155
156 void
157 xml_set_source(struct xml_context *ctx, struct fastbuf *fb)
158 {
159   xml_push_source(ctx, fb, XML_SRC_DOCUMENT | XML_SRC_DECL);
160 }
161
162 static void
163 xml_pop_source(struct xml_context *ctx)
164 {
165   DBG("XML: xml_pop_source");
166   if (unlikely(ctx->depth))
167     xml_fatal(ctx, "Invalid entity nesting");
168   struct xml_source *src = ctx->sources;
169   bclose(src->fb);
170   ctx->sources = src = src->next;
171   if (unlikely(!src))
172     xml_eof(ctx);
173   ctx->bptr = src->bptr;
174   ctx->bstop = src->bstop;
175   ctx->depth = src->depth;
176 }
177
178 static uns
179 xml_error_restricted(struct xml_context *ctx, uns c)
180 {
181   xml_error(ctx, "Restricted char U+%04X", c);
182   return UNI_REPLACEMENT;
183 }
184
185 static void xml_parse_decl(struct xml_context *ctx);
186
187 static void
188 xml_refill(struct xml_context *ctx)
189 {
190   // FIXME:
191   // -- various encodings, especially UTF-16
192   // -- track col/row numbers
193   // -- report incorrect encoding
194   // -- deal with forbidden XML 1.1 newlines in xml/text decl
195   do
196     {
197       struct xml_source *src = ctx->sources;
198       uns c, t, t1, t2, f = src->flags;
199       if (f & XML_SRC_EOF)
200         xml_pop_source(ctx);
201       else if (f & XML_SRC_DECL)
202         xml_parse_decl(ctx);
203       else
204         {
205           struct fastbuf *fb = src->fb;
206           if (ctx->bptr == ctx->bstop)
207             ctx->bptr = ctx->bstop = src->buf;
208           u32 *bend = src->buf + ARRAY_SIZE(src->buf), *bstop = ctx->bstop, *last_0xd = (f & XML_SRC_NEW_LINE) ? bstop : bend;
209           if (ctx->flags & XML_FLAG_VERSION_1_1)
210             {
211               t2 = XML_CHAR_NEW_LINE_1_1;
212               t1 = XML_CHAR_UNRESTRICTED_1_1 & ~t2;
213             }
214           else
215             {
216               t2 = XML_CHAR_NEW_LINE_1_0;
217               t1 = XML_CHAR_VALID_1_0 & ~t2;
218             }
219           while (bstop < bend)
220             {
221               c = bget_utf8_32(fb);
222               t = xml_char_cat(c);
223               if (t & t1)
224                 {
225                   /* Typical branch */
226                   *bstop++ = c;
227                   *bstop++ = t;
228                 }
229               else if (t & t2)
230                 {
231                   /* New line
232                    * XML 1.0: 0xA | 0xD | 0xD 0xA
233                    * XML 1.1: 0xA | 0xD | 0xD 0xA | 0x85 | 0xD 0x85 | 0x2028 */
234                   *bstop++ = 0xa;
235                   *bstop++ = xml_char_cat(0xa);
236                   if (c == 0xd)
237                     last_0xd = bstop;
238                   else if (c != 0x2028 && last_0xd != bstop - 2)
239                     bstop -= 2;
240                 }
241               else if ((int)c >= 0)
242                 {
243                   /* Restricted character */
244                   c = xml_error_restricted(ctx, c);
245                   *bstop++ = c;
246                   *bstop++ = xml_char_cat(c);
247                 }
248               else
249                 {
250                   /* EOF */
251                   if (f & XML_SRC_SURROUND)
252                     {
253                       *bstop++ = 0x20;
254                       *bstop++ = xml_char_cat(0x20);
255                     }
256                   f |= XML_SRC_EOF;
257                   break;
258                 }
259             }
260           if (last_0xd == bstop)
261             f |= XML_SRC_NEW_LINE;
262           else
263             f &= ~XML_SRC_NEW_LINE;
264           ctx->sources->flags = f;
265           ctx->bstop = bstop;
266           DBG("XML: refilled %u characters", (uns)(ctx->bstop - ctx->bptr) / 2);
267         }
268     }
269   while (ctx->bptr == ctx->bstop);
270 }
271
272 static inline uns
273 xml_peek_char(struct xml_context *ctx)
274 {
275   if (ctx->bptr == ctx->bstop)
276     xml_refill(ctx);
277   return ctx->bptr[0];
278 }
279
280 static inline uns
281 xml_peek_cat(struct xml_context *ctx)
282 {
283   if (ctx->bptr == ctx->bstop)
284     xml_refill(ctx);
285   return ctx->bptr[1];
286 }
287
288 static inline uns
289 xml_get_char(struct xml_context *ctx)
290 {
291   uns c = xml_peek_char(ctx);
292   ctx->bptr += 2;
293   return c;
294 }
295
296 static inline uns
297 xml_get_cat(struct xml_context *ctx)
298 {
299   uns c = xml_peek_cat(ctx);
300   ctx->bptr += 2;
301   return c;
302 }
303
304 static inline uns
305 xml_last_char(struct xml_context *ctx)
306 {
307   return ctx->bptr[-2];
308 }
309
310 static inline uns
311 xml_last_cat(struct xml_context *ctx)
312 {
313   return ctx->bptr[-1];
314 }
315
316 static inline uns
317 xml_skip_char(struct xml_context *ctx)
318 {
319   uns c = ctx->bptr[0];
320   ctx->bptr += 2;
321   return c;
322 }
323
324 static inline uns
325 xml_unget_char(struct xml_context *ctx)
326 {
327   return *(ctx->bptr -= 2);
328 }
329
330 /*** Basic parsing ***/
331
332 static void NONRET
333 xml_fatal_expected(struct xml_context *ctx, uns c)
334 {
335   xml_fatal(ctx, "Expected '%c'", c);
336 }
337
338 static void NONRET
339 xml_fatal_expected_white(struct xml_context *ctx)
340 {
341   xml_fatal(ctx, "Expected a white space");
342 }
343
344 static void NONRET
345 xml_fatal_expected_quot(struct xml_context *ctx)
346 {
347   xml_fatal(ctx, "Expected a quotation mark");
348 }
349
350 static inline uns
351 xml_parse_white(struct xml_context *ctx, uns mandatory)
352 {
353   /* mandatory=1 -> S ::= (#x20 | #x9 | #xD | #xA)+
354    * mandatory=0 -> S? */
355   uns cnt = 0;
356   while (xml_peek_cat(ctx) & XML_CHAR_WHITE)
357     {
358       xml_skip_char(ctx);
359       cnt++;
360     }
361   if (unlikely(mandatory && !cnt))
362     xml_fatal_expected_white(ctx);
363   return cnt;
364 }
365
366 static inline void
367 xml_parse_char(struct xml_context *ctx, uns c)
368 {
369   /* Consumes a given Unicode character */
370   if (unlikely(c != xml_get_char(ctx)))
371     xml_fatal_expected(ctx, c);
372 }
373
374 static inline void
375 xml_parse_seq(struct xml_context *ctx, const char *seq)
376 {
377   /* Consumes a given sequence of ASCII characters */
378   while (*seq)
379     xml_parse_char(ctx, *seq++);
380 }
381
382 static void
383 xml_parse_eq(struct xml_context *ctx)
384 {
385   /* Eq ::= S? '=' S? */
386   xml_parse_white(ctx, 0);
387   xml_parse_char(ctx, '=');
388   xml_parse_white(ctx, 0);
389 }
390
391 static inline uns
392 xml_parse_quote(struct xml_context *ctx)
393 {
394   /* "'" | '"' */
395   uns c = xml_get_char(ctx);
396   if (unlikely(c != '\'' && c != '\"'))
397     xml_fatal_expected_quot(ctx);
398   return c;
399 }
400
401 /* Names and nmtokens */
402
403 static char *
404 xml_parse_string(struct xml_context *ctx, uns first_cat, uns next_cat, char *err)
405 {
406   char *p = mp_start_noalign(ctx->pool, 1);
407   if (unlikely(!(xml_peek_cat(ctx) & first_cat)))
408     xml_fatal(ctx, "%s", err);
409   do
410     {
411       p = mp_spread(ctx->pool, p, 5);
412       p = utf8_32_put(p, xml_skip_char(ctx));
413     }
414   while (xml_peek_cat(ctx) & next_cat);
415   *p++ = 0;
416   return mp_end(ctx->pool, p);
417 }
418
419 static void
420 xml_skip_string(struct xml_context *ctx, uns first_cat, uns next_cat, char *err)
421 {
422   if (unlikely(!(xml_get_cat(ctx) & first_cat)))
423     xml_fatal(ctx, "%s", err);
424   while (xml_peek_cat(ctx) & next_cat)
425     xml_skip_char(ctx);
426 }
427
428 static char *
429 xml_parse_name(struct xml_context *ctx)
430 {
431   /* Name ::= NameStartChar (NameChar)* */
432   return xml_parse_string(ctx,
433     !(ctx->flags & XML_FLAG_VERSION_1_1) ? XML_CHAR_SNAME_1_0 : XML_CHAR_SNAME_1_1,
434     !(ctx->flags & XML_FLAG_VERSION_1_1) ? XML_CHAR_NAME_1_0 : XML_CHAR_NAME_1_1,
435     "Expected a name");
436 }
437
438 static void
439 xml_skip_name(struct xml_context *ctx)
440 {
441   xml_skip_string(ctx,
442     !(ctx->flags & XML_FLAG_VERSION_1_1) ? XML_CHAR_SNAME_1_0 : XML_CHAR_SNAME_1_1,
443     !(ctx->flags & XML_FLAG_VERSION_1_1) ? XML_CHAR_NAME_1_0 : XML_CHAR_NAME_1_1,
444     "Expected a name");
445 }
446
447 static char *
448 xml_parse_nmtoken(struct xml_context *ctx)
449 {
450   /* Nmtoken ::= (NameChar)+ */
451   uns cat = !(ctx->flags & XML_FLAG_VERSION_1_1) ? XML_CHAR_NAME_1_0 : XML_CHAR_NAME_1_1;
452   return xml_parse_string(ctx, cat, cat, "Expected a nmtoken");
453 }
454
455 /* Simple literals */
456
457 static char *
458 xml_parse_system_literal(struct xml_context *ctx)
459 {
460   /* SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") */
461   char *p = mp_start_noalign(ctx->pool, 1);
462   uns q = xml_parse_quote(ctx), c;
463   while ((c = xml_get_char(ctx)) != q)
464     {
465       p = mp_spread(ctx->pool, p, 5);
466       p = utf8_32_put(p, c);
467     }
468   *p++ = 0;
469   return mp_end(ctx->pool, p);
470 }
471
472 static char *
473 xml_parse_pubid_literal(struct xml_context *ctx)
474 {
475   /* PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" */
476   char *p = mp_start_noalign(ctx->pool, 1);
477   uns q = xml_parse_quote(ctx), c;
478   while ((c = xml_get_char(ctx)) != q)
479     {
480       if (unlikely(!(xml_last_cat(ctx) & XML_CHAR_PUBID)))
481         xml_fatal(ctx, "Expected a pubid character");
482       p = mp_spread(ctx->pool, p, 2);
483       *p++ = c;
484     }
485   *p++ = 0;
486   return mp_end(ctx->pool, p);
487 }
488
489 static char *
490 xml_parse_encoding_name(struct xml_context *ctx)
491 {
492   /* EncName ::= '"' [A-Za-z] ([A-Za-z0-9._] | '-')* '"' | "'" [A-Za-z] ([A-Za-z0-9._] | '-')* "'" */
493   char *p = mp_start_noalign(ctx->pool, 1);
494   uns q = xml_parse_quote(ctx);
495   if (unlikely(!(xml_peek_cat(ctx) & XML_CHAR_ENC_SNAME)))
496     xml_fatal(ctx, "Invalid character in the encoding name");
497   while(1)
498     {
499       p = mp_spread(ctx->pool, p, 2);
500       *p++ = xml_skip_char(ctx);
501       if (xml_get_char(ctx) == q)
502         break;
503       if (unlikely(!(xml_last_cat(ctx) & XML_CHAR_ENC_NAME)))
504         xml_fatal(ctx, "Invalid character in the encoding name");
505     }
506   *p++ = 0;
507   return mp_end(ctx->pool, p);
508 }
509
510 /* Document/external entity header */
511
512 static void
513 xml_detect_encoding(struct xml_context *ctx)
514 {
515   DBG("XML: xml_detect_encoding");
516   struct xml_source *src = ctx->sources;
517   struct fastbuf *fb = src->fb;
518   char *detected_encoding = NULL;
519   uns x = 0, l = 0, c, z = 1;
520   while (l < 4)
521     {
522       if (!~(c = bgetc(fb)))
523         {
524           src->flags |= XML_SRC_EOF;
525           break;
526         }
527       else if (!c || c >= 0xfe || c == 0xa7 || c == 0x94)
528         z = 0;
529       else if ((c < 0x3c || c > 0x78))
530         {
531           bungetc(fb);
532           break;
533         }
534       x = (x << 8) + c;
535       l++;
536     }
537   if (z)
538     z = x;
539   else if (l == 2)
540     switch (x)
541       {
542         case 0xFEFF:
543           xml_fatal(ctx, "UTF-16BE encoding not supported");
544         case 0xFFFE:
545           xml_fatal(ctx, "UTF-16LE encoding not supported");
546         default:
547           goto cannot_detect;
548       }
549   else if (l == 4)
550     switch (x)
551       {
552         case 0x0000FEFF:
553           xml_fatal(ctx, "UCS-4BE encoding not supported");
554         case 0xFFFE0000:
555           xml_fatal(ctx, "UCS-4LE encoding not supported");
556         case 0x0000FFFE:
557           xml_fatal(ctx, "UCS-4 encoding (order 2143) not supported");
558         case 0xFEFF0000:
559           xml_fatal(ctx, "UCS-4 encoding (order 3412) not supported");
560         case 0x0000003c:
561           xml_fatal(ctx, "UCS-4BE encoding not supported");
562         case 0x3c000000:
563           xml_fatal(ctx, "UCS-4LE encoding not supported");
564         case 0x00003c00:
565           xml_fatal(ctx, "UCS-4 encoding (order 2143) not supported");
566         case 0x003c0000:
567           xml_fatal(ctx, "UCS-4 encoding (order 3412) not supported");
568         case 0x003c003F:
569           xml_fatal(ctx, "UTF-16BE encoding not supported");
570         case 0x3C003F00:
571           xml_fatal(ctx, "UTF-16LE encoding not supported");
572         case 0x3C3F786D:
573           xml_fatal(ctx, "EBCDIC encoding not supported");
574         default:
575           goto cannot_detect;
576       }
577   else
578 cannot_detect:
579     xml_fatal(ctx, "Cannot detect the encoding");
580   ctx->bptr = ctx->bstop = src->buf + 8;
581   while (z)
582     {
583       c = z & 0xff;
584       z >>= 8;
585       *--ctx->bptr = xml_char_cat(c);
586       *--ctx->bptr = c;
587     }
588   if (!detected_encoding && ctx->bstop == ctx->bptr && xml_peek_char(ctx) == 0xfeff)
589     xml_skip_char(ctx);
590   DBG("XML: Detected encoding: %s", detected_encoding ? : "UTF-8");
591   if (!(src->flags & XML_SRC_EOF))
592     xml_refill(ctx);
593 }
594
595 static void
596 xml_parse_decl(struct xml_context *ctx)
597 {
598   DBG("XML: xml_parse_decl");
599   ctx->sources->flags &= ~XML_SRC_DECL;
600   xml_detect_encoding(ctx);
601   uns document = ctx->sources->flags & XML_SRC_DOCUMENT;
602   u32 *bptr = ctx->bptr;
603   uns have_decl =
604     (12 <= ctx->bstop - ctx->bptr &&
605     bptr[0] == '<' && bptr[2] == '?' && (bptr[4] & 0xdf) == 'X' && (bptr[6] & 0xdf) == 'M' && (bptr[8] & 0xdf) == 'L' &&
606     (bptr[11] & XML_CHAR_WHITE));
607   if (!have_decl)
608     {
609       if (document)
610         xml_fatal(ctx, "Missing or corrupted XML declaration header");
611       return;
612     }
613   ctx->bptr += 12;
614
615   /* FIXME: the header must not contain exotic newlines */
616   xml_parse_white(ctx, 0);
617
618   if (xml_peek_char(ctx) == 'v')
619     {
620       xml_parse_seq(ctx, "version");
621       xml_parse_eq(ctx);
622       char *version = xml_parse_pubid_literal(ctx);
623       DBG("XML: Version=%s", version);
624       if (document)
625         {
626           ctx->version_str = version;
627           if (!strcmp(ctx->version_str, "1.0"))
628             ;
629           else if (!strcmp(ctx->version_str, "1.1"))
630             ctx->flags |= XML_FLAG_VERSION_1_1;
631           else
632             xml_fatal(ctx, "Unsupported XML version");
633         }
634       else if (strcmp(version, ctx->version_str))
635         xml_error(ctx, "Mixed XML versions");
636     }
637   else if (document)
638     xml_fatal(ctx, "Missing XML version");
639
640   // FIXME: TextDecl must contain encoding
641   if (!xml_parse_white(ctx, 0))
642     goto end;
643   if (xml_peek_char(ctx) == 'e')
644     {
645       xml_parse_seq(ctx, "encoding");
646       xml_parse_eq(ctx);
647       ctx->encoding = xml_parse_encoding_name(ctx);
648       DBG("encoding=%s", ctx->encoding);
649       // FIXME: check encoding
650       if (!xml_parse_white(ctx, 0))
651         goto end;
652     }
653
654   if (document && xml_peek_char(ctx) == 's')
655     {
656       xml_parse_seq(ctx, "standalone");
657       xml_parse_eq(ctx);
658       uns c = xml_parse_quote(ctx);
659       if (ctx->standalone = (xml_peek_char(ctx) == 'y'))
660         xml_parse_seq(ctx, "yes");
661       else
662         xml_parse_seq(ctx, "no");
663       xml_parse_char(ctx, c);
664       DBG("standalone=%d", ctx->standalone);
665       xml_parse_white(ctx, 0);
666     }
667 end:
668   xml_parse_seq(ctx, "?>");
669 }
670
671 /*** Document Type Definition (DTD) ***/
672
673 /* Notations */
674
675 #define HASH_PREFIX(x) xml_dtd_notns_##x
676 #define HASH_NODE struct xml_dtd_notn
677 #define HASH_KEY_STRING name
678 #define HASH_AUTO_POOL 1024
679 #define HASH_ZERO_FILL
680 #define HASH_TABLE_DYNAMIC
681 #define HASH_WANT_FIND
682 #define HASH_WANT_LOOKUP
683 #define HASH_WANT_CLEANUP
684 #include "lib/hashtable.h"
685
686 /* General entities */
687
688 #define HASH_PREFIX(x) xml_dtd_ents_##x
689 #define HASH_NODE struct xml_dtd_ent
690 #define HASH_KEY_STRING name
691 #define HASH_AUTO_POOL 1024
692 #define HASH_ZERO_FILL
693 #define HASH_TABLE_DYNAMIC
694 #define HASH_WANT_FIND
695 #define HASH_WANT_LOOKUP
696 #define HASH_WANT_CLEANUP
697 #include "lib/hashtable.h"
698
699 static void
700 xml_dtd_declare_trivial_gent(struct xml_context *ctx, char *name, char *text)
701 {
702   struct xml_dtd *dtd = ctx->dtd;
703   struct xml_dtd_ent *ent = xml_dtd_ents_lookup(dtd->tab_gents, name);
704   if (ent->flags & XML_DTD_ENT_DECLARED)
705     {
706       xml_warn(ctx, "Entity &%s; already declared", name);
707       return;
708     }
709   slist_add_tail(&dtd->gents, &ent->n);
710   ent->flags = XML_DTD_ENT_DECLARED | XML_DTD_ENT_TRIVIAL;
711   ent->text = text;
712 }
713
714 static void
715 xml_dtd_declare_default_gents(struct xml_context *ctx)
716 {
717   xml_dtd_declare_trivial_gent(ctx, "lt", "<");
718   xml_dtd_declare_trivial_gent(ctx, "gt", ">");
719   xml_dtd_declare_trivial_gent(ctx, "amp", "&");
720   xml_dtd_declare_trivial_gent(ctx, "apos", "'");
721   xml_dtd_declare_trivial_gent(ctx, "quot", "\"");
722 }
723
724 static struct xml_dtd_ent *
725 xml_dtd_find_gent(struct xml_context *ctx, char *name)
726 {
727   struct xml_dtd *dtd = ctx->dtd;
728   if (dtd)
729     {
730       struct xml_dtd_ent *ent = xml_dtd_ents_find(dtd->tab_gents, name);
731       return (ent->flags & XML_DTD_ENT_DECLARED) ? ent : NULL;
732     }
733   else
734     {
735 #define ENT(n, t) ent_##n = { .name = #n, .text = t, .len = 1, .flags = XML_DTD_ENT_DECLARED | XML_DTD_ENT_TRIVIAL }
736       static struct xml_dtd_ent ENT(lt, "<"), ENT(gt, ">"), ENT(amp, "&"), ENT(apos, "'"), ENT(quot, "\"");
737 #undef ENT
738       switch (name[0])
739         {
740           case 'l':
741             if (!strcmp(name, "lt"))
742               return &ent_lt;
743             break;
744           case 'g':
745             if (!strcmp(name, "gt"))
746               return &ent_gt;
747             break;
748           case 'a':
749             if (!strcmp(name, "amp"))
750               return &ent_amp;
751             if (!strcmp(name, "apos"))
752               return &ent_apos;
753             break;
754           case 'q':
755             if (!strcmp(name, "quot"))
756               return &ent_quot;
757             break;
758         }
759       return NULL;
760     }
761 }
762
763 /* Parameter entities */
764
765 static struct xml_dtd_ent *
766 xml_dtd_find_pent(struct xml_context *ctx, char *name)
767 {
768   struct xml_dtd *dtd = ctx->dtd;
769   struct xml_dtd_ent *ent = xml_dtd_ents_find(dtd->tab_pents, name);
770   return (ent->flags & XML_DTD_ENT_DECLARED) ? ent : NULL;
771 }
772
773 /* Elements */
774
775 #define HASH_PREFIX(x) xml_dtd_elems_##x
776 #define HASH_NODE struct xml_dtd_elem
777 #define HASH_KEY_STRING name
778 #define HASH_TABLE_DYNAMIC
779 #define HASH_AUTO_POOL 1024
780 #define HASH_ZERO_FILL
781 #define HASH_WANT_LOOKUP
782 #define HASH_WANT_CLEANUP
783 #include "lib/hashtable.h"
784
785 /* Element attributes */
786
787 struct xml_dtd_attrs_table;
788
789 static inline uns
790 xml_dtd_attrs_hash(struct xml_dtd_attrs_table *tab UNUSED, struct xml_dtd_elem *elem, char *name)
791 {
792   return hash_pointer(elem) ^ hash_string(name);
793 }
794
795 static inline int
796 xml_dtd_attrs_eq(struct xml_dtd_attrs_table *tab UNUSED, struct xml_dtd_elem *elem1, char *name1, struct xml_dtd_elem *elem2, char *name2)
797 {
798   return (elem1 == elem2) && !strcmp(name1, name2);
799 }
800
801 static inline void
802 xml_dtd_attrs_init_key(struct xml_dtd_attrs_table *tab UNUSED, struct xml_dtd_attr *attr, struct xml_dtd_elem *elem, char *name)
803 {
804   attr->elem = elem;
805   attr->name = name;
806 }
807
808 #define HASH_PREFIX(x) xml_dtd_attrs_##x
809 #define HASH_NODE struct xml_dtd_attr
810 #define HASH_AUTO_POOL 1024
811 #define HASH_ZERO_FILL
812 #define HASH_TABLE_DYNAMIC
813 #define HASH_KEY_COMPLEX(x) x elem, x name
814 #define HASH_KEY_DECL struct xml_dtd_elem *elem, char *name
815 #define HASH_GIVE_HASHFN
816 #define HASH_GIVE_EQ
817 #define HASH_GIVE_INIT_KEY
818 #define HASH_WANT_FIND
819 #define HASH_WANT_NEW
820 #define HASH_WANT_CLEANUP
821 #include "lib/hashtable.h"
822
823 /* Enumerated attribute values */
824
825 struct xml_dtd_evals_table;
826
827 static inline uns
828 xml_dtd_evals_hash(struct xml_dtd_evals_table *tab UNUSED, struct xml_dtd_attr *attr, char *val)
829 {
830   return hash_pointer(attr) ^ hash_string(val);
831 }
832
833 static inline int
834 xml_dtd_evals_eq(struct xml_dtd_evals_table *tab UNUSED, struct xml_dtd_attr *attr1, char *val1, struct xml_dtd_attr *attr2, char *val2)
835 {
836   return (attr1 == attr2) && !strcmp(val1, val2);
837 }
838
839 static inline void
840 xml_dtd_evals_init_key(struct xml_dtd_evals_table *tab UNUSED, struct xml_dtd_eval *eval, struct xml_dtd_attr *attr, char *val)
841 {
842   eval->attr = attr;
843   eval->val = val;
844 }
845
846 #define HASH_PREFIX(x) xml_dtd_evals_##x
847 #define HASH_NODE struct xml_dtd_eval
848 #define HASH_AUTO_POOL 1024
849 #define HASH_TABLE_DYNAMIC
850 #define HASH_KEY_COMPLEX(x) x attr, x val
851 #define HASH_KEY_DECL struct xml_dtd_attr *attr, char *val
852 #define HASH_GIVE_HASHFN
853 #define HASH_GIVE_EQ
854 #define HASH_GIVE_INIT_KEY
855 #define HASH_WANT_FIND
856 #define HASH_WANT_NEW
857 #define HASH_WANT_CLEANUP
858 #include "lib/hashtable.h"
859
860 /* Enumerated attribute notations */
861
862 struct xml_dtd_enotns_table;
863
864 static inline uns
865 xml_dtd_enotns_hash(struct xml_dtd_enotns_table *tab UNUSED, struct xml_dtd_attr *attr, struct xml_dtd_notn *notn)
866 {
867   return hash_pointer(attr) ^ hash_pointer(notn);
868 }
869
870 static inline int
871 xml_dtd_enotns_eq(struct xml_dtd_enotns_table *tab UNUSED, struct xml_dtd_attr *attr1, struct xml_dtd_notn *notn1, struct xml_dtd_attr *attr2, struct xml_dtd_notn *notn2)
872 {
873   return (attr1 == attr2) && (notn1 == notn2);
874 }
875
876 static inline void
877 xml_dtd_enotns_init_key(struct xml_dtd_enotns_table *tab UNUSED, struct xml_dtd_enotn *enotn, struct xml_dtd_attr *attr, struct xml_dtd_notn *notn)
878 {
879   enotn->attr = attr;
880   enotn->notn = notn;
881 }
882
883 #define HASH_PREFIX(x) xml_dtd_enotns_##x
884 #define HASH_NODE struct xml_dtd_enotn
885 #define HASH_AUTO_POOL 1024
886 #define HASH_TABLE_DYNAMIC
887 #define HASH_KEY_COMPLEX(x) x attr, x notn
888 #define HASH_KEY_DECL struct xml_dtd_attr *attr, struct xml_dtd_notn *notn
889 #define HASH_GIVE_HASHFN
890 #define HASH_GIVE_EQ
891 #define HASH_GIVE_INIT_KEY
892 #define HASH_WANT_FIND
893 #define HASH_WANT_NEW
894 #define HASH_WANT_CLEANUP
895 #include "lib/hashtable.h"
896
897 /* DTD initialization/cleanup */
898
899 static void
900 xml_dtd_init(struct xml_context *ctx)
901 {
902   ctx->dtd = mp_alloc_zero(ctx->pool, sizeof(*ctx->dtd));
903   xml_dtd_ents_init(ctx->dtd->tab_gents = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_ents_table)));
904   xml_dtd_ents_init(ctx->dtd->tab_pents = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_ents_table)));
905   xml_dtd_notns_init(ctx->dtd->tab_notns = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_notns_table)));
906   xml_dtd_elems_init(ctx->dtd->tab_elems = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_elems_table)));
907   xml_dtd_attrs_init(ctx->dtd->tab_attrs = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_attrs_table)));
908   xml_dtd_evals_init(ctx->dtd->tab_evals = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_evals_table)));
909   xml_dtd_enotns_init(ctx->dtd->tab_enotns = mp_alloc_zero(ctx->pool, sizeof(struct xml_dtd_enotns_table)));
910   xml_dtd_declare_default_gents(ctx);
911 }
912
913 static void
914 xml_dtd_cleanup(struct xml_context *ctx)
915 {
916   if (!ctx->dtd)
917     return;
918   xml_dtd_ents_cleanup(ctx->dtd->tab_gents);
919   xml_dtd_ents_cleanup(ctx->dtd->tab_pents);
920   xml_dtd_notns_cleanup(ctx->dtd->tab_notns);
921   xml_dtd_elems_cleanup(ctx->dtd->tab_elems);
922   xml_dtd_attrs_cleanup(ctx->dtd->tab_attrs);
923   xml_dtd_evals_cleanup(ctx->dtd->tab_evals);
924   xml_dtd_enotns_cleanup(ctx->dtd->tab_enotns);
925 }
926
927 static void
928 xml_dtd_finish(struct xml_context *ctx)
929 {
930   if (!ctx->dtd)
931     return;
932   // FIXME
933 }
934
935 /*** Parsing functions ***/
936
937 /* Comments */
938
939 static void
940 xml_push_comment(struct xml_context *ctx)
941 {
942   /* Parse a comment to ctx->value:
943    *   Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
944    * Already parsed: '<!-' */
945   struct fastbuf *out = ctx->value;
946   uns c;
947   xml_parse_char(ctx, '-');
948   while (1)
949     {
950       if ((c = xml_get_char(ctx)) == '-')
951         if ((c = xml_get_char(ctx)) == '-')
952           break;
953         else
954           bputc(out, '-');
955       bput_utf8_32(out, c);
956     }
957   xml_parse_char(ctx, '>');
958   fbgrow_rewind(out);
959   if (ctx->h_comment)
960     ctx->h_comment(ctx);
961 }
962
963 static void
964 xml_pop_comment(struct xml_context *ctx)
965 {
966   fbgrow_rewind(ctx->value);
967 }
968
969 static void
970 xml_skip_comment(struct xml_context *ctx)
971 {
972   xml_parse_char(ctx, '-');
973   while (xml_get_char(ctx) != '-' || xml_get_char(ctx) != '-');
974   xml_parse_char(ctx, '>');
975 }
976
977 /* Processing instructions */
978
979 static void
980 xml_push_pi(struct xml_context *ctx)
981 {
982   /* Parses a PI to ctx->value and ctx->name:
983    *   PI       ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
984    *   PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
985    * Already parsed: '<?' */
986
987   ctx->name = xml_parse_name(ctx);
988   if (unlikely(!strcasecmp(ctx->name, "xml")))
989     xml_fatal(ctx, "Reserved PI target");
990   struct fastbuf *out = ctx->value;
991   if (xml_parse_white(ctx, 0))
992     xml_parse_seq(ctx, "?>");
993   else
994     {
995       while (1)
996         {
997           uns c;
998           if ((c = xml_get_char(ctx)) == '?')
999             if (xml_get_char(ctx) == '>')
1000               break;
1001             else
1002               {
1003                 xml_unget_char(ctx);
1004                 bputc(out, '?');
1005               }
1006           else
1007             bput_utf8_32(out, c);
1008         }
1009       fbgrow_rewind(out);
1010     }
1011   if (ctx->h_pi)
1012     ctx->h_pi(ctx);
1013 }
1014
1015 static void
1016 xml_pop_pi(struct xml_context *ctx)
1017 {
1018   fbgrow_reset(ctx->value);
1019 }
1020
1021 static void
1022 xml_skip_pi(struct xml_context *ctx)
1023 {
1024   if (ctx->flags & XML_FLAG_VALIDATING)
1025     {
1026       mp_push(ctx->pool);
1027       if (unlikely(!strcasecmp(xml_parse_name(ctx), "xml")))
1028         xml_fatal(ctx, "Reserved PI target");
1029       mp_pop(ctx->pool);
1030       if (!xml_parse_white(ctx, 0))
1031         {
1032           xml_parse_seq(ctx, "?>");
1033           return;
1034         }
1035     }
1036   while (1)
1037     if (xml_get_char(ctx) == '?')
1038       if (xml_get_char(ctx) == '>')
1039         break;
1040       else
1041         xml_unget_char(ctx);
1042 }
1043
1044 /* Character references */
1045
1046 static uns
1047 xml_parse_char_ref(struct xml_context *ctx)
1048 {
1049   /* CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
1050    * Already parsed: '&#' */
1051   uns v = 0;
1052   if (xml_get_char(ctx) == 'x')
1053     {
1054       if (!(xml_get_cat(ctx) & XML_CHAR_XDIGIT))
1055         {
1056           xml_error(ctx, "Expected a hexadecimal value of character reference");
1057           goto recover;
1058         }
1059       do
1060         {
1061           v = (v << 4) + Cxvalue(xml_last_char(ctx));
1062         }
1063       while (v < 0x110000 && (xml_get_cat(ctx) & XML_CHAR_XDIGIT));
1064     }
1065   else
1066     {
1067       if (!(xml_last_cat(ctx) & XML_CHAR_DIGIT))
1068         {
1069           xml_error(ctx, "Expected a numeric value of character reference");
1070           goto recover;
1071         }
1072       do
1073         {
1074           v = v * 10 + xml_last_char(ctx) - '0';
1075         }
1076       while (v < 0x110000 && (xml_get_cat(ctx) & XML_CHAR_DIGIT));
1077     }
1078   uns cat = xml_char_cat(v);
1079   if (!(cat & XML_CHAR_UNRESTRICTED_1_1) && ((ctx->flags & XML_FLAG_VERSION_1_1) || !(cat & XML_CHAR_VALID_1_0)))
1080     {
1081       xml_error(ctx, "Character reference out of range");
1082       goto recover;
1083     }
1084   if (xml_last_char(ctx) == ';')
1085     return v;
1086   xml_error(ctx, "Expected ';'");
1087 recover:
1088   while (xml_last_char(ctx) != ';')
1089     xml_get_char(ctx);
1090   return UNI_REPLACEMENT;
1091 }
1092
1093 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
1094
1095 static void
1096 xml_parse_parameter_ref(struct xml_context *ctx)
1097 {
1098   char *name = xml_parse_name(ctx);
1099   xml_parse_char(ctx, ';');
1100   struct xml_dtd_ent *ent = xml_dtd_ents_find(ctx->dtd->tab_pents, name);
1101   if (!ent || !(ent->flags & XML_DTD_ENT_DECLARED))
1102     {
1103       xml_error(ctx, "Reference to unknown parameter entity %%%s", name);
1104       return;
1105     }
1106   if (ent->flags & XML_DTD_ENT_VISITED)
1107     {
1108       xml_error(ctx, "Cycled references to parameter entity %%%s", name);
1109       return;
1110     }
1111   if (ent->flags & XML_DTD_ENT_EXTERNAL)
1112     {
1113       // FIXME:
1114       xml_error(ctx, "Support for external parsed entities not implemented");
1115       return;
1116     }
1117   ent->flags |= XML_DTD_ENT_VISITED; // FIXME: clear
1118   struct fastbuf *fb = mp_alloc(ctx->pool, sizeof(*fb));
1119   fbbuf_init_read(fb, ent->text, ent->len, 0);
1120   xml_push_source(ctx, fb, 0);
1121 }
1122
1123 static inline void
1124 xml_check_parameter_ref(struct xml_context *ctx)
1125 {
1126   if (xml_get_char(ctx) != '%')
1127     {
1128       xml_unget_char(ctx);
1129       return;
1130     }
1131   xml_parse_parameter_ref(ctx);
1132 }
1133
1134 static void
1135 xml_parse_external_id(struct xml_context *ctx, struct xml_ext_id *eid, uns allow_public)
1136 {
1137   bzero(eid, sizeof(*eid));
1138   uns c = xml_get_char(ctx);
1139   if (c == 'S')
1140     {
1141       xml_parse_seq(ctx, "YSTEM");
1142       xml_parse_white(ctx, 1);
1143       eid->system_id = xml_parse_system_literal(ctx);
1144     }
1145   else if (c == 'P')
1146     {
1147       xml_parse_seq(ctx, "UBLIC");
1148       xml_parse_white(ctx, 1);
1149       eid->public_id = xml_parse_pubid_literal(ctx);
1150       if (xml_parse_white(ctx, 1))
1151         if ((c = xml_get_char(ctx)) == '\'' || c == '"' || !allow_public)
1152           {
1153             xml_unget_char(ctx);
1154             eid->system_id = xml_parse_system_literal(ctx);
1155           }
1156         else
1157           xml_unget_char(ctx);
1158     }
1159   else
1160     xml_fatal(ctx, "Expected an external ID");
1161 }
1162
1163 static void
1164 xml_parse_notation_decl(struct xml_context *ctx)
1165 {
1166   /* NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'*/
1167   xml_parse_white(ctx, 1);
1168   struct xml_dtd_notn *notn = xml_dtd_notns_lookup(ctx->dtd->tab_notns, xml_parse_name(ctx));
1169   xml_parse_white(ctx, 1);
1170   struct xml_ext_id eid;
1171   xml_parse_external_id(ctx, &eid, 1);
1172   xml_parse_white(ctx, 0);
1173   xml_parse_char(ctx, '>');
1174   if (notn->flags & XML_DTD_NOTN_DECLARED)
1175     xml_warn(ctx, "Notation %s already declared", notn->name);
1176   else
1177     {
1178       notn->flags = XML_DTD_NOTN_DECLARED;
1179       notn->eid = eid;
1180     }
1181 }
1182
1183 static void
1184 xml_parse_internal_subset(struct xml_context *ctx)
1185 {
1186   while (1)
1187     {
1188       xml_parse_white(ctx, 0);
1189       uns c = xml_get_char(ctx);
1190       if (c == '<')
1191         if ((c = xml_get_char(ctx)) == '!')
1192           switch (c = xml_get_char(ctx))
1193             {
1194               case '-':
1195                 xml_push_comment(ctx);
1196                 xml_pop_comment(ctx);
1197                 break;
1198               case 'N':
1199                 xml_parse_seq(ctx, "OTATION");
1200                 xml_parse_notation_decl(ctx);
1201                 break;
1202               case 'E':
1203                 if ((c = xml_get_char(ctx)) == 'N')
1204                   {
1205                     xml_parse_seq(ctx, "TITY");
1206                     //xml_parse_entity_decl(ctx);
1207                   }
1208                 else if (c == 'L')
1209                   {
1210                     xml_parse_seq(ctx, "EMENT");
1211                     // FIXME: Element
1212                   }
1213                 else
1214                   goto invalid_markup;
1215                 break;
1216               case 'A':
1217                 xml_parse_seq(ctx, "TTLIST");
1218                 // FIXME: AttList
1219                 break;
1220               default:
1221                 goto invalid_markup;
1222             }
1223         else if (c == '?')
1224           {
1225             xml_push_pi(ctx);
1226             xml_pop_pi(ctx);
1227           }
1228         else
1229           goto invalid_markup;
1230       else if (c == '%')
1231         xml_parse_parameter_ref(ctx);
1232       else if (c == ']')
1233         break;
1234       else
1235         goto invalid_markup;
1236     }
1237   return;
1238 invalid_markup:
1239   xml_fatal(ctx, "Invalid markup in the internal subset");
1240 }
1241
1242 /*----------------------------------------------*/
1243
1244
1245 /* FIXME */
1246
1247 struct xml_attribute_table;
1248
1249 #define HASH_PREFIX(x) xml_attribute_##x
1250 #define HASH_NODE struct xml_attribute
1251 #define HASH_KEY_COMPLEX(x) x element, x name
1252 #define HASH_KEY_DECL struct xml_element *element, char *name
1253 #define HASH_TABLE_DYNAMIC
1254 #define HASH_AUTO_POOL 1024
1255
1256 #define HASH_GIVE_HASHFN
1257
1258 static inline uns
1259 xml_attribute_hash(struct xml_attribute_table *t UNUSED, struct xml_element *e, char *n)
1260 {
1261   return hash_pointer(e) ^ hash_string(n);
1262 }
1263
1264 #define HASH_GIVE_EQ
1265
1266 static inline int
1267 xml_attribute_eq(struct xml_attribute_table *t UNUSED, struct xml_element *e1, char *n1, struct xml_element *e2, char *n2)
1268 {
1269   return (e1 == e2) && !strcmp(n1, n2);
1270 }
1271
1272 #define HASH_GIVE_INIT_KEY
1273
1274 static inline void
1275 xml_attribute_init_key(struct xml_attribute_table *t UNUSED, struct xml_attribute *a, struct xml_element *e, char *name)
1276 {
1277   a->element = e;
1278   a->name = name;
1279   a->value = NULL;
1280   a->next = e->attrs;
1281   e->attrs = a;
1282 }
1283
1284 #define HASH_WANT_CLEANUP
1285 #define HASH_WANT_REMOVE
1286 #define HASH_WANT_LOOKUP
1287 #define HASH_WANT_FIND
1288 #include "lib/hashtable.h"
1289
1290
1291 /*
1292 #define HASH_PREFIX(x) xml_parsed_entities_##x
1293 #define HASH_NODE struct xml_parsed_entity
1294 #define HASH_KEY_STRING name
1295 #define HASH_TABLE_DYNAMIC
1296 #define HASH_AUTO_POOL 1024
1297 #define HASH_WANT_CLEANUP
1298 #include "lib/hashtable.h"
1299 */
1300
1301 void
1302 xml_init(struct xml_context *ctx)
1303 {
1304   bzero(ctx, sizeof(*ctx));
1305   ctx->pool = mp_new(65536);
1306   ctx->chars = fbgrow_create(4096);
1307   ctx->value = fbgrow_create(4096);
1308   xml_dtd_init(ctx);
1309 }
1310
1311 void
1312 xml_cleanup(struct xml_context *ctx)
1313 {
1314   xml_dtd_cleanup(ctx);
1315   bclose(ctx->value);
1316   bclose(ctx->chars);
1317   mp_delete(ctx->pool);
1318 }
1319
1320 static void
1321 xml_parse_cdata(struct xml_context *ctx)
1322 {
1323   struct fastbuf *out = ctx->chars;
1324   xml_parse_seq(ctx, "CDATA[");
1325   while (1)
1326     {
1327       uns c;
1328       if ((c = xml_get_char(ctx)) == ']')
1329         {
1330           if ((c = xml_get_char(ctx)) == ']')
1331             if ((c = xml_get_char(ctx)) == '>')
1332               break;
1333             else
1334               bputc(out, ']');
1335           bputc(out, ']');
1336         }
1337       bput_utf8_32(out, c);
1338     }
1339 }
1340
1341 static void
1342 xml_skip_cdata(struct xml_context *ctx)
1343 {
1344   xml_parse_cdata(ctx);
1345 }
1346
1347 static void
1348 xml_parse_ref_entity(struct xml_context *ctx UNUSED, struct fastbuf *out UNUSED, struct xml_dtd_ent *entity UNUSED)
1349 {
1350 #if 0
1351   for (struct xml_dtd_ent_node *node = entity->list; node; node = node->next)
1352     if (node->len)
1353       bwrite(out, node->ptr, node->len);
1354     else
1355       xml_parse_ref_entity(ctx, out, node->ptr); // FIXME: do not call the recursion on stack -- could cause segfault
1356 #endif
1357 }
1358
1359 static void
1360 xml_parse_ref(struct xml_context *ctx, struct fastbuf *out)
1361 {
1362   if (xml_get_char(ctx) == '#')
1363     {
1364       uns c = xml_parse_char_ref(ctx);
1365       bput_utf8_32(out, c);
1366     }
1367   else
1368     {
1369 #if 0
1370       xml_unget_char(ctx);
1371       mp_push(ctx->pool);
1372       char *name = xml_parse_name(ctx);
1373       struct xml_parsed_entity *entity = xml_find_parsed_entity(ctx, name);
1374       mp_pop(ctx->pool);
1375       xml_parse_char(ctx, ';');
1376       xml_parse_ref_entity(ctx, out, entity);
1377 #endif
1378     }
1379 }
1380
1381 static void
1382 xml_parse_chars(struct xml_context *ctx)
1383 {
1384   DBG("parse_chars");
1385   struct fastbuf *out = ctx->chars;
1386   uns c;
1387   while ((c = xml_get_char(ctx)) != '<')
1388     if (c == '&')
1389       xml_parse_ref(ctx, out);
1390     else
1391       bput_utf8_32(out, c);
1392   xml_unget_char(ctx);
1393 }
1394
1395 static void
1396 xml_parse_attr(struct xml_context *ctx)
1397 {
1398   DBG("parse_attr");
1399   struct xml_element *e = ctx->element;
1400   char *name = xml_parse_name(ctx);
1401   struct xml_attribute *a = xml_attribute_lookup(ctx->attribute_table, e, name);
1402   if (a->value)
1403     xml_fatal(ctx, "Attribute is not unique");
1404   xml_parse_eq(ctx);
1405   // FIXME
1406   char *value = xml_parse_system_literal(ctx);
1407   a->value = value;
1408 }
1409
1410 static uns
1411 xml_parse_stag(struct xml_context *ctx)
1412 {
1413   DBG("parse_stag");
1414   mp_push(ctx->pool);
1415   struct xml_element *e = mp_alloc_zero(ctx->pool, sizeof(*e));
1416   e->parent = ctx->element;
1417   ctx->element = e;
1418   e->name = xml_parse_name(ctx);
1419   while (1)
1420     {
1421       uns white = xml_parse_white(ctx, 0);
1422       uns c = xml_get_char(ctx);
1423       if (c == '/')
1424         {
1425           xml_parse_char(ctx, '>');
1426           return 1;
1427         }
1428       else if (c == '>')
1429         return 0;
1430       else if (!white)
1431         xml_fatal(ctx, "Expected a white space");
1432       xml_unget_char(ctx);
1433       xml_parse_attr(ctx);
1434     }
1435 }
1436
1437 static void
1438 xml_parse_etag(struct xml_context *ctx)
1439 {
1440   DBG("parse_etag");
1441   struct xml_element *e = ctx->element;
1442   ASSERT(e);
1443   char *name = xml_parse_name(ctx);
1444   if (strcmp(name, e->name))
1445     xml_fatal(ctx, "Invalid ETag, expected '%s'", e->name);
1446   xml_parse_white(ctx, 0);
1447   xml_parse_char(ctx, '>');
1448   // FIXME: remove on pooled hashtable?
1449   for (struct xml_attribute *a = e->attrs; a; a = a->next)
1450     xml_attribute_remove(ctx->attribute_table, a);
1451   ctx->element = e->parent;
1452   mp_pop(ctx->pool);
1453 }
1454
1455 static void
1456 xml_parse_element_decl(struct xml_context *ctx)
1457 {
1458   // FIXME
1459   mp_push(ctx->pool);
1460   xml_parse_seq(ctx, "<!ELEMENT");
1461   xml_parse_white(ctx, 1);
1462   xml_parse_name(ctx);
1463   xml_parse_white(ctx, 1);
1464
1465   uns c = xml_get_char(ctx);
1466   if (c == 'E')
1467     {
1468       xml_parse_seq(ctx, "MPTY");
1469       // FIXME
1470     }
1471   else if (c == 'A')
1472     {
1473       xml_parse_seq(ctx, "NY");
1474       // FIXME
1475     }
1476   else if (c == '(')
1477     {
1478       xml_parse_white(ctx, 0);
1479       if (xml_get_char(ctx) == '#')
1480         {
1481           xml_parse_seq(ctx, "PCDATA");
1482           while (1)
1483             {
1484               xml_parse_white(ctx, 0);
1485               if ((c = xml_get_char(ctx)) == ')')
1486                 break;
1487               else if (c != '|')
1488                 xml_fatal_expected(ctx, ')');
1489               xml_parse_white(ctx, 0);
1490               xml_parse_name(ctx);
1491               // FIXME
1492             }
1493         }
1494       else
1495         {
1496           xml_unget_char(ctx);
1497           uns depth = 1;
1498           while (1)
1499             {
1500               xml_parse_white(ctx, 0);
1501               if ((c = xml_get_char(ctx)) == '(')
1502                 {
1503                   depth++;
1504                 }
1505               else if (c == ')')
1506                 {
1507                   if ((c = xml_get_char(ctx)) == '?' || c == '*' || c == '+')
1508                     {
1509                     }
1510                   else
1511                     xml_unget_char(ctx);
1512                   if (!--depth)
1513                     break;
1514                 }
1515               else if (c == '|')
1516                 {
1517                 }
1518               else if (c == ',')
1519                 {
1520                 }
1521               else
1522                 {
1523                   xml_unget_char(ctx);
1524                   xml_parse_name(ctx);
1525                 }
1526             }
1527         }
1528     }
1529   else
1530     xml_fatal(ctx, "Expected element content specification");
1531
1532   xml_parse_white(ctx, 0);
1533   xml_parse_char(ctx, '>');
1534   mp_pop(ctx->pool);
1535 }
1536
1537 #if 0
1538 static void
1539 xml_parse_attr_list_decl(struct xml_context *ctx)
1540 {
1541   /* AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
1542    * AttDef ::= S Name S AttType S DefaultDecl */
1543   xml_parse_seq(ctx, "ATTLIST");
1544   xml_parse_white(ctx, 1);
1545   struct xml_dtd_elem *e = xml_dtd_elems_lookup(ctx->dtd->tab_elems, xml_parse_name(ctx));
1546   e->attlist_declared = 1;
1547
1548   while (xml_parse_white(ctx, 0) && xml_get_char(ctx) != '>')
1549     {
1550       xml_unget_char(ctx);
1551       char *name = xml_parse_name(ctx);
1552       struct xml_dtd_attr *a = xml_dtd_attrs_find(ctx->dtd->tab_attrs, e, name);
1553       uns ignored = 0;
1554       if (a)
1555         {
1556           xml_warn(ctx, "Duplicate attribute definition");
1557           ignored++;
1558         }
1559       else
1560         a = xml_dtd_attrs_new(ctx->dtd->tab_attrs, e, name);
1561       xml_parse_white(ctx, 1);
1562       if (xml_get_char(ctx) == '(')
1563         {
1564           if (!ignored)
1565             a->type = XML_ATTR_ENUM;
1566           do
1567             {
1568               xml_parse_white(ctx, 0);
1569               char *value = xml_parse_nmtoken(ctx);
1570               if (!ignored)
1571                 if (xml_dtd_evals_find(ctx->dtd->tab_evals, a, value))
1572                   xml_error(ctx, "Duplicate enumeration value");
1573                 else
1574                   xml_dtd_evals_new(ctx->dtd->tab_evals, a, value);
1575               xml_parse_white(ctx, 0);
1576             }
1577           while (xml_get_char(ctx) == '|');
1578           xml_unget_char(ctx);
1579           xml_parse_char(ctx, ')');
1580         }
1581       else
1582         {
1583           xml_unget_char(ctx);
1584           char *type = xml_parse_name(ctx);
1585           enum xml_dtd_attribute_type t;
1586           if (!strcmp(type, "CDATA"))
1587             t = XML_ATTR_CDATA;
1588           else if (!strcmp(type, "ID"))
1589             t = XML_ATTR_ID;
1590           else if (!strcmp(type, "IDREF"))
1591             t = XML_ATTR_IDREF;
1592           else if (!strcmp(type, "IDREFS"))
1593             t = XML_ATTR_IDREFS;
1594           else if (!strcmp(type, "ENTITY"))
1595             t = XML_ATTR_ENTITY;
1596           else if (!strcmp(type, "ENTITIES"))
1597             t = XML_ATTR_ENTITIES;
1598           else if (!strcmp(type, "NMTOKEN"))
1599             t = XML_ATTR_NMTOKEN;
1600           else if (!strcmp(type, "NMTOKENS"))
1601             t = XML_ATTR_NMTOKENS;
1602           else if (!strcmp(type, "NOTATION"))
1603             {
1604               t = XML_ATTR_NOTATION;
1605               xml_parse_white(ctx, 1);
1606               xml_parse_char(ctx, '(');
1607               do
1608                 {
1609                   xml_parse_white(ctx, 0);
1610                   struct xml_dtd_notn *n = xml_dtd_notns_lookup(ctx->dtd->tab_notns, xml_parse_name(ctx));
1611                   if (!ignored)
1612                     if (xml_dtd_enotns_find(ctx->dtd->tab_enotns, a, n))
1613                       xml_error(ctx, "Duplicate enumerated notation");
1614                     else
1615                       xml_dtd_enotns_new(ctx->dtd->tab_enotns, a, n);
1616                   xml_parse_white(ctx, 0);
1617                 }
1618               while (xml_get_char(ctx) == '|');
1619               xml_unget_char(ctx);
1620               xml_parse_char(ctx, ')');
1621             }
1622           else
1623             xml_fatal(ctx, "Unknown attribute type");
1624           if (!ignored)
1625             a->type = t;
1626         }
1627       xml_parse_white(ctx, 1);
1628       enum xml_dtd_attribute_default def = XML_ATTR_NONE;
1629       if (xml_get_char(ctx) == '#')
1630         switch (xml_get_char(ctx))
1631           {
1632             case 'R':
1633               xml_parse_seq(ctx, "EQUIRED");
1634               def = XML_ATTR_REQUIRED;
1635               break;
1636             case 'I':
1637               xml_parse_seq(ctx, "MPLIED");
1638               def = XML_ATTR_IMPLIED;
1639               break;
1640             case 'F':
1641               xml_parse_seq(ctx, "IXED");
1642               def = XML_ATTR_FIXED;
1643               break;
1644             default:
1645               xml_fatal(ctx, "Expected a modifier for default attribute value");
1646           }
1647       else
1648         xml_unget_char(ctx);
1649       if (def != XML_ATTR_REQUIRED && def != XML_ATTR_IMPLIED)
1650         {
1651           xml_parse_system_literal(ctx);
1652           // FIXME
1653         }
1654     }
1655 }
1656 #endif
1657
1658 static void
1659 xml_parse_entity_decl(struct xml_context *ctx)
1660 {
1661   struct xml_dtd *dtd = ctx->dtd;
1662   xml_parse_white(ctx, 1);
1663
1664   uns flags = (xml_get_char(ctx) == '%') ? XML_DTD_ENT_PARAMETER : 0;
1665   if (flags)
1666     xml_parse_white(ctx, 1);
1667   else
1668     xml_unget_char(ctx);
1669
1670   struct xml_dtd_ent *ent = xml_dtd_ents_lookup(flags ? dtd->tab_pents : dtd->tab_gents, xml_parse_name(ctx));
1671   slist *list = flags ? &dtd->pents : &dtd->gents;
1672   xml_parse_white(ctx, 1);
1673   if (ent->flags & XML_DTD_ENT_DECLARED)
1674     {
1675        xml_fatal(ctx, "Entity &%s; already declared, skipping not implemented", ent->name);
1676        // FIXME: should be only warning
1677     }
1678
1679   uns sep = xml_get_char(ctx), c;
1680   if (sep == '\'' || sep == '"')
1681     {
1682       /* Internal entity:
1683        * EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' | "'" ([^%&'] | PEReference | Reference)* "'" */
1684       struct fastbuf *out = ctx->value;
1685       uns sep = c;
1686       while (1)
1687         {
1688           if ((c = xml_get_char(ctx)) == sep)
1689             break;
1690           else if (c == '%')
1691             {
1692               // FIXME
1693               ASSERT(0);
1694               //xml_parse_parameter_ref(ctx);
1695             }
1696           else if (c != '&')
1697             bput_utf8_32(out, c);
1698           else if ((c = xml_get_char(ctx)) == '#')
1699             c = xml_parse_char_ref(ctx);
1700           else
1701             {
1702               /* Bypass references to general entities */
1703               mp_push(ctx->pool);
1704               bputc(out, '&');
1705               xml_unget_char(ctx);
1706               bputs(out, xml_parse_name(ctx));
1707               xml_parse_char(ctx, ';');
1708               bputc(out, ';');
1709               mp_pop(ctx->pool);
1710             }
1711         }
1712       bputc(out, 0);
1713       fbgrow_rewind(out);
1714       slist_add_tail(list, &ent->n);
1715       ent->flags = flags | XML_DTD_ENT_DECLARED;
1716       ent->len = out->bstop - out->bptr - 1;
1717       ent->text = mp_memdup(ctx->pool, out->bptr, ent->len + 1);
1718       fbgrow_reset(out);
1719     }
1720   else
1721     {
1722       /* External entity */
1723       struct xml_ext_id eid;
1724       struct xml_dtd_notn *notn = NULL;
1725       xml_parse_external_id(ctx, &eid, 0);
1726       if (!xml_parse_white(ctx, 0) || !flags)
1727         xml_parse_char(ctx, '>');
1728       else if (xml_get_char(ctx) != '>')
1729         {
1730           /* General external unparsed entity */
1731           flags |= XML_DTD_ENT_UNPARSED;
1732           xml_parse_seq(ctx, "NDATA");
1733           xml_parse_white(ctx, 1);
1734           notn = xml_dtd_notns_lookup(dtd->tab_notns, xml_parse_name(ctx));
1735         }
1736       slist_add_tail(list, &ent->n);
1737       ent->flags = flags | XML_DTD_ENT_DECLARED | XML_DTD_ENT_EXTERNAL;
1738       ent->eid = eid;
1739       ent->notn = notn;
1740     }
1741 }
1742
1743 static void
1744 xml_parse_doctype_decl(struct xml_context *ctx)
1745 {
1746   if (ctx->document_type)
1747     xml_fatal(ctx, "Multiple document types not allowed");
1748   xml_parse_seq(ctx, "DOCTYPE");
1749   xml_parse_white(ctx, 1);
1750   ctx->document_type = xml_parse_name(ctx);
1751   DBG("XML: DocumentType=%s", ctx->document_type);
1752   uns white = xml_parse_white(ctx, 0);
1753   uns c = xml_peek_char(ctx);
1754   if (c != '>' && c != '[' && white)
1755     {
1756       xml_parse_external_id(ctx, &ctx->eid, 0);
1757       xml_parse_white(ctx, 0);
1758     }
1759   if (ctx->h_doctype_decl)
1760     ctx->h_doctype_decl(ctx);
1761 }
1762
1763 int
1764 xml_next(struct xml_context *ctx)
1765 {
1766   /* A nasty state machine */
1767
1768   DBG("XML: xml_next (state=%u)", ctx->state);
1769   jmp_buf throw_buf;
1770   ctx->throw_buf = &throw_buf;
1771   if (setjmp(throw_buf))
1772     {
1773 error:
1774       if (ctx->err_code == XML_ERR_EOF && ctx->h_fatal)
1775         ctx->h_fatal(ctx);
1776       ctx->state = XML_STATE_FATAL;
1777       DBG("XML: raised fatal error");
1778       return -1;
1779     }
1780   uns c;
1781   switch (ctx->state)
1782     {
1783       case XML_STATE_FATAL:
1784         return -1;
1785
1786       case XML_STATE_START:
1787         DBG("XML: Entering Prolog");
1788         if (ctx->h_document_start)
1789           ctx->h_document_start(ctx);
1790         /* XMLDecl */
1791         xml_refill(ctx);
1792         if (ctx->h_xml_decl)
1793           ctx->h_xml_decl(ctx);
1794         if (ctx->want & XML_WANT_DECL)
1795           return ctx->state = XML_STATE_DECL;
1796       case XML_STATE_DECL:
1797
1798         /* Misc* (doctypedecl Misc*)? */
1799         while (1)
1800           {
1801             xml_parse_white(ctx, 0);
1802             xml_parse_char(ctx, '<');
1803             if ((c = xml_get_char(ctx)) == '?')
1804               /* Processing intruction */
1805               if (!(ctx->want & XML_WANT_PI))
1806                 xml_skip_pi(ctx);
1807               else
1808                 {
1809                   xml_push_pi(ctx);
1810                   ctx->state = XML_STATE_PROLOG_PI;
1811                   return XML_STATE_PI;
1812       case XML_STATE_PROLOG_PI:
1813                   xml_pop_pi(ctx);
1814                 }
1815             else if (c != '!')
1816               {
1817                 /* Found the root tag */
1818                 xml_unget_char(ctx);
1819                 goto first_tag;
1820               }
1821             else if (xml_get_char(ctx) == '-')
1822               if (!(ctx->want & XML_WANT_COMMENT))
1823                 xml_skip_comment(ctx);
1824               else
1825                 {
1826                   xml_push_comment(ctx);
1827                   ctx->state = XML_STATE_PROLOG_COMMENT;
1828                   return XML_STATE_COMMENT;
1829       case XML_STATE_PROLOG_COMMENT:
1830                   xml_pop_comment(ctx);
1831                 }
1832             else
1833               {
1834                 /* DocTypeDecl */
1835                 xml_unget_char(ctx);
1836                 xml_parse_doctype_decl(ctx);
1837                 if (ctx->want & XML_WANT_DOCUMENT_TYPE)
1838                   return ctx->state = XML_STATE_DOCUMENT_TYPE;
1839       case XML_STATE_DOCUMENT_TYPE:
1840                 if (xml_peek_char(ctx) == '[')
1841                   {
1842                     xml_skip_char(ctx);
1843                     // FIXME
1844                     while (xml_get_char(ctx) != ']');
1845                     xml_parse_white(ctx, 0);
1846                   }
1847                 xml_parse_char(ctx, '>');
1848               }
1849           }
1850
1851       case XML_STATE_PI:
1852         mp_pop(ctx->pool);
1853       case XML_STATE_COMMENT:
1854         fbgrow_reset(ctx->value);
1855
1856       case XML_STATE_CHARS:
1857
1858         while (1)
1859           {
1860             if (xml_get_char(ctx) != '<')
1861               {
1862                 /* CharData */
1863                 xml_unget_char(ctx);
1864                 xml_parse_chars(ctx);
1865                 continue;
1866               }
1867 first_tag: ;
1868
1869             if ((c = xml_get_char(ctx)) == '?')
1870               {
1871                 /* PI */
1872                 if (!(ctx->want & XML_WANT_PI))
1873                   xml_skip_pi(ctx);
1874                 else
1875                   {
1876                     if (btell(ctx->chars))
1877                       {
1878                         fbgrow_rewind(ctx->chars);
1879                         ctx->state = XML_STATE_CHARS_BEFORE_PI;
1880                         return XML_STATE_PI;
1881       case XML_STATE_CHARS_BEFORE_PI:
1882                         fbgrow_reset(ctx->chars);
1883                       }
1884                     xml_push_pi(ctx);
1885                     return ctx->state = XML_STATE_PI;
1886                   }
1887               }
1888
1889             else if (c == '!')
1890               if ((c = xml_get_char(ctx)) == '-')
1891                 {
1892                   /* Comment */
1893                   if (!(ctx->want & XML_WANT_COMMENT))
1894                     xml_skip_comment(ctx);
1895                   else
1896                     {
1897                       if (btell(ctx->chars))
1898                         {
1899                           fbgrow_rewind(ctx->chars);
1900                           ctx->state = XML_STATE_CHARS_BEFORE_COMMENT;
1901                           return XML_STATE_CHARS;
1902       case XML_STATE_CHARS_BEFORE_COMMENT:
1903                           fbgrow_reset(ctx->chars);
1904                         }
1905                       xml_push_comment(ctx);
1906                       return ctx->state = XML_STATE_COMMENT;
1907                     }
1908                 }
1909               else if (c == '[')
1910                 {
1911                   /* CDATA */
1912                   if (!(ctx->want & XML_WANT_CDATA))
1913                     xml_skip_cdata(ctx);
1914                   else
1915                     {
1916                       if (btell(ctx->chars))
1917                         {
1918                           fbgrow_rewind(ctx->chars);
1919                           ctx->state = XML_STATE_CHARS_BEFORE_CDATA;
1920                           return XML_STATE_CHARS;
1921       case XML_STATE_CHARS_BEFORE_CDATA:
1922                           fbgrow_reset(ctx->chars);
1923                         }
1924                       xml_parse_cdata(ctx);
1925                       if (btell(ctx->chars))
1926                         {
1927                           fbgrow_rewind(ctx->chars);
1928                           return ctx->state = XML_STATE_CDATA;
1929                         }
1930       case XML_STATE_CDATA:
1931                       fbgrow_reset(ctx->chars);
1932                     }
1933                 }
1934               else
1935                 xml_fatal(ctx, "Unexpected character after '<!'");
1936
1937             else if (c != '/')
1938               {
1939                 /* STag | EmptyElemTag */
1940                 xml_unget_char(ctx);
1941                 if (btell(ctx->chars))
1942                   {
1943                     fbgrow_rewind(ctx->chars);
1944                     ctx->state = XML_STATE_CHARS_BEFORE_STAG;
1945                     return XML_STATE_CHARS;
1946       case XML_STATE_CHARS_BEFORE_STAG:
1947                     fbgrow_reset(ctx->chars);
1948                   }
1949
1950                 if (xml_parse_stag(ctx))
1951                   {
1952                   }
1953                 if (ctx->want & XML_WANT_STAG)
1954                   return ctx->state = XML_STATE_STAG;
1955       case XML_STATE_STAG:
1956                 // FIXME: EmptyElemTag
1957                 ;
1958
1959               }
1960
1961             else
1962               {
1963                 /* ETag */
1964                 if (btell(ctx->chars))
1965                   {
1966                     fbgrow_rewind(ctx->chars);
1967                     ctx->state = XML_STATE_CHARS_BEFORE_ETAG;
1968                     return XML_STATE_CHARS;
1969       case XML_STATE_CHARS_BEFORE_ETAG:
1970                     fbgrow_reset(ctx->chars);
1971                   }
1972
1973                 if (ctx->want & XML_WANT_ETAG)
1974                   return ctx->state = XML_STATE_ETAG;
1975       case XML_STATE_ETAG:
1976
1977                 xml_parse_etag(ctx);
1978
1979                 if (!ctx->element)
1980                   goto epilog;
1981               }
1982           }
1983
1984 epilog:
1985         /* Misc* */
1986         DBG("XML: Entering epilog");
1987         while (1)
1988           {
1989             /* Epilog whitespace is the only place, where a valid document can reach EOF */
1990             if (setjmp(throw_buf))
1991               if (ctx->err_code == XML_ERR_EOF)
1992                 {
1993                   DBG("XML: Reached EOF");
1994                   ctx->state = XML_STATE_EOF;
1995                   if (ctx->h_document_end)
1996                     ctx->h_document_end(ctx);
1997       case XML_STATE_EOF:
1998                   return XML_STATE_EOF;
1999                 }
2000               else
2001                 goto error;
2002             xml_parse_white(ctx, 0);
2003             if (setjmp(throw_buf))
2004               goto error;
2005
2006             /* Misc */
2007             xml_parse_char(ctx, '<');
2008             if ((c = xml_get_char(ctx)) == '?')
2009               /* Processing instruction */
2010               if (!(ctx->want & XML_WANT_PI))
2011                 xml_skip_pi(ctx);
2012               else
2013                 {
2014                   xml_push_pi(ctx);
2015                   return ctx->state = XML_STATE_EPILOG_PI, XML_STATE_PI;
2016       case XML_STATE_EPILOG_PI:
2017                   xml_pop_pi(ctx);
2018                 }
2019             else if (c == '!')
2020               /* Comment */
2021               if (!(ctx->want & XML_WANT_COMMENT))
2022                 xml_skip_comment(ctx);
2023               else
2024                 {
2025                   xml_push_comment(ctx);
2026                   return ctx->state = XML_STATE_EPILOG_COMMENT, XML_STATE_COMMENT;
2027       case XML_STATE_EPILOG_COMMENT:
2028                   xml_pop_comment(ctx);
2029                 }
2030             else
2031               xml_fatal(ctx, "Syntax error in the epilog");
2032           }
2033
2034     }
2035   return -1;
2036 }
2037
2038 #ifdef TEST
2039
2040 static void
2041 error(struct xml_context *ctx)
2042 {
2043   msg((ctx->err_code < XML_ERR_ERROR) ? L_WARN_R : L_ERROR_R, "XML: %s", ctx->err_msg);
2044 }
2045
2046 static void
2047 test(struct fastbuf *in, struct fastbuf *out)
2048 {
2049   struct xml_context ctx;
2050   xml_init(&ctx);
2051   ctx.h_warn = ctx.h_error = ctx.h_fatal = error;
2052   ctx.want = XML_WANT_ALL;
2053   xml_set_source(&ctx, in);
2054   int state;
2055   while ((state = xml_next(&ctx)) >= 0)
2056     switch (state)
2057       {
2058         case XML_STATE_CHARS:
2059           bprintf(out, "CHARS [%.*s]\n", (int)(ctx.chars->bstop - ctx.chars->buffer), ctx.chars->buffer);
2060           break;
2061         case XML_STATE_STAG:
2062           bprintf(out, "STAG <%s>\n", ctx.element->name);
2063           for (struct xml_attribute *a = ctx.element->attrs; a; a = a->next)
2064             bprintf(out, "  ATTR %s=[%s]\n", a->name, a->value);
2065           break;
2066         case XML_STATE_ETAG:
2067           bprintf(out, "ETAG </%s>\n", ctx.element->name);
2068           break;
2069         case XML_STATE_COMMENT:
2070           bprintf(out, "COMMENT [%.*s]\n", (int)(ctx.value->bstop - ctx.value->buffer), ctx.value->buffer);
2071           break;
2072         case XML_STATE_PI:
2073           bprintf(out, "PI [%s] [%.*s]\n", ctx.name, (int)(ctx.value->bstop - ctx.value->buffer), ctx.value->buffer);
2074           break;
2075         case XML_STATE_CDATA:
2076           bprintf(out, "CDATA [%.*s]\n", (int)(ctx.chars->bstop - ctx.chars->buffer), ctx.chars->buffer);
2077           break;
2078         case XML_STATE_EOF:
2079           bprintf(out, "EOF\n");
2080           goto end;
2081         default:
2082           bprintf(out, "STATE %u\n", state);
2083           break;
2084       }
2085 end:
2086   xml_cleanup(&ctx);
2087 }
2088
2089 int
2090 main(void)
2091 {
2092   struct fastbuf *in = bfdopen_shared(0, 1024);
2093   struct fastbuf *out = bfdopen_shared(1, 1024);
2094   test(in, out);
2095   bclose(out);
2096   return 0;
2097 }
2098
2099 #endif