]> sjero.net Git - wget/blob - src/html-parse.c
[svn] Use 0x22 instead of '"' or '\"'.
[wget] / src / html-parse.c
1 /* HTML parser for Wget.
2    Copyright (C) 1998, 2000 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 /* The only entry point to this module is map_html_tags(), which see.  */
21
22 /* TODO:
23
24    - Allow hooks for callers to process contents outside tags.  This
25      is needed to implement handling <style> and <script>.  The
26      taginfo structure already carries the information about where the
27      tags are, but this is not enough, because one would also want to
28      skip the comments.  (The funny thing is that for <style> and
29      <script> you *don't* want to skip comments!)
30
31    - Create a test suite for regression testing. */
32
33 /* HISTORY:
34
35    This is the third HTML parser written for Wget.  The first one was
36    written some time during the Geturl 1.0 beta cycle, and was very
37    inefficient and buggy.  It also contained some very complex code to
38    remember a list of parser states, because it was supposed to be
39    reentrant.  The idea was that several parsers would be running
40    concurrently, and you'd have pass the function a unique ID string
41    (for example, the URL) by which it found the relevant parser state
42    and returned the next URL.  Over-engineering at its best.
43
44    The second HTML parser was written for Wget 1.4 (the first version
45    by the name `Wget'), and was a complete rewrite.  Although the new
46    parser behaved much better and made no claims of reentrancy, it
47    still shared many of the fundamental flaws of the old version -- it
48    only regarded HTML in terms tag-attribute pairs, where the
49    attribute's value was a URL to be returned.  Any other property of
50    HTML, such as <base href=...>, or strange way to specify a URL,
51    such as <meta http-equiv=Refresh content="0; URL=..."> had to be
52    crudely hacked in -- and the caller had to be aware of these hacks.
53    Like its predecessor, this parser did not support HTML comments.
54
55    After Wget 1.5.1 was released, I set out to write a third HTML
56    parser.  The objectives of the new parser were to: (1) provide a
57    clean way to analyze HTML lexically, (2) separate interpretation of
58    the markup from the parsing process, (3) be as correct as possible,
59    e.g. correctly skipping comments and other SGML declarations, (4)
60    understand the most common errors in markup and skip them or be
61    relaxed towrds them, and (5) be reasonably efficient (no regexps,
62    minimum copying and minimum or no heap allocation).
63
64    I believe this parser meets all of the above goals.  It is
65    reasonably well structured, and could be relatively easily
66    separated from Wget and used elsewhere.  While some of its
67    intrinsic properties limit its value as a general-purpose HTML
68    parser, I believe that, with minimum modifications, it could serve
69    as a backend for one.
70
71    Due to time and other constraints, this parser was not integrated
72    into Wget until the version 1.7. */
73
74 /* DESCRIPTION:
75
76    The single entry point of this parser is map_html_tags(), which
77    works by calling a function you specify for each tag.  The function
78    gets called with the pointer to a structure describing the tag and
79    its attributes.  */
80
81 /* To test as standalone, compile with `-DSTANDALONE -I.'.  You'll
82    still need Wget headers to compile.  */
83
84 #include <config.h>
85
86 #include <stdio.h>
87 #include <stdlib.h>
88 #ifdef HAVE_STRING_H
89 # include <string.h>
90 #else
91 # include <strings.h>
92 #endif
93 #include <assert.h>
94
95 #include "wget.h"
96 #include "html-parse.h"
97
98 #ifdef STANDALONE
99 # define xmalloc malloc
100 # define xrealloc realloc
101 # define xfree free
102 #endif /* STANDALONE */
103
104 /* Pool support.  For efficiency, map_html_tags() stores temporary
105    string data to a single stack-allocated pool.  If the pool proves
106    too small, additional memory is allocated/resized with
107    malloc()/realloc().  */
108
109 struct pool {
110   char *contents;               /* pointer to the contents. */
111   int size;                     /* size of the pool. */
112   int index;                    /* next unoccupied position in
113                                    contents. */
114
115   int alloca_p;                 /* whether contents was allocated
116                                    using alloca(). */
117   char *orig_contents;          /* orig_contents, allocated by
118                                    alloca().  this is used by
119                                    POOL_FREE to restore the pool to
120                                    the "initial" state. */
121   int orig_size;
122 };
123
124 /* Initialize the pool to hold INITIAL_SIZE bytes of storage. */
125
126 #define POOL_INIT(pool, initial_size) do {              \
127   (pool).size = (initial_size);                         \
128   (pool).contents = ALLOCA_ARRAY (char, (pool).size);   \
129   (pool).index = 0;                                     \
130   (pool).alloca_p = 1;                                  \
131   (pool).orig_contents = (pool).contents;               \
132   (pool).orig_size = (pool).size;                       \
133 } while (0)
134
135 /* Grow the pool to accomodate at least SIZE new bytes.  If the pool
136    already has room to accomodate SIZE bytes of data, this is a no-op.  */
137
138 #define POOL_GROW(pool, increase) do {                                  \
139   int PG_newsize = (pool).index + increase;                             \
140   DO_REALLOC_FROM_ALLOCA ((pool).contents, (pool).size, PG_newsize,     \
141                           (pool).alloca_p, char);                       \
142 } while (0)
143
144 /* Append text in the range [beg, end) to POOL.  No zero-termination
145    is done.  */
146
147 #define POOL_APPEND(pool, beg, end) do {                        \
148   const char *PA_beg = beg;                                     \
149   int PA_size = end - PA_beg;                                   \
150   POOL_GROW (pool, PA_size);                                    \
151   memcpy ((pool).contents + (pool).index, PA_beg, PA_size);     \
152   (pool).index += PA_size;                                      \
153 } while (0)
154
155 /* The same as the above, but with zero termination. */
156
157 #define POOL_APPEND_ZT(pool, beg, end) do {                     \
158   const char *PA_beg = beg;                                     \
159   int PA_size = end - PA_beg;                                   \
160   POOL_GROW (pool, PA_size + 1);                                \
161   memcpy ((pool).contents + (pool).index, PA_beg, PA_size);     \
162   (pool).contents[(pool).index + PA_size] = '\0';               \
163   (pool).index += PA_size + 1;                                  \
164 } while (0)
165
166 /* Forget old pool contents.  The allocated memory is not freed. */
167 #define POOL_REWIND(pool) pool.index = 0
168
169 /* Free heap-allocated memory for contents of POOL.  This calls
170    xfree() if the memory was allocated through malloc.  It also
171    restores `contents' and `size' to their original, pre-malloc
172    values.  That way after POOL_FREE, the pool is fully usable, just
173    as if it were freshly initialized with POOL_INIT.  */
174
175 #define POOL_FREE(pool) do {                    \
176   if (!(pool).alloca_p)                         \
177     xfree ((pool).contents);                    \
178   (pool).contents = (pool).orig_contents;       \
179   (pool).size = (pool).orig_size;               \
180   (pool).index = 0;                             \
181   (pool).alloca_p = 1;                          \
182 } while (0)
183
184 \f
185 #define AP_DOWNCASE             1
186 #define AP_PROCESS_ENTITIES     2
187 #define AP_SKIP_BLANKS          4
188
189 /* Copy the text in the range [BEG, END) to POOL, optionally
190    performing operations specified by FLAGS.  FLAGS may be any
191    combination of AP_DOWNCASE, AP_PROCESS_ENTITIES and AP_SKIP_BLANKS
192    with the following meaning:
193
194    * AP_DOWNCASE -- downcase all the letters;
195
196    * AP_PROCESS_ENTITIES -- process the SGML entities and write out
197    the decoded string.  Recognized entities are &lt, &gt, &amp, &quot,
198    &nbsp and the numerical entities.
199
200    * AP_SKIP_BLANKS -- ignore blanks at the beginning and at the end
201    of text.  */
202 static void
203 convert_and_copy (struct pool *pool, const char *beg, const char *end, int flags)
204 {
205   int old_index = pool->index;
206   int size;
207
208   /* First, skip blanks if required.  We must do this before entities
209      are processed, so that blanks can still be inserted as, for
210      instance, `&#32;'.  */
211   if (flags & AP_SKIP_BLANKS)
212     {
213       while (beg < end && ISSPACE (*beg))
214         ++beg;
215       while (end > beg && ISSPACE (end[-1]))
216         --end;
217     }
218   size = end - beg;
219
220   if (flags & AP_PROCESS_ENTITIES)
221     {
222       /* Stack-allocate a copy of text, process entities and copy it
223          to the pool.  */
224       char *local_copy = (char *)alloca (size + 1);
225       const char *from = beg;
226       char *to = local_copy;
227
228       while (from < end)
229         {
230           if (*from != '&')
231             *to++ = *from++;
232           else
233             {
234               const char *save = from;
235               int remain;
236
237               if (++from == end) goto lose;
238               remain = end - from;
239
240               if (*from == '#')
241                 {
242                   int numeric;
243                   ++from;
244                   if (from == end || !ISDIGIT (*from)) goto lose;
245                   for (numeric = 0; from < end && ISDIGIT (*from); from++)
246                     numeric = 10 * numeric + (*from) - '0';
247                   if (from < end && ISALPHA (*from)) goto lose;
248                   numeric &= 0xff;
249                   *to++ = numeric;
250                 }
251 #define FROB(x) (remain >= (sizeof (x) - 1)                     \
252                  && !memcmp (from, x, sizeof (x) - 1)           \
253                  && (*(from + sizeof (x) - 1) == ';'            \
254                      || remain == sizeof (x) - 1                \
255                      || !ISALNUM (*(from + sizeof (x) - 1))))
256               else if (FROB ("lt"))
257                 *to++ = '<', from += 2;
258               else if (FROB ("gt"))
259                 *to++ = '>', from += 2;
260               else if (FROB ("amp"))
261                 *to++ = '&', from += 3;
262               else if (FROB ("quot"))
263                 *to++ = '\"', from += 4;
264               /* We don't implement the proposed "Added Latin 1"
265                  entities (except for nbsp), because it is unnecessary
266                  in the context of Wget, and would require hashing to
267                  work efficiently.  */
268               else if (FROB ("nbsp"))
269                 *to++ = 160, from += 4;
270               else
271                 goto lose;
272 #undef FROB
273               /* If the entity was followed by `;', we step over the
274                  `;'.  Otherwise, it was followed by either a
275                  non-alphanumeric or EOB, in which case we do nothing.  */
276               if (from < end && *from == ';')
277                 ++from;
278               continue;
279
280             lose:
281               /* This was not an entity after all.  Back out.  */
282               from = save;
283               *to++ = *from++;
284             }
285         }
286       *to++ = '\0';
287       POOL_APPEND (*pool, local_copy, to);
288     }
289   else
290     {
291       /* Just copy the text to the pool.  */
292       POOL_APPEND_ZT (*pool, beg, end);
293     }
294
295   if (flags & AP_DOWNCASE)
296     {
297       char *p = pool->contents + old_index;
298       for (; *p; p++)
299         *p = TOLOWER (*p);
300     }
301 }
302 \f
303 /* Check whether the contents of [POS, POS+LENGTH) match any of the
304    strings in the ARRAY.  */
305 static int
306 array_allowed (const char **array, const char *beg, const char *end)
307 {
308   int length = end - beg;
309   if (array)
310     {
311       for (; *array; array++)
312         if (length >= strlen (*array)
313             && !strncasecmp (*array, beg, length))
314           break;
315       if (!*array)
316         return 0;
317     }
318   return 1;
319 }
320 \f
321 /* RFC1866: name [of attribute or tag] consists of letters, digits,
322    periods, or hyphens.  We also allow _, for compatibility with
323    brain-damaged generators.  */
324 #define NAME_CHAR_P(x) (ISALNUM (x) || (x) == '.' || (x) == '-' || (x) == '_')
325
326 /* States while advancing through comments. */
327 #define AC_S_DONE       0
328 #define AC_S_BACKOUT    1
329 #define AC_S_BANG       2
330 #define AC_S_DEFAULT    3
331 #define AC_S_DCLNAME    4
332 #define AC_S_DASH1      5
333 #define AC_S_DASH2      6
334 #define AC_S_COMMENT    7
335 #define AC_S_DASH3      8
336 #define AC_S_DASH4      9
337 #define AC_S_QUOTE1     10
338 #define AC_S_IN_QUOTE   11
339 #define AC_S_QUOTE2     12
340
341 #ifdef STANDALONE
342 static int comment_backout_count;
343 #endif
344
345 /* Advance over an SGML declaration (the <!...> forms you find in HTML
346    documents).  The function returns the location after the
347    declaration.  The reason we need this is that HTML comments are
348    expressed as comments in so-called "empty declarations".
349
350    To recap: any SGML declaration may have comments associated with
351    it, e.g.
352        <!MY-DECL -- isn't this fun? -- foo bar>
353
354    An HTML comment is merely an empty declaration (<!>) with a comment
355    attached, like this:
356        <!-- some stuff here -->
357
358    Several comments may be embedded in one comment declaration:
359        <!-- have -- -- fun -->
360
361    Whitespace is allowed between and after the comments, but not
362    before the first comment.
363
364    Additionally, this function attempts to handle double quotes in
365    SGML declarations correctly.  */
366 static const char *
367 advance_declaration (const char *beg, const char *end)
368 {
369   const char *p = beg;
370   char quote_char = '\0';       /* shut up, gcc! */
371   char ch;
372   int state = AC_S_BANG;
373
374   if (beg == end)
375     return beg;
376   ch = *p++;
377
378   /* It looked like a good idea to write this as a state machine, but
379      now I wonder...  */
380
381   while (state != AC_S_DONE && state != AC_S_BACKOUT)
382     {
383       if (p == end)
384         state = AC_S_BACKOUT;
385       switch (state)
386         {
387         case AC_S_DONE:
388         case AC_S_BACKOUT:
389           break;
390         case AC_S_BANG:
391           if (ch == '!')
392             {
393               ch = *p++;
394               state = AC_S_DEFAULT;
395             }
396           else
397             state = AC_S_BACKOUT;
398           break;
399         case AC_S_DEFAULT:
400           switch (ch)
401             {
402             case '-':
403               state = AC_S_DASH1;
404               break;
405             case ' ':
406             case '\t':
407             case '\r':
408             case '\n':
409               ch = *p++;
410               break;
411             case '>':
412               state = AC_S_DONE;
413               break;
414             case '\'':
415             case '\"':
416               state = AC_S_QUOTE1;
417               break;
418             default:
419               if (NAME_CHAR_P (ch))
420                 state = AC_S_DCLNAME;
421               else
422                 state = AC_S_BACKOUT;
423               break;
424             }
425           break;
426         case AC_S_DCLNAME:
427           if (NAME_CHAR_P (ch))
428             ch = *p++;
429           else if (ch == '-')
430             state = AC_S_DASH1;
431           else
432             state = AC_S_DEFAULT;
433           break;
434         case AC_S_QUOTE1:
435           /* We must use 0x22 because broken assert macros choke on
436              '"' and '\"'.  */
437           assert (ch == '\'' || ch == 0x22);
438           quote_char = ch;      /* cheating -- I really don't feel like
439                                    introducing more different states for
440                                    different quote characters. */
441           ch = *p++;
442           state = AC_S_IN_QUOTE;
443           break;
444         case AC_S_IN_QUOTE:
445           if (ch == quote_char)
446             state = AC_S_QUOTE2;
447           else
448             ch = *p++;
449           break;
450         case AC_S_QUOTE2:
451           assert (ch == quote_char);
452           ch = *p++;
453           state = AC_S_DEFAULT;
454           break;
455         case AC_S_DASH1:
456           assert (ch == '-');
457           ch = *p++;
458           state = AC_S_DASH2;
459           break;
460         case AC_S_DASH2:
461           switch (ch)
462             {
463             case '-':
464               ch = *p++;
465               state = AC_S_COMMENT;
466               break;
467             default:
468               state = AC_S_BACKOUT;
469             }
470           break;
471         case AC_S_COMMENT:
472           switch (ch)
473             {
474             case '-':
475               state = AC_S_DASH3;
476               break;
477             default:
478               ch = *p++;
479               break;
480             }
481           break;
482         case AC_S_DASH3:
483           assert (ch == '-');
484           ch = *p++;
485           state = AC_S_DASH4;
486           break;
487         case AC_S_DASH4:
488           switch (ch)
489             {
490             case '-':
491               ch = *p++;
492               state = AC_S_DEFAULT;
493               break;
494             default:
495               state = AC_S_COMMENT;
496               break;
497             }
498           break;
499         }
500     }
501
502   if (state == AC_S_BACKOUT)
503     {
504 #ifdef STANDALONE
505       ++comment_backout_count;
506 #endif
507       return beg + 1;
508     }
509   return p;
510 }
511 \f
512 /* Advance P (a char pointer), with the explicit intent of being able
513    to read the next character.  If this is not possible, go to finish.  */
514
515 #define ADVANCE(p) do {                         \
516   ++p;                                          \
517   if (p >= end)                                 \
518     goto finish;                                \
519 } while (0)
520
521 /* Skip whitespace, if any. */
522
523 #define SKIP_WS(p) do {                         \
524   while (ISSPACE (*p)) {                        \
525     ADVANCE (p);                                \
526   }                                             \
527 } while (0)
528
529 /* Skip non-whitespace, if any. */
530
531 #define SKIP_NON_WS(p) do {                     \
532   while (!ISSPACE (*p)) {                       \
533     ADVANCE (p);                                \
534   }                                             \
535 } while (0)
536
537 #ifdef STANDALONE
538 static int tag_backout_count;
539 #endif
540
541 /* Map MAPFUN over HTML tags in TEXT, which is SIZE characters long.
542    MAPFUN will be called with two arguments: pointer to an initialized
543    struct taginfo, and CLOSURE.
544
545    ALLOWED_TAG_NAMES should be a NULL-terminated array of tag names to
546    be processed by this function.  If it is NULL, all the tags are
547    allowed.  The same goes for attributes and ALLOWED_ATTRIBUTE_NAMES.
548
549    (Obviously, the caller can filter out unwanted tags and attributes
550    just as well, but this is just an optimization designed to avoid
551    unnecessary copying for tags/attributes which the caller doesn't
552    want to know about.  These lists are searched linearly; therefore,
553    if you're interested in a large number of tags or attributes, you'd
554    better set these to NULL and filter them out yourself with a
555    hashing process most appropriate for your application.)  */
556
557 void
558 map_html_tags (const char *text, int size,
559                const char **allowed_tag_names,
560                const char **allowed_attribute_names,
561                void (*mapfun) (struct taginfo *, void *),
562                void *closure)
563 {
564   const char *p = text;
565   const char *end = text + size;
566
567   int attr_pair_count = 8;
568   int attr_pair_alloca_p = 1;
569   struct attr_pair *pairs = ALLOCA_ARRAY (struct attr_pair, attr_pair_count);
570   struct pool pool;
571
572   if (!size)
573     return;
574
575   POOL_INIT (pool, 256);
576
577   {
578     int nattrs, end_tag;
579     const char *tag_name_begin, *tag_name_end;
580     const char *tag_start_position;
581     int uninteresting_tag;
582
583   look_for_tag:
584     POOL_REWIND (pool);
585
586     nattrs = 0;
587     end_tag = 0;
588
589     /* Find beginning of tag.  We use memchr() instead of the usual
590        looping with ADVANCE() for speed. */
591     p = memchr (p, '<', end - p);
592     if (!p)
593       goto finish;
594
595     tag_start_position = p;
596     ADVANCE (p);
597
598     /* Establish the type of the tag (start-tag, end-tag or
599        declaration).  */
600     if (*p == '!')
601       {
602         /* This is an SGML declaration -- just skip it.  */
603         p = advance_declaration (p, end);
604         if (p == end)
605           goto finish;
606         goto look_for_tag;
607       }
608     else if (*p == '/')
609       {
610         end_tag = 1;
611         ADVANCE (p);
612       }
613     tag_name_begin = p;
614     while (NAME_CHAR_P (*p))
615       ADVANCE (p);
616     if (p == tag_name_begin)
617       goto look_for_tag;
618     tag_name_end = p;
619     SKIP_WS (p);
620     if (end_tag && *p != '>')
621       goto backout_tag;
622
623     if (!array_allowed (allowed_tag_names, tag_name_begin, tag_name_end))
624       /* We can't just say "goto look_for_tag" here because we need
625          the loop below to properly advance over the tag's attributes.  */
626       uninteresting_tag = 1;
627     else
628       {
629         uninteresting_tag = 0;
630         convert_and_copy (&pool, tag_name_begin, tag_name_end, AP_DOWNCASE);
631       }
632
633     /* Find the attributes. */
634     while (1)
635       {
636         const char *attr_name_begin, *attr_name_end;
637         const char *attr_value_begin, *attr_value_end;
638         const char *attr_raw_value_begin, *attr_raw_value_end;
639         int operation = AP_DOWNCASE; /* stupid compiler. */
640
641         SKIP_WS (p);
642
643         if (*p == '/')
644           {
645             /* A slash at this point means the tag is about to be
646                closed.  This is legal in XML and has been popularized
647                in HTML via XHTML.  */
648             /* <foo a=b c=d /> */
649             /*              ^  */
650             ADVANCE (p);
651             SKIP_WS (p);
652             if (*p != '>')
653               goto backout_tag;
654           }
655
656         /* Check for end of tag definition. */
657         if (*p == '>')
658           break;
659
660         /* Establish bounds of attribute name. */
661         attr_name_begin = p;    /* <foo bar ...> */
662                                 /*      ^        */
663         while (NAME_CHAR_P (*p))
664           ADVANCE (p);
665         attr_name_end = p;      /* <foo bar ...> */
666                                 /*         ^     */
667         if (attr_name_begin == attr_name_end)
668           goto backout_tag;
669
670         /* Establish bounds of attribute value. */
671         SKIP_WS (p);
672         if (NAME_CHAR_P (*p) || *p == '/' || *p == '>')
673           {
674             /* Minimized attribute syntax allows `=' to be omitted.
675                For example, <UL COMPACT> is a valid shorthand for <UL
676                COMPACT="compact">.  Even if such attributes are not
677                useful to Wget, we need to support them, so that the
678                tags containing them can be parsed correctly. */
679             attr_raw_value_begin = attr_value_begin = attr_name_begin;
680             attr_raw_value_end = attr_value_end = attr_name_end;
681           }
682         else if (*p == '=')
683           {
684             ADVANCE (p);
685             SKIP_WS (p);
686             if (*p == '\"' || *p == '\'')
687               {
688                 int newline_seen = 0;
689                 char quote_char = *p;
690                 attr_raw_value_begin = p;
691                 ADVANCE (p);
692                 attr_value_begin = p; /* <foo bar="baz"> */
693                                       /*           ^     */
694                 while (*p != quote_char)
695                   {
696                     if (!newline_seen && *p == '\n')
697                       {
698                         /* If a newline is seen within the quotes, it
699                            is most likely that someone forgot to close
700                            the quote.  In that case, we back out to
701                            the value beginning, and terminate the tag
702                            at either `>' or the delimiter, whichever
703                            comes first.  Such a tag terminated at `>'
704                            is discarded.  */
705                         p = attr_value_begin;
706                         newline_seen = 1;
707                         continue;
708                       }
709                     else if (newline_seen && *p == '>')
710                       break;
711                     ADVANCE (p);
712                   }
713                 attr_value_end = p; /* <foo bar="baz"> */
714                                     /*              ^  */
715                 if (*p == quote_char)
716                   ADVANCE (p);
717                 else
718                   goto look_for_tag;
719                 attr_raw_value_end = p; /* <foo bar="baz"> */
720                                         /*               ^ */
721                 /* The AP_SKIP_BLANKS part is not entirely correct,
722                    because we don't want to skip blanks for all the
723                    attribute values.  */
724                 operation = AP_PROCESS_ENTITIES | AP_SKIP_BLANKS;
725               }
726             else
727               {
728                 attr_value_begin = p; /* <foo bar=baz> */
729                                       /*          ^    */
730                 /* According to SGML, a name token should consist only
731                    of alphanumerics, . and -.  However, this is often
732                    violated by, for instance, `%' in `width=75%'.
733                    We'll be liberal and allow just about anything as
734                    an attribute value.  */
735                 while (!ISSPACE (*p) && *p != '>')
736                   ADVANCE (p);
737                 attr_value_end = p; /* <foo bar=baz qux=quix> */
738                                     /*             ^          */
739                 if (attr_value_begin == attr_value_end)
740                   /* <foo bar=> */
741                   /*          ^ */
742                   goto backout_tag;
743                 attr_raw_value_begin = attr_value_begin;
744                 attr_raw_value_end = attr_value_end;
745                 operation = AP_PROCESS_ENTITIES;
746               }
747           }
748         else
749           {
750             /* We skipped the whitespace and found something that is
751                neither `=' nor the beginning of the next attribute's
752                name.  Back out.  */
753             goto backout_tag;   /* <foo bar [... */
754                                 /*          ^    */
755           }
756
757         /* If we're not interested in the tag, don't bother with any
758            of the attributes.  */
759         if (uninteresting_tag)
760           continue;
761
762         /* If we aren't interested in the attribute, skip it.  We
763            cannot do this test any sooner, because our text pointer
764            needs to correctly advance over the attribute.  */
765         if (allowed_attribute_names
766             && !array_allowed (allowed_attribute_names, attr_name_begin,
767                                attr_name_end))
768           continue;
769
770         DO_REALLOC_FROM_ALLOCA (pairs, attr_pair_count, nattrs + 1,
771                                 attr_pair_alloca_p, struct attr_pair);
772
773         pairs[nattrs].name_pool_index = pool.index;
774         convert_and_copy (&pool, attr_name_begin, attr_name_end, AP_DOWNCASE);
775
776         pairs[nattrs].value_pool_index = pool.index;
777         convert_and_copy (&pool, attr_value_begin, attr_value_end, operation);
778         pairs[nattrs].value_raw_beginning = attr_raw_value_begin;
779         pairs[nattrs].value_raw_size = (attr_raw_value_end
780                                         - attr_raw_value_begin);
781         ++nattrs;
782       }
783
784     if (uninteresting_tag)
785       {
786         ADVANCE (p);
787         goto look_for_tag;
788       }
789
790     /* By now, we have a valid tag with a name and zero or more
791        attributes.  Fill in the data and call the mapper function.  */
792     {
793       int i;
794       struct taginfo taginfo;
795
796       taginfo.name      = pool.contents;
797       taginfo.end_tag_p = end_tag;
798       taginfo.nattrs    = nattrs;
799       /* We fill in the char pointers only now, when pool can no
800          longer get realloc'ed.  If we did that above, we could get
801          hosed by reallocation.  Obviously, after this point, the pool
802          may no longer be grown.  */
803       for (i = 0; i < nattrs; i++)
804         {
805           pairs[i].name = pool.contents + pairs[i].name_pool_index;
806           pairs[i].value = pool.contents + pairs[i].value_pool_index;
807         }
808       taginfo.attrs = pairs;
809       taginfo.start_position = tag_start_position;
810       taginfo.end_position   = p + 1;
811       /* Ta-dam! */
812       (*mapfun) (&taginfo, closure);
813       ADVANCE (p);
814     }
815     goto look_for_tag;
816
817   backout_tag:
818 #ifdef STANDALONE
819     ++tag_backout_count;
820 #endif
821     /* The tag wasn't really a tag.  Treat its contents as ordinary
822        data characters. */
823     p = tag_start_position + 1;
824     goto look_for_tag;
825   }
826
827  finish:
828   POOL_FREE (pool);
829   if (!attr_pair_alloca_p)
830     xfree (pairs);
831 }
832
833 #undef ADVANCE
834 #undef SKIP_WS
835 #undef SKIP_NON_WS
836 \f
837 #ifdef STANDALONE
838 static void
839 test_mapper (struct taginfo *taginfo, void *arg)
840 {
841   int i;
842
843   printf ("%s%s", taginfo->end_tag_p ? "/" : "", taginfo->name);
844   for (i = 0; i < taginfo->nattrs; i++)
845     printf (" %s=%s", taginfo->attrs[i].name, taginfo->attrs[i].value);
846   putchar ('\n');
847   ++*(int *)arg;
848 }
849
850 int main ()
851 {
852   int size = 256;
853   char *x = (char *)xmalloc (size);
854   int length = 0;
855   int read_count;
856   int tag_counter = 0;
857
858   while ((read_count = fread (x + length, 1, size - length, stdin)))
859     {
860       length += read_count;
861       size <<= 1;
862       x = (char *)xrealloc (x, size);
863     }
864
865   map_html_tags (x, length, NULL, NULL, test_mapper, &tag_counter);
866   printf ("TAGS: %d\n", tag_counter);
867   printf ("Tag backouts:     %d\n", tag_backout_count);
868   printf ("Comment backouts: %d\n", comment_backout_count);
869   return 0;
870 }
871 #endif /* STANDALONE */