1 /******************************************************************************
3 * Copyright (C) 1997-2015 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>
40 //#define USE_ORIGINAL_TABLES
47 #include "commentscan.h"
50 #include "commentcnv.h"
57 // is character at position i in data part of an identifier?
59 ((data[i]>='a' && data[i]<='z') || \
60 (data[i]>='A' && data[i]<='Z') || \
61 (data[i]>='0' && data[i]<='9') || \
62 (((unsigned char)data[i])>=0x80)) // unicode characters
64 #define extraChar(i) \
65 (data[i]=='-' || data[i]=='+' || data[i]=='!' || \
66 data[i]=='?' || data[i]=='$' || data[i]=='@' || \
67 data[i]=='&' || data[i]=='*' || data[i]=='%')
69 // is character at position i in data allowed before an emphasis section
70 #define isOpenEmphChar(i) \
71 (data[i]=='\n' || data[i]==' ' || data[i]=='\'' || data[i]=='<' || \
72 data[i]=='{' || data[i]=='(' || data[i]=='[' || data[i]==',' || \
73 data[i]==':' || data[i]==';')
75 // is character at position i in data an escape that prevents ending an emphasis section
76 // so for example *bla (*.txt) is cool*
77 #define ignoreCloseEmphChar(i) \
78 (data[i]=='(' || data[i]=='{' || data[i]=='[' || data[i]=='<' || \
86 LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
93 TableCell() : colSpan(false) {}
98 typedef int (*action_t)(GrowBuf &out,const char *data,int offset,int size);
100 enum Alignment { AlignNone, AlignLeft, AlignCenter, AlignRight };
105 static QDict<LinkRef> g_linkRefs(257);
106 static action_t g_actions[256];
107 static Entry *g_current;
108 static QCString g_fileName;
111 // In case a markdown page starts with a level1 header, that header is used
112 // as a title of the page, in effect making it a level0 header, so the
113 // level of all other sections needs to be corrected as well.
114 // This flag is TRUE if corrections are needed.
115 //static bool g_correctSectionLevel;
120 const int codeBlockIndent = 4;
122 static void processInline(GrowBuf &out,const char *data,int size);
124 // escape characters that have a special meaning later on.
125 static QCString escapeSpecialChars(const QCString &s)
127 if (s.isEmpty()) return "";
128 bool insideQuote=FALSE;
136 case '"': if (pc!='\\') { insideQuote=!insideQuote; } growBuf.addChar(c); break;
137 case '<': if (!insideQuote) { growBuf.addChar('\\'); } growBuf.addChar('<'); break;
138 case '>': if (!insideQuote) { growBuf.addChar('\\'); } growBuf.addChar('>'); break;
139 case '\\': if (!insideQuote) { growBuf.addChar('\\'); } growBuf.addChar('\\'); break;
140 case '@': if (!insideQuote) { growBuf.addChar('\\'); } growBuf.addChar('@'); break;
141 default: growBuf.addChar(c); break;
146 return growBuf.get();
149 static void convertStringFragment(QCString &result,const char *data,int size)
152 result.resize(size+1);
153 memcpy(result.rawData(),data,size);
154 result.at(size)='\0';
157 /** helper function to convert presence of left and/or right alignment markers
158 * to a alignment value
160 static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
162 //printf("markerToAlignment(%d,%d)\n",leftMarker,rightMarker);
163 if (leftMarker && rightMarker)
171 else if (rightMarker)
182 // Check if data contains a block command. If so returned the command
183 // that ends the block. If not an empty string is returned.
184 // Note When offset>0 character position -1 will be inspected.
186 // Checks for and skip the following block commands:
187 // {@code .. { .. } .. }
194 // \verbatim..\endverbatim
195 // \latexonly..\endlatexonly
196 // \htmlonly..\endhtmlonly
197 // \xmlonly..\endxmlonly
198 // \rtfonly..\endrtfonly
199 // \manonly..\endmanonly
200 static QCString isBlockCommand(const char *data,int offset,int size)
202 bool openBracket = offset>0 && data[-1]=='{';
203 bool isEscaped = offset>0 && (data[-1]=='\\' || data[-1]=='@');
204 if (isEscaped) return QCString();
207 while (end<size && (data[end]>='a' && data[end]<='z')) end++;
208 if (end==1) return QCString();
210 convertStringFragment(blockName,data+1,end-1);
211 if (blockName=="code" && openBracket)
215 else if (blockName=="dot" ||
218 blockName=="verbatim" ||
219 blockName=="latexonly" ||
220 blockName=="htmlonly" ||
221 blockName=="xmlonly" ||
222 blockName=="rtfonly" ||
223 blockName=="manonly" ||
224 blockName=="docbookonly"
227 return "end"+blockName;
229 else if (blockName=="startuml")
233 else if (blockName=="f" && end<size)
239 else if (data[end]=='[')
243 else if (data[end]=='{')
251 /** looks for the next emph char, skipping other constructs, and
252 * stopping when either it is found, or we are at the end of a paragraph.
254 static int findEmphasisChar(const char *data, int size, char c, int c_size)
260 while (i<size && data[i]!=c && data[i]!='`' &&
261 data[i]!='\\' && data[i]!='@' &&
263 //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
265 // not counting escaped chars or characters that are unlikely
266 // to appear as the end of the emphasis char
267 if (i>0 && ignoreCloseEmphChar(i-1))
274 // get length of emphasis token
276 while (i+len<size && data[i+len]==c)
283 if (len!=c_size || (i<size-len && isIdChar(i+len))) // to prevent touching some_underscore_identifier
288 return i; // found it
292 // skipping a code span
296 while (i<size && data[i]=='`') snb++,i++;
298 // find same pattern to end the span
300 while (i<size && enb<snb)
302 if (data[i]=='`') enb++;
303 if (snb==1 && data[i]=='\'') break; // ` ended by '
307 else if (data[i]=='@' || data[i]=='\\')
308 { // skip over blocks that should not be processed
309 QCString endBlockName = isBlockCommand(data+i,i,size-i);
310 if (!endBlockName.isEmpty())
313 int l = endBlockName.length();
316 if ((data[i]=='\\' || data[i]=='@') && // command
317 data[i-1]!='\\' && data[i-1]!='@') // not escaped
319 if (qstrncmp(&data[i+1],endBlockName,l)==0)
327 else if (i<size-1 && isIdChar(i+1)) // @cmd, stop processing, see bug 690385
336 else if (data[i]=='\n') // end * or _ at paragraph boundary
339 while (i<size && data[i]==' ') i++;
340 if (i>=size || data[i]=='\n') return 0; // empty line -> paragraph
342 else // should not get here!
351 /** process single emphasis */
352 static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
356 /* skipping one symbol if coming from emph3 */
357 if (size>1 && data[0]==c && data[1]==c) { i=1; }
361 len = findEmphasisChar(data+i, size-i, c, 1);
362 if (len==0) return 0;
364 if (i>=size) return 0;
366 if (i+1<size && data[i+1]==c)
371 if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
374 processInline(out,data,i);
382 /** process double emphasis */
383 static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
389 len = findEmphasisChar(data+i, size-i, c, 2);
395 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' &&
399 if (c == '~') out.addStr("<strike>");
400 else out.addStr("<strong>");
401 processInline(out,data,i);
402 if (c == '~') out.addStr("</strike>");
403 else out.addStr("</strong>");
411 /** Parsing triple emphasis.
412 * Finds the first closing tag, and delegates to the other emph
414 static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
420 len = findEmphasisChar(data+i, size-i, c, 3);
427 /* skip whitespace preceded symbols */
428 if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
433 if (i+2<size && data[i+1]==c && data[i+2]==c)
435 out.addStr("<em><strong>");
436 processInline(out,data,i);
437 out.addStr("</strong></em>");
440 else if (i+1<size && data[i+1]==c)
442 // double symbol found, handing over to emph1
443 len = processEmphasis1(out, data-2, size+2, c);
455 // single symbol found, handing over to emph2
456 len = processEmphasis2(out, data-1, size+1, c);
470 /** Process ndash and mdashes */
471 static int processNmdash(GrowBuf &out,const char *data,int off,int size)
473 // precondition: data[0]=='-'
476 if (i<size && data[i]=='-') // found --
480 if (i<size && data[i]=='-') // found ---
484 if (i<size && data[i]=='-') // found ----
488 if (count==2 && off>=2 && qstrncmp(data-2,"<!",2)==0) return 0; // start HTML comment
489 if (count==2 && (data[2]=='>')) return 0; // end HTML comment
490 if (count==2 && (off<8 || qstrncmp(data-8,"operator",8)!=0)) // -- => ndash
492 out.addStr("–");
495 else if (count==3) // --- => ndash
497 out.addStr("—");
500 // not an ndash or mdash
504 /** Process quoted section "...", can contain one embedded newline */
505 static int processQuoted(GrowBuf &out,const char *data,int,int size)
509 while (i<size && data[i]!='"' && nl<2)
511 if (data[i]=='\n') nl++;
514 if (i<size && data[i]=='"' && nl<2)
516 out.addStr(data,i+1);
519 // not a quoted section
523 /** Process a HTML tag. Note that <pre>..</pre> are treated specially, in
524 * the sense that all code inside is written unprocessed
526 static int processHtmlTag(GrowBuf &out,const char *data,int offset,int size)
528 if (offset>0 && data[-1]=='\\') return 0; // escaped <
530 // find the end of the html tag
533 // compute length of the tag name
534 while (i<size && isIdChar(i)) i++,l++;
536 convertStringFragment(tagName,data+1,i-1);
537 if (tagName.lower()=="pre") // found <pre> tag
539 bool insideStr=FALSE;
543 if (!insideStr && c=='<') // potential start of html tag
545 if (data[i+1]=='/' &&
546 tolower(data[i+2])=='p' && tolower(data[i+3])=='r' &&
547 tolower(data[i+4])=='e' && tolower(data[i+5])=='>')
548 { // found </pre> tag, copy from start to end of tag
549 out.addStr(data,i+6);
550 //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
554 else if (insideStr && c=='"')
556 if (data[i-1]!='\\') insideStr=FALSE;
565 else // some other html tag
569 if (data[i]=='/' && i<size-1 && data[i+1]=='>') // <bla/>
571 //printf("Found htmlTag={%s}\n",QCString(data).left(i+2).data());
572 out.addStr(data,i+2);
575 else if (data[i]=='>') // <bla>
577 //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
578 out.addStr(data,i+1);
581 else if (data[i]==' ') // <bla attr=...
584 bool insideAttr=FALSE;
587 if (!insideAttr && data[i]=='"')
591 else if (data[i]=='"' && data[i-1]!='\\')
595 else if (!insideAttr && data[i]=='>') // found end of tag
597 //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
598 out.addStr(data,i+1);
606 //printf("Not a valid html tag\n");
610 static int processEmphasis(GrowBuf &out,const char *data,int offset,int size)
612 if ((offset>0 && !isOpenEmphChar(-1)) || // invalid char before * or _
613 (size>1 && data[0]!=data[1] && !(isIdChar(1) || extraChar(1) || data[1]=='[')) || // invalid char after * or _
614 (size>2 && data[0]==data[1] && !(isIdChar(2) || extraChar(2) || data[2]=='['))) // invalid char after ** or __
621 if (size>2 && c!='~' && data[1]!=c) // _bla or *bla
623 // whitespace cannot follow an opening emphasis
624 if (data[1]==' ' || data[1]=='\n' ||
625 (ret = processEmphasis1(out, data+1, size-1, c)) == 0)
631 if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
633 if (data[2]==' ' || data[2]=='\n' ||
634 (ret = processEmphasis2(out, data+2, size-2, c)) == 0)
640 if (size>4 && c!='~' && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
642 if (data[3]==' ' || data[3]=='\n' ||
643 (ret = processEmphasis3(out, data+3, size-3, c)) == 0)
652 static void writeMarkdownImage(GrowBuf &out, const char *fmt, bool explicitTitle, QCString title, QCString content, QCString link, FileDef *fd)
654 out.addStr("@image ");
657 out.addStr(link.mid(fd ? 0 : 5));
658 if (!explicitTitle && !content.isEmpty())
664 else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
673 static int processLink(GrowBuf &out,const char *data,int,int size)
678 int contentStart,contentEnd,linkStart,titleStart,titleEnd;
679 bool isImageLink = FALSE;
685 if (size<2 || data[1]!='[')
694 // find the matching ]
697 if (data[i-1]=='\\') // skip escaped characters
700 else if (data[i]=='[')
704 else if (data[i]==']')
709 else if (data[i]=='\n')
712 if (nl>1) return 0; // only allow one newline in the content
716 if (i>=size) return 0; // premature end of comment -> no link
718 convertStringFragment(content,data+contentStart,contentEnd-contentStart);
719 //printf("processLink: content={%s}\n",content.data());
720 if (!isImageLink && content.isEmpty()) return 0; // no link text
724 while (i<size && data[i]==' ') i++;
725 if (i<size && data[i]=='\n') // one newline allowed here
728 // skip more whitespace
729 while (i<size && data[i]==' ') i++;
732 bool explicitTitle=FALSE;
733 if (i<size && data[i]=='(') // inline link
736 while (i<size && data[i]==' ') i++;
737 if (i<size && data[i]=='<') i++;
741 while (i<size && data[i]!='\'' && data[i]!='"' && braceCount>0)
743 if (data[i]=='\n') // unexpected EOL
748 else if (data[i]=='(')
752 else if (data[i]==')')
761 if (i>=size || data[i]=='\n') return 0;
762 convertStringFragment(link,data+linkStart,i-linkStart);
763 link = link.stripWhiteSpace();
764 //printf("processLink: link={%s}\n",link.data());
765 if (link.isEmpty()) return 0;
766 if (link.at(link.length()-1)=='>') link=link.left(link.length()-1);
769 if (data[i]=='\'' || data[i]=='"')
775 while (i<size && data[i]!=')')
789 // search back for closing marker
790 while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
791 if (data[titleEnd]==c) // found it
793 convertStringFragment(title,data+titleStart,titleEnd-titleStart);
794 //printf("processLink: title={%s}\n",title.data());
803 else if (i<size && data[i]=='[') // reference link
809 while (i<size && data[i]!=']')
818 if (i>=size) return 0;
820 convertStringFragment(link,data+linkStart,i-linkStart);
821 //printf("processLink: link={%s}\n",link.data());
822 link = link.stripWhiteSpace();
823 if (link.isEmpty()) // shortcut link
828 LinkRef *lr = g_linkRefs.find(link.lower());
833 //printf("processLink: ref: link={%s} title={%s}\n",link.data(),title.data());
835 else // reference not found!
837 //printf("processLink: ref {%s} do not exist\n",link.lower().data());
842 else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
844 LinkRef *lr = g_linkRefs.find(content.lower());
845 //printf("processLink: minimal link {%s} lr=%p",content.data(),lr);
853 else if (content=="TOC")
868 if (isToc) // special case for [TOC]
870 int level = Config_getInt(TOC_INCLUDE_HEADINGS);
871 if (level > 0 && level <=5)
874 sprintf(levStr,"%d",level);
875 out.addStr("@tableofcontents{html:");
880 else if (isImageLink)
884 if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
885 (fd=findFileDef(Doxygen::imageNameDict,link,ambig)))
886 // assume doxygen symbol link or local image link
888 writeMarkdownImage(out, "html", explicitTitle, title, content, link, fd);
889 writeMarkdownImage(out, "latex", explicitTitle, title, content, link, fd);
890 writeMarkdownImage(out, "rtf", explicitTitle, title, content, link, fd);
891 writeMarkdownImage(out, "docbook", explicitTitle, title, content, link, fd);
895 out.addStr("<img src=\"");
897 out.addStr("\" alt=\"");
900 if (!title.isEmpty())
902 out.addStr(" title=\"");
903 out.addStr(substitute(title.simplifyWhiteSpace(),"\"","""));
911 SrcLangExt lang = getLanguageFromFileName(link);
913 if ((lp=link.find("@ref "))!=-1 || (lp=link.find("\\ref "))!=-1 || lang==SrcLangExt_Markdown)
914 // assume doxygen symbol link
916 if (lp==-1) // link to markdown page
922 if (explicitTitle && !title.isEmpty())
932 else if (link.find('/')!=-1 || link.find('.')!=-1 || link.find('#')!=-1)
934 out.addStr("<a href=\"");
937 if (!title.isEmpty())
939 out.addStr(" title=\"");
940 out.addStr(substitute(title.simplifyWhiteSpace(),"\"","""));
944 content = content.simplifyWhiteSpace();
945 processInline(out,content,content.length());
948 else // avoid link to e.g. F[x](y)
950 //printf("no link for '%s'\n",link.data());
957 /** '`' parsing a code span (assuming codespan != 0) */
958 static int processCodeSpan(GrowBuf &out, const char *data, int /*offset*/, int size)
960 int end, nb = 0, i, f_begin, f_end;
962 /* counting the number of backticks in the delimiter */
963 while (nb<size && data[nb]=='`')
968 /* finding the next delimiter */
971 for (end=nb; end<size && i<nb && nl<2; end++)
977 else if (data[end]=='\n')
982 else if (data[end]=='\'' && nb==1 && (end==size-1 || (end<size-1 && !isIdChar(end+1))))
983 { // look for quoted strings like `some word', but skip strings like `it's cool`
984 QCString textFragment;
985 convertStringFragment(textFragment,data+nb,end-nb);
986 out.addStr("‘");
987 out.addStr(textFragment);
988 out.addStr("’");
996 if (i < nb && end >= size)
998 return 0; // no matching delimiter
1000 if (nl==2) // too many newlines inside the span
1005 // trimming outside whitespaces
1007 while (f_begin < end && data[f_begin]==' ')
1012 while (f_end > nb && data[f_end-1]==' ')
1017 //printf("found code span '%s'\n",QCString(data+f_begin).left(f_end-f_begin).data());
1019 /* real code span */
1020 if (f_begin < f_end)
1022 QCString codeFragment;
1023 convertStringFragment(codeFragment,data+f_begin,f_end-f_begin);
1025 //out.addStr(convertToHtml(codeFragment,TRUE));
1026 out.addStr(escapeSpecialChars(codeFragment));
1027 out.addStr("</tt>");
1033 static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
1036 QCString endBlockName = isBlockCommand(data,offset,size);
1037 if (!endBlockName.isEmpty())
1039 int l = endBlockName.length();
1042 if ((data[i]=='\\' || data[i]=='@') && // command
1043 data[i-1]!='\\' && data[i-1]!='@') // not escaped
1045 if (qstrncmp(&data[i+1],endBlockName,l)==0)
1047 //printf("found end at %d\n",i);
1048 out.addStr(data,i+1+l);
1055 if (size>1 && data[0]=='\\')
1058 if (c=='[' || c==']' || c=='*' || c=='!' || c=='(' || c==')' || c=='`' || c=='_')
1060 out.addChar(data[1]);
1063 else if (c=='-' && size>3 && data[2]=='-' && data[3]=='-') // \---
1065 out.addStr(&data[1],3);
1068 else if (c=='-' && size>2 && data[2]=='-') // \--
1070 out.addStr(&data[1],2);
1077 static void processInline(GrowBuf &out,const char *data,int size)
1080 action_t action = 0;
1083 while (end<size && ((action=g_actions[(uchar)data[end]])==0)) end++;
1084 out.addStr(data+i,end-i);
1085 if (end>=size) break;
1087 end = action(out,data+i,i,size-i);
1100 /** returns whether the line is a setext-style hdr underline */
1101 static int isHeaderline(const char *data, int size)
1104 while (i<size && data[i]==' ') i++;
1106 // test of level 1 header
1109 while (i<size && data[i]=='=') i++,c++;
1110 while (i<size && data[i]==' ') i++;
1111 return (c>1 && (i>=size || data[i]=='\n')) ? 1 : 0;
1113 // test of level 2 header
1116 while (i<size && data[i]=='-') i++,c++;
1117 while (i<size && data[i]==' ') i++;
1118 return (c>1 && (i>=size || data[i]=='\n')) ? 2 : 0;
1123 /** returns TRUE if this line starts a block quote */
1124 static bool isBlockQuote(const char *data,int size,int indent)
1127 while (i<size && data[i]==' ') i++;
1128 if (i<indent+codeBlockIndent) // could be a quotation
1130 // count >'s and skip spaces
1132 while (i<size && (data[i]=='>' || data[i]==' '))
1134 if (data[i]=='>') level++;
1137 // last characters should be a space or newline,
1138 // so a line starting with >= does not match
1139 return level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n');
1141 else // too much indentation -> code block
1145 //return i<size && data[i]=='>' && i<indent+codeBlockIndent;
1148 /** returns end of the link ref if this is indeed a link reference. */
1149 static int isLinkRef(const char *data,int size,
1150 QCString &refid,QCString &link,QCString &title)
1152 //printf("isLinkRef data={%s}\n",data);
1153 // format: start with [some text]:
1155 while (i<size && data[i]==' ') i++;
1156 if (i>=size || data[i]!='[') return 0;
1159 while (i<size && data[i]!='\n' && data[i]!=']') i++;
1160 if (i>=size || data[i]!=']') return 0;
1161 convertStringFragment(refid,data+refIdStart,i-refIdStart);
1162 if (refid.isEmpty()) return 0;
1163 //printf(" isLinkRef: found refid='%s'\n",refid.data());
1165 if (i>=size || data[i]!=':') return 0;
1168 // format: whitespace* \n? whitespace* (<url> | url)
1169 while (i<size && data[i]==' ') i++;
1170 if (i<size && data[i]=='\n')
1173 while (i<size && data[i]==' ') i++;
1175 if (i>=size) return 0;
1177 if (i<size && data[i]=='<') i++;
1179 while (i<size && data[i]!=' ' && data[i]!='\n') i++;
1181 if (i<size && data[i]=='>') i++;
1182 if (linkStart==linkEnd) return 0; // empty link
1183 convertStringFragment(link,data+linkStart,linkEnd-linkStart);
1184 //printf(" isLinkRef: found link='%s'\n",link.data());
1185 if (link=="@ref" || link=="\\ref")
1188 while (i<size && data[i]!='\n' && data[i]!='"') i++;
1190 convertStringFragment(refArg,data+argStart,i-argStart);
1196 // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
1198 while (i<size && data[i]==' ') i++;
1199 if (i<size && data[i]=='\n')
1203 while (i<size && data[i]==' ') i++;
1207 //printf("end of isLinkRef while looking for title! i=%d\n",i);
1208 return i; // end of buffer while looking for the optional title
1212 if (c=='\'' || c=='"' || c=='(') // optional title present?
1214 //printf(" start of title found! char='%c'\n",c);
1216 if (c=='(') c=')'; // replace c by end character
1218 // search for end of the line
1219 while (i<size && data[i]!='\n') i++;
1222 // search back to matching character
1224 while (end>titleStart && data[end]!=c) end--;
1227 convertStringFragment(title,data+titleStart,end-titleStart);
1229 //printf(" title found: '%s'\n",title.data());
1231 while (i<size && data[i]==' ') i++;
1232 //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
1233 // i,size,data[i],eol);
1234 if (i>=size) return i; // end of buffer while ref id was found
1235 else if (eol) return eol; // end of line while ref id was found
1236 return 0; // invalid link ref
1239 static int isHRuler(const char *data,int size)
1242 if (size>0 && data[size-1]=='\n') size--; // ignore newline character
1243 while (i<size && data[i]==' ') i++;
1244 if (i>=size) return 0; // empty line
1246 if (c!='*' && c!='-' && c!='_')
1248 return 0; // not a hrule character
1255 n++; // count rule character
1257 else if (data[i]!=' ')
1259 return 0; // line contains non hruler characters
1263 return n>=3; // at least 3 characters needed for a hruler
1266 static QCString extractTitleId(QCString &title, int level)
1268 //static QRegExp r1("^[a-z_A-Z][a-z_A-Z0-9\\-]*:");
1269 static QRegExp r2("\\{#[a-z_A-Z][a-z_A-Z0-9\\-]*\\}");
1271 int i = r2.match(title,0,&l);
1272 if (i!=-1 && title.mid(i+l).stripWhiteSpace().isEmpty()) // found {#id} style id
1274 QCString id = title.mid(i+2,l-3);
1275 title = title.left(i);
1276 //printf("found id='%s' title='%s'\n",id.data(),title.data());
1279 if ((level > 0) && (level <= Config_getInt(TOC_INCLUDE_HEADINGS)))
1281 static int autoId = 0;
1283 id.sprintf("autotoc_md%d",autoId++);
1284 //printf("auto-generated id='%s' title='%s'\n",id.data(),title.data());
1287 //printf("no id found in title '%s'\n",title.data());
1292 static int isAtxHeader(const char *data,int size,
1293 QCString &header,QCString &id)
1296 int level = 0, blanks=0;
1298 // find start of header text and determine heading level
1299 while (i<size && data[i]==' ') i++;
1300 if (i>=size || data[i]!='#')
1304 while (i<size && level<6 && data[i]=='#') i++,level++;
1305 while (i<size && data[i]==' ') i++,blanks++;
1306 if (level==1 && blanks==0)
1308 return 0; // special case to prevent #someid seen as a header (see bug 671395)
1311 // find end of header text
1313 while (end<size && data[end]!='\n') end++;
1314 while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
1317 convertStringFragment(header,data+i,end-i);
1318 id = extractTitleId(header, level);
1319 if (!id.isEmpty()) // strip #'s between title and id
1321 i=header.length()-1;
1322 while (i>=0 && (header.at(i)=='#' || header.at(i)==' ')) i--;
1323 header=header.left(i+1);
1329 static int isEmptyLine(const char *data,int size)
1334 if (data[i]=='\n') return TRUE;
1335 if (data[i]!=' ') return FALSE;
1341 #define isLiTag(i) \
1342 (data[(i)]=='<' && \
1343 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
1344 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
1347 // compute the indent from the start of the input, excluding list markers
1348 // such as -, -#, *, +, 1., and <li>
1349 static int computeIndentExcludingListMarkers(const char *data,int size)
1355 bool listMarkerSkipped=FALSE;
1357 (data[i]==' ' || // space
1358 (!listMarkerSkipped && // first list marker
1359 (data[i]=='+' || data[i]=='-' || data[i]=='*' || // unordered list char
1360 (data[i]=='#' && i>0 && data[i-1]=='-') || // -# item
1361 (isDigit=(data[i]>='1' && data[i]<='9')) || // ordered list marker?
1362 (isLi=(i<size-3 && isLiTag(i))) // <li> tag
1368 if (isDigit) // skip over ordered list marker '10. '
1371 while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
1373 if (data[j]=='.') // should be end of the list marker
1375 if (j<size-1 && data[j+1]==' ') // valid list marker
1377 listMarkerSkipped=TRUE;
1382 else // not a list marker
1392 i+=3; // skip over <li>
1394 listMarkerSkipped=TRUE;
1396 else if (data[i]=='-' && i<size-2 && data[i+1]=='#' && data[i+2]==' ')
1398 listMarkerSkipped=TRUE; // only a single list marker is accepted
1402 else if (data[i]!=' ' && i<size-1 && data[i+1]==' ')
1403 { // case "- " or "+ " or "* "
1404 listMarkerSkipped=TRUE; // only a single list marker is accepted
1406 if (data[i]!=' ' && !listMarkerSkipped)
1412 //printf("{%s}->%d\n",QCString(data).left(size).data(),indent);
1416 static bool isFencedCodeBlock(const char *data,int size,int refIndent,
1417 QCString &lang,int &start,int &end,int &offset)
1419 // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
1424 while (i<size && data[i]==' ') indent++,i++;
1425 if (indent>=refIndent+4) return FALSE; // part of code block
1427 if (i<size && data[i]=='`') tildaChar='`';
1428 while (i<size && data[i]==tildaChar) startTildes++,i++;
1429 if (startTildes<3) return FALSE; // not enough tildes
1430 if (i<size && data[i]=='{') i++; // skip over optional {
1432 while (i<size && (data[i]!='\n' && data[i]!='}' && data[i]!=' ')) i++;
1433 convertStringFragment(lang,data+startLang,i-startLang);
1434 while (i<size && data[i]!='\n') i++; // proceed to the end of the line
1438 if (data[i]==tildaChar)
1442 while (i<size && data[i]==tildaChar) endTildes++,i++;
1443 while (i<size && data[i]==' ') i++;
1444 if (i==size || data[i]=='\n')
1447 return endTildes==startTildes;
1455 static bool isCodeBlock(const char *data,int offset,int size,int &indent)
1457 //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
1458 // determine the indent of this line
1461 while (i<size && data[i]==' ') indent0++,i++;
1463 if (indent0<codeBlockIndent)
1465 //printf(">isCodeBlock: line is not indented enough %d<4\n",indent0);
1468 if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
1470 //printf("only spaces at the end of a comment block\n");
1477 // search back 3 lines and remember the start of lines -1 and -2
1480 if (data[i-offset-1]=='\n') nl_pos[nl++]=i-offset;
1484 // if there are only 2 preceding lines, then line -2 starts at -offset
1485 if (i==0 && nl==2) nl_pos[nl++]=-offset;
1486 //printf(" nl=%d\n",nl);
1488 if (nl==3) // we have at least 2 preceding lines
1490 //printf(" positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
1491 // nl_pos[0],nl_pos[1],nl_pos[2],
1492 // QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1).data(),
1493 // QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1).data());
1495 // check that line -1 is empty
1496 if (!isEmptyLine(data+nl_pos[1],nl_pos[0]-nl_pos[1]-1))
1501 // determine the indent of line -2
1502 indent=computeIndentExcludingListMarkers(data+nl_pos[2],nl_pos[1]-nl_pos[2]);
1504 //printf(">isCodeBlock local_indent %d>=%d+4=%d\n",
1505 // indent0,indent2,indent0>=indent2+4);
1506 // if the difference is >4 spaces -> code block
1507 return indent0>=indent+codeBlockIndent;
1509 else // not enough lines to determine the relative indent, use global indent
1511 // check that line -1 is empty
1512 if (nl==1 && !isEmptyLine(data-offset,offset-1))
1516 //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
1517 // indent0,indent,indent0>=indent+4,nl);
1518 return indent0>=indent+codeBlockIndent;
1522 /** Finds the location of the table's contains in the string \a data.
1523 * Only one line will be inspected.
1524 * @param[in] data pointer to the string buffer.
1525 * @param[in] size the size of the buffer.
1526 * @param[out] start offset of the first character of the table content
1527 * @param[out] end offset of the last character of the table content
1528 * @param[out] columns number of table columns found
1529 * @returns The offset until the next line in the buffer.
1531 int findTableColumns(const char *data,int size,int &start,int &end,int &columns)
1535 // find start character of the table line
1536 while (i<size && data[i]==' ') i++;
1537 if (i<size && data[i]=='|' && data[i]!='\n') i++,n++; // leading | does not count
1540 // find end character of the table line
1541 while (i<size && data[i]!='\n') i++;
1544 while (i>0 && data[i]==' ') i--;
1545 if (i>0 && data[i-1]!='\\' && data[i]=='|') i--,n++; // trailing or escaped | does not count
1548 // count columns between start and end
1553 while (i<=end) // look for more column markers
1555 if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
1556 if (columns==1) columns++; // first | make a non-table into a two column table
1560 if (n==2 && columns==0) // table row has | ... |
1564 //printf("findTableColumns(start=%d,end=%d,columns=%d) eol=%d\n",
1565 // start,end,columns,eol);
1569 /** Returns TRUE iff data points to the start of a table block */
1570 static bool isTableBlock(const char *data,int size)
1574 // the first line should have at least two columns separated by '|'
1575 int i = findTableColumns(data,size,start,end,cc0);
1576 if (i>=size || cc0<1)
1578 //printf("isTableBlock: no |'s in the header\n");
1583 int ret = findTableColumns(data+i,size-i,start,end,cc1);
1585 // separator line should consist of |, - and : and spaces only
1588 if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
1590 //printf("isTableBlock: invalid character '%c'\n",data[j]);
1591 return FALSE; // invalid characters in table separator
1595 if (cc1!=cc0) // number of columns should be same as previous line
1600 i+=ret; // goto next line
1602 findTableColumns(data+i,size-i,start,end,cc2);
1604 //printf("isTableBlock: %d\n",cc1==cc2);
1608 static int writeTableBlock(GrowBuf &out,const char *data,int size)
1611 int columns,start,end,cc;
1613 i = findTableColumns(data,size,start,end,columns);
1615 int headerStart = start;
1616 int headerEnd = end;
1618 #ifdef USE_ORIGINAL_TABLES
1619 out.addStr("<table>");
1621 // write table header, in range [start..end]
1625 // read cell alignments
1626 int ret = findTableColumns(data+i,size-i,start,end,cc);
1628 Alignment *columnAlignment = new Alignment[columns];
1630 bool leftMarker=FALSE,rightMarker=FALSE;
1631 bool startFound=FALSE;
1637 if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
1638 if (data[j]=='-') startFound=TRUE;
1639 //printf(" data[%d]=%c startFound=%d\n",j,data[j],startFound);
1641 if (data[j]=='-') rightMarker=FALSE;
1642 else if (data[j]==':') rightMarker=TRUE;
1643 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1647 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1648 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1659 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1660 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1662 // proceed to next line
1665 #ifdef USE_ORIGINAL_TABLES
1668 for (k=0;k<columns;k++)
1671 switch (columnAlignment[k])
1673 case AlignLeft: out.addStr(" align=\"left\""); break;
1674 case AlignRight: out.addStr(" align=\"right\""); break;
1675 case AlignCenter: out.addStr(" align=\"center\""); break;
1676 case AlignNone: break;
1679 while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
1681 out.addChar(data[m++]);
1685 out.addStr("\n</th>\n");
1687 // write table cells
1690 int ret = findTableColumns(data+i,size-i,start,end,cc);
1691 //printf("findTableColumns cc=%d\n",cc);
1692 if (cc!=columns) break; // end of table
1703 switch (columnAlignment[k])
1705 case AlignLeft: out.addStr(" align=\"left\""); break;
1706 case AlignRight: out.addStr(" align=\"right\""); break;
1707 case AlignCenter: out.addStr(" align=\"center\""); break;
1708 case AlignNone: break;
1712 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1719 out.addChar(data[j]);
1725 // proceed to next line
1729 out.addStr("</table> ");
1731 // Store the table cell information by row then column. This
1732 // allows us to handle row spanning.
1733 QVector<QVector<TableCell> > tableContents;
1734 tableContents.setAutoDelete(TRUE);
1737 QVector<TableCell> *headerContents = new QVector<TableCell>(columns);
1738 headerContents->setAutoDelete(TRUE);
1739 for (k=0;k<columns;k++)
1741 headerContents->insert(k, new TableCell);
1742 while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
1744 headerContents->at(k)->cellText += data[m++];
1747 // do the column span test before stripping white space
1748 // || is spanning columns, | | is not
1749 headerContents->at(k)->colSpan = headerContents->at(k)->cellText.isEmpty();
1750 headerContents->at(k)->cellText = headerContents->at(k)->cellText.stripWhiteSpace();
1752 // qvector doesn't have an append like std::vector, so we gotta do
1754 tableContents.resize(1);
1755 tableContents.insert(0, headerContents);
1757 // write table cells
1761 int ret = findTableColumns(data+i,size-i,start,end,cc);
1762 if (cc!=columns) break; // end of table
1766 QVector<TableCell> *rowContents = new QVector<TableCell>(columns);
1767 rowContents->setAutoDelete(TRUE);
1768 rowContents->insert(k, new TableCell);
1771 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1773 // do the column span test before stripping white space
1774 // || is spanning columns, | | is not
1775 rowContents->at(k)->colSpan = rowContents->at(k)->cellText.isEmpty();
1776 rowContents->at(k)->cellText = rowContents->at(k)->cellText.stripWhiteSpace();
1778 rowContents->insert(k, new TableCell);
1779 } // if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
1782 rowContents->at(k)->cellText += data[j];
1783 } // else { if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\'))) }
1785 } // while (j<=end+i)
1786 // do the column span test before stripping white space
1787 // || is spanning columns, | | is not
1788 rowContents->at(k)->colSpan = rowContents->at(k)->cellText.isEmpty();
1789 rowContents->at(k)->cellText = rowContents->at(k)->cellText.stripWhiteSpace();
1790 // qvector doesn't have an append like std::vector, so we gotta do
1792 tableContents.resize(tableContents.size()+1);
1793 tableContents.insert(rowNum++, rowContents);
1795 // proceed to next line
1800 out.addStr("<table class=\"markdownTable\">\n");
1801 QCString cellTag("th"), cellClass("class=\"markdownTableHead");
1802 for (unsigned row = 0; row < tableContents.size(); row++)
1808 out.addStr("<tr class=\"markdownTableRowOdd\">\n");
1812 out.addStr("<tr class=\"markdownTableRowEven\">\n");
1817 out.addStr(" <tr class=\"markdownTableHead\">\n");
1819 for (int c = 0; c < columns; c++)
1821 // save the cell text for use after column span computation
1822 QCString cellText(tableContents[row]->at(c)->cellText);
1824 // Row span handling. Spanning rows will contain a caret ('^').
1825 // If the current cell contains just a caret, this is part of an
1826 // earlier row's span and the cell should not be added to the
1828 if (tableContents[row]->at(c)->cellText == "^")
1830 unsigned rowSpan = 1, spanRow = row+1;
1831 while ((spanRow < tableContents.size()) &&
1832 (tableContents[spanRow]->at(c)->cellText == "^"))
1838 out.addStr(" <" + cellTag + " " + cellClass);
1839 // use appropriate alignment style
1840 switch (columnAlignment[c])
1842 case AlignLeft: out.addStr("Left\""); break;
1843 case AlignRight: out.addStr("Right\""); break;
1844 case AlignCenter: out.addStr("Center\""); break;
1845 case AlignNone: out.addStr("None\""); break;
1851 spanStr.setNum(rowSpan);
1852 out.addStr(" rowspan=\"" + spanStr + "\"");
1854 // Column span handling, assumes that column spans will have
1855 // empty strings, which would indicate the sequence "||", used
1856 // to signify spanning columns.
1857 unsigned colSpan = 1;
1858 while ((c < columns-1) &&
1859 tableContents[row]->at(c+1)->colSpan)
1867 spanStr.setNum(colSpan);
1868 out.addStr(" colspan=\"" + spanStr + "\"");
1870 // need at least one space on either side of the cell text in
1871 // order for doxygen to do other formatting
1872 out.addStr("> " + cellText + " </" + cellTag + ">\n");
1875 cellClass = "class=\"markdownTableBody";
1876 out.addStr(" </tr>\n");
1878 out.addStr("</table>\n");
1881 delete[] columnAlignment;
1886 static int hasLineBreak(const char *data,int size)
1889 while (i<size && data[i]!='\n') i++;
1890 if (i>=size) return 0; // empty line
1891 if (i<2) return 0; // not long enough
1892 return (data[i-1]==' ' && data[i-2]==' ');
1896 void writeOneLineHeaderOrRuler(GrowBuf &out,const char *data,int size)
1901 if (isHRuler(data,size))
1903 out.addStr("\n<hr>\n");
1905 else if ((level=isAtxHeader(data,size,header,id)))
1907 //if (level==1) g_correctSectionLevel=FALSE;
1908 //if (g_correctSectionLevel) level--;
1910 if (level<5 && !id.isEmpty())
1912 SectionInfo::SectionType type = SectionInfo::Anchor;
1915 case 1: out.addStr("@section ");
1916 type=SectionInfo::Section;
1918 case 2: out.addStr("@subsection ");
1919 type=SectionInfo::Subsection;
1921 case 3: out.addStr("@subsubsection ");
1922 type=SectionInfo::Subsubsection;
1924 default: out.addStr("@paragraph ");
1925 type=SectionInfo::Paragraph;
1937 out.addStr("\\anchor "+id+"\n");
1939 hTag.sprintf("h%d",level);
1940 out.addStr("<"+hTag+">");
1942 out.addStr("</"+hTag+">\n");
1945 else // nothing interesting -> just output the line
1947 out.addStr(data,size);
1948 if (hasLineBreak(data,size))
1955 static int writeBlockQuote(GrowBuf &out,const char *data,int size)
1963 // find end of this line
1965 while (end<=size && data[end-1]!='\n') end++;
1969 // compute the quoting level
1970 while (j<end && (data[j]==' ' || data[j]=='>'))
1972 if (data[j]=='>') { level++; indent=j+1; }
1973 else if (j>0 && data[j-1]=='>') indent=j+1;
1976 if (j>0 && data[j-1]=='>' &&
1977 !(j==size || data[j]=='\n')) // disqualify last > if not followed by space
1982 if (level>curLevel) // quote level increased => add start markers
1984 for (l=curLevel;l<level;l++)
1986 out.addStr("<blockquote>\n");
1989 else if (level<curLevel) // quote level descreased => add end markers
1991 for (l=level;l<curLevel;l++)
1993 out.addStr("</blockquote>\n");
1997 if (level==0) break; // end of quote block
1998 // copy line without quotation marks
1999 out.addStr(data+indent,end-indent);
2000 // proceed with next line
2003 // end of comment within blockquote => add end markers
2004 for (l=0;l<curLevel;l++)
2006 out.addStr("</blockquote>\n");
2011 static int writeCodeBlock(GrowBuf &out,const char *data,int size,int refIndent)
2014 //printf("writeCodeBlock: data={%s}\n",QCString(data).left(size).data());
2015 out.addStr("@verbatim\n");
2019 // find end of this line
2021 while (end<=size && data[end-1]!='\n') end++;
2024 while (j<end && data[j]==' ') j++,indent++;
2025 //printf("j=%d end=%d indent=%d refIndent=%d tabSize=%d data={%s}\n",
2026 // j,end,indent,refIndent,Config_getInt(TAB_SIZE),QCString(data+i).left(end-i-1).data());
2027 if (j==end-1) // empty line
2032 else if (indent>=refIndent+codeBlockIndent) // enough indent to contine the code block
2034 while (emptyLines>0) // write skipped empty lines
2040 // add code line minus the indent
2041 out.addStr(data+i+refIndent+codeBlockIndent,end-i-refIndent-codeBlockIndent);
2044 else // end of code block
2049 out.addStr("@endverbatim\n");
2050 while (emptyLines>0) // write skipped empty lines
2056 //printf("i=%d\n",i);
2060 // start searching for the end of the line start at offset \a i
2061 // keeping track of possible blocks that need to be skipped.
2062 static void findEndOfLine(GrowBuf &out,const char *data,int size,
2063 int &pi,int&i,int &end)
2065 // find end of the line
2068 while (end<=size && data[end-1]!='\n')
2070 // while looking for the end of the line we might encounter a block
2071 // that needs to be passed unprocessed.
2072 if ((data[end-1]=='\\' || data[end-1]=='@') && // command
2073 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
2076 QCString endBlockName = isBlockCommand(data+end-1,end-1,size-(end-1));
2078 if (!endBlockName.isEmpty())
2080 int l = endBlockName.length();
2081 for (;end<size-l-1;end++) // search for end of block marker
2083 if ((data[end]=='\\' || data[end]=='@') &&
2084 data[end-1]!='\\' && data[end-1]!='@'
2087 if (qstrncmp(&data[end+1],endBlockName,l)==0)
2089 if (pi!=-1) // output previous line if available
2091 //printf("feol out={%s}\n",QCString(data+pi).left(i-pi).data());
2092 out.addStr(data+pi,i-pi);
2094 // found end marker, skip over this block
2095 //printf("feol.block out={%s}\n",QCString(data+i).left(end+l+1-i).data());
2096 out.addStr(data+i,end+l+1-i);
2098 i=end+l+1; // continue after block
2106 else if (nb==0 && data[end-1]=='<' && end<size-6 &&
2107 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
2110 if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
2111 tolower(data[end+2])=='e' && data[end+3]=='>') // <pre> tag
2113 if (pi!=-1) // output previous line if available
2115 out.addStr(data+pi,i-pi);
2117 // output part until <pre>
2118 out.addStr(data+i,end-1-i);
2119 // output part until </pre>
2120 i = end-1 + processHtmlTag(out,data+end-1,end-1,size-end+1);
2130 else if (nb==0 && data[end-1]=='`')
2132 while (end<=size && data[end-1]=='`') end++,nb++;
2134 else if (nb>0 && data[end-1]=='`')
2137 while (end<=size && data[end-1]=='`') end++,enb++;
2145 //printf("findEndOfLine pi=%d i=%d end=%d {%s}\n",pi,i,end,QCString(data+i).left(end-i).data());
2148 static void writeFencedCodeBlock(GrowBuf &out,const char *data,const char *lng,
2149 int blockStart,int blockEnd)
2151 QCString lang = lng;
2152 if (!lang.isEmpty() && lang.at(0)=='.') lang=lang.mid(1);
2153 out.addStr("@code");
2154 if (!lang.isEmpty())
2156 out.addStr("{"+lang+"}");
2158 out.addStr(data+blockStart,blockEnd-blockStart);
2160 out.addStr("@endcode");
2163 static QCString processQuotations(const QCString &s,int refIndent)
2166 const char *data = s.data();
2167 int size = s.length();
2168 int i=0,end=0,pi=-1;
2169 int blockStart,blockEnd,blockOffset;
2173 findEndOfLine(out,data,size,pi,i,end);
2174 // line is now found at [i..end)
2178 if (isFencedCodeBlock(data+pi,size-pi,refIndent,lang,blockStart,blockEnd,blockOffset))
2180 writeFencedCodeBlock(out,data+pi,lang,blockStart,blockEnd);
2186 else if (isBlockQuote(data+pi,i-pi,refIndent))
2188 i = pi+writeBlockQuote(out,data+pi,size-pi);
2195 //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
2196 out.addStr(data+pi,i-pi);
2202 if (pi!=-1 && pi<size) // deal with the last line
2204 if (isBlockQuote(data+pi,size-pi,refIndent))
2206 writeBlockQuote(out,data+pi,size-pi);
2210 out.addStr(data+pi,size-pi);
2215 //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
2216 // s.data(),out.get());
2221 static QCString processBlocks(const QCString &s,int indent)
2224 const char *data = s.data();
2225 int size = s.length();
2226 int i=0,end=0,pi=-1,ref,level;
2227 QCString id,link,title;
2228 int blockIndent = indent;
2230 // get indent for the first line
2233 while (end<=size && data[end-1]!='\n')
2235 if (data[end-1]==' ') sp++;
2239 #if 0 // commented out, since starting with a comment block is probably a usage error
2240 // see also http://stackoverflow.com/q/20478611/784672
2242 // special case when the documentation starts with a code block
2243 // since the first line is skipped when looking for a code block later on.
2244 if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
2246 i=writeCodeBlock(out,data,size,blockIndent);
2252 // process each line
2255 findEndOfLine(out,data,size,pi,i,end);
2256 // line is now found at [i..end)
2258 //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
2262 int blockStart,blockEnd,blockOffset;
2264 blockIndent = indent;
2265 //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
2266 if ((level=isHeaderline(data+i,size-i))>0)
2268 //if (level==1) g_correctSectionLevel=FALSE;
2269 //if (g_correctSectionLevel) level--;
2270 //printf("Found header at %d-%d\n",i,end);
2271 while (pi<size && data[pi]==' ') pi++;
2273 convertStringFragment(header,data+pi,i-pi-1);
2274 id = extractTitleId(header, level);
2275 //printf("header='%s' is='%s'\n",header.data(),id.data());
2276 if (!header.isEmpty())
2280 out.addStr(level==1?"@section ":"@subsection ");
2285 SectionInfo *si = Doxygen::sectionDict->find(id);
2288 if (si->lineNr != -1)
2290 warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s, line %d)",header.data(),si->fileName.data(),si->lineNr);
2294 warn(g_fileName,g_lineNr,"multiple use of section label '%s', (first occurrence: %s)",header.data(),si->fileName.data());
2299 si = new SectionInfo(g_fileName,g_lineNr,id,header,
2300 level==1 ? SectionInfo::Section : SectionInfo::Subsection,level);
2303 g_current->anchors->append(si);
2305 Doxygen::sectionDict->append(id,si);
2310 out.addStr(level==1?"<h1>":"<h2>");
2312 out.addStr(level==1?"\n</h1>\n":"\n</h2>\n");
2317 out.addStr("\n<hr>\n");
2324 else if ((ref=isLinkRef(data+pi,size-pi,id,link,title)))
2326 //printf("found link ref: id='%s' link='%s' title='%s'\n",
2327 // id.data(),link.data(),title.data());
2328 g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2333 else if (isFencedCodeBlock(data+pi,size-pi,indent,lang,blockStart,blockEnd,blockOffset))
2335 //printf("Found FencedCodeBlock lang='%s' start=%d end=%d code={%s}\n",
2336 // lang.data(),blockStart,blockEnd,QCString(data+pi+blockStart).left(blockEnd-blockStart).data());
2337 writeFencedCodeBlock(out,data+pi,lang,blockStart,blockEnd);
2343 else if (isCodeBlock(data+i,i,end-i,blockIndent))
2345 // skip previous line (it is empty anyway)
2346 i+=writeCodeBlock(out,data+i,size-i,blockIndent);
2351 else if (isTableBlock(data+pi,size-pi))
2353 i=pi+writeTableBlock(out,data+pi,size-pi);
2360 writeOneLineHeaderOrRuler(out,data+pi,i-pi);
2366 //printf("last line %d size=%d\n",i,size);
2367 if (pi!=-1 && pi<size) // deal with the last line
2369 if (isLinkRef(data+pi,size-pi,id,link,title))
2371 //printf("found link ref: id='%s' link='%s' title='%s'\n",
2372 // id.data(),link.data(),title.data());
2373 g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2377 writeOneLineHeaderOrRuler(out,data+pi,size-pi);
2385 /** returns TRUE if input string docs starts with \@page or \@mainpage command */
2386 static bool isExplicitPage(const QCString &docs)
2389 const char *data = docs.data();
2392 int size=docs.size();
2393 while (i<size && (data[i]==' ' || data[i]=='\n'))
2398 (data[i]=='\\' || data[i]=='@') &&
2399 (qstrncmp(&data[i+1],"page ",5)==0 || qstrncmp(&data[i+1],"mainpage",8)==0)
2408 static QCString extractPageTitle(QCString &docs,QCString &id)
2411 // first first non-empty line
2413 const char *data = docs.data();
2415 int size=docs.size();
2416 while (i<size && (data[i]==' ' || data[i]=='\n'))
2418 if (data[i]=='\n') ln++;
2421 if (i>=size) return "";
2423 while (end1<size && data[end1-1]!='\n') end1++;
2424 //printf("i=%d end1=%d size=%d line='%s'\n",i,end1,size,docs.mid(i,end1-i).data());
2425 // first line from i..end1
2429 // second line form end1..end2
2431 while (end2<size && data[end2-1]!='\n') end2++;
2432 if (isHeaderline(data+end1,size-end1))
2434 convertStringFragment(title,data+i,end1-i-1);
2437 docs=lns+docs.mid(end2);
2438 id = extractTitleId(title, 0);
2439 //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2443 if (i<end1 && isAtxHeader(data+i,end1-i,title,id)>0)
2445 docs=docs.mid(end1);
2447 //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2451 static QCString detab(const QCString &s,int &refIndent)
2453 static int tabSize = Config_getInt(TAB_SIZE);
2455 int size = s.length();
2456 const char *data = s.data();
2459 const int maxIndent=1000000; // value representing infinity
2460 int minIndent=maxIndent;
2466 case '\t': // expand tab
2468 int stop = tabSize - (col%tabSize);
2469 //printf("expand at %d stop=%d\n",col,stop);
2471 while (stop--) out.addChar(' ');
2474 case '\n': // reset colomn counter
2478 case ' ': // increment column counter
2482 default: // non-whitespace => update minIndent
2484 if (c<0 && i<size) // multibyte sequence
2486 out.addChar(data[i++]); // >= 2 bytes
2487 if (((uchar)c&0xE0)==0xE0 && i<size)
2489 out.addChar(data[i++]); // 3 bytes
2491 if (((uchar)c&0xF0)==0xF0 && i<size)
2493 out.addChar(data[i++]); // 4 byres
2496 if (col<minIndent) minIndent=col;
2500 if (minIndent!=maxIndent) refIndent=minIndent; else refIndent=0;
2502 //printf("detab refIndent=%d\n",refIndent);
2506 //---------------------------------------------------------------------------
2508 QCString processMarkdown(const QCString &fileName,const int lineNr,Entry *e,const QCString &input)
2510 static bool init=FALSE;
2513 // setup callback table for special characters
2514 g_actions[(unsigned int)'_']=processEmphasis;
2515 g_actions[(unsigned int)'*']=processEmphasis;
2516 g_actions[(unsigned int)'~']=processEmphasis;
2517 g_actions[(unsigned int)'`']=processCodeSpan;
2518 g_actions[(unsigned int)'\\']=processSpecialCommand;
2519 g_actions[(unsigned int)'@']=processSpecialCommand;
2520 g_actions[(unsigned int)'[']=processLink;
2521 g_actions[(unsigned int)'!']=processLink;
2522 g_actions[(unsigned int)'<']=processHtmlTag;
2523 g_actions[(unsigned int)'-']=processNmdash;
2524 g_actions[(unsigned int)'"']=processQuoted;
2528 g_linkRefs.setAutoDelete(TRUE);
2531 g_fileName = fileName;
2534 if (input.isEmpty()) return input;
2537 // for replace tabs by spaces
2538 QCString s = detab(input,refIndent);
2539 //printf("======== DeTab =========\n---- output -----\n%s\n---------\n",s.data());
2540 // then process quotation blocks (as these may contain other blocks)
2541 s = processQuotations(s,refIndent);
2542 //printf("======== Quotations =========\n---- output -----\n%s\n---------\n",s.data());
2543 // then process block items (headers, rules, and code blocks, references)
2544 s = processBlocks(s,refIndent);
2545 //printf("======== Blocks =========\n---- output -----\n%s\n---------\n",s.data());
2546 // finally process the inline markup (links, emphasis and code spans)
2547 processInline(out,s,s.length());
2549 Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n%s\n---- output -----\n%s\n=========\n",qPrint(input),qPrint(out.get()));
2553 //---------------------------------------------------------------------------
2555 QCString markdownFileNameToId(const QCString &fileName)
2557 QCString baseFn = stripFromPath(QFileInfo(fileName).absFilePath().utf8());
2558 int i = baseFn.findRev('.');
2559 if (i!=-1) baseFn = baseFn.left(i);
2560 QCString baseName = substitute(substitute(baseFn," ","_"),"/","_");
2561 return "md_"+baseName;
2564 void MarkdownFileParser::parseInput(const char *fileName,
2565 const char *fileBuf,
2567 bool /*sameTranslationUnit*/,
2568 QStrList & /*filesInSameTranslationUnit*/)
2570 Entry *current = new Entry;
2571 current->lang = SrcLangExt_Markdown;
2572 current->fileName = fileName;
2573 current->docFile = fileName;
2574 current->docLine = 1;
2575 QCString docs = fileBuf;
2577 QCString title=extractPageTitle(docs,id).stripWhiteSpace();
2578 QCString titleFn = QFileInfo(fileName).baseName().utf8();
2579 QCString fn = QFileInfo(fileName).fileName().utf8();
2580 static QCString mdfileAsMainPage = Config_getString(USE_MDFILE_AS_MAINPAGE);
2581 if (id.isEmpty()) id = markdownFileNameToId(fileName);
2582 if (!isExplicitPage(docs))
2584 if (!mdfileAsMainPage.isEmpty() &&
2585 (fn==mdfileAsMainPage || // name reference
2586 QFileInfo(fileName).absFilePath()==
2587 QFileInfo(mdfileAsMainPage).absFilePath()) // file reference with path
2590 docs.prepend("@mainpage "+title+"\n");
2592 else if (id=="mainpage" || id=="index")
2594 if (title.isEmpty()) title = titleFn;
2595 docs.prepend("@mainpage "+title+"\n");
2599 if (title.isEmpty()) title = titleFn;
2600 docs.prepend("@page "+id+" "+title+"\n");
2606 // even without markdown support enabled, we still
2607 // parse markdown files as such
2608 bool markdownEnabled = Doxygen::markdownSupport;
2609 Doxygen::markdownSupport = TRUE;
2611 bool needsEntry = FALSE;
2612 Protection prot=Public;
2613 while (parseCommentBlock(
2620 FALSE, // javadoc autobrief
2621 FALSE, // inBodyDocs
2628 QCString docFile = current->docFile;
2629 root->addSubEntry(current);
2630 current = new Entry;
2631 current->lang = SrcLangExt_Markdown;
2632 current->docFile = docFile;
2633 current->docLine = lineNr;
2638 root->addSubEntry(current);
2642 Doxygen::markdownSupport = markdownEnabled;
2643 //g_correctSectionLevel = FALSE;
2646 void MarkdownFileParser::parseCode(CodeOutputInterface &codeOutIntf,
2647 const char *scopeName,
2648 const QCString &input,
2650 bool isExampleBlock,
2651 const char *exampleName,
2655 bool inlineFragment,
2656 MemberDef *memberDef,
2657 bool showLineNumbers,
2658 Definition *searchCtx,
2662 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2666 codeOutIntf,scopeName,input,lang,isExampleBlock,exampleName,
2667 fileDef,startLine,endLine,inlineFragment,memberDef,showLineNumbers,
2668 searchCtx,collectXRefs);
2672 void MarkdownFileParser::resetCodeParserState()
2674 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2677 pIntf->resetCodeParserState();
2681 void MarkdownFileParser::parsePrototype(const char *text)
2683 ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2686 pIntf->parsePrototype(text);