grabtitle.c - grabtitle - stupid HTML title grabber
HTML git clone git://git.codemadness.org/grabtitle
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
grabtitle.c (4428B)
---
1 #include <errno.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <strings.h>
6
7 #include "xml.h"
8
9 #ifdef __OpenBSD__
10 #include <unistd.h>
11 #else
12 #define pledge(a,b) 0
13 #endif
14
15 /* ctype-like macros, but always compatible with ASCII / UTF-8 */
16 #define ISCNTRL(c) ((c) < ' ' || (c) == 0x7f)
17 #define TOLOWER(c) ((((unsigned)c) - 'A' < 26) ? ((c) | 32) : (c))
18
19 static XMLParser parser;
20
21 static int ignore = 1;
22
23 static char *ignorestate;
24 static char endtagmatch[16];
25 static unsigned char pushbuf[32], *pushoff, *pushend; /* pushback buffer */
26 static unsigned char *pushtagend; /* end position of tag in pushback buffer */
27 static int (*getnext)(void);
28
29 static void
30 getnext_literal_reset(void)
31 {
32 pushoff = pushbuf;
33 pushend = pushbuf;
34 pushtagend = NULL;
35 }
36
37 static void
38 getnext_literal_end(void)
39 {
40 parser.getnext = getnext; /* restore original getnext callback */
41 }
42
43 /* Collect data as literal text, used for certain HTML tags such as <script> or
44 <title> until some case-insensitive string occurs (closing tag).
45 This code is very ugly and I'm not proud of it. Mmmmm spaghetti. */
46 static int
47 getnext_literal(void)
48 {
49 char *p;
50 int c;
51
52 /* check if there is data in the pushback buffer: process it first. */
53 if (pushend != pushbuf) {
54 if (pushoff >= pushend) {
55 /* reached the offset where the tag ended */
56 if (pushtagend) {
57 c = *pushtagend;
58 getnext_literal_end();
59 return c; /* end tag */
60 }
61 /* otherwise reached the end of pushback buffer, proceed normally */
62 getnext_literal_reset();
63 } else {
64 return (int)*(pushoff++);
65 }
66 }
67
68 if (*ignorestate) {
69 for (; *ignorestate; ignorestate++) {
70 if ((c = getnext()) == EOF)
71 return EOF;
72 if (TOLOWER((unsigned char)c) != TOLOWER((unsigned char)*ignorestate))
73 break;
74 }
75 } else {
76 c = EOF; /* cannot happen, endtagmatch should be set (not empty) */
77 }
78
79 /* typical case: regular character */
80 if (ignorestate == endtagmatch)
81 return c;
82
83 /* process a partial or full match on the tag:
84 full: handle as end tag. partial: handle "<" as a data entity "<".
85 buffers are not checked, they always fit (size of tag + space for at least "<<"). */
86 pushoff = pushbuf;
87 pushend = pushbuf;
88 if (*ignorestate == '\0') { /* full match */
89 for (p = endtagmatch; *p && p < ignorestate; p++)
90 *(pushend++) = *p;
91 pushtagend = pushend; /* offset where the tag ended */
92 } else { /* partial match */
93 for (p = endtagmatch; *p && p < ignorestate; p++) {
94 if (*p == '<') {
95 memcpy(pushend, "<", 4);
96 pushend += 4;
97 } else {
98 *(pushend++) = *p;
99 }
100 }
101 /* append current character to end of pushback buffer */
102 if (c == '<') {
103 memcpy(pushend, "<", 4);
104 pushend += 4;
105 } else {
106 *(pushend++) = c;
107 }
108 }
109 ignorestate = endtagmatch; /* reset state for matching to beginning */
110
111 return (int)*(pushoff++); /* first character in pushback buffer, always exists */
112 }
113
114 static void
115 getnext_literal_start(const char *t)
116 {
117 snprintf(endtagmatch, sizeof(endtagmatch), "</%s>", t);
118 ignorestate = endtagmatch;
119 getnext_literal_reset(); /* reset pushback buffer */
120
121 getnext = parser.getnext; /* for restore */
122 parser.getnext = getnext_literal;
123 }
124
125 static void
126 xmltagend(XMLParser *p, const char *t, size_t tl, int isshort)
127 {
128 if (ignore)
129 return;
130
131 putchar('\n');
132 exit(0);
133 }
134
135 /* data and CDATA */
136 static void
137 xmldata(XMLParser *p, const char *d, size_t dl)
138 {
139 size_t i;
140
141 if (ignore)
142 return;
143
144 for (i = 0; i < dl; i++, d++) {
145 if (!ISCNTRL((unsigned char)*d))
146 putchar(*d);
147 }
148 }
149
150 static void
151 xmldataentity(XMLParser *p, const char *d, size_t dl)
152 {
153 char buf[16];
154 ssize_t len;
155
156 if ((len = xml_entitytostr(d, buf, sizeof(buf))) > 0)
157 xmldata(p, buf, (size_t)len);
158 else
159 xmldata(p, d, dl);
160 }
161
162 static void
163 xmltagstartparsed(XMLParser *p, const char *t, size_t tl, int isshort)
164 {
165 if (!strcasecmp(t, "title")) {
166 ignore = 0;
167 } else if (!strcasecmp(t, "script") ||
168 !strcasecmp(t, "style") ||
169 !strcasecmp(t, "template") ||
170 !strcasecmp(t, "textarea")) {
171 } else {
172 return;
173 }
174
175 getnext_literal_start(t);
176 }
177
178 int
179 main(void)
180 {
181 if (pledge("stdio", NULL) == -1) {
182 fprintf(stderr, "pledge: %s\n", strerror(errno));
183 return 2;
184 }
185
186 parser.xmlcdata = xmldata;
187 parser.xmldata = xmldata;
188 parser.xmldataentity = xmldataentity;
189 parser.xmltagend = xmltagend;
190 parser.xmltagstartparsed = xmltagstartparsed;
191 parser.getnext = getchar;
192 xml_parse(&parser);
193
194 return 1;
195 }