markdown.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2015 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 
16 /* Note: part of the code below is inspired by libupskirt written by
17  * Natacha Porté. Original copyright message follows:
18  *
19  * Copyright (c) 2008, Natacha Porté
20  *
21  * Permission to use, copy, modify, and distribute this software for any
22  * purpose with or without fee is hereby granted, provided that the above
23  * copyright notice and this permission notice appear in all copies.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32  */
33 
34 #include <stdio.h>
35 #include <qglobal.h>
36 #include <qregexp.h>
37 #include <qfileinfo.h>
38 #include <qdict.h>
39 
40 #include "markdown.h"
41 #include "growbuf.h"
42 #include "debug.h"
43 #include "util.h"
44 #include "doxygen.h"
45 #include "commentscan.h"
46 #include "entry.h"
47 #include "bufstr.h"
48 #include "commentcnv.h"
49 #include "config.h"
50 #include "section.h"
51 #include "message.h"
52 
53 //-----------
54 
55 // is character at position i in data part of an identifier?
56 #define isIdChar(i) \
57  ((data[i]>='a' && data[i]<='z') || \
58  (data[i]>='A' && data[i]<='Z') || \
59  (data[i]>='0' && data[i]<='9') || \
60  (((unsigned char)data[i])>=0x80)) // unicode characters
61 
62 // is character at position i in data allowed before an emphasis section
63 #define isOpenEmphChar(i) \
64  (data[i]=='\n' || data[i]==' ' || data[i]=='\'' || data[i]=='<' || \
65  data[i]=='{' || data[i]=='(' || data[i]=='[' || data[i]==',' || \
66  data[i]==':' || data[i]==';')
67 
68 // is character at position i in data an escape that prevents ending an emphasis section
69 // so for example *bla (*.txt) is cool*
70 #define ignoreCloseEmphChar(i) \
71  (data[i]=='(' || data[i]=='{' || data[i]=='[' || data[i]=='<' || \
72  data[i]=='=' || data[i]=='+' || data[i]=='-' || data[i]=='\\' || \
73  data[i]=='@')
74 
75 //----------
76 
77 struct LinkRef
78 {
79  LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
82 };
83 
84 typedef int (*action_t)(GrowBuf &out,const char *data,int offset,int size);
85 
87 
88 
89 //----------
90 
91 static QDict<LinkRef> g_linkRefs(257);
92 static action_t g_actions[256];
93 static Entry *g_current;
95 static int g_lineNr;
96 
97 // In case a markdown page starts with a level1 header, that header is used
98 // as a title of the page, in effect making it a level0 header, so the
99 // level of all other sections needs to be corrected as well.
100 // This flag is TRUE if corrections are needed.
101 //static bool g_correctSectionLevel;
102 
103 
104 //----------
105 
106 const int codeBlockIndent = 4;
107 
108 static void processInline(GrowBuf &out,const char *data,int size);
109 
110 // escape characters that have a special meaning later on.
112 {
113  if (s.isEmpty()) return "";
114  GrowBuf growBuf;
115  const char *p=s;
116  char c;
117  while ((c=*p++))
118  {
119  switch (c)
120  {
121  case '<': growBuf.addStr("\\<"); break;
122  case '>': growBuf.addStr("\\>"); break;
123  case '\\': growBuf.addStr("\\\\"); break;
124  case '@': growBuf.addStr("\\@"); break;
125  default: growBuf.addChar(c); break;
126  }
127  }
128  growBuf.addChar(0);
129  return growBuf.get();
130 }
131 
132 static void convertStringFragment(QCString &result,const char *data,int size)
133 {
134  if (size<0) size=0;
135  result.resize(size+1);
136  memcpy(result.rawData(),data,size);
137  result.at(size)='\0';
138 }
139 
140 /** helper function to convert presence of left and/or right alignment markers
141  * to a alignment value
142  */
143 static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
144 {
145  //printf("markerToAlignment(%d,%d)\n",leftMarker,rightMarker);
146  if (leftMarker && rightMarker)
147  {
148  return AlignCenter;
149  }
150  else if (leftMarker)
151  {
152  return AlignLeft;
153  }
154  else if (rightMarker)
155  {
156  return AlignRight;
157  }
158  else
159  {
160  return AlignNone;
161  }
162 }
163 
164 
165 // Check if data contains a block command. If so returned the command
166 // that ends the block. If not an empty string is returned.
167 // Note When offset>0 character position -1 will be inspected.
168 //
169 // Checks for and skip the following block commands:
170 // {@code .. { .. } .. }
171 // \dot .. \enddot
172 // \code .. \endcode
173 // \msc .. \endmsc
174 // \f$..\f$
175 // \f[..\f]
176 // \f{..\f}
177 // \verbatim..\endverbatim
178 // \latexonly..\endlatexonly
179 // \htmlonly..\endhtmlonly
180 // \xmlonly..\endxmlonly
181 // \rtfonly..\endrtfonly
182 // \manonly..\endmanonly
183 static QCString isBlockCommand(const char *data,int offset,int size)
184 {
185  bool openBracket = offset>0 && data[-1]=='{';
186  bool isEscaped = offset>0 && (data[-1]=='\\' || data[-1]=='@');
187  if (isEscaped) return QCString();
188 
189  int end=1;
190  while (end<size && (data[end]>='a' && data[end]<='z')) end++;
191  if (end==1) return QCString();
193  convertStringFragment(blockName,data+1,end-1);
194  if (blockName=="code" && openBracket)
195  {
196  return "}";
197  }
198  else if (blockName=="dot" ||
199  blockName=="code" ||
200  blockName=="msc" ||
201  blockName=="verbatim" ||
202  blockName=="latexonly" ||
203  blockName=="htmlonly" ||
204  blockName=="xmlonly" ||
205  blockName=="rtfonly" ||
206  blockName=="manonly" ||
207  blockName=="docbookonly"
208  )
209  {
210  return "end"+blockName;
211  }
212  else if (blockName=="startuml")
213  {
214  return "enduml";
215  }
216  else if (blockName=="f" && end<size)
217  {
218  if (data[end]=='$')
219  {
220  return "f$";
221  }
222  else if (data[end]=='[')
223  {
224  return "f]";
225  }
226  else if (data[end]=='}')
227  {
228  return "f}";
229  }
230  }
231  return QCString();
232 }
233 
234 /** looks for the next emph char, skipping other constructs, and
235  * stopping when either it is found, or we are at the end of a paragraph.
236  */
237 static int findEmphasisChar(const char *data, int size, char c, int c_size)
238 {
239  int i = 1;
240 
241  while (i<size)
242  {
243  while (i<size && data[i]!=c && data[i]!='`' &&
244  data[i]!='\\' && data[i]!='@' &&
245  data[i]!='\n') i++;
246  //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
247 
248  // not counting escaped chars or characters that are unlikely
249  // to appear as the end of the emphasis char
250  if (i>0 && ignoreCloseEmphChar(i-1))
251  {
252  i++;
253  continue;
254  }
255  else
256  {
257  // get length of emphasis token
258  int len = 0;
259  while (i+len<size && data[i+len]==c)
260  {
261  len++;
262  }
263 
264  if (len>0)
265  {
266  if (len!=c_size || (i<size-len && isIdChar(i+len))) // to prevent touching some_underscore_identifier
267  {
268  i=i+len;
269  continue;
270  }
271  return i; // found it
272  }
273  }
274 
275  // skipping a code span
276  if (data[i]=='`')
277  {
278  int snb=0;
279  while (i<size && data[i]=='`') snb++,i++;
280 
281  // find same pattern to end the span
282  int enb=0;
283  while (i<size && enb<snb)
284  {
285  if (data[i]=='`') enb++;
286  if (snb==1 && data[i]=='\'') break; // ` ended by '
287  i++;
288  }
289  }
290  else if (data[i]=='@' || data[i]=='\\')
291  { // skip over blocks that should not be processed
292  QCString endBlockName = isBlockCommand(data+i,i,size-i);
293  if (!endBlockName.isEmpty())
294  {
295  i++;
296  int l = endBlockName.length();
297  while (i<size-l)
298  {
299  if ((data[i]=='\\' || data[i]=='@') && // command
300  data[i-1]!='\\' && data[i-1]!='@') // not escaped
301  {
302  if (qstrncmp(&data[i+1],endBlockName,l)==0)
303  {
304  break;
305  }
306  }
307  i++;
308  }
309  }
310  else if (i<size-1 && isIdChar(i+1)) // @cmd, stop processing, see bug 690385
311  {
312  return 0;
313  }
314  else
315  {
316  i++;
317  }
318  }
319  else if (data[i]=='\n') // end * or _ at paragraph boundary
320  {
321  i++;
322  while (i<size && data[i]==' ') i++;
323  if (i>=size || data[i]=='\n') return 0; // empty line -> paragraph
324  }
325  else // should not get here!
326  {
327  i++;
328  }
329 
330  }
331  return 0;
332 }
333 
334 /** process single emphasis */
335 static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
336 {
337  int i = 0, len;
338 
339  /* skipping one symbol if coming from emph3 */
340  if (size>1 && data[0]==c && data[1]==c) { i=1; }
341 
342  while (i<size)
343  {
344  len = findEmphasisChar(data+i, size-i, c, 1);
345  if (len==0) return 0;
346  i+=len;
347  if (i>=size) return 0;
348 
349  if (i+1<size && data[i+1]==c)
350  {
351  i++;
352  continue;
353  }
354  if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
355  {
356  out.addStr("<em>");
357  processInline(out,data,i);
358  out.addStr("</em>");
359  return i+1;
360  }
361  }
362  return 0;
363 }
364 
365 /** process double emphasis */
366 static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
367 {
368  int i = 0, len;
369 
370  while (i<size)
371  {
372  len = findEmphasisChar(data+i, size-i, c, 2);
373  if (len==0)
374  {
375  return 0;
376  }
377  i += len;
378  if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' &&
379  data[i-1]!='\n'
380  )
381  {
382  out.addStr("<strong>");
383  processInline(out,data,i);
384  out.addStr("</strong>");
385  return i + 2;
386  }
387  i++;
388  }
389  return 0;
390 }
391 
392 /** Parsing tripple emphasis.
393  * Finds the first closing tag, and delegates to the other emph
394  */
395 static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
396 {
397  int i = 0, len;
398 
399  while (i<size)
400  {
401  len = findEmphasisChar(data+i, size-i, c, 3);
402  if (len==0)
403  {
404  return 0;
405  }
406  i+=len;
407 
408  /* skip whitespace preceded symbols */
409  if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
410  {
411  continue;
412  }
413 
414  if (i+2<size && data[i+1]==c && data[i+2]==c)
415  {
416  out.addStr("<em><strong>");
417  processInline(out,data,i);
418  out.addStr("</strong></em>");
419  return i+3;
420  }
421  else if (i+1<size && data[i+1]==c)
422  {
423  // double symbol found, handing over to emph1
424  len = processEmphasis1(out, data-2, size+2, c);
425  if (len==0)
426  {
427  return 0;
428  }
429  else
430  {
431  return len - 2;
432  }
433  }
434  else
435  {
436  // single symbol found, handing over to emph2
437  len = processEmphasis2(out, data-1, size+1, c);
438  if (len==0)
439  {
440  return 0;
441  }
442  else
443  {
444  return len - 1;
445  }
446  }
447  }
448  return 0;
449 }
450 
451 /** Process ndash and mdashes */
452 static int processNmdash(GrowBuf &out,const char *data,int off,int size)
453 {
454  // precondition: data[0]=='-'
455  int i=1;
456  int count=1;
457  if (i<size && data[i]=='-') // found --
458  {
459  count++,i++;
460  }
461  if (i<size && data[i]=='-') // found ---
462  {
463  count++,i++;
464  }
465  if (i<size && data[i]=='-') // found ----
466  {
467  count++;
468  }
469  if (count==2 && (off<8 || qstrncmp(data-8,"operator",8)!=0)) // -- => ndash
470  {
471  out.addStr("&ndash;");
472  return 2;
473  }
474  else if (count==3) // --- => ndash
475  {
476  out.addStr("&mdash;");
477  return 3;
478  }
479  // not an ndash or mdash
480  return 0;
481 }
482 
483 /** Process quoted section "...", can contain one embedded newline */
484 static int processQuoted(GrowBuf &out,const char *data,int,int size)
485 {
486  int i=1;
487  int nl=0;
488  while (i<size && data[i]!='"' && nl<2)
489  {
490  if (data[i]=='\n') nl++;
491  i++;
492  }
493  if (i<size && data[i]=='"' && nl<2)
494  {
495  out.addStr(data,i+1);
496  return i+1;
497  }
498  // not a quoted section
499  return 0;
500 }
501 
502 /** Process a HTML tag. Note that <pre>..</pre> are treated specially, in
503  * the sense that all code inside is written unprocessed
504  */
505 static int processHtmlTag(GrowBuf &out,const char *data,int offset,int size)
506 {
507  if (offset>0 && data[-1]=='\\') return 0; // escaped <
508 
509  // find the end of the html tag
510  int i=1;
511  int l=0;
512  // compute length of the tag name
513  while (i<size && isIdChar(i)) i++,l++;
514  QCString tagName;
515  convertStringFragment(tagName,data+1,i-1);
516  if (tagName.lower()=="pre") // found <pre> tag
517  {
518  bool insideStr=FALSE;
519  while (i<size-6)
520  {
521  char c=data[i];
522  if (!insideStr && c=='<') // potential start of html tag
523  {
524  if (data[i+1]=='/' &&
525  tolower(data[i+2])=='p' && tolower(data[i+3])=='r' &&
526  tolower(data[i+4])=='e' && tolower(data[i+5])=='>')
527  { // found </pre> tag, copy from start to end of tag
528  out.addStr(data,i+6);
529  //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
530  return i+6;
531  }
532  }
533  else if (insideStr && c=='"')
534  {
535  if (data[i-1]!='\\') insideStr=FALSE;
536  }
537  else if (c=='"')
538  {
539  insideStr=TRUE;
540  }
541  i++;
542  }
543  }
544  else // some other html tag
545  {
546  if (l>0 && i<size)
547  {
548  if (data[i]=='/' && i<size-1 && data[i+1]=='>') // <bla/>
549  {
550  //printf("Found htmlTag={%s}\n",QCString(data).left(i+2).data());
551  out.addStr(data,i+2);
552  return i+2;
553  }
554  else if (data[i]=='>') // <bla>
555  {
556  //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
557  out.addStr(data,i+1);
558  return i+1;
559  }
560  else if (data[i]==' ') // <bla attr=...
561  {
562  i++;
563  bool insideAttr=FALSE;
564  while (i<size)
565  {
566  if (!insideAttr && data[i]=='"')
567  {
568  insideAttr=TRUE;
569  }
570  else if (data[i]=='"' && data[i-1]!='\\')
571  {
572  insideAttr=FALSE;
573  }
574  else if (!insideAttr && data[i]=='>') // found end of tag
575  {
576  //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
577  out.addStr(data,i+1);
578  return i+1;
579  }
580  i++;
581  }
582  }
583  }
584  }
585  //printf("Not a valid html tag\n");
586  return 0;
587 }
588 
589 static int processEmphasis(GrowBuf &out,const char *data,int offset,int size)
590 {
591  if ((offset>0 && !isOpenEmphChar(-1)) || // invalid char before * or _
592  (size>1 && data[0]!=data[1] && !(isIdChar(1) || data[1]=='[')) || // invalid char after * or _
593  (size>2 && data[0]==data[1] && !(isIdChar(2) || data[2]=='['))) // invalid char after ** or __
594  {
595  return 0;
596  }
597 
598  char c = data[0];
599  int ret;
600  if (size>2 && data[1]!=c) // _bla or *bla
601  {
602  // whitespace cannot follow an opening emphasis
603  if (data[1]==' ' || data[1]=='\n' ||
604  (ret = processEmphasis1(out, data+1, size-1, c)) == 0)
605  {
606  return 0;
607  }
608  return ret+1;
609  }
610  if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
611  {
612  if (data[2]==' ' || data[2]=='\n' ||
613  (ret = processEmphasis2(out, data+2, size-2, c)) == 0)
614  {
615  return 0;
616  }
617  return ret+2;
618  }
619  if (size>4 && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
620  {
621  if (data[3]==' ' || data[3]=='\n' ||
622  (ret = processEmphasis3(out, data+3, size-3, c)) == 0)
623  {
624  return 0;
625  }
626  return ret+3;
627  }
628  return 0;
629 }
630 
631 static int processLink(GrowBuf &out,const char *data,int,int size)
632 {
633  QCString content;
634  QCString link;
635  QCString title;
636  int contentStart,contentEnd,linkStart,titleStart,titleEnd;
637  bool isImageLink = FALSE;
638  bool isToc = FALSE;
639  int i=1;
640  if (data[0]=='!')
641  {
642  isImageLink = TRUE;
643  if (size<2 || data[1]!='[')
644  {
645  return 0;
646  }
647  i++;
648  }
649  contentStart=i;
650  int level=1;
651  int nl=0;
652  // find the matching ]
653  while (i<size)
654  {
655  if (data[i-1]=='\\') // skip escaped characters
656  {
657  }
658  else if (data[i]=='[')
659  {
660  level++;
661  }
662  else if (data[i]==']')
663  {
664  level--;
665  if (level<=0) break;
666  }
667  else if (data[i]=='\n')
668  {
669  nl++;
670  if (nl>1) return 0; // only allow one newline in the content
671  }
672  i++;
673  }
674  if (i>=size) return 0; // premature end of comment -> no link
675  contentEnd=i;
676  convertStringFragment(content,data+contentStart,contentEnd-contentStart);
677  //printf("processLink: content={%s}\n",content.data());
678  if (!isImageLink && content.isEmpty()) return 0; // no link text
679  i++; // skip over ]
680 
681  // skip whitespace
682  while (i<size && data[i]==' ') i++;
683  if (i<size && data[i]=='\n') // one newline allowed here
684  {
685  i++;
686  // skip more whitespace
687  while (i<size && data[i]==' ') i++;
688  }
689 
690  bool explicitTitle=FALSE;
691  if (i<size && data[i]=='(') // inline link
692  {
693  i++;
694  while (i<size && data[i]==' ') i++;
695  if (i<size && data[i]=='<') i++;
696  linkStart=i;
697  nl=0;
698  int braceCount=1;
699  while (i<size && data[i]!='\'' && data[i]!='"' && braceCount>0)
700  {
701  if (data[i]=='\n') // unexpected EOL
702  {
703  nl++;
704  if (nl>1) return 0;
705  }
706  else if (data[i]=='(')
707  {
708  braceCount++;
709  }
710  else if (data[i]==')')
711  {
712  braceCount--;
713  }
714  if (braceCount>0)
715  {
716  i++;
717  }
718  }
719  if (i>=size || data[i]=='\n') return 0;
720  convertStringFragment(link,data+linkStart,i-linkStart);
721  link = link.stripWhiteSpace();
722  //printf("processLink: link={%s}\n",link.data());
723  if (link.isEmpty()) return 0;
724  if (link.at(link.length()-1)=='>') link=link.left(link.length()-1);
725 
726  // optional title
727  if (data[i]=='\'' || data[i]=='"')
728  {
729  char c = data[i];
730  i++;
731  titleStart=i;
732  nl=0;
733  while (i<size && data[i]!=')')
734  {
735  if (data[i]=='\n')
736  {
737  if (nl>1) return 0;
738  nl++;
739  }
740  i++;
741  }
742  if (i>=size)
743  {
744  return 0;
745  }
746  titleEnd = i-1;
747  // search back for closing marker
748  while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
749  if (data[titleEnd]==c) // found it
750  {
751  convertStringFragment(title,data+titleStart,titleEnd-titleStart);
752  //printf("processLink: title={%s}\n",title.data());
753  }
754  else
755  {
756  return 0;
757  }
758  }
759  i++;
760  }
761  else if (i<size && data[i]=='[') // reference link
762  {
763  i++;
764  linkStart=i;
765  nl=0;
766  // find matching ]
767  while (i<size && data[i]!=']')
768  {
769  if (data[i]=='\n')
770  {
771  nl++;
772  if (nl>1) return 0;
773  }
774  i++;
775  }
776  if (i>=size) return 0;
777  // extract link
778  convertStringFragment(link,data+linkStart,i-linkStart);
779  //printf("processLink: link={%s}\n",link.data());
780  link = link.stripWhiteSpace();
781  if (link.isEmpty()) // shortcut link
782  {
783  link=content;
784  }
785  // lookup reference
786  LinkRef *lr = g_linkRefs.find(link.lower());
787  if (lr) // found it
788  {
789  link = lr->link;
790  title = lr->title;
791  //printf("processLink: ref: link={%s} title={%s}\n",link.data(),title.data());
792  }
793  else // reference not found!
794  {
795  //printf("processLink: ref {%s} do not exist\n",link.lower().data());
796  return 0;
797  }
798  i++;
799  }
800  else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
801  {
802  LinkRef *lr = g_linkRefs.find(content.lower());
803  //printf("processLink: minimal link {%s} lr=%p",content.data(),lr);
804  if (lr) // found it
805  {
806  link = lr->link;
807  title = lr->title;
808  explicitTitle=TRUE;
809  i=contentEnd;
810  }
811  else if (content=="TOC")
812  {
813  isToc=TRUE;
814  i=contentEnd;
815  }
816  else
817  {
818  return 0;
819  }
820  i++;
821  }
822  else
823  {
824  return 0;
825  }
826  if (isToc) // special case for [TOC]
827  {
828  if (g_current) g_current->stat=TRUE;
829  }
830  else if (isImageLink)
831  {
832  bool ambig;
833  FileDef *fd=0;
834  if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
835  (fd=findFileDef(Doxygen::imageNameDict,link,ambig)))
836  // assume doxygen symbol link or local image link
837  {
838  out.addStr("@image html ");
839  out.addStr(link.mid(fd ? 0 : 5));
840  if (!explicitTitle && !content.isEmpty())
841  {
842  out.addStr(" \"");
843  out.addStr(content);
844  out.addStr("\"");
845  }
846  else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
847  {
848  out.addStr(" \"");
849  out.addStr(title);
850  out.addStr("\"");
851  }
852  }
853  else
854  {
855  out.addStr("<img src=\"");
856  out.addStr(link);
857  out.addStr("\" alt=\"");
858  out.addStr(content);
859  out.addStr("\"");
860  if (!title.isEmpty())
861  {
862  out.addStr(" title=\"");
863  out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
864  out.addStr("\"");
865  }
866  out.addStr("/>");
867  }
868  }
869  else
870  {
871  SrcLangExt lang = getLanguageFromFileName(link);
872  int lp=-1;
873  if ((lp=link.find("@ref "))!=-1 || (lp=link.find("\\ref "))!=-1 || lang==SrcLangExt_Markdown)
874  // assume doxygen symbol link
875  {
876  if (lp==-1) // link to markdown page
877  {
878  out.addStr("@ref ");
879  }
880  out.addStr(link);
881  out.addStr(" \"");
882  if (explicitTitle && !title.isEmpty())
883  {
884  out.addStr(title);
885  }
886  else
887  {
888  out.addStr(content);
889  }
890  out.addStr("\"");
891  }
892  else if (link.find('/')!=-1 || link.find('.')!=-1 || link.find('#')!=-1)
893  { // file/url link
894  out.addStr("<a href=\"");
895  out.addStr(link);
896  out.addStr("\"");
897  if (!title.isEmpty())
898  {
899  out.addStr(" title=\"");
900  out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
901  out.addStr("\"");
902  }
903  out.addStr(">");
904  content = content.simplifyWhiteSpace();
905  processInline(out,content,content.length());
906  out.addStr("</a>");
907  }
908  else // avoid link to e.g. F[x](y)
909  {
910  //printf("no link for '%s'\n",link.data());
911  return 0;
912  }
913  }
914  return i;
915 }
916 
917 /** '`' parsing a code span (assuming codespan != 0) */
918 static int processCodeSpan(GrowBuf &out, const char *data, int /*offset*/, int size)
919 {
920  int end, nb = 0, i, f_begin, f_end;
921 
922  /* counting the number of backticks in the delimiter */
923  while (nb<size && data[nb]=='`')
924  {
925  nb++;
926  }
927 
928  /* finding the next delimiter */
929  i = 0;
930  int nl=0;
931  for (end=nb; end<size && i<nb && nl<2; end++)
932  {
933  if (data[end]=='`')
934  {
935  i++;
936  }
937  else if (data[end]=='\n')
938  {
939  i=0;
940  nl++;
941  }
942  else
943  {
944  i=0;
945  }
946  }
947  if (i < nb && end >= size)
948  {
949  return 0; // no matching delimiter
950  }
951  if (nl==2) // too many newlines inside the span
952  {
953  return 0;
954  }
955 
956  // trimming outside whitespaces
957  f_begin = nb;
958  while (f_begin < end && data[f_begin]==' ')
959  {
960  f_begin++;
961  }
962  f_end = end - nb;
963  while (f_end > nb && data[f_end-1]==' ')
964  {
965  f_end--;
966  }
967 
968  if (nb==1) // check for closing ' followed by space within f_begin..f_end
969  {
970  i=f_begin;
971  while (i<f_end-1)
972  {
973  if (data[i]=='\'' && !isIdChar(i+1)) // reject `some word' and not `it's cool`
974  {
975  return 0;
976  }
977  i++;
978  }
979  }
980  //printf("found code span '%s'\n",QCString(data+f_begin).left(f_end-f_begin).data());
981 
982  /* real code span */
983  if (f_begin < f_end)
984  {
985  QCString codeFragment;
986  convertStringFragment(codeFragment,data+f_begin,f_end-f_begin);
987  out.addStr("<tt>");
988  //out.addStr(convertToHtml(codeFragment,TRUE));
989  out.addStr(escapeSpecialChars(codeFragment));
990  out.addStr("</tt>");
991  }
992  return end;
993 }
994 
995 
996 static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
997 {
998  int i=1;
999  QCString endBlockName = isBlockCommand(data,offset,size);
1000  if (!endBlockName.isEmpty())
1001  {
1002  int l = endBlockName.length();
1003  while (i<size-l)
1004  {
1005  if ((data[i]=='\\' || data[i]=='@') && // command
1006  data[i-1]!='\\' && data[i-1]!='@') // not escaped
1007  {
1008  if (qstrncmp(&data[i+1],endBlockName,l)==0)
1009  {
1010  //printf("found end at %d\n",i);
1011  out.addStr(data,i+1+l);
1012  return i+1+l;
1013  }
1014  }
1015  i++;
1016  }
1017  }
1018  if (size>1 && data[0]=='\\')
1019  {
1020  char c=data[1];
1021  if (c=='[' || c==']' || c=='*' || /* c=='+' || c=='-' || c=='.' || */
1022  c=='!' || c=='(' || c==')' || c=='`' || c=='_')
1023  {
1024  if (c=='-' && size>3 && data[2]=='-' && data[3]=='-') // \---
1025  {
1026  out.addStr(&data[1],3);
1027  return 4;
1028  }
1029  else if (c=='-' && size>2 && data[2]=='-') // \--
1030  {
1031  out.addStr(&data[1],2);
1032  return 3;
1033  }
1034  else if (c=='-') // \-
1035  {
1036  out.addChar(c);
1037  }
1038  out.addChar(data[1]);
1039  return 2;
1040  }
1041  }
1042  return 0;
1043 }
1044 
1045 static void processInline(GrowBuf &out,const char *data,int size)
1046 {
1047  int i=0, end=0;
1048  action_t action = 0;
1049  while (i<size)
1050  {
1051  while (end<size && ((action=g_actions[(uchar)data[end]])==0)) end++;
1052  out.addStr(data+i,end-i);
1053  if (end>=size) break;
1054  i=end;
1055  end = action(out,data+i,i,size-i);
1056  if (!end)
1057  {
1058  end=i+1;
1059  }
1060  else
1061  {
1062  i+=end;
1063  end=i;
1064  }
1065  }
1066 }
1067 
1068 /** returns whether the line is a setext-style hdr underline */
1069 static int isHeaderline(const char *data, int size)
1070 {
1071  int i=0, c=0;
1072  while (i<size && data[i]==' ') i++;
1073 
1074  // test of level 1 header
1075  if (data[i]=='=')
1076  {
1077  while (i<size && data[i]=='=') i++,c++;
1078  while (i<size && data[i]==' ') i++;
1079  return (c>1 && (i>=size || data[i]=='\n')) ? 1 : 0;
1080  }
1081  // test of level 2 header
1082  if (data[i]=='-')
1083  {
1084  while (i<size && data[i]=='-') i++,c++;
1085  while (i<size && data[i]==' ') i++;
1086  return (c>1 && (i>=size || data[i]=='\n')) ? 2 : 0;
1087  }
1088  return 0;
1089 }
1090 
1091 /** returns TRUE if this line starts a block quote */
1092 static bool isBlockQuote(const char *data,int size,int indent)
1093 {
1094  int i = 0;
1095  while (i<size && data[i]==' ') i++;
1096  if (i<indent+codeBlockIndent) // could be a quotation
1097  {
1098  // count >'s and skip spaces
1099  int level=0;
1100  while (i<size && (data[i]=='>' || data[i]==' '))
1101  {
1102  if (data[i]=='>') level++;
1103  i++;
1104  }
1105  // last characters should be a space or newline,
1106  // so a line starting with >= does not match
1107  return level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n');
1108  }
1109  else // too much indentation -> code block
1110  {
1111  return FALSE;
1112  }
1113  //return i<size && data[i]=='>' && i<indent+codeBlockIndent;
1114 }
1115 
1116 /** returns end of the link ref if this is indeed a link reference. */
1117 static int isLinkRef(const char *data,int size,
1118  QCString &refid,QCString &link,QCString &title)
1119 {
1120  //printf("isLinkRef data={%s}\n",data);
1121  // format: start with [some text]:
1122  int i = 0;
1123  while (i<size && data[i]==' ') i++;
1124  if (i>=size || data[i]!='[') return 0;
1125  i++;
1126  int refIdStart=i;
1127  while (i<size && data[i]!='\n' && data[i]!=']') i++;
1128  if (i>=size || data[i]!=']') return 0;
1129  convertStringFragment(refid,data+refIdStart,i-refIdStart);
1130  if (refid.isEmpty()) return 0;
1131  //printf(" isLinkRef: found refid='%s'\n",refid.data());
1132  i++;
1133  if (i>=size || data[i]!=':') return 0;
1134  i++;
1135 
1136  // format: whitespace* \n? whitespace* (<url> | url)
1137  while (i<size && data[i]==' ') i++;
1138  if (i<size && data[i]=='\n')
1139  {
1140  i++;
1141  while (i<size && data[i]==' ') i++;
1142  }
1143  if (i>=size) return 0;
1144 
1145  if (i<size && data[i]=='<') i++;
1146  int linkStart=i;
1147  while (i<size && data[i]!=' ' && data[i]!='\n') i++;
1148  int linkEnd=i;
1149  if (i<size && data[i]=='>') i++;
1150  if (linkStart==linkEnd) return 0; // empty link
1151  convertStringFragment(link,data+linkStart,linkEnd-linkStart);
1152  //printf(" isLinkRef: found link='%s'\n",link.data());
1153  if (link=="@ref" || link=="\\ref")
1154  {
1155  int argStart=i;
1156  while (i<size && data[i]!='\n' && data[i]!='"') i++;
1157  QCString refArg;
1158  convertStringFragment(refArg,data+argStart,i-argStart);
1159  link+=refArg;
1160  }
1161 
1162  title.resize(0);
1163 
1164  // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
1165  int eol=0;
1166  while (i<size && data[i]==' ') i++;
1167  if (i<size && data[i]=='\n')
1168  {
1169  eol=i;
1170  i++;
1171  while (i<size && data[i]==' ') i++;
1172  }
1173  if (i>=size)
1174  {
1175  //printf("end of isLinkRef while looking for title! i=%d\n",i);
1176  return i; // end of buffer while looking for the optional title
1177  }
1178 
1179  char c = data[i];
1180  if (c=='\'' || c=='"' || c=='(') // optional title present?
1181  {
1182  //printf(" start of title found! char='%c'\n",c);
1183  i++;
1184  if (c=='(') c=')'; // replace c by end character
1185  int titleStart=i;
1186  // search for end of the line
1187  while (i<size && data[i]!='\n') i++;
1188  eol = i;
1189 
1190  // search back to matching character
1191  int end=i-1;
1192  while (end>titleStart && data[end]!=c) end--;
1193  if (end>titleStart)
1194  {
1195  convertStringFragment(title,data+titleStart,end-titleStart);
1196  }
1197  //printf(" title found: '%s'\n",title.data());
1198  }
1199  while (i<size && data[i]==' ') i++;
1200  //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
1201  // i,size,data[i],eol);
1202  if (i>=size) return i; // end of buffer while ref id was found
1203  else if (eol) return eol; // end of line while ref id was found
1204  return 0; // invalid link ref
1205 }
1206 
1207 static int isHRuler(const char *data,int size)
1208 {
1209  int i=0;
1210  if (size>0 && data[size-1]=='\n') size--; // ignore newline character
1211  while (i<size && data[i]==' ') i++;
1212  if (i>=size) return 0; // empty line
1213  char c=data[i];
1214  if (c!='*' && c!='-' && c!='_')
1215  {
1216  return 0; // not a hrule character
1217  }
1218  int n=0;
1219  while (i<size)
1220  {
1221  if (data[i]==c)
1222  {
1223  n++; // count rule character
1224  }
1225  else if (data[i]!=' ')
1226  {
1227  return 0; // line contains non hruler characters
1228  }
1229  i++;
1230  }
1231  return n>=3; // at least 3 characters needed for a hruler
1232 }
1233 
1235 {
1236  //static QRegExp r1("^[a-z_A-Z][a-z_A-Z0-9\\-]*:");
1237  static QRegExp r2("\\{#[a-z_A-Z][a-z_A-Z0-9\\-]*\\}");
1238  int l=0;
1239  int i = r2.match(title,0,&l);
1240  if (i!=-1 && title.mid(i+l).stripWhiteSpace().isEmpty()) // found {#id} style id
1241  {
1242  QCString id = title.mid(i+2,l-3);
1243  title = title.left(i);
1244  //printf("found id='%s' title='%s'\n",id.data(),title.data());
1245  return id;
1246  }
1247  //printf("no id found in title '%s'\n",title.data());
1248  return "";
1249 }
1250 
1251 
1252 static int isAtxHeader(const char *data,int size,
1253  QCString &header,QCString &id)
1254 {
1255  int i = 0, end;
1256  int level = 0, blanks=0;
1257 
1258  // find start of header text and determine heading level
1259  while (i<size && data[i]==' ') i++;
1260  if (i>=size || data[i]!='#')
1261  {
1262  return 0;
1263  }
1264  while (i<size && level<6 && data[i]=='#') i++,level++;
1265  while (i<size && data[i]==' ') i++,blanks++;
1266  if (level==1 && blanks==0)
1267  {
1268  return 0; // special case to prevent #someid seen as a header (see bug 671395)
1269  }
1270 
1271  // find end of header text
1272  end=i;
1273  while (end<size && data[end]!='\n') end++;
1274  while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
1275 
1276  // store result
1277  convertStringFragment(header,data+i,end-i);
1278  id = extractTitleId(header);
1279  if (!id.isEmpty()) // strip #'s between title and id
1280  {
1281  i=header.length()-1;
1282  while (i>=0 && (header.at(i)=='#' || header.at(i)==' ')) i--;
1283  header=header.left(i+1);
1284  }
1285 
1286  return level;
1287 }
1288 
1289 static int isEmptyLine(const char *data,int size)
1290 {
1291  int i=0;
1292  while (i<size)
1293  {
1294  if (data[i]=='\n') return TRUE;
1295  if (data[i]!=' ') return FALSE;
1296  i++;
1297  }
1298  return TRUE;
1299 }
1300 
1301 #define isLiTag(i) \
1302  (data[(i)]=='<' && \
1303  (data[(i)+1]=='l' || data[(i)+1]=='L') && \
1304  (data[(i)+2]=='i' || data[(i)+2]=='I') && \
1305  (data[(i)+3]=='>'))
1306 
1307 // compute the indent from the start of the input, excluding list markers
1308 // such as -, -#, *, +, 1., and <li>
1309 static int computeIndentExcludingListMarkers(const char *data,int size)
1310 {
1311  int i=0;
1312  int indent=0;
1313  bool isDigit=FALSE;
1314  bool isLi=FALSE;
1315  bool listMarkerSkipped=FALSE;
1316  while (i<size &&
1317  (data[i]==' ' || // space
1318  (!listMarkerSkipped && // first list marker
1319  (data[i]=='+' || data[i]=='-' || data[i]=='*' || // unordered list char
1320  (data[i]=='#' && i>0 && data[i-1]=='-') || // -# item
1321  (isDigit=(data[i]>='1' && data[i]<='9')) || // ordered list marker?
1322  (isLi=(i<size-3 && isLiTag(i))) // <li> tag
1323  )
1324  )
1325  )
1326  )
1327  {
1328  if (isDigit) // skip over ordered list marker '10. '
1329  {
1330  int j=i+1;
1331  while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
1332  {
1333  if (data[j]=='.') // should be end of the list marker
1334  {
1335  if (j<size-1 && data[j+1]==' ') // valid list marker
1336  {
1337  listMarkerSkipped=TRUE;
1338  indent+=j+1-i;
1339  i=j+1;
1340  break;
1341  }
1342  else // not a list marker
1343  {
1344  break;
1345  }
1346  }
1347  j++;
1348  }
1349  }
1350  else if (isLi)
1351  {
1352  i+=3; // skip over <li>
1353  indent+=3;
1354  listMarkerSkipped=TRUE;
1355  }
1356  else if (data[i]=='-' && i<size-2 && data[i+1]=='#' && data[i+2]==' ')
1357  { // case "-# "
1358  listMarkerSkipped=TRUE; // only a single list marker is accepted
1359  i++; // skip over #
1360  indent++;
1361  }
1362  else if (data[i]!=' ' && i<size-1 && data[i+1]==' ')
1363  { // case "- " or "+ " or "* "
1364  listMarkerSkipped=TRUE; // only a single list marker is accepted
1365  }
1366  if (data[i]!=' ' && !listMarkerSkipped)
1367  { // end of indent
1368  break;
1369  }
1370  indent++,i++;
1371  }
1372  //printf("{%s}->%d\n",QCString(data).left(size).data(),indent);
1373  return indent;
1374 }
1375 
1376 static bool isFencedCodeBlock(const char *data,int size,int refIndent,
1377  QCString &lang,int &start,int &end,int &offset)
1378 {
1379  // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
1380  // return FALSE
1381  int i=0;
1382  int indent=0;
1383  int startTildes=0;
1384  while (i<size && data[i]==' ') indent++,i++;
1385  if (indent>=refIndent+4) return FALSE; // part of code block
1386  char tildaChar='~';
1387  if (i<size && data[i]=='`') tildaChar='`';
1388  while (i<size && data[i]==tildaChar) startTildes++,i++;
1389  if (startTildes<3) return FALSE; // not enough tildes
1390  if (i<size && data[i]=='{') i++; // skip over optional {
1391  int startLang=i;
1392  while (i<size && (data[i]!='\n' && data[i]!='}' && data[i]!=' ')) i++;
1393  convertStringFragment(lang,data+startLang,i-startLang);
1394  while (i<size && data[i]!='\n') i++; // proceed to the end of the line
1395  start=i;
1396  while (i<size)
1397  {
1398  if (data[i]==tildaChar)
1399  {
1400  end=i-1;
1401  int endTildes=0;
1402  while (i<size && data[i]==tildaChar) endTildes++,i++;
1403  while (i<size && data[i]==' ') i++;
1404  if (i==size || data[i]=='\n')
1405  {
1406  offset=i;
1407  return endTildes==startTildes;
1408  }
1409  }
1410  i++;
1411  }
1412  return FALSE;
1413 }
1414 
1415 static bool isCodeBlock(const char *data,int offset,int size,int &indent)
1416 {
1417  //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
1418  // determine the indent of this line
1419  int i=0;
1420  int indent0=0;
1421  while (i<size && data[i]==' ') indent0++,i++;
1422 
1423  if (indent0<codeBlockIndent)
1424  {
1425  //printf(">isCodeBlock: line is not indented enough %d<4\n",indent0);
1426  return FALSE;
1427  }
1428  if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
1429  {
1430  //printf("only spaces at the end of a comment block\n");
1431  return FALSE;
1432  }
1433 
1434  i=offset;
1435  int nl=0;
1436  int nl_pos[3];
1437  // search back 3 lines and remember the start of lines -1 and -2
1438  while (i>0 && nl<3)
1439  {
1440  if (data[i-offset-1]=='\n') nl_pos[nl++]=i-offset;
1441  i--;
1442  }
1443 
1444  // if there are only 2 preceding lines, then line -2 starts at -offset
1445  if (i==0 && nl==2) nl_pos[nl++]=-offset;
1446  //printf(" nl=%d\n",nl);
1447 
1448  if (nl==3) // we have at least 2 preceding lines
1449  {
1450  //printf(" positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
1451  // nl_pos[0],nl_pos[1],nl_pos[2],
1452  // QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1).data(),
1453  // QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1).data());
1454 
1455  // check that line -1 is empty
1456  if (!isEmptyLine(data+nl_pos[1],nl_pos[0]-nl_pos[1]-1))
1457  {
1458  return FALSE;
1459  }
1460 
1461  // determine the indent of line -2
1462  indent=computeIndentExcludingListMarkers(data+nl_pos[2],nl_pos[1]-nl_pos[2]);
1463 
1464  //printf(">isCodeBlock local_indent %d>=%d+4=%d\n",
1465  // indent0,indent2,indent0>=indent2+4);
1466  // if the difference is >4 spaces -> code block
1467  return indent0>=indent+codeBlockIndent;
1468  }
1469  else // not enough lines to determine the relative indent, use global indent
1470  {
1471  // check that line -1 is empty
1472  if (nl==1 && !isEmptyLine(data-offset,offset-1))
1473  {
1474  return FALSE;
1475  }
1476  //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
1477  // indent0,indent,indent0>=indent+4,nl);
1478  return indent0>=indent+codeBlockIndent;
1479  }
1480 }
1481 
1482 /** Finds the location of the table's contains in the string \a data.
1483  * Only one line will be inspected.
1484  * @param[in] data pointer to the string buffer.
1485  * @param[in] size the size of the buffer.
1486  * @param[out] start offset of the first character of the table content
1487  * @param[out] end offset of the last character of the table content
1488  * @param[out] columns number of table columns found
1489  * @returns The offset until the next line in the buffer.
1490  */
1491 int findTableColumns(const char *data,int size,int &start,int &end,int &columns)
1492 {
1493  int i=0,n=0;
1494  int eol;
1495  // find start character of the table line
1496  while (i<size && data[i]==' ') i++;
1497  if (i<size && data[i]=='|' && data[i]!='\n') i++,n++; // leading | does not count
1498  start = i;
1499 
1500  // find end character of the table line
1501  while (i<size && data[i]!='\n') i++;
1502  eol=i+1;
1503  i--;
1504  while (i>0 && data[i]==' ') i--;
1505  if (i>0 && data[i-1]!='\\' && data[i]=='|') i--,n++; // trailing or escaped | does not count
1506  end = i;
1507 
1508  // count columns between start and end
1509  columns=0;
1510  if (end>start)
1511  {
1512  i=start;
1513  while (i<=end) // look for more column markers
1514  {
1515  if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
1516  if (columns==1) columns++; // first | make a non-table into a two column table
1517  i++;
1518  }
1519  }
1520  if (n==2 && columns==0) // table row has | ... |
1521  {
1522  columns++;
1523  }
1524  //printf("findTableColumns(start=%d,end=%d,columns=%d) eol=%d\n",
1525  // start,end,columns,eol);
1526  return eol;
1527 }
1528 
1529 /** Returns TRUE iff data points to the start of a table block */
1530 static bool isTableBlock(const char *data,int size)
1531 {
1532  int cc0,start,end;
1533 
1534  // the first line should have at least two columns separated by '|'
1535  int i = findTableColumns(data,size,start,end,cc0);
1536  if (i>=size || cc0<1)
1537  {
1538  //printf("isTableBlock: no |'s in the header\n");
1539  return FALSE;
1540  }
1541 
1542  int cc1;
1543  int ret = findTableColumns(data+i,size-i,start,end,cc1);
1544  int j=i+start;
1545  // separator line should consist of |, - and : and spaces only
1546  while (j<=end+i)
1547  {
1548  if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
1549  {
1550  //printf("isTableBlock: invalid character '%c'\n",data[j]);
1551  return FALSE; // invalid characters in table separator
1552  }
1553  j++;
1554  }
1555  if (cc1!=cc0) // number of columns should be same as previous line
1556  {
1557  return FALSE;
1558  }
1559 
1560  i+=ret; // goto next line
1561  int cc2;
1562  findTableColumns(data+i,size-i,start,end,cc2);
1563 
1564  //printf("isTableBlock: %d\n",cc1==cc2);
1565  return cc1==cc2;
1566 }
1567 
1568 static int writeTableBlock(GrowBuf &out,const char *data,int size)
1569 {
1570  int i=0,j,k;
1571  int columns,start,end,cc;
1572 
1573  i = findTableColumns(data,size,start,end,columns);
1574 
1575  out.addStr("<table>");
1576 
1577  // write table header, in range [start..end]
1578  out.addStr("<tr>");
1579 
1580  int headerStart = start;
1581  int headerEnd = end;
1582 
1583  // read cell alignments
1584  int ret = findTableColumns(data+i,size-i,start,end,cc);
1585  k=0;
1586  Alignment *columnAlignment = new Alignment[columns];
1587 
1588  bool leftMarker=FALSE,rightMarker=FALSE;
1589  bool startFound=FALSE;
1590  j=start+i;
1591  while (j<=end+i)
1592  {
1593  if (!startFound)
1594  {
1595  if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
1596  if (data[j]=='-') startFound=TRUE;
1597  //printf(" data[%d]=%c startFound=%d\n",j,data[j],startFound);
1598  }
1599  if (data[j]=='-') rightMarker=FALSE;
1600  else if (data[j]==':') rightMarker=TRUE;
1601  if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1602  {
1603  if (k<columns)
1604  {
1605  columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1606  //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1607  leftMarker=FALSE;
1608  rightMarker=FALSE;
1609  startFound=FALSE;
1610  }
1611  k++;
1612  }
1613  j++;
1614  }
1615  if (k<columns)
1616  {
1617  columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1618  //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1619  }
1620  // proceed to next line
1621  i+=ret;
1622 
1623  int m=headerStart;
1624  for (k=0;k<columns;k++)
1625  {
1626  out.addStr("<th");
1627  switch (columnAlignment[k])
1628  {
1629  case AlignLeft: out.addStr(" align=\"left\""); break;
1630  case AlignRight: out.addStr(" align=\"right\""); break;
1631  case AlignCenter: out.addStr(" align=\"center\""); break;
1632  case AlignNone: break;
1633  }
1634  out.addStr(">");
1635  while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
1636  {
1637  out.addChar(data[m++]);
1638  }
1639  m++;
1640  }
1641  out.addStr("\n</th>\n");
1642 
1643  // write table cells
1644  while (i<size)
1645  {
1646  int ret = findTableColumns(data+i,size-i,start,end,cc);
1647  //printf("findTableColumns cc=%d\n",cc);
1648  if (cc!=columns) break; // end of table
1649 
1650  out.addStr("<tr>");
1651  j=start+i;
1652  int columnStart=j;
1653  k=0;
1654  while (j<=end+i)
1655  {
1656  if (j==columnStart)
1657  {
1658  out.addStr("<td");
1659  switch (columnAlignment[k])
1660  {
1661  case AlignLeft: out.addStr(" align=\"left\""); break;
1662  case AlignRight: out.addStr(" align=\"right\""); break;
1663  case AlignCenter: out.addStr(" align=\"center\""); break;
1664  case AlignNone: break;
1665  }
1666  out.addStr(">");
1667  }
1668  if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1669  {
1670  columnStart=j+1;
1671  k++;
1672  }
1673  else
1674  {
1675  out.addChar(data[j]);
1676  }
1677  j++;
1678  }
1679  out.addChar('\n');
1680 
1681  // proceed to next line
1682  i+=ret;
1683  }
1684 
1685  out.addStr("</table> ");
1686 
1687  delete[] columnAlignment;
1688  return i;
1689 }
1690 
1691 
1692 void writeOneLineHeaderOrRuler(GrowBuf &out,const char *data,int size)
1693 {
1694  int level;
1695  QCString header;
1696  QCString id;
1697  if (isHRuler(data,size))
1698  {
1699  out.addStr("<hr>\n");
1700  }
1701  else if ((level=isAtxHeader(data,size,header,id)))
1702  {
1703  //if (level==1) g_correctSectionLevel=FALSE;
1704  //if (g_correctSectionLevel) level--;
1705  QCString hTag;
1706  if (level<5 && !id.isEmpty())
1707  {
1709  switch(level)
1710  {
1711  case 1: out.addStr("@section ");
1712  type=SectionInfo::Section;
1713  break;
1714  case 2: out.addStr("@subsection ");
1716  break;
1717  case 3: out.addStr("@subsubsection ");
1719  break;
1720  default: out.addStr("@paragraph ");
1722  break;
1723  }
1724  out.addStr(id);
1725  out.addStr(" ");
1726  out.addStr(header);
1727  out.addStr("\n");
1729  if (si)
1730  {
1731  if (si->lineNr != -1)
1732  {
1733  warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s, line %d)",header.data(),si->fileName.data(),si->lineNr);
1734  }
1735  else
1736  {
1737  warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s)",header.data(),si->fileName.data());
1738  }
1739  }
1740  else
1741  {
1742  si = new SectionInfo(g_fileName,g_lineNr,id,header,type,level);
1743  if (g_current)
1744  {
1745  g_current->anchors->append(si);
1746  }
1747  Doxygen::sectionDict->append(id,si);
1748  }
1749  }
1750  else
1751  {
1752  if (!id.isEmpty())
1753  {
1754  out.addStr("\\anchor "+id+"\n");
1755  }
1756  hTag.sprintf("h%d",level);
1757  out.addStr("<"+hTag+">");
1758  out.addStr(header);
1759  out.addStr("</"+hTag+">\n");
1760  }
1761  }
1762  else // nothing interesting -> just output the line
1763  {
1764  out.addStr(data,size);
1765  }
1766 }
1767 
1768 static int writeBlockQuote(GrowBuf &out,const char *data,int size)
1769 {
1770  int l;
1771  int i=0;
1772  int curLevel=0;
1773  int end=0;
1774  while (i<size)
1775  {
1776  // find end of this line
1777  end=i+1;
1778  while (end<=size && data[end-1]!='\n') end++;
1779  int j=i;
1780  int level=0;
1781  int indent=i;
1782  // compute the quoting level
1783  while (j<end && (data[j]==' ' || data[j]=='>'))
1784  {
1785  if (data[j]=='>') { level++; indent=j+1; }
1786  else if (j>0 && data[j-1]=='>') indent=j+1;
1787  j++;
1788  }
1789  if (j>0 && data[j-1]=='>' &&
1790  !(j==size || data[j]=='\n')) // disqualify last > if not followed by space
1791  {
1792  indent--;
1793  j--;
1794  }
1795  if (level>curLevel) // quote level increased => add start markers
1796  {
1797  for (l=curLevel;l<level;l++)
1798  {
1799  out.addStr("<blockquote>\n");
1800  }
1801  }
1802  else if (level<curLevel) // quote level descreased => add end markers
1803  {
1804  for (l=level;l<curLevel;l++)
1805  {
1806  out.addStr("</blockquote>\n");
1807  }
1808  }
1809  curLevel=level;
1810  if (level==0) break; // end of quote block
1811  // copy line without quotation marks
1812  out.addStr(data+indent,end-indent);
1813  // proceed with next line
1814  i=end;
1815  }
1816  // end of comment within blockquote => add end markers
1817  for (l=0;l<curLevel;l++)
1818  {
1819  out.addStr("</blockquote>\n");
1820  }
1821  return i;
1822 }
1823 
1824 static int writeCodeBlock(GrowBuf &out,const char *data,int size,int refIndent)
1825 {
1826  int i=0,end;
1827  //printf("writeCodeBlock: data={%s}\n",QCString(data).left(size).data());
1828  out.addStr("@verbatim\n");
1829  int emptyLines=0;
1830  while (i<size)
1831  {
1832  // find end of this line
1833  end=i+1;
1834  while (end<=size && data[end-1]!='\n') end++;
1835  int j=i;
1836  int indent=0;
1837  while (j<end && data[j]==' ') j++,indent++;
1838  //printf("j=%d end=%d indent=%d refIndent=%d tabSize=%d data={%s}\n",
1839  // j,end,indent,refIndent,Config_getInt("TAB_SIZE"),QCString(data+i).left(end-i-1).data());
1840  if (j==end-1) // empty line
1841  {
1842  emptyLines++;
1843  i=end;
1844  }
1845  else if (indent>=refIndent+codeBlockIndent) // enough indent to contine the code block
1846  {
1847  while (emptyLines>0) // write skipped empty lines
1848  {
1849  // add empty line
1850  out.addStr("\n");
1851  emptyLines--;
1852  }
1853  // add code line minus the indent
1854  out.addStr(data+i+refIndent+codeBlockIndent,end-i-refIndent-codeBlockIndent);
1855  i=end;
1856  }
1857  else // end of code block
1858  {
1859  break;
1860  }
1861  }
1862  out.addStr("@endverbatim\n");
1863  while (emptyLines>0) // write skipped empty lines
1864  {
1865  // add empty line
1866  out.addStr("\n");
1867  emptyLines--;
1868  }
1869  //printf("i=%d\n",i);
1870  return i;
1871 }
1872 
1873 // start searching for the end of the line start at offset \a i
1874 // keeping track of possible blocks that need to to skipped.
1875 static void findEndOfLine(GrowBuf &out,const char *data,int size,
1876  int &pi,int&i,int &end)
1877 {
1878  // find end of the line
1879  int nb=0;
1880  end=i+1;
1881  while (end<=size && data[end-1]!='\n')
1882  {
1883  // while looking for the end of the line we might encounter a block
1884  // that needs to be passed unprocessed.
1885  if ((data[end-1]=='\\' || data[end-1]=='@') && // command
1886  (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
1887  )
1888  {
1889  QCString endBlockName = isBlockCommand(data+end-1,end-1,size-(end-1));
1890  end++;
1891  if (!endBlockName.isEmpty())
1892  {
1893  int l = endBlockName.length();
1894  for (;end<size-l-1;end++) // search for end of block marker
1895  {
1896  if ((data[end]=='\\' || data[end]=='@') &&
1897  data[end-1]!='\\' && data[end-1]!='@'
1898  )
1899  {
1900  if (qstrncmp(&data[end+1],endBlockName,l)==0)
1901  {
1902  if (pi!=-1) // output previous line if available
1903  {
1904  //printf("feol out={%s}\n",QCString(data+pi).left(i-pi).data());
1905  out.addStr(data+pi,i-pi);
1906  }
1907  // found end marker, skip over this block
1908  //printf("feol.block out={%s}\n",QCString(data+i).left(end+l+1-i).data());
1909  out.addStr(data+i,end+l+1-i);
1910  pi=-1;
1911  i=end+l+1; // continue after block
1912  end=i+1;
1913  break;
1914  }
1915  }
1916  }
1917  }
1918  }
1919  else if (nb==0 && data[end-1]=='<' && end<size-6 &&
1920  (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
1921  )
1922  {
1923  if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
1924  tolower(data[end+2])=='e' && data[end+3]=='>') // <pre> tag
1925  {
1926  if (pi!=-1) // output previous line if available
1927  {
1928  out.addStr(data+pi,i-pi);
1929  }
1930  // output part until <pre>
1931  out.addStr(data+i,end-1-i);
1932  // output part until </pre>
1933  i = end-1 + processHtmlTag(out,data+end-1,end-1,size-end+1);
1934  pi=-1;
1935  end = i+1;
1936  break;
1937  }
1938  else
1939  {
1940  end++;
1941  }
1942  }
1943  else if (nb==0 && data[end-1]=='`')
1944  {
1945  while (end<=size && data[end-1]=='`') end++,nb++;
1946  }
1947  else if (nb>0 && data[end-1]=='`')
1948  {
1949  int enb=0;
1950  while (end<=size && data[end-1]=='`') end++,enb++;
1951  if (enb==nb) nb=0;
1952  }
1953  else
1954  {
1955  end++;
1956  }
1957  }
1958  //printf("findEndOfLine pi=%d i=%d end=%d {%s}\n",pi,i,end,QCString(data+i).left(end-i).data());
1959 }
1960 
1961 static void writeFencedCodeBlock(GrowBuf &out,const char *data,const char *lng,
1962  int blockStart,int blockEnd)
1963 {
1964  QCString lang = lng;
1965  if (!lang.isEmpty() && lang.at(0)=='.') lang=lang.mid(1);
1966  out.addStr("@code");
1967  if (!lang.isEmpty())
1968  {
1969  out.addStr("{"+lang+"}");
1970  }
1971  out.addStr(data+blockStart,blockEnd-blockStart);
1972  out.addStr("\n");
1973  out.addStr("@endcode");
1974 }
1975 
1976 static QCString processQuotations(const QCString &s,int refIndent)
1977 {
1978  GrowBuf out;
1979  const char *data = s.data();
1980  int size = s.length();
1981  int i=0,end=0,pi=-1;
1982  int blockStart,blockEnd,blockOffset;
1983  QCString lang;
1984  while (i<size)
1985  {
1986  findEndOfLine(out,data,size,pi,i,end);
1987  // line is now found at [i..end)
1988 
1989  if (pi!=-1)
1990  {
1991  if (isFencedCodeBlock(data+pi,size-pi,refIndent,lang,blockStart,blockEnd,blockOffset))
1992  {
1993  writeFencedCodeBlock(out,data+pi,lang,blockStart,blockEnd);
1994  i=pi+blockOffset;
1995  pi=-1;
1996  end=i+1;
1997  continue;
1998  }
1999  else if (isBlockQuote(data+pi,i-pi,refIndent))
2000  {
2001  i = pi+writeBlockQuote(out,data+pi,size-pi);
2002  pi=-1;
2003  end=i+1;
2004  continue;
2005  }
2006  else
2007  {
2008  //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
2009  out.addStr(data+pi,i-pi);
2010  }
2011  }
2012  pi=i;
2013  i=end;
2014  }
2015  if (pi!=-1 && pi<size) // deal with the last line
2016  {
2017  if (isBlockQuote(data+pi,size-pi,refIndent))
2018  {
2019  writeBlockQuote(out,data+pi,size-pi);
2020  }
2021  else
2022  {
2023  out.addStr(data+pi,size-pi);
2024  }
2025  }
2026  out.addChar(0);
2027 
2028  //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
2029  // s.data(),out.get());
2030 
2031  return out.get();
2032 }
2033 
2035 {
2036  GrowBuf out;
2037  const char *data = s.data();
2038  int size = s.length();
2039  int i=0,end=0,pi=-1,ref,level;
2040  QCString id,link,title;
2041  int blockIndent = indent;
2042 
2043  // get indent for the first line
2044  end = i+1;
2045  int sp=0;
2046  while (end<=size && data[end-1]!='\n')
2047  {
2048  if (data[end-1]==' ') sp++;
2049  end++;
2050  }
2051 
2052 #if 0 // commented out, since starting with a comment block is probably a usage error
2053  // see also http://stackoverflow.com/q/20478611/784672
2054 
2055  // special case when the documentation starts with a code block
2056  // since the first line is skipped when looking for a code block later on.
2057  if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
2058  {
2059  i=writeCodeBlock(out,data,size,blockIndent);
2060  end=i+1;
2061  pi=-1;
2062  }
2063 #endif
2064 
2065  // process each line
2066  while (i<size)
2067  {
2068  findEndOfLine(out,data,size,pi,i,end);
2069  // line is now found at [i..end)
2070 
2071  //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
2072 
2073  if (pi!=-1)
2074  {
2075  int blockStart,blockEnd,blockOffset;
2076  QCString lang;
2077  blockIndent = indent;
2078  //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
2079  if ((level=isHeaderline(data+i,size-i))>0)
2080  {
2081  //if (level==1) g_correctSectionLevel=FALSE;
2082  //if (g_correctSectionLevel) level--;
2083  //printf("Found header at %d-%d\n",i,end);
2084  while (pi<size && data[pi]==' ') pi++;
2085  QCString header,id;
2086  convertStringFragment(header,data+pi,i-pi-1);
2087  id = extractTitleId(header);
2088  //printf("header='%s' is='%s'\n",header.data(),id.data());
2089  if (!header.isEmpty())
2090  {
2091  if (!id.isEmpty())
2092  {
2093  out.addStr(level==1?"@section ":"@subsection ");
2094  out.addStr(id);
2095  out.addStr(" ");
2096  out.addStr(header);
2097  out.addStr("\n\n");
2099  if (si)
2100  {
2101  if (si->lineNr != -1)
2102  {
2103  warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s, line %d)",header.data(),si->fileName.data(),si->lineNr);
2104  }
2105  else
2106  {
2107  warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s)",header.data(),si->fileName.data());
2108  }
2109  }
2110  else
2111  {
2112  si = new SectionInfo(g_fileName,g_lineNr,id,header,
2113  level==1 ? SectionInfo::Section : SectionInfo::Subsection,level);
2114  if (g_current)
2115  {
2116  g_current->anchors->append(si);
2117  }
2118  Doxygen::sectionDict->append(id,si);
2119  }
2120  }
2121  else
2122  {
2123  out.addStr(level==1?"<h1>":"<h2>");
2124  out.addStr(header);
2125  out.addStr(level==1?"\n</h1>\n":"\n</h2>\n");
2126  }
2127  }
2128  else
2129  {
2130  out.addStr("<hr>\n");
2131  }
2132  pi=-1;
2133  i=end;
2134  end=i+1;
2135  continue;
2136  }
2137  else if ((ref=isLinkRef(data+pi,size-pi,id,link,title)))
2138  {
2139  //printf("found link ref: id='%s' link='%s' title='%s'\n",
2140  // id.data(),link.data(),title.data());
2141  g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2142  i=ref+pi;
2143  pi=-1;
2144  end=i+1;
2145  }
2146  else if (isFencedCodeBlock(data+pi,size-pi,indent,lang,blockStart,blockEnd,blockOffset))
2147  {
2148  //printf("Found FencedCodeBlock lang='%s' start=%d end=%d code={%s}\n",
2149  // lang.data(),blockStart,blockEnd,QCString(data+pi+blockStart).left(blockEnd-blockStart).data());
2150  writeFencedCodeBlock(out,data+pi,lang,blockStart,blockEnd);
2151  i=pi+blockOffset;
2152  pi=-1;
2153  end=i+1;
2154  continue;
2155  }
2156  else if (isCodeBlock(data+i,i,end-i,blockIndent))
2157  {
2158  // skip previous line (it is empty anyway)
2159  i+=writeCodeBlock(out,data+i,size-i,blockIndent);
2160  pi=-1;
2161  end=i+1;
2162  continue;
2163  }
2164  else if (isTableBlock(data+pi,size-pi))
2165  {
2166  i=pi+writeTableBlock(out,data+pi,size-pi);
2167  pi=-1;
2168  end=i+1;
2169  continue;
2170  }
2171  else
2172  {
2173  writeOneLineHeaderOrRuler(out,data+pi,i-pi);
2174  }
2175  }
2176  pi=i;
2177  i=end;
2178  }
2179  //printf("last line %d size=%d\n",i,size);
2180  if (pi!=-1 && pi<size) // deal with the last line
2181  {
2182  if (isLinkRef(data+pi,size-pi,id,link,title))
2183  {
2184  //printf("found link ref: id='%s' link='%s' title='%s'\n",
2185  // id.data(),link.data(),title.data());
2186  g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2187  }
2188  else
2189  {
2190  writeOneLineHeaderOrRuler(out,data+pi,size-pi);
2191  }
2192  }
2193 
2194  out.addChar(0);
2195  return out.get();
2196 }
2197 
2199 {
2200  int ln=0;
2201  // first first non-empty line
2202  QCString title;
2203  const char *data = docs.data();
2204  int i=0;
2205  int size=docs.size();
2206  while (i<size && (data[i]==' ' || data[i]=='\n'))
2207  {
2208  if (data[i]=='\n') ln++;
2209  i++;
2210  }
2211  if (i>=size) return "";
2212  int end1=i+1;
2213  while (end1<size && data[end1-1]!='\n') end1++;
2214  //printf("i=%d end1=%d size=%d line='%s'\n",i,end1,size,docs.mid(i,end1-i).data());
2215  // first line from i..end1
2216  if (end1<size)
2217  {
2218  ln++;
2219  // second line form end1..end2
2220  int end2=end1+1;
2221  while (end2<size && data[end2-1]!='\n') end2++;
2222  if (isHeaderline(data+end1,size-end1))
2223  {
2224  convertStringFragment(title,data+i,end1-i-1);
2225  QCString lns;
2226  lns.fill('\n',ln);
2227  docs=lns+docs.mid(end2);
2228  id = extractTitleId(title);
2229  //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2230  return title;
2231  }
2232  }
2233  if (i<end1 && isAtxHeader(data+i,end1-i,title,id)>0)
2234  {
2235  docs=docs.mid(end1);
2236  }
2237  //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2238  return title;
2239 }
2240 
2241 static QCString detab(const QCString &s,int &refIndent)
2242 {
2243  static int tabSize = Config_getInt("TAB_SIZE");
2244  GrowBuf out;
2245  int size = s.length();
2246  const char *data = s.data();
2247  int i=0;
2248  int col=0;
2249  const int maxIndent=1000000; // value representing infinity
2250  int minIndent=maxIndent;
2251  while (i<size)
2252  {
2253  char c = data[i++];
2254  switch(c)
2255  {
2256  case '\t': // expand tab
2257  {
2258  int stop = tabSize - (col%tabSize);
2259  //printf("expand at %d stop=%d\n",col,stop);
2260  col+=stop;
2261  while (stop--) out.addChar(' ');
2262  }
2263  break;
2264  case '\n': // reset colomn counter
2265  out.addChar(c);
2266  col=0;
2267  break;
2268  case ' ': // increment column counter
2269  out.addChar(c);
2270  col++;
2271  break;
2272  default: // non-whitespace => update minIndent
2273  out.addChar(c);
2274  if (c<0 && i<size) // multibyte sequence
2275  {
2276  out.addChar(data[i++]); // >= 2 bytes
2277  if (((uchar)c&0xE0)==0xE0 && i<size)
2278  {
2279  out.addChar(data[i++]); // 3 bytes
2280  }
2281  if (((uchar)c&0xF0)==0xF0 && i<size)
2282  {
2283  out.addChar(data[i++]); // 4 byres
2284  }
2285  }
2286  if (col<minIndent) minIndent=col;
2287  col++;
2288  }
2289  }
2290  if (minIndent!=maxIndent) refIndent=minIndent; else refIndent=0;
2291  out.addChar(0);
2292  //printf("detab refIndent=%d\n",refIndent);
2293  return out.get();
2294 }
2295 
2296 //---------------------------------------------------------------------------
2297 
2298 QCString processMarkdown(const QCString &fileName,const int lineNr,Entry *e,const QCString &input)
2299 {
2300  static bool init=FALSE;
2301  if (!init)
2302  {
2303  // setup callback table for special characters
2304  g_actions[(unsigned int)'_']=processEmphasis;
2305  g_actions[(unsigned int)'*']=processEmphasis;
2306  g_actions[(unsigned int)'`']=processCodeSpan;
2307  g_actions[(unsigned int)'\\']=processSpecialCommand;
2308  g_actions[(unsigned int)'@']=processSpecialCommand;
2309  g_actions[(unsigned int)'[']=processLink;
2310  g_actions[(unsigned int)'!']=processLink;
2311  g_actions[(unsigned int)'<']=processHtmlTag;
2312  g_actions[(unsigned int)'-']=processNmdash;
2313  g_actions[(unsigned int)'"']=processQuoted;
2314  init=TRUE;
2315  }
2316 
2317  g_linkRefs.setAutoDelete(TRUE);
2318  g_linkRefs.clear();
2319  g_current = e;
2320  g_fileName = fileName;
2321  g_lineNr = lineNr;
2322  static GrowBuf out;
2323  if (input.isEmpty()) return input;
2324  out.clear();
2325  int refIndent;
2326  // for replace tabs by spaces
2327  QCString s = detab(input,refIndent);
2328  //printf("======== DeTab =========\n---- output -----\n%s\n---------\n",s.data());
2329  // then process quotation blocks (as these may contain other blocks)
2330  s = processQuotations(s,refIndent);
2331  //printf("======== Quotations =========\n---- output -----\n%s\n---------\n",s.data());
2332  // then process block items (headers, rules, and code blocks, references)
2333  s = processBlocks(s,refIndent);
2334  //printf("======== Blocks =========\n---- output -----\n%s\n---------\n",s.data());
2335  // finally process the inline markup (links, emphasis and code spans)
2336  processInline(out,s,s.length());
2337  out.addChar(0);
2338  Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n%s\n---- output -----\n%s\n---------\n",qPrint(input),qPrint(out.get()));
2339  return out.get();
2340 }
2341 
2342 //---------------------------------------------------------------------------
2343 
2345 {
2346  QCString baseFn = stripFromPath(QFileInfo(fileName).absFilePath().utf8());
2347  int i = baseFn.findRev('.');
2348  if (i!=-1) baseFn = baseFn.left(i);
2349  QCString baseName = substitute(substitute(baseFn," ","_"),"/","_");
2350  return "md_"+baseName;
2351 }
2352 
2354  const char *fileBuf,
2355  Entry *root,
2356  bool /*sameTranslationUnit*/,
2357  QStrList & /*filesInSameTranslationUnit*/)
2358 {
2359  Entry *current = new Entry;
2360  current->lang = SrcLangExt_Markdown;
2361  current->fileName = fileName;
2362  current->docFile = fileName;
2363  current->docLine = 1;
2364  QCString docs = fileBuf;
2365  QCString id;
2367  QCString titleFn = QFileInfo(fileName).baseName().utf8();
2368  QCString fn = QFileInfo(fileName).fileName().utf8();
2369  static QCString mdfileAsMainPage = Config_getString("USE_MDFILE_AS_MAINPAGE");
2370  if (id.isEmpty()) id = markdownFileNameToId(fileName);
2371  if (!mdfileAsMainPage.isEmpty() &&
2372  (fn==mdfileAsMainPage || // name reference
2373  QFileInfo(fileName).absFilePath()==
2374  QFileInfo(mdfileAsMainPage).absFilePath()) // file reference with path
2375  )
2376  {
2377  docs.prepend("@mainpage "+title+"\n");
2378  }
2379  else if (id=="mainpage" || id=="index")
2380  {
2381  if (title.isEmpty()) title = titleFn;
2382  docs.prepend("@mainpage "+title+"\n");
2383  }
2384  else
2385  {
2386  if (title.isEmpty()) title = titleFn;
2387  docs.prepend("@page "+id+" "+title+"\n");
2388  }
2389  int lineNr=1;
2390  int position=0;
2391 
2392  // even without markdown support enabled, we still
2393  // parse markdown files as such
2394  bool markdownEnabled = Doxygen::markdownSupport;
2396 
2397  bool needsEntry = FALSE;
2398  Protection prot=Public;
2399  while (parseCommentBlock(
2400  this,
2401  current,
2402  docs,
2403  fileName,
2404  lineNr,
2405  FALSE, // isBrief
2406  FALSE, // javadoc autobrief
2407  FALSE, // inBodyDocs
2408  prot, // protection
2409  position,
2410  needsEntry))
2411  {
2412  if (needsEntry)
2413  {
2414  QCString docFile = current->docFile;
2415  root->addSubEntry(current);
2416  current = new Entry;
2417  current->lang = SrcLangExt_Markdown;
2418  current->docFile = docFile;
2419  current->docLine = lineNr;
2420  }
2421  }
2422  if (needsEntry)
2423  {
2424  root->addSubEntry(current);
2425  }
2426 
2427  // restore setting
2428  Doxygen::markdownSupport = markdownEnabled;
2429  //g_correctSectionLevel = FALSE;
2430 }
2431 
2433  const char *scopeName,
2434  const QCString &input,
2435  SrcLangExt lang,
2436  bool isExampleBlock,
2437  const char *exampleName,
2438  FileDef *fileDef,
2439  int startLine,
2440  int endLine,
2441  bool inlineFragment,
2442  MemberDef *memberDef,
2443  bool showLineNumbers,
2444  Definition *searchCtx,
2445  bool collectXRefs
2446  )
2447 {
2449  if (pIntf!=this)
2450  {
2451  pIntf->parseCode(
2452  codeOutIntf,scopeName,input,lang,isExampleBlock,exampleName,
2453  fileDef,startLine,endLine,inlineFragment,memberDef,showLineNumbers,
2454  searchCtx,collectXRefs);
2455  }
2456 }
2457 
2459 {
2461  if (pIntf!=this)
2462  {
2463  pIntf->resetCodeParserState();
2464  }
2465 }
2466 
2468 {
2470  if (pIntf!=this)
2471  {
2472  pIntf->parsePrototype(text);
2473  }
2474 }
2475 
static bool markdownSupport
Definition: doxygen.h:153
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
bool resize(uint newlen)
Definition: qcstring.h:225
Q_EXPORT int qstrncmp(const char *str1, const char *str2, uint len)
Definition: qcstring.h:101
char * rawData() const
Definition: qcstring.h:216
ParserInterface * getParser(const char *extension)
Definition: parserintf.h:191
static void convertStringFragment(QCString &result, const char *data, int size)
Definition: markdown.cpp:132
static bool isTableBlock(const char *data, int size)
Definition: markdown.cpp:1530
QCString stripWhiteSpace() const
Definition: qcstring.cpp:295
static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
Definition: markdown.cpp:366
static constexpr double nb
Definition: Units.h:81
QCString link
Definition: markdown.cpp:80
void clear()
Definition: growbuf.h:15
bool stat
static ?
Definition: entry.h:245
static int isAtxHeader(const char *data, int size, QCString &header, QCString &id)
Definition: markdown.cpp:1252
static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
Definition: markdown.cpp:335
void addStr(const char *s)
Definition: growbuf.h:19
#define ignoreCloseEmphChar(i)
Definition: markdown.cpp:70
static QCString result
bool isEmpty() const
Definition: qcstring.h:189
The QRegExp class provides pattern matching using regular expressions or wildcards.
Definition: qregexp.h:46
void parseCode(CodeOutputInterface &codeOutIntf, const char *scopeName, const QCString &input, SrcLangExt lang, bool isExampleBlock, const char *exampleName=0, FileDef *fileDef=0, int startLine=-1, int endLine=-1, bool inlineFragment=FALSE, MemberDef *memberDef=0, bool showLineNumbers=TRUE, Definition *searchCtx=0, bool collectXRefs=TRUE)
Definition: markdown.cpp:2432
uint length() const
Definition: qcstring.h:195
void append(const type *d)
Definition: qlist.h:73
Definition: entry.h:63
Abstract interface for programming language parsers.
Definition: parserintf.h:38
Definition: types.h:26
char & at(uint i) const
Definition: qcstring.h:326
static int computeIndentExcludingListMarkers(const char *data, int size)
Definition: markdown.cpp:1309
static int processEmphasis(GrowBuf &out, const char *data, int offset, int size)
Definition: markdown.cpp:589
static QCString blockName
static action_t g_actions[256]
Definition: markdown.cpp:92
QCString title
Definition: markdown.cpp:81
static int processHtmlTag(GrowBuf &out, const char *data, int offset, int size)
Definition: markdown.cpp:505
int docLine
line number at which the documentation was found
Definition: entry.h:261
const bool FALSE
Definition: qglobal.h:370
void addSubEntry(Entry *e)
Definition: entry.cpp:206
static int isHRuler(const char *data, int size)
Definition: markdown.cpp:1207
static QCString processQuotations(const QCString &s, int refIndent)
Definition: markdown.cpp:1976
QCString left(uint len) const
Definition: qcstring.cpp:213
static int writeTableBlock(GrowBuf &out, const char *data, int size)
Definition: markdown.cpp:1568
static void findEndOfLine(GrowBuf &out, const char *data, int size, int &pi, int &i, int &end)
Definition: markdown.cpp:1875
uint size() const
Definition: qcstring.h:201
int(* action_t)(GrowBuf &out, const char *data, int offset, int size)
Definition: markdown.cpp:84
init
Definition: train.py:42
int find(char c, int index=0, bool cs=TRUE) const
Definition: qcstring.cpp:41
void writeOneLineHeaderOrRuler(GrowBuf &out, const char *data, int size)
Definition: markdown.cpp:1692
void append(const char *key, const T *d)
Definition: sortdict.h:135
SrcLangExt
Definition: types.h:41
QString baseName() const
Definition: qfileinfo.cpp:341
static QStrList * l
Definition: config.cpp:1044
int findRev(char c, int index=-1, bool cs=TRUE) const
Definition: qcstring.cpp:95
unsigned char uchar
Definition: nybbler.cc:11
virtual void parsePrototype(const char *text)=0
#define eol
static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
Definition: markdown.cpp:996
static QCString processBlocks(const QCString &s, int indent)
Definition: markdown.cpp:2034
static QCString stripFromPath(const QCString &path, QStrList &l)
Definition: util.cpp:274
QString absFilePath() const
static Alignment markersToAlignment(bool leftMarker, bool rightMarker)
Definition: markdown.cpp:143
#define Config_getInt(val)
Definition: config.cpp:661
#define isLiTag(i)
Definition: markdown.cpp:1301
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
int findTableColumns(const char *data, int size, int &start, int &end, int &columns)
Definition: markdown.cpp:1491
static int isHeaderline(const char *data, int size)
Definition: markdown.cpp:1069
static ParserManager * parserManager
Definition: doxygen.h:141
static int processNmdash(GrowBuf &out, const char *data, int off, int size)
Definition: markdown.cpp:452
QAsciiDict< Entry > fn
static int isEmptyLine(const char *data, int size)
Definition: markdown.cpp:1289
LinkRef(const QCString &l, const QCString &t)
Definition: markdown.cpp:79
void addChar(char c)
Definition: growbuf.h:16
static int g_lineNr
Definition: markdown.cpp:95
static int writeBlockQuote(GrowBuf &out, const char *data, int size)
Definition: markdown.cpp:1768
string columns
Definition: depos.py:13
const double e
static int findEmphasisChar(const char *data, int size, char c, int c_size)
Definition: markdown.cpp:237
static int input(void)
Definition: code.cpp:15695
fileName
Definition: dumpTree.py:9
static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
Definition: markdown.cpp:395
static QCString extractTitleId(QCString &title)
Definition: markdown.cpp:1234
static void writeFencedCodeBlock(GrowBuf &out, const char *data, const char *lng, int blockStart, int blockEnd)
Definition: markdown.cpp:1961
std::void_t< T > n
static int writeCodeBlock(GrowBuf &out, const char *data, int size, int refIndent)
Definition: markdown.cpp:1824
FileDef * findFileDef(const FileNameDict *fnDict, const char *n, bool &ambig)
Definition: util.cpp:4963
QList< SectionInfo > * anchors
list of anchors defined in this entry
Definition: entry.h:281
QCString fileName
Definition: section.h:61
void parsePrototype(const char *text)
Definition: markdown.cpp:2467
static void print(DebugMask mask, int prio, const char *fmt,...)
Definition: debug.cpp:84
QCString & prepend(const char *s)
Definition: qcstring.cpp:387
p
Definition: test.py:223
static Entry * g_current
Definition: markdown.cpp:93
static SectionDict * sectionDict
Definition: doxygen.h:117
A bunch of utility functions.
static Entry * current
const char * data() const
Definition: qcstring.h:207
static QCString extractPageTitle(QCString &docs, QCString &id)
Definition: markdown.cpp:2198
#define Config_getString(val)
Definition: config.cpp:660
int lineNr
Definition: section.h:62
static QCString detab(const QCString &s, int &refIndent)
Definition: markdown.cpp:2241
void warn(const char *file, int line, const char *fmt,...)
Definition: message.cpp:183
static FileNameDict * imageNameDict
Definition: doxygen.h:110
static int processQuoted(GrowBuf &out, const char *data, int, int size)
Definition: markdown.cpp:484
QCString mid(uint index, uint len=0xffffffff) const
Definition: qcstring.cpp:246
int match(const QCString &str, int index=0, int *len=0, bool indexIsStart=TRUE) const
Definition: qregexp.cpp:649
void resetCodeParserState()
Definition: markdown.cpp:2458
QCString & sprintf(const char *format,...)
Definition: qcstring.cpp:27
static int processCodeSpan(GrowBuf &out, const char *data, int, int size)
Definition: markdown.cpp:918
bool parseCommentBlock(ParserInterface *parser, Entry *curEntry, const QCString &comment, const QCString &fileName, int &lineNr, bool isBrief, bool isAutoBriefOn, bool isInbody, Protection &prot, int &position, bool &newEntryNeeded)
static bool isFencedCodeBlock(const char *data, int size, int refIndent, QCString &lang, int &start, int &end, int &offset)
Definition: markdown.cpp:1376
static bool isCodeBlock(const char *data, int offset, int size, int &indent)
Definition: markdown.cpp:1415
const char * get()
Definition: growbuf.h:38
const int codeBlockIndent
Definition: markdown.cpp:106
static int processLink(GrowBuf &out, const char *data, int, int size)
Definition: markdown.cpp:631
SrcLangExt getLanguageFromFileName(const QCString fileName)
Definition: util.cpp:7061
QCString processMarkdown(const QCString &fileName, const int lineNr, Entry *e, const QCString &input)
Definition: markdown.cpp:2298
static QCString escapeSpecialChars(const QCString &s)
Definition: markdown.cpp:111
static bool isBlockQuote(const char *data, int size, int indent)
Definition: markdown.cpp:1092
static QCString g_fileName
Definition: markdown.cpp:94
bool fill(char c, int len=-1)
Definition: qcstring.h:243
QCString fileName
file this entry was extracted from
Definition: entry.h:282
QString fileName() const
std::string indent0()
Protection
Definition: types.h:26
QCString docFile
file in which the documentation was found
Definition: entry.h:262
float pi
Definition: units.py:11
static void processInline(GrowBuf &out, const char *data, int size)
Definition: markdown.cpp:1045
static QCString baseName
Definition: scanner.cpp:10890
The QFileInfo class provides system-independent file information.
Definition: qfileinfo.h:51
QCString lower() const
Definition: qcstring.cpp:263
static int braceCount
QCString markdownFileNameToId(const QCString &fileName)
Definition: markdown.cpp:2344
SrcLangExt lang
programming language in which this entry was found
Definition: entry.h:286
#define isOpenEmphChar(i)
Definition: markdown.cpp:63
T * find(const char *key)
Definition: sortdict.h:232
QCString utf8() const
Definition: qstring.cpp:14507
static QCString isBlockCommand(const char *data, int offset, int size)
Definition: markdown.cpp:183
Alignment
Definition: markdown.cpp:86
virtual void parseCode(CodeOutputInterface &codeOutIntf, const char *scopeName, const QCString &input, SrcLangExt lang, bool isExampleBlock, const char *exampleName=0, FileDef *fileDef=0, int startLine=-1, int endLine=-1, bool inlineFragment=FALSE, MemberDef *memberDef=0, bool showLineNumbers=TRUE, Definition *searchCtx=0, bool collectXRefs=TRUE)=0
void parseInput(const char *fileName, const char *fileBuf, Entry *root, bool sameTranslationUnit, QStrList &filesInSameTranslationUnit)
Definition: markdown.cpp:2353
std::string nl(std::size_t i=1)
Interface for the comment block parser.
static QCString * s
Definition: config.cpp:1042
const char * qPrint(const char *s)
Definition: qcstring.h:797
static int isLinkRef(const char *data, int size, QCString &refid, QCString &link, QCString &title)
Definition: markdown.cpp:1117
const bool TRUE
Definition: qglobal.h:371
bool collectXRefs
#define isIdChar(i)
Definition: markdown.cpp:56
QCString simplifyWhiteSpace() const
Definition: qcstring.cpp:323
QCString substitute(const QCString &s, const QCString &src, const QCString &dst)
substitute all occurrences of src in s by dst
Definition: util.cpp:5088
static QDict< LinkRef > g_linkRefs(257)
virtual void resetCodeParserState()=0