PageRenderTime 86ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/markdown.cpp

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

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