Line data Source code
1 : // Snap Websites Servers -- generate HTML from the output of an XML Query
2 : // Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License 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 : // The importance of having an HTML specific serializer comes from
19 : // the fact that XML defines empty tags as <div/> which are not
20 : // supported by most browsers. This serializer generates <div></div>
21 : // which works in all browsers. It is sad that such things are as
22 : // they are, but browsers have to be compatible with many old websites
23 : // which include such bad syntax.
24 : //
25 : // Also unfortunate, Qt does not provide such a class.
26 :
27 :
28 : // self
29 : //
30 : #include "snapwebsites/qhtmlserializer.h"
31 :
32 :
33 : // C++ lib
34 : //
35 : #include <stdexcept>
36 :
37 :
38 : // last include
39 : //
40 : #include <snapdev/poison.h>
41 :
42 :
43 :
44 :
45 :
46 0 : QHtmlSerializer::QHtmlSerializer(QXmlNamePool namepool, QBuffer *output, bool const is_html)
47 : : f_namepool(namepool)
48 : , f_output(output)
49 : , f_status(html_serializer_status_t::HTML_SERIALIZER_STATUS_READY)
50 0 : , f_is_html(is_html)
51 : {
52 0 : }
53 :
54 :
55 0 : QHtmlSerializer::~QHtmlSerializer()
56 : {
57 0 : }
58 :
59 :
60 : #pragma GCC diagnostic push
61 : #pragma GCC diagnostic ignored "-Wunused-parameter"
62 0 : void QHtmlSerializer::atomicValue(const QVariant& value)
63 : {
64 0 : }
65 : #pragma GCC diagnostic pop
66 :
67 :
68 0 : void QHtmlSerializer::attribute(const QXmlName& name, const QStringRef& value)
69 : {
70 0 : f_output->write(" ");
71 0 : if(!name.prefix(f_namepool).isEmpty())
72 : {
73 0 : f_output->write(name.prefix(f_namepool).toUtf8());
74 0 : f_output->write(":");
75 : }
76 0 : f_output->write(name.localName(f_namepool).toUtf8());
77 0 : f_output->write("=\"");
78 0 : QString v(value.toString());
79 0 : v.replace('&', "&")
80 0 : .replace('\"', """)
81 0 : .replace('<', "<")
82 0 : .replace('>', ">");
83 0 : f_output->write(v.simplified().toUtf8());
84 0 : f_output->write("\"");
85 0 : }
86 :
87 :
88 0 : void QHtmlSerializer::characters(const QStringRef& value)
89 : {
90 0 : closeElement();
91 :
92 0 : QString v(value.toString());
93 0 : v.replace('&', "&")
94 0 : .replace('<', "<")
95 0 : .replace('>', ">");
96 0 : f_output->write(v.toUtf8());
97 0 : }
98 :
99 :
100 0 : void QHtmlSerializer::comment(const QString& value)
101 : {
102 0 : closeElement();
103 :
104 : // TBD -- I would think that value cannot include "--"
105 : // because it has to be a valid comment;
106 : // also, we want to have a way to remove all
107 : // "useless" comments from the output
108 0 : f_output->write("<!--");
109 0 : f_output->write(value.toUtf8());
110 0 : f_output->write("-->");
111 0 : }
112 :
113 :
114 0 : void QHtmlSerializer::endDocument()
115 : {
116 : // we are done
117 0 : }
118 :
119 :
120 0 : void QHtmlSerializer::endElement()
121 : {
122 : // here is the magic necessary for proper HTML, all tags
123 : // are always closed with </name> except it is marked as
124 : // an empty tag
125 0 : QString element(f_element_stack.back());
126 0 : f_element_stack.pop_back();
127 :
128 0 : QString e(element.toLower());
129 0 : bool is_empty(false);
130 0 : if(f_is_html) switch(e[0].unicode())
131 : {
132 0 : case 'a':
133 0 : is_empty = e == "area";
134 0 : break;
135 :
136 0 : case 'b':
137 0 : is_empty = e == "br" || e == "base" || e == "basefont";
138 0 : break;
139 :
140 0 : case 'c':
141 0 : is_empty = e == "col";
142 0 : break;
143 :
144 0 : case 'f':
145 0 : is_empty = e == "frame";
146 0 : break;
147 :
148 0 : case 'h':
149 0 : is_empty = e == "hr";
150 0 : break;
151 :
152 0 : case 'i':
153 0 : is_empty = e == "img" || e == "input" || e == "isindex";
154 0 : break;
155 :
156 0 : case 'l':
157 0 : is_empty = e == "link";
158 0 : break;
159 :
160 0 : case 'm':
161 0 : is_empty = e == "meta";
162 0 : break;
163 :
164 0 : case 'p':
165 0 : is_empty = e == "param";
166 0 : break;
167 :
168 0 : default:
169 0 : is_empty = false;
170 0 : break;
171 :
172 : }
173 0 : if(is_empty)
174 : {
175 0 : if(f_status != html_serializer_status_t::HTML_SERIALIZER_STATUS_ELEMENT_OPEN)
176 : {
177 0 : throw std::runtime_error(QString("data was written inside empty HTML tag \"%1\"").arg(e).toUtf8().data());
178 : }
179 0 : f_status = html_serializer_status_t::HTML_SERIALIZER_STATUS_READY;
180 :
181 : // close empty tag
182 : // (note that the / is not required, but we want to keep it
183 : // XML compatible)
184 0 : f_output->write("/>");
185 : }
186 : else
187 : {
188 0 : closeElement();
189 :
190 : // close the element
191 0 : f_output->write("</");
192 0 : f_output->write(element.toUtf8());
193 0 : f_output->write(">");
194 : }
195 0 : }
196 :
197 :
198 0 : void QHtmlSerializer::endOfSequence()
199 : {
200 : // nothing to do here
201 0 : }
202 :
203 :
204 0 : void QHtmlSerializer::namespaceBinding(const QXmlName& name)
205 : {
206 0 : QString uri(name.namespaceUri(f_namepool));
207 0 : if(!uri.isEmpty())
208 : {
209 : // prefix is saved as a suffix in an attribute name
210 0 : f_output->write(" xmlns");
211 0 : if(!name.prefix(f_namepool).isEmpty())
212 : {
213 0 : f_output->write(":");
214 0 : f_output->write(name.prefix(f_namepool).toUtf8());
215 : }
216 0 : f_output->write("=\"");
217 0 : uri.replace('\"', """)
218 0 : .replace('<', "<")
219 0 : .replace('>', ">")
220 0 : .replace('&', "&");
221 0 : f_output->write(uri.toUtf8());
222 0 : f_output->write("\"");
223 : }
224 0 : }
225 :
226 :
227 0 : void QHtmlSerializer::processingInstruction(const QXmlName& target, const QString& value )
228 : {
229 0 : closeElement();
230 :
231 : // prefix is saved as a suffix in a processing instruction
232 0 : f_output->write("<?");
233 0 : f_output->write(target.localName(f_namepool).toUtf8());
234 0 : QString prefix(target.prefix(f_namepool));
235 0 : if(!prefix.isEmpty())
236 : {
237 0 : f_output->write(":");
238 0 : f_output->write(prefix.toUtf8());
239 : }
240 0 : f_output->write(value.toUtf8());
241 0 : f_output->write("?>");
242 0 : }
243 :
244 :
245 0 : void QHtmlSerializer::startDocument()
246 : {
247 : // should we create docs here?
248 0 : }
249 :
250 :
251 0 : void QHtmlSerializer::startElement(const QXmlName& name)
252 : {
253 0 : closeElement();
254 :
255 0 : f_output->write("<");
256 0 : QString element(name.prefix(f_namepool));
257 0 : if(!element.isEmpty())
258 : {
259 0 : element += ":";
260 : }
261 0 : element += name.localName(f_namepool);
262 0 : f_output->write(element.toUtf8());
263 0 : f_status = html_serializer_status_t::HTML_SERIALIZER_STATUS_ELEMENT_OPEN;
264 0 : f_element_stack.push_back(element);
265 0 : }
266 :
267 :
268 0 : void QHtmlSerializer::startOfSequence()
269 : {
270 : // nothing to do here
271 0 : }
272 :
273 :
274 0 : void QHtmlSerializer::closeElement()
275 : {
276 0 : if(f_status == html_serializer_status_t::HTML_SERIALIZER_STATUS_ELEMENT_OPEN)
277 : {
278 0 : f_status = html_serializer_status_t::HTML_SERIALIZER_STATUS_READY;
279 0 : f_output->write(">");
280 : }
281 0 : }
282 :
283 :
284 : // vim: ts=4 sw=4 et
|