Imported Upstream version 0.18.1.1
[platform/upstream/gettext.git] / gnulib-local / lib / html-ostream.oo.c
1 /* Output stream that produces HTML output.
2    Copyright (C) 2006-2009 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 <http://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 "unistr.h"
30 #include "xalloc.h"
31
32 struct html_ostream : struct ostream
33 {
34 fields:
35   /* The destination stream.  */
36   ostream_t destination;
37   /* The stack of active CSS classes.  */
38   gl_list_t /* <char *> */ class_stack;
39   /* Current and last size of the active portion of this stack.  Always
40      size(class_stack) == max(curr_class_stack_size,last_class_stack_size).  */
41   size_t curr_class_stack_size;
42   size_t last_class_stack_size;
43   /* Last few bytes that could not yet be converted.  */
44   #define BUFSIZE 6
45   char buf[BUFSIZE];
46   size_t buflen;
47 };
48
49 /* Implementation of ostream_t methods.  */
50
51 static void
52 emit_pending_spans (html_ostream_t stream, bool shrink_stack)
53 {
54   if (stream->curr_class_stack_size > stream->last_class_stack_size)
55     {
56       size_t i;
57
58       for (i = stream->last_class_stack_size; i < stream->curr_class_stack_size; i++)
59         {
60           char *classname = (char *) gl_list_get_at (stream->class_stack, i);
61
62           ostream_write_str (stream->destination, "<span class=\"");
63           ostream_write_str (stream->destination, classname);
64           ostream_write_str (stream->destination, "\">");
65         }
66       stream->last_class_stack_size = stream->curr_class_stack_size;
67     }
68   else if (stream->curr_class_stack_size < stream->last_class_stack_size)
69     {
70       size_t i = stream->last_class_stack_size;
71
72       while (i > stream->curr_class_stack_size)
73         {
74           char *classname;
75
76           --i;
77           classname = (char *) gl_list_get_at (stream->class_stack, i);
78           ostream_write_str (stream->destination, "</span>");
79           if (shrink_stack)
80             {
81               gl_list_remove_at (stream->class_stack, i);
82               free (classname);
83             }
84         }
85       stream->last_class_stack_size = stream->curr_class_stack_size;
86     }
87 }
88
89 static void
90 html_ostream::write_mem (html_ostream_t stream, const void *data, size_t len)
91 {
92   if (len > 0)
93     {
94       #define BUFFERSIZE 2048
95       char inbuffer[BUFFERSIZE];
96       size_t inbufcount;
97
98       inbufcount = stream->buflen;
99       if (inbufcount > 0)
100         memcpy (inbuffer, stream->buf, inbufcount);
101       for (;;)
102         {
103           /* At this point, inbuffer[0..inbufcount-1] is filled.  */
104           {
105             /* Combine the previous rest with a chunk of new input.  */
106             size_t n =
107               (len <= BUFFERSIZE - inbufcount ? len : BUFFERSIZE - inbufcount);
108
109             if (n > 0)
110               {
111                 memcpy (inbuffer + inbufcount, data, n);
112                 data = (char *) data + n;
113                 inbufcount += n;
114                 len -= n;
115               }
116           }
117           {
118             /* Handle complete UTF-8 characters.  */
119             const char *inptr = inbuffer;
120             size_t insize = inbufcount;
121
122             while (insize > 0)
123               {
124                 unsigned char c0;
125                 ucs4_t uc;
126                 int nbytes;
127
128                 c0 = ((const unsigned char *) inptr)[0];
129                 if (insize < (c0 < 0xc0 ? 1 : c0 < 0xe0 ? 2 : c0 < 0xf0 ? 3 :
130                               c0 < 0xf8 ? 4 : c0 < 0xfc ? 5 : 6))
131                   break;
132
133                 nbytes = u8_mbtouc (&uc, (const unsigned char *) inptr, insize);
134
135                 if (uc == '\n')
136                   {
137                     size_t prev_class_stack_size = stream->curr_class_stack_size;
138                     stream->curr_class_stack_size = 0;
139                     emit_pending_spans (stream, false);
140                     ostream_write_str (stream->destination, "<br/>");
141                     stream->curr_class_stack_size = prev_class_stack_size;
142                   }
143                 else
144                   {
145                     emit_pending_spans (stream, true);
146
147                     switch (uc)
148                       {
149                       case '"':
150                         ostream_write_str (stream->destination, "&quot;");
151                         break;
152                       case '&':
153                         ostream_write_str (stream->destination, "&amp;");
154                         break;
155                       case '<':
156                         ostream_write_str (stream->destination, "&lt;");
157                         break;
158                       case '>':
159                         /* Needed to avoid "]]>" in the output.  */
160                         ostream_write_str (stream->destination, "&gt;");
161                         break;
162                       case ' ':
163                         /* Needed because HTML viewers merge adjacent spaces
164                            and drop spaces adjacent to <br> and similar.  */
165                         ostream_write_str (stream->destination, "&nbsp;");
166                         break;
167                       default:
168                         if (uc >= 0x20 && uc < 0x7F)
169                           {
170                             /* Output ASCII characters as such.  */
171                             char bytes[1];
172                             bytes[0] = uc;
173                             ostream_write_mem (stream->destination, bytes, 1);
174                           }
175                         else
176                           {
177                             /* Output non-ASCII characters in #&nnn;
178                                notation.  */
179                             char bytes[32];
180                             sprintf (bytes, "&#%d;", (int) uc);
181                             ostream_write_str (stream->destination, bytes);
182                           }
183                         break;
184                       }
185                   }
186
187                 inptr += nbytes;
188                 insize -= nbytes;
189               }
190             /* Put back the unconverted part.  */
191             if (insize > BUFSIZE)
192               abort ();
193             if (len == 0)
194               {
195                 if (insize > 0)
196                   memcpy (stream->buf, inptr, insize);
197                 stream->buflen = insize;
198                 break;
199               }
200             if (insize > 0)
201               memmove (inbuffer, inptr, insize);
202             inbufcount = insize;
203           }
204         }
205       #undef BUFFERSIZE
206     }
207 }
208
209 static void
210 html_ostream::flush (html_ostream_t stream)
211 {
212   /* There's nothing to do here, since stream->buf[] contains only a few
213      bytes that don't correspond to a character, and it's not worth closing
214      the open spans.  */
215 }
216
217 static void
218 html_ostream::free (html_ostream_t stream)
219 {
220   stream->curr_class_stack_size = 0;
221   emit_pending_spans (stream, true);
222   gl_list_free (stream->class_stack);
223   free (stream);
224 }
225
226 /* Implementation of html_ostream_t methods.  */
227
228 static void
229 html_ostream::begin_span (html_ostream_t stream, const char *classname)
230 {
231   if (stream->last_class_stack_size > stream->curr_class_stack_size
232       && strcmp ((char *) gl_list_get_at (stream->class_stack,
233                                           stream->curr_class_stack_size),
234                  classname) != 0)
235     emit_pending_spans (stream, true);
236   /* Now either
237        last_class_stack_size <= curr_class_stack_size
238        - in this case we have to append the given CLASSNAME -
239      or
240        last_class_stack_size > curr_class_stack_size
241        && class_stack[curr_class_stack_size] == CLASSNAME
242        - in this case we only need to increment curr_class_stack_size.  */
243   if (stream->last_class_stack_size <= stream->curr_class_stack_size)
244     gl_list_add_at (stream->class_stack, stream->curr_class_stack_size,
245                     xstrdup (classname));
246   stream->curr_class_stack_size++;
247 }
248
249 static void
250 html_ostream::end_span (html_ostream_t stream, const char *classname)
251 {
252   if (!(stream->curr_class_stack_size > 0
253         && strcmp ((char *) gl_list_get_at (stream->class_stack,
254                                             stream->curr_class_stack_size - 1),
255                    classname) == 0))
256     /* Improperly nested begin_span/end_span calls.  */
257     abort ();
258   stream->curr_class_stack_size--;
259 }
260
261 /* Constructor.  */
262
263 html_ostream_t
264 html_ostream_create (ostream_t destination)
265 {
266   html_ostream_t stream = XMALLOC (struct html_ostream_representation);
267
268   stream->base.vtable = &html_ostream_vtable;
269   stream->destination = destination;
270   stream->class_stack =
271     gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, true);
272   stream->curr_class_stack_size = 0;
273   stream->last_class_stack_size = 0;
274   stream->buflen = 0;
275
276   return stream;
277 }