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.

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