Reformat of HTMLparser source
[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 TAG_MAX_LEN 64
29
30 #define SPACECHR " \t\r\n\f"
31
32 #define DEBUG(x) x
33
34 #if HAVE_SYS_TYPES_H
35 #include <sys/types.h>
36 #endif
37
38 namespace mp = metaproxy_1;
39
40 mp::HTMLParser::HTMLParser()
41 {
42 }
43
44 mp::HTMLParser::~HTMLParser()
45 {
46 }
47
48 static void parse_str(mp::HTMLParserEvent & event, const char * str);
49
50 void mp::HTMLParser::parse(mp::HTMLParserEvent & event, const char *str) const
51 {
52     parse_str(event, str);
53 }
54
55 //static C functions follow would probably make sense to wrap this in PIMPL?
56
57 static char* dupe(const char *buff, int len)
58 {
59     char *value = (char *) malloc(len + 1);
60     assert(value);
61     memcpy(value, buff, len);
62     value[len] = '\0';
63     return value;
64 }
65
66 static int skipSpace(const char *cp)
67 {
68     int i = 0;
69     while (cp[i] && strchr(SPACECHR, cp[i]))
70         i++;
71     return i;
72 }
73
74 static int skipName(const char *cp, char *dst)
75 {
76     int i;
77     int j = 0;
78     for (i=0; cp[i] && !strchr(SPACECHR "/>=", cp[i]); i++)
79         if (j < TAG_MAX_LEN-1)
80         {
81             dst[j] = tolower(cp[j]);
82             j++;
83         }
84     dst[j] = '\0';
85     return i;
86 }
87
88 static int skipAttribute(const char *cp, char *name, const char **value, int *val_len)
89 {
90     int i = skipName(cp, name);
91     *value = NULL;
92     if (!i)
93         return skipSpace(cp);
94     i += skipSpace(cp + i);
95     if (cp[i] == '=')
96     {
97         int v0, v1;
98         i++;
99         i += skipSpace(cp + i);
100         if (cp[i] == '\"' || cp[i] == '\'')
101         {
102             char tr = cp[i];
103             v0 = ++i;
104             while (cp[i] != tr && cp[i])
105                 i++;
106             v1 = i;
107             if (cp[i])
108                 i++;
109         }
110         else
111         {
112             v0 = i;
113             while (cp[i] && !strchr(SPACECHR ">", cp[i]))
114                 i++;
115             v1 = i;
116         }
117         *value = cp + v0;
118         *val_len = v1 - v0;
119     }
120     i += skipSpace(cp + i);
121     return i;
122 }
123
124 static int tagAttrs(mp::HTMLParserEvent & event,
125                      const char *tagName,
126                      const char *cp)
127 {
128     char attr_name[TAG_MAX_LEN];
129     const char *attr_value;
130     int val_len;
131     int i = skipSpace(cp);
132     while (cp[i] && cp[i] != '>' && cp[i] != '/')
133     {
134         int nor = skipAttribute(cp+i, attr_name, &attr_value, &val_len);
135         i += nor;
136         if (nor)
137         {
138             DEBUG(printf ("------ attr %s=%s\n", attr_name, dupe(attr_value, val_len)));
139             event.attribute(tagName, attr_name, attr_value, val_len);
140         }
141         else
142         {
143             if (!nor)
144                 i++;
145         }
146     }
147     return i;
148 }
149
150 static int tagStart(mp::HTMLParserEvent & event,
151         char *tagName, const char *cp, const char which)
152 {
153     int i = skipName(cp, tagName);
154     switch (which)
155     {
156     case '/' :
157         DEBUG(printf("------ tag close %s\n", tagName));
158         event.closeTag(tagName);
159         break;
160     case '!' :
161         DEBUG(printf("------ dtd %s\n", tagName));
162         break;
163     case '?' :
164         DEBUG(printf("------ pi %s\n", tagName));
165         break;
166     default :
167         DEBUG(printf("------ tag open %s\n", tagName));
168         event.openTagStart(tagName);
169         break;
170     }
171     return i;
172 }
173
174 static int tagEnd(mp::HTMLParserEvent & event, const char *tagName, const char *cp)
175 {
176     int i = 0;
177     int close_it = 0;
178     while (cp[i] && cp[i] != '>')
179     {
180         if (cp[i] == '/')
181             close_it = 1;
182         i++;
183     }
184     if (cp[i] == '>')
185     {
186         event.anyTagEnd(tagName, close_it);
187         i++;
188     }
189     return i;
190 }
191
192 static void tagText(mp::HTMLParserEvent & event, const char *text_start, const char *text_end)
193 {
194     if (text_end - text_start) //got text to flush
195     {
196         DEBUG(printf("------ text %s\n", dupe(text_start, text_end-text_start)));
197         event.text(text_start, text_end-text_start);
198     }
199 }
200
201 static void parse_str(mp::HTMLParserEvent & event, const char *cp)
202 {
203     const char *text_start = cp;
204     const char *text_end = cp;
205     while (*cp)
206     {
207         if (cp[0] == '<' && cp[1])  //tag?
208         {
209             char which = cp[1];
210             if (which == '/')
211                 cp++;
212             if (!strchr(SPACECHR, cp[1])) //valid tag starts
213             {
214                 tagText(event, text_start, text_end); //flush any text
215                 char tagName[TAG_MAX_LEN];
216                 cp++;
217                 if (which == '/')
218                 {
219                     cp += tagStart(event, tagName, cp, which);
220                 }
221                 else if (which == '!' || which == '?') //pi or dtd
222                 {
223                     cp++;
224                     cp += tagStart(event, tagName, cp, which);
225                 }
226                 else
227                 {
228                     cp += tagStart(event, tagName, cp, which);
229                     cp += tagAttrs(event, tagName, cp);
230                 }
231                 cp += tagEnd(event, tagName, cp);
232                 text_start = cp;
233                 text_end = cp;
234                 continue;
235             }
236         }
237         //text
238         cp++;
239         text_end = cp;
240     }
241     tagText(event, text_start, text_end); //flush any text
242 }
243
244 /*
245  * Local variables:
246  * c-basic-offset: 4
247  * c-file-style: "Stroustrup"
248  * indent-tabs-mode: nil
249  * End:
250  * vim: shiftwidth=4 tabstop=8 expandtab
251  */
252