generate.c 40 KB

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