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