1 /******************************************************************************
3 * Copyright (C) 1997-2012 by Dimitri van Heesch.
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.
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
16 /* Note: part of the code below is inspired by libupskirt written by
17 * Natacha Porté. Original copyright message follows:
19 * Copyright (c) 2008, Natacha Porté
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.
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.
37 #include <qfileinfo.h>
45 #include "commentscan.h"
48 #include "commentcnv.h"
52 // is character at position i in data part of an identifier?
54 ((data[i]>='a' && data[i]<='z') || \
55 (data[i]>='A' && data[i]<='Z') || \
56 (data[i]>='0' && data[i]<='9')) \
58 // is character at position i in data allowed before an emphasis section
59 #define isOpenEmphChar(i) \
60 (data[i]=='\n' || data[i]==' ' || data[i]=='\'' || data[i]=='<' || \
61 data[i]=='{' || data[i]=='(' || data[i]=='[' || data[i]==',' || \
62 data[i]==':' || data[i]==';')
64 // is character at position i in data an escape that prevents ending an emphasis section
65 // so for example *bla (*.txt) is cool*
66 #define ignoreCloseEmphChar(i) \
67 (data[i]=='(' || data[i]=='{' || data[i]=='[' || data[i]=='<' || \
68 data[i]=='=' || data[i]=='+' || data[i]=='-' || data[i]=='\\' || \
75 LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
80 typedef int (*action_t)(GrowBuf &out,const char *data,int offset,int size);
82 enum Alignment { AlignNone, AlignLeft, AlignCenter, AlignRight };
87 static QDict<LinkRef> g_linkRefs(257);
88 static action_t g_actions[256];
89 static Entry *g_current;
90 static QCString g_fileName;
92 // In case a markdown page starts with a level1 header, that header is used
93 // as a title of the page, in effect making it a level0 header, so the
94 // level of all other sections needs to be corrected as well.
95 // This flag is TRUE if corrections are needed.
96 //static bool g_correctSectionLevel;
101 const int codeBlockIndent = 4;
103 static void processInline(GrowBuf &out,const char *data,int size);
105 // escape characters that have a special meaning later on.
106 static QCString escapeSpecialChars(const QCString &s)
108 if (s.isEmpty()) return "";
116 case '<': growBuf.addStr("\\<"); break;
117 case '>': growBuf.addStr("\\>"); break;
118 case '\\': growBuf.addStr("\\\\"); break;
119 case '@': growBuf.addStr("\\@"); break;
120 default: growBuf.addChar(c); break;
124 return growBuf.get();
127 static void convertStringFragment(QCString &result,const char *data,int size)
130 result.resize(size+1);
131 memcpy(result.data(),data,size);
132 result.at(size)='\0';
135 /** helper function to convert presence of left and/or right alignment markers
136 * to a alignment value
138 static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
140 //printf("markerToAlignment(%d,%d)\n",leftMarker,rightMarker);
141 if (leftMarker && rightMarker)
149 else if (rightMarker)
160 // Check if data contains a block command. If so returned the command
161 // that ends the block. If not an empty string is returned.
162 // Note When offset>0 character position -1 will be inspected.
164 // Checks for and skip the following block commands:
165 // {@code .. { .. } .. }
172 // \verbatim..\endverbatim
173 // \latexonly..\endlatexonly
174 // \htmlonly..\endhtmlonly
175 // \xmlonly..\endxmlonly
176 // \rtfonly..\endrtfonly
177 // \manonly..\endmanonly
178 static QCString isBlockCommand(const char *data,int offset,int size)
180 bool openBracket = offset>0 && data[-1]=='{';
181 bool isEscaped = offset>0 && (data[-1]=='\\' || data[-1]=='@');
182 if (isEscaped) return QCString();
185 while (end<size && (data[end]>='a' && data[end]<='z')) end++;
186 if (end==1) return QCString();
188 convertStringFragment(blockName,data+1,end-1);
189 if (blockName=="code" && openBracket)
193 else if (blockName=="dot" ||
196 blockName=="verbatim" ||
197 blockName=="latexonly" ||
198 blockName=="htmlonly" ||
199 blockName=="xmlonly" ||
200 blockName=="rtfonly" ||
204 return "end"+blockName;
206 else if (blockName=="f" && end<size)
212 else if (data[end]=='[')
216 else if (data[end]=='}')
224 /** looks for the next emph char, skipping other constructs, and
225 * stopping when either it is found, or we are at the end of a paragraph.
227 static int findEmphasisChar(const char *data, int size, char c)
233 while (i<size && data[i]!=c && data[i]!='`' &&
234 data[i]!='\\' && data[i]!='@' &&
236 //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
238 // not counting escaped chars or characters that are unlikely
239 // to appear as the end of the emphasis char
240 if (i>0 && ignoreCloseEmphChar(i-1))
245 else if (data[i] == c)
247 if (i<size-1 && isIdChar(i+1)) // to prevent touching some_underscore_identifier
252 return i; // found it
256 // skipping a code span
260 while (i<size && data[i]=='`') snb++,i++;
262 // find same pattern to end the span
264 while (i<size && enb<snb)
266 if (data[i]=='`') enb++;
267 if (snb==1 && data[i]=='\'') break; // ` ended by '
271 else if (data[i]=='@' || data[i]=='\\')
272 { // skip over blocks that should not be processed
273 QCString endBlockName = isBlockCommand(data+i,i,size-i);
274 if (!endBlockName.isEmpty())
277 int l = endBlockName.length();
280 if ((data[i]=='\\' || data[i]=='@') && // command
281 data[i-1]!='\\' && data[i-1]!='@') // not escaped
283 if (strncmp(&data[i+1],endBlockName,l)==0)
296 else if (data[i]=='\n') // end * or _ at paragraph boundary
299 while (i<size && data[i]==' ') i++;
300 if (i>=size || data[i]=='\n') return 0; // empty line -> paragraph
302 else // should not get here!
311 /** process single emphasis */
312 static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
316 /* skipping one symbol if coming from emph3 */
317 if (size>1 && data[0]==c && data[1]==c) { i=1; }
321 len = findEmphasisChar(data+i, size-i, c);
322 if (len==0) return 0;
324 if (i>=size) return 0;
326 if (i+1<size && data[i+1]==c)
331 if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
334 processInline(out,data,i);
342 /** process double emphasis */
343 static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
349 len = findEmphasisChar(data+i, size-i, c);
355 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' &&
359 out.addStr("<strong>");
360 processInline(out,data,i);
361 out.addStr("</strong>");
369 /** Parsing tripple emphasis.
370 * Finds the first closing tag, and delegates to the other emph
372 static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
378 len = findEmphasisChar(data+i, size-i, c);
385 /* skip whitespace preceded symbols */
386 if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
391 if (i+2<size && data[i+1]==c && data[i+2]==c)
393 out.addStr("<em><strong>");
394 processInline(out,data,i);
395 out.addStr("</strong></em>");
398 else if (i+1<size && data[i+1]==c)
400 // double symbol found, handing over to emph1
401 len = processEmphasis1(out, data-2, size+2, c);
413 // single symbol found, handing over to emph2
414 len = processEmphasis2(out, data-1, size+1, c);
428 /** Process ndash and mdashes */
429 static int processNmdash(GrowBuf &out,const char *data,int,int size)
431 // precondition: data[0]=='-'
434 if (i<size && data[i]=='-') // found --
438 if (i<size && data[i]=='-') // found ---
442 if (i<size && data[i]=='-') // found ----
446 if (count==2) // -- => ndash
448 out.addStr("–");
451 else if (count==3) // --- => ndash
453 out.addStr("—");
456 // not an ndash or mdash
460 /** Process a HTML tag. Note that <pre>..</pre> are treated specially, in
461 * the sense that all code inside is written unprocessed
463 static int processHtmlTag(GrowBuf &out,const char *data,int offset,int size)
465 if (offset>0 && data[-1]=='\\') return 0; // escaped <
467 // find the end of the html tag
470 // compute length of the tag name
471 while (i<size && isIdChar(i)) i++,l++;
473 convertStringFragment(tagName,data+1,i-1);
474 if (tagName.lower()=="pre") // found <pre> tag
476 bool insideStr=FALSE;
480 if (!insideStr && c=='<') // potential start of html tag
482 if (data[i+1]=='/' &&
483 tolower(data[i+2])=='p' && tolower(data[i+3])=='r' &&
484 tolower(data[i+4])=='e' && tolower(data[i+5])=='>')
485 { // found </pre> tag, copy from start to end of tag
486 out.addStr(data,i+6);
487 //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
491 else if (insideStr && c=='"')
493 if (data[i-1]!='\\') insideStr=FALSE;
502 else // some other html tag
506 if (data[i]=='/' && i<size-1 && data[i+1]=='>') // <bla/>
508 //printf("Found htmlTag={%s}\n",QCString(data).left(i+2).data());
509 out.addStr(data,i+2);
512 else if (data[i]=='>') // <bla>
514 //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
515 out.addStr(data,i+1);
518 else if (data[i]==' ') // <bla attr=...
521 bool insideAttr=FALSE;
524 if (!insideAttr && data[i]=='"')
528 else if (data[i]=='"' && data[i-1]!='\\')
532 else if (!insideAttr && data[i]=='>') // found end of tag
534 //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
535 out.addStr(data,i+1);
543 //printf("Not a valid html tag\n");
547 static int processEmphasis(GrowBuf &out,const char *data,int offset,int size)
549 if ((offset>0 && !isOpenEmphChar(-1)) || // invalid char before * or _
550 (size>1 && data[0]!=data[1] && !isIdChar(1)) || // invalid char after * or _
551 (size>2 && data[0]==data[1] && !isIdChar(2))) // invalid char after ** or __
558 if (size>2 && data[1]!=c) // _bla or *bla
560 // whitespace cannot follow an opening emphasis
561 if (data[1]==' ' || data[1]=='\n' ||
562 (ret = processEmphasis1(out, data+1, size-1, c)) == 0)
568 if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
570 if (data[2]==' ' || data[2]=='\n' ||
571 (ret = processEmphasis2(out, data+2, size-2, c)) == 0)
577 if (size>4 && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
579 if (data[3]==' ' || data[3]=='\n' ||
580 (ret = processEmphasis3(out, data+3, size-3, c)) == 0)
589 static int processLink(GrowBuf &out,const char *data,int,int size)
594 int contentStart,contentEnd,linkStart,titleStart,titleEnd;
595 bool isImageLink = FALSE;
601 if (size<2 || data[1]!='[')
610 // find the matching ]
613 if (data[i-1]=='\\') // skip escaped characters
616 else if (data[i]=='[')
620 else if (data[i]==']')
625 else if (data[i]=='\n')
628 if (nl>1) return 0; // only allow one newline in the content
632 if (i>=size) return 0; // premature end of comment -> no link
634 convertStringFragment(content,data+contentStart,contentEnd-contentStart);
635 //printf("processLink: content={%s}\n",content.data());
636 if (!isImageLink && content.isEmpty()) return 0; // no link text
640 while (i<size && data[i]==' ') i++;
641 if (i<size && data[i]=='\n') // one newline allowed here
644 // skip more whitespace
645 while (i<size && data[i]==' ') i++;
648 bool explicitTitle=FALSE;
649 if (i<size && data[i]=='(') // inline link
652 while (i<size && data[i]==' ') i++;
653 if (i<size && data[i]=='<') i++;
656 while (i<size && data[i]!='\'' && data[i]!='"' && data[i]!=')')
665 if (i>=size || data[i]=='\n') return 0;
666 convertStringFragment(link,data+linkStart,i-linkStart);
667 link = link.stripWhiteSpace();
668 //printf("processLink: link={%s}\n",link.data());
669 if (link.isEmpty()) return 0;
670 if (link.at(link.length()-1)=='>') link=link.left(link.length()-1);
673 if (data[i]=='\'' || data[i]=='"')
679 while (i<size && data[i]!=')')
693 // search back for closing marker
694 while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
695 if (data[titleEnd]==c) // found it
697 convertStringFragment(title,data+titleStart,titleEnd-titleStart);
698 //printf("processLink: title={%s}\n",title.data());
707 else if (i<size && data[i]=='[') // reference link
713 while (i<size && data[i]!=']')
722 if (i>=size) return 0;
724 convertStringFragment(link,data+linkStart,i-linkStart);
725 //printf("processLink: link={%s}\n",link.data());
726 link = link.stripWhiteSpace();
727 if (link.isEmpty()) // shortcut link
732 LinkRef *lr = g_linkRefs.find(link.lower());
737 //printf("processLink: ref: link={%s} title={%s}\n",link.data(),title.data());
739 else // reference not found!
741 //printf("processLink: ref {%s} do not exist\n",link.lower().data());
746 else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
748 LinkRef *lr = g_linkRefs.find(content.lower());
749 //printf("processLink: minimal link {%s} lr=%p",content.data(),lr);
757 else if (content=="TOC")
772 static QRegExp re("^[@\\]ref ");
773 if (isToc) // special case for [TOC]
775 if (g_current) g_current->stat=TRUE;
777 else if (isImageLink)
781 if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
782 (fd=findFileDef(Doxygen::imageNameDict,link,ambig)))
783 // assume doxygen symbol link or local image link
785 out.addStr("@image html ");
786 out.addStr(link.mid(fd ? 0 : 5));
787 if (!explicitTitle && !content.isEmpty())
793 else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
802 out.addStr("<img src=\"");
804 out.addStr("\" alt=\"");
807 if (!title.isEmpty())
809 out.addStr(" title=\"");
810 out.addStr(substitute(title.simplifyWhiteSpace(),"\"","""));
818 if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1)
819 // assume doxygen symbol link
823 if (explicitTitle && !title.isEmpty())
833 else if (link.find('/')!=-1 || link.find('.')!=-1 || link.find('#')!=-1)
835 out.addStr("<a href=\"");
838 if (!title.isEmpty())
840 out.addStr(" title=\"");
841 out.addStr(substitute(title.simplifyWhiteSpace(),"\"","""));
845 out.addStr(content.simplifyWhiteSpace());
848 else // avoid link to e.g. F[x](y)
850 //printf("no link for '%s'\n",link.data());
857 /** '`' parsing a code span (assuming codespan != 0) */
858 static int processCodeSpan(GrowBuf &out, const char *data, int /*offset*/, int size)
860 int end, nb = 0, i, f_begin, f_end;
862 /* counting the number of backticks in the delimiter */
863 while (nb<size && data[nb]=='`')
868 /* finding the next delimiter */
871 for (end=nb; end<size && i<nb && nl<2; end++)
877 else if (data[end]=='\n')
887 if (i < nb && end >= size)
889 return 0; // no matching delimiter
891 if (nl==2) // too many newlines inside the span
896 // trimming outside whitespaces
898 while (f_begin < end && data[f_begin]==' ')
903 while (f_end > nb && data[f_end-1]==' ')
908 if (nb==1) // check for closing ' followed by space within f_begin..f_end
913 if (data[i]=='\'' && !isIdChar(i+1)) // reject `some word' and not `it's cool`
920 //printf("found code span '%s'\n",QCString(data+f_begin).left(f_end-f_begin).data());
925 QCString codeFragment;
926 convertStringFragment(codeFragment,data+f_begin,f_end-f_begin);
928 //out.addStr(convertToHtml(codeFragment,TRUE));
929 out.addStr(escapeSpecialChars(codeFragment));
936 static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
939 QCString endBlockName = isBlockCommand(data,offset,size);
940 if (!endBlockName.isEmpty())
942 int l = endBlockName.length();
945 if ((data[i]=='\\' || data[i]=='@') && // command
946 data[i-1]!='\\' && data[i-1]!='@') // not escaped
948 if (strncmp(&data[i+1],endBlockName,l)==0)
950 //printf("found end at %d\n",i);
951 out.addStr(data,i+1+l);
958 if (size>1 && data[0]=='\\')
961 if (c=='[' || c==']' || c=='*' || c=='+' || c=='-' ||
962 c=='!' || c=='(' || c==')' || c=='.' || c=='`' || c=='_')
964 out.addStr(&data[1],1);
971 static void processInline(GrowBuf &out,const char *data,int size)
977 while (end<size && ((action=g_actions[(uchar)data[end]])==0)) end++;
978 out.addStr(data+i,end-i);
979 if (end>=size) break;
981 end = action(out,data+i,i,size-i);
994 /** returns whether the line is a setext-style hdr underline */
995 static int isHeaderline(const char *data, int size)
998 while (i<size && data[i]==' ') i++;
1000 // test of level 1 header
1003 while (i<size && data[i]=='=') i++,c++;
1004 while (i<size && data[i]==' ') i++;
1005 return (c>1 && (i>=size || data[i]=='\n')) ? 1 : 0;
1007 // test of level 2 header
1010 while (i<size && data[i]=='-') i++,c++;
1011 while (i<size && data[i]==' ') i++;
1012 return (c>1 && (i>=size || data[i]=='\n')) ? 2 : 0;
1017 /** returns TRUE if this line starts a block quote */
1018 static bool isBlockQuote(const char *data,int size,int indent)
1021 while (i<size && data[i]==' ') i++;
1022 if (i<indent+codeBlockIndent) // could be a quotation
1024 // count >'s and skip spaces
1026 while (i<size && (data[i]=='>' || data[i]==' '))
1028 if (data[i]=='>') level++;
1031 // last characters should be a space or newline,
1032 // so a line starting with >= does not match
1033 return level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n');
1035 else // too much indentation -> code block
1039 //return i<size && data[i]=='>' && i<indent+codeBlockIndent;
1042 /** returns end of the link ref if this is indeed a link reference. */
1043 static int isLinkRef(const char *data,int size,
1044 QCString &refid,QCString &link,QCString &title)
1046 //printf("isLinkRef data={%s}\n",data);
1047 // format: start with [some text]:
1049 while (i<size && data[i]==' ') i++;
1050 if (i>=size || data[i]!='[') return 0;
1053 while (i<size && data[i]!='\n' && data[i]!=']') i++;
1054 if (i>=size || data[i]!=']') return 0;
1055 convertStringFragment(refid,data+refIdStart,i-refIdStart);
1056 if (refid.isEmpty()) return 0;
1057 //printf(" isLinkRef: found refid='%s'\n",refid.data());
1059 if (i>=size || data[i]!=':') return 0;
1062 // format: whitespace* \n? whitespace* (<url> | url)
1063 while (i<size && data[i]==' ') i++;
1064 if (i<size && data[i]=='\n')
1067 while (i<size && data[i]==' ') i++;
1069 if (i>=size) return 0;
1071 if (i<size && data[i]=='<') i++;
1073 while (i<size && data[i]!=' ' && data[i]!='\n') i++;
1075 if (i<size && data[i]=='>') i++;
1076 if (linkStart==linkEnd) return 0; // empty link
1077 convertStringFragment(link,data+linkStart,linkEnd-linkStart);
1078 //printf(" isLinkRef: found link='%s'\n",link.data());
1079 if (link=="@ref" || link=="\\ref")
1082 while (i<size && data[i]!='\n' && data[i]!='"') i++;
1084 convertStringFragment(refArg,data+argStart,i-argStart);
1090 // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
1092 while (i<size && data[i]==' ') i++;
1093 if (i<size && data[i]=='\n')
1097 while (i<size && data[i]==' ') i++;
1101 //printf("end of isLinkRef while looking for title! i=%d\n",i);
1102 return i; // end of buffer while looking for the optional title
1106 if (c=='\'' || c=='"' || c=='(') // optional title present?
1108 //printf(" start of title found! char='%c'\n",c);
1110 if (c=='(') c=')'; // replace c by end character
1112 // search for end of the line
1113 while (i<size && data[i]!='\n') i++;
1115 // search back to matching character
1117 while (end>titleStart && data[end]!=c) end--;
1120 convertStringFragment(title,data+titleStart,end-titleStart);
1122 //printf(" title found: '%s'\n",title.data());
1124 while (i<size && data[i]==' ') i++;
1125 //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
1126 // i,size,data[i],eol);
1127 if (i>=size) return i; // end of buffer while ref id was found
1128 else if (data[i]=='\n') return i+1; // end of line while ref id was found
1129 else if (eol) return eol; // no optional title found
1130 return 0; // invalid link ref
1133 static int isHRuler(const char *data,int size)
1136 if (size>0 && data[size-1]=='\n') size--; // ignore newline character
1137 while (i<size && data[i]==' ') i++;
1138 if (i>=size) return 0; // empty line
1140 if (c!='*' && c!='-' && c!='_')
1142 return 0; // not a hrule character
1149 n++; // count rule character
1151 else if (data[i]!=' ')
1153 return 0; // line contains non hruler characters
1157 return n>=3; // at least 3 characters needed for a hruler
1160 static QCString extractTitleId(QCString &title)
1162 //static QRegExp r1("^[a-z_A-Z][a-z_A-Z0-9\\-]*:");
1163 static QRegExp r2("\\{#[a-z_A-Z][a-z_A-Z0-9\\-]*\\}$");
1165 int i = r2.match(title,0,&l);
1166 if (i!=-1) // found {#id} style id
1168 QCString id = title.mid(i+2,l-3);
1169 title = title.left(i)+title.mid(i+l);
1170 //printf("found id='%s' title='%s'\n",id.data(),title.data());
1173 //printf("no id found in title '%s'\n",title.data());
1178 static int isAtxHeader(const char *data,int size,
1179 QCString &header,QCString &id)
1182 int level = 0, blanks=0;
1184 // find start of header text and determine heading level
1185 while (i<size && data[i]==' ') i++;
1186 if (i>=size || data[i]!='#')
1190 while (i<size && level<6 && data[i]=='#') i++,level++;
1191 while (i<size && data[i]==' ') i++,blanks++;
1192 if (level==1 && blanks==0)
1194 return 0; // special case to prevent #someid seen as a header (see bug 671395)
1197 // find end of header text
1199 while (end<size && data[end]!='\n') end++;
1200 while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
1203 convertStringFragment(header,data+i,end-i);
1204 id = extractTitleId(header);
1205 if (!id.isEmpty()) // strip #'s between title and id
1207 i=header.length()-1;
1208 while (i>=0 && (header.at(i)=='#' || header.at(i)==' ')) i--;
1209 header=header.left(i+1);
1215 static int isEmptyLine(const char *data,int size)
1220 if (data[i]=='\n') return TRUE;
1221 if (data[i]!=' ') return FALSE;
1227 #define isLiTag(i) \
1228 (data[(i)]=='<' && \
1229 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
1230 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
1233 // compute the indent from the start of the input, excluding list markers
1234 // such as -, -#, *, +, 1., and <li>
1235 static int computeIndentExcludingListMarkers(const char *data,int size)
1241 bool listMarkerSkipped=FALSE;
1243 (data[i]==' ' || // space
1244 (!listMarkerSkipped && // first list marker
1245 (data[i]=='+' || data[i]=='-' || data[i]=='*' || // unordered list char
1246 (data[i]=='#' && i>0 && data[i-1]=='-') || // -# item
1247 (isDigit=(data[i]>='1' && data[i]<='9')) || // ordered list marker?
1248 (isLi=(i<size-3 && isLiTag(i))) // <li> tag
1254 if (isDigit) // skip over ordered list marker '10. '
1257 while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
1259 if (data[j]=='.') // should be end of the list marker
1261 if (j<size-1 && data[j+1]==' ') // valid list marker
1263 listMarkerSkipped=TRUE;
1268 else // not a list marker
1278 i+=3; // skip over <li>
1280 listMarkerSkipped=TRUE;
1282 else if (data[i]=='-' && i<size-2 && data[i+1]=='#' && data[i+2]==' ')
1284 listMarkerSkipped=TRUE; // only a single list marker is accepted
1288 else if (data[i]!=' ' && i<size-1 && data[i+1]==' ')
1289 { // case "- " or "+ " or "* "
1290 listMarkerSkipped=TRUE; // only a single list marker is accepted
1292 if (data[i]!=' ' && !listMarkerSkipped)
1298 //printf("{%s}->%d\n",QCString(data).left(size).data(),indent);
1302 static bool isFencedCodeBlock(const char *data,int size,int refIndent,
1303 QCString &lang,int &start,int &end,int &offset)
1305 // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
1310 while (i<size && data[i]==' ') indent++,i++;
1311 if (indent>=refIndent+4) return FALSE; // part of code block
1312 while (i<size && data[i]=='~') startTildes++,i++;
1313 if (startTildes<3) return FALSE; // not enough tildes
1314 if (i<size && data[i]=='{') i++; // skip over optional {
1316 while (i<size && (data[i]!='\n' && data[i]!='}' && data[i]!=' ')) i++;
1317 convertStringFragment(lang,data+startLang,i-startLang);
1318 while (i<size && data[i]!='\n') i++; // proceed to the end of the line
1326 while (i<size && data[i]=='~') endTildes++,i++;
1327 while (i<size && data[i]==' ') i++;
1328 if (i==size || data[i]=='\n')
1331 return endTildes==startTildes;
1339 static bool isCodeBlock(const char *data,int offset,int size,int &indent)
1341 //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
1342 // determine the indent of this line
1345 while (i<size && data[i]==' ') indent0++,i++;
1347 if (indent0<codeBlockIndent)
1349 //printf(">isCodeBlock: line is not indented enough %d<4\n",indent0);
1352 if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
1354 //printf("only spaces at the end of a comment block\n");
1361 // search back 3 lines and remember the start of lines -1 and -2
1364 if (data[i-offset-1]=='\n') nl_pos[nl++]=i-offset;
1368 // if there are only 2 preceding lines, then line -2 starts at -offset
1369 if (i==0 && nl==2) nl_pos[nl++]=-offset;
1370 //printf(" nl=%d\n",nl);
1372 if (nl==3) // we have at least 2 preceding lines
1374 //printf(" positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
1375 // nl_pos[0],nl_pos[1],nl_pos[2],
1376 // QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1).data(),
1377 // QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1).data());
1379 // check that line -1 is empty
1380 if (!isEmptyLine(data+nl_pos[1],nl_pos[0]-nl_pos[1]-1))
1385 // determine the indent of line -2
1386 indent=computeIndentExcludingListMarkers(data+nl_pos[2],nl_pos[1]-nl_pos[2]);
1388 //printf(">isCodeBlock local_indent %d>=%d+4=%d\n",
1389 // indent0,indent2,indent0>=indent2+4);
1390 // if the difference is >4 spaces -> code block
1391 return indent0>=indent+codeBlockIndent;
1393 else // not enough lines to determine the relative indent, use global indent
1395 // check that line -1 is empty
1396 if (nl==1 && !isEmptyLine(data-offset,offset-1))
1400 //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
1401 // indent0,indent,indent0>=indent+4,nl);
1402 return indent0>=indent+codeBlockIndent;
1406 /** Finds the location of the table's contains in the string \a data.
1407 * Only one line will be inspected.
1408 * @param[in] data pointer to the string buffer.
1409 * @param[in] size the size of the buffer.
1410 * @param[out] start offset of the first character of the table content
1411 * @param[out] end offset of the last character of the table content
1412 * @param[out] columns number of table columns found
1413 * @returns The offset until the next line in the buffer.
1415 int findTableColumns(const char *data,int size,int &start,int &end,int &columns)
1419 // find start character of the table line
1420 while (i<size && data[i]==' ') i++;
1421 if (i<size && data[i]=='|') i++; // leading | does not count
1424 // find end character of the table line
1425 while (i<size && data[i]!='\n') i++;
1428 while (i>0 && data[i]==' ') i--;
1429 if (i>0 && data[i]=='|') i--; // trailing | does not count
1432 // count columns between start and end
1439 if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
1443 //printf("findTableColumns(start=%d,end=%d,columns=%d) eol=%d\n",
1444 // start,end,columns,eol);
1448 /** Returns TRUE iff data points to the start of a table block */
1449 static bool isTableBlock(const char *data,int size)
1453 // the first line should have at least two columns separated by '|'
1454 int i = findTableColumns(data,size,start,end,cc0);
1455 if (i>=size || cc0<2)
1457 //printf("isTableBlock: no |'s in the header\n");
1462 int ret = findTableColumns(data+i,size-i,start,end,cc1);
1464 // separator line should consist of |, - and : and spaces only
1467 if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
1469 //printf("isTableBlock: invalid character '%c'\n",data[j]);
1470 return FALSE; // invalid characters in table separator
1474 if (cc1!=cc0) // number of columns should be same as previous line
1479 i+=ret; // goto next line
1481 ret = findTableColumns(data+i,size-i,start,end,cc2);
1483 //printf("isTableBlock: %d\n",cc1==cc2);
1487 static int writeTableBlock(GrowBuf &out,const char *data,int size)
1490 int columns,start,end,cc;
1492 i = findTableColumns(data,size,start,end,columns);
1494 out.addStr("<table>");
1496 // write table header, in range [start..end]
1499 int headerStart = start;
1500 int headerEnd = end;
1502 // read cell alignments
1503 int ret = findTableColumns(data+i,size-i,start,end,cc);
1505 Alignment *columnAlignment = new Alignment[columns];
1507 bool leftMarker=FALSE,rightMarker=FALSE;
1508 bool startFound=FALSE;
1514 if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
1515 if (data[j]=='-') startFound=TRUE;
1516 //printf(" data[%d]=%c startFound=%d\n",j,data[j],startFound);
1518 if (data[j]=='-') rightMarker=FALSE;
1519 else if (data[j]==':') rightMarker=TRUE;
1520 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1524 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1525 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1536 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1537 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1539 // proceed to next line
1543 for (k=0;k<columns;k++)
1546 switch (columnAlignment[k])
1548 case AlignLeft: out.addStr(" align=left"); break;
1549 case AlignRight: out.addStr(" align=right"); break;
1550 case AlignCenter: out.addStr(" align=center"); break;
1551 case AlignNone: break;
1554 while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
1556 out.addChar(data[m++]);
1561 // write table cells
1564 int ret = findTableColumns(data+i,size-i,start,end,cc);
1565 //printf("findTableColumns cc=%d\n",cc);
1566 if (cc!=columns) break; // end of table
1577 switch (columnAlignment[k])
1579 case AlignLeft: out.addStr(" align=left"); break;
1580 case AlignRight: out.addStr(" align=right"); break;
1581 case AlignCenter: out.addStr(" align=center"); break;
1582 case AlignNone: break;
1586 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1593 out.addChar(data[j]);
1599 // proceed to next line
1603 out.addStr("</table>\n");
1605 delete[] columnAlignment;
1610 void writeOneLineHeaderOrRuler(GrowBuf &out,const char *data,int size)
1615 if (isHRuler(data,size))
1617 out.addStr("<hr>\n");
1619 else if ((level=isAtxHeader(data,size,header,id)))
1621 //if (level==1) g_correctSectionLevel=FALSE;
1622 //if (g_correctSectionLevel) level--;
1624 if (level<5 && !id.isEmpty())
1626 SectionInfo::SectionType type = SectionInfo::Anchor;
1629 case 1: out.addStr("@section ");
1630 type=SectionInfo::Section;
1632 case 2: out.addStr("@subsection ");
1633 type=SectionInfo::Subsection;
1635 case 3: out.addStr("@subsubsection ");
1636 type=SectionInfo::Subsubsection;
1638 default: out.addStr("@paragraph ");
1639 type=SectionInfo::Paragraph;
1646 SectionInfo *si = new SectionInfo(g_fileName,id,header,type,level);
1649 g_current->anchors->append(si);
1651 Doxygen::sectionDict.append(header,si);
1657 out.addStr("\\anchor "+id+"\n");
1659 hTag.sprintf("h%d",level);
1660 out.addStr("<"+hTag+">");
1662 out.addStr("</"+hTag+">\n");
1665 else // nothing interesting -> just output the line
1667 out.addStr(data,size);
1671 static int writeBlockQuote(GrowBuf &out,const char *data,int size)
1679 // find end of this line
1681 while (end<size && data[end-1]!='\n') end++;
1685 // compute the quoting level
1686 while (j<end && (data[j]==' ' || data[j]=='>'))
1688 if (data[j]=='>') { level++; indent=j+1; }
1689 else if (j>0 && data[j-1]=='>') indent=j+1;
1692 if (j>0 && data[j-1]=='>') // disqualify last > if not followed by space
1697 if (level>curLevel) // quote level increased => add start markers
1699 for (l=curLevel;l<level;l++)
1701 out.addStr("<blockquote>\n");
1704 else if (level<curLevel) // quote level descreased => add end markers
1706 for (l=level;l<curLevel;l++)
1708 out.addStr("\n</blockquote>\n");
1712 if (level==0) break; // end of quote block
1713 // copy line without quotation marks
1714 out.addStr(data+indent,end-indent);
1715 // proceed with next line
1718 // end of comment within blockquote => add end markers
1719 for (l=0;l<curLevel;l++)
1721 out.addStr("\n</blockquote>\n");
1726 static int writeCodeBlock(GrowBuf &out,const char *data,int size,int refIndent)
1729 //printf("writeCodeBlock: data={%s}\n",QCString(data).left(size).data());
1730 out.addStr("@verbatim\n");
1734 // find end of this line
1736 while (end<size && data[end-1]!='\n') end++;
1739 while (j<end && data[j]==' ') j++,indent++;
1740 //printf("j=%d end=%d indent=%d refIndent=%d tabSize=%d data={%s}\n",
1741 // j,end,indent,refIndent,Config_getInt("TAB_SIZE"),QCString(data+i).left(end-i-1).data());
1742 if (j==end-1) // empty line
1747 else if (indent>=refIndent+codeBlockIndent) // enough indent to contine the code block
1749 while (emptyLines>0) // write skipped empty lines
1755 // add code line minus the indent
1756 out.addStr(data+i+refIndent+codeBlockIndent,end-i-refIndent-codeBlockIndent);
1759 else // end of code block
1764 out.addStr("@endverbatim\n");
1765 while (emptyLines>0) // write skipped empty lines
1771 //printf("i=%d\n",i);
1775 // start searching for the end of the line start at offset \a i
1776 // keeping track of possible blocks that need to to skipped.
1777 static void findEndOfLine(GrowBuf &out,const char *data,int size,
1778 int &pi,int&i,int &end)
1780 // find end of the line
1783 while (end<size && data[end-1]!='\n')
1785 // while looking for the end of the line we might encounter a block
1786 // that needs to be passed unprocessed.
1787 if ((data[end-1]=='\\' || data[end-1]=='@') && // command
1788 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
1791 QCString endBlockName = isBlockCommand(data+end-1,end-1,size-(end-1));
1792 if (!endBlockName.isEmpty())
1794 int l = endBlockName.length();
1795 for (;end<size-l;end++) // search for end of block marker
1797 if ((data[end]=='\\' || data[end]=='@') &&
1798 data[end-1]!='\\' && data[end-1]!='@'
1801 if (strncmp(&data[end+1],endBlockName,l)==0)
1803 if (pi!=-1) // output previous line if available
1805 //printf("feol out={%s}\n",QCString(data+pi).left(i-pi).data());
1806 out.addStr(data+pi,i-pi);
1808 // found end marker, skip over this block
1809 //printf("feol.block out={%s}\n",QCString(data+i).left(end+l+1-i).data());
1810 out.addStr(data+i,end+l+1-i);
1812 i=end+l+1; // continue after block
1824 else if (nb==0 && data[end-1]=='<' && end<size-6 &&
1825 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
1828 if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
1829 tolower(data[end+2])=='e' && data[end+3]=='>') // <pre> tag
1831 if (pi!=-1) // output previous line if available
1833 out.addStr(data+pi,i-pi);
1835 // output part until <pre>
1836 out.addStr(data+i,end-1-i);
1837 // output part until </pre>
1838 i = end-1 + processHtmlTag(out,data+end-1,end-1,size-end+1);
1848 else if (nb==0 && data[end-1]=='`')
1850 while (end<size && data[end-1]=='`') end++,nb++;
1852 else if (nb>0 && data[end-1]=='`')
1855 while (end<size && data[end-1]=='`') end++,enb++;
1863 //printf("findEndOfLine pi=%d i=%d end=%d {%s}\n",pi,i,end,QCString(data+i).left(end-i).data());
1866 static QCString processQuotations(const QCString &s,int refIndent)
1869 const char *data = s.data();
1870 int size = s.length();
1871 int i=0,end=0,pi=-1;
1874 findEndOfLine(out,data,size,pi,i,end);
1875 // line is now found at [i..end)
1879 if (isBlockQuote(data+pi,i-pi,refIndent))
1881 i = pi+writeBlockQuote(out,data+pi,size-pi);
1888 //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
1889 out.addStr(data+pi,i-pi);
1895 if (pi!=-1 && pi<size) // deal with the last line
1897 if (isBlockQuote(data+pi,size-pi,refIndent))
1899 writeBlockQuote(out,data+pi,size-pi);
1903 out.addStr(data+pi,size-pi);
1908 //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
1909 // s.data(),out.get());
1914 static QCString processBlocks(const QCString &s,int indent)
1917 const char *data = s.data();
1918 int size = s.length();
1919 int i=0,end=0,pi=-1,ref,level;
1920 QCString id,link,title;
1921 int blockIndent = indent;
1923 // get indent for the first line
1926 while (end<size && data[end-1]!='\n')
1928 if (data[end-1]==' ') sp++;
1932 // special case when the documentation starts with a code block
1933 // since the first line is skipped when looking for a code block later on.
1934 if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
1936 i=writeCodeBlock(out,data,size,blockIndent);
1941 // process each line
1944 findEndOfLine(out,data,size,pi,i,end);
1945 // line is now found at [i..end)
1947 //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
1951 int blockStart,blockEnd,blockOffset;
1953 blockIndent = indent;
1954 //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
1955 if ((level=isHeaderline(data+i,size-i))>0)
1957 //if (level==1) g_correctSectionLevel=FALSE;
1958 //if (g_correctSectionLevel) level--;
1959 //printf("Found header at %d-%d\n",i,end);
1960 while (pi<size && data[pi]==' ') pi++;
1962 convertStringFragment(header,data+pi,i-pi-1);
1963 id = extractTitleId(header);
1964 if (!header.isEmpty())
1968 out.addStr(level==1?"@section ":"@subsection ");
1973 SectionInfo *si = new SectionInfo(g_fileName,id,header,
1974 level==1 ? SectionInfo::Section : SectionInfo::Subsection,level);
1977 g_current->anchors->append(si);
1979 Doxygen::sectionDict.append(header,si);
1983 out.addStr(level==1?"<h1>":"<h2>");
1985 out.addStr(level==1?"</h1>\n":"</h2>\n");
1990 out.addStr("<hr>\n");
1997 else if ((ref=isLinkRef(data+pi,size-pi,id,link,title)))
1999 //printf("found link ref: id='%s' link='%s' title='%s'\n",
2000 // id.data(),link.data(),title.data());
2001 g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2006 else if (isFencedCodeBlock(data+pi,size-pi,indent,lang,blockStart,blockEnd,blockOffset))
2008 //printf("Found FencedCodeBlock lang='%s' start=%d end=%d code={%s}\n",
2009 // lang.data(),blockStart,blockEnd,QCString(data+pi+blockStart).left(blockEnd-blockStart).data());
2010 if (!lang.isEmpty() && lang.at(0)=='.') lang=lang.mid(1);
2011 out.addStr("@code");
2012 if (!lang.isEmpty())
2014 out.addStr("{"+lang+"}");
2016 out.addStr(data+pi+blockStart,blockEnd-blockStart);
2018 out.addStr("@endcode");
2024 else if (isCodeBlock(data+i,i,end-i,blockIndent))
2026 // skip previous line (it is empty anyway)
2027 i+=writeCodeBlock(out,data+i,size-i,blockIndent);
2032 else if (isTableBlock(data+pi,size-pi))
2034 i=pi+writeTableBlock(out,data+pi,size-pi);
2041 writeOneLineHeaderOrRuler(out,data+pi,i-pi);
2047 //printf("last line %d size=%d\n",i,size);
2048 if (pi!=-1 && pi<size) // deal with the last line
2050 if (isLinkRef(data+pi,size-pi,id,link,title))
2052 //printf("found link ref: id='%s' link='%s' title='%s'\n",
2053 // id.data(),link.data(),title.data());
2054 g_linkRefs.insert(id,new LinkRef(link,title));
2058 writeOneLineHeaderOrRuler(out,data+pi,size-pi);
2066 static QCString extractPageTitle(QCString &docs,QCString &id)
2068 // first first non-empty line
2070 const char *data = docs.data();
2072 int size=docs.size();
2073 while (i<size && (data[i]==' ' || data[i]=='\n')) i++;
2074 if (i>=size) return "";
2076 while (end1<size && data[end1-1]!='\n') end1++;
2077 //printf("i=%d end1=%d size=%d line='%s'\n",i,end1,size,docs.mid(i,end1-i).data());
2078 // first line from i..end1
2081 // second line form end1..end2
2083 while (end2<size && data[end2-1]!='\n') end2++;
2084 if (isHeaderline(data+end1,size-end1))
2086 convertStringFragment(title,data+i,end1-i-1);
2087 docs=docs.mid(end2);
2088 id = extractTitleId(title);
2089 //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2093 if (i<end1 && isAtxHeader(data+i,end1-i,title,id)>0)
2095 docs=docs.mid(end1);
2097 id = extractTitleId(title);
2098 //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2102 static QCString detab(const QCString &s,int &refIndent)
2104 static int tabSize = Config_getInt("TAB_SIZE");
2106 int size = s.length();
2107 const char *data = s.data();
2110 const int maxIndent=1000000; // value representing infinity
2111 int minIndent=maxIndent;
2117 case '\t': // expand tab
2119 int stop = tabSize - (col%tabSize);
2120 //printf("expand at %d stop=%d\n",col,stop);
2122 while (stop--) out.addChar(' ');
2125 case '\n': // reset colomn counter
2129 case ' ': // increment column counter
2133 default: // non-whitespace => update minIndent
2135 if (col<minIndent) minIndent=col;
2139 if (minIndent!=maxIndent) refIndent=minIndent; else refIndent=0;
2141 //printf("detab refIndent=%d\n",refIndent);
2145 //---------------------------------------------------------------------------
2147 QCString processMarkdown(const QCString &fileName,Entry *e,const QCString &input)
2149 static bool init=FALSE;
2152 // setup callback table for special characters
2153 g_actions[(unsigned int)'_']=processEmphasis;
2154 g_actions[(unsigned int)'*']=processEmphasis;
2155 g_actions[(unsigned int)'`']=processCodeSpan;
2156 g_actions[(unsigned int)'\\']=processSpecialCommand;
2157 g_actions[(unsigned int)'@']=processSpecialCommand;
2158 g_actions[(unsigned int)'[']=processLink;
2159 g_actions[(unsigned int)'!']=processLink;
2160 g_actions[(unsigned int)'<']=processHtmlTag;
2161 g_actions[(unsigned int)'-']=processNmdash;
2165 g_linkRefs.setAutoDelete(TRUE);
2168 g_fileName = fileName;
2170 if (input.isEmpty()) return input;
2173 // for replace tabs by spaces
2174 QCString s = detab(input,refIndent);
2175 //printf("======== DeTab =========\n---- output -----\n%s\n---------\n",s.data());
2176 // then process quotation blocks (as these may contain other blocks)
2177 s = processQuotations(s,refIndent);
2178 //printf("======== Quotations =========\n---- output -----\n%s\n---------\n",s.data());
2179 // then process block items (headers, rules, and code blocks, references)
2180 s = processBlocks(s,refIndent);
2181 //printf("======== Blocks =========\n---- output -----\n%s\n---------\n",s.data());
2182 // finally process the inline markup (links, emphasis and code spans)
2183 processInline(out,s,s.length());
2185 Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n%s\n---- output -----\n%s\n---------\n",input.data(),out.get());
2189 //---------------------------------------------------------------------------
2191 void MarkdownFileParser::parseInput(const char *fileName,
2192 const char *fileBuf,
2195 Entry *current = new Entry;
2196 current->lang = SrcLangExt_Markdown;
2197 current->fileName = fileName;
2198 current->docFile = fileName;
2199 int len = strlen(fileBuf);
2202 input.addArray(fileBuf,strlen(fileBuf));
2203 input.addChar('\0');
2204 convertCppComments(&input,&output,fileName);
2205 output.addChar('\0');
2206 QCString docs = output.data();
2208 QCString title=extractPageTitle(docs,id).stripWhiteSpace();
2209 //g_correctSectionLevel = !title.isEmpty();
2210 QCString baseName = substitute(QFileInfo(fileName).baseName().utf8()," ","_");
2211 if (id.isEmpty()) id = "md_"+baseName;
2212 if (title.isEmpty()) title = baseName;
2213 if (id=="mainpage" || id=="index")
2215 docs.prepend("@mainpage "+title+"\n");
2219 docs.prepend("@page "+id+" "+title+"\n");
2224 // even without markdown support enabled, we still
2225 // parse markdown files as such
2226 bool markdownEnabled = Doxygen::markdownSupport;
2227 Doxygen::markdownSupport = TRUE;
2231 while (parseCommentBlock(
2238 FALSE, // javadoc autobrief
2239 FALSE, // inBodyDocs
2246 QCString docFile = current->docFile;
2247 root->addSubEntry(current);
2248 current = new Entry;
2249 current->lang = SrcLangExt_Markdown;
2250 current->docFile = docFile;
2251 current->docLine = lineNr;
2256 root->addSubEntry(current);
2260 Doxygen::markdownSupport = markdownEnabled;
2261 //g_correctSectionLevel = FALSE;
2264 void MarkdownFileParser::parseCode(CodeOutputInterface &codeOutIntf,
2265 const char *scopeName,
2266 const QCString &input,
2267 bool isExampleBlock,
2268 const char *exampleName,
2272 bool inlineFragment,
2273 MemberDef *memberDef,
2274 bool showLineNumbers,
2275 Definition *searchCtx
2278 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2282 codeOutIntf,scopeName,input,isExampleBlock,exampleName,
2283 fileDef,startLine,endLine,inlineFragment,memberDef,showLineNumbers,searchCtx);
2287 void MarkdownFileParser::resetCodeParserState()
2289 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2292 pIntf->resetCodeParserState();
2296 void MarkdownFileParser::parsePrototype(const char *text)
2298 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2301 pIntf->parsePrototype(text);