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