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