+/* Test whether n+1-sized entity name fits in P. We don't support
+ IE-style non-terminated entities, e.g. "<foo" -> "<foo".
+ However, "<foo" will work, as will "<!foo", "<", etc. In
+ other words an entity needs to be terminated by either a
+ non-alphanumeric or the end of string. */
+#define FITS(p, n) (p + n == end || (p + n < end && !c_isalnum (p[n])))
+
+/* Macros that test entity names by returning true if P is followed by
+ the specified characters. */
+#define ENT1(p, c0) (FITS (p, 1) && p[0] == c0)
+#define ENT2(p, c0, c1) (FITS (p, 2) && p[0] == c0 && p[1] == c1)
+#define ENT3(p, c0, c1, c2) (FITS (p, 3) && p[0]==c0 && p[1]==c1 && p[2]==c2)
+
+/* Increment P by INC chars. If P lands at a semicolon, increment it
+ past the semicolon. This ensures that e.g. "<foo" is converted
+ to "<foo", but "<,foo" to "<,foo". */
+#define SKIP_SEMI(p, inc) (p += inc, p < end && *p == ';' ? ++p : p)
+
+struct tagstack_item {
+ const char *tagname_begin;
+ const char *tagname_end;
+ const char *contents_begin;
+ struct tagstack_item *prev;
+ struct tagstack_item *next;
+};
+
+struct tagstack_item *
+tagstack_push (struct tagstack_item **head, struct tagstack_item **tail)
+{
+ struct tagstack_item *ts = xmalloc(sizeof(struct tagstack_item));
+ if (*head == NULL)
+ {
+ *head = *tail = ts;
+ ts->prev = ts->next = NULL;
+ }
+ else
+ {
+ (*tail)->next = ts;
+ ts->prev = *tail;
+ *tail = ts;
+ ts->next = NULL;
+ }
+
+ return ts;
+}
+
+/* remove ts and everything after it from the stack */
+void
+tagstack_pop (struct tagstack_item **head, struct tagstack_item **tail,
+ struct tagstack_item *ts)
+{
+ if (*head == NULL)
+ return;
+
+ if (ts == *tail)
+ {
+ if (ts == *head)
+ {
+ xfree (ts);
+ *head = *tail = NULL;
+ }
+ else
+ {
+ ts->prev->next = NULL;
+ *tail = ts->prev;
+ xfree (ts);
+ }
+ }
+ else
+ {
+ if (ts == *head)
+ {
+ *head = NULL;
+ }
+ *tail = ts->prev;
+
+ if (ts->prev)
+ {
+ ts->prev->next = NULL;
+ }
+ while (ts)
+ {
+ struct tagstack_item *p = ts->next;
+ xfree (ts);
+ ts = p;
+ }
+ }
+}
+
+struct tagstack_item *
+tagstack_find (struct tagstack_item *tail, const char *tagname_begin,
+ const char *tagname_end)
+{
+ int len = tagname_end - tagname_begin;
+ while (tail)
+ {
+ if (len == (tail->tagname_end - tail->tagname_begin))
+ {
+ if (0 == strncasecmp (tail->tagname_begin, tagname_begin, len))
+ return tail;
+ }
+ tail = tail->prev;
+ }
+ return NULL;
+}
+
+/* Decode the HTML character entity at *PTR, considering END to be end
+ of buffer. It is assumed that the "&" character that marks the
+ beginning of the entity has been seen at *PTR-1. If a recognized
+ ASCII entity is seen, it is returned, and *PTR is moved to the end
+ of the entity. Otherwise, -1 is returned and *PTR left unmodified.
+
+ The recognized entities are: <, >, &, &apos, and ". */
+
+static int
+decode_entity (const char **ptr, const char *end)
+{
+ const char *p = *ptr;
+ int value = -1;
+
+ if (++p == end)
+ return -1;
+
+ switch (*p++)
+ {
+ case '#':
+ /* Process numeric entities "&#DDD;" and "&#xHH;". */
+ {
+ int digits = 0;
+ value = 0;
+ if (*p == 'x')
+ for (++p; value < 256 && p < end && c_isxdigit (*p); p++, digits++)
+ value = (value << 4) + XDIGIT_TO_NUM (*p);
+ else
+ for (; value < 256 && p < end && c_isdigit (*p); p++, digits++)
+ value = (value * 10) + (*p - '0');
+ if (!digits)
+ return -1;
+ /* Don't interpret 128+ codes and NUL because we cannot
+ portably reinserted them into HTML. */
+ if (!value || (value & ~0x7f))
+ return -1;
+ *ptr = SKIP_SEMI (p, 0);
+ return value;
+ }
+ /* Process named ASCII entities. */
+ case 'g':
+ if (ENT1 (p, 't'))
+ value = '>', *ptr = SKIP_SEMI (p, 1);
+ break;
+ case 'l':
+ if (ENT1 (p, 't'))
+ value = '<', *ptr = SKIP_SEMI (p, 1);
+ break;
+ case 'a':
+ if (ENT2 (p, 'm', 'p'))
+ value = '&', *ptr = SKIP_SEMI (p, 2);
+ else if (ENT3 (p, 'p', 'o', 's'))
+ /* handle &apos for the sake of the XML/XHTML crowd. */
+ value = '\'', *ptr = SKIP_SEMI (p, 3);
+ break;
+ case 'q':
+ if (ENT3 (p, 'u', 'o', 't'))
+ value = '\"', *ptr = SKIP_SEMI (p, 3);
+ break;
+ }
+ return value;
+}
+#undef ENT1
+#undef ENT2
+#undef ENT3
+#undef FITS
+#undef SKIP_SEMI
+
+enum {
+ AP_DOWNCASE = 1,
+ AP_DECODE_ENTITIES = 2,
+ AP_TRIM_BLANKS = 4
+};