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.

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