36#include <unordered_map>
57#if !ENABLE_MARKDOWN_TRACING
61#define AUTO_TRACE(...) (void)0
62#define AUTO_TRACE_ADD(...) (void)0
63#define AUTO_TRACE_EXIT(...) (void)0
78 ((c>='a' && c<='z') || \
79 (c>='A' && c<='Z') || \
80 (c>='0' && c<='9') || \
81 (static_cast<unsigned char>(c)>=0x80))
85 (c=='-' || c=='+' || c=='!' || \
86 c=='?' || c=='$' || c=='@' || \
87 c=='&' || c=='*' || c=='_' || c=='%' || \
88 c=='[' || c=='(' || c=='.' || \
89 c=='>' || c==':' || c==',' || \
90 c==';' || c=='\'' || c=='"' || c=='`')
92// is character c allowed before an emphasis section
93#define isOpenEmphChar(c) \
94 (c=='\n' || c==' ' || c=='\'' || c=='<' || \
95 c=='>' || c=='{' || c=='(' || c=='[' || \
96 c==',' || c==':' || c==';')
98// test for non breakable space (UTF-8)
99#define isUtf8Nbsp(c1,c2) \
100 (c1==static_cast<char>(0xc2) && c2==static_cast<char>(0xa0))
102#define isAllowedEmphStr(data,offset) \
103 (!(offset>0 && isOpenEmphChar(data.data()[-1])) && \
104 !(offset>1 && isUtf8Nbsp(data.data()[-2],data.data()[-1])))
106// is character at position i in data an escape that prevents ending an emphasis section
107// so for example *bla (*.txt) is cool*
108#define ignoreCloseEmphChar(c,cn) \
109 (c=='(' || c=='{' || c=='[' || (c=='<' && cn!='/') || \
116 TableCell() : colSpan(false) {}
121struct Markdown::Private
123 Private(const QCString &fn,int line,int indent)
124 : fileName(fn), lineNr(line), indentLevel(indent)
126 // setup callback table for special characters
127 actions[static_cast<unsigned int>('_')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
128 actions[static_cast<unsigned int>('*')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
129 actions[static_cast<unsigned int>('~')] = [this](std::string_view data,size_t offset) { return processEmphasis (data,offset); };
130 actions[static_cast<unsigned int>('`')] = [this](std::string_view data,size_t offset) { return processCodeSpan (data,offset); };
131 actions[static_cast<unsigned int>('\\')]= [this](std::string_view data,size_t offset) { return processSpecialCommand(data,offset); };
132 actions[static_cast<unsigned int>('@')] = [this](std::string_view data,size_t offset) { return processSpecialCommand(data,offset); };
133 actions[static_cast<unsigned int>('[')] = [this](std::string_view data,size_t offset) { return processLink (data,offset); };
134 actions[static_cast<unsigned int>('!')] = [this](std::string_view data,size_t offset) { return processLink (data,offset); };
135 actions[static_cast<unsigned int>('<')] = [this](std::string_view data,size_t offset) { return processHtmlTag (data,offset); };
136 actions[static_cast<unsigned int>('-')] = [this](std::string_view data,size_t offset) { return processNmdash (data,offset); };
137 actions[static_cast<unsigned int>('"')] = [this](std::string_view data,size_t offset) { return processQuoted (data,offset); };
140 QCString processQuotations(std::string_view data,
size_t refIndent);
141 QCString processBlocks(std::string_view data,
size_t indent);
142 QCString isBlockCommand(std::string_view data,
size_t offset);
143 size_t isSpecialCommand(std::string_view data,
size_t offset);
144 size_t findEndOfLine(std::string_view data,
size_t offset);
145 int processHtmlTagWrite(std::string_view data,
size_t offset,
bool doWrite);
146 int processHtmlTag(std::string_view data,
size_t offset);
147 int processEmphasis(std::string_view data,
size_t offset);
148 int processEmphasis1(std::string_view data,
char c);
149 int processEmphasis2(std::string_view data,
char c);
150 int processEmphasis3(std::string_view data,
char c);
151 int processNmdash(std::string_view data,
size_t offset);
152 int processQuoted(std::string_view data,
size_t offset);
153 int processCodeSpan(std::string_view data,
size_t offset);
154 int processSpecialCommand(std::string_view data,
size_t offset);
155 int processLink(std::string_view data,
size_t offset);
156 size_t findEmphasisChar(std::string_view,
char c,
size_t c_size);
157 void addStrEscapeUtf8Nbsp(std::string_view data);
158 void processInline(std::string_view data);
159 void writeMarkdownImage(std::string_view
fmt,
bool inline_img,
bool explicitTitle,
163 int isHeaderline(std::string_view data,
bool allowAdjustLevel);
164 int isAtxHeader(std::string_view data,
QCString &header,
QCString &
id,
bool allowAdjustLevel,
165 bool *pIsIdGenerated=
nullptr);
166 void writeOneLineHeaderOrRuler(std::string_view data);
167 void writeFencedCodeBlock(std::string_view data, std::string_view lang,
168 size_t blockStart,
size_t blockEnd);
169 size_t writeBlockQuote(std::string_view data);
170 size_t writeCodeBlock(std::string_view,
size_t refIndent);
171 size_t writeTableBlock(std::string_view data);
172 QCString extractTitleId(
QCString &title,
int level,
bool *pIsIdGenerated=
nullptr);
180 using Action_t = std::function<int(std::string_view,
size_t)>;
191 :
prv(std::make_unique<
Private>(fileName,lineNr,indentLevel))
193 using namespace std::placeholders;
218 if (data[0] ==
'\n')
return 1;
222 return (data.size()>8 && data[8]==
' ') ? 9 : 8;
233 const char *p=s.
data();
237 if (c==
'"' && pc!=
'\\') result+=
'\\';
250 bool insideQuote=
FALSE;
252 const char *p=s.
data();
267 insideQuote=!insideQuote;
279 if ((p[0]==
':') && (p[1]==
':'))
291 case '\\':
if (!insideQuote) { result+=
'\\'; } result+=
'\\';
break;
292 case '@':
if (!insideQuote) { result+=
'\\'; } result+=
'@';
break;
295 case '#':
if (!insideQuote) { result+=
'\\'; } result+=
'#';
break;
296 case '$':
if (!insideQuote) { result+=
'\\'; } result+=
'$';
break;
297 case '&':
if (!insideQuote) { result+=
'\\'; } result+=
'&';
break;
312 if (leftMarker && rightMarker)
320 else if (rightMarker)
335 for (
const auto &attr_ : attrList)
338 int i = attr.
find(
':');
345 return attr.
mid(i+1);
382 using EndBlockFunc =
QCString (*)(
const std::string &,bool,char);
384 static const auto getEndBlock = [](
const std::string &blockName,bool,char) ->
QCString
386 return "end"+blockName;
388 static const auto getEndCode = [](
const std::string &blockName,
bool openBracket,char) ->
QCString
390 return openBracket ?
QCString(
"}") :
"end"+blockName;
392 static const auto getEndUml = [](
const std::string &,bool,char) ->
QCString
396 static const auto getEndFormula = [](
const std::string &,bool,
char nextChar) ->
QCString
400 case '$':
return "f$";
401 case '(':
return "f)";
402 case '[':
return "f]";
403 case '{':
return "f}";
409 static const std::unordered_map<std::string,EndBlockFunc> blockNames =
411 {
"dot", getEndBlock },
412 {
"code", getEndCode },
413 {
"icode", getEndBlock },
414 {
"msc", getEndBlock },
415 {
"verbatim", getEndBlock },
416 {
"iverbatim", getEndBlock },
417 {
"iliteral", getEndBlock },
418 {
"latexonly", getEndBlock },
419 {
"htmlonly", getEndBlock },
420 {
"xmlonly", getEndBlock },
421 {
"rtfonly", getEndBlock },
422 {
"manonly", getEndBlock },
423 {
"docbookonly", getEndBlock },
424 {
"startuml", getEndUml },
425 {
"f", getEndFormula }
428 const size_t size = data.
size();
429 bool openBracket = offset>0 && data.data()[-1]==
'{';
430 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
434 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
436 std::string blockName(data.substr(1,
end-1));
437 auto it = blockNames.find(blockName);
439 if (it!=blockNames.end())
441 result = it->second(blockName, openBracket,
end<size ? data[
end] : 0);
451 using EndCmdFunc = size_t (*)(std::string_view,size_t);
453 static const auto endOfLine = [](std::string_view data_,
size_t offset_) ->
size_t
458 while (offset_<data_.size() && ((c=data_[offset_])!=
'\n' || lc==
'\\'))
460 if (c==
'\\') lc=
'\\';
461 else if (c!=
' ') lc=0;
467 static const auto endOfLabels = [](std::string_view data_,
size_t offset_,
bool multi_) ->
size_t
469 if (offset_<data_.size() && data_[offset_]==
' ')
477 while (offset_<data_.size() && data_[offset_]==
' ')
482 while (offset_<data_.size() && (c=data_[offset_])!=
' ' && c!=
',' && c!=
'\\' && c!=
'@' && c!=
'\n')
487 if (multi_ && offset_<data_.size() && (data_[offset_]==
',' || data_[offset_]==
' '))
489 size_t off = offset_;
490 while (off<data_.size() && data_[off]==
' ')
494 if (off<data_.size() && data_[off]==
',')
513 static const auto endOfLabel = [](std::string_view data_,
size_t offset_) ->
size_t
515 return endOfLabels(data_,offset_,
false);
518 static const auto endOfLabelOpt = [](std::string_view data_,
size_t offset_) ->
size_t
520 size_t index=offset_;
521 if (index<data_.size() && data_[index]==
' ')
524 while (index<data_.size() && data_[index]==
' ') index++;
526 if (index<data_.size() && data_[index]==
'{')
530 while (index<data_.size() && (c=data_[index])!=
'}' && c!=
'\\' && c!=
'@' && c!=
'\n') index++;
531 if (index==data_.size() || data_[index]!=
'}')
return 0;
534 return endOfLabel(data_,offset_);
537 static const auto endOfParam = [](std::string_view data_,
size_t offset_) ->
size_t
539 size_t index=offset_;
540 if (index<data_.size() && data_[index]==
' ')
543 while (index<data_.size() && data_[index]==
' ') index++;
545 if (index<data_.size() && data_[index]==
'[')
549 while (index<data_.size() && (c=data_[index])!=
']' && c!=
'\n') index++;
550 if (index==data_.size() || data_[index]!=
']')
return 0;
553 return endOfLabels(data_,offset_,
true);
556 static const auto endOfRetVal = [](std::string_view data_,
size_t offset_) ->
size_t
558 return endOfLabels(data_,offset_,
true);
561 static const auto endOfFuncLike = [](std::string_view data_,
size_t offset_,
bool allowSpaces) ->
size_t
563 if (offset_<data_.size() && data_[offset_]==
' ')
568 while (offset_<data_.size() && data_[offset_]==
' ')
573 while (offset_<data_.size() && (c=data_[offset_])!=
'\n' && (allowSpaces || c!=
' ') && c!=
'(')
575 if (
literal_at(data_.substr(offset_),
"\\ilinebr "))
break;
582 while (offset_<data_.size() && (c=data_[offset_++]))
585 else if (c==
')') count--;
586 if (count==0)
return offset_;
594 static const auto endOfFunc = [](std::string_view data_,
size_t offset_) ->
size_t
596 return endOfFuncLike(data_,offset_,
true);
599 static const auto endOfGuard = [](std::string_view data_,
size_t offset_) ->
size_t
601 return endOfFuncLike(data_,offset_,
false);
604 static const std::unordered_map<std::string,EndCmdFunc> cmdNames =
607 {
"addindex", endOfLine },
608 {
"addtogroup", endOfLabel },
609 {
"anchor", endOfLabel },
612 {
"category", endOfLine },
613 {
"cite", endOfLabel },
614 {
"class", endOfLine },
615 {
"concept", endOfLine },
616 {
"copybrief", endOfFunc },
617 {
"copydetails", endOfFunc },
618 {
"copydoc", endOfFunc },
619 {
"def", endOfFunc },
620 {
"defgroup", endOfLabel },
621 {
"diafile", endOfLine },
622 {
"dir", endOfLine },
623 {
"dockbookinclude",endOfLine },
624 {
"dontinclude", endOfLine },
625 {
"dotfile", endOfLine },
627 {
"elseif", endOfGuard },
628 {
"em", endOfLabel },
629 {
"emoji", endOfLabel },
630 {
"enum", endOfLabel },
631 {
"example", endOfLine },
632 {
"exception", endOfLine },
633 {
"extends", endOfLabel },
634 {
"file", endOfLine },
636 {
"headerfile", endOfLine },
637 {
"htmlinclude", endOfLine },
638 {
"ianchor", endOfLabelOpt },
639 {
"idlexcept", endOfLine },
640 {
"if", endOfGuard },
641 {
"ifnot", endOfGuard },
642 {
"image", endOfLine },
643 {
"implements", endOfLine },
644 {
"include", endOfLine },
645 {
"includedoc", endOfLine },
646 {
"includelineno", endOfLine },
647 {
"ingroup", endOfLabel },
648 {
"interface", endOfLine },
649 {
"latexinclude", endOfLine },
650 {
"maninclude", endOfLine },
651 {
"memberof", endOfLabel },
652 {
"mscfile", endOfLine },
653 {
"namespace", endOfLabel },
654 {
"noop", endOfLine },
655 {
"overload", endOfLine },
657 {
"package", endOfLabel },
658 {
"page", endOfLabel },
659 {
"paragraph", endOfLabel },
660 {
"param", endOfParam },
661 {
"property", endOfLine },
662 {
"protocol", endOfLine },
663 {
"qualifier", endOfLine },
664 {
"ref", endOfLabel },
665 {
"refitem", endOfLine },
666 {
"related", endOfLabel },
667 {
"relatedalso", endOfLabel },
668 {
"relates", endOfLabel },
669 {
"relatesalso", endOfLabel },
670 {
"retval", endOfRetVal},
671 {
"rtfinclude", endOfLine },
672 {
"section", endOfLabel },
673 {
"skip", endOfLine },
674 {
"skipline", endOfLine },
675 {
"snippet", endOfLine },
676 {
"snippetdoc", endOfLine },
677 {
"snippetlineno", endOfLine },
678 {
"struct", endOfLine },
679 {
"subpage", endOfLabel },
680 {
"subparagraph", endOfLabel },
681 {
"subsubparagraph",endOfLabel },
682 {
"subsection", endOfLabel },
683 {
"subsubsection", endOfLabel },
684 {
"throw", endOfLabel },
685 {
"throws", endOfLabel },
686 {
"tparam", endOfLabel },
687 {
"typedef", endOfLine },
688 {
"plantumlfile", endOfLine },
689 {
"union", endOfLine },
690 {
"until", endOfLine },
691 {
"var", endOfLine },
692 {
"verbinclude", endOfLine },
693 {
"weakgroup", endOfLabel },
694 {
"xmlinclude", endOfLine },
695 {
"xrefitem", endOfLabel }
698 bool isEscaped = offset>0 && (data.data()[-1]==
'\\' || data.data()[-1]==
'@');
699 if (isEscaped)
return 0;
701 const size_t size = data.size();
703 while (
end<size && (data[
end]>=
'a' && data[
end]<=
'z'))
end++;
704 if (
end==1)
return 0;
705 std::string cmdName(data.substr(1,
end-1));
707 auto it = cmdNames.find(cmdName);
708 if (it!=cmdNames.end())
711 result = it->second(data,
end);
724 const size_t size = data.size();
728 while (i<size && data[i]!=c &&
729 data[i]!=
'\\' && data[i]!=
'@' &&
730 !(data[i]==
'/' && data[i-1]==
'<') &&
750 while (i+len<size && data[i+len]==c)
757 if (len!=c_size || (i+len<size &&
isIdChar(data[i+len])))
763 return static_cast<int>(i);
771 while (i<size && data[i]==
'`') snb++,i++;
775 while (i<size && enb<snb)
777 if (data[i]==
'`') enb++;
778 if (snb==1 && data[i]==
'\'')
break;
782 else if (data[i]==
'@' || data[i]==
'\\')
788 size_t l = endBlockName.
length();
791 if ((data[i]==
'\\' || data[i]==
'@') &&
792 data[i-1]!=
'\\' && data[i-1]!=
'@')
802 else if (i+1<size &&
isIdChar(data[i+1]))
811 else if (data[i-1]==
'<' && data[i]==
'/')
815 else if (data[i]==
'\n')
818 while (i<size && data[i]==
' ') i++;
819 if (i>=size || data[i]==
'\n')
837 const size_t size = data.size();
840 if (size>1 && data[0]==c && data[1]==c) { i=1; }
845 if (len==0) {
return 0; }
847 if (i>=size) {
return 0; }
849 if (i+1<size && data[i+1]==c)
854 if (data[i]==c && data[i-1]!=
' ' && data[i-1]!=
'\n')
860 return static_cast<int>(i+1);
871 const size_t size = data.size();
881 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=
' ' && data[i-1]!=
'\n')
883 if (c ==
'~')
out+=
"<strike>";
884 else out+=
"<strong>";
886 if (c ==
'~')
out+=
"</strike>";
887 else out+=
"</strong>";
889 return static_cast<int>(i+2);
903 const size_t size = data.size();
915 if (data[i]!=c || data[i-1]==
' ' || data[i-1]==
'\n')
920 if (i+2<size && data[i+1]==c && data[i+2]==c)
924 out+=
"</strong></em>";
926 return static_cast<int>(i+3);
928 else if (i+1<size && data[i+1]==c)
939 return static_cast<int>(len - 2);
953 return static_cast<int>(len - 1);
964 const size_t size = data.size();
968 if (i<size && data[i]==
'-')
972 if (i<size && data[i]==
'-')
976 if (i<size && data[i]==
'-')
980 if (count>=2 && offset>=2 &&
literal_at(data.data()-2,
"<!"))
982 if (count==2 && size > 2 && data[2]==
'>')
984 if (count==3 && size > 3 && data[3]==
'>')
986 if (count==2 && (offset<8 || !
literal_at(data.data()-8,
"operator")))
1006 const size_t size = data.size();
1009 while (i<size && data[i]!=
'"' && nl<2)
1011 if (data[i]==
'\n') nl++;
1014 if (i<size && data[i]==
'"' && nl<2)
1016 out+=data.substr(0,i+1);
1018 return static_cast<int>(i+1);
1030 if (offset>0 && data.data()[-1]==
'\\') {
return 0; }
1032 const size_t size = data.size();
1038 while (i<size &&
isIdChar(data[i])) i++,l++;
1039 QCString tagName(data.substr(1,i-1));
1040 if (tagName.
lower()==
"pre")
1042 bool insideStr=
FALSE;
1046 if (!insideStr && c==
'<')
1048 if (data[i+1]==
'/' &&
1049 tolower(data[i+2])==
'p' && tolower(data[i+3])==
'r' &&
1050 tolower(data[i+4])==
'e' && tolower(data[i+5])==
'>')
1052 if (doWrite)
out+=data.substr(0,i+6);
1055 return static_cast<int>(i+6);
1058 else if (insideStr && c==
'"')
1060 if (data[i-1]!=
'\\') insideStr=
FALSE;
1073 if (data[i]==
'/' && i+1<size && data[i+1]==
'>')
1076 if (doWrite)
out+=data.substr(0,i+2);
1078 return static_cast<int>(i+2);
1080 else if (data[i]==
'>')
1083 if (doWrite)
out+=data.substr(0,i+1);
1085 return static_cast<int>(i+1);
1087 else if (data[i]==
' ')
1090 bool insideAttr=
FALSE;
1093 if (!insideAttr && data[i]==
'"')
1097 else if (data[i]==
'"' && data[i-1]!=
'\\')
1101 else if (!insideAttr && data[i]==
'>')
1104 if (doWrite)
out+=data.substr(0,i+1);
1106 return static_cast<int>(i+1);
1126 const size_t size = data.size();
1138 if (size>2 && c!=
'~' && data[1]!=c)
1141 if (data[1]==
' ' || data[1]==
'\n' ||
1149 if (size>3 && data[1]==c && data[2]!=c)
1151 if (data[2]==
' ' || data[2]==
'\n' ||
1159 if (size>4 && c!=
'~' && data[1]==c && data[2]==c && data[3]!=c)
1161 if (data[3]==
' ' || data[3]==
'\n' ||
1173 std::string_view
fmt,
bool inline_img,
bool explicitTitle,
1178 AUTO_TRACE(
"fmt={} inline_img={} explicitTitle={} title={} content={} link={} attrs={}",
1189 out+=link.
mid(fd ? 0 : 5);
1190 if (!explicitTitle && !content.
isEmpty())
1196 else if ((content.
isEmpty() || explicitTitle) && !title.
isEmpty())
1218 const size_t size = data.size();
1222 bool isImageLink =
FALSE;
1223 bool isImageInline =
FALSE;
1229 if (size<2 || data[1]!=
'[')
1238 while (pos>=-
static_cast<int>(offset) && numNLsNeeded>0)
1240 if (data.data()[pos]==
'\n') numNLsNeeded--;
1241 else if (data.data()[pos]!=
' ')
1251 size_t contentStart=i;
1258 if (data[i-1]==
'\\')
1261 else if (data[i]==
'[')
1265 else if (data[i]==
']')
1268 if (level<=0)
break;
1270 else if (data[i]==
'\n')
1273 if (nl>1) {
return 0; }
1279 if (i>=size)
return 0;
1280 size_t contentEnd=i;
1281 content = data.substr(contentStart,contentEnd-contentStart);
1283 if (!isImageLink && content.
isEmpty()) {
return 0; }
1286 bool whiteSpace =
false;
1288 while (i<size && data[i]==
' ') { whiteSpace =
true; i++; }
1289 if (i<size && data[i]==
'\n')
1294 while (i<size && data[i]==
' ') i++;
1296 if (whiteSpace && i<size && (data[i]==
'(' || data[i]==
'['))
return 0;
1298 bool explicitTitle=
FALSE;
1299 if (i<size && data[i]==
'(')
1302 while (i<size && data[i]==
' ') i++;
1303 bool uriFormat=
false;
1304 if (i<size && data[i]==
'<') { i++; uriFormat=
true; }
1307 while (i<size && data[i]!=
'\'' && data[i]!=
'"' && braceCount>0)
1312 if (nl>1) {
return 0; }
1314 else if (data[i]==
'(')
1318 else if (data[i]==
')')
1329 if (i>=size || data[i]==
'\n') {
return 0; }
1330 link = data.substr(linkStart,i-linkStart);
1333 if (link.
isEmpty()) {
return 0; }
1337 if (data[i]==
'\'' || data[i]==
'"')
1341 size_t titleStart=i;
1347 if (nl>1) {
return 0; }
1350 else if (data[i]==
'\\')
1354 else if (data[i]==c)
1365 size_t titleEnd = i-1;
1367 while (titleEnd>titleStart && data[titleEnd]==
' ') titleEnd--;
1368 if (data[titleEnd]==c)
1370 title = data.substr(titleStart,titleEnd-titleStart);
1374 if (data[i]==
' ')i++;
1375 else if (data[i] ==
')')
break;
1389 else if (i<size && data[i]==
'[')
1395 while (i<size && data[i]!=
']')
1400 if (nl>1) {
return 0; }
1404 if (i>=size) {
return 0; }
1406 link = data.substr(linkStart,i-linkStart);
1418 link = lr_it->second.link;
1419 title = lr_it->second.title;
1429 else if (i<size && data[i]!=
':' && !content.
isEmpty())
1436 link = lr_it->second.link;
1437 title = lr_it->second.title;
1441 else if (content==
"TOC")
1464 while (j<size && data[j]==
' ') { j++; }
1465 if (j<size && data[j]==
'{')
1470 size_t attributesStart=i;
1475 if (data[i-1]==
'\\')
1478 else if (data[i]==
'{')
1482 else if (data[i]==
'}')
1485 if (level<=0)
break;
1487 else if (data[i]==
'\n')
1490 if (nl>1) {
return 0; }
1495 if (i>=size)
return 0;
1496 size_t attributesEnd=i;
1497 attributes = data.substr(attributesStart,attributesEnd-attributesStart);
1506 while (pos<size && numNLsNeeded>0)
1508 if (data[pos]==
'\n') numNLsNeeded--;
1509 else if (data[pos]!=
' ')
1524 out+=
"@tableofcontents{html:";
1529 else if (isImageLink)
1533 if (link.
find(
"@ref ")!=-1 || link.
find(
"\\ref ")!=-1 ||
1538 writeMarkdownImage(
"html", isImageInline, explicitTitle, title, content, link, attributes, fd);
1539 writeMarkdownImage(
"latex", isImageInline, explicitTitle, title, content, link, attributes, fd);
1540 writeMarkdownImage(
"rtf", isImageInline, explicitTitle, title, content, link, attributes, fd);
1541 writeMarkdownImage(
"docbook", isImageInline, explicitTitle, title, content, link, attributes, fd);
1542 writeMarkdownImage(
"xml", isImageInline, explicitTitle, title, content, link, attributes, fd);
1564 if ((lp=link.
find(
"@ref "))!=-1 || (lp=link.
find(
"\\ref "))!=-1 || (lang==SrcLangExt::Markdown && !
isURL(link)))
1596 if (explicitTitle && !title.
isEmpty())
1606 else if ((lp=link.
find(
'#'))!=-1 || link.
find(
'/')!=-1 || link.
find(
'.')!=-1)
1608 if (lp==0 || (lp>0 && !
isURL(link) &&
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
1621 for (
int ii = 0; ii < nlTotal; ii++)
out+=
"\n";
1643 return static_cast<int>(i);
1650 const size_t size = data.size();
1654 while (nb<size && data[nb]==
'`')
1671 if (
end+1<size && data[
end+1]==
'`')
1686 if (
end+1<size && data[
end+1]==
'`')
1699 else if (data[
end]==
'\n')
1710 else if (!markdownStrict && data[
end]==
'\'' && nb==1 && (
end+1==size || (
end+1<size && data[
end+1]!=
'\'' && !
isIdChar(data[
end+1]))))
1713 out+=data.substr(nb,
end-nb);
1716 return static_cast<int>(
end+1);
1718 else if (!markdownStrict && data[
end]==
'\'' && nb==2 &&
end+1<size && data[
end+1]==
'\'')
1721 out+=data.substr(nb,
end-nb);
1724 return static_cast<int>(
end+2);
1728 if (data[
end]!=
' ') pc = data[
end];
1732 if (i < nb && end >= size)
1737 out+=data.substr(0,nb);
1742 while (
end<size && data[
end]==
'`')
1752 QCString codeFragment = data.substr(nb,
end-nb-nb);
1758 return static_cast<int>(
end);
1777 const size_t size = data.size();
1783 size_t l = endBlockName.
length();
1786 if ((data[i]==
'\\' || data[i]==
'@') &&
1787 data[i-1]!=
'\\' && data[i-1]!=
'@')
1794 return static_cast<int>(i+1+l);
1803 out+=data.substr(0,endPos);
1804 return static_cast<int>(endPos);
1806 if (size>1 && data[0]==
'\\')
1809 if (c==
'[' || c==
']' || c==
'*' || c==
'(' || c==
')' || c==
'`' || c==
'_')
1815 else if (c==
'\\' || c==
'@')
1817 out+=data.substr(0,2);
1821 else if (c==
'-' && size>3 && data[2]==
'-' && data[3]==
'-')
1823 out+=data.substr(1,3);
1827 else if (c==
'-' && size>2 && data[2]==
'-')
1829 out+=data.substr(1,2);
1834 else if (size>1 && data[0]==
'@')
1837 if (c==
'\\' || c==
'@')
1839 out+=data.substr(0,2);
1853 const size_t size = data.size();
1857 while (
end<size && ((action=
actions[
static_cast<uint8_t
>(data[
end])])==
nullptr))
end++;
1859 out+=data.substr(i,
end-i);
1860 if (
end>=size)
break;
1863 int iend = action(data.substr(i),i);
1881 const size_t size = data.size();
1882 while (i<size && data[i]==
' ') i++;
1883 if (i==size)
return 0;
1888 while (i<size && data[i]==
'=') i++,c++;
1889 while (i<size && data[i]==
' ') i++;
1890 int level = (c>1 && (i>=size || data[i]==
'\n')) ? 1 : 0;
1891 if (allowAdjustLevel && level==1 &&
indentLevel==-1)
1906 while (i<size && data[i]==
'-') i++,c++;
1907 while (i<size && data[i]==
' ') i++;
1908 return (c>1 && (i>=size || data[i]==
'\n')) ?
indentLevel+2 : 0;
1918 const size_t size = data.size();
1919 while (i<size && data[i]==
' ') i++;
1924 while (i<size && (data[i]==
'>' || data[i]==
' '))
1926 if (data[i]==
'>') level++;
1931 bool res = (level>0 && i<size && ((data[i-1]==
' ') || data[i]==
'\n')) || (level > 1);
1946 const size_t size = data.size();
1949 while (i<size && data[i]==
' ') i++;
1950 if (i>=size || data[i]!=
'[') {
return 0; }
1952 size_t refIdStart=i;
1953 while (i<size && data[i]!=
'\n' && data[i]!=
']') i++;
1954 if (i>=size || data[i]!=
']') {
return 0; }
1955 refid = data.substr(refIdStart,i-refIdStart);
1956 if (refid.
isEmpty()) {
return 0; }
1960 if (i>=size || data[i]!=
':') {
return 0; }
1964 while (i<size && data[i]==
' ') i++;
1965 if (i<size && data[i]==
'\n')
1968 while (i<size && data[i]==
' ') i++;
1970 if (i>=size) {
return 0; }
1972 if (i<size && data[i]==
'<') i++;
1974 while (i<size && data[i]!=
' ' && data[i]!=
'\n') i++;
1976 if (i<size && data[i]==
'>') i++;
1977 if (linkStart==linkEnd) {
return 0; }
1978 link = data.substr(linkStart,linkEnd-linkStart);
1980 if (link==
"@ref" || link==
"\\ref")
1983 while (i<size && data[i]!=
'\n' && data[i]!=
'"') i++;
1984 link+=data.substr(argStart,i-argStart);
1991 while (i<size && data[i]==
' ') i++;
1992 if (i<size && data[i]==
'\n')
1996 while (i<size && data[i]==
' ') i++;
2000 AUTO_TRACE_EXIT(
"result={}: end of isLinkRef while looking for title",i);
2005 if (c==
'\'' || c==
'"' || c==
'(')
2010 size_t titleStart=i;
2012 while (i<size && data[i]!=
'\n') i++;
2017 while (
end>titleStart && data[
end]!=c)
end--;
2020 title = data.substr(titleStart,
end-titleStart);
2024 while (i<size && data[i]==
' ') i++;
2036 size_t size = data.size();
2037 if (size>0 && data[size-1]==
'\n') size--;
2038 while (i<size && data[i]==
' ') i++;
2039 if (i>=size) {
AUTO_TRACE_EXIT(
"result=false: empty line");
return false; }
2041 if (c!=
'*' && c!=
'-' && c!=
'_')
2053 else if (data[i]!=
' ')
2068 static const reg::Ex r2(R
"({#(\a[\w-]*)}\s*$)");
2070 std::string ti = title.str();
2073 std::string
id = match[1].str();
2074 title = title.
left(match.position());
2077 warn(
fileName,
lineNr,
"An automatically generated id already has the name '{}'!",
id);
2083 if (((level>0) && (level<=
Config_getInt(TOC_INCLUDE_HEADINGS))) || (
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
2086 if (pIsIdGenerated) *pIsIdGenerated=
true;
2101 int level = 0, blanks=0;
2102 const size_t size = data.size();
2105 while (i<size && data[i]==
' ') i++;
2106 if (i>=size || data[i]!=
'#')
2110 while (i<size && data[i]==
'#') i++,level++;
2115 while (i<size && data[i]==
' ') i++,blanks++;
2116 if (level==1 && blanks==0)
2123 while (
end<size && data[
end]!=
'\n')
end++;
2124 while (
end>i && (data[
end-1]==
'#' || data[
end-1]==
' '))
end--;
2127 header = data.substr(i,
end-i);
2131 int idx=
static_cast<int>(header.
length())-1;
2132 while (idx>=0 && (header.
at(idx)==
'#' || header.
at(idx)==
' ')) idx--;
2133 header=header.
left(idx+1);
2136 if (allowAdjustLevel && level==1 &&
indentLevel==-1)
2167 while (i<data.size())
2178 (data[(i)]=='<' && \
2179 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
2180 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
2189 const size_t size=data.size();
2193 bool listMarkerSkipped=
FALSE;
2196 (!listMarkerSkipped &&
2197 (data[i]==
'+' || data[i]==
'-' || data[i]==
'*' ||
2198 (data[i]==
'#' && i>0 && data[i-1]==
'-') ||
2199 (isDigit=(data[i]>=
'1' && data[i]<=
'9')) ||
2200 (isLi=(size>=3 && i+3<size &&
isLiTag(i)))
2209 while (j<size && ((data[j]>=
'0' && data[j]<=
'9') || data[j]==
'.'))
2213 if (j+1<size && data[j+1]==
' ')
2215 listMarkerSkipped=
TRUE;
2232 listMarkerSkipped=
TRUE;
2234 else if (data[i]==
'-' && size>=2 && i+2<size && data[i+1]==
'#' && data[i+2]==
' ')
2236 listMarkerSkipped=
TRUE;
2240 else if (data[i]!=
' ' && i+1<size && data[i+1]==
' ')
2242 listMarkerSkipped=
TRUE;
2244 if (data[i]!=
' ' && !listMarkerSkipped)
2257 size_t normalIndent = 0;
2258 while (normalIndent<data.size() && data[normalIndent]==
' ') normalIndent++;
2260 size_t result = listIndent>normalIndent ? listIndent : 0;
2271 while (i<data.size())
2277 else if (data[i]==
'\n')
2281 else if (data[i]!=
' ' && data[i]!=
'\t')
2293 QCString &lang,
size_t &start,
size_t &
end,
size_t &offset)
2296 const char dot =
'.';
2297 auto isAlphaChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z'); };
2298 auto isAlphaNChar = [ ](
char c) {
return (c>=
'A' && c<=
'Z') || (c>=
'a' && c<=
'z') || (c>=
'0' && c<=
'9') || (c==
'+'); };
2299 auto isLangChar = [&](
char c) {
return c==dot || isAlphaChar(c); };
2305 const size_t size = data.size();
2306 while (i<size && data[i]==
' ') indent++,i++;
2307 if (indent>=refIndent+4)
2309 AUTO_TRACE_EXIT(
"result=false: content is part of code block indent={} refIndent={}",indent,refIndent);
2313 if (i<size && data[i]==
'`') tildaChar=
'`';
2314 while (i<size && data[i]==tildaChar) startTildes++,i++;
2317 AUTO_TRACE_EXIT(
"result=false: no fence marker found #tildes={}",startTildes);
2320 if (i<size && data[i]==
'{')
2323 if (data[i] == dot) i++;
2325 while (i<size && (data[i]!=
'\n' && data[i]!=
'}')) i++;
2326 if (i<size && data[i]==
'}')
2328 lang = data.substr(startLang,i-startLang);
2337 else if (i<size && isLangChar(data[i]))
2339 if (data[i] == dot) i++;
2341 if (i<size && isAlphaChar(data[i]))
2344 while (i<size && isAlphaNChar(data[i])) i++;
2346 lang = data.substr(startLang,i-startLang);
2356 if (data[i]==tildaChar)
2360 while (i<size && data[i]==tildaChar) endTildes++,i++;
2361 while (i<size && data[i]==
' ') i++;
2363 if (endTildes==startTildes)
2366 AUTO_TRACE_EXIT(
"result=true: found end marker at offset {} lang='{}'",offset,lang);
2377static bool isCodeBlock(std::string_view data,
size_t offset,
size_t &indent)
2384 const size_t size = data.size();
2385 while (i<size && data[i]==
' ') indent0++,i++;
2389 AUTO_TRACE_EXIT(
"result={}: line is not indented enough {}<4",
false,indent0);
2392 if (indent0>=size || data[indent0]==
'\n')
2394 AUTO_TRACE_EXIT(
"result={}: only spaces at the end of a comment block",
false);
2401 int offset_i =
static_cast<int>(offset);
2405 int j =
static_cast<int>(i)-offset_i-1;
2407 size_t nl_size =
isNewline(std::string_view(data.data()+j,data.size()-j));
2410 nl_pos[nl++]=j+
static_cast<int>(nl_size);
2416 if (i==0 && nl==2) nl_pos[nl++]=-offset_i;
2427 if (!
isEmptyLine(std::string_view(data.data()+nl_pos[1],nl_pos[0]-nl_pos[1]-1)))
2436 std::string_view(data.data()+nl_pos[2],nl_pos[1]-nl_pos[2])));
2442 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2449 if (nl==1 && !
isEmptyLine(std::string_view(data.data()-offset,offset-1)))
2457 AUTO_TRACE_EXIT(
"result={}: code block if indent difference >4 spaces",res);
2473 const size_t size = data.size();
2476 while (i<size && data[i]==
' ') i++;
2477 if (i<size && data[i]==
'|' && data[i]!=
'\n') i++,n++;
2482 while (i<size && (j =
isNewline(data.substr(i)))==0) i++;
2485 if (j>0 && i>0) i--;
2486 while (i>0 && data[i]==
' ') i--;
2487 if (i>0 && data[i-1]!=
'\\' && data[i]==
'|') i--,n++;
2497 if (data[i]==
'|' && (i==0 || data[i-1]!=
'\\')) columns++;
2498 if (columns==1) columns++;
2502 if (n==2 && columns==0)
2514 size_t cc0=0, start=0,
end=0;
2518 if (i>=data.size() || cc0<1)
2530 if (data[j]!=
':' && data[j]!=
'-' && data[j]!=
'|' && data[j]!=
' ')
2539 AUTO_TRACE_EXIT(
"result=false: different number of columns as previous line {}!={}",cc1,cc0);
2554 const size_t size = data.size();
2556 size_t columns=0, start=0,
end=0;
2558 size_t headerStart = start;
2559 size_t headerEnd =
end;
2565 std::vector<int> columnAlignment(columns);
2567 bool leftMarker=
false, rightMarker=
false, startFound=
false;
2573 if (data[j]==
':') { leftMarker=
TRUE; startFound=
TRUE; }
2574 if (data[j]==
'-') startFound=
TRUE;
2577 if (data[j]==
'-') rightMarker=
FALSE;
2578 else if (data[j]==
':') rightMarker=
TRUE;
2579 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2603 std::vector<std::vector<TableCell> > tableContents;
2605 size_t m = headerStart;
2606 std::vector<TableCell> headerContents(columns);
2607 for (k=0;k<columns;k++)
2609 while (m<=headerEnd && (data[m]!=
'|' || (m>0 && data[m-1]==
'\\')))
2611 headerContents[k].cellText += data[m++];
2616 headerContents[k].colSpan = headerContents[k].cellText.isEmpty();
2617 headerContents[k].cellText = headerContents[k].cellText.stripWhiteSpace();
2619 tableContents.push_back(headerContents);
2625 if (cc!=columns)
break;
2629 std::vector<TableCell> rowContents(columns);
2632 if (j<=
end+i && (data[j]==
'|' && (j==0 || data[j-1]!=
'\\')))
2636 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2637 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2642 rowContents[k].cellText += data[j];
2648 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2649 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2650 tableContents.push_back(rowContents);
2656 out+=
"<table class=\"markdownTable\">";
2657 QCString cellTag(
"th"), cellClass(
"class=\"markdownTableHead");
2658 for (
size_t row = 0; row < tableContents.size(); row++)
2664 out+=
"\n<tr class=\"markdownTableRowOdd\">";
2668 out+=
"\n<tr class=\"markdownTableRowEven\">";
2673 out+=
"\n <tr class=\"markdownTableHead\">";
2675 for (
size_t c = 0; c < columns; c++)
2678 QCString cellText(tableContents[row][c].cellText);
2684 if (tableContents[row][c].cellText ==
"^")
2688 if (tableContents[row][c].colSpan)
2690 int cr =
static_cast<int>(c);
2691 while ( cr >= 0 && tableContents[row][cr].colSpan)
2695 if (cr >= 0 && tableContents[row][cr].cellText ==
"^")
continue;
2697 size_t rowSpan = 1, spanRow = row+1;
2698 while ((spanRow < tableContents.size()) &&
2699 (tableContents[spanRow][c].cellText ==
"^"))
2705 out+=
" <" + cellTag +
" " + cellClass;
2707 switch (columnAlignment[c])
2719 out+=
" rowspan=\"" + spanStr +
"\"";
2725 while ((c+1 < columns) && tableContents[row][c+1].colSpan)
2734 out+=
" colspan=\"" + spanStr +
"\"";
2738 out+=
"> " + cellText +
" \\ilinebr </" + cellTag +
">";
2741 cellClass =
"class=\"markdownTableBody";
2757 while (i<data.size() && data[i]!=
'\n')
2759 if (data[i]!=
' ' && data[i]!=
'\t') j++;
2762 if (i>=data.size()) {
return 0; }
2763 if (i<2) {
return 0; }
2764 bool res = (j>0 && data[i-1]==
' ' && data[i-2]==
' ');
2804 out+=
"</"+hTag+
">\n";
2807 else if (data.size()>0)
2809 size_t tmpSize = data.size();
2810 if (data[data.size()-1] ==
'\n') tmpSize--;
2811 out+=data.substr(0,tmpSize);
2815 out+=
"\\ilinebr<br>";
2817 if (tmpSize != data.size())
out+=
'\n';
2823 {
"[!note]",
"\\note" },
2824 {
"[!warning]",
"\\warning" },
2825 {
"[!tip]",
"\\remark" },
2826 {
"[!caution]",
"\\attention" },
2827 {
"[!important]",
"\\important" }
2836 const size_t size = data.size();
2837 std::string startCmd;
2838 int isGitHubAlert =
false;
2839 int isGitHubFirst =
false;
2844 while (
end<=size && data[
end-1]!=
'\n')
end++;
2849 while (j<
end && (data[j]==
' ' || data[j]==
'>'))
2851 if (data[j]==
'>') { level++; indent=j+1; }
2852 else if (j>0 && data[j-1]==
'>') indent=j+1;
2855 if (indent>0 && j>0 && data[j-1]==
'>' &&
2856 !(j==size || data[j]==
'\n'))
2873 isGitHubAlert =
true;
2874 isGitHubFirst =
true;
2875 startCmd = it->second;
2880 if (level!=1 || !isGitHubAlert)
2882 for (
int l=curLevel;l<level-1;l++)
2884 out+=
"<blockquote>";
2886 out +=
"<blockquote>‍";
2888 else if (!startCmd.empty())
2890 out += startCmd +
" ";
2893 else if (level<curLevel)
2896 if (level==0 && isGitHubAlert)
2902 out +=
"</blockquote>\\ilinebr ";
2911 if (curLevel!=0 || !isGitHubAlert)
2913 std::string_view txt = data.substr(indent,
end-indent);
2916 if (!isGitHubFirst)
out +=
"<br>";
2923 isGitHubFirst =
false;
2938 for (
int l=0;l<curLevel;l++)
2940 out+=
"</blockquote>";
2951 size_t size = data.size();
2952 while (i<data.size() && data[i]==
' ') i++;
2955 size_t locStart = i;
2956 if (i>offset) locStart--;
2959 while (i+9<size && data[i]!=
'\n')
2971 location=data.substr(locStart,i-locStart);
2973 while (indent>0 && i<size && data[i]==
' ') i++,indent--;
2974 if (i<size && data[i]==
'\n') i++;
2985 const size_t size = data.size();
2988 out+=
"@iverbatim\n";
2990 std::string location;
2995 while (
end<=size && data[
end-1]!=
'\n')
end++;
2998 while (j<
end && data[j]==
' ') j++,indent++;
3008 while (emptyLines>0)
3016 std::string lineLoc;
3021 out+=data.substr(offset,
end-offset);
3029 out+=
"@endiverbatim";
3030 if (!location.empty())
3038 while (emptyLines>0)
3054 const size_t size = data.size();
3055 size_t nb=0,
end=offset+1, j=0;
3060 if ((data[
end-1]==
'\\' || data[
end-1]==
'@') &&
3061 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
3068 size_t l = endBlockName.
length();
3069 for (;
end+l+1<size;
end++)
3071 if ((data[
end]==
'\\' || data[
end]==
'@') &&
3072 data[
end-1]!=
'\\' && data[
end-1]!=
'@'
3086 else if (nb==0 && data[
end-1]==
'<' && size>=6 &&
end+6<size &&
3087 (
end<=1 || (data[
end-2]!=
'\\' && data[
end-2]!=
'@'))
3090 if (tolower(data[
end])==
'p' && tolower(data[
end+1])==
'r' &&
3091 tolower(data[
end+2])==
'e' && (data[
end+3]==
'>' || data[
end+3]==
' '))
3102 else if (nb==0 && data[
end-1]==
'`')
3104 while (
end<=size && data[
end-1]==
'`')
end++,nb++;
3106 else if (nb>0 && data[
end-1]==
'`')
3109 while (
end<=size && data[
end-1]==
'`')
end++,enb++;
3123 size_t blockStart,
size_t blockEnd)
3126 if (!lang.empty() && lang[0]==
'.') lang=lang.substr(1);
3127 const size_t size=data.size();
3129 while (i<size && (data[i]==
' ' || data[i]==
'\t'))
3150 size_t pi=std::string::npos;
3151 bool newBlock =
false;
3152 bool insideList =
false;
3153 size_t currentIndent = refIndent;
3154 size_t listIndent = refIndent;
3155 const size_t size = data.size();
3162 size_t lineIndent=0;
3163 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3169 if (insideList && lineIndent<currentIndent)
3172 currentIndent = refIndent;
3180 if (listIndent<currentIndent+4)
3184 currentIndent = listIndent;
3191 currentIndent = listIndent;
3200 if (pi!=std::string::npos)
3202 size_t blockStart=0, blockEnd=0, blockOffset=0;
3203 if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3205 auto addSpecialCommand = [&](
const QCString &startCmd,
const QCString &endCmd)
3207 size_t cmdPos = pi+blockStart+1;
3208 QCString pl = data.substr(cmdPos,blockEnd-blockStart-1);
3214 if (pl[ii]==
'\n') nl++;
3217 bool addNewLines =
false;
3219 (pl[ii]!=
'\\' && pl[ii]!=
'@') ||
3228 pl =
"@"+startCmd+
"\n" + pl +
"@"+endCmd;
3229 addNewLines =
false;
3244 if (addNewLines)
for (
int j=0;j<nl;j++)
out+=
'\n';
3246 if (addNewLines)
out+=
'\n';
3251 addSpecialCommand(
"startuml",
"enduml");
3255 addSpecialCommand(
"dot",
"enddot");
3257 else if (lang==
"msc")
3259 addSpecialCommand(
"msc",
"endmsc");
3266 pi=std::string::npos;
3270 else if (
isBlockQuote(data.substr(pi,i-pi),currentIndent))
3273 pi=std::string::npos;
3280 out+=data.substr(pi,i-pi);
3286 if (pi!=std::string::npos && pi<size)
3294 out+=data.substr(pi);
3308 size_t pi = std::string::npos;
3324 size_t currentIndent = indent;
3325 size_t listIndent = indent;
3326 bool insideList =
false;
3327 bool newBlock =
false;
3330 while (i<data.size())
3335 size_t lineIndent=0;
3337 while (lineIndent<
end && data[i+lineIndent]==
' ') lineIndent++;
3343 if (insideList && lineIndent<currentIndent)
3346 currentIndent = indent;
3354 if (listIndent<currentIndent+4)
3358 currentIndent = listIndent;
3365 currentIndent = listIndent;
3377 if (pi!=std::string::npos)
3379 size_t blockStart=0, blockEnd=0, blockOffset=0;
3381 size_t blockIndent = currentIndent;
3385 if (data[i]==
'@' || data[i]==
'\\') endBlockName =
isBlockCommand(data.substr(i),i);
3389 if (
isLinkRef(data.substr(pi,i-pi),
id,link,title))
3399 size_t l = endBlockName.
length();
3400 while (i+l<data.size())
3402 if ((data[i]==
'\\' || data[i]==
'@') &&
3403 data[i-1]!=
'\\' && data[i-1]!=
'@')
3420 while (pi<data.size() && data[pi]==
' ') pi++;
3421 QCString header = data.substr(pi,i-pi-1);
3428 out+=level==1?
"@section ":
"@subsection ";
3436 out+=level==1?
"<h1>":
"<h2>";
3438 out+=level==1?
"\n</h1>\n":
"\n</h2>\n";
3445 pi=std::string::npos;
3450 else if ((ref=
isLinkRef(data.substr(pi),
id,link,title)))
3458 else if (
isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3464 pi=std::string::npos;
3472 pi=std::string::npos;
3479 pi=std::string::npos;
3492 if (pi!=std::string::npos && pi<data.size())
3494 if (
isLinkRef(data.substr(pi),
id,link,title))
3511#define OPC(x) if (literal_at(data,#x " ") || literal_at(data,#x "\n")) return true
3512 OPC(dir);
OPC(defgroup);
OPC(addtogroup);
OPC(weakgroup);
OPC(ingroup);
3515 OPC(protocol);
OPC(category);
OPC(
union);
OPC(
struct);
OPC(interface);
3516 OPC(idlexcept);
OPC(file);
3526 std::string_view data(docs.
str());
3527 const size_t size = data.size();
3530 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3537 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3543 (data[i]==
'\\' || data[i]==
'@') &&
3558 else if (i+1<size && (data[i]==
'\\' || data[i]==
'@') &&
isOtherPage(data.substr(i+1)))
3576 std::string_view data(docs_org.
str());
3577 const size_t size = data.size();
3579 while (i<size && (data[i]==
' ' || data[i]==
'\n'))
3581 if (data[i]==
'\n') prepend++;
3584 if (i>=size) {
return QCString(); }
3586 while (end1<size && data[end1-1]!=
'\n') end1++;
3593 while (end2<size && data[end2-1]!=
'\n') end2++;
3594 if (
prv->isHeaderline(data.substr(end1),
FALSE))
3596 title = data.substr(i,end1-i-1);
3597 docs+=
"\n\n"+docs_org.
mid(end2);
3598 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3604 if (i<end1 && prv->isAtxHeader(data.substr(i,end1-i),title,
id,
FALSE,&isIdGenerated)>0)
3607 docs+=docs_org.
mid(end1);
3612 id =
prv->extractTitleId(title, 0, &isIdGenerated);
3623 if (input.
isEmpty())
return input;
3628 if (s.
at(s.
length()-1)!=
'\n') s +=
"\n";
3629 s =
detab(s,refIndent);
3633 s =
prv->processQuotations(s.
view(),refIndent);
3637 s =
prv->processBlocks(s.
view(),refIndent);
3655 const char *p = result.
data();
3658 while (*p==
' ') p++;
3659 while (*p==
'\n') {startNewlines++;p++;};
3662 if (p>result.
data())
3665 result = result.
mid(
static_cast<int>(p-result.
data()));
3678 if (i!=-1) baseFn = baseFn.
left(i);
3702 const char *fileBuf,
3703 const std::shared_ptr<Entry> &root,
3706 std::shared_ptr<Entry> current = std::make_shared<Entry>();
3708 current->lang = SrcLangExt::Markdown;
3709 current->fileName = fileName;
3710 current->docFile = fileName;
3711 current->docLine = 1;
3717 bool isIdGenerated =
false;
3725 int indentLevel=title.
isEmpty() ? 0 : -1;
3732 bool wasEmpty =
id.isEmpty();
3733 if (wasEmpty)
id = mdFileNameId;
3739 if (!mdfileAsMainPage.
isEmpty() &&
3743 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3744 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3746 else if (
id==
"mainpage" ||
id==
"index")
3748 if (title.
isEmpty()) title = titleFn;
3749 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr ");
3750 docs.
prepend(
"@mainpage "+title+
"\\ilinebr ");
3752 else if (isSubdirDocs)
3756 docs.
prepend(
"@section " + generatedId +
" " + title +
"\\ilinebr ");
3758 docs.
prepend(
"@dir\\ilinebr ");
3769 docs.
prepend(
"@ianchor{" + title +
"} " +
id +
"\\ilinebr @ianchor{" + relFileName +
"} " + mdFileNameId +
"\\ilinebr ");
3771 else if (!generatedId.
isEmpty())
3773 docs.
prepend(
"@ianchor " + generatedId +
"\\ilinebr ");
3775 else if (
Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB)
3778 docs.
prepend(
"@ianchor{" + title +
"} " + autoId +
"\\ilinebr ");
3780 docs.
prepend(
"@page "+
id+
" "+title+
"\\ilinebr ");
3782 for (
int i = 0; i < prepend; i++) docs.
prepend(
"\n");
3787 static const reg::Ex re(R
"([ ]*[\\@]page\s+(\a[\w-]*)(\s*[^\n]*)\n)");
3789 std::string s = docs.str();
3792 QCString orgLabel = match[1].str();
3793 QCString orgTitle = match[2].str();
3796 docs = docs.
left(match[1].position())+
3799 "\\ilinebr @ianchor{" + orgTitle +
"} "+orgLabel+
"\n"+
3811 p->commentScanner.enterFile(fileName,lineNr);
3813 bool needsEntry =
false;
3817 while (
p->commentScanner.parseCommentBlock(
3835 QCString docFile = current->docFile;
3836 root->moveToSubEntryAndRefresh(current);
3837 current->lang = SrcLangExt::Markdown;
3838 current->docFile = docFile;
3839 current->docLine = lineNr;
3844 root->moveToSubEntryAndKeep(current);
3846 p->commentScanner.leaveFile(fileName,lineNr);
#define eol
The end of line string for this machine.
static AnchorGenerator & instance()
Returns the singleton instance.
static std::string addPrefixIfNeeded(const std::string &anchor)
std::string generate(const std::string &title)
generates an anchor for a section with title.
Clang parser object for a single translation unit, which consists of a source file and the directly o...
static void print(DebugMask mask, int prio, fmt::format_string< Args... > fmt, Args &&... args)
static ParserManager * parserManager
static FileNameLinkedMap * imageNameLinkedMap
A model of a file symbol.
Minimal replacement for QFileInfo.
std::string fileName() const
std::string absFilePath() const
Helper class to process markdown formatted text.
std::unique_ptr< Private > prv
void setIndentLevel(int level)
QCString extractPageTitle(QCString &docs, QCString &id, int &prepend, bool &isIdGenerated)
Markdown(const QCString &fileName, int lineNr, int indentLevel=0)
QCString process(const QCString &input, int &startNewlines, bool fromParseInput=false)
void parseInput(const QCString &fileName, const char *fileBuf, const std::shared_ptr< Entry > &root, ClangTUParser *clangParser) override
Parses a single input file with the goal to build an Entry tree.
~MarkdownOutlineParser() override
void parsePrototype(const QCString &text) override
Callback function called by the comment block scanner.
std::unique_ptr< Private > p
This is an alternative implementation of QCString.
int find(char c, int index=0, bool cs=TRUE) const
QCString & prepend(const char *s)
size_t length() const
Returns the length of the string, not counting the 0-terminator.
QCString mid(size_t index, size_t len=static_cast< size_t >(-1)) const
bool endsWith(const char *s) const
char & at(size_t i)
Returns a reference to the character at index i.
bool isEmpty() const
Returns TRUE iff the string is empty.
QCString stripWhiteSpace() const
returns a copy of this string with leading and trailing whitespace removed
const std::string & str() const
QCString & setNum(short n)
QCString simplifyWhiteSpace() const
return a copy of this string with leading and trailing whitespace removed and multiple whitespace cha...
QCString right(size_t len) const
size_t size() const
Returns the length of the string, not counting the 0-terminator.
QCString & sprintf(const char *format,...)
int findRev(char c, int index=-1, bool cs=TRUE) const
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
std::string_view view() const
QCString left(size_t len) const
static constexpr int Section
static constexpr int MaxLevel
static constexpr int Subsection
static constexpr int Subsubsection
static constexpr int MinLevel
static constexpr int Paragraph
static constexpr int Subsubparagraph
static constexpr int Subparagraph
Class representing a regular expression.
Object representing the matching results.
#define Config_getInt(name)
#define Config_getBool(name)
#define Config_getString(name)
#define Config_getEnum(name)
std::vector< std::string > StringVector
DirIterator end(const DirIterator &) noexcept
#define AUTO_TRACE_ADD(...)
#define AUTO_TRACE_EXIT(...)
static bool isOtherPage(std::string_view data)
static bool hasLineBreak(std::string_view data)
@ explicitMainPage
docs start with a mainpage command
@ explicitPage
docs start with a page command
@ notExplicit
docs doesn't start with either page or mainpage
@ explicitOtherPage
docs start with a dir / defgroup / addtogroup command
static bool isBlockQuote(std::string_view data, size_t indent)
returns true if this line starts a block quote
static size_t isLinkRef(std::string_view data, QCString &refid, QCString &link, QCString &title)
returns end of the link ref if this is indeed a link reference.
static QCString escapeDoubleQuotes(const QCString &s)
static bool isEndOfList(std::string_view data)
static size_t computeIndentExcludingListMarkers(std::string_view data)
static Alignment markersToAlignment(bool leftMarker, bool rightMarker)
helper function to convert presence of left and/or right alignment markers to an alignment value
static QCString escapeSpecialChars(const QCString &s)
static bool isCodeBlock(std::string_view data, size_t offset, size_t &indent)
static bool isEmptyLine(std::string_view data)
#define AUTO_TRACE_EXIT(...)
static size_t findTableColumns(std::string_view data, size_t &start, size_t &end, size_t &columns)
Finds the location of the table's contains in the string data.
const size_t codeBlockIndent
static ExplicitPageResult isExplicitPage(const QCString &docs)
#define ignoreCloseEmphChar(c, cn)
static const std::unordered_map< std::string, std::string > g_quotationHeaderMap
static bool isFencedCodeBlock(std::string_view data, size_t refIndent, QCString &lang, size_t &start, size_t &end, size_t &offset)
static size_t isListMarker(std::string_view data)
static bool isHRuler(std::string_view data)
static QCString getFilteredImageAttributes(std::string_view fmt, const QCString &attrs)
parse the image attributes and return attributes for given format
bool skipOverFileAndLineCommands(std::string_view data, size_t indent, size_t &offset, std::string &location)
#define isAllowedEmphStr(data, offset)
static bool isTableBlock(std::string_view data)
Returns TRUE iff data points to the start of a table block.
size_t isNewline(std::string_view data)
QCString markdownFileNameToId(const QCString &fileName)
processes string s and converts markdown into doxygen/html commands.
#define warn(file, line, fmt,...)
bool isAbsolutePath(const QCString &fileName)
const char * strnstr(const char *haystack, const char *needle, size_t haystack_len)
QCString trunc(const QCString &s, size_t numChars=15)
bool search(std::string_view str, Match &match, const Ex &re, size_t pos)
Search in a given string str starting at position pos for a match against regular expression re.
Portable versions of functions that are platform dependent.
static void decrLevel(yyscan_t yyscanner)
QCString substitute(const QCString &s, const QCString &src, const QCString &dst)
substitute all occurrences of src in s by dst
int qstrncmp(const char *str1, const char *str2, size_t len)
const char * qPrint(const char *s)
Some helper functions for std::string.
bool literal_at(const char *data, const char(&str)[N])
returns TRUE iff data points to a substring that matches string literal str
std::string_view stripWhiteSpace(std::string_view s)
Given a string view s, returns a new, narrower view on that string, skipping over any leading or trai...
LinkRef(const QCString &l, const QCString &t)
int processEmphasis1(std::string_view data, char c)
process single emphasis
int processQuoted(std::string_view data, size_t offset)
Process quoted section "...", can contain one embedded newline.
void writeMarkdownImage(std::string_view fmt, bool inline_img, bool explicitTitle, const QCString &title, const QCString &content, const QCString &link, const QCString &attributes, const FileDef *fd)
size_t writeTableBlock(std::string_view data)
size_t writeBlockQuote(std::string_view data)
size_t isSpecialCommand(std::string_view data, size_t offset)
std::function< int(std::string_view, size_t)> Action_t
int processEmphasis3(std::string_view data, char c)
Parsing triple emphasis.
int processCodeSpan(std::string_view data, size_t offset)
` parsing a code span (assuming codespan != 0)
int processSpecialCommand(std::string_view data, size_t offset)
QCString extractTitleId(QCString &title, int level, bool *pIsIdGenerated=nullptr)
void writeFencedCodeBlock(std::string_view data, std::string_view lang, size_t blockStart, size_t blockEnd)
int isHeaderline(std::string_view data, bool allowAdjustLevel)
returns whether the line is a setext-style hdr underline
size_t findEmphasisChar(std::string_view, char c, size_t c_size)
looks for the next emph char, skipping other constructs, and stopping when either it is found,...
std::unordered_map< std::string, LinkRef > linkRefs
void addStrEscapeUtf8Nbsp(std::string_view data)
QCString isBlockCommand(std::string_view data, size_t offset)
size_t writeCodeBlock(std::string_view, size_t refIndent)
int processHtmlTag(std::string_view data, size_t offset)
QCString processQuotations(std::string_view data, size_t refIndent)
QCString processBlocks(std::string_view data, size_t indent)
int processEmphasis(std::string_view data, size_t offset)
int processLink(std::string_view data, size_t offset)
int processHtmlTagWrite(std::string_view data, size_t offset, bool doWrite)
Process a HTML tag.
int isAtxHeader(std::string_view data, QCString &header, QCString &id, bool allowAdjustLevel, bool *pIsIdGenerated=nullptr)
size_t findEndOfLine(std::string_view data, size_t offset)
int processEmphasis2(std::string_view data, char c)
process double emphasis
void processInline(std::string_view data)
int processNmdash(std::string_view data, size_t offset)
Process ndash and mdashes.
void writeOneLineHeaderOrRuler(std::string_view data)
std::array< Action_t, 256 > actions
CommentScanner commentScanner
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
QCString stripIndentation(const QCString &s, bool skipFirstLine)
QCString escapeCharsInString(const QCString &name, bool allowDots, bool allowUnderscore)
QCString stripExtensionGeneral(const QCString &fName, const QCString &ext)
bool isURL(const QCString &url)
Checks whether the given url starts with a supported protocol.
static QCString stripFromPath(const QCString &p, const StringVector &l)
QCString detab(const QCString &s, size_t &refIndent)
StringVector split(const std::string &s, const std::string &delimiter)
split input string s by string delimiter delimiter.
QCString externalLinkTarget(const bool parent)
QCString getFileNameExtension(const QCString &fn)
FileDef * findFileDef(const FileNameLinkedMap *fnMap, const QCString &n, bool &ambig)
A bunch of utility functions.