]> mj.ucw.cz Git - libucw.git/blob - sherlock/xml/parse.c
Merge with git+ssh://git.ucw.cz/projects/sherlock/GIT/sherlock.git
[libucw.git] / sherlock / xml / parse.c
1 /*
2  *      Sherlock 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 #undef LOCAL_DEBUG
11
12 #include "sherlock/sherlock.h"
13 #include "sherlock/xml/xml.h"
14 #include "sherlock/xml/dtd.h"
15 #include "sherlock/xml/common.h"
16 #include "lib/fastbuf.h"
17 #include "lib/ff-unicode.h"
18 #include "lib/unicode.h"
19 #include "lib/chartype.h"
20 #include "lib/hashfunc.h"
21
22 #include <setjmp.h>
23
24 /*** Basic parsing ***/
25
26 void NONRET
27 xml_fatal_expected(struct xml_context *ctx, uns c)
28 {
29   if (c >= 32 && c < 128)
30     xml_fatal(ctx, "Expected '%c'", c);
31   else
32     xml_fatal(ctx, "Expected U+%04x", c);
33 }
34
35 void NONRET
36 xml_fatal_expected_white(struct xml_context *ctx)
37 {
38   xml_fatal(ctx, "Expected a white space");
39 }
40
41 void NONRET
42 xml_fatal_expected_quot(struct xml_context *ctx)
43 {
44   xml_fatal(ctx, "Expected a quotation mark");
45 }
46
47 void
48 xml_parse_eq(struct xml_context *ctx)
49 {
50   /* Eq ::= S? '=' S? */
51   xml_parse_white(ctx, 0);
52   xml_parse_char(ctx, '=');
53   xml_parse_white(ctx, 0);
54 }
55
56 /*** Names and nmtokens ***/
57
58 static char *
59 xml_parse_string(struct xml_context *ctx, struct mempool *pool, uns first_cat, uns next_cat, char *err)
60 {
61   char *p = mp_start_noalign(pool, 1);
62   if (unlikely(!(xml_peek_cat(ctx) & first_cat)))
63     xml_fatal(ctx, "%s", err);
64   do
65     {
66       p = mp_spread(pool, p, 5);
67       p = utf8_32_put(p, xml_skip_char(ctx));
68     }
69   while (xml_peek_cat(ctx) & next_cat);
70   *p++ = 0;
71   return mp_end(pool, p);
72 }
73
74 static void
75 xml_skip_string(struct xml_context *ctx, uns first_cat, uns next_cat, char *err)
76 {
77   if (unlikely(!(xml_get_cat(ctx) & first_cat)))
78     xml_fatal(ctx, "%s", err);
79   while (xml_peek_cat(ctx) & next_cat)
80     xml_skip_char(ctx);
81 }
82
83 char *
84 xml_parse_name(struct xml_context *ctx, struct mempool *pool)
85 {
86   /* Name ::= NameStartChar (NameChar)* */
87   return xml_parse_string(ctx, pool, ctx->cat_sname, ctx->cat_name, "Expected a name");
88 }
89
90 void
91 xml_skip_name(struct xml_context *ctx)
92 {
93   xml_skip_string(ctx, ctx->cat_sname, ctx->cat_name, "Expected a name");
94 }
95
96 char *
97 xml_parse_nmtoken(struct xml_context *ctx, struct mempool *pool)
98 {
99   /* Nmtoken ::= (NameChar)+ */
100   return xml_parse_string(ctx, pool, ctx->cat_name, ctx->cat_name, "Expected a nmtoken");
101 }
102
103 /*** Simple literals ***/
104
105 char *
106 xml_parse_system_literal(struct xml_context *ctx, struct mempool *pool)
107 {
108   /* SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") */
109   char *p = mp_start_noalign(pool, 1);
110   uns q = xml_parse_quote(ctx), c;
111   while ((c = xml_get_char(ctx)) != q)
112     {
113       p = mp_spread(pool, p, 5);
114       p = utf8_32_put(p, c);
115     }
116   *p++ = 0;
117   return mp_end(pool, p);
118 }
119
120 char *
121 xml_parse_pubid_literal(struct xml_context *ctx, struct mempool *pool)
122 {
123   /* PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" */
124   char *p = mp_start_noalign(pool, 1);
125   uns q = xml_parse_quote(ctx), c;
126   while ((c = xml_get_char(ctx)) != q)
127     {
128       if (unlikely(!(xml_last_cat(ctx) & XML_CHAR_PUBID)))
129         xml_fatal(ctx, "Expected a pubid character");
130       p = mp_spread(pool, p, 2);
131       *p++ = c;
132     }
133   *p++ = 0;
134   return mp_end(pool, p);
135 }
136
137 /*** Comments ***/
138
139 void
140 xml_push_comment(struct xml_context *ctx)
141 {
142   TRACE(ctx, "push_comment");
143   /* Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
144    * Already parsed: '<!-' */
145   xml_parse_char(ctx, '-');
146   struct xml_node *n = xml_push_dom(ctx);
147   n->type = XML_NODE_COMMENT;
148   char *p = mp_start_noalign(ctx->pool, 6);
149   while (1)
150     {
151       if (xml_get_char(ctx) == '-')
152         if (xml_get_char(ctx) == '-')
153           break;
154         else
155           *p++ = '-';
156       p = utf8_32_put(p, xml_last_char(ctx));
157       p = mp_spread(ctx->pool, p, 6);
158     }
159   xml_parse_char(ctx, '>');
160   *p = 0;
161   n->len = p - (char *)mp_ptr(ctx->pool);
162   n->text = mp_end(ctx->pool, p + 1);
163   if ((ctx->flags & XML_REPORT_COMMENTS) && ctx->h_comment)
164     ctx->h_comment(ctx);
165 }
166
167 void
168 xml_pop_comment(struct xml_context *ctx)
169 {
170   xml_pop_dom(ctx, !(ctx->flags & XML_ALLOC_COMMENTS));
171   xml_dec(ctx);
172   TRACE(ctx, "pop_comment");
173 }
174
175 void
176 xml_skip_comment(struct xml_context *ctx)
177 {
178   TRACE(ctx, "skip_comment");
179   xml_parse_char(ctx, '-');
180   while (xml_get_char(ctx) != '-' || xml_get_char(ctx) != '-');
181   xml_parse_char(ctx, '>');
182   xml_dec(ctx);
183 }
184
185 /*** Processing instructions ***/
186
187 void
188 xml_push_pi(struct xml_context *ctx)
189 {
190   TRACE(ctx, "push_pi");
191   /* Parses a PI to ctx->value and ctx->name:
192    *   PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
193    *   PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
194    * Already parsed: '<?' */
195   struct xml_node *n = xml_push_dom(ctx);
196   n->type = XML_NODE_PI;
197   n->name = xml_parse_name(ctx, ctx->pool);
198   if (unlikely(!strcasecmp(n->name, "xml")))
199     xml_error(ctx, "Reserved PI target");
200   char *p = mp_start_noalign(ctx->pool, 5);
201   if (!xml_parse_white(ctx, 0))
202     xml_parse_seq(ctx, "?>");
203   else
204     while (1)
205       {
206         if (xml_get_char(ctx) == '?')
207           if (xml_peek_char(ctx) == '>')
208             {
209               xml_skip_char(ctx);
210               break;
211             }
212           else
213             *p++ = '?';
214         else
215           p = utf8_32_put(p, xml_last_char(ctx));
216         p = mp_spread(ctx->pool, p, 5);
217       }
218   *p = 0;
219   n->len = p - (char *)mp_ptr(ctx->pool);
220   n->text = mp_end(ctx->pool, p + 1);
221   if ((ctx->flags & XML_REPORT_PIS) && ctx->h_pi)
222     ctx->h_pi(ctx);
223 }
224
225 void
226 xml_pop_pi(struct xml_context *ctx)
227 {
228   xml_pop_dom(ctx, !(ctx->flags & XML_ALLOC_PIS));
229   xml_dec(ctx);
230   TRACE(ctx, "pop_pi");
231 }
232
233 void
234 xml_skip_pi(struct xml_context *ctx)
235 {
236   TRACE(ctx, "skip_pi");
237   if (ctx->flags & XML_VALIDATING)
238     {
239       struct mempool_state state;
240       mp_save(ctx->stack, &state);
241       if (unlikely(!strcasecmp(xml_parse_name(ctx, ctx->stack), "xml")))
242         xml_error(ctx, "Reserved PI target");
243       mp_restore(ctx->stack, &state);
244       if (!xml_parse_white(ctx, 0))
245         {
246           xml_parse_seq(ctx, "?>");
247           xml_dec(ctx);
248           return;
249         }
250     }
251   while (1)
252     if (xml_get_char(ctx) == '?')
253       if (xml_peek_char(ctx) == '>')
254         break;
255   xml_skip_char(ctx);
256   xml_dec(ctx);
257 }
258
259 /*** Character data ***/
260
261 static inline uns
262 xml_flush_chars(struct xml_context *ctx)
263 {
264   struct fastbuf *fb = &ctx->chars;
265   if (fb->bufend == fb->buffer)
266     return 0;
267   TRACE(ctx, "flush_chars");
268   struct xml_node *n = ctx->node;
269   n->text = xml_end_chars(ctx, &n->len);
270   n->len = fb->bufend - fb->buffer;
271   if ((ctx->flags & XML_REPORT_CHARS) && ctx->h_chars)
272     ctx->h_chars(ctx);
273   return 1;
274 }
275
276 static inline void
277 xml_pop_chars(struct xml_context *ctx)
278 {
279   xml_pop_dom(ctx, !(ctx->flags & XML_ALLOC_CHARS));
280   TRACE(ctx, "pop_chars");
281 }
282
283 static inline void
284 xml_append_chars(struct xml_context *ctx)
285 {
286   TRACE(ctx, "append_chars");
287   struct fastbuf *out = &ctx->chars;
288   while (xml_get_char(ctx) != '<')
289     if (xml_last_char(ctx) == '&')
290       {
291         xml_inc(ctx);
292         xml_parse_ref(ctx);
293       }
294     else
295       bput_utf8_32(out, xml_last_char(ctx));
296   xml_unget_char(ctx);
297 }
298
299 /*** CDATA sections ***/
300
301 static void
302 xml_push_cdata(struct xml_context *ctx)
303 {
304   TRACE(ctx, "push_cdata");
305   /* CDSect :== '<![CDATA[' (Char* - (Char* ']]>' Char*)) ']]>'
306    * Already parsed: '<![' */
307   xml_parse_seq(ctx, "CDATA[");
308   struct xml_node *n = xml_push_dom(ctx);
309   n->type = XML_NODE_CHARS;
310   char *p = mp_start_noalign(ctx->pool, 7);
311   while (1)
312     {
313       if (xml_get_char(ctx) == ']')
314         {
315           if (xml_get_char(ctx) == ']')
316             if (xml_get_char(ctx) == '>')
317               break;
318             else
319               *p++ = ']';
320           *p++ = ']';
321         }
322       p = utf8_32_put(p, xml_last_char(ctx));
323       p = mp_spread(ctx->pool, p, 7);
324     }
325   *p = 0;
326   n->len = p - (char *)mp_ptr(ctx->pool);
327   n->text = mp_end(ctx->pool, p + 1);
328   if ((ctx->flags & XML_REPORT_CHARS) && ctx->h_cdata)
329     ctx->h_cdata(ctx);
330 }
331
332 static void
333 xml_pop_cdata(struct xml_context *ctx)
334 {
335   xml_pop_dom(ctx, !(ctx->flags & XML_ALLOC_CHARS));
336   xml_dec(ctx);
337   TRACE(ctx, "pop_cdata");
338 }
339
340 static void
341 xml_append_cdata(struct xml_context *ctx)
342 {
343   TRACE(ctx, "append_cdata");
344   xml_parse_seq(ctx, "CDATA[");
345   struct fastbuf *out = &ctx->chars;
346   while (1)
347     {
348       if (xml_get_char(ctx) == ']')
349         {
350           if (xml_get_char(ctx) == ']')
351             if (xml_get_char(ctx) == '>')
352               break;
353             else
354               bputc(out, ']');
355           bputc(out, ']');
356         }
357       bput_utf8_32(out, xml_last_char(ctx));
358     }
359   xml_dec(ctx);
360 }
361
362 static void UNUSED
363 xml_skip_cdata(struct xml_context *ctx)
364 {
365   TRACE(ctx, "skip_cdata");
366   xml_parse_seq(ctx, "CDATA[");
367   while (xml_get_char(ctx) != ']' || xml_get_char(ctx) != ']' || xml_get_char(ctx) != '>');
368   xml_dec(ctx);
369 }
370
371 /*** Character references ***/
372
373 uns
374 xml_parse_char_ref(struct xml_context *ctx)
375 {
376   TRACE(ctx, "parse_char_ref");
377   /* CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
378    * Already parsed: '&#' */
379   uns v = 0;
380   if (xml_get_char(ctx) == 'x')
381     {
382       if (!(xml_get_cat(ctx) & XML_CHAR_XDIGIT))
383         {
384           xml_error(ctx, "Expected a hexadecimal value of character reference");
385           goto recover;
386         }
387       do
388         {
389           v = (v << 4) + Cxvalue(xml_last_char(ctx));
390         }
391       while (v < 0x110000 && (xml_get_cat(ctx) & XML_CHAR_XDIGIT));
392     }
393   else
394     {
395       if (!(xml_last_cat(ctx) & XML_CHAR_DIGIT))
396         {
397           xml_error(ctx, "Expected a numeric value of character reference");
398           goto recover;
399         }
400       do
401         {
402           v = v * 10 + xml_last_char(ctx) - '0';
403         }
404       while (v < 0x110000 && (xml_get_cat(ctx) & XML_CHAR_DIGIT));
405     }
406   uns cat = xml_char_cat(v);
407   if (!(cat & ctx->cat_unrestricted))
408     {
409       xml_error(ctx, "Character reference out of range");
410       goto recover;
411     }
412   if (xml_last_char(ctx) == ';')
413     {
414       xml_dec(ctx);
415       return v;
416     }
417   xml_error(ctx, "Expected ';'");
418 recover:
419   while (xml_last_char(ctx) != ';')
420     xml_get_char(ctx);
421   xml_dec(ctx);
422   return UNI_REPLACEMENT;
423 }
424
425 /*** References to general entities ***/
426
427 void
428 xml_parse_ref(struct xml_context *ctx)
429 {
430   /* Reference ::= EntityRef | CharRef
431    * EntityRef ::= '&' Name ';'
432    * Already parsed: '&' */
433   struct fastbuf *out = &ctx->chars;
434   if (xml_peek_char(ctx) == '#')
435     {
436       xml_skip_char(ctx);
437       bput_utf8_32(out, xml_parse_char_ref(ctx));
438     }
439   else
440     {
441       TRACE(ctx, "parse_ge_ref");
442       struct mempool_state state;
443       mp_save(ctx->stack, &state);
444       char *name = xml_parse_name(ctx, ctx->stack);
445       xml_parse_char(ctx, ';');
446       struct xml_dtd_ent *ent = xml_dtd_find_ent(ctx, name);
447       if (!ent)
448         {
449           xml_error(ctx, "Unknown entity &%s;", name);
450           bputc(out, '&');
451           bputs(out, name);
452           bputc(out, ';');
453         }
454       else if (ent->flags & XML_DTD_ENT_TRIVIAL_UNI)
455         {
456           TRACE(ctx, "Trivial entity &%s;", name);
457           bput_utf8_32(out, ent->uni);
458         }
459       else if (ent->flags & XML_DTD_ENT_TRIVIAL_STR)
460         {
461           TRACE(ctx, "Trivial entity &%s;", name);
462           bwrite(out, ent->text, ent->len);
463         }
464       else
465         {
466           TRACE(ctx, "Pushed entity &%s;", name);
467           mp_restore(ctx->stack, &state);
468           xml_dec(ctx);
469           xml_push_entity(ctx, ent);
470           return;
471         }
472       mp_restore(ctx->stack, &state);
473       xml_dec(ctx);
474     }
475 }
476
477 /*** Attribute values ***/
478
479 char *
480 xml_parse_attr_value(struct xml_context *ctx, struct xml_dtd_attr *attr UNUSED)
481 {
482   TRACE(ctx, "parse_attr_value");
483   /* AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" */
484   /* FIXME:
485    * -- copying from ctx->chars to ctx->pool is not necessary, we could directly write to ctx->pool
486    * -- berare quotes inside parased entities
487    * -- check value constrains / normalize value */
488   struct mempool_state state;
489   uns quote = xml_parse_quote(ctx);
490   mp_save(ctx->stack, &state);
491   xml_start_chars(ctx);
492   struct fastbuf *out = &ctx->chars;
493   while (1)
494     {
495       uns c = xml_get_char(ctx);
496       if (c == '&')
497         {
498           xml_inc(ctx);
499           xml_parse_ref(ctx);
500         }
501       else if (c == quote) // FIXME: beware quotes inside parsed entities
502         break;
503       else if (c == '<')
504         xml_error(ctx, "Attribute value must not contain '<'");
505       else if (xml_last_cat(ctx) & XML_CHAR_WHITE)
506         bputc(out, ' ');
507       else
508         bput_utf8_32(out, c);
509     }
510   mp_restore(ctx->stack, &state);
511   uns len;
512   return xml_end_chars(ctx, &len);
513 }
514
515 /*** Attributes ***/
516
517 struct xml_attrs_table;
518
519 static inline uns
520 xml_attrs_hash(struct xml_attrs_table *t UNUSED, struct xml_node *e, char *n)
521 {
522   return hash_pointer(e) ^ hash_string(n);
523 }
524
525 static inline int
526 xml_attrs_eq(struct xml_attrs_table *t UNUSED, struct xml_node *e1, char *n1, struct xml_node *e2, char *n2)
527 {
528   return (e1 == e2) && !strcmp(n1, n2);
529 }
530
531 static inline void
532 xml_attrs_init_key(struct xml_attrs_table *t UNUSED, struct xml_attr *a, struct xml_node *e, char *name)
533 {
534   a->elem = e;
535   a->name = name;
536   a->val = NULL;
537   a->user = NULL;
538   slist_add_tail(&e->attrs, &a->n);
539 }
540
541 #define HASH_PREFIX(x) xml_attrs_##x
542 #define HASH_NODE struct xml_attr
543 #define HASH_KEY_COMPLEX(x) x elem, x name
544 #define HASH_KEY_DECL struct xml_node *elem, char *name
545 #define HASH_TABLE_DYNAMIC
546 #define HASH_GIVE_EQ
547 #define HASH_GIVE_HASHFN
548 #define HASH_GIVE_INIT_KEY
549 #define HASH_WANT_CLEANUP
550 #define HASH_WANT_REMOVE
551 #define HASH_WANT_LOOKUP
552 #define HASH_WANT_FIND
553 #define HASH_GIVE_ALLOC
554 XML_HASH_GIVE_ALLOC
555 #include "lib/hashtable.h"
556
557 static void
558 xml_parse_attr(struct xml_context *ctx)
559 {
560   TRACE(ctx, "parse_attr");
561   /* Attribute ::= Name Eq AttValue */
562   /* FIXME:
563    * -- memory management
564    * -- DTD */
565   struct xml_node *e = ctx->node;
566   char *n = xml_parse_name(ctx, ctx->pool);
567   struct xml_attr *a = xml_attrs_lookup(ctx->tab_attrs, e, n);
568   xml_parse_eq(ctx);
569   char *v = xml_parse_attr_value(ctx, NULL);
570   if (a->val)
571     xml_error(ctx, "Attribute %s is not unique", n);
572   else
573     a->val = v;
574 }
575
576 struct xml_attr *
577 xml_attr_find(struct xml_context *ctx, struct xml_node *node, char *name)
578 {
579   return xml_attrs_find(ctx->tab_attrs, node, name);
580 }
581
582 void
583 xml_attrs_table_init(struct xml_context *ctx)
584 {
585   xml_attrs_init(ctx->tab_attrs = xml_hash_new(ctx->pool, sizeof(struct xml_attrs_table)));
586 }
587
588 void
589 xml_attrs_table_cleanup(struct xml_context *ctx)
590 {
591   xml_attrs_cleanup(ctx->tab_attrs);
592 }
593
594 /*** Elements ***/
595
596 static void
597 xml_push_element(struct xml_context *ctx)
598 {
599   TRACE(ctx, "push_element");
600   /* EmptyElemTag | STag
601    * EmptyElemTag ::= '<' Name (S  Attribute)* S? '/>'
602    * STag ::= '<' Name (S  Attribute)* S? '>'
603    * Already parsed: '<' */
604   struct xml_node *e = xml_push_dom(ctx);
605   clist_init(&e->sons);
606   e->type = XML_NODE_ELEM;
607   e->name = xml_parse_name(ctx, ctx->pool);
608   slist_init(&e->attrs);
609   if (!e->parent)
610     {
611       ctx->dom = e;
612       if (ctx->doctype && strcmp(e->name, ctx->doctype))
613         xml_error(ctx, "The root element %s does not match the document type %s", e->name, ctx->doctype);
614     }
615   while (1)
616     {
617       uns white = xml_parse_white(ctx, 0);
618       uns c = xml_get_char(ctx);
619       if (c == '/')
620         {
621           xml_parse_char(ctx, '>');
622           ctx->flags |= XML_EMPTY_ELEM_TAG;
623           break;
624         }
625       else if (c == '>')
626         break;
627       else if (!white)
628         xml_fatal_expected_white(ctx);
629       xml_unget_char(ctx);
630       xml_parse_attr(ctx);
631     }
632   if ((ctx->flags & XML_REPORT_TAGS) && ctx->h_stag)
633     ctx->h_stag(ctx);
634 }
635
636 static void
637 xml_pop_element(struct xml_context *ctx)
638 {
639   TRACE(ctx, "pop_element");
640   if ((ctx->flags & XML_REPORT_TAGS) && ctx->h_etag)
641     ctx->h_etag(ctx);
642   struct xml_node *e = ctx->node;
643   uns free = !(ctx->flags & XML_ALLOC_TAGS);
644   if (free)
645     {
646       if (!e->parent)
647         ctx->dom = NULL;
648       /* Restore hash table of attributes */
649       SLIST_FOR_EACH(struct xml_attr *, a, e->attrs)
650         xml_attrs_remove(ctx->tab_attrs, a);
651       struct xml_node *n;
652       while (n = clist_head(&e->sons))
653         {
654           if (n->type == XML_NODE_ELEM)
655             {
656               SLIST_FOR_EACH(struct xml_attr *, a, n->attrs)
657                 xml_attrs_remove(ctx->tab_attrs, a);
658               clist_insert_list_after(&n->sons, &n->n);
659             }
660           clist_remove(&n->n);
661         }
662     }
663   xml_pop_dom(ctx, free);
664   xml_dec(ctx);
665 }
666
667 static void
668 xml_parse_etag(struct xml_context *ctx)
669 {
670  /* ETag ::= '</' Name S? '>'
671   * Already parsed: '<' */
672   struct xml_node *e = ctx->node;
673   ASSERT(e);
674   char *n = e->name;
675   while (*n)
676     {
677       uns c;
678       n = utf8_32_get(n, &c);
679       if (xml_get_char(ctx) != c)
680         goto recover;
681     }
682   xml_parse_white(ctx, 0);
683   if (xml_get_char(ctx) != '>')
684     {
685 recover:
686       xml_error(ctx, "Invalid ETag, expected </%s>", e->name);
687       while (xml_get_char(ctx) != '>');
688     }
689   xml_dec(ctx);
690 }
691
692 /*** Document type declaration ***/
693
694 static void
695 xml_parse_doctype_decl(struct xml_context *ctx)
696 {
697   TRACE(ctx, "parse_doctype_decl");
698   /* doctypedecl ::= '<!DOCTYPE' S  Name (S  ExternalID)? S? ('[' intSubset ']' S?)? '>'
699    * Already parsed: '<!'
700    * Terminated before '[' or '>' */
701   if (ctx->doctype)
702     xml_fatal(ctx, "Multiple document types not allowed");
703   xml_parse_seq(ctx, "DOCTYPE");
704   xml_parse_white(ctx, 1);
705   ctx->doctype = xml_parse_name(ctx, ctx->pool);
706   TRACE(ctx, "doctype=%s", ctx->doctype);
707   uns c;
708   if (xml_parse_white(ctx, 0) && ((c = xml_peek_char(ctx)) == 'S' || c == 'P'))
709     {
710       if (c == 'S')
711         {
712           xml_parse_seq(ctx, "SYSTEM");
713           xml_parse_white(ctx, 1);
714           ctx->system_id = xml_parse_system_literal(ctx, ctx->pool);
715         }
716       else
717         {
718           xml_parse_seq(ctx, "PUBLIC");
719           xml_parse_white(ctx, 1);
720           ctx->public_id = xml_parse_pubid_literal(ctx, ctx->pool);
721           xml_parse_white(ctx, 1);
722           ctx->system_id = xml_parse_system_literal(ctx, ctx->pool);
723         }
724       xml_parse_white(ctx, 0);
725       ctx->flags |= XML_HAS_EXTERNAL_SUBSET;
726     }
727   if (xml_peek_char(ctx) == '[')
728     ctx->flags |= XML_HAS_INTERNAL_SUBSET;
729   if (ctx->h_doctype_decl)
730     ctx->h_doctype_decl(ctx);
731 }
732
733
734
735 ///////////////////////////////////////////////////////////////////////////////////////////////////////////
736
737 /* DTD: Internal subset */
738
739 static void
740 xml_parse_internal_subset(struct xml_context *ctx)
741 {
742   // FIXME: comments/pi have no parent
743   /* '[' intSubset ']'
744    * intSubset :== (markupdecl | DeclSep)
745    * Already parsed: ']' */
746   while (1)
747     {
748       xml_parse_white(ctx, 0);
749       uns c = xml_get_char(ctx);
750       xml_inc(ctx);
751       if (c == '<')
752         if ((c = xml_get_char(ctx)) == '!')
753           switch (c = xml_get_char(ctx))
754             {
755               case '-':
756                 xml_push_comment(ctx);
757                 xml_pop_comment(ctx);
758                 break;
759               case 'N':
760                 xml_parse_seq(ctx, "OTATION");
761                 xml_parse_notation_decl(ctx);
762                 break;
763               case 'E':
764                 if ((c = xml_get_char(ctx)) == 'N')
765                   {
766                     xml_parse_seq(ctx, "TITY");
767                     xml_parse_entity_decl(ctx);
768                   }
769                 else if (c == 'L')
770                   {
771                     xml_parse_seq(ctx, "EMENT");
772                     xml_parse_element_decl(ctx);
773                   }
774                 else
775                   goto invalid_markup;
776                 break;
777               case 'A':
778                 xml_parse_seq(ctx, "TTLIST");
779                 xml_parse_attr_list_decl(ctx);
780                 break;
781               default:
782                 goto invalid_markup;
783             }
784         else if (c == '?')
785           {
786             xml_push_pi(ctx);
787             xml_pop_pi(ctx);
788           }
789         else
790           goto invalid_markup;
791       else if (c == '%')
792         xml_parse_pe_ref(ctx);
793       else if (c == ']')
794         break;
795       else
796         goto invalid_markup;
797     }
798   xml_dec(ctx);
799   xml_dec(ctx);
800   return;
801 invalid_markup:
802   xml_fatal(ctx, "Invalid markup in the internal subset");
803 }
804
805 /*** The State Machine ***/
806
807 uns
808 xml_next(struct xml_context *ctx)
809 {
810   /* A nasty state machine */
811
812 #define PULL(x) do { if (ctx->pull & XML_PULL_##x) return ctx->state = XML_STATE_##x; case XML_STATE_##x: ; } while (0)
813 #define PULL_STATE(x, s) do { if (ctx->pull & XML_PULL_##x) return ctx->state = XML_STATE_##s, XML_STATE_##x; case XML_STATE_##s: ; } while (0)
814
815   TRACE(ctx, "xml_next (state=%u)", ctx->state);
816   jmp_buf throw_buf;
817   ctx->throw_buf = &throw_buf;
818   if (setjmp(throw_buf))
819     {
820 error:
821       if (ctx->err_code == XML_ERR_EOF && ctx->h_fatal)
822         ctx->h_fatal(ctx);
823       TRACE(ctx, "raised fatal error");
824       return ctx->state = XML_STATE_EOF;
825     }
826   uns c;
827   switch (ctx->state)
828     {
829       case XML_STATE_START:
830         TRACE(ctx, "entering prolog");
831         if (ctx->h_document_start)
832           ctx->h_document_start(ctx);
833         /* XMLDecl */
834         xml_refill(ctx);
835         if (ctx->h_xml_decl)
836           ctx->h_xml_decl(ctx);
837         PULL(XML_DECL);
838
839         /* Misc* (doctypedecl Misc*)? */
840         while (1)
841           {
842             xml_parse_white(ctx, 0);
843             xml_parse_char(ctx, '<');
844             xml_inc(ctx);
845             if ((c = xml_get_char(ctx)) == '?')
846               /* Processing intruction */
847               if (!(ctx->flags & XML_REPORT_PIS))
848                 xml_skip_pi(ctx);
849               else
850                 {
851                   xml_push_pi(ctx);
852                   PULL_STATE(PI, PROLOG_PI);
853                   xml_pop_pi(ctx);
854                 }
855             else if (c != '!')
856               {
857                 /* Found the root tag */
858                 xml_unget_char(ctx);
859                 goto first_tag;
860               }
861             else if (xml_get_char(ctx) == '-')
862               if (!(ctx->flags & XML_REPORT_COMMENTS))
863                 xml_skip_comment(ctx);
864               else
865                 {
866                   xml_push_comment(ctx);
867                   PULL_STATE(COMMENT, PROLOG_COMMENT);
868                   xml_pop_comment(ctx);
869                 }
870             else
871               {
872                 /* DocTypeDecl */
873                 xml_unget_char(ctx);
874                 xml_parse_doctype_decl(ctx);
875                 PULL(DOCTYPE_DECL);
876                 if (xml_peek_char(ctx) == '[')
877                   {
878                     xml_skip_char(ctx);
879                     xml_inc(ctx);
880                     if (ctx->flags & XML_PARSE_DTD)
881                       {
882                         xml_dtd_init(ctx);
883                         if (ctx->h_dtd_start)
884                           ctx->h_dtd_start(ctx);
885                         // FIXME: pull iface?
886                         xml_parse_internal_subset(ctx);
887                         // FIXME: external subset
888                         if (ctx->h_dtd_end)
889                           ctx->h_dtd_end(ctx);
890                       }
891                     else
892                       xml_skip_internal_subset(ctx);
893                   }
894                 xml_parse_white(ctx, 0);
895                 xml_parse_char(ctx, '>');
896                 xml_dec(ctx);
897               }
898           }
899
900       case XML_STATE_CHARS:
901
902         while (1)
903           {
904             if (xml_peek_char(ctx) != '<')
905               {
906                 /* CharData */
907                 xml_append_chars(ctx);
908                 continue;
909               }
910             else
911               xml_skip_char(ctx);
912             xml_inc(ctx);
913 first_tag:
914
915             if ((c = xml_get_char(ctx)) == '?')
916               {
917                 /* PI */
918                 if (!(ctx->flags & (XML_REPORT_PIS | XML_ALLOC_PIS)))
919                   xml_skip_pi(ctx);
920                 else
921                   {
922                     if (xml_flush_chars(ctx))
923                       {
924                         PULL_STATE(CHARS, CHARS_BEFORE_PI);
925                         xml_pop_chars(ctx);
926                       }
927                     xml_push_pi(ctx);
928                     PULL(PI);
929                     xml_pop_pi(ctx);
930                   }
931               }
932
933             else if (c == '!')
934               if ((c = xml_get_char(ctx)) == '-')
935                 {
936                   /* Comment */
937                   if (!(ctx->flags & (XML_REPORT_COMMENTS | XML_ALLOC_COMMENTS)))
938                     xml_skip_comment(ctx);
939                   else
940                     {
941                       if (xml_flush_chars(ctx))
942                         {
943                           PULL_STATE(CHARS, CHARS_BEFORE_COMMENT);
944                           xml_pop_chars(ctx);
945                         }
946                       xml_push_comment(ctx);
947                       PULL(COMMENT);
948                       xml_pop_comment(ctx);
949                     }
950                 }
951               else if (c == '[')
952                 {
953                   /* CDATA */
954                   if (!(ctx->flags & XML_UNFOLD_CDATA))
955                     xml_append_cdata(ctx);
956                   else
957                     {
958                       if (xml_flush_chars(ctx))
959                         {
960                           PULL_STATE(CHARS, CHARS_BEFORE_CDATA);
961                           xml_pop_chars(ctx);
962                         }
963                       xml_push_cdata(ctx);
964                       PULL(CDATA);
965                       xml_pop_cdata(ctx);
966                     }
967                 }
968               else
969                 xml_fatal(ctx, "Unexpected character after '<!'");
970
971             else if (c != '/')
972               {
973                 /* STag | EmptyElemTag */
974                 xml_unget_char(ctx);
975                 if (xml_flush_chars(ctx))
976                   {
977                     PULL_STATE(CHARS, CHARS_BEFORE_STAG);
978                     xml_pop_chars(ctx);
979                   }
980
981                 xml_push_element(ctx);
982                 PULL(STAG);
983                 if (ctx->flags & XML_EMPTY_ELEM_TAG)
984                   goto pop_element;
985               }
986
987             else
988               {
989                 /* ETag */
990                 if (xml_flush_chars(ctx))
991                   {
992                     PULL_STATE(CHARS, CHARS_BEFORE_ETAG);
993                     xml_pop_chars(ctx);
994                   }
995
996                 xml_parse_etag(ctx);
997 pop_element:
998                 PULL(ETAG);
999                 xml_pop_element(ctx);
1000                 if (!ctx->node)
1001                   goto epilog;
1002               }
1003           }
1004
1005 epilog:
1006         /* Misc* */
1007         TRACE(ctx, "entering epilog");
1008         while (1)
1009           {
1010             /* Epilog whitespace is the only place, where a valid document can reach EOF */
1011             if (setjmp(throw_buf))
1012               if (ctx->err_code == XML_ERR_EOF)
1013                 {
1014                   TRACE(ctx, "reached EOF");
1015                   ctx->state = XML_STATE_EOF;
1016                   if (ctx->h_document_end)
1017                     ctx->h_document_end(ctx);
1018       case XML_STATE_EOF:
1019                   ctx->err_code = 0;
1020                   ctx->err_msg = NULL;
1021                   return XML_STATE_EOF;
1022                 }
1023               else
1024                 goto error;
1025             xml_parse_white(ctx, 0);
1026             if (setjmp(throw_buf))
1027               goto error;
1028
1029             /* Misc */
1030             xml_parse_char(ctx, '<');
1031             xml_inc(ctx);
1032             if ((c = xml_get_char(ctx)) == '?')
1033               /* Processing instruction */
1034               if (!(ctx->flags & XML_REPORT_PIS))
1035                 xml_skip_pi(ctx);
1036               else
1037                 {
1038                   xml_push_pi(ctx);
1039                   PULL_STATE(PI, EPILOG_PI);
1040                   xml_pop_pi(ctx);
1041                 }
1042             else if (c == '!')
1043               {
1044                 xml_parse_char(ctx, '-');
1045                 /* Comment */
1046                 if (!(ctx->flags & XML_REPORT_COMMENTS))
1047                   xml_skip_comment(ctx);
1048                 else
1049                   {
1050                     xml_push_comment(ctx);
1051                     PULL_STATE(COMMENT, EPILOG_COMMENT);
1052                     xml_pop_comment(ctx);
1053                   }
1054               }
1055             else
1056               xml_fatal(ctx, "Syntax error in the epilog");
1057           }
1058
1059     }
1060   ASSERT(0);
1061 }
1062
1063 uns
1064 xml_parse(struct xml_context *ctx)
1065 {
1066   /* This cycle shoud run only once unless the user overrides the value of ctx->pull in a SAX handler */
1067   do
1068     {
1069       ctx->pull = 0;
1070     }
1071   while (xml_next(ctx));
1072   return ctx->err_code;
1073 }