• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Output stream that produces HTML output.
2    Copyright (C) 2006-2009, 2019 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <config.h>
19 
20 /* Specification.  */
21 #include "html-ostream.h"
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "gl_xlist.h"
28 #include "gl_array_list.h"
29 #include "minmax.h"
30 #include "unistr.h"
31 #include "xalloc.h"
32 
33 struct html_ostream : struct ostream
34 {
35 fields:
36   /* The destination stream.  */
37   ostream_t destination;
38   /* The current hyperlink ref.  */
39   char *hyperlink_ref;
40   /* The stack of active CSS classes.  */
41   gl_list_t /* <char *> */ class_stack;
42   /* Current and last size of the active portion of this stack.  Always
43      size(class_stack) == max(curr_class_stack_size,last_class_stack_size).  */
44   size_t curr_class_stack_size;
45   size_t last_class_stack_size;
46   /* Last few bytes that could not yet be converted.  */
47   #define BUFSIZE 6
48   char buf[BUFSIZE];
49   size_t buflen;
50 };
51 
52 /* Emit an HTML attribute value.
53    quote is either '"' or '\''.  */
54 static void
write_attribute_value(html_ostream_t stream,const char * value,char quote)55 write_attribute_value (html_ostream_t stream, const char *value, char quote)
56 {
57   /* Need to escape the '<', '>', '&', quote characters.  */
58   ostream_t destination = stream->destination;
59   const char *p = value;
60 
61   for (;;)
62     {
63       const char *q = p;
64 
65       while (*q != '\0' && *q != '<' && *q != '>' && *q != '&' && *q != quote)
66         q++;
67       if (p < q)
68         ostream_write_mem (destination, p, q - p);
69       if (*q == '\0')
70         break;
71       switch (*q)
72         {
73         case '<':
74           ostream_write_str (destination, "&lt;");
75           break;
76         case '>':
77           ostream_write_str (destination, "&gt;");
78           break;
79         case '&':
80           ostream_write_str (destination, "&amp;");
81           break;
82         case '"':
83           ostream_write_str (destination, "&quot;");
84           break;
85         case '\'':
86           ostream_write_str (destination, "&apos;");
87           break;
88         default:
89           abort ();
90         }
91       p = q + 1;
92     }
93 }
94 
95 /* Implementation of ostream_t methods.  */
96 
97 static void
verify_invariants(html_ostream_t stream)98 verify_invariants (html_ostream_t stream)
99 {
100   /* Verify the invariant regarding size(class_stack).  */
101   if (gl_list_size (stream->class_stack)
102       != MAX (stream->curr_class_stack_size, stream->last_class_stack_size))
103     abort ();
104 }
105 
106 /* Removes the excess elements of class_stack.
107    Needs to be called after max(curr_class_stack_size,last_class_stack_size)
108    may have been reduced.  */
109 static void
shrink_class_stack(html_ostream_t stream)110 shrink_class_stack (html_ostream_t stream)
111 {
112   size_t keep =
113     MAX (stream->curr_class_stack_size, stream->last_class_stack_size);
114   size_t i = gl_list_size (stream->class_stack);
115   while (i > keep)
116     {
117       i--;
118       free ((char *) gl_list_get_at (stream->class_stack, i));
119       gl_list_remove_at (stream->class_stack, i);
120     }
121 }
122 
123 /* Emits <span> or </span> tags, to follow the increase / decrease of the
124    class_stack from last_class_stack_size to curr_class_stack_size.
125    When done, sets last_class_stack_size to curr_class_stack_size.  */
126 static void
emit_pending_spans(html_ostream_t stream,bool shrink_stack)127 emit_pending_spans (html_ostream_t stream, bool shrink_stack)
128 {
129   if (stream->curr_class_stack_size > stream->last_class_stack_size)
130     {
131       size_t i;
132 
133       for (i = stream->last_class_stack_size; i < stream->curr_class_stack_size; i++)
134         {
135           char *classname = (char *) gl_list_get_at (stream->class_stack, i);
136 
137           ostream_write_str (stream->destination, "<span class=\"");
138           ostream_write_str (stream->destination, classname);
139           ostream_write_str (stream->destination, "\">");
140         }
141       stream->last_class_stack_size = stream->curr_class_stack_size;
142     }
143   else if (stream->curr_class_stack_size < stream->last_class_stack_size)
144     {
145       size_t i;
146 
147       for (i = stream->last_class_stack_size; i > stream->curr_class_stack_size; i--)
148         ostream_write_str (stream->destination, "</span>");
149       stream->last_class_stack_size = stream->curr_class_stack_size;
150       if (shrink_stack)
151         shrink_class_stack (stream);
152     }
153   /* Here last_class_stack_size == curr_class_stack_size.  */
154   if (shrink_stack)
155     verify_invariants (stream);
156 }
157 
158 static void
write_mem(html_ostream_t stream,const void * data,size_t len)159 html_ostream::write_mem (html_ostream_t stream, const void *data, size_t len)
160 {
161   if (len > 0)
162     {
163       #define BUFFERSIZE 2048
164       char inbuffer[BUFFERSIZE];
165       size_t inbufcount;
166 
167       inbufcount = stream->buflen;
168       if (inbufcount > 0)
169         memcpy (inbuffer, stream->buf, inbufcount);
170       for (;;)
171         {
172           /* At this point, inbuffer[0..inbufcount-1] is filled.  */
173           {
174             /* Combine the previous rest with a chunk of new input.  */
175             size_t n =
176               (len <= BUFFERSIZE - inbufcount ? len : BUFFERSIZE - inbufcount);
177 
178             if (n > 0)
179               {
180                 memcpy (inbuffer + inbufcount, data, n);
181                 data = (char *) data + n;
182                 inbufcount += n;
183                 len -= n;
184               }
185           }
186           {
187             /* Handle complete UTF-8 characters.  */
188             const char *inptr = inbuffer;
189             size_t insize = inbufcount;
190 
191             while (insize > 0)
192               {
193                 unsigned char c0;
194                 ucs4_t uc;
195                 int nbytes;
196 
197                 c0 = ((const unsigned char *) inptr)[0];
198                 if (insize < (c0 < 0xc0 ? 1 : c0 < 0xe0 ? 2 : c0 < 0xf0 ? 3 :
199                               c0 < 0xf8 ? 4 : c0 < 0xfc ? 5 : 6))
200                   break;
201 
202                 nbytes = u8_mbtouc (&uc, (const unsigned char *) inptr, insize);
203 
204                 if (uc == '\n')
205                   {
206                     verify_invariants (stream);
207                     /* Emit </span> tags to follow the decrease of the class stack
208                        from last_class_stack_size to 0.  Then emit the newline.
209                        Then prepare for emitting <span> tags to go back from 0
210                        to curr_class_stack_size.  */
211                     size_t prev_class_stack_size = stream->curr_class_stack_size;
212                     stream->curr_class_stack_size = 0;
213                     emit_pending_spans (stream, false);
214                     stream->curr_class_stack_size = prev_class_stack_size;
215                     ostream_write_str (stream->destination, "<br/>");
216                     shrink_class_stack (stream);
217                     verify_invariants (stream);
218                   }
219                 else
220                   {
221                     emit_pending_spans (stream, true);
222 
223                     switch (uc)
224                       {
225                       case '"':
226                         ostream_write_str (stream->destination, "&quot;");
227                         break;
228                       case '&':
229                         ostream_write_str (stream->destination, "&amp;");
230                         break;
231                       case '<':
232                         ostream_write_str (stream->destination, "&lt;");
233                         break;
234                       case '>':
235                         /* Needed to avoid "]]>" in the output.  */
236                         ostream_write_str (stream->destination, "&gt;");
237                         break;
238                       case ' ':
239                         /* Needed because HTML viewers merge adjacent spaces
240                            and drop spaces adjacent to <br> and similar.  */
241                         ostream_write_str (stream->destination, "&nbsp;");
242                         break;
243                       default:
244                         if (uc >= 0x20 && uc < 0x7F)
245                           {
246                             /* Output ASCII characters as such.  */
247                             char bytes[1];
248                             bytes[0] = uc;
249                             ostream_write_mem (stream->destination, bytes, 1);
250                           }
251                         else
252                           {
253                             /* Output non-ASCII characters in #&nnn;
254                                notation.  */
255                             char bytes[32];
256                             sprintf (bytes, "&#%d;", (int) uc);
257                             ostream_write_str (stream->destination, bytes);
258                           }
259                         break;
260                       }
261                   }
262 
263                 inptr += nbytes;
264                 insize -= nbytes;
265               }
266             /* Put back the unconverted part.  */
267             if (insize > BUFSIZE)
268               abort ();
269             if (len == 0)
270               {
271                 if (insize > 0)
272                   memcpy (stream->buf, inptr, insize);
273                 stream->buflen = insize;
274                 break;
275               }
276             if (insize > 0)
277               memmove (inbuffer, inptr, insize);
278             inbufcount = insize;
279           }
280         }
281       #undef BUFFERSIZE
282     }
283 }
284 
285 static void
flush(html_ostream_t stream,ostream_flush_scope_t scope)286 html_ostream::flush (html_ostream_t stream, ostream_flush_scope_t scope)
287 {
288   verify_invariants (stream);
289   /* stream->buf[] contains only a few bytes that don't correspond to a
290      character.  Can't flush it.  */
291   /* Close the open <span> tags, and prepare for reopening the same <span>
292      tags.  */
293   size_t prev_class_stack_size = stream->curr_class_stack_size;
294   stream->curr_class_stack_size = 0;
295   emit_pending_spans (stream, false);
296   stream->curr_class_stack_size = prev_class_stack_size;
297   shrink_class_stack (stream);
298   verify_invariants (stream);
299 
300   if (scope != FLUSH_THIS_STREAM)
301     ostream_flush (stream->destination, scope);
302 }
303 
304 static void
free(html_ostream_t stream)305 html_ostream::free (html_ostream_t stream)
306 {
307   stream->curr_class_stack_size = 0;
308   emit_pending_spans (stream, true);
309   if (stream->hyperlink_ref != NULL)
310     {
311       /* Close the current <a> element.  */
312       ostream_write_str (stream->destination, "</a>");
313       free (stream->hyperlink_ref);
314     }
315   verify_invariants (stream);
316   gl_list_free (stream->class_stack);
317   free (stream);
318 }
319 
320 /* Implementation of html_ostream_t methods.  */
321 
322 static void
begin_span(html_ostream_t stream,const char * classname)323 html_ostream::begin_span (html_ostream_t stream, const char *classname)
324 {
325   verify_invariants (stream);
326   if (stream->last_class_stack_size > stream->curr_class_stack_size
327       && strcmp ((char *) gl_list_get_at (stream->class_stack,
328                                           stream->curr_class_stack_size),
329                  classname) != 0)
330     emit_pending_spans (stream, true);
331   /* Now either
332        last_class_stack_size <= curr_class_stack_size
333        - in this case we have to append the given CLASSNAME -
334      or
335        last_class_stack_size > curr_class_stack_size
336        && class_stack[curr_class_stack_size] == CLASSNAME
337        - in this case we only need to increment curr_class_stack_size.  */
338   if (stream->last_class_stack_size <= stream->curr_class_stack_size)
339     gl_list_add_at (stream->class_stack, stream->curr_class_stack_size,
340                     xstrdup (classname));
341   stream->curr_class_stack_size++;
342   verify_invariants (stream);
343 }
344 
345 static void
end_span(html_ostream_t stream,const char * classname)346 html_ostream::end_span (html_ostream_t stream, const char *classname)
347 {
348   verify_invariants (stream);
349   if (stream->curr_class_stack_size > 0)
350     {
351       char *innermost_active_span =
352         (char *) gl_list_get_at (stream->class_stack,
353                                  stream->curr_class_stack_size - 1);
354       if (strcmp (innermost_active_span, classname) == 0)
355         {
356           stream->curr_class_stack_size--;
357           shrink_class_stack (stream);
358           verify_invariants (stream);
359           return;
360         }
361     }
362   /* Improperly nested begin_span/end_span calls.  */
363   abort ();
364 }
365 
366 static const char *
get_hyperlink_ref(html_ostream_t stream)367 html_ostream::get_hyperlink_ref (html_ostream_t stream)
368 {
369   return stream->hyperlink_ref;
370 }
371 
372 static void
set_hyperlink_ref(html_ostream_t stream,const char * ref)373 html_ostream::set_hyperlink_ref (html_ostream_t stream, const char *ref)
374 {
375   char *ref_copy = (ref != NULL ? xstrdup (ref) : NULL);
376 
377   verify_invariants (stream);
378   if (stream->hyperlink_ref != NULL)
379     {
380       /* Close the open <span> tags, and prepare for reopening the same <span>
381          tags.  */
382       size_t prev_class_stack_size = stream->curr_class_stack_size;
383       stream->curr_class_stack_size = 0;
384       emit_pending_spans (stream, false);
385       stream->curr_class_stack_size = prev_class_stack_size;
386       /* Close the current <a> element.  */
387       ostream_write_str (stream->destination, "</a>");
388       shrink_class_stack (stream);
389 
390       free (stream->hyperlink_ref);
391     }
392   stream->hyperlink_ref = ref_copy;
393   if (stream->hyperlink_ref != NULL)
394     {
395       /* Close the open <span> tags, and prepare for reopening the same <span>
396          tags.  */
397       size_t prev_class_stack_size = stream->curr_class_stack_size;
398       stream->curr_class_stack_size = 0;
399       emit_pending_spans (stream, false);
400       stream->curr_class_stack_size = prev_class_stack_size;
401       /* Open an <a> element.  */
402       ostream_write_str (stream->destination, "<a href=\"");
403       write_attribute_value (stream, stream->hyperlink_ref, '"');
404       ostream_write_str (stream->destination, "\">");
405       shrink_class_stack (stream);
406     }
407   verify_invariants (stream);
408 }
409 
410 static void
flush_to_current_style(html_ostream_t stream)411 html_ostream::flush_to_current_style (html_ostream_t stream)
412 {
413   verify_invariants (stream);
414   /* stream->buf[] contains only a few bytes that don't correspond to a
415      character.  Can't flush it.  */
416   /* Open all requested <span> tags.  */
417   emit_pending_spans (stream, true);
418   verify_invariants (stream);
419 }
420 
421 /* Constructor.  */
422 
423 html_ostream_t
html_ostream_create(ostream_t destination)424 html_ostream_create (ostream_t destination)
425 {
426   html_ostream_t stream = XMALLOC (struct html_ostream_representation);
427 
428   stream->base.vtable = &html_ostream_vtable;
429   stream->destination = destination;
430   stream->hyperlink_ref = NULL;
431   stream->class_stack =
432     gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, true);
433   stream->curr_class_stack_size = 0;
434   stream->last_class_stack_size = 0;
435   stream->buflen = 0;
436 
437   return stream;
438 }
439