generate.c 38 KB

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