PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/reddish/lib/sundown/html/html.c

https://bitbucket.org/murarth/reddish
C | 635 lines | 489 code | 112 blank | 34 comment | 144 complexity | 31e36fdfe42803c25ff79c9aec73758a MD5 | raw file
  1. /*
  2. * Copyright (c) 2009, Natacha Porté
  3. * Copyright (c) 2011, Vicent Marti
  4. *
  5. * Permission to use, copy, modify, and distribute this software for any
  6. * purpose with or without fee is hereby granted, provided that the above
  7. * copyright notice and this permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. */
  17. #include "markdown.h"
  18. #include "html.h"
  19. #include <string.h>
  20. #include <stdlib.h>
  21. #include <stdio.h>
  22. #include <ctype.h>
  23. #include "houdini.h"
  24. #define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
  25. int
  26. sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
  27. {
  28. size_t i;
  29. int closed = 0;
  30. if (tag_size < 3 || tag_data[0] != '<')
  31. return HTML_TAG_NONE;
  32. i = 1;
  33. if (tag_data[i] == '/') {
  34. closed = 1;
  35. i++;
  36. }
  37. for (; i < tag_size; ++i, ++tagname) {
  38. if (*tagname == 0)
  39. break;
  40. if (tag_data[i] != *tagname)
  41. return HTML_TAG_NONE;
  42. }
  43. if (i == tag_size)
  44. return HTML_TAG_NONE;
  45. if (isspace(tag_data[i]) || tag_data[i] == '>')
  46. return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
  47. return HTML_TAG_NONE;
  48. }
  49. static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length)
  50. {
  51. houdini_escape_html0(ob, source, length, 0);
  52. }
  53. static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length)
  54. {
  55. houdini_escape_href(ob, source, length);
  56. }
  57. /********************
  58. * GENERIC RENDERER *
  59. ********************/
  60. static int
  61. rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque)
  62. {
  63. struct html_renderopt *options = opaque;
  64. if (!link || !link->size)
  65. return 0;
  66. if ((options->flags & HTML_SAFELINK) != 0 &&
  67. !sd_autolink_issafe(link->data, link->size) &&
  68. type != MKDA_EMAIL)
  69. return 0;
  70. BUFPUTSL(ob, "<a href=\"");
  71. if (type == MKDA_EMAIL)
  72. BUFPUTSL(ob, "mailto:");
  73. escape_href(ob, link->data, link->size);
  74. if (options->link_attributes) {
  75. bufputc(ob, '\"');
  76. options->link_attributes(ob, link, opaque);
  77. bufputc(ob, '>');
  78. } else {
  79. BUFPUTSL(ob, "\">");
  80. }
  81. /*
  82. * Pretty printing: if we get an email address as
  83. * an actual URI, e.g. `mailto:foo@bar.com`, we don't
  84. * want to print the `mailto:` prefix
  85. */
  86. if (bufprefix(link, "mailto:") == 0) {
  87. escape_html(ob, link->data + 7, link->size - 7);
  88. } else {
  89. escape_html(ob, link->data, link->size);
  90. }
  91. BUFPUTSL(ob, "</a>");
  92. return 1;
  93. }
  94. static void
  95. rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque)
  96. {
  97. if (ob->size) bufputc(ob, '\n');
  98. if (lang && lang->size) {
  99. size_t i, cls;
  100. BUFPUTSL(ob, "<pre><code class=\"");
  101. for (i = 0, cls = 0; i < lang->size; ++i, ++cls) {
  102. while (i < lang->size && isspace(lang->data[i]))
  103. i++;
  104. if (i < lang->size) {
  105. size_t org = i;
  106. while (i < lang->size && !isspace(lang->data[i]))
  107. i++;
  108. if (lang->data[org] == '.')
  109. org++;
  110. if (cls) bufputc(ob, ' ');
  111. escape_html(ob, lang->data + org, i - org);
  112. }
  113. }
  114. BUFPUTSL(ob, "\">");
  115. } else
  116. BUFPUTSL(ob, "<pre><code>");
  117. if (text)
  118. escape_html(ob, text->data, text->size);
  119. BUFPUTSL(ob, "</code></pre>\n");
  120. }
  121. static void
  122. rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
  123. {
  124. if (ob->size) bufputc(ob, '\n');
  125. BUFPUTSL(ob, "<blockquote>\n");
  126. if (text) bufput(ob, text->data, text->size);
  127. BUFPUTSL(ob, "</blockquote>\n");
  128. }
  129. static int
  130. rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
  131. {
  132. BUFPUTSL(ob, "<code>");
  133. if (text) escape_html(ob, text->data, text->size);
  134. BUFPUTSL(ob, "</code>");
  135. return 1;
  136. }
  137. static int
  138. rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
  139. {
  140. if (!text || !text->size)
  141. return 0;
  142. BUFPUTSL(ob, "<del>");
  143. bufput(ob, text->data, text->size);
  144. BUFPUTSL(ob, "</del>");
  145. return 1;
  146. }
  147. static int
  148. rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
  149. {
  150. if (!text || !text->size)
  151. return 0;
  152. BUFPUTSL(ob, "<strong>");
  153. bufput(ob, text->data, text->size);
  154. BUFPUTSL(ob, "</strong>");
  155. return 1;
  156. }
  157. static int
  158. rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
  159. {
  160. if (!text || !text->size) return 0;
  161. BUFPUTSL(ob, "<em>");
  162. if (text) bufput(ob, text->data, text->size);
  163. BUFPUTSL(ob, "</em>");
  164. return 1;
  165. }
  166. static int
  167. rndr_linebreak(struct buf *ob, void *opaque)
  168. {
  169. struct html_renderopt *options = opaque;
  170. bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
  171. return 1;
  172. }
  173. static void
  174. rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
  175. {
  176. struct html_renderopt *options = opaque;
  177. if (ob->size)
  178. bufputc(ob, '\n');
  179. if (options->flags & HTML_TOC)
  180. bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);
  181. else
  182. bufprintf(ob, "<h%d>", level);
  183. if (text) bufput(ob, text->data, text->size);
  184. bufprintf(ob, "</h%d>\n", level);
  185. }
  186. static int
  187. rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
  188. {
  189. struct html_renderopt *options = opaque;
  190. if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
  191. return 0;
  192. BUFPUTSL(ob, "<a href=\"");
  193. if (link && link->size)
  194. escape_href(ob, link->data, link->size);
  195. if (title && title->size) {
  196. BUFPUTSL(ob, "\" title=\"");
  197. escape_html(ob, title->data, title->size);
  198. }
  199. if (options->link_attributes) {
  200. bufputc(ob, '\"');
  201. options->link_attributes(ob, link, opaque);
  202. bufputc(ob, '>');
  203. } else {
  204. BUFPUTSL(ob, "\">");
  205. }
  206. if (content && content->size) bufput(ob, content->data, content->size);
  207. BUFPUTSL(ob, "</a>");
  208. return 1;
  209. }
  210. static void
  211. rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
  212. {
  213. if (ob->size) bufputc(ob, '\n');
  214. bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);
  215. if (text) bufput(ob, text->data, text->size);
  216. bufput(ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6);
  217. }
  218. static void
  219. rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
  220. {
  221. BUFPUTSL(ob, "<li>");
  222. if (text) {
  223. size_t size = text->size;
  224. while (size && text->data[size - 1] == '\n')
  225. size--;
  226. bufput(ob, text->data, size);
  227. }
  228. BUFPUTSL(ob, "</li>\n");
  229. }
  230. static void
  231. rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
  232. {
  233. struct html_renderopt *options = opaque;
  234. size_t i = 0;
  235. if (ob->size) bufputc(ob, '\n');
  236. if (!text || !text->size)
  237. return;
  238. while (i < text->size && isspace(text->data[i])) i++;
  239. if (i == text->size)
  240. return;
  241. BUFPUTSL(ob, "<p>");
  242. if (options->flags & HTML_HARD_WRAP) {
  243. size_t org;
  244. while (i < text->size) {
  245. org = i;
  246. while (i < text->size && text->data[i] != '\n')
  247. i++;
  248. if (i > org)
  249. bufput(ob, text->data + org, i - org);
  250. /*
  251. * do not insert a line break if this newline
  252. * is the last character on the paragraph
  253. */
  254. if (i >= text->size - 1)
  255. break;
  256. rndr_linebreak(ob, opaque);
  257. i++;
  258. }
  259. } else {
  260. bufput(ob, &text->data[i], text->size - i);
  261. }
  262. BUFPUTSL(ob, "</p>\n");
  263. }
  264. static void
  265. rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
  266. {
  267. size_t org, sz;
  268. if (!text) return;
  269. sz = text->size;
  270. while (sz > 0 && text->data[sz - 1] == '\n') sz--;
  271. org = 0;
  272. while (org < sz && text->data[org] == '\n') org++;
  273. if (org >= sz) return;
  274. if (ob->size) bufputc(ob, '\n');
  275. bufput(ob, text->data + org, sz - org);
  276. bufputc(ob, '\n');
  277. }
  278. static int
  279. rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
  280. {
  281. if (!text || !text->size) return 0;
  282. BUFPUTSL(ob, "<strong><em>");
  283. bufput(ob, text->data, text->size);
  284. BUFPUTSL(ob, "</em></strong>");
  285. return 1;
  286. }
  287. static void
  288. rndr_hrule(struct buf *ob, void *opaque)
  289. {
  290. struct html_renderopt *options = opaque;
  291. if (ob->size) bufputc(ob, '\n');
  292. bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");
  293. }
  294. static int
  295. rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
  296. {
  297. struct html_renderopt *options = opaque;
  298. if (!link || !link->size) return 0;
  299. BUFPUTSL(ob, "<img src=\"");
  300. escape_href(ob, link->data, link->size);
  301. BUFPUTSL(ob, "\" alt=\"");
  302. if (alt && alt->size)
  303. escape_html(ob, alt->data, alt->size);
  304. if (title && title->size) {
  305. BUFPUTSL(ob, "\" title=\"");
  306. escape_html(ob, title->data, title->size); }
  307. bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
  308. return 1;
  309. }
  310. static int
  311. rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
  312. {
  313. struct html_renderopt *options = opaque;
  314. /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
  315. * It doens't see if there are any valid tags, just escape all of them. */
  316. if((options->flags & HTML_ESCAPE) != 0) {
  317. escape_html(ob, text->data, text->size);
  318. return 1;
  319. }
  320. if ((options->flags & HTML_SKIP_HTML) != 0)
  321. return 1;
  322. if ((options->flags & HTML_SKIP_STYLE) != 0 &&
  323. sdhtml_is_tag(text->data, text->size, "style"))
  324. return 1;
  325. if ((options->flags & HTML_SKIP_LINKS) != 0 &&
  326. sdhtml_is_tag(text->data, text->size, "a"))
  327. return 1;
  328. if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
  329. sdhtml_is_tag(text->data, text->size, "img"))
  330. return 1;
  331. bufput(ob, text->data, text->size);
  332. return 1;
  333. }
  334. static void
  335. rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
  336. {
  337. if (ob->size) bufputc(ob, '\n');
  338. BUFPUTSL(ob, "<table><thead>\n");
  339. if (header)
  340. bufput(ob, header->data, header->size);
  341. BUFPUTSL(ob, "</thead><tbody>\n");
  342. if (body)
  343. bufput(ob, body->data, body->size);
  344. BUFPUTSL(ob, "</tbody></table>\n");
  345. }
  346. static void
  347. rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
  348. {
  349. BUFPUTSL(ob, "<tr>\n");
  350. if (text)
  351. bufput(ob, text->data, text->size);
  352. BUFPUTSL(ob, "</tr>\n");
  353. }
  354. static void
  355. rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
  356. {
  357. if (flags & MKD_TABLE_HEADER) {
  358. BUFPUTSL(ob, "<th");
  359. } else {
  360. BUFPUTSL(ob, "<td");
  361. }
  362. switch (flags & MKD_TABLE_ALIGNMASK) {
  363. case MKD_TABLE_ALIGN_CENTER:
  364. BUFPUTSL(ob, " align=\"center\">");
  365. break;
  366. case MKD_TABLE_ALIGN_L:
  367. BUFPUTSL(ob, " align=\"left\">");
  368. break;
  369. case MKD_TABLE_ALIGN_R:
  370. BUFPUTSL(ob, " align=\"right\">");
  371. break;
  372. default:
  373. BUFPUTSL(ob, ">");
  374. }
  375. if (text)
  376. bufput(ob, text->data, text->size);
  377. if (flags & MKD_TABLE_HEADER) {
  378. BUFPUTSL(ob, "</th>\n");
  379. } else {
  380. BUFPUTSL(ob, "</td>\n");
  381. }
  382. }
  383. static int
  384. rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
  385. {
  386. if (!text || !text->size) return 0;
  387. BUFPUTSL(ob, "<sup>");
  388. bufput(ob, text->data, text->size);
  389. BUFPUTSL(ob, "</sup>");
  390. return 1;
  391. }
  392. static void
  393. rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
  394. {
  395. if (text)
  396. escape_html(ob, text->data, text->size);
  397. }
  398. static void
  399. toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
  400. {
  401. struct html_renderopt *options = opaque;
  402. /* set the level offset if this is the first header
  403. * we're parsing for the document */
  404. if (options->toc_data.current_level == 0) {
  405. options->toc_data.level_offset = level - 1;
  406. }
  407. level -= options->toc_data.level_offset;
  408. if (level > options->toc_data.current_level) {
  409. while (level > options->toc_data.current_level) {
  410. BUFPUTSL(ob, "<ul>\n<li>\n");
  411. options->toc_data.current_level++;
  412. }
  413. } else if (level < options->toc_data.current_level) {
  414. BUFPUTSL(ob, "</li>\n");
  415. while (level < options->toc_data.current_level) {
  416. BUFPUTSL(ob, "</ul>\n</li>\n");
  417. options->toc_data.current_level--;
  418. }
  419. BUFPUTSL(ob,"<li>\n");
  420. } else {
  421. BUFPUTSL(ob,"</li>\n<li>\n");
  422. }
  423. bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++);
  424. if (text)
  425. escape_html(ob, text->data, text->size);
  426. BUFPUTSL(ob, "</a>\n");
  427. }
  428. static int
  429. toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
  430. {
  431. if (content && content->size)
  432. bufput(ob, content->data, content->size);
  433. return 1;
  434. }
  435. static void
  436. toc_finalize(struct buf *ob, void *opaque)
  437. {
  438. struct html_renderopt *options = opaque;
  439. while (options->toc_data.current_level > 0) {
  440. BUFPUTSL(ob, "</li>\n</ul>\n");
  441. options->toc_data.current_level--;
  442. }
  443. }
  444. void
  445. sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)
  446. {
  447. static const struct sd_callbacks cb_default = {
  448. NULL,
  449. NULL,
  450. NULL,
  451. toc_header,
  452. NULL,
  453. NULL,
  454. NULL,
  455. NULL,
  456. NULL,
  457. NULL,
  458. NULL,
  459. NULL,
  460. rndr_codespan,
  461. rndr_double_emphasis,
  462. rndr_emphasis,
  463. NULL,
  464. NULL,
  465. toc_link,
  466. NULL,
  467. rndr_triple_emphasis,
  468. rndr_strikethrough,
  469. rndr_superscript,
  470. NULL,
  471. NULL,
  472. NULL,
  473. toc_finalize,
  474. };
  475. memset(options, 0x0, sizeof(struct html_renderopt));
  476. options->flags = HTML_TOC;
  477. memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
  478. }
  479. void
  480. sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
  481. {
  482. static const struct sd_callbacks cb_default = {
  483. rndr_blockcode,
  484. rndr_blockquote,
  485. rndr_raw_block,
  486. rndr_header,
  487. rndr_hrule,
  488. rndr_list,
  489. rndr_listitem,
  490. rndr_paragraph,
  491. rndr_table,
  492. rndr_tablerow,
  493. rndr_tablecell,
  494. rndr_autolink,
  495. rndr_codespan,
  496. rndr_double_emphasis,
  497. rndr_emphasis,
  498. rndr_image,
  499. rndr_linebreak,
  500. rndr_link,
  501. rndr_raw_html,
  502. rndr_triple_emphasis,
  503. rndr_strikethrough,
  504. rndr_superscript,
  505. NULL,
  506. rndr_normal_text,
  507. NULL,
  508. NULL,
  509. };
  510. /* Prepare the options pointer */
  511. memset(options, 0x0, sizeof(struct html_renderopt));
  512. options->flags = render_flags;
  513. /* Prepare the callbacks */
  514. memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
  515. if (render_flags & HTML_SKIP_IMAGES)
  516. callbacks->image = NULL;
  517. if (render_flags & HTML_SKIP_LINKS) {
  518. callbacks->link = NULL;
  519. callbacks->autolink = NULL;
  520. }
  521. if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE)
  522. callbacks->blockhtml = NULL;
  523. }