doxygen  1.8.18
About: Doxygen is a source code documentation generator tool for C++, C, Objective-C, C#, PHP, Java, Python, IDL (diverse flavors), Fortran, VHDL, Tcl, and to some extent D. Different output formats are supported.
  Fossies Dox: doxygen-1.8.18.src.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

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