PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/doxygen-1.8.1.1/src/markdown.cpp

#
C++ | 2283 lines | 2211 code | 24 blank | 48 comment | 49 complexity | 77f760e512a0abe69028d6c2a55ac69d MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. /******************************************************************************
  2. *
  3. * Copyright (C) 1997-2012 by Dimitri van Heesch.
  4. *
  5. * Permission to use, copy, modify, and distribute this software and its
  6. * documentation under the terms of the GNU General Public License is hereby
  7. * granted. No representations are made about the suitability of this software
  8. * for any purpose. It is provided "as is" without express or implied warranty.
  9. * See the GNU General Public License for more details.
  10. *
  11. * Documents produced by Doxygen are derivative works derived from the
  12. * input used in their production; they are not affected by this license.
  13. *
  14. */
  15. /* Note: part of the code below is inspired by libupskirt written by
  16. * Natacha Port?Š. Original copyright message follows:
  17. *
  18. * Copyright (c) 2008, Natacha Port?Š
  19. *
  20. * Permission to use, copy, modify, and distribute this software for any
  21. * purpose with or without fee is hereby granted, provided that the above
  22. * copyright notice and this permission notice appear in all copies.
  23. *
  24. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  25. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  26. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  27. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  28. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  29. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  30. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  31. */
  32. #include <stdio.h>
  33. #include <qglobal.h>
  34. #include <qregexp.h>
  35. #include <qfileinfo.h>
  36. #include <qdict.h>
  37. #include "markdown.h"
  38. #include "growbuf.h"
  39. #include "debug.h"
  40. #include "util.h"
  41. #include "doxygen.h"
  42. #include "commentscan.h"
  43. #include "entry.h"
  44. #include "bufstr.h"
  45. #include "commentcnv.h"
  46. //-----------
  47. // is character at position i in data part of an identifier?
  48. #define isIdChar(i) \
  49. ((data[i]>='a' && data[i]<='z') || \
  50. (data[i]>='A' && data[i]<='Z') || \
  51. (data[i]>='0' && data[i]<='9')) \
  52. // is character at position i in data allowed before an emphasis section
  53. #define isOpenEmphChar(i) \
  54. (data[i]=='\n' || data[i]==' ' || data[i]=='\'' || data[i]=='<' || \
  55. data[i]=='{' || data[i]=='(' || data[i]=='[' || data[i]==',' || \
  56. data[i]==':' || data[i]==';')
  57. // is character at position i in data an escape that prevents ending an emphasis section
  58. // so for example *bla (*.txt) is cool*
  59. #define ignoreCloseEmphChar(i) \
  60. (data[i]=='(' || data[i]=='{' || data[i]=='[' || data[i]=='<' || \
  61. data[i]=='=' || data[i]=='+' || data[i]=='-' || data[i]=='\\' || \
  62. data[i]=='@')
  63. //----------
  64. struct LinkRef
  65. {
  66. LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
  67. QCString link;
  68. QCString title;
  69. };
  70. typedef int (*action_t)(GrowBuf &out,const char *data,int offset,int size);
  71. enum Alignment { AlignNone, AlignLeft, AlignCenter, AlignRight };
  72. //----------
  73. static QDict<LinkRef> g_linkRefs(257);
  74. static action_t g_actions[256];
  75. static Entry *g_current;
  76. static QCString g_fileName;
  77. // In case a markdown page starts with a level1 header, that header is used
  78. // as a title of the page, in effect making it a level0 header, so the
  79. // level of all other sections needs to be corrected as well.
  80. // This flag is TRUE if corrections are needed.
  81. static bool g_correctSectionLevel;
  82. //----------
  83. const int codeBlockIndent = 4;
  84. static void processInline(GrowBuf &out,const char *data,int size);
  85. // escape characters that have a special meaning later on.
  86. static QCString escapeSpecialChars(const QCString &s)
  87. {
  88. if (s.isEmpty()) return "";
  89. GrowBuf growBuf;
  90. const char *p=s;
  91. char c;
  92. while ((c=*p++))
  93. {
  94. switch (c)
  95. {
  96. case '<': growBuf.addStr("\\<"); break;
  97. case '>': growBuf.addStr("\\>"); break;
  98. case '\\': growBuf.addStr("\\\\"); break;
  99. case '@': growBuf.addStr("\\@"); break;
  100. default: growBuf.addChar(c); break;
  101. }
  102. }
  103. growBuf.addChar(0);
  104. return growBuf.get();
  105. }
  106. static void convertStringFragment(QCString &result,const char *data,int size)
  107. {
  108. if (size<0) size=0;
  109. result.resize(size+1);
  110. memcpy(result.data(),data,size);
  111. result.at(size)='\0';
  112. }
  113. /** helper function to convert presence of left and/or right alignment markers
  114. * to a alignment value
  115. */
  116. static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
  117. {
  118. //printf("markerToAlignment(%d,%d)\n",leftMarker,rightMarker);
  119. if (leftMarker && rightMarker)
  120. {
  121. return AlignCenter;
  122. }
  123. else if (leftMarker)
  124. {
  125. return AlignLeft;
  126. }
  127. else if (rightMarker)
  128. {
  129. return AlignRight;
  130. }
  131. else
  132. {
  133. return AlignNone;
  134. }
  135. }
  136. // Check if data contains a block command. If so returned the command
  137. // that ends the block. If not an empty string is returned.
  138. // Note When offset>0 character position -1 will be inspected.
  139. //
  140. // Checks for and skip the following block commands:
  141. // {@code .. { .. } .. }
  142. // \dot .. \enddot
  143. // \code .. \endcode
  144. // \msc .. \endmsc
  145. // \f$..\f$
  146. // \f[..\f]
  147. // \f{..\f}
  148. // \verbatim..\endverbatim
  149. // \latexonly..\endlatexonly
  150. // \htmlonly..\endhtmlonly
  151. // \xmlonly..\endxmlonly
  152. // \rtfonly..\endrtfonly
  153. // \manonly..\endmanonly
  154. static QCString isBlockCommand(const char *data,int offset,int size)
  155. {
  156. bool openBracket = offset>0 && data[-1]=='{';
  157. bool isEscaped = offset>0 && (data[-1]=='\\' || data[-1]=='@');
  158. if (isEscaped) return QCString();
  159. int end=1;
  160. while (end<size && (data[end]>='a' && data[end]<='z')) end++;
  161. if (end==1) return QCString();
  162. QCString blockName;
  163. convertStringFragment(blockName,data+1,end-1);
  164. if (blockName=="code" && openBracket)
  165. {
  166. return "}";
  167. }
  168. else if (blockName=="dot" ||
  169. blockName=="code" ||
  170. blockName=="msc" ||
  171. blockName=="verbatim" ||
  172. blockName=="latexonly" ||
  173. blockName=="htmlonly" ||
  174. blockName=="xmlonly" ||
  175. blockName=="rtfonly" ||
  176. blockName=="manonly"
  177. )
  178. {
  179. return "end"+blockName;
  180. }
  181. else if (blockName=="f" && end<size)
  182. {
  183. if (data[end]=='$')
  184. {
  185. return "f$";
  186. }
  187. else if (data[end]=='[')
  188. {
  189. return "f]";
  190. }
  191. else if (data[end]=='}')
  192. {
  193. return "f}";
  194. }
  195. }
  196. return QCString();
  197. }
  198. /** looks for the next emph char, skipping other constructs, and
  199. * stopping when either it is found, or we are at the end of a paragraph.
  200. */
  201. static int findEmphasisChar(const char *data, int size, char c)
  202. {
  203. int i = 1;
  204. while (i<size)
  205. {
  206. while (i<size && data[i]!=c && data[i]!='`' &&
  207. data[i]!='\\' && data[i]!='@' &&
  208. data[i]!='\n') i++;
  209. //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
  210. // not counting escaped chars or characters that are unlikely
  211. // to appear as the end of the emphasis char
  212. if (i>0 && ignoreCloseEmphChar(i-1))
  213. {
  214. i++;
  215. continue;
  216. }
  217. else if (data[i] == c)
  218. {
  219. if (i<size-1 && isIdChar(i+1)) // to prevent touching some_underscore_identifier
  220. {
  221. i++;
  222. continue;
  223. }
  224. return i; // found it
  225. }
  226. // skipping a code span
  227. if (data[i]=='`')
  228. {
  229. int snb=0;
  230. while (i<size && data[i]=='`') snb++,i++;
  231. // find same pattern to end the span
  232. int enb=0;
  233. while (i<size && enb<snb)
  234. {
  235. if (data[i]=='`') enb++;
  236. if (snb==1 && data[i]=='\'') break; // ` ended by '
  237. i++;
  238. }
  239. }
  240. else if (data[i]=='@' || data[i]=='\\')
  241. { // skip over blocks that should not be processed
  242. QCString endBlockName = isBlockCommand(data+i,i,size-i);
  243. if (!endBlockName.isEmpty())
  244. {
  245. i++;
  246. int l = endBlockName.length();
  247. while (i<size-l-1)
  248. {
  249. if ((data[i]=='\\' || data[i]=='@') && // command
  250. data[i-1]!='\\' && data[i-1]!='@') // not escaped
  251. {
  252. if (strncmp(&data[i+1],endBlockName,l)==0)
  253. {
  254. break;
  255. }
  256. }
  257. i++;
  258. }
  259. }
  260. else
  261. {
  262. i++;
  263. }
  264. }
  265. else if (data[i]=='\n') // end * or _ at paragraph boundary
  266. {
  267. i++;
  268. while (i<size && data[i]==' ') i++;
  269. if (i>=size || data[i]=='\n') return 0; // empty line -> paragraph
  270. }
  271. else // should not get here!
  272. {
  273. i++;
  274. }
  275. }
  276. return 0;
  277. }
  278. /** process single emphasis */
  279. static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
  280. {
  281. int i = 0, len;
  282. /* skipping one symbol if coming from emph3 */
  283. if (size>1 && data[0]==c && data[1]==c) { i=1; }
  284. while (i<size)
  285. {
  286. len = findEmphasisChar(data+i, size-i, c);
  287. if (len==0) return 0;
  288. i+=len;
  289. if (i>=size) return 0;
  290. if (i+1<size && data[i+1]==c)
  291. {
  292. i++;
  293. continue;
  294. }
  295. if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
  296. {
  297. out.addStr("<em>");
  298. processInline(out,data,i);
  299. out.addStr("</em>");
  300. return i+1;
  301. }
  302. }
  303. return 0;
  304. }
  305. /** process double emphasis */
  306. static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
  307. {
  308. int i = 0, len;
  309. while (i<size)
  310. {
  311. len = findEmphasisChar(data+i, size-i, c);
  312. if (len==0)
  313. {
  314. return 0;
  315. }
  316. i += len;
  317. if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' &&
  318. data[i-1]!='\n'
  319. )
  320. {
  321. out.addStr("<strong>");
  322. processInline(out,data,i);
  323. out.addStr("</strong>");
  324. return i + 2;
  325. }
  326. i++;
  327. }
  328. return 0;
  329. }
  330. /** Parsing single emphase.
  331. * Finds the first closing tag, and delegates to the other emph
  332. */
  333. static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
  334. {
  335. int i = 0, len;
  336. while (i<size)
  337. {
  338. len = findEmphasisChar(data+i, size-i, c);
  339. if (len==0)
  340. {
  341. return 0;
  342. }
  343. i+=len;
  344. /* skip whitespace preceded symbols */
  345. if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
  346. {
  347. continue;
  348. }
  349. if (i+2<size && data[i+1]==c && data[i+2]==c)
  350. {
  351. out.addStr("<em><strong>");
  352. processInline(out,data,i);
  353. out.addStr("</strong></em>");
  354. return i+3;
  355. }
  356. else if (i+1<size && data[i+1]==c)
  357. {
  358. // double symbol found, handing over to emph1
  359. len = processEmphasis1(out, data-2, size+2, c);
  360. if (len==0)
  361. {
  362. return 0;
  363. }
  364. else
  365. {
  366. return len - 2;
  367. }
  368. }
  369. else
  370. {
  371. // single symbol found, handing over to emph2
  372. len = processEmphasis2(out, data-1, size+1, c);
  373. if (len==0)
  374. {
  375. return 0;
  376. }
  377. else
  378. {
  379. return len - 1;
  380. }
  381. }
  382. }
  383. return 0;
  384. }
  385. static int processHtmlTag(GrowBuf &out,const char *data,int offset,int size)
  386. {
  387. if (offset>0 && data[-1]=='\\') return 0; // escaped <
  388. // find the end of the html tag
  389. int i=1;
  390. int l=0;
  391. // compute length of the tag name
  392. while (i<size && isIdChar(i)) i++,l++;
  393. QCString tagName;
  394. convertStringFragment(tagName,data+1,i-1);
  395. if (tagName.lower()=="pre") // found <pre> tag
  396. {
  397. bool insideStr=FALSE;
  398. while (i<size-6)
  399. {
  400. char c=data[i];
  401. if (!insideStr && c=='<') // potential start of html tag
  402. {
  403. if (data[i+1]=='/' &&
  404. tolower(data[i+2])=='p' && tolower(data[i+3])=='r' &&
  405. tolower(data[i+4])=='e' && tolower(data[i+5])=='>')
  406. { // found </pre> tag, copy from start to end of tag
  407. out.addStr(data,i+6);
  408. //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
  409. return i+6;
  410. }
  411. }
  412. else if (insideStr && c=='"')
  413. {
  414. if (data[i-1]!='\\') insideStr=FALSE;
  415. }
  416. else if (c=='"')
  417. {
  418. insideStr=TRUE;
  419. }
  420. i++;
  421. }
  422. }
  423. else // some other html tag
  424. {
  425. if (l>0 && i<size)
  426. {
  427. if (data[i]=='/' && i<size-1 && data[i+1]=='>') // <bla/>
  428. {
  429. //printf("Found htmlTag={%s}\n",QCString(data).left(i+2).data());
  430. out.addStr(data,i+2);
  431. return i+2;
  432. }
  433. else if (data[i]=='>') // <bla>
  434. {
  435. //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
  436. out.addStr(data,i+1);
  437. return i+1;
  438. }
  439. else if (data[i]==' ') // <bla attr=...
  440. {
  441. i++;
  442. bool insideAttr=FALSE;
  443. while (i<size)
  444. {
  445. if (!insideAttr && data[i]=='"')
  446. {
  447. insideAttr=TRUE;
  448. }
  449. else if (data[i]=='"' && data[i-1]!='\\')
  450. {
  451. insideAttr=FALSE;
  452. }
  453. else if (!insideAttr && data[i]=='>') // found end of tag
  454. {
  455. //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
  456. out.addStr(data,i+1);
  457. return i+1;
  458. }
  459. i++;
  460. }
  461. }
  462. }
  463. }
  464. //printf("Not a valid html tag\n");
  465. return 0;
  466. }
  467. static int processEmphasis(GrowBuf &out,const char *data,int offset,int size)
  468. {
  469. if ((offset>0 && !isOpenEmphChar(-1)) || // invalid char before * or _
  470. (size>1 && data[0]!=data[1] && !isIdChar(1)) || // invalid char after * or _
  471. (size>2 && data[0]==data[1] && !isIdChar(2))) // invalid char after ** or __
  472. {
  473. return 0;
  474. }
  475. #if 0
  476. if (offset>0 && size>1 && (isIdChar(-1) || data[-1]==data[0]))
  477. {
  478. if (isIdChar(1) || data[-1]==data[0])
  479. {
  480. // avoid processing interal * and _ as cmd_id, or 4*10 as emphasis,
  481. // also x**2,y*2 should not be processed
  482. return 0;
  483. }
  484. else if (size>2 && data[0]==data[1] && isIdChar(2))
  485. {
  486. // avoid processing interal ** and __ such as cmd__id__bla,
  487. // or 4**10,5**10 as emphasis
  488. return 0;
  489. }
  490. }
  491. #endif
  492. char c = data[0];
  493. int ret;
  494. if (size>2 && data[1]!=c) // _bla or *bla
  495. {
  496. // whitespace cannot follow an opening emphasis
  497. if (data[1]==' ' || data[1]=='\n' ||
  498. (ret = processEmphasis1(out, data+1, size-1, c)) == 0)
  499. {
  500. return 0;
  501. }
  502. return ret+1;
  503. }
  504. if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
  505. {
  506. if (data[2]==' ' || data[2]=='\n' ||
  507. (ret = processEmphasis2(out, data+2, size-2, c)) == 0)
  508. {
  509. return 0;
  510. }
  511. return ret+2;
  512. }
  513. if (size>4 && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
  514. {
  515. if (data[3]==' ' || data[3]=='\n' ||
  516. (ret = processEmphasis3(out, data+3, size-3, c)) == 0)
  517. {
  518. return 0;
  519. }
  520. return ret+3;
  521. }
  522. return 0;
  523. }
  524. static int processLink(GrowBuf &out,const char *data,int,int size)
  525. {
  526. QCString content;
  527. QCString link;
  528. QCString title;
  529. int contentStart,contentEnd,linkStart,titleStart,titleEnd;
  530. bool isImageLink = FALSE;
  531. bool isToc = FALSE;
  532. int i=1;
  533. if (data[0]=='!')
  534. {
  535. isImageLink = TRUE;
  536. if (size<2 || data[1]!='[')
  537. {
  538. return 0;
  539. }
  540. i++;
  541. }
  542. contentStart=i;
  543. int level=1;
  544. int nl=0;
  545. // find the matching ]
  546. while (i<size)
  547. {
  548. if (data[i-1]=='\\') // skip escaped characters
  549. {
  550. }
  551. else if (data[i]=='[')
  552. {
  553. level++;
  554. }
  555. else if (data[i]==']')
  556. {
  557. level--;
  558. if (level<=0) break;
  559. }
  560. else if (data[i]=='\n')
  561. {
  562. nl++;
  563. if (nl>1) return 0; // only allow one newline in the content
  564. }
  565. i++;
  566. }
  567. if (i>=size) return 0; // premature end of comment -> no link
  568. contentEnd=i;
  569. convertStringFragment(content,data+contentStart,contentEnd-contentStart);
  570. //printf("processLink: content={%s}\n",content.data());
  571. if (!isImageLink && content.isEmpty()) return 0; // no link text
  572. i++; // skip over ]
  573. // skip whitespace
  574. while (i<size && data[i]==' ') i++;
  575. if (i<size && data[i]=='\n') // one newline allowed here
  576. {
  577. i++;
  578. // skip more whitespace
  579. while (i<size && data[i]==' ') i++;
  580. }
  581. bool explicitTitle=FALSE;
  582. if (i<size && data[i]=='(') // inline link
  583. {
  584. i++;
  585. while (i<size && data[i]==' ') i++;
  586. if (i<size && data[i]=='<') i++;
  587. linkStart=i;
  588. nl=0;
  589. while (i<size && data[i]!='\'' && data[i]!='"' && data[i]!=')')
  590. {
  591. if (data[i]=='\n')
  592. {
  593. nl++;
  594. if (nl>1) return 0;
  595. }
  596. i++;
  597. }
  598. if (i>=size || data[i]=='\n') return 0;
  599. convertStringFragment(link,data+linkStart,i-linkStart);
  600. link = link.stripWhiteSpace();
  601. //printf("processLink: link={%s}\n",link.data());
  602. if (link.isEmpty()) return 0;
  603. if (link.at(link.length()-1)=='>') link=link.left(link.length()-1);
  604. // optional title
  605. if (data[i]=='\'' || data[i]=='"')
  606. {
  607. char c = data[i];
  608. i++;
  609. titleStart=i;
  610. nl=0;
  611. while (i<size && data[i]!=')')
  612. {
  613. if (data[i]=='\n')
  614. {
  615. if (nl>1) return 0;
  616. nl++;
  617. }
  618. i++;
  619. }
  620. if (i>=size)
  621. {
  622. return 0;
  623. }
  624. titleEnd = i-1;
  625. // search back for closing marker
  626. while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
  627. if (data[titleEnd]==c) // found it
  628. {
  629. convertStringFragment(title,data+titleStart,titleEnd-titleStart);
  630. //printf("processLink: title={%s}\n",title.data());
  631. }
  632. else
  633. {
  634. return 0;
  635. }
  636. }
  637. i++;
  638. }
  639. else if (i<size && data[i]=='[') // reference link
  640. {
  641. i++;
  642. linkStart=i;
  643. nl=0;
  644. // find matching ]
  645. while (i<size && data[i]!=']')
  646. {
  647. if (data[i]=='\n')
  648. {
  649. nl++;
  650. if (nl>1) return 0;
  651. }
  652. i++;
  653. }
  654. if (i>=size) return 0;
  655. // extract link
  656. convertStringFragment(link,data+linkStart,i-linkStart);
  657. //printf("processLink: link={%s}\n",link.data());
  658. link = link.stripWhiteSpace();
  659. if (link.isEmpty()) // shortcut link
  660. {
  661. link=content;
  662. }
  663. // lookup reference
  664. LinkRef *lr = g_linkRefs.find(link.lower());
  665. if (lr) // found it
  666. {
  667. link = lr->link;
  668. title = lr->title;
  669. //printf("processLink: ref: link={%s} title={%s}\n",link.data(),title.data());
  670. }
  671. else // reference not found!
  672. {
  673. //printf("processLink: ref {%s} do not exist\n",link.lower().data());
  674. return 0;
  675. }
  676. i++;
  677. }
  678. else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
  679. {
  680. LinkRef *lr = g_linkRefs.find(content.lower());
  681. //printf("processLink: minimal link {%s} lr=%p",content.data(),lr);
  682. if (lr) // found it
  683. {
  684. link = lr->link;
  685. title = lr->title;
  686. explicitTitle=TRUE;
  687. i=contentEnd;
  688. }
  689. else if (content=="TOC")
  690. {
  691. isToc=TRUE;
  692. i=contentEnd;
  693. }
  694. else
  695. {
  696. return 0;
  697. }
  698. i++;
  699. }
  700. else
  701. {
  702. return 0;
  703. }
  704. static QRegExp re("^[@\\]ref ");
  705. if (isToc) // special case for [TOC]
  706. {
  707. if (g_current) g_current->stat=TRUE;
  708. }
  709. else if (isImageLink)
  710. {
  711. bool ambig;
  712. FileDef *fd=0;
  713. if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
  714. (fd=findFileDef(Doxygen::imageNameDict,link,ambig)))
  715. // assume doxygen symbol link or local image link
  716. {
  717. out.addStr("@image html ");
  718. out.addStr(link.mid(fd ? 0 : 5));
  719. if (!explicitTitle && !content.isEmpty())
  720. {
  721. out.addStr(" \"");
  722. out.addStr(content);
  723. out.addStr("\"");
  724. }
  725. else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
  726. {
  727. out.addStr(" \"");
  728. out.addStr(title);
  729. out.addStr("\"");
  730. }
  731. }
  732. else
  733. {
  734. out.addStr("<img src=\"");
  735. out.addStr(link);
  736. out.addStr("\" alt=\"");
  737. out.addStr(content);
  738. out.addStr("\"");
  739. if (!title.isEmpty())
  740. {
  741. out.addStr(" title=\"");
  742. out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
  743. out.addStr("\"");
  744. }
  745. out.addStr("/>");
  746. }
  747. }
  748. else
  749. {
  750. if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1)
  751. // assume doxygen symbol link
  752. {
  753. out.addStr(link);
  754. out.addStr(" \"");
  755. if (explicitTitle && !title.isEmpty())
  756. {
  757. out.addStr(title);
  758. }
  759. else
  760. {
  761. out.addStr(content);
  762. }
  763. out.addStr("\"");
  764. }
  765. else if (link.find('/')!=-1 || link.find('.')!=-1 || link.find('#')!=-1)
  766. { // file/url link
  767. out.addStr("<a href=\"");
  768. out.addStr(link);
  769. out.addStr("\"");
  770. if (!title.isEmpty())
  771. {
  772. out.addStr(" title=\"");
  773. out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
  774. out.addStr("\"");
  775. }
  776. out.addStr(">");
  777. out.addStr(content.simplifyWhiteSpace());
  778. out.addStr("</a>");
  779. }
  780. else // avoid link to e.g. F[x](y)
  781. {
  782. //printf("no link for '%s'\n",link.data());
  783. return 0;
  784. }
  785. }
  786. return i;
  787. }
  788. /** '`' parsing a code span (assuming codespan != 0) */
  789. static int processCodeSpan(GrowBuf &out, const char *data, int /*offset*/, int size)
  790. {
  791. int end, nb = 0, i, f_begin, f_end;
  792. /* counting the number of backticks in the delimiter */
  793. while (nb<size && data[nb]=='`')
  794. {
  795. nb++;
  796. }
  797. /* finding the next delimiter */
  798. i = 0;
  799. int nl=0;
  800. for (end=nb; end<size && i<nb && nl<2; end++)
  801. {
  802. if (data[end]=='`')
  803. {
  804. i++;
  805. }
  806. else if (data[end]=='\n')
  807. {
  808. i=0;
  809. nl++;
  810. }
  811. else
  812. {
  813. i=0;
  814. }
  815. }
  816. if (i < nb && end >= size)
  817. {
  818. return 0; // no matching delimiter
  819. }
  820. if (nl==2) // too many newlines inside the span
  821. {
  822. return 0;
  823. }
  824. // trimming outside whitespaces
  825. f_begin = nb;
  826. while (f_begin < end && data[f_begin]==' ')
  827. {
  828. f_begin++;
  829. }
  830. f_end = end - nb;
  831. while (f_end > nb && data[f_end-1]==' ')
  832. {
  833. f_end--;
  834. }
  835. if (nb==1) // check for closing ' followed by space within f_begin..f_end
  836. {
  837. i=f_begin;
  838. while (i<f_end-1)
  839. {
  840. if (data[i]=='\'' && !isIdChar(i+1)) // reject `some word' and not `it's cool`
  841. {
  842. return 0;
  843. }
  844. i++;
  845. }
  846. }
  847. //printf("found code span '%s'\n",QCString(data+f_begin).left(f_end-f_begin).data());
  848. /* real code span */
  849. if (f_begin < f_end)
  850. {
  851. QCString codeFragment;
  852. convertStringFragment(codeFragment,data+f_begin,f_end-f_begin);
  853. out.addStr("<tt>");
  854. //out.addStr(convertToHtml(codeFragment,TRUE));
  855. out.addStr(escapeSpecialChars(codeFragment));
  856. out.addStr("</tt>");
  857. }
  858. return end;
  859. }
  860. static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
  861. {
  862. int i=1;
  863. QCString endBlockName = isBlockCommand(data,offset,size);
  864. if (!endBlockName.isEmpty())
  865. {
  866. int l = endBlockName.length();
  867. while (i<size-l-1)
  868. {
  869. if ((data[i]=='\\' || data[i]=='@') && // command
  870. data[i-1]!='\\' && data[i-1]!='@') // not escaped
  871. {
  872. if (strncmp(&data[i+1],endBlockName,l)==0)
  873. {
  874. //printf("found end at %d\n",i);
  875. out.addStr(data,i+1+l);
  876. return i+1+l;
  877. }
  878. }
  879. i++;
  880. }
  881. }
  882. if (size>1 && data[0]=='\\')
  883. {
  884. char c=data[1];
  885. if (c=='[' || c==']' || c=='*' || c=='+' || c=='-' ||
  886. c=='!' || c=='(' || c==')' || c=='.' || c=='`' || c=='_')
  887. {
  888. out.addStr(&data[1],1);
  889. return 2;
  890. }
  891. }
  892. return 0;
  893. }
  894. static void processInline(GrowBuf &out,const char *data,int size)
  895. {
  896. int i=0, end=0;
  897. action_t action = 0;
  898. while (i<size)
  899. {
  900. while (end<size && ((action=g_actions[(uchar)data[end]])==0)) end++;
  901. out.addStr(data+i,end-i);
  902. if (end>=size) break;
  903. i=end;
  904. end = action(out,data+i,i,size-i);
  905. if (!end)
  906. {
  907. end=i+1;
  908. }
  909. else
  910. {
  911. i+=end;
  912. end=i;
  913. }
  914. }
  915. }
  916. /** returns whether the line is a setext-style hdr underline */
  917. static int isHeaderline(const char *data, int size)
  918. {
  919. int i=0, c=0;
  920. while (i<size && data[i]==' ') i++;
  921. // test of level 1 header
  922. if (data[i]=='=')
  923. {
  924. while (i<size && data[i]=='=') i++,c++;
  925. while (i<size && data[i]==' ') i++;
  926. return (c>1 && (i>=size || data[i]=='\n')) ? 1 : 0;
  927. }
  928. // test of level 2 header
  929. if (data[i]=='-')
  930. {
  931. while (i<size && data[i]=='-') i++,c++;
  932. while (i<size && data[i]==' ') i++;
  933. return (c>1 && (i>=size || data[i]=='\n')) ? 2 : 0;
  934. }
  935. return 0;
  936. }
  937. /** returns TRUE if this line starts a block quote */
  938. static bool isBlockQuote(const char *data,int size,int indent)
  939. {
  940. int i = 0;
  941. while (i<size && data[i]==' ') i++;
  942. if (i<indent+codeBlockIndent) // could be a quotation
  943. {
  944. // count >'s and skip spaces
  945. int level=0;
  946. while (i<size && (data[i]=='>' || data[i]==' '))
  947. {
  948. if (data[i]=='>') level++;
  949. i++;
  950. }
  951. // last characters should be a space or newline,
  952. // so a line starting with >= does not match
  953. return level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n');
  954. }
  955. else // too much indentation -> code block
  956. {
  957. return FALSE;
  958. }
  959. //return i<size && data[i]=='>' && i<indent+codeBlockIndent;
  960. }
  961. /** returns end of the link ref if this is indeed a link reference. */
  962. static int isLinkRef(const char *data,int size,
  963. QCString &refid,QCString &link,QCString &title)
  964. {
  965. //printf("isLinkRef data={%s}\n",data);
  966. // format: start with [some text]:
  967. int i = 0;
  968. while (i<size && data[i]==' ') i++;
  969. if (i>=size || data[i]!='[') return 0;
  970. i++;
  971. int refIdStart=i;
  972. while (i<size && data[i]!='\n' && data[i]!=']') i++;
  973. if (i>=size || data[i]!=']') return 0;
  974. convertStringFragment(refid,data+refIdStart,i-refIdStart);
  975. if (refid.isEmpty()) return 0;
  976. //printf(" isLinkRef: found refid='%s'\n",refid.data());
  977. i++;
  978. if (i>=size || data[i]!=':') return 0;
  979. i++;
  980. // format: whitespace* \n? whitespace* (<url> | url)
  981. while (i<size && data[i]==' ') i++;
  982. if (i<size && data[i]=='\n')
  983. {
  984. i++;
  985. while (i<size && data[i]==' ') i++;
  986. }
  987. if (i>=size) return 0;
  988. if (i<size && data[i]=='<') i++;
  989. int linkStart=i;
  990. while (i<size && data[i]!=' ' && data[i]!='\n') i++;
  991. int linkEnd=i;
  992. if (i<size && data[i]=='>') i++;
  993. if (linkStart==linkEnd) return 0; // empty link
  994. convertStringFragment(link,data+linkStart,linkEnd-linkStart);
  995. //printf(" isLinkRef: found link='%s'\n",link.data());
  996. if (link=="@ref" || link=="\\ref")
  997. {
  998. int argStart=i;
  999. while (i<size && data[i]!='\n' && data[i]!='"') i++;
  1000. QCString refArg;
  1001. convertStringFragment(refArg,data+argStart,i-argStart);
  1002. link+=refArg;
  1003. }
  1004. title.resize(0);
  1005. // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
  1006. int eol=0;
  1007. while (i<size && data[i]==' ') i++;
  1008. if (i<size && data[i]=='\n')
  1009. {
  1010. i++;
  1011. eol=i;
  1012. while (i<size && data[i]==' ') i++;
  1013. }
  1014. if (i>=size)
  1015. {
  1016. //printf("end of isLinkRef while looking for title! i=%d\n",i);
  1017. return i; // end of buffer while looking for the optional title
  1018. }
  1019. char c = data[i];
  1020. if (c=='\'' || c=='"' || c=='(') // optional title present?
  1021. {
  1022. //printf(" start of title found! char='%c'\n",c);
  1023. i++;
  1024. if (c=='(') c=')'; // replace c by end character
  1025. int titleStart=i;
  1026. // search for end of the line
  1027. while (i<size && data[i]!='\n') i++;
  1028. // search back to matching character
  1029. int end=i-1;
  1030. while (end>titleStart && data[end]!=c) end--;
  1031. if (end>titleStart)
  1032. {
  1033. convertStringFragment(title,data+titleStart,end-titleStart);
  1034. }
  1035. //printf(" title found: '%s'\n",title.data());
  1036. }
  1037. while (i<size && data[i]==' ') i++;
  1038. //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
  1039. // i,size,data[i],eol);
  1040. if (i>=size) return i; // end of buffer while ref id was found
  1041. else if (data[i]=='\n') return i+1; // end of line while ref id was found
  1042. else if (eol) return eol; // no optional title found
  1043. return 0; // invalid link ref
  1044. }
  1045. static int isHRuler(const char *data,int size)
  1046. {
  1047. int i=0;
  1048. if (size>0 && data[size-1]=='\n') size--; // ignore newline character
  1049. while (i<size && data[i]==' ') i++;
  1050. if (i>=size) return 0; // empty line
  1051. char c=data[i];
  1052. if (c!='*' && c!='-' && c!='_')
  1053. {
  1054. return 0; // not a hrule character
  1055. }
  1056. int n=0;
  1057. while (i<size)
  1058. {
  1059. if (data[i]==c)
  1060. {
  1061. n++; // count rule character
  1062. }
  1063. else if (data[i]!=' ')
  1064. {
  1065. return 0; // line contains non hruler characters
  1066. }
  1067. i++;
  1068. }
  1069. return n>=3; // at least 3 characters needed for a hruler
  1070. }
  1071. static QCString extractTitleId(QCString &title)
  1072. {
  1073. //static QRegExp r1("^[a-z_A-Z][a-z_A-Z0-9\\-]*:");
  1074. static QRegExp r2("\\{#[a-z_A-Z][a-z_A-Z0-9\\-]*\\}$");
  1075. int l=0;
  1076. int i = r2.match(title,0,&l);
  1077. if (i!=-1) // found {#id} style id
  1078. {
  1079. QCString id = title.mid(i+2,l-3);
  1080. title = title.left(i)+title.mid(i+l);
  1081. //printf("found id='%s' title='%s'\n",id.data(),title.data());
  1082. return id;
  1083. }
  1084. //i = r1.match(title,0,&l);
  1085. //if (i!=-1) // found id: style id
  1086. //{
  1087. // QCString id = title.mid(i,l-1);
  1088. // title = title.left(i)+title.mid(i+l);
  1089. // //printf("found id='%s' title='%s'\n",id.data(),title.data());
  1090. // return id;
  1091. //}
  1092. //printf("no id found in title '%s'\n",title.data());
  1093. return "";
  1094. }
  1095. static int isAtxHeader(const char *data,int size,
  1096. QCString &header,QCString &id)
  1097. {
  1098. int i = 0, end;
  1099. int level = 0, blanks=0;
  1100. // find start of header text and determine heading level
  1101. while (i<size && data[i]==' ') i++;
  1102. if (i>=size || data[i]!='#')
  1103. {
  1104. return 0;
  1105. }
  1106. while (i<size && level<6 && data[i]=='#') i++,level++;
  1107. while (i<size && data[i]==' ') i++,blanks++;
  1108. if (level==1 && blanks==0)
  1109. {
  1110. return 0; // special case to prevent #someid seen as a header (see bug 671395)
  1111. }
  1112. // find end of header text
  1113. end=i;
  1114. while (end<size && data[end]!='\n') end++;
  1115. while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
  1116. // store result
  1117. convertStringFragment(header,data+i,end-i);
  1118. id = extractTitleId(header);
  1119. if (!id.isEmpty()) // strip #'s between title and id
  1120. {
  1121. i=header.length()-1;
  1122. while (i>=0 && (header.at(i)=='#' || header.at(i)==' ')) i--;
  1123. header=header.left(i+1);
  1124. }
  1125. return level;
  1126. }
  1127. static int isEmptyLine(const char *data,int size)
  1128. {
  1129. int i=0;
  1130. while (i<size)
  1131. {
  1132. if (data[i]=='\n') return TRUE;
  1133. if (data[i]!=' ') return FALSE;
  1134. i++;
  1135. }
  1136. return TRUE;
  1137. }
  1138. #define isLiTag(i) \
  1139. (data[(i)]=='<' && \
  1140. (data[(i)+1]=='l' || data[(i)+1]=='L') && \
  1141. (data[(i)+2]=='i' || data[(i)+2]=='I') && \
  1142. (data[(i)+3]=='>'))
  1143. // compute the indent from the start of the input, excluding list markers
  1144. // such as -, -#, *, +, 1., and <li>
  1145. static int computeIndentExcludingListMarkers(const char *data,int size)
  1146. {
  1147. int i=0;
  1148. int indent=0;
  1149. bool isDigit=FALSE;
  1150. bool isLi=FALSE;
  1151. bool listMarkerSkipped=FALSE;
  1152. while (i<size &&
  1153. (data[i]==' ' || // space
  1154. (!listMarkerSkipped && // first list marker
  1155. (data[i]=='+' || data[i]=='-' || data[i]=='*' || // unordered list char
  1156. (data[i]=='#' && i>0 && data[i-1]=='-') || // -# item
  1157. (isDigit=(data[i]>='1' && data[i]<='9')) || // ordered list marker?
  1158. (isLi=(i<size-3 && isLiTag(i))) // <li> tag
  1159. )
  1160. )
  1161. )
  1162. )
  1163. {
  1164. if (isDigit) // skip over ordered list marker '10. '
  1165. {
  1166. int j=i+1;
  1167. while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
  1168. {
  1169. if (data[j]=='.') // should be end of the list marker
  1170. {
  1171. if (j<size-1 && data[j+1]==' ') // valid list marker
  1172. {
  1173. listMarkerSkipped=TRUE;
  1174. indent+=j+1-i;
  1175. i=j+1;
  1176. break;
  1177. }
  1178. else // not a list marker
  1179. {
  1180. break;
  1181. }
  1182. }
  1183. j++;
  1184. }
  1185. }
  1186. else if (isLi)
  1187. {
  1188. i+=3; // skip over <li>
  1189. indent+=3;
  1190. listMarkerSkipped=TRUE;
  1191. }
  1192. else if (data[i]=='-' && i<size-2 && data[i+1]=='#' && data[i+2]==' ')
  1193. { // case "-# "
  1194. listMarkerSkipped=TRUE; // only a single list marker is accepted
  1195. i++; // skip over #
  1196. indent++;
  1197. }
  1198. else if (data[i]!=' ' && i<size-1 && data[i+1]==' ')
  1199. { // case "- " or "+ " or "* "
  1200. listMarkerSkipped=TRUE; // only a single list marker is accepted
  1201. }
  1202. if (data[i]!=' ' && !listMarkerSkipped)
  1203. { // end of indent
  1204. break;
  1205. }
  1206. indent++,i++;
  1207. }
  1208. //printf("{%s}->%d\n",QCString(data).left(size).data(),indent);
  1209. return indent;
  1210. }
  1211. static bool isFencedCodeBlock(const char *data,int size,int refIndent,
  1212. QCString &lang,int &start,int &end,int &offset)
  1213. {
  1214. // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
  1215. // return FALSE
  1216. int i=0;
  1217. int indent=0;
  1218. int startTildes=0;
  1219. while (i<size && data[i]==' ') indent++,i++;
  1220. if (indent>=refIndent+4) return FALSE; // part of code block
  1221. while (i<size && data[i]=='~') startTildes++,i++;
  1222. if (startTildes<3) return FALSE; // not enough tildes
  1223. if (i<size && data[i]=='{') i++; // skip over optional {
  1224. int startLang=i;
  1225. while (i<size && (data[i]!='\n' && data[i]!='}' && data[i]!=' ')) i++;
  1226. convertStringFragment(lang,data+startLang,i-startLang);
  1227. while (i<size && data[i]!='\n') i++; // proceed to the end of the line
  1228. start=i;
  1229. while (i<size)
  1230. {
  1231. if (data[i]=='~')
  1232. {
  1233. end=i-1;
  1234. int endTildes=0;
  1235. while (i<size && data[i]=='~') endTildes++,i++;
  1236. while (i<size && data[i]==' ') i++;
  1237. if (i==size || data[i]=='\n')
  1238. {
  1239. offset=i;
  1240. return endTildes==startTildes;
  1241. }
  1242. }
  1243. i++;
  1244. }
  1245. return FALSE;
  1246. }
  1247. static bool isCodeBlock(const char *data,int offset,int size,int &indent)
  1248. {
  1249. //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
  1250. // determine the indent of this line
  1251. int i=0;
  1252. int indent0=0;
  1253. while (i<size && data[i]==' ') indent0++,i++;
  1254. if (indent0<codeBlockIndent)
  1255. {
  1256. //printf(">isCodeBlock: line is not indented enough %d<4\n",indent0);
  1257. return FALSE;
  1258. }
  1259. if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
  1260. {
  1261. //printf("only spaces at the end of a comment block\n");
  1262. return FALSE;
  1263. }
  1264. i=offset;
  1265. int nl=0;
  1266. int nl_pos[3];
  1267. // search back 3 lines and remember the start of lines -1 and -2
  1268. while (i>0 && nl<3)
  1269. {
  1270. if (data[i-offset-1]=='\n') nl_pos[nl++]=i-offset;
  1271. i--;
  1272. }
  1273. // if there are only 2 preceding lines, then line -2 starts at -offset
  1274. if (i==0 && nl==2) nl_pos[nl++]=-offset;
  1275. //printf(" nl=%d\n",nl);
  1276. if (nl==3) // we have at least 2 preceding lines
  1277. {
  1278. //printf(" positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
  1279. // nl_pos[0],nl_pos[1],nl_pos[2],
  1280. // QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1).data(),
  1281. // QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1).data());
  1282. // check that line -1 is empty
  1283. if (!isEmptyLine(data+nl_pos[1],nl_pos[0]-nl_pos[1]-1))
  1284. {
  1285. return FALSE;
  1286. }
  1287. // determine the indent of line -2
  1288. indent=computeIndentExcludingListMarkers(data+nl_pos[2],nl_pos[1]-nl_pos[2]);
  1289. //printf(">isCodeBlock local_indent %d>=%d+4=%d\n",
  1290. // indent0,indent2,indent0>=indent2+4);
  1291. // if the difference is >4 spaces -> code block
  1292. return indent0>=indent+codeBlockIndent;
  1293. }
  1294. else // not enough lines to determine the relative indent, use global indent
  1295. {
  1296. // check that line -1 is empty
  1297. if (nl==1 && !isEmptyLine(data-offset,offset-1))
  1298. {
  1299. return FALSE;
  1300. }
  1301. //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
  1302. // indent0,indent,indent0>=indent+4,nl);
  1303. return indent0>=indent+codeBlockIndent;
  1304. }
  1305. }
  1306. /** Finds the location of the table's contains in the string \a data.
  1307. * Only one line will be inspected.
  1308. * @param[in] data pointer to the string buffer.
  1309. * @param[in] size the size of the buffer.
  1310. * @param[out] start offset of the first character of the table content
  1311. * @param[out] end offset of the last character of the table content
  1312. * @param[out] columns number of table columns found
  1313. * @returns The offset until the next line in the buffer.
  1314. */
  1315. int findTableColumns(const char *data,int size,int &start,int &end,int &columns)
  1316. {
  1317. int i=0;
  1318. int eol;
  1319. // find start character of the table line
  1320. while (i<size && data[i]==' ') i++;
  1321. if (i<size && data[i]=='|') i++; // leading | does not count
  1322. start = i;
  1323. // find end character of the table line
  1324. while (i<size && data[i]!='\n') i++;
  1325. eol=i+1;
  1326. i--;
  1327. while (i>0 && data[i]==' ') i--;
  1328. if (i>0 && data[i]=='|') i--; // trailing | does not count
  1329. end = i;
  1330. // count columns between start and end
  1331. columns=1;
  1332. if (end>start)
  1333. {
  1334. i=start;
  1335. while (i<=end)
  1336. {
  1337. if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
  1338. i++;
  1339. }
  1340. }
  1341. //printf("findTableColumns(start=%d,end=%d,columns=%d) eol=%d\n",
  1342. // start,end,columns,eol);
  1343. return eol;
  1344. }
  1345. /** Returns TRUE iff data points to the start of a table block */
  1346. static bool isTableBlock(const char *data,int size)
  1347. {
  1348. int cc0,start,end;
  1349. // the first line should have at least two columns separated by '|'
  1350. int i = findTableColumns(data,size,start,end,cc0);
  1351. if (i>=size || cc0<2)
  1352. {
  1353. //printf("isTableBlock: no |'s in the header\n");
  1354. return FALSE;
  1355. }
  1356. int cc1;
  1357. int ret = findTableColumns(data+i,size-i,start,end,cc1);
  1358. int j=i+start;
  1359. // separator line should consist of |, - and : and spaces only
  1360. while (j<=end+i)
  1361. {
  1362. if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
  1363. {
  1364. //printf("isTableBlock: invalid character '%c'\n",data[j]);
  1365. return FALSE; // invalid characters in table separator
  1366. }
  1367. j++;
  1368. }
  1369. if (cc1!=cc0) // number of columns should be same as previous line
  1370. {
  1371. return FALSE;
  1372. }
  1373. i+=ret; // goto next line
  1374. int cc2;
  1375. ret = findTableColumns(data+i,size-i,start,end,cc2);
  1376. //printf("isTableBlock: %d\n",cc1==cc2);
  1377. return cc1==cc2;
  1378. }
  1379. static int writeTableBlock(GrowBuf &out,const char *data,int size)
  1380. {
  1381. int i=0,j,k;
  1382. int columns,start,end,cc;
  1383. i = findTableColumns(data,size,start,end,columns);
  1384. out.addStr("<table>");
  1385. // write table header, in range [start..end]
  1386. out.addStr("<tr>");
  1387. int headerStart = start;
  1388. int headerEnd = end;
  1389. // read cell alignments
  1390. int ret = findTableColumns(data+i,size-i,start,end,cc);
  1391. k=0;
  1392. Alignment *columnAlignment = new Alignment[columns];
  1393. bool leftMarker=FALSE,rightMarker=FALSE;
  1394. bool startFound=FALSE;
  1395. j=start+i;
  1396. while (j<=end+i)
  1397. {
  1398. if (!startFound)
  1399. {
  1400. if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
  1401. if (data[j]=='-') startFound=TRUE;
  1402. //printf(" data[%d]=%c startFound=%d\n",j,data[j],startFound);
  1403. }
  1404. if (data[j]=='-') rightMarker=FALSE;
  1405. else if (data[j]==':') rightMarker=TRUE;
  1406. if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
  1407. {
  1408. if (k<columns)
  1409. {
  1410. columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
  1411. //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
  1412. leftMarker=FALSE;
  1413. rightMarker=FALSE;
  1414. startFound=FALSE;
  1415. }
  1416. k++;
  1417. }
  1418. j++;
  1419. }
  1420. if (k<columns)
  1421. {
  1422. columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
  1423. //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
  1424. }
  1425. // proceed to next line
  1426. i+=ret;
  1427. int m=headerStart;
  1428. for (k=0;k<columns;k++)
  1429. {
  1430. out.addStr("<th");
  1431. switch (columnAlignment[k])
  1432. {
  1433. case AlignLeft: out.addStr(" align=left"); break;
  1434. case AlignRight: out.addStr(" align=right"); break;
  1435. case AlignCenter: out.addStr(" align=center"); break;
  1436. case AlignNone: break;
  1437. }
  1438. out.addStr(">");
  1439. while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
  1440. {
  1441. out.addChar(data[m++]);
  1442. }
  1443. m++;
  1444. }
  1445. // write table cells
  1446. while (i<size)
  1447. {
  1448. int ret = findTableColumns(data+i,size-i,start,end,cc);
  1449. //printf("findTableColumns cc=%d\n",cc);
  1450. if (cc!=columns) break; // end of table
  1451. out.addStr("<tr>");
  1452. j=start+i;
  1453. int columnStart=j;
  1454. k=0;
  1455. while (j<=end+i)
  1456. {
  1457. if (j==columnStart)
  1458. {
  1459. out.addStr("<td");
  1460. switch (columnAlignment[k])
  1461. {
  1462. case AlignLeft: out.addStr(" align=left"); break;
  1463. case AlignRight: out.addStr(" align=right"); break;
  1464. case AlignCenter: out.addStr(" align=center"); break;
  1465. case AlignNone: break;
  1466. }
  1467. out.addStr(">");
  1468. }
  1469. if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
  1470. {
  1471. columnStart=j+1;
  1472. k++;
  1473. }
  1474. else
  1475. {
  1476. out.addChar(data[j]);
  1477. }
  1478. j++;
  1479. }
  1480. out.addChar('\n');
  1481. // proceed to next line
  1482. i+=ret;
  1483. }
  1484. out.addStr("</table>\n");
  1485. delete[] columnAlignment;
  1486. return i;
  1487. }
  1488. void writeOneLineHeaderOrRuler(GrowBuf &out,const char *data,int size)
  1489. {
  1490. int level;
  1491. QCString header;
  1492. QCString id;
  1493. if (isHRuler(data,size))
  1494. {
  1495. out.addStr("<hr>\n");
  1496. }
  1497. else if ((level=isAtxHeader(data,size,header,id)))
  1498. {
  1499. if (level==1) g_correctSectionLevel=FALSE;
  1500. if (g_correctSectionLevel) level--;
  1501. QCString hTag;
  1502. if (level<5 && !id.isEmpty())
  1503. {
  1504. SectionInfo::SectionType type = SectionInfo::Anchor;
  1505. switch(level)
  1506. {
  1507. case 1: out.addStr("@section ");
  1508. type=SectionInfo::Section;
  1509. break;
  1510. case 2: out.addStr("@subsection ");
  1511. type=SectionInfo::Subsection;
  1512. break;
  1513. case 3: out.addStr("@subsubsection ");
  1514. type=SectionInfo::Subsubsection;
  1515. break;
  1516. default: out.addStr("@paragraph ");
  1517. type=SectionInfo::Paragraph;
  1518. break;
  1519. }
  1520. out.addStr(id);
  1521. out.addStr(" ");
  1522. out.addStr(header);
  1523. out.addStr("\n");
  1524. SectionInfo *si = new SectionInfo(g_fileName,id,header,type,level);
  1525. if (g_current)
  1526. {
  1527. g_current->anchors->append(si);
  1528. }
  1529. Doxygen::sectionDict.append(header,si);
  1530. }
  1531. else
  1532. {
  1533. if (!id.isEmpty())
  1534. {
  1535. out.addStr("\\anchor "+id+"\n");
  1536. }
  1537. hTag.sprintf("h%d",level);
  1538. out.addStr("<"+hTag+">");
  1539. out.addStr(header);
  1540. out.addStr("</"+hTag+">\n");
  1541. }
  1542. }
  1543. else // nothing interesting -> just output the line
  1544. {
  1545. out.addStr(data,size);
  1546. }
  1547. }
  1548. static int writeBlockQuote(GrowBuf &out,const char *data,int size)
  1549. {
  1550. int l;
  1551. int i=0;
  1552. int curLevel=0;
  1553. int end=0;
  1554. while (i<size)
  1555. {
  1556. // find end of this line
  1557. end=i+1;
  1558. while (end<size && data[end-1]!='\n') end++;
  1559. int j=i;
  1560. int level=0;
  1561. int indent=i;
  1562. // compute the quoting level
  1563. while (j<end && (data[j]==' ' || data[j]=='>'))
  1564. {
  1565. if (data[j]=='>') { level++; indent=j+1; }
  1566. else if (j>0 && data[j-1]=='>') indent=j+1;
  1567. j++;
  1568. }
  1569. if (j>0 && data[j-1]=='>') // disqualify last > if not followed by space
  1570. {
  1571. indent--;
  1572. j--;
  1573. }
  1574. if (level>curLevel) // quote level increased => add start markers
  1575. {
  1576. for (l=curLevel;l<level;l++)
  1577. {
  1578. out.addStr("<blockquote>\n");
  1579. }
  1580. }
  1581. else if (level<curLevel) // quote level descreased => add end markers
  1582. {
  1583. for (l=level;l<curLevel;l++)
  1584. {
  1585. out.addStr("\n</blockquote>\n");
  1586. }
  1587. }
  1588. curLevel=level;
  1589. if (level==0) break; // end of quote block
  1590. // copy line without quotation marks
  1591. out.addStr(data+indent,end-indent);
  1592. // proceed with next line
  1593. i=end;
  1594. }
  1595. // end of comment within blockquote => add end markers
  1596. for (l=0;l<curLevel;l++)
  1597. {
  1598. out.addStr("\n</blockquote>\n");
  1599. }
  1600. return i;
  1601. }
  1602. static int writeCodeBlock(GrowBuf &out,const char *data,int size,int refIndent)
  1603. {
  1604. int i=0,end;
  1605. //printf("writeCodeBlock: data={%s}\n",QCString(data).left(size).data());
  1606. out.addStr("@verbatim\n");
  1607. int emptyLines=0;
  1608. while (i<size)
  1609. {
  1610. // find end of this line
  1611. end=i+1;
  1612. while (end<size && data[end-1]!='\n') end++;
  1613. int j=i;
  1614. int indent=0;
  1615. while (j<end && data[j]==' ') j++,indent++;
  1616. //printf("j=%d end=%d indent=%d refIndent=%d data={%s}\n",j,end,indent,refIndent,QCString(data+i).left(end-i-1).data());
  1617. if (j==end-1) // empty line
  1618. {
  1619. emptyLines++;
  1620. i=end;
  1621. }
  1622. else if (indent>=refIndent+codeBlockIndent) // enough indent to contine the code block
  1623. {
  1624. while (emptyLines>0) // write skipped empty lines
  1625. {
  1626. // add empty line
  1627. out.addStr("\n");
  1628. emptyLines--;
  1629. }
  1630. // add code line minus the indent
  1631. out.addStr(data+i+refIndent+codeBlockIndent,end-i-refIndent-codeBlockIndent);
  1632. i=end;
  1633. }
  1634. else // end of code block
  1635. {
  1636. break;
  1637. }
  1638. }
  1639. out.addStr("@endverbatim\n");
  1640. while (emptyLines>0) // write skipped empty lines
  1641. {
  1642. // add empty line
  1643. out.addStr("\n");
  1644. emptyLines--;
  1645. }
  1646. //printf("i=%d\n",i);
  1647. return i;
  1648. }
  1649. // start searching for the end of the line start at offset \a i
  1650. // keeping track of possible blocks that need to to skipped.
  1651. static void findEndOfLine(GrowBuf &out,const char *data,int size,
  1652. int &pi,int&i,int &end)
  1653. {
  1654. // find end of the line
  1655. int nb=0;
  1656. end=i+1;
  1657. while (end<size && data[end-1]!='\n')
  1658. {
  1659. // while looking for the end of the line we might encounter a block
  1660. // that needs to be passed unprocessed.
  1661. if ((data[end-1]=='\\' || data[end-1]=='@') && // command
  1662. (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
  1663. )
  1664. {
  1665. QCString endBlockName = isBlockCommand(data+end-1,end-1,size-(end-1));
  1666. if (!endBlockName.isEmpty())
  1667. {
  1668. int l = endBlockName.length();
  1669. for (;end<size-l;end++) // search for end of block marker
  1670. {
  1671. if ((data[end]=='\\' || data[end]=='@') &&
  1672. data[end-1]!='\\' && data[end-1]!='@'
  1673. )
  1674. {
  1675. if (strncmp(&data[end+1],endBlockName,l)==0)
  1676. {
  1677. if (pi!=-1) // output previous line if available
  1678. {
  1679. //printf("feol out={%s}\n",QCString(data+pi).left(i-pi).data());
  1680. out.addStr(data+pi,i-pi);
  1681. }
  1682. // found end marker, skip over this block
  1683. //printf("feol.block out={%s}\n",QCString(data+i).left(end+l+1-i).data());
  1684. out.addStr(data+i,end+l+1-i);
  1685. pi=-1;
  1686. i=end+l+1; // continue after block
  1687. end=i+1;
  1688. break;
  1689. }
  1690. }
  1691. }
  1692. }
  1693. else
  1694. {
  1695. end++;
  1696. }
  1697. }
  1698. else if (nb==0 && data[end-1]=='<' && end<size-6 &&
  1699. (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
  1700. )
  1701. {
  1702. if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
  1703. tolower(data[end+2])=='e' && data[end+3]=='>') // <pre> tag
  1704. {
  1705. if (pi!=-1) // output previous line if available
  1706. {
  1707. out.addStr(data+pi,i-pi);
  1708. }
  1709. // output part until <pre>
  1710. out.addStr(data+i,end-1-i);
  1711. // output part until </pre>
  1712. i = end-1 + processHtmlTag(out,data+end-1,end-1,size-end+1);
  1713. pi=-1;
  1714. end = i+1;
  1715. break;
  1716. }
  1717. else
  1718. {
  1719. end++;
  1720. }
  1721. }
  1722. else if (nb==0 && data[end-1]=='`')
  1723. {
  1724. while (end<size && data[end-1]=='`') end++,nb++;
  1725. }
  1726. else if (nb>0 && data[end-1]=='`')
  1727. {
  1728. int enb=0;
  1729. while (end<size && data[end-1]=='`') end++,enb++;
  1730. if (enb==nb) nb=0;
  1731. }
  1732. else
  1733. {
  1734. end++;
  1735. }
  1736. }
  1737. //printf("findEndOfLine pi=%d i=%d end=%d {%s}\n",pi,i,end,QCString(data+i).left(end-i).data());
  1738. }
  1739. static QCString processQuotations(const QCString &s,int refIndent)
  1740. {
  1741. GrowBuf out;
  1742. const char *data = s.data();
  1743. int size = s.length();
  1744. int i=0,end=0,pi=-1;
  1745. while (i<size)
  1746. {
  1747. findEndOfLine(out,data,size,pi,i,end);
  1748. // line is now found at [i..end)
  1749. if (pi!=-1)
  1750. {
  1751. if (isBlockQuote(data+pi,i-pi,refIndent))
  1752. {
  1753. i = pi+writeBlockQuote(out,data+pi,size-pi);
  1754. pi=-1;
  1755. end=i+1;
  1756. continue;
  1757. }
  1758. else
  1759. {
  1760. //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
  1761. out.addStr(data+pi,i-pi);
  1762. }
  1763. }
  1764. pi=i;
  1765. i=end;
  1766. }
  1767. if (pi!=-1 && pi<size) // deal with the last line
  1768. {
  1769. if (isBlockQuote(data+pi,size-pi,refIndent))
  1770. {
  1771. writeBlockQuote(out,data+pi,size-pi);
  1772. }
  1773. else
  1774. {
  1775. out.addStr(data+pi,size-pi);
  1776. }
  1777. }
  1778. out.addChar(0);
  1779. //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
  1780. // s.data(),out.get());
  1781. return out.get();
  1782. }
  1783. static QCString processBlocks(const QCString &s,int indent)
  1784. {
  1785. GrowBuf out;
  1786. const char *data = s.data();
  1787. int size = s.length();
  1788. int i=0,end=0,pi=-1,ref,level;
  1789. QCString id,link,title;
  1790. int blockIndent = indent;
  1791. // get indent for the first line
  1792. end = i+1;
  1793. int sp=0;
  1794. while (end<size && data[end-1]!='\n')
  1795. {
  1796. if (data[end-1]==' ') sp++;
  1797. end++;
  1798. }
  1799. // special case when the documentation starts with a code block
  1800. // since the first line is skipped when looking for a code block later on.
  1801. if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
  1802. {
  1803. i=writeCodeBlock(out,data,size,blockIndent);
  1804. end=i+1;
  1805. pi=-1;
  1806. }
  1807. // process each line
  1808. while (i<size)
  1809. {
  1810. findEndOfLine(out,data,size,pi,i,end);
  1811. // line is now found at [i..end)
  1812. //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
  1813. if (pi!=-1)
  1814. {
  1815. int blockStart,blockEnd,blockOffset;
  1816. QCString lang;
  1817. blockIndent = indent;
  1818. //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
  1819. if ((level=isHeaderline(data+i,size-i))>0)
  1820. {
  1821. if (level==1) g_correctSectionLevel=FALSE;
  1822. if (g_correctSectionLevel) level--;
  1823. //printf("Found header at %d-%d\n",i,end);
  1824. while (pi<size && data[pi]==' ') pi++;
  1825. QCString header,id;
  1826. convertStringFragment(header,data+pi,i-pi-1);
  1827. id = extractTitleId(header);
  1828. if (!header.isEmpty())
  1829. {…

Large files files are truncated, but you can click here to view the full file