Revise HTML parser; keep spelling
[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         Rep();
46         ~Rep();
47         int m_verbose;
48     };
49 }
50
51 namespace mp = metaproxy_1;
52
53 mp::HTMLParser::Rep::Rep()
54 {
55     m_verbose = 0;
56 }
57
58 mp::HTMLParser::Rep::~Rep()
59 {
60 }
61
62 mp::HTMLParser::HTMLParser() : m_p(new Rep)
63 {
64 }
65
66 mp::HTMLParser::~HTMLParser()
67 {
68 }
69
70 void mp::HTMLParser::parse(mp::HTMLParserEvent & event, const char *str) const
71 {
72     m_p->parse_str(event, str);
73 }
74
75 static int skipSpace(const char *cp)
76 {
77     int i = 0;
78     while (cp[i] && strchr(SPACECHR, cp[i]))
79         i++;
80     return i;
81 }
82
83 static int skipName(const char *cp)
84 {
85     int i;
86     for (i = 0; cp[i] && !strchr(SPACECHR "/>=", cp[i]); i++)
87         ;
88     return i;
89 }
90
91 static int skipAttribute(const char *cp, int *attr_len,
92                          const char **value, int *val_len)
93 {
94     int i = skipName(cp);
95     *attr_len = i;
96     *value = NULL;
97     if (!i)
98         return skipSpace(cp);
99     i += skipSpace(cp + i);
100     if (cp[i] == '=')
101     {
102         int v0, v1;
103         i++;
104         i += skipSpace(cp + i);
105         if (cp[i] == '\"' || cp[i] == '\'')
106         {
107             char tr = cp[i];
108             v0 = ++i;
109             while (cp[i] != tr && cp[i])
110                 i++;
111             v1 = i;
112             if (cp[i])
113                 i++;
114         }
115         else
116         {
117             v0 = i;
118             while (cp[i] && !strchr(SPACECHR ">", cp[i]))
119                 i++;
120             v1 = i;
121         }
122         *value = cp + v0;
123         *val_len = v1 - v0;
124     }
125     i += skipSpace(cp + i);
126     return i;
127 }
128
129 int mp::HTMLParser::Rep::tagAttrs(HTMLParserEvent &event,
130                                   const char *name, int len,
131                                   const char *cp)
132 {
133     int i = skipSpace(cp);
134     while (cp[i] && cp[i] != '>' && cp[i] != '/')
135     {
136         const char *attr_name = cp + i;
137         int attr_len;
138         const char *value;
139         int val_len;
140         int nor = skipAttribute(cp+i, &attr_len, &value, &val_len);
141         i += nor;
142         if (nor)
143         {
144             if (m_verbose)
145                 printf ("------ attr %.*s=%.*s\n", attr_len, attr_name,
146                         val_len, value);
147             event.attribute(name, len, attr_name, attr_len, value, val_len);
148         }
149         else
150         {
151             i++;
152         }
153     }
154     return i;
155 }
156
157 int mp::HTMLParser::Rep::tagStart(HTMLParserEvent &event,
158                                   int *tag_len,
159                                   const char *cp, const char which)
160 {
161     int i;
162     switch (which)
163     {
164     case '/':
165         i = skipName(cp);
166         *tag_len = i;
167         if (m_verbose)
168             printf("------ tag close %.*s\n", i, cp);
169         event.closeTag(cp, i);
170         break;
171     case '!':
172         for (i = 0; cp[i] && cp[i] != '>'; i++)
173             ;
174         *tag_len = i;
175         event.openTagStart(cp, i);
176         if (m_verbose)
177             printf("------ dtd %.*s\n", i, cp);
178         break;
179     case '?':
180         for (i = 0; cp[i] && cp[i] != '>'; i++)
181             ;
182         *tag_len = i;
183         event.openTagStart(cp, i);
184         if (m_verbose)
185             printf("------ pi %.*s\n", i, cp);
186         break;
187     default:
188         i = skipName(cp);
189         *tag_len = i;
190         if (m_verbose)
191             printf("------ tag open %.*s\n", i, cp);
192         event.openTagStart(cp, i);
193
194         i += tagAttrs(event, cp, i, cp + i);
195
196         break;
197     }
198     return i;
199 }
200
201 int mp::HTMLParser::Rep::tagEnd(HTMLParserEvent &event,
202                                 const char *tag, int tag_len, const char *cp)
203 {
204     int i = 0;
205     int close_it = 0;
206     while (cp[i] && cp[i] != '>')
207     {
208         if (cp[i] == '/')
209             close_it = 1;
210         i++;
211     }
212     if (cp[i] == '>')
213     {
214         event.anyTagEnd(tag, tag_len, close_it);
215         i++;
216     }
217     return i;
218 }
219
220 void mp::HTMLParser::Rep::tagText(HTMLParserEvent &event,
221                                   const char *text_start, const char *text_end)
222 {
223     if (text_end - text_start) //got text to flush
224     {
225         if (m_verbose)
226             printf("------ text %.*s\n",
227                    (int) (text_end - text_start), text_start);
228         event.text(text_start, text_end-text_start);
229     }
230 }
231
232 void mp::HTMLParser::Rep::parse_str(HTMLParserEvent &event, const char *cp)
233 {
234     const char *text_start = cp;
235     const char *text_end = cp;
236     while (*cp)
237     {
238         if (cp[0] == '<' && cp[1])  //tag?
239         {
240             char which = cp[1];
241             if (which == '/')
242                 cp++;
243             if (!strchr(SPACECHR, cp[1])) //valid tag starts
244             {
245                 int i = 0;
246                 int tag_len;
247
248                 tagText(event, text_start, text_end); //flush any text
249                 cp++;
250                 i += tagStart(event, &tag_len, cp, which);
251                 i += tagEnd(event, cp, tag_len, cp + i);
252                 cp += i;
253                 text_start = cp;
254                 text_end = cp;
255                 continue;
256             }
257         }
258         //text
259         cp++;
260         text_end = cp;
261     }
262     tagText(event, text_start, text_end); //flush any text
263 }
264
265 /*
266  * Local variables:
267  * c-basic-offset: 4
268  * c-file-style: "Stroustrup"
269  * indent-tabs-mode: nil
270  * End:
271  * vim: shiftwidth=4 tabstop=8 expandtab
272  */
273