HTMLParser more forgiving with bad attributes
[metaproxy-moved-to-github.git] / src / html_parser.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2013 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include "html_parser.hpp"
21
22 #include <assert.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <stdio.h>
27
28 #define SPACECHR " \t\r\n\f"
29
30
31 namespace metaproxy_1 {
32     class HTMLParser::Rep {
33         friend class HTMLParser;
34     public:
35         void parse_str(HTMLParserEvent &event, const char *cp);
36         void tagText(HTMLParserEvent &event,
37                      const char *text_start, const char *text_end);
38         int tagEnd(HTMLParserEvent &event,
39                    const char *tag, int tag_len, const char *cp);
40         int tagStart(HTMLParserEvent &event,
41                      int *tag_len, const char *cp, const char which);
42         int tagAttrs(HTMLParserEvent &event,
43                      const char *name, int len,
44                      const char *cp);
45         int skipAttribute(HTMLParserEvent &event,
46                           const char *cp, int *attr_len,
47                           const char **value, int *val_len, int *tr);
48         Rep();
49         ~Rep();
50         int m_verbose;
51     };
52 }
53
54 namespace mp = metaproxy_1;
55
56 mp::HTMLParser::Rep::Rep()
57 {
58     m_verbose = 0;
59 }
60
61 mp::HTMLParser::Rep::~Rep()
62 {
63 }
64
65 mp::HTMLParser::HTMLParser() : m_p(new Rep)
66 {
67 }
68
69 mp::HTMLParser::~HTMLParser()
70 {
71 }
72
73 void mp::HTMLParser::set_verbose(int v)
74 {
75     m_p->m_verbose = v;
76 }
77
78
79 void mp::HTMLParser::parse(mp::HTMLParserEvent & event, const char *str) const
80 {
81     m_p->parse_str(event, str);
82 }
83
84 static int skipSpace(const char *cp)
85 {
86     int i = 0;
87     while (cp[i] && strchr(SPACECHR, cp[i]))
88         i++;
89     return i;
90 }
91
92 static int skipName(const char *cp)
93 {
94     int i;
95     for (i = 0; cp[i] && !strchr(SPACECHR "/>=", cp[i]); i++)
96         ;
97     return i;
98 }
99
100 int mp::HTMLParser::Rep::skipAttribute(HTMLParserEvent &event,
101                                        const char *cp, int *attr_len,
102                                        const char **value, int *val_len,
103                                        int *tr)
104 {
105     int v0, v1;
106     int i = skipName(cp);
107     *attr_len = i;
108     *value = NULL;
109     if (!i)
110         return skipSpace(cp);
111     i += skipSpace(cp + i);
112     if (cp[i] != '=')
113         return 0;
114
115     i++;
116     i += skipSpace(cp + i);
117     if (cp[i] == '\"' || cp[i] == '\'')
118     {
119         *tr = cp[i];
120         v0 = ++i;
121         while (cp[i] != *tr && cp[i])
122             i++;
123         v1 = i;
124         if (cp[i])
125             i++;
126     }
127     else
128     {
129         *tr = 0;
130         v0 = i;
131         while (cp[i] && !strchr(SPACECHR ">", cp[i]))
132             i++;
133         v1 = i;
134     }
135     *value = cp + v0;
136     *val_len = v1 - v0;
137
138     i += skipSpace(cp + i);
139     return i;
140 }
141
142 int mp::HTMLParser::Rep::tagAttrs(HTMLParserEvent &event,
143                                   const char *name, int len,
144                                   const char *cp)
145 {
146     int i = skipSpace(cp);
147     while (cp[i] && cp[i] != '>' && cp[i] != '/')
148     {
149         const char *attr_name = cp + i;
150         int attr_len;
151         const char *value;
152         int val_len;
153         int tr;
154         char x[2];
155         int nor = skipAttribute(event, cp+i, &attr_len, &value, &val_len, &tr);
156         if (!nor)
157             break;
158         i += nor;
159
160         x[0] = tr;
161         x[1] = 0;
162         if (m_verbose)
163             printf ("------ attr %.*s=%.*s\n", attr_len, attr_name,
164                     val_len, value);
165         event.attribute(name, len, attr_name, attr_len, value, val_len, x);
166     }
167     return i;
168 }
169
170 int mp::HTMLParser::Rep::tagStart(HTMLParserEvent &event,
171                                   int *tag_len,
172                                   const char *cp, const char which)
173 {
174     int i;
175     switch (which)
176     {
177     case '/':
178         i = skipName(cp);
179         *tag_len = i;
180         if (m_verbose)
181             printf("------ tag close %.*s\n", i, cp);
182         event.closeTag(cp, i);
183         break;
184     case '!':
185         for (i = 0; cp[i] && cp[i] != '>'; i++)
186             ;
187         *tag_len = i;
188         event.openTagStart(cp, i);
189         if (m_verbose)
190             printf("------ dtd %.*s\n", i, cp);
191         break;
192     case '?':
193         for (i = 0; cp[i] && cp[i] != '>'; i++)
194             ;
195         *tag_len = i;
196         event.openTagStart(cp, i);
197         if (m_verbose)
198             printf("------ pi %.*s\n", i, cp);
199         break;
200     default:
201         i = skipName(cp);
202         *tag_len = i;
203         if (m_verbose)
204             printf("------ tag open %.*s\n", i, cp);
205         event.openTagStart(cp, i);
206
207         i += tagAttrs(event, cp, i, cp + i);
208
209         break;
210     }
211     return i;
212 }
213
214 int mp::HTMLParser::Rep::tagEnd(HTMLParserEvent &event,
215                                 const char *tag, int tag_len, const char *cp)
216 {
217     int i = 0;
218     int close_it = 0;
219     for (; cp[i] && cp[i] != '/' && cp[i] != '>'; i++)
220         ;
221     if (i > 0)
222     {
223         if (m_verbose)
224             printf("------ text %.*s\n", i, cp);
225         event.text(cp, i);
226     }
227     if (cp[i] == '/')
228     {
229         close_it = 1;
230         i++;
231     }
232     if (cp[i] == '>')
233     {
234         if (m_verbose)
235             printf("------ any tag %s %.*s\n",
236                    close_it ? " close" : "end", tag_len, tag);
237         event.anyTagEnd(tag, tag_len, close_it);
238         i++;
239     }
240     return i;
241 }
242
243 void mp::HTMLParser::Rep::tagText(HTMLParserEvent &event,
244                                   const char *text_start, const char *text_end)
245 {
246     if (text_end - text_start) //got text to flush
247     {
248         if (m_verbose)
249             printf("------ text %.*s\n",
250                    (int) (text_end - text_start), text_start);
251         event.text(text_start, text_end-text_start);
252     }
253 }
254
255 void mp::HTMLParser::Rep::parse_str(HTMLParserEvent &event, const char *cp)
256 {
257     const char *text_start = cp;
258     const char *text_end = cp;
259     while (*cp)
260     {
261         if (cp[0] == '<' && cp[1])  //tag?
262         {
263             char which = cp[1];
264             if (which == '/')
265                 cp++;
266             if (!strchr(SPACECHR, cp[1])) //valid tag starts
267             {
268                 int i = 0;
269                 int tag_len;
270
271                 tagText(event, text_start, text_end); //flush any text
272                 cp++;
273                 i += tagStart(event, &tag_len, cp, which);
274                 i += tagEnd(event, cp, tag_len, cp + i);
275                 cp += i;
276                 text_start = cp;
277                 text_end = cp;
278                 continue;
279             }
280         }
281         //text
282         cp++;
283         text_end = cp;
284     }
285     tagText(event, text_start, text_end); //flush any text
286 }
287
288 /*
289  * Local variables:
290  * c-basic-offset: 4
291  * c-file-style: "Stroustrup"
292  * indent-tabs-mode: nil
293  * End:
294  * vim: shiftwidth=4 tabstop=8 expandtab
295  */
296