generate.c 41 KB

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