Text::Markdown::Discount
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1754 lines
36 KiB

  1. /* markdown: a C implementation of John Gruber's Markdown markup language.
  2. *
  3. * Copyright (C) 2007 David L Parsons.
  4. * The redistribution terms are provided in the COPYRIGHT file that must
  5. * be distributed with this source code.
  6. */
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <stdarg.h>
  10. #include <stdlib.h>
  11. #include <time.h>
  12. #include <ctype.h>
  13. #include "config.h"
  14. #include "cstring.h"
  15. #include "markdown.h"
  16. #include "amalloc.h"
  17. typedef int (*stfu)(const void*,const void*);
  18. typedef void (*spanhandler)(MMIOT*,int);
  19. /* forward declarations */
  20. static void text(MMIOT *f);
  21. static Paragraph *display(Paragraph*, MMIOT*);
  22. /* externals from markdown.c */
  23. int __mkd_footsort(Footnote *, Footnote *);
  24. /*
  25. * push text into the generator input buffer
  26. */
  27. static void
  28. push(char *bfr, int size, MMIOT *f)
  29. {
  30. while ( size-- > 0 )
  31. EXPAND(f->in) = *bfr++;
  32. }
  33. /* look <i> characters ahead of the cursor.
  34. */
  35. static inline int
  36. peek(MMIOT *f, int i)
  37. {
  38. i += (f->isp-1);
  39. return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
  40. }
  41. /* pull a byte from the input buffer
  42. */
  43. static inline int
  44. pull(MMIOT *f)
  45. {
  46. return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
  47. }
  48. /* return a pointer to the current position in the input buffer.
  49. */
  50. static inline char*
  51. cursor(MMIOT *f)
  52. {
  53. return T(f->in) + f->isp;
  54. }
  55. static inline int
  56. isthisspace(MMIOT *f, int i)
  57. {
  58. int c = peek(f, i);
  59. return isspace(c) || (c == EOF);
  60. }
  61. static inline int
  62. isthisalnum(MMIOT *f, int i)
  63. {
  64. int c = peek(f, i);
  65. return (c != EOF) && isalnum(c);
  66. }
  67. static inline int
  68. isthisnonword(MMIOT *f, int i)
  69. {
  70. return isthisspace(f, i) || ispunct(peek(f,i));
  71. }
  72. /* return/set the current cursor position
  73. */
  74. #define mmiotseek(f,x) (f->isp = x)
  75. #define mmiottell(f) (f->isp)
  76. /* move n characters forward ( or -n characters backward) in the input buffer.
  77. */
  78. static void
  79. shift(MMIOT *f, int i)
  80. {
  81. if (f->isp + i >= 0 )
  82. f->isp += i;
  83. }
  84. /* Qchar()
  85. */
  86. static void
  87. Qchar(int c, MMIOT *f)
  88. {
  89. block *cur;
  90. if ( S(f->Q) == 0 ) {
  91. cur = &EXPAND(f->Q);
  92. memset(cur, 0, sizeof *cur);
  93. cur->b_type = bTEXT;
  94. }
  95. else
  96. cur = &T(f->Q)[S(f->Q)-1];
  97. EXPAND(cur->b_text) = c;
  98. }
  99. /* Qstring()
  100. */
  101. static void
  102. Qstring(char *s, MMIOT *f)
  103. {
  104. while (*s)
  105. Qchar(*s++, f);
  106. }
  107. /* Qwrite()
  108. */
  109. static void
  110. Qwrite(char *s, int size, MMIOT *f)
  111. {
  112. while (size-- > 0)
  113. Qchar(*s++, f);
  114. }
  115. /* Qprintf()
  116. */
  117. static void
  118. Qprintf(MMIOT *f, char *fmt, ...)
  119. {
  120. char bfr[80];
  121. va_list ptr;
  122. va_start(ptr,fmt);
  123. vsnprintf(bfr, sizeof bfr, fmt, ptr);
  124. va_end(ptr);
  125. Qstring(bfr, f);
  126. }
  127. /* Qem()
  128. */
  129. static void
  130. Qem(MMIOT *f, char c, int count)
  131. {
  132. block *p = &EXPAND(f->Q);
  133. memset(p, 0, sizeof *p);
  134. p->b_type = (c == '*') ? bSTAR : bUNDER;
  135. p->b_char = c;
  136. p->b_count = count;
  137. memset(&EXPAND(f->Q), 0, sizeof(block));
  138. }
  139. /* generate html from a markup fragment
  140. */
  141. void
  142. ___mkd_reparse(char *bfr, int size, int flags, MMIOT *f)
  143. {
  144. MMIOT sub;
  145. ___mkd_initmmiot(&sub, f->footnotes);
  146. sub.flags = f->flags | flags;
  147. sub.cb = f->cb;
  148. sub.ref_prefix = f->ref_prefix;
  149. push(bfr, size, &sub);
  150. EXPAND(sub.in) = 0;
  151. S(sub.in)--;
  152. text(&sub);
  153. ___mkd_emblock(&sub);
  154. Qwrite(T(sub.out), S(sub.out), f);
  155. ___mkd_freemmiot(&sub, f->footnotes);
  156. }
  157. /*
  158. * write out a url, escaping problematic characters
  159. */
  160. static void
  161. puturl(char *s, int size, MMIOT *f, int display)
  162. {
  163. unsigned char c;
  164. while ( size-- > 0 ) {
  165. c = *s++;
  166. if ( c == '\\' && size-- > 0 ) {
  167. c = *s++;
  168. if ( !( ispunct(c) || isspace(c) ) )
  169. Qchar('\\', f);
  170. }
  171. if ( c == '&' )
  172. Qstring("&amp;", f);
  173. else if ( c == '<' )
  174. Qstring("&lt;", f);
  175. else if ( c == '"' )
  176. Qstring("%22", f);
  177. else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) )
  178. Qchar(c, f);
  179. else if ( c == 003 ) /* untokenize ^C */
  180. Qstring(" ", f);
  181. else
  182. Qprintf(f, "%%%02X", c);
  183. }
  184. }
  185. /* advance forward until the next character is not whitespace
  186. */
  187. static int
  188. eatspace(MMIOT *f)
  189. {
  190. int c;
  191. for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) )
  192. ;
  193. return c;
  194. }
  195. /* (match (a (nested (parenthetical (string.)))))
  196. */
  197. static int
  198. parenthetical(int in, int out, MMIOT *f)
  199. {
  200. int size, indent, c;
  201. for ( indent=1,size=0; indent; size++ ) {
  202. if ( (c = pull(f)) == EOF )
  203. return EOF;
  204. else if ( (c == '\\') && (peek(f,1) == out || peek(f,1) == in) ) {
  205. ++size;
  206. pull(f);
  207. }
  208. else if ( c == in )
  209. ++indent;
  210. else if ( c == out )
  211. --indent;
  212. }
  213. return size ? (size-1) : 0;
  214. }
  215. /* extract a []-delimited label from the input stream.
  216. */
  217. static int
  218. linkylabel(MMIOT *f, Cstring *res)
  219. {
  220. char *ptr = cursor(f);
  221. int size;
  222. if ( (size = parenthetical('[',']',f)) != EOF ) {
  223. T(*res) = ptr;
  224. S(*res) = size;
  225. return 1;
  226. }
  227. return 0;
  228. }
  229. /* see if the quote-prefixed linky segment is actually a title.
  230. */
  231. static int
  232. linkytitle(MMIOT *f, char quote, Footnote *ref)
  233. {
  234. int whence = mmiottell(f);
  235. char *title = cursor(f);
  236. char *e;
  237. register int c;
  238. while ( (c = pull(f)) != EOF ) {
  239. e = cursor(f);
  240. if ( c == quote ) {
  241. if ( (c = eatspace(f)) == ')' ) {
  242. T(ref->title) = 1+title;
  243. S(ref->title) = (e-title)-2;
  244. return 1;
  245. }
  246. }
  247. }
  248. mmiotseek(f, whence);
  249. return 0;
  250. }
  251. /* extract a =HHHxWWW size from the input stream
  252. */
  253. static int
  254. linkysize(MMIOT *f, Footnote *ref)
  255. {
  256. int height=0, width=0;
  257. int whence = mmiottell(f);
  258. int c;
  259. if ( isspace(peek(f,0)) ) {
  260. pull(f); /* eat '=' */
  261. for ( c = pull(f); isdigit(c); c = pull(f))
  262. width = (width * 10) + (c - '0');
  263. if ( c == 'x' ) {
  264. for ( c = pull(f); isdigit(c); c = pull(f))
  265. height = (height*10) + (c - '0');
  266. if ( isspace(c) )
  267. c = eatspace(f);
  268. if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) {
  269. ref->height = height;
  270. ref->width = width;
  271. return 1;
  272. }
  273. }
  274. }
  275. mmiotseek(f, whence);
  276. return 0;
  277. }
  278. /* extract a <...>-encased url from the input stream.
  279. * (markdown 1.0.2b8 compatibility; older versions
  280. * of markdown treated the < and > as syntactic
  281. * sugar that didn't have to be there. 1.0.2b8
  282. * requires a closing >, and then falls into the
  283. * title or closing )
  284. */
  285. static int
  286. linkybroket(MMIOT *f, int image, Footnote *p)
  287. {
  288. int c;
  289. int good = 0;
  290. T(p->link) = cursor(f);
  291. for ( S(p->link)=0; (c = pull(f)) != '>'; ++S(p->link) ) {
  292. /* pull in all input until a '>' is found, or die trying.
  293. */
  294. if ( c == EOF )
  295. return 0;
  296. else if ( (c == '\\') && ispunct(peek(f,2)) ) {
  297. ++S(p->link);
  298. pull(f);
  299. }
  300. }
  301. c = eatspace(f);
  302. /* next nonspace needs to be a title, a size, or )
  303. */
  304. if ( ( c == '\'' || c == '"' ) && linkytitle(f,c,p) )
  305. good=1;
  306. else if ( image && (c == '=') && linkysize(f,p) )
  307. good=1;
  308. else
  309. good=( c == ')' );
  310. if ( good ) {
  311. if ( peek(f, 1) == ')' )
  312. pull(f);
  313. ___mkd_tidy(&p->link);
  314. }
  315. return good;
  316. } /* linkybroket */
  317. /* extract a (-prefixed url from the input stream.
  318. * the label is either of the format `<link>`, where I
  319. * extract until I find a >, or it is of the format
  320. * `text`, where I extract until I reach a ')', a quote,
  321. * or (if image) a '='
  322. */
  323. static int
  324. linkyurl(MMIOT *f, int image, Footnote *p)
  325. {
  326. int c;
  327. int mayneedtotrim=0;
  328. if ( (c = eatspace(f)) == EOF )
  329. return 0;
  330. if ( c == '<' ) {
  331. pull(f);
  332. if ( !(f->flags & MKD_1_COMPAT) )
  333. return linkybroket(f,image,p);
  334. mayneedtotrim=1;
  335. }
  336. T(p->link) = cursor(f);
  337. for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) {
  338. if ( c == EOF )
  339. return 0;
  340. else if ( (c == '"' || c == '\'') && linkytitle(f, c, p) )
  341. break;
  342. else if ( image && (c == '=') && linkysize(f, p) )
  343. break;
  344. else if ( (c == '\\') && ispunct(peek(f,2)) ) {
  345. ++S(p->link);
  346. pull(f);
  347. }
  348. pull(f);
  349. }
  350. if ( peek(f, 1) == ')' )
  351. pull(f);
  352. ___mkd_tidy(&p->link);
  353. if ( mayneedtotrim && (T(p->link)[S(p->link)-1] == '>') )
  354. --S(p->link);
  355. return 1;
  356. }
  357. /* prefixes for <automatic links>
  358. */
  359. static struct _protocol {
  360. char *name;
  361. int nlen;
  362. } protocol[] = {
  363. #define _aprotocol(x) { x, (sizeof x)-1 }
  364. _aprotocol( "https:" ),
  365. _aprotocol( "http:" ),
  366. _aprotocol( "news:" ),
  367. _aprotocol( "ftp:" ),
  368. #undef _aprotocol
  369. };
  370. #define NRPROTOCOLS (sizeof protocol / sizeof protocol[0])
  371. static int
  372. isautoprefix(char *text, int size)
  373. {
  374. int i;
  375. struct _protocol *p;
  376. for (i=0, p=protocol; i < NRPROTOCOLS; i++, p++)
  377. if ( (size >= p->nlen) && strncasecmp(text, p->name, p->nlen) == 0 )
  378. return 1;
  379. return 0;
  380. }
  381. /*
  382. * all the tag types that linkylinky can produce are
  383. * defined by this structure.
  384. */
  385. typedef struct linkytype {
  386. char *pat;
  387. int szpat;
  388. char *link_pfx; /* tag prefix and link pointer (eg: "<a href="\"" */
  389. char *link_sfx; /* link suffix (eg: "\"" */
  390. int WxH; /* this tag allows width x height arguments */
  391. char *text_pfx; /* text prefix (eg: ">" */
  392. char *text_sfx; /* text suffix (eg: "</a>" */
  393. int flags; /* reparse flags */
  394. int kind; /* tag is url or something else? */
  395. #define IS_URL 0x01
  396. } linkytype;
  397. static linkytype imaget = { 0, 0, "<img src=\"", "\"",
  398. 1, " alt=\"", "\" />", MKD_NOIMAGE|MKD_TAGTEXT, IS_URL };
  399. static linkytype linkt = { 0, 0, "<a href=\"", "\"",
  400. 0, ">", "</a>", MKD_NOLINKS, IS_URL };
  401. /*
  402. * pseudo-protocols for [][];
  403. *
  404. * id: generates <a id="link">tag</a>
  405. * class: generates <span class="link">tag</span>
  406. * raw: just dump the link without any processing
  407. */
  408. static linkytype specials[] = {
  409. { "id:", 3, "<span id=\"", "\"", 0, ">", "</span>", 0, 0 },
  410. { "raw:", 4, 0, 0, 0, 0, 0, MKD_NOHTML, 0 },
  411. { "lang:", 5, "<span lang=\"", "\"", 0, ">", "</span>", 0, 0 },
  412. { "abbr:", 5, "<abbr title=\"", "\"", 0, ">", "</abbr>", 0, 0 },
  413. { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0, 0 },
  414. } ;
  415. #define NR(x) (sizeof x / sizeof x[0])
  416. /* see if t contains one of our pseudo-protocols.
  417. */
  418. static linkytype *
  419. pseudo(Cstring t)
  420. {
  421. int i;
  422. linkytype *r;
  423. for ( i=0, r=specials; i < NR(specials); i++,r++ ) {
  424. if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) )
  425. return r;
  426. }
  427. return 0;
  428. }
  429. /* print out the start of an `img' or `a' tag, applying callbacks as needed.
  430. */
  431. static void
  432. printlinkyref(MMIOT *f, linkytype *tag, char *link, int size)
  433. {
  434. char *edit;
  435. if ( f->flags & IS_LABEL )
  436. return;
  437. Qstring(tag->link_pfx, f);
  438. if ( tag->kind & IS_URL ) {
  439. if ( f->cb && f->cb->e_url && (edit = (*f->cb->e_url)(link, size, f->cb->e_data)) ) {
  440. puturl(edit, strlen(edit), f, 0);
  441. if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
  442. }
  443. else
  444. puturl(link + tag->szpat, size - tag->szpat, f, 0);
  445. }
  446. else
  447. ___mkd_reparse(link + tag->szpat, size - tag->szpat, MKD_TAGTEXT, f);
  448. Qstring(tag->link_sfx, f);
  449. if ( f->cb && f->cb->e_flags && (edit = (*f->cb->e_flags)(link, size, f->cb->e_data)) ) {
  450. Qchar(' ', f);
  451. Qstring(edit, f);
  452. if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
  453. }
  454. } /* printlinkyref */
  455. /* helper function for php markdown extra footnotes; allow the user to
  456. * define a prefix tag instead of just `fn`
  457. */
  458. static char *
  459. p_or_nothing(p)
  460. MMIOT *p;
  461. {
  462. return p->ref_prefix ? p->ref_prefix : "fn";
  463. }
  464. /* php markdown extra/daring fireball style print footnotes
  465. */
  466. static int
  467. extra_linky(MMIOT *f, Cstring text, Footnote *ref)
  468. {
  469. if ( ref->flags & REFERENCED )
  470. return 0;
  471. if ( f->flags & IS_LABEL )
  472. ___mkd_reparse(T(text), S(text), linkt.flags, f);
  473. else {
  474. ref->flags |= REFERENCED;
  475. ref->refnumber = ++ f->reference;
  476. Qprintf(f, "<sup id=\"%sref:%d\"><a href=\"#%s:%d\" rel=\"footnote\">%d</a></sup>",
  477. p_or_nothing(f), ref->refnumber,
  478. p_or_nothing(f), ref->refnumber, ref->refnumber);
  479. }
  480. return 1;
  481. } /* extra_linky */
  482. /* print out a linky (or fail if it's Not Allowed)
  483. */
  484. static int
  485. linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
  486. {
  487. linkytype *tag;
  488. if ( image || (ref == 0) )
  489. tag = &imaget;
  490. else if ( tag = pseudo(ref->link) ) {
  491. if ( f->flags & (MKD_NO_EXT|MKD_SAFELINK) )
  492. return 0;
  493. }
  494. else if ( (f->flags & MKD_SAFELINK) && T(ref->link)
  495. && (T(ref->link)[0] != '/')
  496. && !isautoprefix(T(ref->link), S(ref->link)) )
  497. /* if MKD_SAFELINK, only accept links that are local or
  498. * a well-known protocol
  499. */
  500. return 0;
  501. else
  502. tag = &linkt;
  503. if ( f->flags & tag->flags )
  504. return 0;
  505. if ( f->flags & IS_LABEL )
  506. ___mkd_reparse(T(text), S(text), tag->flags, f);
  507. else if ( tag->link_pfx ) {
  508. printlinkyref(f, tag, T(ref->link), S(ref->link));
  509. if ( tag->WxH ) {
  510. if ( ref->height ) Qprintf(f," height=\"%d\"", ref->height);
  511. if ( ref->width ) Qprintf(f, " width=\"%d\"", ref->width);
  512. }
  513. if ( S(ref->title) ) {
  514. Qstring(" title=\"", f);
  515. ___mkd_reparse(T(ref->title), S(ref->title), MKD_TAGTEXT, f);
  516. Qchar('"', f);
  517. }
  518. Qstring(tag->text_pfx, f);
  519. ___mkd_reparse(T(text), S(text), tag->flags, f);
  520. Qstring(tag->text_sfx, f);
  521. }
  522. else
  523. Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f);
  524. return 1;
  525. } /* linkyformat */
  526. /*
  527. * process embedded links and images
  528. */
  529. static int
  530. linkylinky(int image, MMIOT *f)
  531. {
  532. int start = mmiottell(f);
  533. Cstring name;
  534. Footnote key, *ref;
  535. int status = 0;
  536. int extra_footnote = 0;
  537. CREATE(name);
  538. memset(&key, 0, sizeof key);
  539. if ( linkylabel(f, &name) ) {
  540. if ( peek(f,1) == '(' ) {
  541. pull(f);
  542. if ( linkyurl(f, image, &key) )
  543. status = linkyformat(f, name, image, &key);
  544. }
  545. else {
  546. int goodlink, implicit_mark = mmiottell(f);
  547. if ( isspace(peek(f,1)) )
  548. pull(f);
  549. if ( peek(f,1) == '[' ) {
  550. pull(f); /* consume leading '[' */
  551. goodlink = linkylabel(f, &key.tag);
  552. }
  553. else {
  554. /* new markdown implicit name syntax doesn't
  555. * require a second []
  556. */
  557. mmiotseek(f, implicit_mark);
  558. goodlink = !(f->flags & MKD_1_COMPAT);
  559. if ( (f->flags & MKD_EXTRA_FOOTNOTE) && (!image) && S(name) && T(name)[0] == '^' )
  560. extra_footnote = 1;
  561. }
  562. if ( goodlink ) {
  563. if ( !S(key.tag) ) {
  564. DELETE(key.tag);
  565. T(key.tag) = T(name);
  566. S(key.tag) = S(name);
  567. }
  568. if ( ref = bsearch(&key, T(*f->footnotes), S(*f->footnotes),
  569. sizeof key, (stfu)__mkd_footsort) ) {
  570. if ( extra_footnote )
  571. status = extra_linky(f,name,ref);
  572. else
  573. status = linkyformat(f, name, image, ref);
  574. }
  575. else if ( f->flags & IS_LABEL )
  576. status = linkyformat(f, name, image, 0);
  577. }
  578. }
  579. }
  580. DELETE(name);
  581. ___mkd_freefootnote(&key);
  582. if ( status == 0 )
  583. mmiotseek(f, start);
  584. return status;
  585. }
  586. /* write a character to output, doing text escapes ( & -> &amp;,
  587. * > -> &gt; < -> &lt; )
  588. */
  589. static void
  590. cputc(int c, MMIOT *f)
  591. {
  592. switch (c) {
  593. case '&': Qstring("&amp;", f); break;
  594. case '>': Qstring("&gt;", f); break;
  595. case '<': Qstring("&lt;", f); break;
  596. default : Qchar(c, f); break;
  597. }
  598. }
  599. /*
  600. * convert an email address to a string of nonsense
  601. */
  602. static void
  603. mangle(char *s, int len, MMIOT *f)
  604. {
  605. while ( len-- > 0 ) {
  606. Qstring("&#", f);
  607. Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) );
  608. }
  609. }
  610. /* nrticks() -- count up a row of tick marks
  611. */
  612. static int
  613. nrticks(int offset, int tickchar, MMIOT *f)
  614. {
  615. int tick = 0;
  616. while ( peek(f, offset+tick) == tickchar ) tick++;
  617. return tick;
  618. } /* nrticks */
  619. /* matchticks() -- match a certain # of ticks, and if that fails
  620. * match the largest subset of those ticks.
  621. *
  622. * if a subset was matched, return the # of ticks
  623. * that were matched.
  624. */
  625. static int
  626. matchticks(MMIOT *f, int tickchar, int ticks, int *endticks)
  627. {
  628. int size, count, c;
  629. int subsize=0, subtick=0;
  630. *endticks = ticks;
  631. for (size = 0; (c=peek(f,size+ticks)) != EOF; size ++) {
  632. if ( (c == tickchar) && ( count = nrticks(size+ticks,tickchar,f)) ) {
  633. if ( count == ticks )
  634. return size;
  635. else if ( count ) {
  636. if ( (count > subtick) && (count < ticks) ) {
  637. subsize = size;
  638. subtick = count;
  639. }
  640. size += count;
  641. }
  642. }
  643. }
  644. if ( subsize ) {
  645. *endticks = subtick;
  646. return subsize;
  647. }
  648. return 0;
  649. } /* matchticks */
  650. /* code() -- write a string out as code. The only characters that have
  651. * special meaning in a code block are * `<' and `&' , which
  652. * are /always/ expanded to &lt; and &amp;
  653. */
  654. static void
  655. code(MMIOT *f, char *s, int length)
  656. {
  657. int i,c;
  658. for ( i=0; i < length; i++ )
  659. if ( (c = s[i]) == 003) /* ^C: expand back to 2 spaces */
  660. Qstring(" ", f);
  661. else
  662. cputc(c, f);
  663. } /* code */
  664. /* delspan() -- write out a chunk of text, blocking with <del>...</del>
  665. */
  666. static void
  667. delspan(MMIOT *f, int size)
  668. {
  669. Qstring("<del>", f);
  670. ___mkd_reparse(cursor(f)-1, size, 0, f);
  671. Qstring("</del>", f);
  672. }
  673. /* codespan() -- write out a chunk of text as code, trimming one
  674. * space off the front and/or back as appropriate.
  675. */
  676. static void
  677. codespan(MMIOT *f, int size)
  678. {
  679. int i=0;
  680. if ( size > 1 && peek(f, size-1) == ' ' ) --size;
  681. if ( peek(f,i) == ' ' ) ++i, --size;
  682. Qstring("<code>", f);
  683. code(f, cursor(f)+(i-1), size);
  684. Qstring("</code>", f);
  685. } /* codespan */
  686. /* before letting a tag through, validate against
  687. * MKD_NOLINKS and MKD_NOIMAGE
  688. */
  689. static int
  690. forbidden_tag(MMIOT *f)
  691. {
  692. int c = toupper(peek(f, 1));
  693. if ( f->flags & MKD_NOHTML )
  694. return 1;
  695. if ( c == 'A' && (f->flags & MKD_NOLINKS) && !isthisalnum(f,2) )
  696. return 1;
  697. if ( c == 'I' && (f->flags & MKD_NOIMAGE)
  698. && strncasecmp(cursor(f)+1, "MG", 2) == 0
  699. && !isthisalnum(f,4) )
  700. return 1;
  701. return 0;
  702. }
  703. /* Check a string to see if it looks like a mail address
  704. * "looks like a mail address" means alphanumeric + some
  705. * specials, then a `@`, then alphanumeric + some specials,
  706. * but with a `.`
  707. */
  708. static int
  709. maybe_address(char *p, int size)
  710. {
  711. int ok = 0;
  712. for ( ;size && (isalnum(*p) || strchr("._-+*", *p)); ++p, --size)
  713. ;
  714. if ( ! (size && *p == '@') )
  715. return 0;
  716. --size, ++p;
  717. if ( size && *p == '.' ) return 0;
  718. for ( ;size && (isalnum(*p) || strchr("._-+", *p)); ++p, --size )
  719. if ( *p == '.' && size > 1 ) ok = 1;
  720. return size ? 0 : ok;
  721. }
  722. /* The size-length token at cursor(f) is either a mailto:, an
  723. * implicit mailto:, one of the approved url protocols, or just
  724. * plain old text. If it's a mailto: or an approved protocol,
  725. * linkify it, otherwise say "no"
  726. */
  727. static int
  728. process_possible_link(MMIOT *f, int size)
  729. {
  730. int address= 0;
  731. int mailto = 0;
  732. char *text = cursor(f);
  733. if ( f->flags & MKD_NOLINKS ) return 0;
  734. if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) {
  735. /* if it says it's a mailto, it's a mailto -- who am
  736. * I to second-guess the user?
  737. */
  738. address = 1;
  739. mailto = 7; /* 7 is the length of "mailto:"; we need this */
  740. }
  741. else
  742. address = maybe_address(text, size);
  743. if ( address ) {
  744. Qstring("<a href=\"", f);
  745. if ( !mailto ) {
  746. /* supply a mailto: protocol if one wasn't attached */
  747. mangle("mailto:", 7, f);
  748. }
  749. mangle(text, size, f);
  750. Qstring("\">", f);
  751. mangle(text+mailto, size-mailto, f);
  752. Qstring("</a>", f);
  753. return 1;
  754. }
  755. else if ( isautoprefix(text, size) ) {
  756. printlinkyref(f, &linkt, text, size);
  757. Qchar('>', f);
  758. puturl(text,size,f, 1);
  759. Qstring("</a>", f);
  760. return 1;
  761. }
  762. return 0;
  763. } /* process_possible_link */
  764. /* a < may be just a regular character, the start of an embedded html
  765. * tag, or the start of an <automatic link>. If it's an automatic
  766. * link, we also need to know if it's an email address because if it
  767. * is we need to mangle it in our futile attempt to cut down on the
  768. * spaminess of the rendered page.
  769. */
  770. static int
  771. maybe_tag_or_link(MMIOT *f)
  772. {
  773. int c, size;
  774. int maybetag = 1;
  775. if ( f->flags & MKD_TAGTEXT )
  776. return 0;
  777. for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
  778. if ( c == EOF )
  779. return 0;
  780. else if ( c == '\\' ) {
  781. maybetag=0;
  782. if ( peek(f, size+2) != EOF )
  783. size++;
  784. }
  785. else if ( isspace(c) )
  786. break;
  787. #if WITH_GITHUB_TAGS
  788. else if ( ! (c == '/' || c == '-' || c == '_' || isalnum(c) ) )
  789. #else
  790. else if ( ! (c == '/' || isalnum(c) ) )
  791. #endif
  792. maybetag=0;
  793. }
  794. if ( size ) {
  795. if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
  796. /* It is not a html tag unless we find the closing '>' in
  797. * the same block.
  798. */
  799. while ( (c = peek(f, size+1)) != '>' )
  800. if ( c == EOF )
  801. return 0;
  802. else
  803. size++;
  804. if ( forbidden_tag(f) )
  805. return 0;
  806. Qchar('<', f);
  807. while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
  808. Qchar(pull(f), f);
  809. return 1;
  810. }
  811. else if ( !isspace(c) && process_possible_link(f, size) ) {
  812. shift(f, size+1);
  813. return 1;
  814. }
  815. }
  816. return 0;
  817. }
  818. /* autolinking means that all inline html is <a href'ified>. A
  819. * autolink url is alphanumerics, slashes, periods, underscores,
  820. * the at sign, colon, and the % character.
  821. */
  822. static int
  823. maybe_autolink(MMIOT *f)
  824. {
  825. register int c;
  826. int size;
  827. /* greedily scan forward for the end of a legitimate link.
  828. */
  829. for ( size=0; (c=peek(f, size+1)) != EOF; size++ )
  830. if ( c == '\\' ) {
  831. if ( peek(f, size+2) != EOF )
  832. ++size;
  833. }
  834. else if ( isspace(c) || strchr("'\"()[]{}<>`", c) )
  835. break;
  836. if ( (size > 1) && process_possible_link(f, size) ) {
  837. shift(f, size);
  838. return 1;
  839. }
  840. return 0;
  841. }
  842. /* smartyquote code that's common for single and double quotes
  843. */
  844. static int
  845. smartyquote(int *flags, char typeofquote, MMIOT *f)
  846. {
  847. int bit = (typeofquote == 's') ? 0x01 : 0x02;
  848. if ( bit & (*flags) ) {
  849. if ( isthisnonword(f,1) ) {
  850. Qprintf(f, "&r%cquo;", typeofquote);
  851. (*flags) &= ~bit;
  852. return 1;
  853. }
  854. }
  855. else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) {
  856. Qprintf(f, "&l%cquo;", typeofquote);
  857. (*flags) |= bit;
  858. return 1;
  859. }
  860. return 0;
  861. }
  862. static int
  863. islike(MMIOT *f, char *s)
  864. {
  865. int len;
  866. int i;
  867. if ( s[0] == '<' ) {
  868. if ( !isthisnonword(f, -1) )
  869. return 0;
  870. ++s;
  871. }
  872. if ( !(len = strlen(s)) )
  873. return 0;
  874. if ( s[len-1] == '>' ) {
  875. if ( !isthisnonword(f,len-1) )
  876. return 0;
  877. len--;
  878. }
  879. for (i=1; i < len; i++)
  880. if (tolower(peek(f,i)) != s[i])
  881. return 0;
  882. return 1;
  883. }
  884. static struct smarties {
  885. char c0;
  886. char *pat;
  887. char *entity;
  888. int shift;
  889. } smarties[] = {
  890. { '\'', "'s>", "rsquo", 0 },
  891. { '\'', "'t>", "rsquo", 0 },
  892. { '\'', "'re>", "rsquo", 0 },
  893. { '\'', "'ll>", "rsquo", 0 },
  894. { '\'', "'ve>", "rsquo", 0 },
  895. { '\'', "'m>", "rsquo", 0 },
  896. { '\'', "'d>", "rsquo", 0 },
  897. { '-', "---", "mdash", 2 },
  898. { '-', "--", "ndash", 1 },
  899. { '.', "...", "hellip", 2 },
  900. { '.', ". . .", "hellip", 4 },
  901. { '(', "(c)", "copy", 2 },
  902. { '(', "(r)", "reg", 2 },
  903. { '(', "(tm)", "trade", 3 },
  904. { '3', "<3/4>", "frac34", 2 },
  905. { '3', "<3/4ths>", "frac34", 2 },
  906. { '1', "<1/2>", "frac12", 2 },
  907. { '1', "<1/4>", "frac14", 2 },
  908. { '1', "<1/4th>", "frac14", 2 },
  909. { '&', "&#0;", 0, 3 },
  910. } ;
  911. #define NRSMART ( sizeof smarties / sizeof smarties[0] )
  912. /* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm)
  913. */
  914. static int
  915. smartypants(int c, int *flags, MMIOT *f)
  916. {
  917. int i;
  918. if ( f->flags & (MKD_NOPANTS|MKD_TAGTEXT|IS_LABEL) )
  919. return 0;
  920. for ( i=0; i < NRSMART; i++)
  921. if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) {
  922. if ( smarties[i].entity )
  923. Qprintf(f, "&%s;", smarties[i].entity);
  924. shift(f, smarties[i].shift);
  925. return 1;
  926. }
  927. switch (c) {
  928. case '<' : return 0;
  929. case '\'': if ( smartyquote(flags, 's', f) ) return 1;
  930. break;
  931. case '"': if ( smartyquote(flags, 'd', f) ) return 1;
  932. break;
  933. case '`': if ( peek(f, 1) == '`' ) {
  934. int j = 2;
  935. while ( (c=peek(f,j)) != EOF ) {
  936. if ( c == '\\' )
  937. j += 2;
  938. else if ( c == '`' )
  939. break;
  940. else if ( c == '\'' && peek(f, j+1) == '\'' ) {
  941. Qstring("&ldquo;", f);
  942. ___mkd_reparse(cursor(f)+1, j-2, 0, f);
  943. Qstring("&rdquo;", f);
  944. shift(f,j+1);
  945. return 1;
  946. }
  947. else ++j;
  948. }
  949. }
  950. break;
  951. }
  952. return 0;
  953. } /* smartypants */
  954. /* process a body of text encased in some sort of tick marks. If it
  955. * works, generate the output and return 1, otherwise just return 0 and
  956. * let the caller figure it out.
  957. */
  958. static int
  959. tickhandler(MMIOT *f, int tickchar, int minticks, int allow_space, spanhandler spanner)
  960. {
  961. int endticks, size;
  962. int tick = nrticks(0, tickchar, f);
  963. if ( !allow_space && isspace(peek(f,tick)) )
  964. return 0;
  965. if ( (tick >= minticks) && (size = matchticks(f,tickchar,tick,&endticks)) ) {
  966. if ( endticks < tick ) {
  967. size += (tick - endticks);
  968. tick = endticks;
  969. }
  970. shift(f, tick);
  971. (*spanner)(f,size);
  972. shift(f, size+tick-1);
  973. return 1;
  974. }
  975. return 0;
  976. }
  977. #define tag_text(f) (f->flags & MKD_TAGTEXT)
  978. static void
  979. text(MMIOT *f)
  980. {
  981. int c, j;
  982. int rep;
  983. int smartyflags = 0;
  984. while (1) {
  985. if ( (f->flags & MKD_AUTOLINK) && isalpha(peek(f,1)) && !tag_text(f) )
  986. maybe_autolink(f);
  987. c = pull(f);
  988. if (c == EOF)
  989. break;
  990. if ( smartypants(c, &smartyflags, f) )
  991. continue;
  992. switch (c) {
  993. case 0: break;
  994. case 3: Qstring(tag_text(f) ? " " : "<br/>", f);
  995. break;
  996. case '>': if ( tag_text(f) )
  997. Qstring("&gt;", f);
  998. else
  999. Qchar(c, f);
  1000. break;
  1001. case '"': if ( tag_text(f) )
  1002. Qstring("&quot;", f);
  1003. else
  1004. Qchar(c, f);
  1005. break;
  1006. case '!': if ( peek(f,1) == '[' ) {
  1007. pull(f);
  1008. if ( tag_text(f) || !linkylinky(1, f) )
  1009. Qstring("![", f);
  1010. }
  1011. else
  1012. Qchar(c, f);
  1013. break;
  1014. case '[': if ( tag_text(f) || !linkylinky(0, f) )
  1015. Qchar(c, f);
  1016. break;
  1017. /* A^B -> A<sup>B</sup> */
  1018. case '^': if ( (f->flags & (MKD_NOSUPERSCRIPT|MKD_STRICT|MKD_TAGTEXT))
  1019. || (isthisnonword(f,-1) && peek(f,-1) != ')')
  1020. || isthisspace(f,1) )
  1021. Qchar(c,f);
  1022. else {
  1023. char *sup = cursor(f);
  1024. int len = 0;
  1025. if ( peek(f,1) == '(' ) {
  1026. int here = mmiottell(f);
  1027. pull(f);
  1028. if ( (len = parenthetical('(',')',f)) <= 0 ) {
  1029. mmiotseek(f,here);
  1030. Qchar(c, f);
  1031. break;
  1032. }
  1033. sup++;
  1034. }
  1035. else {
  1036. while ( isthisalnum(f,1+len) )
  1037. ++len;
  1038. if ( !len ) {
  1039. Qchar(c,f);
  1040. break;
  1041. }
  1042. shift(f,len);
  1043. }
  1044. Qstring("<sup>",f);
  1045. ___mkd_reparse(sup, len, 0, f);
  1046. Qstring("</sup>", f);
  1047. }
  1048. break;
  1049. case '_':
  1050. /* Underscores don't count if they're in the middle of a word */
  1051. if ( !(f->flags & (MKD_NORELAXED|MKD_STRICT))
  1052. && isthisalnum(f,-1)
  1053. && isthisalnum(f,1) ) {
  1054. Qchar(c, f);
  1055. break;
  1056. }
  1057. case '*':
  1058. /* Underscores & stars don't count if they're out in the middle
  1059. * of whitespace */
  1060. if ( isthisspace(f,-1) && isthisspace(f,1) ) {
  1061. Qchar(c, f);
  1062. break;
  1063. }
  1064. /* else fall into the regular old emphasis case */
  1065. if ( tag_text(f) )
  1066. Qchar(c, f);
  1067. else {
  1068. for (rep = 1; peek(f,1) == c; pull(f) )
  1069. ++rep;
  1070. Qem(f,c,rep);
  1071. }
  1072. break;
  1073. case '~': if ( (f->flags & (MKD_NOSTRIKETHROUGH|MKD_TAGTEXT|MKD_STRICT)) || ! tickhandler(f,c,2,0, delspan) )
  1074. Qchar(c, f);
  1075. break;
  1076. case '`': if ( tag_text(f) || !tickhandler(f,c,1,1,codespan) )
  1077. Qchar(c, f);
  1078. break;
  1079. case '\\': switch ( c = pull(f) ) {
  1080. case '&': Qstring("&amp;", f);
  1081. break;
  1082. case '<': Qstring("&lt;", f);
  1083. break;
  1084. case '^': if ( f->flags & (MKD_STRICT|MKD_NOSUPERSCRIPT) ) {
  1085. Qchar('\\', f);
  1086. shift(f,-1);
  1087. break;
  1088. }
  1089. Qchar(c, f);
  1090. break;
  1091. case ':': case '|':
  1092. if ( f->flags & MKD_NOTABLES ) {
  1093. Qchar('\\', f);
  1094. shift(f,-1);
  1095. break;
  1096. }
  1097. Qchar(c, f);
  1098. break;
  1099. case '>': case '#': case '.': case '-':
  1100. case '+': case '{': case '}': case ']':
  1101. case '!': case '[': case '*': case '_':
  1102. case '\\':case '(': case ')':
  1103. case '`': Qchar(c, f);
  1104. break;
  1105. default:
  1106. Qchar('\\', f);
  1107. if ( c != EOF )
  1108. shift(f,-1);
  1109. break;
  1110. }
  1111. break;
  1112. case '<': if ( !maybe_tag_or_link(f) )
  1113. Qstring("&lt;", f);
  1114. break;
  1115. case '&': j = (peek(f,1) == '#' ) ? 2 : 1;
  1116. while ( isthisalnum(f,j) )
  1117. ++j;
  1118. if ( peek(f,j) != ';' )
  1119. Qstring("&amp;", f);
  1120. else
  1121. Qchar(c, f);
  1122. break;
  1123. default: Qchar(c, f);
  1124. break;
  1125. }
  1126. }
  1127. /* truncate the input string after we've finished processing it */
  1128. S(f->in) = f->isp = 0;
  1129. } /* text */
  1130. /* print a header block
  1131. */
  1132. static void
  1133. printheader(Paragraph *pp, MMIOT *f)
  1134. {
  1135. #if WITH_ID_ANCHOR
  1136. Qprintf(f, "<h%d", pp->hnumber);
  1137. if ( f->flags & MKD_TOC ) {
  1138. Qstring(" id=\"", f);
  1139. mkd_string_to_anchor(T(pp->text->text),
  1140. S(pp->text->text),
  1141. (mkd_sta_function_t)Qchar, f, 1);
  1142. Qchar('"', f);
  1143. }
  1144. Qchar('>', f);
  1145. #else
  1146. if ( f->flags & MKD_TOC ) {
  1147. Qstring("<a name=\"", f);
  1148. mkd_string_to_anchor(T(pp->text->text),
  1149. S(pp->text->text),
  1150. (mkd_sta_function_t)Qchar, f, 1);
  1151. Qstring("\"></a>\n", f);
  1152. }
  1153. Qprintf(f, "<h%d>", pp->hnumber);
  1154. #endif
  1155. push(T(pp->text->text), S(pp->text->text), f);
  1156. text(f);
  1157. Qprintf(f, "</h%d>", pp->hnumber);
  1158. }
  1159. enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT };
  1160. static char* alignments[] = { "", " align=\"center\"", " align=\"left\"",
  1161. " align=\"right\"" };
  1162. typedef STRING(int) Istring;
  1163. static int
  1164. splat(Line *p, int leading_pipe, char *block, Istring align, int force, MMIOT *f)
  1165. {
  1166. int first,
  1167. idx = p->dle,
  1168. colno = 0;
  1169. if ( leading_pipe ) idx++;
  1170. ___mkd_tidy(&p->text);
  1171. if ( T(p->text)[S(p->text)-1] == '|' )
  1172. --S(p->text);
  1173. Qstring("<tr>\n", f);
  1174. while ( idx < S(p->text) ) {
  1175. first = idx;
  1176. if ( force && (colno >= S(align)-1) )
  1177. idx = S(p->text);
  1178. else
  1179. while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') ) {
  1180. if ( T(p->text)[idx] == '\\' )
  1181. ++idx;
  1182. ++idx;
  1183. }
  1184. Qprintf(f, "<%s%s>",
  1185. block,
  1186. alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]);
  1187. ___mkd_reparse(T(p->text)+first, idx-first, 0, f);
  1188. Qprintf(f, "</%s>\n", block);
  1189. idx++;
  1190. colno++;
  1191. }
  1192. if ( force )
  1193. while (colno < S(align) ) {
  1194. Qprintf(f, "<%s></%s>\n", block, block);
  1195. ++colno;
  1196. }
  1197. Qstring("</tr>\n", f);
  1198. return colno;
  1199. }
  1200. static int
  1201. printtable(Paragraph *pp, MMIOT *f)
  1202. {
  1203. /* header, dashes, then lines of content */
  1204. Line *hdr, *dash, *body;
  1205. Istring align;
  1206. int hcols,start,starts_with_pipe=0;
  1207. char *p;
  1208. hdr = pp->text;
  1209. dash= hdr->next;
  1210. body= dash->next;
  1211. starts_with_pipe = T(hdr->text)[hdr->dle] == '|';
  1212. /* first figure out cell alignments */
  1213. CREATE(align);
  1214. for (p=T(dash->text), start=0; start < S(dash->text); ) {
  1215. char first, last;
  1216. int end;
  1217. last=first=0;
  1218. for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
  1219. if ( p[end] == '\\' )
  1220. ++ end;
  1221. else if ( !isspace(p[end]) ) {
  1222. if ( !first) first = p[end];
  1223. last = p[end];
  1224. }
  1225. }
  1226. EXPAND(align) = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT)
  1227. : (( last == ':') ? a_RIGHT : a_NONE );
  1228. start = 1+end;
  1229. }
  1230. Qstring("<table>\n", f);
  1231. Qstring("<thead>\n", f);
  1232. hcols = splat(hdr, starts_with_pipe, "th", align, 0, f);
  1233. Qstring("</thead>\n", f);
  1234. if ( hcols < S(align) )
  1235. S(align) = hcols;
  1236. else
  1237. while ( hcols > S(align) )
  1238. EXPAND(align) = a_NONE;
  1239. Qstring("<tbody>\n", f);
  1240. for ( ; body; body = body->next)
  1241. splat(body, starts_with_pipe, "td", align, 1, f);
  1242. Qstring("</tbody>\n", f);
  1243. Qstring("</table>\n", f);
  1244. DELETE(align);
  1245. return 1;
  1246. }
  1247. static int
  1248. printblock(Paragraph *pp, MMIOT *f)
  1249. {
  1250. Line *t = pp->text;
  1251. static char *Begin[] = { "", "<p>", "<p style=\"text-align:center;\">" };
  1252. static char *End[] = { "", "</p>","</p>" };
  1253. while (t) {
  1254. if ( S(t->text) ) {
  1255. if ( t->next && S(t->text) > 2
  1256. && T(t->text)[S(t->text)-2] == ' '
  1257. && T(t->text)[S(t->text)-1] == ' ' ) {
  1258. push(T(t->text), S(t->text)-2, f);
  1259. push("\003\n", 2, f);
  1260. }
  1261. else {
  1262. ___mkd_tidy(&t->text);
  1263. push(T(t->text), S(t->text), f);
  1264. if ( t->next )
  1265. push("\n", 1, f);
  1266. }
  1267. }
  1268. t = t->next;
  1269. }
  1270. Qstring(Begin[pp->align], f);
  1271. text(f);
  1272. Qstring(End[pp->align], f);
  1273. return 1;
  1274. }
  1275. static void
  1276. printcode(Line *t, MMIOT *f)
  1277. {
  1278. int blanks;
  1279. Qstring("<pre><code>", f);
  1280. for ( blanks = 0; t ; t = t->next ) {
  1281. if ( S(t->text) > t->dle ) {
  1282. while ( blanks ) {
  1283. Qchar('\n', f);
  1284. --blanks;
  1285. }
  1286. code(f, T(t->text), S(t->text));
  1287. Qchar('\n', f);
  1288. }
  1289. else blanks++;
  1290. }
  1291. Qstring("</code></pre>", f);
  1292. }
  1293. static void
  1294. printhtml(Line *t, MMIOT *f)
  1295. {
  1296. int blanks;
  1297. for ( blanks=0; t ; t = t->next )
  1298. if ( S(t->text) ) {
  1299. for ( ; blanks; --blanks )
  1300. Qchar('\n', f);
  1301. Qwrite(T(t->text), S(t->text), f);
  1302. Qchar('\n', f);
  1303. }
  1304. else
  1305. blanks++;
  1306. }
  1307. static void
  1308. htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
  1309. {
  1310. ___mkd_emblock(f);
  1311. if ( block )
  1312. Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
  1313. ___mkd_emblock(f);
  1314. while (( p = display(p, f) )) {
  1315. ___mkd_emblock(f);
  1316. Qstring("\n\n", f);
  1317. }
  1318. if ( block )
  1319. Qprintf(f, "</%s>", block);
  1320. ___mkd_emblock(f);
  1321. }
  1322. static void
  1323. definitionlist(Paragraph *p, MMIOT *f)
  1324. {
  1325. Line *tag;
  1326. if ( p ) {
  1327. Qstring("<dl>\n", f);
  1328. for ( ; p ; p = p->next) {
  1329. for ( tag = p->text; tag; tag = tag->next ) {
  1330. Qstring("<dt>", f);
  1331. ___mkd_reparse(T(tag->text), S(tag->text), 0, f);
  1332. Qstring("</dt>\n", f);
  1333. }
  1334. htmlify(p->down, "dd", p->ident, f);
  1335. Qchar('\n', f);
  1336. }
  1337. Qstring("</dl>", f);
  1338. }
  1339. }
  1340. static void
  1341. listdisplay(int typ, Paragraph *p, MMIOT* f)
  1342. {
  1343. if ( p ) {
  1344. Qprintf(f, "<%cl", (typ==UL)?'u':'o');
  1345. if ( typ == AL )
  1346. Qprintf(f, " type=\"a\"");
  1347. Qprintf(f, ">\n");
  1348. for ( ; p ; p = p->next ) {
  1349. htmlify(p->down, "li", p->ident, f);
  1350. Qchar('\n', f);
  1351. }
  1352. Qprintf(f, "</%cl>\n", (typ==UL)?'u':'o');
  1353. }
  1354. }
  1355. /* dump out a Paragraph in the desired manner
  1356. */
  1357. static Paragraph*
  1358. display(Paragraph *p, MMIOT *f)
  1359. {
  1360. if ( !p ) return 0;
  1361. switch ( p->typ ) {
  1362. case STYLE:
  1363. case WHITESPACE:
  1364. break;
  1365. case HTML:
  1366. printhtml(p->text, f);
  1367. break;
  1368. case CODE:
  1369. printcode(p->text, f);
  1370. break;
  1371. case QUOTE:
  1372. htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f);
  1373. break;
  1374. case UL:
  1375. case OL:
  1376. case AL:
  1377. listdisplay(p->typ, p->down, f);
  1378. break;
  1379. case DL:
  1380. definitionlist(p->down, f);
  1381. break;
  1382. case HR:
  1383. Qstring("<hr />", f);
  1384. break;
  1385. case HDR:
  1386. printheader(p, f);
  1387. break;
  1388. case TABLE:
  1389. printtable(p, f);
  1390. break;
  1391. case SOURCE:
  1392. htmlify(p->down, 0, 0, f);
  1393. break;
  1394. default:
  1395. printblock(p, f);
  1396. break;
  1397. }
  1398. return p->next;
  1399. }
  1400. /* dump out a list of footnotes
  1401. */
  1402. static void
  1403. mkd_extra_footnotes(MMIOT *m)
  1404. {
  1405. int j, i;
  1406. Footnote *t;
  1407. if ( m->reference == 0 )
  1408. return;
  1409. Csprintf(&m->out, "\n<div class=\"footnotes\">\n<hr/>\n<ol>\n");
  1410. for ( i=1; i <= m->reference; i++ ) {
  1411. for ( j=0; j < S(*m->footnotes); j++ ) {
  1412. t = &T(*m->footnotes)[j];
  1413. if ( (t->refnumber == i) && (t->flags & REFERENCED) ) {
  1414. Csprintf(&m->out, "<li id=\"%s:%d\">\n<p>",
  1415. p_or_nothing(m), t->refnumber);
  1416. Csreparse(&m->out, T(t->title), S(t->title), 0);
  1417. Csprintf(&m->out, "<a href=\"#%sref:%d\" rev=\"footnote\">&#8617;</a>",
  1418. p_or_nothing(m), t->refnumber);
  1419. Csprintf(&m->out, "</p></li>\n");
  1420. }
  1421. }
  1422. }
  1423. Csprintf(&m->out, "</ol>\n</div>\n");
  1424. }
  1425. /* return a pointer to the compiled markdown
  1426. * document.
  1427. */
  1428. int
  1429. mkd_document(Document *p, char **res)
  1430. {
  1431. int size;
  1432. if ( p && p->compiled ) {
  1433. if ( ! p->html ) {
  1434. htmlify(p->code, 0, 0, p->ctx);
  1435. if ( p->ctx->flags & MKD_EXTRA_FOOTNOTE )
  1436. mkd_extra_footnotes(p->ctx);
  1437. p->html = 1;
  1438. }
  1439. size = S(p->ctx->out);
  1440. if ( (size == 0) || T(p->ctx->out)[size-1] )
  1441. EXPAND(p->ctx->out) = 0;
  1442. *res = T(p->ctx->out);
  1443. return size;
  1444. }
  1445. return EOF;
  1446. }