1/* Output stream that produces HTML output. 2 Copyright (C) 2006-2007 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_list.h" 28#include "gl_array_list.h" 29#include "unistr.h" 30#include "xalloc.h" 31 32struct html_ostream : struct ostream 33{ 34fields: 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 51static void 52emit_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 89static void 90html_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 unsigned int 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, """); 151 break; 152 case '&': 153 ostream_write_str (stream->destination, "&"); 154 break; 155 case '<': 156 ostream_write_str (stream->destination, "<"); 157 break; 158 case '>': 159 /* Needed to avoid "]]>" in the output. */ 160 ostream_write_str (stream->destination, ">"); 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, " "); 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;", 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 209static void 210html_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 217static void 218html_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 228void 229html_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 249void 250html_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 263html_ostream_t 264html_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} 278