size; ++i, ++cls) {
128 | while (i < lang->size && isspace(lang->data[i]))
129 | i++;
130 |
131 | if (i < lang->size) {
132 | size_t org = i;
133 | while (i < lang->size && !isspace(lang->data[i]))
134 | i++;
135 |
136 | if (lang->data[org] == '.')
137 | org++;
138 |
139 | if (cls) bufputc(ob, ' ');
140 | escape_html(ob, lang->data + org, i - org);
141 | }
142 | }
143 |
144 | BUFPUTSL(ob, "\">");
145 | } else
146 | BUFPUTSL(ob, "");
147 |
148 | if (text)
149 | escape_html(ob, text->data, text->size);
150 |
151 | BUFPUTSL(ob, "
\n");
152 | }
153 |
154 | static void
155 | rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
156 | {
157 | if (ob->size) bufputc(ob, '\n');
158 | BUFPUTSL(ob, "\n");
159 | if (text) bufput(ob, text->data, text->size);
160 | BUFPUTSL(ob, "
\n");
161 | }
162 |
163 | static int
164 | rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
165 | {
166 | BUFPUTSL(ob, "");
167 | if (text) escape_html(ob, text->data, text->size);
168 | BUFPUTSL(ob, "");
169 | return 1;
170 | }
171 |
172 | static int
173 | rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
174 | {
175 | if (!text || !text->size)
176 | return 0;
177 |
178 | BUFPUTSL(ob, "");
179 | bufput(ob, text->data, text->size);
180 | BUFPUTSL(ob, "");
181 | return 1;
182 | }
183 |
184 | static int
185 | rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
186 | {
187 | if (!text || !text->size)
188 | return 0;
189 |
190 | BUFPUTSL(ob, "");
191 | bufput(ob, text->data, text->size);
192 | BUFPUTSL(ob, "");
193 |
194 | return 1;
195 | }
196 |
197 | static int
198 | rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
199 | {
200 | if (!text || !text->size) return 0;
201 | BUFPUTSL(ob, "");
202 | if (text) bufput(ob, text->data, text->size);
203 | BUFPUTSL(ob, "");
204 | return 1;
205 | }
206 |
207 | static int
208 | rndr_linebreak(struct buf *ob, void *opaque)
209 | {
210 | struct html_renderopt *options = opaque;
211 | bufputs(ob, USE_XHTML(options) ? "
\n" : "
\n");
212 | return 1;
213 | }
214 |
215 | static void
216 | rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
217 | {
218 | struct html_renderopt *options = opaque;
219 |
220 | if (ob->size)
221 | bufputc(ob, '\n');
222 |
223 | if (options->flags & HTML_TOC)
224 | bufprintf(ob, "", level, options->toc_data.header_count++);
225 | else
226 | bufprintf(ob, "", level);
227 |
228 | if (text) bufput(ob, text->data, text->size);
229 | bufprintf(ob, " \n", level);
230 | }
231 |
232 | static int
233 | rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
234 | {
235 | struct html_renderopt *options = opaque;
236 |
237 | if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
238 | return 0;
239 |
240 | BUFPUTSL(ob, "size)
243 | escape_href(ob, link->data, link->size);
244 |
245 | if (title && title->size) {
246 | BUFPUTSL(ob, "\" title=\"");
247 | escape_html(ob, title->data, title->size);
248 | }
249 |
250 | if (options->link_attributes) {
251 | bufputc(ob, '\"');
252 | options->link_attributes(ob, link, opaque);
253 | bufputc(ob, '>');
254 | } else {
255 | BUFPUTSL(ob, "\">");
256 | }
257 |
258 | if (content && content->size) bufput(ob, content->data, content->size);
259 | BUFPUTSL(ob, "");
260 | return 1;
261 | }
262 |
263 | static void
264 | rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
265 | {
266 | if (ob->size) bufputc(ob, '\n');
267 | bufput(ob, flags & MKD_LIST_ORDERED ? "\n" : "\n", 5);
268 | if (text) bufput(ob, text->data, text->size);
269 | bufput(ob, flags & MKD_LIST_ORDERED ? "
\n" : "\n", 6);
270 | }
271 |
272 | static void
273 | rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
274 | {
275 | BUFPUTSL(ob, "");
276 | if (text) {
277 | size_t size = text->size;
278 | while (size && text->data[size - 1] == '\n')
279 | size--;
280 |
281 | bufput(ob, text->data, size);
282 | }
283 | BUFPUTSL(ob, " \n");
284 | }
285 |
286 | static void
287 | rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
288 | {
289 | struct html_renderopt *options = opaque;
290 | size_t i = 0;
291 |
292 | if (ob->size) bufputc(ob, '\n');
293 |
294 | if (!text || !text->size)
295 | return;
296 |
297 | while (i < text->size && isspace(text->data[i])) i++;
298 |
299 | if (i == text->size)
300 | return;
301 |
302 | BUFPUTSL(ob, "");
303 | if (options->flags & HTML_HARD_WRAP) {
304 | size_t org;
305 | while (i < text->size) {
306 | org = i;
307 | while (i < text->size && text->data[i] != '\n')
308 | i++;
309 |
310 | if (i > org)
311 | bufput(ob, text->data + org, i - org);
312 |
313 | /*
314 | * do not insert a line break if this newline
315 | * is the last character on the paragraph
316 | */
317 | if (i >= text->size - 1)
318 | break;
319 |
320 | rndr_linebreak(ob, opaque);
321 | i++;
322 | }
323 | } else {
324 | bufput(ob, &text->data[i], text->size - i);
325 | }
326 | BUFPUTSL(ob, "
\n");
327 | }
328 |
329 | static void
330 | rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
331 | {
332 | size_t org, sz;
333 | if (!text) return;
334 | sz = text->size;
335 | while (sz > 0 && text->data[sz - 1] == '\n') sz--;
336 | org = 0;
337 | while (org < sz && text->data[org] == '\n') org++;
338 | if (org >= sz) return;
339 | if (ob->size) bufputc(ob, '\n');
340 | bufput(ob, text->data + org, sz - org);
341 | bufputc(ob, '\n');
342 | }
343 |
344 | static int
345 | rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
346 | {
347 | if (!text || !text->size) return 0;
348 | BUFPUTSL(ob, "");
349 | bufput(ob, text->data, text->size);
350 | BUFPUTSL(ob, "");
351 | return 1;
352 | }
353 |
354 | static void
355 | rndr_hrule(struct buf *ob, void *opaque)
356 | {
357 | struct html_renderopt *options = opaque;
358 | if (ob->size) bufputc(ob, '\n');
359 | bufputs(ob, USE_XHTML(options) ? "
\n" : "
\n");
360 | }
361 |
362 | static int
363 | rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
364 | {
365 | struct html_renderopt *options = opaque;
366 | if (!link || !link->size) return 0;
367 |
368 | BUFPUTSL(ob, "
data, link->size);
370 | BUFPUTSL(ob, "\" alt=\"");
371 |
372 | if (alt && alt->size)
373 | escape_html(ob, alt->data, alt->size);
374 |
375 | if (title && title->size) {
376 | BUFPUTSL(ob, "\" title=\"");
377 | escape_html(ob, title->data, title->size); }
378 |
379 | bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
380 | return 1;
381 | }
382 |
383 | static int
384 | rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
385 | {
386 | struct html_renderopt *options = opaque;
387 |
388 | /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
389 | * It doens't see if there are any valid tags, just escape all of them. */
390 | if((options->flags & HTML_ESCAPE) != 0) {
391 | escape_html(ob, text->data, text->size);
392 | return 1;
393 | }
394 |
395 | if ((options->flags & HTML_SKIP_HTML) != 0)
396 | return 1;
397 |
398 | if ((options->flags & HTML_SKIP_STYLE) != 0 &&
399 | sdhtml_is_tag(text->data, text->size, "style"))
400 | return 1;
401 |
402 | if ((options->flags & HTML_SKIP_LINKS) != 0 &&
403 | sdhtml_is_tag(text->data, text->size, "a"))
404 | return 1;
405 |
406 | if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
407 | sdhtml_is_tag(text->data, text->size, "img"))
408 | return 1;
409 |
410 | bufput(ob, text->data, text->size);
411 | return 1;
412 | }
413 |
414 | static void
415 | rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
416 | {
417 | if (ob->size) bufputc(ob, '\n');
418 | BUFPUTSL(ob, "\n");
419 | if (header)
420 | bufput(ob, header->data, header->size);
421 | BUFPUTSL(ob, "\n");
422 | if (body)
423 | bufput(ob, body->data, body->size);
424 | BUFPUTSL(ob, "
\n");
425 | }
426 |
427 | static void
428 | rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
429 | {
430 | BUFPUTSL(ob, "\n");
431 | if (text)
432 | bufput(ob, text->data, text->size);
433 | BUFPUTSL(ob, " \n");
434 | }
435 |
436 | static void
437 | rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
438 | {
439 | if (flags & MKD_TABLE_HEADER) {
440 | BUFPUTSL(ob, "");
448 | break;
449 |
450 | case MKD_TABLE_ALIGN_L:
451 | BUFPUTSL(ob, " align=\"left\">");
452 | break;
453 |
454 | case MKD_TABLE_ALIGN_R:
455 | BUFPUTSL(ob, " align=\"right\">");
456 | break;
457 |
458 | default:
459 | BUFPUTSL(ob, ">");
460 | }
461 |
462 | if (text)
463 | bufput(ob, text->data, text->size);
464 |
465 | if (flags & MKD_TABLE_HEADER) {
466 | BUFPUTSL(ob, " \n");
467 | } else {
468 | BUFPUTSL(ob, "\n");
469 | }
470 | }
471 |
472 | static int
473 | rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
474 | {
475 | if (!text || !text->size) return 0;
476 | BUFPUTSL(ob, "");
477 | bufput(ob, text->data, text->size);
478 | BUFPUTSL(ob, "");
479 | return 1;
480 | }
481 |
482 | static void
483 | rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
484 | {
485 | if (text)
486 | escape_html(ob, text->data, text->size);
487 | }
488 |
489 | static void
490 | toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
491 | {
492 | struct html_renderopt *options = opaque;
493 |
494 | /* set the level offset if this is the first header
495 | * we're parsing for the document */
496 | if (options->toc_data.current_level == 0) {
497 | options->toc_data.level_offset = level - 1;
498 | }
499 | level -= options->toc_data.level_offset;
500 |
501 | if (level > options->toc_data.current_level) {
502 | while (level > options->toc_data.current_level) {
503 | BUFPUTSL(ob, "\n- \n");
504 | options->toc_data.current_level++;
505 | }
506 | } else if (level < options->toc_data.current_level) {
507 | BUFPUTSL(ob, "
\n");
508 | while (level < options->toc_data.current_level) {
509 | BUFPUTSL(ob, "
\n