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