Doxygen
Loading...
Searching...
No Matches
markdown.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2020 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16/* Note: part of the code below is inspired by libupskirt written by
17 * Natacha Porté. Original copyright message follows:
18 *
19 * Copyright (c) 2008, Natacha Porté
20 *
21 * Permission to use, copy, modify, and distribute this software for any
22 * purpose with or without fee is hereby granted, provided that the above
23 * copyright notice and this permission notice appear in all copies.
24 *
25 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32 */
33
34#include <stdio.h>
35
36#include <unordered_map>
37#include <functional>
38#include <atomic>
39#include <array>
40#include <string_view>
41
42#include "markdown.h"
43#include "debug.h"
44#include "util.h"
45#include "doxygen.h"
46#include "commentscan.h"
47#include "entry.h"
48#include "config.h"
49#include "message.h"
50#include "portable.h"
51#include "regex.h"
52#include "fileinfo.h"
53#include "trace.h"
54#include "anchor.h"
55#include "stringutil.h"
56
57#if !ENABLE_MARKDOWN_TRACING
58#undef AUTO_TRACE
59#undef AUTO_TRACE_ADD
60#undef AUTO_TRACE_EXIT
61#define AUTO_TRACE(...) (void)0
62#define AUTO_TRACE_ADD(...) (void)0
63#define AUTO_TRACE_EXIT(...) (void)0
64#endif
65
67{
68 explicitPage, /**< docs start with a page command */
69 explicitMainPage, /**< docs start with a mainpage command */
70 explicitOtherPage, /**< docs start with a dir / defgroup / addtogroup command */
71 notExplicit /**< docs doesn't start with either page or mainpage */
72};
73
74//-----------
75
76// is character c part of an identifier?
77#define isIdChar(c) \
78 ((c>='a' && c<='z') || \
79 (c>='A' && c<='Z') || \
80 (c>='0' && c<='9') || \
81 (static_cast<unsigned char>(c)>=0x80)) // unicode characters
82
83// is character allowed right at the beginning of an emphasis section
84#define extraChar(c) \
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=='`')
91
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==';')
97
98// test for non breakable space (UTF-8)
99#define isUtf8Nbsp(c1,c2) \
100 (c1==static_cast<char>(0xc2) && c2==static_cast<char>(0xa0))
101
102#define isAllowedEmphStr(data,offset) \
103 (!(offset>0 && isOpenEmphChar(data.data()[-1])) && \
104 !(offset>1 && isUtf8Nbsp(data.data()[-2],data.data()[-1])))
105
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!='/') || \
110 c=='\\' || \
111 c=='@')
112//----------
113
114struct TableCell
115{
116 TableCell() : colSpan(false) {}
117 QCString cellText;
118 bool colSpan;
119};
120
121struct Markdown::Private
122{
123 Private(const QCString &fn,int line,int indent)
124 : fileName(fn), lineNr(line), indentLevel(indent)
125 {
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); };
138 }
139
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,
160 const QCString &title, const QCString &content,
161 const QCString &link, const QCString &attributes,
162 const FileDef *fd);
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);
173
174 struct LinkRef
175 {
176 LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
179 };
180 using Action_t = std::function<int(std::string_view,size_t)>;
181
182 std::unordered_map<std::string,LinkRef> linkRefs;
184 int lineNr = 0;
185 int indentLevel=0; // 0 is outside markdown, -1=page level
187 std::array<Action_t,256> actions;
188};
189
190Markdown::Markdown(const QCString &fileName,int lineNr,int indentLevel)
191 : prv(std::make_unique<Private>(fileName,lineNr,indentLevel))
192{
193 using namespace std::placeholders;
194 (void)lineNr; // not used yet
195}
196
197Markdown::~Markdown() = default;
198
199void Markdown::setIndentLevel(int level) { prv->indentLevel = level; }
200
201
203
204
205//---------- constants -------
206//
207const char *g_utf8_nbsp = "\xc2\xa0"; // UTF-8 nbsp
208const char *g_doxy_nbsp = "&_doxy_nbsp;"; // doxygen escape command for UTF-8 nbsp
209const size_t codeBlockIndent = 4;
210
211//---------- helpers -------
212
213// test if the next characters in data represent a new line (which can be character \n or string \ilinebr).
214// returns 0 if no newline is found, or the number of characters that make up the newline if found.
215inline size_t isNewline(std::string_view data)
216{
217 // normal newline
218 if (data[0] == '\n') return 1;
219 // artificial new line from ^^ in ALIASES
220 if (literal_at(data,"\\ilinebr"))
221 {
222 return (data.size()>8 && data[8]==' ') ? 9 : 8; // also count space after \ilinebr if present
223 }
224 return 0;
225}
226
227// escape double quotes in string
229{
230 AUTO_TRACE("s={}",Trace::trunc(s));
231 if (s.isEmpty()) return s;
232 QCString result;
233 const char *p=s.data();
234 char c=0, pc='\0';
235 while ((c=*p++))
236 {
237 if (c=='"' && pc!='\\') result+='\\';
238 result+=c;
239 pc=c;
240 }
241 AUTO_TRACE_EXIT("result={}",result);
242 return result;
243}
244
245// escape characters that have a special meaning later on.
247{
248 AUTO_TRACE("s={}",Trace::trunc(s));
249 if (s.isEmpty()) return s;
250 bool insideQuote=FALSE;
251 QCString result;
252 const char *p=s.data();
253 char c=0, pc='\0';
254 while ((c=*p++))
255 {
256 switch (c)
257 {
258 case '"':
259 if (pc!='\\')
260 {
261 if (Config_getBool(MARKDOWN_STRICT))
262 {
263 result+='\\';
264 }
265 else // For Doxygen's markup style a quoted text is left untouched
266 {
267 insideQuote=!insideQuote;
268 }
269 }
270 result+=c;
271 break;
272 case '<':
273 // fall through
274 case '>':
275 if (!insideQuote)
276 {
277 result+='\\';
278 result+=c;
279 if ((p[0]==':') && (p[1]==':'))
280 {
281 result+='\\';
282 result+=':';
283 p++;
284 }
285 }
286 else
287 {
288 result+=c;
289 }
290 break;
291 case '\\': if (!insideQuote) { result+='\\'; } result+='\\'; break;
292 case '@': if (!insideQuote) { result+='\\'; } result+='@'; break;
293 // commented out next line due to regression when using % to suppress a link
294 //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;
298 default:
299 result+=c; break;
300 }
301 pc=c;
302 }
303 AUTO_TRACE_EXIT("result={}",result);
304 return result;
305}
306
307/** helper function to convert presence of left and/or right alignment markers
308 * to an alignment value
309 */
310static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
311{
312 if (leftMarker && rightMarker)
313 {
314 return AlignCenter;
315 }
316 else if (leftMarker)
317 {
318 return AlignLeft;
319 }
320 else if (rightMarker)
321 {
322 return AlignRight;
323 }
324 else
325 {
326 return AlignNone;
327 }
328}
329
330/** parse the image attributes and return attributes for given format */
331static QCString getFilteredImageAttributes(std::string_view fmt, const QCString &attrs)
332{
333 AUTO_TRACE("fmt={} attrs={}",fmt,attrs);
334 StringVector attrList = split(attrs.str(),",");
335 for (const auto &attr_ : attrList)
336 {
337 QCString attr = QCString(attr_).stripWhiteSpace();
338 int i = attr.find(':');
339 if (i>0) // has format
340 {
341 QCString format = attr.left(i).stripWhiteSpace().lower();
342 if (format == fmt) // matching format
343 {
344 AUTO_TRACE_EXIT("result={}",attr.mid(i+1));
345 return attr.mid(i+1); // keep part after :
346 }
347 }
348 else // option that applies to all formats
349 {
350 AUTO_TRACE_EXIT("result={}",attr);
351 return attr;
352 }
353 }
354 return QCString();
355}
356
357// Check if data contains a block command. If so returned the command
358// that ends the block. If not an empty string is returned.
359// Note When offset>0 character position -1 will be inspected.
360//
361// Checks for and skip the following block commands:
362// {@code .. { .. } .. }
363// \dot .. \enddot
364// \code .. \endcode
365// \msc .. \endmsc
366// \f$..\f$
367// \f(..\f)
368// \f[..\f]
369// \f{..\f}
370// \verbatim..\endverbatim
371// \iliteral..\endiliteral
372// \latexonly..\endlatexonly
373// \htmlonly..\endhtmlonly
374// \xmlonly..\endxmlonly
375// \rtfonly..\endrtfonly
376// \manonly..\endmanonly
377// \startuml..\enduml
378QCString Markdown::Private::isBlockCommand(std::string_view data,size_t offset)
379{
380 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
381
382 using EndBlockFunc = QCString (*)(const std::string &,bool,char);
383
384 static const auto getEndBlock = [](const std::string &blockName,bool,char) -> QCString
385 {
386 return "end"+blockName;
387 };
388 static const auto getEndCode = [](const std::string &blockName,bool openBracket,char) -> QCString
389 {
390 return openBracket ? QCString("}") : "end"+blockName;
391 };
392 static const auto getEndUml = [](const std::string &/* blockName */,bool,char) -> QCString
393 {
394 return "enduml";
395 };
396 static const auto getEndFormula = [](const std::string &/* blockName */,bool,char nextChar) -> QCString
397 {
398 switch (nextChar)
399 {
400 case '$': return "f$";
401 case '(': return "f)";
402 case '[': return "f]";
403 case '{': return "f}";
404 }
405 return "";
406 };
407
408 // table mapping a block start command to a function that can return the matching end block string
409 static const std::unordered_map<std::string,EndBlockFunc> blockNames =
410 {
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 }
426 };
427
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]=='@');
431 if (isEscaped) return QCString();
432
433 size_t end=1;
434 while (end<size && (data[end]>='a' && data[end]<='z')) end++;
435 if (end==1) return QCString();
436 std::string blockName(data.substr(1,end-1));
437 auto it = blockNames.find(blockName);
438 QCString result;
439 if (it!=blockNames.end()) // there is a function assigned
440 {
441 result = it->second(blockName, openBracket, end<size ? data[end] : 0);
442 }
443 AUTO_TRACE_EXIT("result={}",result);
444 return result;
445}
446
447size_t Markdown::Private::isSpecialCommand(std::string_view data,size_t offset)
448{
449 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
450
451 using EndCmdFunc = size_t (*)(std::string_view,size_t);
452
453 static const auto endOfLine = [](std::string_view data_,size_t offset_) -> size_t
454 {
455 // skip until the end of line (allowing line continuation characters)
456 char lc = 0;
457 char c = 0;
458 while (offset_<data_.size() && ((c=data_[offset_])!='\n' || lc=='\\'))
459 {
460 if (c=='\\') lc='\\'; // last character was a line continuation
461 else if (c!=' ') lc=0; // rest line continuation
462 offset_++;
463 }
464 return offset_;
465 };
466
467 static const auto endOfLabels = [](std::string_view data_,size_t offset_,bool multi_) -> size_t
468 {
469 if (offset_<data_.size() && data_[offset_]==' ') // we expect a space before the label
470 {
471 char c = 0;
472 offset_++;
473 bool done=false;
474 while (!done)
475 {
476 // skip over spaces
477 while (offset_<data_.size() && data_[offset_]==' ')
478 {
479 offset_++;
480 }
481 // skip over label
482 while (offset_<data_.size() && (c=data_[offset_])!=' ' && c!=',' && c!='\\' && c!='@' && c!='\n')
483 {
484 offset_++;
485 }
486 // optionally skip over a comma separated list of labels
487 if (multi_ && offset_<data_.size() && (data_[offset_]==',' || data_[offset_]==' '))
488 {
489 size_t off = offset_;
490 while (off<data_.size() && data_[off]==' ')
491 {
492 off++;
493 }
494 if (off<data_.size() && data_[off]==',')
495 {
496 offset_ = ++off;
497 }
498 else // no next label found
499 {
500 done=true;
501 }
502 }
503 else
504 {
505 done=true;
506 }
507 }
508 return offset_;
509 }
510 return 0;
511 };
512
513 static const auto endOfLabel = [](std::string_view data_,size_t offset_) -> size_t
514 {
515 return endOfLabels(data_,offset_,false);
516 };
517
518 static const auto endOfLabelOpt = [](std::string_view data_,size_t offset_) -> size_t
519 {
520 size_t index=offset_;
521 if (index<data_.size() && data_[index]==' ') // skip over optional spaces
522 {
523 index++;
524 while (index<data_.size() && data_[index]==' ') index++;
525 }
526 if (index<data_.size() && data_[index]=='{') // find matching '}'
527 {
528 index++;
529 char c = 0;
530 while (index<data_.size() && (c=data_[index])!='}' && c!='\\' && c!='@' && c!='\n') index++;
531 if (index==data_.size() || data_[index]!='}') return 0; // invalid option
532 offset_=index+1; // part after {...} is the option
533 }
534 return endOfLabel(data_,offset_);
535 };
536
537 static const auto endOfParam = [](std::string_view data_,size_t offset_) -> size_t
538 {
539 size_t index=offset_;
540 if (index<data_.size() && data_[index]==' ') // skip over optional spaces
541 {
542 index++;
543 while (index<data_.size() && data_[index]==' ') index++;
544 }
545 if (index<data_.size() && data_[index]=='[') // find matching ']'
546 {
547 index++;
548 char c = 0;
549 while (index<data_.size() && (c=data_[index])!=']' && c!='\n') index++;
550 if (index==data_.size() || data_[index]!=']') return 0; // invalid parameter
551 offset_=index+1; // part after [...] is the parameter name
552 }
553 return endOfLabels(data_,offset_,true);
554 };
555
556 static const auto endOfRetVal = [](std::string_view data_,size_t offset_) -> size_t
557 {
558 return endOfLabels(data_,offset_,true);
559 };
560
561 static const auto endOfFuncLike = [](std::string_view data_,size_t offset_,bool allowSpaces) -> size_t
562 {
563 if (offset_<data_.size() && data_[offset_]==' ') // we expect a space before the name
564 {
565 char c=0;
566 offset_++;
567 // skip over spaces
568 while (offset_<data_.size() && data_[offset_]==' ')
569 {
570 offset_++;
571 }
572 // skip over name (and optionally type)
573 while (offset_<data_.size() && (c=data_[offset_])!='\n' && (allowSpaces || c!=' ') && c!='(')
574 {
575 if (literal_at(data_.substr(offset_),"\\ilinebr ")) break;
576 offset_++;
577 }
578 if (c=='(') // find the end of the function
579 {
580 int count=1;
581 offset_++;
582 while (offset_<data_.size() && (c=data_[offset_++]))
583 {
584 if (c=='(') count++;
585 else if (c==')') count--;
586 if (count==0) return offset_;
587 }
588 }
589 return offset_;
590 }
591 return 0;
592 };
593
594 static const auto endOfFunc = [](std::string_view data_,size_t offset_) -> size_t
595 {
596 return endOfFuncLike(data_,offset_,true);
597 };
598
599 static const auto endOfGuard = [](std::string_view data_,size_t offset_) -> size_t
600 {
601 return endOfFuncLike(data_,offset_,false);
602 };
603
604 static const std::unordered_map<std::string,EndCmdFunc> cmdNames =
605 {
606 { "a", endOfLabel },
607 { "addindex", endOfLine },
608 { "addtogroup", endOfLabel },
609 { "anchor", endOfLabel },
610 { "b", endOfLabel },
611 { "c", 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 },
626 { "e", endOfLabel },
627 { "elseif", endOfGuard },
628 { "em", endOfLabel },
629 { "emoji", endOfLabel },
630 { "enum", endOfLabel },
631 { "example", endOfLine },
632 { "exception", endOfLine },
633 { "extends", endOfLabel },
634 { "file", endOfLine },
635 { "fn", endOfFunc },
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 },
656 { "p", endOfLabel },
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 }
696 };
697
698 bool isEscaped = offset>0 && (data.data()[-1]=='\\' || data.data()[-1]=='@');
699 if (isEscaped) return 0;
700
701 const size_t size = data.size();
702 size_t end=1;
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));
706 size_t result=0;
707 auto it = cmdNames.find(cmdName);
708 if (it!=cmdNames.end()) // command with parameters that should be ignored by markdown
709 {
710 // find the end of the parameters
711 result = it->second(data,end);
712 }
713 AUTO_TRACE_EXIT("result={}",result);
714 return result;
715}
716
717/** looks for the next emph char, skipping other constructs, and
718 * stopping when either it is found, or we are at the end of a paragraph.
719 */
720size_t Markdown::Private::findEmphasisChar(std::string_view data, char c, size_t c_size)
721{
722 AUTO_TRACE("data='{}' c={} c_size={}",Trace::trunc(data),c,c_size);
723 size_t i = 1;
724 const size_t size = data.size();
725
726 while (i<size)
727 {
728 while (i<size && data[i]!=c &&
729 data[i]!='\\' && data[i]!='@' &&
730 !(data[i]=='/' && data[i-1]=='<') && // html end tag also ends emphasis
731 data[i]!='\n') i++;
732 // avoid overflow (unclosed emph token)
733 if (i==size)
734 {
735 return 0;
736 }
737 //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
738
739 // not counting escaped chars or characters that are unlikely
740 // to appear as the end of the emphasis char
741 if (ignoreCloseEmphChar(data[i-1],data[i]))
742 {
743 i++;
744 continue;
745 }
746 else
747 {
748 // get length of emphasis token
749 size_t len = 0;
750 while (i+len<size && data[i+len]==c)
751 {
752 len++;
753 }
754
755 if (len>0)
756 {
757 if (len!=c_size || (i+len<size && isIdChar(data[i+len]))) // to prevent touching some_underscore_identifier
758 {
759 i+=len;
760 continue;
761 }
762 AUTO_TRACE_EXIT("result={}",i);
763 return static_cast<int>(i); // found it
764 }
765 }
766
767 // skipping a code span
768 if (data[i]=='`')
769 {
770 int snb=0;
771 while (i<size && data[i]=='`') snb++,i++;
772
773 // find same pattern to end the span
774 int enb=0;
775 while (i<size && enb<snb)
776 {
777 if (data[i]=='`') enb++;
778 if (snb==1 && data[i]=='\'') break; // ` ended by '
779 i++;
780 }
781 }
782 else if (data[i]=='@' || data[i]=='\\')
783 { // skip over blocks that should not be processed
784 QCString endBlockName = isBlockCommand(data.substr(i),i);
785 if (!endBlockName.isEmpty())
786 {
787 i++;
788 size_t l = endBlockName.length();
789 while (i+l<size)
790 {
791 if ((data[i]=='\\' || data[i]=='@') && // command
792 data[i-1]!='\\' && data[i-1]!='@') // not escaped
793 {
794 if (qstrncmp(&data[i+1],endBlockName.data(),l)==0)
795 {
796 break;
797 }
798 }
799 i++;
800 }
801 }
802 else if (i+1<size && isIdChar(data[i+1])) // @cmd, stop processing, see bug 690385
803 {
804 return 0;
805 }
806 else
807 {
808 i++;
809 }
810 }
811 else if (data[i-1]=='<' && data[i]=='/') // html end tag invalidates emphasis
812 {
813 return 0;
814 }
815 else if (data[i]=='\n') // end * or _ at paragraph boundary
816 {
817 i++;
818 while (i<size && data[i]==' ') i++;
819 if (i>=size || data[i]=='\n')
820 {
821 return 0;
822 } // empty line -> paragraph
823 }
824 else // should not get here!
825 {
826 i++;
827 }
828 }
829 return 0;
830}
831
832/** process single emphasis */
833int Markdown::Private::processEmphasis1(std::string_view data, char c)
834{
835 AUTO_TRACE("data='{}' c={}",Trace::trunc(data),c);
836 size_t i = 0;
837 const size_t size = data.size();
838
839 /* skipping one symbol if coming from emph3 */
840 if (size>1 && data[0]==c && data[1]==c) { i=1; }
841
842 while (i<size)
843 {
844 size_t len = findEmphasisChar(data.substr(i), c, 1);
845 if (len==0) { return 0; }
846 i+=len;
847 if (i>=size) { return 0; }
848
849 if (i+1<size && data[i+1]==c)
850 {
851 i++;
852 continue;
853 }
854 if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
855 {
856 out+="<em>";
857 processInline(data.substr(0,i));
858 out+="</em>";
859 AUTO_TRACE_EXIT("result={}",i+1);
860 return static_cast<int>(i+1);
861 }
862 }
863 return 0;
864}
865
866/** process double emphasis */
867int Markdown::Private::processEmphasis2(std::string_view data, char c)
868{
869 AUTO_TRACE("data='{}' c={}",Trace::trunc(data),c);
870 size_t i = 0;
871 const size_t size = data.size();
872
873 while (i<size)
874 {
875 size_t len = findEmphasisChar(data.substr(i), c, 2);
876 if (len==0)
877 {
878 return 0;
879 }
880 i += len;
881 if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' && data[i-1]!='\n')
882 {
883 if (c == '~') out+="<strike>";
884 else out+="<strong>";
885 processInline(data.substr(0,i));
886 if (c == '~') out+="</strike>";
887 else out+="</strong>";
888 AUTO_TRACE_EXIT("result={}",i+2);
889 return static_cast<int>(i+2);
890 }
891 i++;
892 }
893 return 0;
894}
895
896/** Parsing triple emphasis.
897 * Finds the first closing tag, and delegates to the other emph
898 */
899int Markdown::Private::processEmphasis3(std::string_view data,char c)
900{
901 AUTO_TRACE("data='{}' c={}",Trace::trunc(data),c);
902 size_t i = 0;
903 const size_t size = data.size();
904
905 while (i<size)
906 {
907 size_t len = findEmphasisChar(data.substr(i), c, 3);
908 if (len==0)
909 {
910 return 0;
911 }
912 i+=len;
913
914 /* skip whitespace preceded symbols */
915 if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
916 {
917 continue;
918 }
919
920 if (i+2<size && data[i+1]==c && data[i+2]==c)
921 {
922 out+="<em><strong>";
923 processInline(data.substr(0,i));
924 out+="</strong></em>";
925 AUTO_TRACE_EXIT("result={}",i+3);
926 return static_cast<int>(i+3);
927 }
928 else if (i+1<size && data[i+1]==c)
929 {
930 // double symbol found, handing over to emph1
931 len = processEmphasis1(std::string_view(data.data()-2, size+2), c);
932 if (len==0)
933 {
934 return 0;
935 }
936 else
937 {
938 AUTO_TRACE_EXIT("result={}",len-2);
939 return static_cast<int>(len - 2);
940 }
941 }
942 else
943 {
944 // single symbol found, handing over to emph2
945 len = processEmphasis2(std::string_view(data.data()-1, size+1), c);
946 if (len==0)
947 {
948 return 0;
949 }
950 else
951 {
952 AUTO_TRACE_EXIT("result={}",len-1);
953 return static_cast<int>(len - 1);
954 }
955 }
956 }
957 return 0;
958}
959
960/** Process ndash and mdashes */
961int Markdown::Private::processNmdash(std::string_view data,size_t offset)
962{
963 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
964 const size_t size = data.size();
965 // precondition: data[0]=='-'
966 size_t i=1;
967 int count=1;
968 if (i<size && data[i]=='-') // found --
969 {
970 count++,i++;
971 }
972 if (i<size && data[i]=='-') // found ---
973 {
974 count++,i++;
975 }
976 if (i<size && data[i]=='-') // found ----
977 {
978 count++;
979 }
980 if (count>=2 && offset>=2 && literal_at(data.data()-2,"<!"))
981 { AUTO_TRACE_EXIT("result={}",1-count); return 1-count; } // start HTML comment
982 if (count==2 && size > 2 && data[2]=='>')
983 { return 0; } // end HTML comment
984 if (count==3 && size > 3 && data[3]=='>')
985 { return 0; } // end HTML comment
986 if (count==2 && (offset<8 || !literal_at(data.data()-8,"operator"))) // -- => ndash
987 {
988 out+="&ndash;";
989 AUTO_TRACE_EXIT("result=2");
990 return 2;
991 }
992 else if (count==3) // --- => ndash
993 {
994 out+="&mdash;";
995 AUTO_TRACE_EXIT("result=3");
996 return 3;
997 }
998 // not an ndash or mdash
999 return 0;
1000}
1001
1002/** Process quoted section "...", can contain one embedded newline */
1003int Markdown::Private::processQuoted(std::string_view data,size_t)
1004{
1005 AUTO_TRACE("data='{}'",Trace::trunc(data));
1006 const size_t size = data.size();
1007 size_t i=1;
1008 int nl=0;
1009 while (i<size && data[i]!='"' && nl<2)
1010 {
1011 if (data[i]=='\n') nl++;
1012 i++;
1013 }
1014 if (i<size && data[i]=='"' && nl<2)
1015 {
1016 out+=data.substr(0,i+1);
1017 AUTO_TRACE_EXIT("result={}",i+2);
1018 return static_cast<int>(i+1);
1019 }
1020 // not a quoted section
1021 return 0;
1022}
1023
1024/** Process a HTML tag. Note that <pre>..</pre> are treated specially, in
1025 * the sense that all code inside is written unprocessed
1026 */
1027int Markdown::Private::processHtmlTagWrite(std::string_view data,size_t offset,bool doWrite)
1028{
1029 AUTO_TRACE("data='{}' offset={} doWrite={}",Trace::trunc(data),offset,doWrite);
1030 if (offset>0 && data.data()[-1]=='\\') { return 0; } // escaped <
1031
1032 const size_t size = data.size();
1033
1034 // find the end of the html tag
1035 size_t i=1;
1036 size_t l=0;
1037 // compute length of the tag name
1038 while (i<size && isIdChar(data[i])) i++,l++;
1039 QCString tagName(data.substr(1,i-1));
1040 if (tagName.lower()=="pre") // found <pre> tag
1041 {
1042 bool insideStr=FALSE;
1043 while (i+6<size)
1044 {
1045 char c=data[i];
1046 if (!insideStr && c=='<') // potential start of html tag
1047 {
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])=='>')
1051 { // found </pre> tag, copy from start to end of tag
1052 if (doWrite) out+=data.substr(0,i+6);
1053 //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
1054 AUTO_TRACE_EXIT("result={}",i+6);
1055 return static_cast<int>(i+6);
1056 }
1057 }
1058 else if (insideStr && c=='"')
1059 {
1060 if (data[i-1]!='\\') insideStr=FALSE;
1061 }
1062 else if (c=='"')
1063 {
1064 insideStr=TRUE;
1065 }
1066 i++;
1067 }
1068 }
1069 else // some other html tag
1070 {
1071 if (l>0 && i<size)
1072 {
1073 if (data[i]=='/' && i+1<size && data[i+1]=='>') // <bla/>
1074 {
1075 //printf("Found htmlTag={%s}\n",qPrint(QCString(data).left(i+2)));
1076 if (doWrite) out+=data.substr(0,i+2);
1077 AUTO_TRACE_EXIT("result={}",i+2);
1078 return static_cast<int>(i+2);
1079 }
1080 else if (data[i]=='>') // <bla>
1081 {
1082 //printf("Found htmlTag={%s}\n",qPrint(QCString(data).left(i+1)));
1083 if (doWrite) out+=data.substr(0,i+1);
1084 AUTO_TRACE_EXIT("result={}",i+1);
1085 return static_cast<int>(i+1);
1086 }
1087 else if (data[i]==' ') // <bla attr=...
1088 {
1089 i++;
1090 bool insideAttr=FALSE;
1091 while (i<size)
1092 {
1093 if (!insideAttr && data[i]=='"')
1094 {
1095 insideAttr=TRUE;
1096 }
1097 else if (data[i]=='"' && data[i-1]!='\\')
1098 {
1099 insideAttr=FALSE;
1100 }
1101 else if (!insideAttr && data[i]=='>') // found end of tag
1102 {
1103 //printf("Found htmlTag={%s}\n",qPrint(QCString(data).left(i+1)));
1104 if (doWrite) out+=data.substr(0,i+1);
1105 AUTO_TRACE_EXIT("result={}",i+1);
1106 return static_cast<int>(i+1);
1107 }
1108 i++;
1109 }
1110 }
1111 }
1112 }
1113 AUTO_TRACE_EXIT("not a valid html tag");
1114 return 0;
1115}
1116
1117int Markdown::Private::processHtmlTag(std::string_view data,size_t offset)
1118{
1119 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
1120 return processHtmlTagWrite(data,offset,true);
1121}
1122
1123int Markdown::Private::processEmphasis(std::string_view data,size_t offset)
1124{
1125 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
1126 const size_t size = data.size();
1127
1128 if (isAllowedEmphStr(data,offset) || // invalid char before * or _
1129 (size>1 && data[0]!=data[1] && !(isIdChar(data[1]) || extraChar(data[1]))) || // invalid char after * or _
1130 (size>2 && data[0]==data[1] && !(isIdChar(data[2]) || extraChar(data[2])))) // invalid char after ** or __
1131 {
1132 AUTO_TRACE_EXIT("invalid surrounding characters");
1133 return 0;
1134 }
1135
1136 char c = data[0];
1137 int ret = 0;
1138 if (size>2 && c!='~' && data[1]!=c) // _bla or *bla
1139 {
1140 // whitespace cannot follow an opening emphasis
1141 if (data[1]==' ' || data[1]=='\n' ||
1142 (ret = processEmphasis1(data.substr(1), c)) == 0)
1143 {
1144 return 0;
1145 }
1146 AUTO_TRACE_EXIT("result={}",ret+1);
1147 return ret+1;
1148 }
1149 if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
1150 {
1151 if (data[2]==' ' || data[2]=='\n' ||
1152 (ret = processEmphasis2(data.substr(2), c)) == 0)
1153 {
1154 return 0;
1155 }
1156 AUTO_TRACE_EXIT("result={}",ret+2);
1157 return ret+2;
1158 }
1159 if (size>4 && c!='~' && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
1160 {
1161 if (data[3]==' ' || data[3]=='\n' ||
1162 (ret = processEmphasis3(data.substr(3), c)) == 0)
1163 {
1164 return 0;
1165 }
1166 AUTO_TRACE_EXIT("result={}",ret+3);
1167 return ret+3;
1168 }
1169 return 0;
1170}
1171
1173 std::string_view fmt, bool inline_img, bool explicitTitle,
1174 const QCString &title, const QCString &content,
1175 const QCString &link, const QCString &attrs,
1176 const FileDef *fd)
1177{
1178 AUTO_TRACE("fmt={} inline_img={} explicitTitle={} title={} content={} link={} attrs={}",
1179 fmt,inline_img,explicitTitle,Trace::trunc(title),Trace::trunc(content),link,attrs);
1180 QCString attributes = getFilteredImageAttributes(fmt, attrs);
1181 out+="@image";
1182 if (inline_img)
1183 {
1184 out+="{inline}";
1185 }
1186 out+=" ";
1187 out+=fmt;
1188 out+=" ";
1189 out+=link.mid(fd ? 0 : 5);
1190 if (!explicitTitle && !content.isEmpty())
1191 {
1192 out+=" \"";
1193 out+=escapeDoubleQuotes(content);
1194 out+="\"";
1195 }
1196 else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
1197 {
1198 out+=" \"";
1199 out+=escapeDoubleQuotes(title);
1200 out+="\"";
1201 }
1202 else
1203 {
1204 out+=" ";// so the line break will not be part of the image name
1205 }
1206 if (!attributes.isEmpty())
1207 {
1208 out+=" ";
1209 out+=attributes;
1210 out+=" ";
1211 }
1212 out+="\\ilinebr ";
1213}
1214
1215int Markdown::Private::processLink(const std::string_view data,size_t offset)
1216{
1217 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
1218 const size_t size = data.size();
1219 QCString content;
1220 QCString link;
1221 QCString title;
1222 bool isImageLink = FALSE;
1223 bool isImageInline = FALSE;
1224 bool isToc = FALSE;
1225 size_t i=1;
1226 if (data[0]=='!')
1227 {
1228 isImageLink = TRUE;
1229 if (size<2 || data[1]!='[')
1230 {
1231 return 0;
1232 }
1233
1234 // if there is non-whitespace before the ![ within the scope of two new lines, the image
1235 // is considered inlined, i.e. the image is not preceded by an empty line
1236 int numNLsNeeded=2;
1237 int pos = -1;
1238 while (pos>=-static_cast<int>(offset) && numNLsNeeded>0)
1239 {
1240 if (data.data()[pos]=='\n') numNLsNeeded--;
1241 else if (data.data()[pos]!=' ') // found non-whitespace, stop searching
1242 {
1243 isImageInline=true;
1244 break;
1245 }
1246 pos--;
1247 }
1248 // skip '!['
1249 i++;
1250 }
1251 size_t contentStart=i;
1252 int level=1;
1253 int nlTotal=0;
1254 int nl=0;
1255 // find the matching ]
1256 while (i<size)
1257 {
1258 if (data[i-1]=='\\') // skip escaped characters
1259 {
1260 }
1261 else if (data[i]=='[')
1262 {
1263 level++;
1264 }
1265 else if (data[i]==']')
1266 {
1267 level--;
1268 if (level<=0) break;
1269 }
1270 else if (data[i]=='\n')
1271 {
1272 nl++;
1273 if (nl>1) { return 0; } // only allow one newline in the content
1274 }
1275 i++;
1276 }
1277 nlTotal += nl;
1278 nl = 0;
1279 if (i>=size) return 0; // premature end of comment -> no link
1280 size_t contentEnd=i;
1281 content = data.substr(contentStart,contentEnd-contentStart);
1282 //printf("processLink: content={%s}\n",qPrint(content));
1283 if (!isImageLink && content.isEmpty()) { return 0; } // no link text
1284 i++; // skip over ]
1285
1286 bool whiteSpace = false;
1287 // skip whitespace
1288 while (i<size && data[i]==' ') { whiteSpace = true; i++; }
1289 if (i<size && data[i]=='\n') // one newline allowed here
1290 {
1291 whiteSpace = true;
1292 i++;
1293 // skip more whitespace
1294 while (i<size && data[i]==' ') i++;
1295 }
1296 if (whiteSpace && i<size && (data[i]=='(' || data[i]=='[')) return 0;
1297
1298 bool explicitTitle=FALSE;
1299 if (i<size && data[i]=='(') // inline link
1300 {
1301 i++;
1302 while (i<size && data[i]==' ') i++;
1303 bool uriFormat=false;
1304 if (i<size && data[i]=='<') { i++; uriFormat=true; }
1305 size_t linkStart=i;
1306 int braceCount=1;
1307 while (i<size && data[i]!='\'' && data[i]!='"' && braceCount>0)
1308 {
1309 if (data[i]=='\n') // unexpected EOL
1310 {
1311 nl++;
1312 if (nl>1) { return 0; }
1313 }
1314 else if (data[i]=='(')
1315 {
1316 braceCount++;
1317 }
1318 else if (data[i]==')')
1319 {
1320 braceCount--;
1321 }
1322 if (braceCount>0)
1323 {
1324 i++;
1325 }
1326 }
1327 nlTotal += nl;
1328 nl = 0;
1329 if (i>=size || data[i]=='\n') { return 0; }
1330 link = data.substr(linkStart,i-linkStart);
1331 link = link.stripWhiteSpace();
1332 //printf("processLink: link={%s}\n",qPrint(link));
1333 if (link.isEmpty()) { return 0; }
1334 if (uriFormat && link.at(link.length()-1)=='>') link=link.left(link.length()-1);
1335
1336 // optional title
1337 if (data[i]=='\'' || data[i]=='"')
1338 {
1339 char c = data[i];
1340 i++;
1341 size_t titleStart=i;
1342 nl=0;
1343 while (i<size)
1344 {
1345 if (data[i]=='\n')
1346 {
1347 if (nl>1) { return 0; }
1348 nl++;
1349 }
1350 else if (data[i]=='\\') // escaped char in string
1351 {
1352 i++;
1353 }
1354 else if (data[i]==c)
1355 {
1356 i++;
1357 break;
1358 }
1359 i++;
1360 }
1361 if (i>=size)
1362 {
1363 return 0;
1364 }
1365 size_t titleEnd = i-1;
1366 // search back for closing marker
1367 while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
1368 if (data[titleEnd]==c) // found it
1369 {
1370 title = data.substr(titleStart,titleEnd-titleStart);
1371 explicitTitle=TRUE;
1372 while (i<size)
1373 {
1374 if (data[i]==' ')i++; // remove space after the closing quote and the closing bracket
1375 else if (data[i] == ')') break; // the end bracket
1376 else // illegal
1377 {
1378 return 0;
1379 }
1380 }
1381 }
1382 else
1383 {
1384 return 0;
1385 }
1386 }
1387 i++;
1388 }
1389 else if (i<size && data[i]=='[') // reference link
1390 {
1391 i++;
1392 size_t linkStart=i;
1393 nl=0;
1394 // find matching ]
1395 while (i<size && data[i]!=']')
1396 {
1397 if (data[i]=='\n')
1398 {
1399 nl++;
1400 if (nl>1) { return 0; }
1401 }
1402 i++;
1403 }
1404 if (i>=size) { return 0; }
1405 // extract link
1406 link = data.substr(linkStart,i-linkStart);
1407 //printf("processLink: link={%s}\n",qPrint(link));
1408 link = link.stripWhiteSpace();
1409 if (link.isEmpty()) // shortcut link
1410 {
1411 link=content;
1412 }
1413 // lookup reference
1414 QCString link_lower = link.lower();
1415 auto lr_it=linkRefs.find(link_lower.str());
1416 if (lr_it!=linkRefs.end()) // found it
1417 {
1418 link = lr_it->second.link;
1419 title = lr_it->second.title;
1420 //printf("processLink: ref: link={%s} title={%s}\n",qPrint(link),qPrint(title));
1421 }
1422 else // reference not found!
1423 {
1424 //printf("processLink: ref {%s} do not exist\n",link.qPrint(lower()));
1425 return 0;
1426 }
1427 i++;
1428 }
1429 else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
1430 {
1431 QCString content_lower = content.lower();
1432 auto lr_it = linkRefs.find(content_lower.str());
1433 //printf("processLink: minimal link {%s} lr=%p",qPrint(content),lr);
1434 if (lr_it!=linkRefs.end()) // found it
1435 {
1436 link = lr_it->second.link;
1437 title = lr_it->second.title;
1438 explicitTitle=TRUE;
1439 i=contentEnd;
1440 }
1441 else if (content=="TOC")
1442 {
1443 isToc=TRUE;
1444 i=contentEnd;
1445 }
1446 else
1447 {
1448 return 0;
1449 }
1450 i++;
1451 }
1452 else
1453 {
1454 return 0;
1455 }
1456 nlTotal += nl;
1457
1458 // search for optional image attributes
1459 QCString attributes;
1460 if (isImageLink)
1461 {
1462 size_t j = i;
1463 // skip over whitespace
1464 while (j<size && data[j]==' ') { j++; }
1465 if (j<size && data[j]=='{') // we have attributes
1466 {
1467 i = j;
1468 // skip over '{'
1469 i++;
1470 size_t attributesStart=i;
1471 nl=0;
1472 // find the matching '}'
1473 while (i<size)
1474 {
1475 if (data[i-1]=='\\') // skip escaped characters
1476 {
1477 }
1478 else if (data[i]=='{')
1479 {
1480 level++;
1481 }
1482 else if (data[i]=='}')
1483 {
1484 level--;
1485 if (level<=0) break;
1486 }
1487 else if (data[i]=='\n')
1488 {
1489 nl++;
1490 if (nl>1) { return 0; } // only allow one newline in the content
1491 }
1492 i++;
1493 }
1494 nlTotal += nl;
1495 if (i>=size) return 0; // premature end of comment -> no attributes
1496 size_t attributesEnd=i;
1497 attributes = data.substr(attributesStart,attributesEnd-attributesStart);
1498 i++; // skip over '}'
1499 }
1500 if (!isImageInline)
1501 {
1502 // if there is non-whitespace after the image within the scope of two new lines, the image
1503 // is considered inlined, i.e. the image is not followed by an empty line
1504 int numNLsNeeded=2;
1505 size_t pos = i;
1506 while (pos<size && numNLsNeeded>0)
1507 {
1508 if (data[pos]=='\n') numNLsNeeded--;
1509 else if (data[pos]!=' ') // found non-whitespace, stop searching
1510 {
1511 isImageInline=true;
1512 break;
1513 }
1514 pos++;
1515 }
1516 }
1517 }
1518
1519 if (isToc) // special case for [TOC]
1520 {
1521 int toc_level = Config_getInt(TOC_INCLUDE_HEADINGS);
1522 if (toc_level>=SectionType::MinLevel && toc_level<=SectionType::MaxLevel)
1523 {
1524 out+="@tableofcontents{html:";
1525 out+=QCString().setNum(toc_level);
1526 out+="}";
1527 }
1528 }
1529 else if (isImageLink)
1530 {
1531 bool ambig = false;
1532 FileDef *fd=nullptr;
1533 if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
1535 // assume doxygen symbol link or local image link
1536 {
1537 // check if different handling is needed per format
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);
1543 }
1544 else
1545 {
1546 out+="<img src=\"";
1547 out+=link;
1548 out+="\" alt=\"";
1549 out+=content;
1550 out+="\"";
1551 if (!title.isEmpty())
1552 {
1553 out+=" title=\"";
1554 out+=substitute(title.simplifyWhiteSpace(),"\"","&quot;");
1555 out+="\"";
1556 }
1557 out+="/>";
1558 }
1559 }
1560 else
1561 {
1563 int lp=-1;
1564 if ((lp=link.find("@ref "))!=-1 || (lp=link.find("\\ref "))!=-1 || (lang==SrcLangExt::Markdown && !isURL(link)))
1565 // assume doxygen symbol link
1566 {
1567 if (lp==-1) // link to markdown page
1568 {
1569 out+="@ref \"";
1570 if (!(Portable::isAbsolutePath(link) || isURL(link)))
1571 {
1572 FileInfo forg(link.str());
1573 if (forg.exists() && forg.isReadable())
1574 {
1575 link = forg.absFilePath();
1576 }
1577 else if (!(forg.exists() && forg.isReadable()))
1578 {
1579 FileInfo fi(fileName.str());
1580 QCString mdFile = fileName.left(fileName.length()-fi.fileName().length()) + link;
1581 FileInfo fmd(mdFile.str());
1582 if (fmd.exists() && fmd.isReadable())
1583 {
1584 link = fmd.absFilePath().data();
1585 }
1586 }
1587 }
1588 out+=link;
1589 out+="\"";
1590 }
1591 else
1592 {
1593 out+=link;
1594 }
1595 out+=" \"";
1596 if (explicitTitle && !title.isEmpty())
1597 {
1598 out+=substitute(title,"\"","&quot;");
1599 }
1600 else
1601 {
1602 processInline(std::string_view(substitute(content,"\"","&quot;").str()));
1603 }
1604 out+="\"";
1605 }
1606 else if ((lp=link.find('#'))!=-1 || link.find('/')!=-1 || link.find('.')!=-1)
1607 { // file/url link
1608 if (lp==0 || (lp>0 && !isURL(link) && Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
1609 {
1610 out+="@ref \"";
1612 out+="\" \"";
1613 out+=substitute(content.simplifyWhiteSpace(),"\"","&quot;");
1614 out+="\"";
1615 }
1616 else
1617 {
1618 out+="<a href=\"";
1619 out+=link;
1620 out+="\"";
1621 for (int ii = 0; ii < nlTotal; ii++) out+="\n";
1622 if (!title.isEmpty())
1623 {
1624 out+=" title=\"";
1625 out+=substitute(title.simplifyWhiteSpace(),"\"","&quot;");
1626 out+="\"";
1627 }
1628 out+=" ";
1630 out+=">";
1631 content = content.simplifyWhiteSpace();
1632 processInline(std::string_view(content.str()));
1633 out+="</a>";
1634 }
1635 }
1636 else // avoid link to e.g. F[x](y)
1637 {
1638 //printf("no link for '%s'\n",qPrint(link));
1639 return 0;
1640 }
1641 }
1642 AUTO_TRACE_EXIT("result={}",i);
1643 return static_cast<int>(i);
1644}
1645
1646/** `` ` `` parsing a code span (assuming codespan != 0) */
1647int Markdown::Private::processCodeSpan(std::string_view data,size_t offset)
1648{
1649 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
1650 const size_t size = data.size();
1651
1652 /* counting the number of backticks in the delimiter */
1653 size_t nb=0, end=0;
1654 while (nb<size && data[nb]=='`')
1655 {
1656 nb++;
1657 }
1658
1659 /* finding the next delimiter with the same amount of backticks */
1660 size_t i = 0;
1661 char pc = '`';
1662 bool markdownStrict = Config_getBool(MARKDOWN_STRICT);
1663 for (end=nb; end<size; end++)
1664 {
1665 //AUTO_TRACE_ADD("c={} nb={} i={} size={}",data[end],nb,i,size);
1666 if (data[end]=='`')
1667 {
1668 i++;
1669 if (nb==1) // `...`
1670 {
1671 if (end+1<size && data[end+1]=='`') // skip over `` inside `...`
1672 {
1673 AUTO_TRACE_ADD("case1.1");
1674 // skip
1675 end++;
1676 i=0;
1677 }
1678 else // normal end of `...`
1679 {
1680 AUTO_TRACE_ADD("case1.2");
1681 break;
1682 }
1683 }
1684 else if (i==nb) // ``...``
1685 {
1686 if (end+1<size && data[end+1]=='`') // do greedy match
1687 {
1688 // skip this quote and use the next one to terminate the sequence, e.g. ``X`Y```
1689 i--;
1690 AUTO_TRACE_ADD("case2.1");
1691 }
1692 else // normal end of ``...``
1693 {
1694 AUTO_TRACE_ADD("case2.2");
1695 break;
1696 }
1697 }
1698 }
1699 else if (data[end]=='\n')
1700 {
1701 // consecutive newlines
1702 if (pc == '\n')
1703 {
1704 AUTO_TRACE_EXIT("new paragraph");
1705 return 0;
1706 }
1707 pc = '\n';
1708 i = 0;
1709 }
1710 else if (!markdownStrict && data[end]=='\'' && nb==1 && (end+1==size || (end+1<size && data[end+1]!='\'' && !isIdChar(data[end+1]))))
1711 { // look for quoted strings like 'some word', but skip strings like `it's cool`
1712 out+="&lsquo;";
1713 out+=data.substr(nb,end-nb);
1714 out+="&rsquo;";
1715 AUTO_TRACE_EXIT("quoted end={}",end+1);
1716 return static_cast<int>(end+1);
1717 }
1718 else if (!markdownStrict && data[end]=='\'' && nb==2 && end+1<size && data[end+1]=='\'')
1719 { // look for '' to match a ``
1720 out+="&ldquo;";
1721 out+=data.substr(nb,end-nb);
1722 out+="&rdquo;";
1723 AUTO_TRACE_EXIT("double quoted end={}",end+1);
1724 return static_cast<int>(end+2);
1725 }
1726 else
1727 {
1728 if (data[end]!=' ') pc = data[end];
1729 i=0;
1730 }
1731 }
1732 if (i < nb && end >= size)
1733 {
1734 AUTO_TRACE_EXIT("no matching delimiter nb={} i={}",nb,i);
1735 if (nb>=3) // found ``` that is not at the start of the line, keep it as-is.
1736 {
1737 out+=data.substr(0,nb);
1738 return nb;
1739 }
1740 return 0; // no matching delimiter
1741 }
1742 while (end<size && data[end]=='`') // do greedy match in case we have more end backticks.
1743 {
1744 end++;
1745 }
1746
1747 //printf("found code span '%s'\n",qPrint(QCString(data+f_begin).left(f_end-f_begin)));
1748
1749 /* real code span */
1750 if (nb+nb < end)
1751 {
1752 QCString codeFragment = data.substr(nb, end-nb-nb);
1753 out+="<tt>";
1754 out+=escapeSpecialChars(codeFragment);
1755 out+="</tt>";
1756 }
1757 AUTO_TRACE_EXIT("result={} nb={}",end,nb);
1758 return static_cast<int>(end);
1759}
1760
1762{
1763 AUTO_TRACE("{}",Trace::trunc(data));
1764 if (Portable::strnstr(data.data(),g_doxy_nbsp,data.size())==nullptr) // no escape needed -> fast
1765 {
1766 out+=data;
1767 }
1768 else // escape needed -> slow
1769 {
1771 }
1772}
1773
1774int Markdown::Private::processSpecialCommand(std::string_view data, size_t offset)
1775{
1776 AUTO_TRACE("{}",Trace::trunc(data));
1777 const size_t size = data.size();
1778 size_t i=1;
1779 QCString endBlockName = isBlockCommand(data,offset);
1780 if (!endBlockName.isEmpty())
1781 {
1782 AUTO_TRACE_ADD("endBlockName={}",endBlockName);
1783 size_t l = endBlockName.length();
1784 while (i+l<size)
1785 {
1786 if ((data[i]=='\\' || data[i]=='@') && // command
1787 data[i-1]!='\\' && data[i-1]!='@') // not escaped
1788 {
1789 if (qstrncmp(&data[i+1],endBlockName.data(),l)==0)
1790 {
1791 //printf("found end at %d\n",i);
1792 addStrEscapeUtf8Nbsp(data.substr(0,i+1+l));
1793 AUTO_TRACE_EXIT("result={}",i+1+l);
1794 return static_cast<int>(i+1+l);
1795 }
1796 }
1797 i++;
1798 }
1799 }
1800 size_t endPos = isSpecialCommand(data,offset);
1801 if (endPos>0)
1802 {
1803 out+=data.substr(0,endPos);
1804 return static_cast<int>(endPos);
1805 }
1806 if (size>1 && data[0]=='\\') // escaped characters
1807 {
1808 char c=data[1];
1809 if (c=='[' || c==']' || c=='*' || c=='(' || c==')' || c=='`' || c=='_')
1810 {
1811 out+=data[1];
1812 AUTO_TRACE_EXIT("2");
1813 return 2;
1814 }
1815 else if (c=='\\' || c=='@')
1816 {
1817 out+=data.substr(0,2);
1818 AUTO_TRACE_EXIT("2");
1819 return 2;
1820 }
1821 else if (c=='-' && size>3 && data[2]=='-' && data[3]=='-') // \---
1822 {
1823 out+=data.substr(1,3);
1824 AUTO_TRACE_EXIT("2");
1825 return 4;
1826 }
1827 else if (c=='-' && size>2 && data[2]=='-') // \--
1828 {
1829 out+=data.substr(1,2);
1830 AUTO_TRACE_EXIT("3");
1831 return 3;
1832 }
1833 }
1834 else if (size>1 && data[0]=='@') // escaped characters
1835 {
1836 char c=data[1];
1837 if (c=='\\' || c=='@')
1838 {
1839 out+=data.substr(0,2);
1840 AUTO_TRACE_EXIT("2");
1841 return 2;
1842 }
1843 }
1844 return 0;
1845}
1846
1847void Markdown::Private::processInline(std::string_view data)
1848{
1849 AUTO_TRACE("data='{}'",Trace::trunc(data));
1850 size_t i=0;
1851 size_t end=0;
1852 Action_t action;
1853 const size_t size = data.size();
1854 while (i<size)
1855 {
1856 // skip over characters that do not trigger a specific action
1857 while (end<size && ((action=actions[static_cast<uint8_t>(data[end])])==nullptr)) end++;
1858 // and add them to the output
1859 out+=data.substr(i,end-i);
1860 if (end>=size) break;
1861 i=end;
1862 // do the action matching a special character at i
1863 int iend = action(data.substr(i),i);
1864 if (iend<=0) // update end
1865 {
1866 end=i+1-iend;
1867 }
1868 else // skip until end
1869 {
1870 i+=iend;
1871 end=i;
1872 }
1873 }
1874}
1875
1876/** returns whether the line is a setext-style hdr underline */
1877int Markdown::Private::isHeaderline(std::string_view data, bool allowAdjustLevel)
1878{
1879 AUTO_TRACE("data='{}' allowAdjustLevel",Trace::trunc(data),allowAdjustLevel);
1880 size_t i=0, c=0;
1881 const size_t size = data.size();
1882 while (i<size && data[i]==' ') i++;
1883 if (i==size) return 0;
1884
1885 // test of level 1 header
1886 if (data[i]=='=')
1887 {
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)
1892 {
1893 // In case a page starts with a header line we use it as title, promoting it to @page.
1894 // We set g_indentLevel to -1 to promoting the other sections if they have a deeper
1895 // nesting level than the page header, i.e. @section..@subsection becomes @page..@section.
1896 // In case a section at the same level is found (@section..@section) however we need
1897 // to undo this (and the result will be @page..@section).
1898 indentLevel=0;
1899 }
1900 AUTO_TRACE_EXIT("result={}",indentLevel+level);
1901 return indentLevel+level;
1902 }
1903 // test of level 2 header
1904 if (data[i]=='-')
1905 {
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;
1909 }
1910 return 0;
1911}
1912
1913/** returns true if this line starts a block quote */
1914static bool isBlockQuote(std::string_view data,size_t indent)
1915{
1916 AUTO_TRACE("data='{}' indent={}",Trace::trunc(data),indent);
1917 size_t i = 0;
1918 const size_t size = data.size();
1919 while (i<size && data[i]==' ') i++;
1920 if (i<indent+codeBlockIndent) // could be a quotation
1921 {
1922 // count >'s and skip spaces
1923 int level=0;
1924 while (i<size && (data[i]=='>' || data[i]==' '))
1925 {
1926 if (data[i]=='>') level++;
1927 i++;
1928 }
1929 // last characters should be a space or newline,
1930 // so a line starting with >= does not match, but only when level equals 1
1931 bool res = (level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n')) || (level > 1);
1932 AUTO_TRACE_EXIT("result={}",res);
1933 return res;
1934 }
1935 else // too much indentation -> code block
1936 {
1937 AUTO_TRACE_EXIT("result=false: too much indentation");
1938 return false;
1939 }
1940}
1941
1942/** returns end of the link ref if this is indeed a link reference. */
1943static size_t isLinkRef(std::string_view data, QCString &refid, QCString &link, QCString &title)
1944{
1945 AUTO_TRACE("data='{}'",Trace::trunc(data));
1946 const size_t size = data.size();
1947 // format: start with [some text]:
1948 size_t i = 0;
1949 while (i<size && data[i]==' ') i++;
1950 if (i>=size || data[i]!='[') { return 0; }
1951 i++;
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; }
1957 AUTO_TRACE_ADD("refid found {}",refid);
1958 //printf(" isLinkRef: found refid='%s'\n",qPrint(refid));
1959 i++;
1960 if (i>=size || data[i]!=':') { return 0; }
1961 i++;
1962
1963 // format: whitespace* \n? whitespace* (<url> | url)
1964 while (i<size && data[i]==' ') i++;
1965 if (i<size && data[i]=='\n')
1966 {
1967 i++;
1968 while (i<size && data[i]==' ') i++;
1969 }
1970 if (i>=size) { return 0; }
1971
1972 if (i<size && data[i]=='<') i++;
1973 size_t linkStart=i;
1974 while (i<size && data[i]!=' ' && data[i]!='\n') i++;
1975 size_t linkEnd=i;
1976 if (i<size && data[i]=='>') i++;
1977 if (linkStart==linkEnd) { return 0; } // empty link
1978 link = data.substr(linkStart,linkEnd-linkStart);
1979 AUTO_TRACE_ADD("link found {}",Trace::trunc(link));
1980 if (link=="@ref" || link=="\\ref")
1981 {
1982 size_t argStart=i;
1983 while (i<size && data[i]!='\n' && data[i]!='"') i++;
1984 link+=data.substr(argStart,i-argStart);
1985 }
1986
1987 title.clear();
1988
1989 // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
1990 size_t eol=0;
1991 while (i<size && data[i]==' ') i++;
1992 if (i<size && data[i]=='\n')
1993 {
1994 eol=i;
1995 i++;
1996 while (i<size && data[i]==' ') i++;
1997 }
1998 if (i>=size)
1999 {
2000 AUTO_TRACE_EXIT("result={}: end of isLinkRef while looking for title",i);
2001 return i; // end of buffer while looking for the optional title
2002 }
2003
2004 char c = data[i];
2005 if (c=='\'' || c=='"' || c=='(') // optional title present?
2006 {
2007 //printf(" start of title found! char='%c'\n",c);
2008 i++;
2009 if (c=='(') c=')'; // replace c by end character
2010 size_t titleStart=i;
2011 // search for end of the line
2012 while (i<size && data[i]!='\n') i++;
2013 eol = i;
2014
2015 // search back to matching character
2016 size_t end=i-1;
2017 while (end>titleStart && data[end]!=c) end--;
2018 if (end>titleStart)
2019 {
2020 title = data.substr(titleStart,end-titleStart);
2021 }
2022 AUTO_TRACE_ADD("title found {}",Trace::trunc(title));
2023 }
2024 while (i<size && data[i]==' ') i++;
2025 //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
2026 // i,size,data[i],eol);
2027 if (i>=size) { AUTO_TRACE_EXIT("result={}",i); return i; } // end of buffer while ref id was found
2028 else if (eol>0) { AUTO_TRACE_EXIT("result={}",eol); return eol; } // end of line while ref id was found
2029 return 0; // invalid link ref
2030}
2031
2032static bool isHRuler(std::string_view data)
2033{
2034 AUTO_TRACE("data='{}'",Trace::trunc(data));
2035 size_t i=0;
2036 size_t size = data.size();
2037 if (size>0 && data[size-1]=='\n') size--; // ignore newline character
2038 while (i<size && data[i]==' ') i++;
2039 if (i>=size) { AUTO_TRACE_EXIT("result=false: empty line"); return false; } // empty line
2040 char c=data[i];
2041 if (c!='*' && c!='-' && c!='_')
2042 {
2043 AUTO_TRACE_EXIT("result=false: {} is not a hrule character",c);
2044 return false; // not a hrule character
2045 }
2046 int n=0;
2047 while (i<size)
2048 {
2049 if (data[i]==c)
2050 {
2051 n++; // count rule character
2052 }
2053 else if (data[i]!=' ')
2054 {
2055 AUTO_TRACE_EXIT("result=false: line contains non hruler characters");
2056 return false; // line contains non hruler characters
2057 }
2058 i++;
2059 }
2060 AUTO_TRACE_EXIT("result={}",n>=3);
2061 return n>=3; // at least 3 characters needed for a hruler
2062}
2063
2064QCString Markdown::Private::extractTitleId(QCString &title, int level, bool *pIsIdGenerated)
2065{
2066 AUTO_TRACE("title={} level={}",Trace::trunc(title),level);
2067 // match e.g. '{#id-b11} ' and capture 'id-b11'
2068 static const reg::Ex r2(R"({#(\a[\w-]*)}\s*$)");
2069 reg::Match match;
2070 std::string ti = title.str();
2071 if (reg::search(ti,match,r2))
2072 {
2073 std::string id = match[1].str();
2074 title = title.left(match.position());
2075 if (AnchorGenerator::instance().reserve(id)>0)
2076 {
2077 warn(fileName, lineNr, "An automatically generated id already has the name '{}'!", id);
2078 }
2079 //printf("found match id='%s' title=%s\n",qPrint(id),qPrint(title));
2080 AUTO_TRACE_EXIT("id={}",id);
2081 return id;
2082 }
2083 if (((level>0) && (level<=Config_getInt(TOC_INCLUDE_HEADINGS))) || (Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB))
2084 {
2086 if (pIsIdGenerated) *pIsIdGenerated=true;
2087 //printf("auto-generated id='%s' title='%s'\n",qPrint(id),qPrint(title));
2088 AUTO_TRACE_EXIT("id={}",id);
2089 return id;
2090 }
2091 //printf("no id found in title '%s'\n",qPrint(title));
2092 return "";
2093}
2094
2095
2096int Markdown::Private::isAtxHeader(std::string_view data,
2097 QCString &header,QCString &id,bool allowAdjustLevel,bool *pIsIdGenerated)
2098{
2099 AUTO_TRACE("data='{}' header={} id={} allowAdjustLevel={}",Trace::trunc(data),Trace::trunc(header),id,allowAdjustLevel);
2100 size_t i = 0;
2101 int level = 0, blanks=0;
2102 const size_t size = data.size();
2103
2104 // find start of header text and determine heading level
2105 while (i<size && data[i]==' ') i++;
2106 if (i>=size || data[i]!='#')
2107 {
2108 return 0;
2109 }
2110 while (i<size && data[i]=='#') i++,level++;
2111 if (level>SectionType::MaxLevel) // too many #'s -> no section
2112 {
2113 return 0;
2114 }
2115 while (i<size && data[i]==' ') i++,blanks++;
2116 if (level==1 && blanks==0)
2117 {
2118 return 0; // special case to prevent #someid seen as a header (see bug 671395)
2119 }
2120
2121 // find end of header text
2122 size_t end=i;
2123 while (end<size && data[end]!='\n') end++;
2124 while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
2125
2126 // store result
2127 header = data.substr(i,end-i);
2128 id = extractTitleId(header, level, pIsIdGenerated);
2129 if (!id.isEmpty()) // strip #'s between title and id
2130 {
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);
2134 }
2135
2136 if (allowAdjustLevel && level==1 && indentLevel==-1)
2137 {
2138 // in case we find a `# Section` on a markdown page that started with the same level
2139 // header, we no longer need to artificially decrease the paragraph level.
2140 // So both
2141 // -------------------
2142 // # heading 1 <-- here we set g_indentLevel to -1
2143 // # heading 2 <-- here we set g_indentLevel back to 0 such that this will be a @section
2144 // -------------------
2145 // and
2146 // -------------------
2147 // # heading 1 <-- here we set g_indentLevel to -1
2148 // ## heading 2 <-- here we keep g_indentLevel at -1 such that @subsection will be @section
2149 // -------------------
2150 // will convert to
2151 // -------------------
2152 // @page md_page Heading 1
2153 // @section autotoc_md1 Heading 2
2154 // -------------------
2155
2156 indentLevel=0;
2157 }
2158 int res = level+indentLevel;
2159 AUTO_TRACE_EXIT("result={}",res);
2160 return res;
2161}
2162
2163static bool isEmptyLine(std::string_view data)
2164{
2165 AUTO_TRACE("data='{}'",Trace::trunc(data));
2166 size_t i=0;
2167 while (i<data.size())
2168 {
2169 if (data[i]=='\n') { AUTO_TRACE_EXIT("true"); return true; }
2170 if (data[i]!=' ') { AUTO_TRACE_EXIT("false"); return false; }
2171 i++;
2172 }
2173 AUTO_TRACE_EXIT("true");
2174 return true;
2175}
2176
2177#define isLiTag(i) \
2178 (data[(i)]=='<' && \
2179 (data[(i)+1]=='l' || data[(i)+1]=='L') && \
2180 (data[(i)+2]=='i' || data[(i)+2]=='I') && \
2181 (data[(i)+3]=='>'))
2182
2183// compute the indent from the start of the input, excluding list markers
2184// such as -, -#, *, +, 1., and <li>
2185static size_t computeIndentExcludingListMarkers(std::string_view data)
2186{
2187 AUTO_TRACE("data='{}'",Trace::trunc(data));
2188 size_t i=0;
2189 const size_t size=data.size();
2190 size_t indent=0;
2191 bool isDigit=FALSE;
2192 bool isLi=FALSE;
2193 bool listMarkerSkipped=FALSE;
2194 while (i<size &&
2195 (data[i]==' ' || // space
2196 (!listMarkerSkipped && // first list marker
2197 (data[i]=='+' || data[i]=='-' || data[i]=='*' || // unordered list char
2198 (data[i]=='#' && i>0 && data[i-1]=='-') || // -# item
2199 (isDigit=(data[i]>='1' && data[i]<='9')) || // ordered list marker?
2200 (isLi=(size>=3 && i+3<size && isLiTag(i))) // <li> tag
2201 )
2202 )
2203 )
2204 )
2205 {
2206 if (isDigit) // skip over ordered list marker '10. '
2207 {
2208 size_t j=i+1;
2209 while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
2210 {
2211 if (data[j]=='.') // should be end of the list marker
2212 {
2213 if (j+1<size && data[j+1]==' ') // valid list marker
2214 {
2215 listMarkerSkipped=TRUE;
2216 indent+=j+1-i;
2217 i=j+1;
2218 break;
2219 }
2220 else // not a list marker
2221 {
2222 break;
2223 }
2224 }
2225 j++;
2226 }
2227 }
2228 else if (isLi)
2229 {
2230 i+=3; // skip over <li>
2231 indent+=3;
2232 listMarkerSkipped=TRUE;
2233 }
2234 else if (data[i]=='-' && size>=2 && i+2<size && data[i+1]=='#' && data[i+2]==' ')
2235 { // case "-# "
2236 listMarkerSkipped=TRUE; // only a single list marker is accepted
2237 i++; // skip over #
2238 indent++;
2239 }
2240 else if (data[i]!=' ' && i+1<size && data[i+1]==' ')
2241 { // case "- " or "+ " or "* "
2242 listMarkerSkipped=TRUE; // only a single list marker is accepted
2243 }
2244 if (data[i]!=' ' && !listMarkerSkipped)
2245 { // end of indent
2246 break;
2247 }
2248 indent++,i++;
2249 }
2250 AUTO_TRACE_EXIT("result={}",indent);
2251 return indent;
2252}
2253
2254static size_t isListMarker(std::string_view data)
2255{
2256 AUTO_TRACE("data='{}'",Trace::trunc(data));
2257 size_t normalIndent = 0;
2258 while (normalIndent<data.size() && data[normalIndent]==' ') normalIndent++;
2259 size_t listIndent = computeIndentExcludingListMarkers(data);
2260 size_t result = listIndent>normalIndent ? listIndent : 0;
2261 AUTO_TRACE_EXIT("result={}",result);
2262 return result;
2263}
2264
2265static bool isEndOfList(std::string_view data)
2266{
2267 AUTO_TRACE("data='{}'",Trace::trunc(data));
2268 int dots=0;
2269 size_t i=0;
2270 // end of list marker is an otherwise empty line with a dot.
2271 while (i<data.size())
2272 {
2273 if (data[i]=='.')
2274 {
2275 dots++;
2276 }
2277 else if (data[i]=='\n')
2278 {
2279 break;
2280 }
2281 else if (data[i]!=' ' && data[i]!='\t') // bail out if the line is not empty
2282 {
2283 AUTO_TRACE_EXIT("result=false");
2284 return false;
2285 }
2286 i++;
2287 }
2288 AUTO_TRACE_EXIT("result={}",dots==1);
2289 return dots==1;
2290}
2291
2292static bool isFencedCodeBlock(std::string_view data,size_t refIndent,
2293 QCString &lang,size_t &start,size_t &end,size_t &offset)
2294{
2295 AUTO_TRACE("data='{}' refIndent={}",Trace::trunc(data),refIndent);
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); };
2300 // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
2301 // return FALSE
2302 size_t i=0;
2303 size_t indent=0;
2304 int startTildes=0;
2305 const size_t size = data.size();
2306 while (i<size && data[i]==' ') indent++,i++;
2307 if (indent>=refIndent+4)
2308 {
2309 AUTO_TRACE_EXIT("result=false: content is part of code block indent={} refIndent={}",indent,refIndent);
2310 return FALSE;
2311 } // part of code block
2312 char tildaChar='~';
2313 if (i<size && data[i]=='`') tildaChar='`';
2314 while (i<size && data[i]==tildaChar) startTildes++,i++;
2315 if (startTildes<3)
2316 {
2317 AUTO_TRACE_EXIT("result=false: no fence marker found #tildes={}",startTildes);
2318 return FALSE;
2319 } // not enough tildes
2320 if (i<size && data[i]=='{') // extract .py from ```{.py} ... ```
2321 {
2322 i++; // skip over {
2323 if (data[i] == dot) i++; // skip over initial dot
2324 size_t startLang=i;
2325 while (i<size && (data[i]!='\n' && data[i]!='}')) i++; // find matching }
2326 if (i<size && data[i]=='}')
2327 {
2328 lang = data.substr(startLang,i-startLang);
2329 i++;
2330 }
2331 else // missing closing bracket, treat `{` as part of the content
2332 {
2333 i=startLang-1;
2334 lang="";
2335 }
2336 }
2337 else if (i<size && isLangChar(data[i])) /// extract python or .py from ```python...``` or ```.py...```
2338 {
2339 if (data[i] == dot) i++; // skip over initial dot
2340 size_t startLang=i;
2341 if (i<size && isAlphaChar(data[i])) //check first character of language specifier
2342 {
2343 i++;
2344 while (i<size && isAlphaNChar(data[i])) i++; // find end of language specifier
2345 }
2346 lang = data.substr(startLang,i-startLang);
2347 }
2348 else // no language specified
2349 {
2350 lang="";
2351 }
2352
2353 start=i;
2354 while (i<size)
2355 {
2356 if (data[i]==tildaChar)
2357 {
2358 end=i;
2359 int endTildes=0;
2360 while (i<size && data[i]==tildaChar) endTildes++,i++;
2361 while (i<size && data[i]==' ') i++;
2362 {
2363 if (endTildes==startTildes)
2364 {
2365 offset=i;
2366 AUTO_TRACE_EXIT("result=true: found end marker at offset {} lang='{}'",offset,lang);
2367 return true;
2368 }
2369 }
2370 }
2371 i++;
2372 }
2373 AUTO_TRACE_EXIT("result=false: no end marker found lang={}'",lang);
2374 return false;
2375}
2376
2377static bool isCodeBlock(std::string_view data, size_t offset,size_t &indent)
2378{
2379 AUTO_TRACE("data='{}' offset={}",Trace::trunc(data),offset);
2380 //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
2381 // determine the indent of this line
2382 size_t i=0;
2383 size_t indent0=0;
2384 const size_t size = data.size();
2385 while (i<size && data[i]==' ') indent0++,i++;
2386
2387 if (indent0<codeBlockIndent)
2388 {
2389 AUTO_TRACE_EXIT("result={}: line is not indented enough {}<4",false,indent0);
2390 return false;
2391 }
2392 if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
2393 {
2394 AUTO_TRACE_EXIT("result={}: only spaces at the end of a comment block",false);
2395 return false;
2396 }
2397
2398 i=offset;
2399 int nl=0;
2400 int nl_pos[3];
2401 int offset_i = static_cast<int>(offset);
2402 // search back 3 lines and remember the start of lines -1 and -2
2403 while (i>0 && nl<3) // i counts down from offset to 1
2404 {
2405 int j = static_cast<int>(i)-offset_i-1; // j counts from -1 to -offset
2406 // since j can be negative we need to rewrap data in a std::string_view
2407 size_t nl_size = isNewline(std::string_view(data.data()+j,data.size()-j));
2408 if (nl_size>0)
2409 {
2410 nl_pos[nl++]=j+static_cast<int>(nl_size);
2411 }
2412 i--;
2413 }
2414
2415 // if there are only 2 preceding lines, then line -2 starts at -offset
2416 if (i==0 && nl==2) nl_pos[nl++]=-offset_i;
2417
2418 if (nl==3) // we have at least 2 preceding lines
2419 {
2420 //printf(" positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
2421 // nl_pos[0],nl_pos[1],nl_pos[2],
2422 // qPrint(QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1)),
2423 // qPrint(QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1)));
2424
2425 // check that line -1 is empty
2426 // Note that the offset is negative so we need to rewrap the string view
2427 if (!isEmptyLine(std::string_view(data.data()+nl_pos[1],nl_pos[0]-nl_pos[1]-1)))
2428 {
2429 AUTO_TRACE_EXIT("result={}",FALSE);
2430 return FALSE;
2431 }
2432
2433 // determine the indent of line -2
2434 // Note that the offset is negative so we need to rewrap the string view
2435 indent=std::max(indent,computeIndentExcludingListMarkers(
2436 std::string_view(data.data()+nl_pos[2],nl_pos[1]-nl_pos[2])));
2437
2438 //printf(">isCodeBlock local_indent %d>=%d+%d=%d\n",
2439 // indent0,indent,codeBlockIndent,indent0>=indent+codeBlockIndent);
2440 // if the difference is >4 spaces -> code block
2441 bool res = indent0>=indent+codeBlockIndent;
2442 AUTO_TRACE_EXIT("result={}: code block if indent difference >4 spaces",res);
2443 return res;
2444 }
2445 else // not enough lines to determine the relative indent, use global indent
2446 {
2447 // check that line -1 is empty
2448 // Note that the offset is negative so we need to rewrap the string view
2449 if (nl==1 && !isEmptyLine(std::string_view(data.data()-offset,offset-1)))
2450 {
2451 AUTO_TRACE_EXIT("result=false");
2452 return FALSE;
2453 }
2454 //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
2455 // indent0,indent,indent0>=indent+4,nl);
2456 bool res = indent0>=indent+codeBlockIndent;
2457 AUTO_TRACE_EXIT("result={}: code block if indent difference >4 spaces",res);
2458 return res;
2459 }
2460}
2461
2462/** Finds the location of the table's contains in the string \a data.
2463 * Only one line will be inspected.
2464 * @param[in] data pointer to the string buffer.
2465 * @param[out] start offset of the first character of the table content
2466 * @param[out] end offset of the last character of the table content
2467 * @param[out] columns number of table columns found
2468 * @returns The offset until the next line in the buffer.
2469 */
2470static size_t findTableColumns(std::string_view data,size_t &start,size_t &end,size_t &columns)
2471{
2472 AUTO_TRACE("data='{}'",Trace::trunc(data));
2473 const size_t size = data.size();
2474 size_t i=0,n=0;
2475 // find start character of the table line
2476 while (i<size && data[i]==' ') i++;
2477 if (i<size && data[i]=='|' && data[i]!='\n') i++,n++; // leading | does not count
2478 start = i;
2479
2480 // find end character of the table line
2481 size_t j = 0;
2482 while (i<size && (j = isNewline(data.substr(i)))==0) i++;
2483 size_t eol=i+j;
2484
2485 if (j>0 && i>0) i--; // move i to point before newline
2486 while (i>0 && data[i]==' ') i--;
2487 if (i>0 && data[i-1]!='\\' && data[i]=='|') i--,n++; // trailing or escaped | does not count
2488 end = i;
2489
2490 // count columns between start and end
2491 columns=0;
2492 if (end>start)
2493 {
2494 i=start;
2495 while (i<=end) // look for more column markers
2496 {
2497 if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
2498 if (columns==1) columns++; // first | make a non-table into a two column table
2499 i++;
2500 }
2501 }
2502 if (n==2 && columns==0) // table row has | ... |
2503 {
2504 columns++;
2505 }
2506 AUTO_TRACE_EXIT("eol={} start={} end={} columns={}",eol,start,end,columns);
2507 return eol;
2508}
2509
2510/** Returns TRUE iff data points to the start of a table block */
2511static bool isTableBlock(std::string_view data)
2512{
2513 AUTO_TRACE("data='{}'",Trace::trunc(data));
2514 size_t cc0=0, start=0, end=0;
2515
2516 // the first line should have at least two columns separated by '|'
2517 size_t i = findTableColumns(data,start,end,cc0);
2518 if (i>=data.size() || cc0<1)
2519 {
2520 AUTO_TRACE_EXIT("result=false: no |'s in the header");
2521 return FALSE;
2522 }
2523
2524 size_t cc1 = 0;
2525 size_t ret = findTableColumns(data.substr(i),start,end,cc1);
2526 size_t j=i+start;
2527 // separator line should consist of |, - and : and spaces only
2528 while (j<=end+i)
2529 {
2530 if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
2531 {
2532 AUTO_TRACE_EXIT("result=false: invalid character '{}'",data[j]);
2533 return FALSE; // invalid characters in table separator
2534 }
2535 j++;
2536 }
2537 if (cc1!=cc0) // number of columns should be same as previous line
2538 {
2539 AUTO_TRACE_EXIT("result=false: different number of columns as previous line {}!={}",cc1,cc0);
2540 return FALSE;
2541 }
2542
2543 i+=ret; // goto next line
2544 size_t cc2 = 0;
2545 findTableColumns(data.substr(i),start,end,cc2);
2546
2547 AUTO_TRACE_EXIT("result={}",cc1==cc2);
2548 return cc1==cc2;
2549}
2550
2551size_t Markdown::Private::writeTableBlock(std::string_view data)
2552{
2553 AUTO_TRACE("data='{}'",Trace::trunc(data));
2554 const size_t size = data.size();
2555
2556 size_t columns=0, start=0, end=0;
2557 size_t i = findTableColumns(data,start,end,columns);
2558 size_t headerStart = start;
2559 size_t headerEnd = end;
2560
2561 // read cell alignments
2562 size_t cc = 0;
2563 size_t ret = findTableColumns(data.substr(i),start,end,cc);
2564 size_t k=0;
2565 std::vector<int> columnAlignment(columns);
2566
2567 bool leftMarker=false, rightMarker=false, startFound=false;
2568 size_t j=start+i;
2569 while (j<=end+i)
2570 {
2571 if (!startFound)
2572 {
2573 if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
2574 if (data[j]=='-') startFound=TRUE;
2575 //printf(" data[%d]=%c startFound=%d\n",j,data[j],startFound);
2576 }
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]!='\\')))
2580 {
2581 if (k<columns)
2582 {
2583 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
2584 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
2585 leftMarker=FALSE;
2586 rightMarker=FALSE;
2587 startFound=FALSE;
2588 }
2589 k++;
2590 }
2591 j++;
2592 }
2593 if (k<columns)
2594 {
2595 columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
2596 //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
2597 }
2598 // proceed to next line
2599 i+=ret;
2600
2601 // Store the table cell information by row then column. This
2602 // allows us to handle row spanning.
2603 std::vector<std::vector<TableCell> > tableContents;
2604
2605 size_t m = headerStart;
2606 std::vector<TableCell> headerContents(columns);
2607 for (k=0;k<columns;k++)
2608 {
2609 while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
2610 {
2611 headerContents[k].cellText += data[m++];
2612 }
2613 m++;
2614 // do the column span test before stripping white space
2615 // || is spanning columns, | | is not
2616 headerContents[k].colSpan = headerContents[k].cellText.isEmpty();
2617 headerContents[k].cellText = headerContents[k].cellText.stripWhiteSpace();
2618 }
2619 tableContents.push_back(headerContents);
2620
2621 // write table cells
2622 while (i<size)
2623 {
2624 ret = findTableColumns(data.substr(i),start,end,cc);
2625 if (cc!=columns) break; // end of table
2626
2627 j=start+i;
2628 k=0;
2629 std::vector<TableCell> rowContents(columns);
2630 while (j<=end+i)
2631 {
2632 if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
2633 {
2634 // do the column span test before stripping white space
2635 // || is spanning columns, | | is not
2636 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2637 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2638 k++;
2639 } // if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\')))
2640 else
2641 {
2642 rowContents[k].cellText += data[j];
2643 } // else { if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\'))) }
2644 j++;
2645 } // while (j<=end+i)
2646 // do the column span test before stripping white space
2647 // || is spanning columns, | | is not
2648 rowContents[k].colSpan = rowContents[k].cellText.isEmpty();
2649 rowContents[k].cellText = rowContents[k].cellText.stripWhiteSpace();
2650 tableContents.push_back(rowContents);
2651
2652 // proceed to next line
2653 i+=ret;
2654 }
2655
2656 out+="<table class=\"markdownTable\">";
2657 QCString cellTag("th"), cellClass("class=\"markdownTableHead");
2658 for (size_t row = 0; row < tableContents.size(); row++)
2659 {
2660 if (row)
2661 {
2662 if (row % 2)
2663 {
2664 out+="\n<tr class=\"markdownTableRowOdd\">";
2665 }
2666 else
2667 {
2668 out+="\n<tr class=\"markdownTableRowEven\">";
2669 }
2670 }
2671 else
2672 {
2673 out+="\n <tr class=\"markdownTableHead\">";
2674 }
2675 for (size_t c = 0; c < columns; c++)
2676 {
2677 // save the cell text for use after column span computation
2678 QCString cellText(tableContents[row][c].cellText);
2679
2680 // Row span handling. Spanning rows will contain a caret ('^').
2681 // If the current cell contains just a caret, this is part of an
2682 // earlier row's span and the cell should not be added to the
2683 // output.
2684 if (tableContents[row][c].cellText == "^")
2685 {
2686 continue;
2687 }
2688 if (tableContents[row][c].colSpan)
2689 {
2690 int cr = static_cast<int>(c);
2691 while ( cr >= 0 && tableContents[row][cr].colSpan)
2692 {
2693 cr--;
2694 };
2695 if (cr >= 0 && tableContents[row][cr].cellText == "^") continue;
2696 }
2697 size_t rowSpan = 1, spanRow = row+1;
2698 while ((spanRow < tableContents.size()) &&
2699 (tableContents[spanRow][c].cellText == "^"))
2700 {
2701 spanRow++;
2702 rowSpan++;
2703 }
2704
2705 out+=" <" + cellTag + " " + cellClass;
2706 // use appropriate alignment style
2707 switch (columnAlignment[c])
2708 {
2709 case AlignLeft: out+="Left\""; break;
2710 case AlignRight: out+="Right\""; break;
2711 case AlignCenter: out+="Center\""; break;
2712 case AlignNone: out+="None\""; break;
2713 }
2714
2715 if (rowSpan > 1)
2716 {
2717 QCString spanStr;
2718 spanStr.setNum(rowSpan);
2719 out+=" rowspan=\"" + spanStr + "\"";
2720 }
2721 // Column span handling, assumes that column spans will have
2722 // empty strings, which would indicate the sequence "||", used
2723 // to signify spanning columns.
2724 size_t colSpan = 1;
2725 while ((c+1 < columns) && tableContents[row][c+1].colSpan)
2726 {
2727 c++;
2728 colSpan++;
2729 }
2730 if (colSpan > 1)
2731 {
2732 QCString spanStr;
2733 spanStr.setNum(colSpan);
2734 out+=" colspan=\"" + spanStr + "\"";
2735 }
2736 // need at least one space on either side of the cell text in
2737 // order for doxygen to do other formatting
2738 out+="> " + cellText + " \\ilinebr </" + cellTag + ">";
2739 }
2740 cellTag = "td";
2741 cellClass = "class=\"markdownTableBody";
2742 out+=" </tr>";
2743 }
2744 out+="</table>\n";
2745
2746 AUTO_TRACE_EXIT("i={}",i);
2747 return i;
2748}
2749
2750
2751static bool hasLineBreak(std::string_view data)
2752{
2753 AUTO_TRACE("data='{}'",Trace::trunc(data));
2754 size_t i=0;
2755 size_t j=0;
2756 // search for end of line and also check if it is not a completely blank
2757 while (i<data.size() && data[i]!='\n')
2758 {
2759 if (data[i]!=' ' && data[i]!='\t') j++; // some non whitespace
2760 i++;
2761 }
2762 if (i>=data.size()) { return 0; } // empty line
2763 if (i<2) { return 0; } // not long enough
2764 bool res = (j>0 && data[i-1]==' ' && data[i-2]==' '); // non blank line with at two spaces at the end
2765 AUTO_TRACE_EXIT("result={}",res);
2766 return res;
2767}
2768
2769
2771{
2772 AUTO_TRACE("data='{}'",Trace::trunc(data));
2773 int level=0;
2774 QCString header;
2775 QCString id;
2776 if (isHRuler(data))
2777 {
2778 out+="<hr>\n";
2779 }
2780 else if ((level=isAtxHeader(data,header,id,TRUE)))
2781 {
2782 QCString hTag;
2783 if (!id.isEmpty())
2784 {
2785 switch (level)
2786 {
2787 case SectionType::Section: out+="@section "; break;
2788 case SectionType::Subsection: out+="@subsection "; break;
2789 case SectionType::Subsubsection: out+="@subsubsection "; break;
2790 case SectionType::Paragraph: out+="@paragraph "; break;
2791 case SectionType::Subparagraph: out+="@subparagraph "; break;
2792 case SectionType::Subsubparagraph: out+="@subsubparagraph "; break;
2793 }
2794 out+=id;
2795 out+=" ";
2796 out+=header;
2797 out+="\n";
2798 }
2799 else
2800 {
2801 hTag.sprintf("h%d",level);
2802 out+="<"+hTag+">";
2803 out+=header;
2804 out+="</"+hTag+">\n";
2805 }
2806 }
2807 else if (data.size()>0) // nothing interesting -> just output the line
2808 {
2809 size_t tmpSize = data.size();
2810 if (data[data.size()-1] == '\n') tmpSize--;
2811 out+=data.substr(0,tmpSize);
2812
2813 if (hasLineBreak(data))
2814 {
2815 out+="\\ilinebr<br>";
2816 }
2817 if (tmpSize != data.size()) out+='\n';
2818 }
2819}
2820
2821static const std::unordered_map<std::string,std::string> g_quotationHeaderMap = {
2822 // GitHub style Doxygen command
2823 { "[!note]", "\\note" },
2824 { "[!warning]", "\\warning" },
2825 { "[!tip]", "\\remark" },
2826 { "[!caution]", "\\attention" },
2827 { "[!important]", "\\important" }
2828};
2829
2830size_t Markdown::Private::writeBlockQuote(std::string_view data)
2831{
2832 AUTO_TRACE("data='{}'",Trace::trunc(data));
2833 size_t i=0;
2834 int curLevel=0;
2835 size_t end=0;
2836 const size_t size = data.size();
2837 std::string startCmd;
2838 int isGitHubAlert = false;
2839 int isGitHubFirst = false;
2840 while (i<size)
2841 {
2842 // find end of this line
2843 end=i+1;
2844 while (end<=size && data[end-1]!='\n') end++;
2845 size_t j=i;
2846 int level=0;
2847 size_t indent=i;
2848 // compute the quoting level
2849 while (j<end && (data[j]==' ' || data[j]=='>'))
2850 {
2851 if (data[j]=='>') { level++; indent=j+1; }
2852 else if (j>0 && data[j-1]=='>') indent=j+1;
2853 j++;
2854 }
2855 if (indent>0 && j>0 && data[j-1]=='>' &&
2856 !(j==size || data[j]=='\n')) // disqualify last > if not followed by space
2857 {
2858 indent--;
2859 level--;
2860 j--;
2861 }
2862 AUTO_TRACE_ADD("indent={} i={} j={} end={} level={} line={}",indent,i,j,end,level,Trace::trunc(&data[i]));
2863 if (level==0 && j<end-1 && !isListMarker(data.substr(j)) && !isHRuler(data.substr(j)))
2864 {
2865 level = curLevel; // lazy
2866 }
2867 if (level==1)
2868 {
2869 QCString txt = stripWhiteSpace(data.substr(indent,end-indent));
2870 auto it = g_quotationHeaderMap.find(txt.lower().str()); // TODO: in C++20 the std::string can be dropped
2871 if (it != g_quotationHeaderMap.end())
2872 {
2873 isGitHubAlert = true;
2874 isGitHubFirst = true;
2875 startCmd = it->second;
2876 }
2877 }
2878 if (level>curLevel) // quote level increased => add start markers
2879 {
2880 if (level!=1 || !isGitHubAlert) // normal block quote
2881 {
2882 for (int l=curLevel;l<level-1;l++)
2883 {
2884 out+="<blockquote>";
2885 }
2886 out += "<blockquote>&zwj;"; // empty blockquotes are also shown
2887 }
2888 else if (!startCmd.empty()) // GitHub style alert
2889 {
2890 out += startCmd + " ";
2891 }
2892 }
2893 else if (level<curLevel) // quote level decreased => add end markers
2894 {
2895 int decrLevel = curLevel;
2896 if (level==0 && isGitHubAlert)
2897 {
2898 decrLevel--;
2899 }
2900 for (int l=level;l<decrLevel;l++)
2901 {
2902 out += "</blockquote>\\ilinebr ";
2903 }
2904 }
2905 if (level==0)
2906 {
2907 curLevel=0;
2908 break; // end of quote block
2909 }
2910 // copy line without quotation marks
2911 if (curLevel!=0 || !isGitHubAlert)
2912 {
2913 std::string_view txt = data.substr(indent,end-indent);
2914 if (stripWhiteSpace(txt).empty() && !startCmd.empty())
2915 {
2916 if (!isGitHubFirst) out += "<br>";
2917 out += "<br>\n";
2918 }
2919 else
2920 {
2921 out += txt;
2922 }
2923 isGitHubFirst = false;
2924 }
2925 else // GitHub alert section
2926 {
2927 out+= "\n";
2928 }
2929 curLevel=level;
2930 // proceed with next line
2931 i=end;
2932 }
2933 // end of comment within blockquote => add end markers
2934 if (isGitHubAlert) // GitHub alert doesn't have a blockquote
2935 {
2936 curLevel--;
2937 }
2938 for (int l=0;l<curLevel;l++)
2939 {
2940 out+="</blockquote>";
2941 }
2942 AUTO_TRACE_EXIT("i={}",i);
2943 return i;
2944}
2945
2946// For code blocks that are outputted as part of an indented include or snippet command, we need to filter out
2947// the location string, i.e. '\ifile "..." \iline \ilinebr'.
2948bool skipOverFileAndLineCommands(std::string_view data,size_t indent,size_t &offset,std::string &location)
2949{
2950 size_t i = offset;
2951 size_t size = data.size();
2952 while (i<data.size() && data[i]==' ') i++;
2953 if (literal_at(data.substr(i),"\\ifile \""))
2954 {
2955 size_t locStart = i;
2956 if (i>offset) locStart--; // include the space before \ifile
2957 i+=8;
2958 bool found=false;
2959 while (i+9<size && data[i]!='\n')
2960 {
2961 if (literal_at(data.substr(i),"\\ilinebr "))
2962 {
2963 found=true;
2964 break;
2965 }
2966 i++;
2967 }
2968 if (found)
2969 {
2970 i+=9;
2971 location=data.substr(locStart,i-locStart);
2972 location+='\n';
2973 while (indent>0 && i<size && data[i]==' ') i++,indent--;
2974 if (i<size && data[i]=='\n') i++;
2975 offset = i;
2976 return true;
2977 }
2978 }
2979 return false;
2980}
2981
2982size_t Markdown::Private::writeCodeBlock(std::string_view data,size_t refIndent)
2983{
2984 AUTO_TRACE("data='{}' refIndent={}",Trace::trunc(data),refIndent);
2985 const size_t size = data.size();
2986 size_t i=0;
2987 // no need for \ilinebr here as the previous line was empty and was skipped
2988 out+="@iverbatim\n";
2989 int emptyLines=0;
2990 std::string location;
2991 while (i<size)
2992 {
2993 // find end of this line
2994 size_t end=i+1;
2995 while (end<=size && data[end-1]!='\n') end++;
2996 size_t j=i;
2997 size_t indent=0;
2998 while (j<end && data[j]==' ') j++,indent++;
2999 //printf("j=%d end=%d indent=%d refIndent=%d tabSize=%d data={%s}\n",
3000 // j,end,indent,refIndent,Config_getInt(TAB_SIZE),qPrint(QCString(data+i).left(end-i-1)));
3001 if (j==end-1) // empty line
3002 {
3003 emptyLines++;
3004 i=end;
3005 }
3006 else if (indent>=refIndent+codeBlockIndent) // enough indent to continue the code block
3007 {
3008 while (emptyLines>0) // write skipped empty lines
3009 {
3010 // add empty line
3011 out+="\n";
3012 emptyLines--;
3013 }
3014 // add code line minus the indent
3015 size_t offset = i+refIndent+codeBlockIndent;
3016 std::string lineLoc;
3017 if (skipOverFileAndLineCommands(data,codeBlockIndent,offset,lineLoc))
3018 {
3019 location = lineLoc;
3020 }
3021 out+=data.substr(offset,end-offset);
3022 i=end;
3023 }
3024 else // end of code block
3025 {
3026 break;
3027 }
3028 }
3029 out+="@endiverbatim";
3030 if (!location.empty())
3031 {
3032 out+=location;
3033 }
3034 else
3035 {
3036 out+="\\ilinebr ";
3037 }
3038 while (emptyLines>0) // write skipped empty lines
3039 {
3040 // add empty line
3041 out+="\n";
3042 emptyLines--;
3043 }
3044 AUTO_TRACE_EXIT("i={}",i);
3045 return i;
3046}
3047
3048// start searching for the end of the line start at offset \a i
3049// keeping track of possible blocks that need to be skipped.
3050size_t Markdown::Private::findEndOfLine(std::string_view data,size_t offset)
3051{
3052 AUTO_TRACE("data='{}'",Trace::trunc(data));
3053 // find end of the line
3054 const size_t size = data.size();
3055 size_t nb=0, end=offset+1, j=0;
3056 while (end<=size && (j=isNewline(data.substr(end-1)))==0)
3057 {
3058 // while looking for the end of the line we might encounter a block
3059 // that needs to be passed unprocessed.
3060 if ((data[end-1]=='\\' || data[end-1]=='@') && // command
3061 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
3062 )
3063 {
3064 QCString endBlockName = isBlockCommand(data.substr(end-1),end-1);
3065 end++;
3066 if (!endBlockName.isEmpty())
3067 {
3068 size_t l = endBlockName.length();
3069 for (;end+l+1<size;end++) // search for end of block marker
3070 {
3071 if ((data[end]=='\\' || data[end]=='@') &&
3072 data[end-1]!='\\' && data[end-1]!='@'
3073 )
3074 {
3075 if (qstrncmp(&data[end+1],endBlockName.data(),l)==0)
3076 {
3077 // found end marker, skip over this block
3078 //printf("feol.block out={%s}\n",qPrint(QCString(data+i).left(end+l+1-i)));
3079 end = end + l + 2;
3080 break;
3081 }
3082 }
3083 }
3084 }
3085 }
3086 else if (nb==0 && data[end-1]=='<' && size>=6 && end+6<size &&
3087 (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
3088 )
3089 {
3090 if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
3091 tolower(data[end+2])=='e' && (data[end+3]=='>' || data[end+3]==' ')) // <pre> tag
3092 {
3093 // skip part until including </pre>
3094 end = end + processHtmlTagWrite(data.substr(end-1),end-1,false);
3095 break;
3096 }
3097 else
3098 {
3099 end++;
3100 }
3101 }
3102 else if (nb==0 && data[end-1]=='`')
3103 {
3104 while (end<=size && data[end-1]=='`') end++,nb++;
3105 }
3106 else if (nb>0 && data[end-1]=='`')
3107 {
3108 size_t enb=0;
3109 while (end<=size && data[end-1]=='`') end++,enb++;
3110 if (enb==nb) nb=0;
3111 }
3112 else
3113 {
3114 end++;
3115 }
3116 }
3117 if (j>0) end+=j-1;
3118 AUTO_TRACE_EXIT("offset={} end={}",offset,end);
3119 return end;
3120}
3121
3122void Markdown::Private::writeFencedCodeBlock(std::string_view data,std::string_view lang,
3123 size_t blockStart,size_t blockEnd)
3124{
3125 AUTO_TRACE("data='{}' lang={} blockStart={} blockEnd={}",Trace::trunc(data),lang,blockStart,blockEnd);
3126 if (!lang.empty() && lang[0]=='.') lang=lang.substr(1);
3127 const size_t size=data.size();
3128 size_t i=0;
3129 while (i<size && (data[i]==' ' || data[i]=='\t'))
3130 {
3131 out+=data[i++];
3132 blockStart--;
3133 blockEnd--;
3134 }
3135 out+="@icode";
3136 if (!lang.empty())
3137 {
3138 out+="{"+lang+"}";
3139 }
3140 out+=" ";
3141 addStrEscapeUtf8Nbsp(data.substr(blockStart+i,blockEnd-blockStart));
3142 out+="@endicode ";
3143}
3144
3145QCString Markdown::Private::processQuotations(std::string_view data,size_t refIndent)
3146{
3147 AUTO_TRACE("data='{}' refIndex='{}'",Trace::trunc(data),refIndent);
3148 out.clear();
3149 size_t i=0,end=0;
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();
3156 QCString lang;
3157 while (i<size)
3158 {
3159 end = findEndOfLine(data,i);
3160 // line is now found at [i..end)
3161
3162 size_t lineIndent=0;
3163 while (lineIndent<end && data[i+lineIndent]==' ') lineIndent++;
3164 //printf("** lineIndent=%d line=(%s)\n",lineIndent,qPrint(QCString(data+i).left(end-i)));
3165
3166 if (newBlock)
3167 {
3168 //printf("** end of block\n");
3169 if (insideList && lineIndent<currentIndent) // end of list
3170 {
3171 //printf("** end of list\n");
3172 currentIndent = refIndent;
3173 insideList = false;
3174 }
3175 newBlock = false;
3176 }
3177
3178 if ((listIndent=isListMarker(data.substr(i,end-i)))) // see if we need to increase the indent level
3179 {
3180 if (listIndent<currentIndent+4)
3181 {
3182 //printf("** start of list\n");
3183 insideList = true;
3184 currentIndent = listIndent;
3185 }
3186 }
3187 else if (isEndOfList(data.substr(i,end-i)))
3188 {
3189 //printf("** end of list\n");
3190 insideList = false;
3191 currentIndent = listIndent;
3192 }
3193 else if (isEmptyLine(data.substr(i,end-i)))
3194 {
3195 //printf("** new block\n");
3196 newBlock = true;
3197 }
3198 //printf("currentIndent=%d listIndent=%d refIndent=%d\n",currentIndent,listIndent,refIndent);
3199
3200 if (pi!=std::string::npos)
3201 {
3202 size_t blockStart=0, blockEnd=0, blockOffset=0;
3203 if (isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3204 {
3205 auto addSpecialCommand = [&](const QCString &startCmd,const QCString &endCmd)
3206 {
3207 size_t cmdPos = pi+blockStart+1;
3208 QCString pl = data.substr(cmdPos,blockEnd-blockStart-1);
3209 size_t ii = 0;
3210 int nl = 1;
3211 // check for absence of start command, either @start<cmd>, or \\start<cmd>
3212 while (ii<pl.length() && qisspace(pl[ii]))
3213 {
3214 if (pl[ii]=='\n') nl++;
3215 ii++; // skip leading whitespace
3216 }
3217 bool addNewLines = false;
3218 if (ii+startCmd.length()>=pl.length() || // no room for start command
3219 (pl[ii]!='\\' && pl[ii]!='@') || // no @ or \ after whitespace
3220 qstrncmp(pl.data()+ii+1,startCmd.data(),startCmd.length())!=0) // no start command
3221 {
3222 // input: output:
3223 // ----------------------------------------------------
3224 // ```{plantuml} => @startuml
3225 // A->B A->B
3226 // ``` @enduml
3227 // ----------------------------------------------------
3228 pl = "@"+startCmd+"\n" + pl + "@"+endCmd;
3229 addNewLines = false;
3230 }
3231 else // we have a @start... command inside the code block
3232 {
3233 // input: output:
3234 // ----------------------------------------------------
3235 // ```{plantuml} \n
3236 // \n
3237 // @startuml => @startuml
3238 // A->B A->B
3239 // @enduml @enduml
3240 // ``` \n
3241 // ----------------------------------------------------
3242 addNewLines = true;
3243 }
3244 if (addNewLines) for (int j=0;j<nl;j++) out+='\n';
3245 processSpecialCommand(pl.view().substr(ii),ii);
3246 if (addNewLines) out+='\n';
3247 };
3248
3249 if (!Config_getString(PLANTUML_JAR_PATH).isEmpty() && lang=="plantuml")
3250 {
3251 addSpecialCommand("startuml","enduml");
3252 }
3253 else if (Config_getBool(HAVE_DOT) && lang=="dot")
3254 {
3255 addSpecialCommand("dot","enddot");
3256 }
3257 else if (lang=="msc") // msc is built-in
3258 {
3259 addSpecialCommand("msc","endmsc");
3260 }
3261 else // normal code block
3262 {
3263 writeFencedCodeBlock(data.substr(pi),lang.view(),blockStart,blockEnd);
3264 }
3265 i=pi+blockOffset;
3266 pi=std::string::npos;
3267 end=i+1;
3268 continue;
3269 }
3270 else if (isBlockQuote(data.substr(pi,i-pi),currentIndent))
3271 {
3272 i = pi+writeBlockQuote(data.substr(pi));
3273 pi=std::string::npos;
3274 end=i+1;
3275 continue;
3276 }
3277 else
3278 {
3279 //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
3280 out+=data.substr(pi,i-pi);
3281 }
3282 }
3283 pi=i;
3284 i=end;
3285 }
3286 if (pi!=std::string::npos && pi<size) // deal with the last line
3287 {
3288 if (isBlockQuote(data.substr(pi),currentIndent))
3289 {
3290 writeBlockQuote(data.substr(pi));
3291 }
3292 else
3293 {
3294 out+=data.substr(pi);
3295 }
3296 }
3297
3298 //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
3299 // qPrint(s),prv->out.get());
3300
3301 return out;
3302}
3303
3304QCString Markdown::Private::processBlocks(std::string_view data,const size_t indent)
3305{
3306 AUTO_TRACE("data='{}' indent={}",Trace::trunc(data),indent);
3307 out.clear();
3308 size_t pi = std::string::npos;
3309 QCString id,link,title;
3310
3311#if 0 // commented out, since starting with a comment block is probably a usage error
3312 // see also http://stackoverflow.com/q/20478611/784672
3313
3314 // special case when the documentation starts with a code block
3315 // since the first line is skipped when looking for a code block later on.
3316 if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
3317 {
3318 i=writeCodeBlock(out,data,size,blockIndent);
3319 end=i+1;
3320 pi=-1;
3321 }
3322#endif
3323
3324 size_t currentIndent = indent;
3325 size_t listIndent = indent;
3326 bool insideList = false;
3327 bool newBlock = false;
3328 // process each line
3329 size_t i=0;
3330 while (i<data.size())
3331 {
3332 size_t end = findEndOfLine(data,i);
3333 // line is now found at [i..end)
3334
3335 size_t lineIndent=0;
3336 int level = 0;
3337 while (lineIndent<end && data[i+lineIndent]==' ') lineIndent++;
3338 //printf("** lineIndent=%d line=(%s)\n",lineIndent,qPrint(QCString(data+i).left(end-i)));
3339
3340 if (newBlock)
3341 {
3342 //printf("** end of block\n");
3343 if (insideList && lineIndent<currentIndent) // end of list
3344 {
3345 //printf("** end of list\n");
3346 currentIndent = indent;
3347 insideList = false;
3348 }
3349 newBlock = false;
3350 }
3351
3352 if ((listIndent=isListMarker(data.substr(i,end-i)))) // see if we need to increase the indent level
3353 {
3354 if (listIndent<currentIndent+4)
3355 {
3356 //printf("** start of list\n");
3357 insideList = true;
3358 currentIndent = listIndent;
3359 }
3360 }
3361 else if (isEndOfList(data.substr(i,end-i)))
3362 {
3363 //printf("** end of list\n");
3364 insideList = false;
3365 currentIndent = listIndent;
3366 }
3367 else if (isEmptyLine(data.substr(i,end-i)))
3368 {
3369 //printf("** new block\n");
3370 newBlock = true;
3371 }
3372
3373 //printf("indent=%d listIndent=%d blockIndent=%d\n",indent,listIndent,blockIndent);
3374
3375 //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
3376
3377 if (pi!=std::string::npos)
3378 {
3379 size_t blockStart=0, blockEnd=0, blockOffset=0;
3380 QCString lang;
3381 size_t blockIndent = currentIndent;
3382 size_t ref = 0;
3383 //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
3384 QCString endBlockName;
3385 if (data[i]=='@' || data[i]=='\\') endBlockName = isBlockCommand(data.substr(i),i);
3386 if (!endBlockName.isEmpty())
3387 {
3388 // handle previous line
3389 if (isLinkRef(data.substr(pi,i-pi),id,link,title))
3390 {
3391 linkRefs.emplace(id.lower().str(),LinkRef(link,title));
3392 }
3393 else
3394 {
3395 writeOneLineHeaderOrRuler(data.substr(pi,i-pi));
3396 }
3397 out+=data[i];
3398 i++;
3399 size_t l = endBlockName.length();
3400 while (i+l<data.size())
3401 {
3402 if ((data[i]=='\\' || data[i]=='@') && // command
3403 data[i-1]!='\\' && data[i-1]!='@') // not escaped
3404 {
3405 if (qstrncmp(&data[i+1],endBlockName.data(),l)==0)
3406 {
3407 out+=data[i];
3408 out+=endBlockName;
3409 i+=l+1;
3410 break;
3411 }
3412 }
3413 out+=data[i];
3414 i++;
3415 }
3416 }
3417 else if ((level=isHeaderline(data.substr(i),TRUE))>0)
3418 {
3419 //printf("Found header at %d-%d\n",i,end);
3420 while (pi<data.size() && data[pi]==' ') pi++;
3421 QCString header = data.substr(pi,i-pi-1);
3422 id = extractTitleId(header, level);
3423 //printf("header='%s' is='%s'\n",qPrint(header),qPrint(id));
3424 if (!header.isEmpty())
3425 {
3426 if (!id.isEmpty())
3427 {
3428 out+=level==1?"@section ":"@subsection ";
3429 out+=id;
3430 out+=" ";
3431 out+=header;
3432 out+="\n\n";
3433 }
3434 else
3435 {
3436 out+=level==1?"<h1>":"<h2>";
3437 out+=header;
3438 out+=level==1?"\n</h1>\n":"\n</h2>\n";
3439 }
3440 }
3441 else
3442 {
3443 out+="\n<hr>\n";
3444 }
3445 pi=std::string::npos;
3446 i=end;
3447 end=i+1;
3448 continue;
3449 }
3450 else if ((ref=isLinkRef(data.substr(pi),id,link,title)))
3451 {
3452 //printf("found link ref: id='%s' link='%s' title='%s'\n",
3453 // qPrint(id),qPrint(link),qPrint(title));
3454 linkRefs.emplace(id.lower().str(),LinkRef(link,title));
3455 i=ref+pi;
3456 end=i+1;
3457 }
3458 else if (isFencedCodeBlock(data.substr(pi),currentIndent,lang,blockStart,blockEnd,blockOffset))
3459 {
3460 //printf("Found FencedCodeBlock lang='%s' start=%d end=%d code={%s}\n",
3461 // qPrint(lang),blockStart,blockEnd,QCString(data+pi+blockStart).left(blockEnd-blockStart).data());
3462 writeFencedCodeBlock(data.substr(pi),lang.view(),blockStart,blockEnd);
3463 i=pi+blockOffset;
3464 pi=std::string::npos;
3465 end=i+1;
3466 continue;
3467 }
3468 else if (isCodeBlock(data.substr(i,end-i),i,blockIndent))
3469 {
3470 // skip previous line (it is empty anyway)
3471 i+=writeCodeBlock(data.substr(i),blockIndent);
3472 pi=std::string::npos;
3473 end=i+1;
3474 continue;
3475 }
3476 else if (isTableBlock(data.substr(pi)))
3477 {
3478 i=pi+writeTableBlock(data.substr(pi));
3479 pi=std::string::npos;
3480 end=i+1;
3481 continue;
3482 }
3483 else
3484 {
3485 writeOneLineHeaderOrRuler(data.substr(pi,i-pi));
3486 }
3487 }
3488 pi=i;
3489 i=end;
3490 }
3491 //printf("last line %d size=%d\n",i,size);
3492 if (pi!=std::string::npos && pi<data.size()) // deal with the last line
3493 {
3494 if (isLinkRef(data.substr(pi),id,link,title))
3495 {
3496 //printf("found link ref: id='%s' link='%s' title='%s'\n",
3497 // qPrint(id),qPrint(link),qPrint(title));
3498 linkRefs.emplace(id.lower().str(),LinkRef(link,title));
3499 }
3500 else
3501 {
3502 writeOneLineHeaderOrRuler(data.substr(pi));
3503 }
3504 }
3505
3506 return out;
3507}
3508
3509static bool isOtherPage(std::string_view data)
3510{
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);
3513 OPC(fn); OPC(property); OPC(typedef); OPC(var); OPC(def);
3514 OPC(enum); OPC(namespace); OPC(class); OPC(concept); OPC(module);
3515 OPC(protocol); OPC(category); OPC(union); OPC(struct); OPC(interface);
3516 OPC(idlexcept); OPC(file);
3517#undef OPC
3518
3519 return false;
3520}
3521
3523{
3524 AUTO_TRACE("docs={}",Trace::trunc(docs));
3525 size_t i=0;
3526 std::string_view data(docs.str());
3527 const size_t size = data.size();
3528 if (!data.empty())
3529 {
3530 while (i<size && (data[i]==' ' || data[i]=='\n'))
3531 {
3532 i++;
3533 }
3534 if (literal_at(data.substr(i),"<!--!")) // skip over <!--! marker
3535 {
3536 i+=5;
3537 while (i<size && (data[i]==' ' || data[i]=='\n')) // skip over spaces after the <!--! marker
3538 {
3539 i++;
3540 }
3541 }
3542 if (i+1<size &&
3543 (data[i]=='\\' || data[i]=='@') &&
3544 (literal_at(data.substr(i+1),"page ") || literal_at(data.substr(i+1),"mainpage"))
3545 )
3546 {
3547 if (literal_at(data.substr(i+1),"page "))
3548 {
3549 AUTO_TRACE_EXIT("result=ExplicitPageResult::explicitPage");
3551 }
3552 else
3553 {
3554 AUTO_TRACE_EXIT("result=ExplicitPageResult::explicitMainPage");
3556 }
3557 }
3558 else if (i+1<size && (data[i]=='\\' || data[i]=='@') && isOtherPage(data.substr(i+1)))
3559 {
3560 AUTO_TRACE_EXIT("result=ExplicitPageResult::explicitOtherPage");
3562 }
3563 }
3564 AUTO_TRACE_EXIT("result=ExplicitPageResult::notExplicit");
3566}
3567
3568QCString Markdown::extractPageTitle(QCString &docs, QCString &id, int &prepend, bool &isIdGenerated)
3569{
3570 AUTO_TRACE("docs={} prepend={}",Trace::trunc(docs),id,prepend);
3571 // first first non-empty line
3572 prepend = 0;
3573 QCString title;
3574 size_t i=0;
3575 QCString docs_org(docs);
3576 std::string_view data(docs_org.str());
3577 const size_t size = data.size();
3578 docs.clear();
3579 while (i<size && (data[i]==' ' || data[i]=='\n'))
3580 {
3581 if (data[i]=='\n') prepend++;
3582 i++;
3583 }
3584 if (i>=size) { return QCString(); }
3585 size_t end1=i+1;
3586 while (end1<size && data[end1-1]!='\n') end1++;
3587 //printf("i=%d end1=%d size=%d line='%s'\n",i,end1,size,docs.mid(i,end1-i).data());
3588 // first line from i..end1
3589 if (end1<size)
3590 {
3591 // second line form end1..end2
3592 size_t end2=end1+1;
3593 while (end2<size && data[end2-1]!='\n') end2++;
3594 if (prv->isHeaderline(data.substr(end1),FALSE))
3595 {
3596 title = data.substr(i,end1-i-1);
3597 docs+="\n\n"+docs_org.mid(end2);
3598 id = prv->extractTitleId(title, 0, &isIdGenerated);
3599 //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
3600 AUTO_TRACE_EXIT("result={} id={} isIdGenerated={}",Trace::trunc(title),id,isIdGenerated);
3601 return title;
3602 }
3603 }
3604 if (i<end1 && prv->isAtxHeader(data.substr(i,end1-i),title,id,FALSE,&isIdGenerated)>0)
3605 {
3606 docs+="\n";
3607 docs+=docs_org.mid(end1);
3608 }
3609 else
3610 {
3611 docs=docs_org;
3612 id = prv->extractTitleId(title, 0, &isIdGenerated);
3613 }
3614 AUTO_TRACE_EXIT("result={} id={} isIdGenerated={}",Trace::trunc(title),id,isIdGenerated);
3615 return title;
3616}
3617
3618
3619//---------------------------------------------------------------------------
3620
3621QCString Markdown::process(const QCString &input, int &startNewlines, bool fromParseInput)
3622{
3623 if (input.isEmpty()) return input;
3624 size_t refIndent=0;
3625
3626 // for replace tabs by spaces
3627 QCString s = input;
3628 if (s.at(s.length()-1)!='\n') s += "\n"; // see PR #6766
3629 s = detab(s,refIndent);
3630 //printf("======== DeTab =========\n---- output -----\n%s\n---------\n",qPrint(s));
3631
3632 // then process quotation blocks (as these may contain other blocks)
3633 s = prv->processQuotations(s.view(),refIndent);
3634 //printf("======== Quotations =========\n---- output -----\n%s\n---------\n",qPrint(s));
3635
3636 // then process block items (headers, rules, and code blocks, references)
3637 s = prv->processBlocks(s.view(),refIndent);
3638 //printf("======== Blocks =========\n---- output -----\n%s\n---------\n",qPrint(s));
3639
3640 // finally process the inline markup (links, emphasis and code spans)
3641 prv->out.clear();
3642 prv->out.reserve(s.length());
3643 prv->processInline(s.view());
3644 if (fromParseInput)
3645 {
3646 Debug::print(Debug::Markdown,0,"---- output -----\n{}\n=========\n",qPrint(prv->out));
3647 }
3648 else
3649 {
3650 Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n{}\n---- output -----\n{}\n=========\n",input,prv->out);
3651 }
3652
3653 // post processing
3654 QCString result = substitute(prv->out,g_doxy_nbsp,"&nbsp;");
3655 const char *p = result.data();
3656 if (p)
3657 {
3658 while (*p==' ') p++; // skip over spaces
3659 while (*p=='\n') {startNewlines++;p++;}; // skip over newlines
3660 if (literal_at(p,"<br>")) p+=4; // skip over <br>
3661 }
3662 if (p>result.data())
3663 {
3664 // strip part of the input
3665 result = result.mid(static_cast<int>(p-result.data()));
3666 }
3667 return result;
3668}
3669
3670//---------------------------------------------------------------------------
3671
3673{
3674 AUTO_TRACE("fileName={}",fileName);
3675 QCString absFileName = FileInfo(fileName.str()).absFilePath();
3676 QCString baseFn = stripFromPath(absFileName);
3677 int i = baseFn.findRev('.');
3678 if (i!=-1) baseFn = baseFn.left(i);
3679 QCString baseName = escapeCharsInString(baseFn,false,false);
3680 //printf("markdownFileNameToId(%s)=md_%s\n",qPrint(fileName),qPrint(baseName));
3681 QCString res = "md_"+baseName;
3682 AUTO_TRACE_EXIT("result={}",res);
3683 return res;
3684}
3685
3686//---------------------------------------------------------------------------
3687
3692
3694{
3695}
3696
3700
3702 const char *fileBuf,
3703 const std::shared_ptr<Entry> &root,
3704 ClangTUParser* /*clangParser*/)
3705{
3706 std::shared_ptr<Entry> current = std::make_shared<Entry>();
3707 int prepend = 0; // number of empty lines in front
3708 current->lang = SrcLangExt::Markdown;
3709 current->fileName = fileName;
3710 current->docFile = fileName;
3711 current->docLine = 1;
3712 QCString docs = stripIndentation(fileBuf);
3713 if (!docs.stripWhiteSpace().size()) return;
3714 Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n{}\n",fileBuf);
3715 QCString id;
3716 Markdown markdown(fileName,1,0);
3717 bool isIdGenerated = false;
3718 QCString title = markdown.extractPageTitle(docs, id, prepend, isIdGenerated).stripWhiteSpace();
3719 QCString generatedId;
3720 if (isIdGenerated)
3721 {
3722 generatedId = id;
3723 id = "";
3724 }
3725 int indentLevel=title.isEmpty() ? 0 : -1;
3726 markdown.setIndentLevel(indentLevel);
3727 FileInfo fi(fileName.str());
3728 QCString fn = fi.fileName();
3730 QCString mdfileAsMainPage = Config_getString(USE_MDFILE_AS_MAINPAGE);
3731 QCString mdFileNameId = markdownFileNameToId(fileName);
3732 bool wasEmpty = id.isEmpty();
3733 if (wasEmpty) id = mdFileNameId;
3734 QCString relFileName = stripFromPath(fileName);
3735 bool isSubdirDocs = Config_getBool(IMPLICIT_DIR_DOCS) && relFileName.lower().endsWith("/readme.md");
3736 switch (isExplicitPage(docs))
3737 {
3739 if (!mdfileAsMainPage.isEmpty() &&
3740 (fi.absFilePath()==FileInfo(mdfileAsMainPage.str()).absFilePath()) // file reference with path
3741 )
3742 {
3743 docs.prepend("@ianchor{" + title + "} " + id + "\\ilinebr ");
3744 docs.prepend("@mainpage "+title+"\\ilinebr ");
3745 }
3746 else if (id=="mainpage" || id=="index")
3747 {
3748 if (title.isEmpty()) title = titleFn;
3749 docs.prepend("@ianchor{" + title + "} " + id + "\\ilinebr ");
3750 docs.prepend("@mainpage "+title+"\\ilinebr ");
3751 }
3752 else if (isSubdirDocs)
3753 {
3754 if (!generatedId.isEmpty() && !title.isEmpty())
3755 {
3756 docs.prepend("@section " + generatedId + " " + title + "\\ilinebr ");
3757 }
3758 docs.prepend("@dir\\ilinebr ");
3759 }
3760 else
3761 {
3762 if (title.isEmpty())
3763 {
3764 title = titleFn;
3765 prepend = 0;
3766 }
3767 if (!wasEmpty)
3768 {
3769 docs.prepend("@ianchor{" + title + "} " + id + "\\ilinebr @ianchor{" + relFileName + "} " + mdFileNameId + "\\ilinebr ");
3770 }
3771 else if (!generatedId.isEmpty())
3772 {
3773 docs.prepend("@ianchor " + generatedId + "\\ilinebr ");
3774 }
3775 else if (Config_getEnum(MARKDOWN_ID_STYLE)==MARKDOWN_ID_STYLE_t::GITHUB)
3776 {
3777 QCString autoId = AnchorGenerator::instance().generate(title.str());
3778 docs.prepend("@ianchor{" + title + "} " + autoId + "\\ilinebr ");
3779 }
3780 docs.prepend("@page "+id+" "+title+"\\ilinebr ");
3781 }
3782 for (int i = 0; i < prepend; i++) docs.prepend("\n");
3783 break;
3785 {
3786 // look for `@page label My Title\n` and capture `label` (match[1]) and ` My Title` (match[2])
3787 static const reg::Ex re(R"([ ]*[\\@]page\s+(\a[\w-]*)(\s*[^\n]*)\n)");
3788 reg::Match match;
3789 std::string s = docs.str();
3790 if (reg::search(s,match,re))
3791 {
3792 QCString orgLabel = match[1].str();
3793 QCString orgTitle = match[2].str();
3794 orgTitle = orgTitle.stripWhiteSpace();
3795 QCString newLabel = markdownFileNameToId(fileName);
3796 docs = docs.left(match[1].position())+ // part before label
3797 newLabel+ // new label
3798 match[2].str()+ // part between orgLabel and \n
3799 "\\ilinebr @ianchor{" + orgTitle + "} "+orgLabel+"\n"+ // add original anchor plus \n of above
3800 docs.right(docs.length()-match.length()); // add remainder of docs
3801 }
3802 }
3803 break;
3805 break;
3807 break;
3808 }
3809 int lineNr=1;
3810
3811 p->commentScanner.enterFile(fileName,lineNr);
3812 Protection prot = Protection::Public;
3813 bool needsEntry = false;
3814 int position=0;
3815 GuardedSectionStack guards;
3816 QCString processedDocs = markdown.process(docs,lineNr,true);
3817 while (p->commentScanner.parseCommentBlock(
3818 this,
3819 current.get(),
3820 processedDocs,
3821 fileName,
3822 lineNr,
3823 FALSE, // isBrief
3824 FALSE, // javadoc autobrief
3825 FALSE, // inBodyDocs
3826 prot, // protection
3827 position,
3828 needsEntry,
3829 true,
3830 &guards
3831 ))
3832 {
3833 if (needsEntry)
3834 {
3835 QCString docFile = current->docFile;
3836 root->moveToSubEntryAndRefresh(current);
3837 current->lang = SrcLangExt::Markdown;
3838 current->docFile = docFile;
3839 current->docLine = lineNr;
3840 }
3841 }
3842 if (needsEntry)
3843 {
3844 root->moveToSubEntryAndKeep(current);
3845 }
3846 p->commentScanner.leaveFile(fileName,lineNr);
3847}
3848
3850{
3851 Doxygen::parserManager->getOutlineParser("*.cpp")->parsePrototype(text);
3852}
3853
3854//------------------------------------------------------------------------
#define eol
The end of line string for this machine.
static AnchorGenerator & instance()
Returns the singleton instance.
Definition anchor.cpp:38
static std::string addPrefixIfNeeded(const std::string &anchor)
Definition anchor.cpp:46
std::string generate(const std::string &title)
generates an anchor for a section with title.
Definition anchor.cpp:59
Clang parser object for a single translation unit, which consists of a source file and the directly o...
Definition clangparser.h:25
@ Markdown
Definition debug.h:37
static void print(DebugMask mask, int prio, fmt::format_string< Args... > fmt, Args &&... args)
Definition debug.h:76
static ParserManager * parserManager
Definition doxygen.h:131
static FileNameLinkedMap * imageNameLinkedMap
Definition doxygen.h:106
A model of a file symbol.
Definition filedef.h:99
Minimal replacement for QFileInfo.
Definition fileinfo.h:23
bool exists() const
Definition fileinfo.cpp:30
std::string fileName() const
Definition fileinfo.cpp:118
bool isReadable() const
Definition fileinfo.cpp:44
std::string absFilePath() const
Definition fileinfo.cpp:101
Helper class to process markdown formatted text.
Definition markdown.h:32
std::unique_ptr< Private > prv
Definition markdown.h:43
void setIndentLevel(int level)
Definition markdown.cpp:199
QCString extractPageTitle(QCString &docs, QCString &id, int &prepend, bool &isIdGenerated)
Markdown(const QCString &fileName, int lineNr, int indentLevel=0)
Definition markdown.cpp:190
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
Definition markdown.h:60
This is an alternative implementation of QCString.
Definition qcstring.h:101
int find(char c, int index=0, bool cs=TRUE) const
Definition qcstring.cpp:43
QCString & prepend(const char *s)
Definition qcstring.h:422
size_t length() const
Returns the length of the string, not counting the 0-terminator.
Definition qcstring.h:166
QCString mid(size_t index, size_t len=static_cast< size_t >(-1)) const
Definition qcstring.h:241
QCString lower() const
Definition qcstring.h:249
bool endsWith(const char *s) const
Definition qcstring.h:524
char & at(size_t i)
Returns a reference to the character at index i.
Definition qcstring.h:593
bool isEmpty() const
Returns TRUE iff the string is empty.
Definition qcstring.h:163
QCString stripWhiteSpace() const
returns a copy of this string with leading and trailing whitespace removed
Definition qcstring.h:260
const std::string & str() const
Definition qcstring.h:552
QCString & setNum(short n)
Definition qcstring.h:459
QCString simplifyWhiteSpace() const
return a copy of this string with leading and trailing whitespace removed and multiple whitespace cha...
Definition qcstring.cpp:190
QCString right(size_t len) const
Definition qcstring.h:234
size_t size() const
Returns the length of the string, not counting the 0-terminator.
Definition qcstring.h:169
QCString & sprintf(const char *format,...)
Definition qcstring.cpp:29
int findRev(char c, int index=-1, bool cs=TRUE) const
Definition qcstring.cpp:96
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition qcstring.h:172
std::string_view view() const
Definition qcstring.h:174
QCString left(size_t len) const
Definition qcstring.h:229
void clear()
Definition qcstring.h:182
static constexpr int Section
Definition section.h:33
static constexpr int MaxLevel
Definition section.h:39
static constexpr int Subsection
Definition section.h:34
static constexpr int Subsubsection
Definition section.h:35
static constexpr int MinLevel
Definition section.h:32
static constexpr int Paragraph
Definition section.h:36
static constexpr int Subsubparagraph
Definition section.h:38
static constexpr int Subparagraph
Definition section.h:37
Class representing a regular expression.
Definition regex.h:39
Object representing the matching results.
Definition regex.h:153
Interface for the comment block scanner.
std::stack< GuardedSection > GuardedSectionStack
Definition commentscan.h:48
#define Config_getInt(name)
Definition config.h:34
#define Config_getBool(name)
Definition config.h:33
#define Config_getString(name)
Definition config.h:32
#define Config_getEnum(name)
Definition config.h:35
std::vector< std::string > StringVector
Definition containers.h:33
DirIterator end(const DirIterator &) noexcept
Definition dir.cpp:175
#define AUTO_TRACE_ADD(...)
Definition docnode.cpp:47
#define AUTO_TRACE(...)
Definition docnode.cpp:46
#define AUTO_TRACE_EXIT(...)
Definition docnode.cpp:48
static bool isOtherPage(std::string_view data)
#define AUTO_TRACE(...)
Definition markdown.cpp:61
static bool hasLineBreak(std::string_view data)
#define isIdChar(c)
Definition markdown.cpp:77
ExplicitPageResult
Definition markdown.cpp:67
@ explicitMainPage
docs start with a mainpage command
Definition markdown.cpp:69
@ explicitPage
docs start with a page command
Definition markdown.cpp:68
@ notExplicit
docs doesn't start with either page or mainpage
Definition markdown.cpp:71
@ explicitOtherPage
docs start with a dir / defgroup / addtogroup command
Definition markdown.cpp:70
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)
Definition markdown.cpp:228
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
Definition markdown.cpp:310
const char * g_doxy_nbsp
Definition markdown.cpp:208
static QCString escapeSpecialChars(const QCString &s)
Definition markdown.cpp:246
#define OPC(x)
static bool isCodeBlock(std::string_view data, size_t offset, size_t &indent)
static bool isEmptyLine(std::string_view data)
#define AUTO_TRACE_EXIT(...)
Definition markdown.cpp:63
#define isLiTag(i)
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
Definition markdown.cpp:209
static ExplicitPageResult isExplicitPage(const QCString &docs)
const char * g_utf8_nbsp
Definition markdown.cpp:207
#define ignoreCloseEmphChar(c, cn)
Definition markdown.cpp:108
static const std::unordered_map< std::string, std::string > g_quotationHeaderMap
Alignment
Definition markdown.cpp:202
@ AlignLeft
Definition markdown.cpp:202
@ AlignNone
Definition markdown.cpp:202
@ AlignRight
Definition markdown.cpp:202
@ AlignCenter
Definition markdown.cpp:202
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
Definition markdown.cpp:331
bool skipOverFileAndLineCommands(std::string_view data, size_t indent, size_t &offset, std::string &location)
#define isAllowedEmphStr(data, offset)
Definition markdown.cpp:102
#define extraChar(c)
Definition markdown.cpp:84
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)
Definition markdown.cpp:215
QCString markdownFileNameToId(const QCString &fileName)
processes string s and converts markdown into doxygen/html commands.
#define warn(file, line, fmt,...)
Definition message.h:97
bool isAbsolutePath(const QCString &fileName)
Definition portable.cpp:498
const char * strnstr(const char *haystack, const char *needle, size_t haystack_len)
Definition portable.cpp:601
QCString trunc(const QCString &s, size_t numChars=15)
Definition trace.h:56
Definition message.h:144
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.
Definition regex.cpp:748
Portable versions of functions that are platform dependent.
static void decrLevel(yyscan_t yyscanner)
Definition pre.l:2240
QCString substitute(const QCString &s, const QCString &src, const QCString &dst)
substitute all occurrences of src in s by dst
Definition qcstring.cpp:482
int qstrncmp(const char *str1, const char *str2, size_t len)
Definition qcstring.h:75
bool qisspace(char c)
Definition qcstring.h:81
const char * qPrint(const char *s)
Definition qcstring.h:687
#define TRUE
Definition qcstring.h:37
#define FALSE
Definition qcstring.h:34
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
Definition stringutil.h:98
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...
Definition stringutil.h:72
int processEmphasis1(std::string_view data, char c)
process single emphasis
Definition markdown.cpp:833
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)
Definition markdown.cpp:447
std::function< int(std::string_view, size_t)> Action_t
Definition markdown.cpp:180
int processEmphasis3(std::string_view data, char c)
Parsing triple emphasis.
Definition markdown.cpp:899
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,...
Definition markdown.cpp:720
std::unordered_map< std::string, LinkRef > linkRefs
Definition markdown.cpp:182
void addStrEscapeUtf8Nbsp(std::string_view data)
QCString isBlockCommand(std::string_view data, size_t offset)
Definition markdown.cpp:378
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
Definition markdown.cpp:867
void processInline(std::string_view data)
int processNmdash(std::string_view data, size_t offset)
Process ndash and mdashes.
Definition markdown.cpp:961
void writeOneLineHeaderOrRuler(std::string_view data)
std::array< Action_t, 256 > actions
Definition markdown.cpp:187
Protection
Definition types.h:32
SrcLangExt
Definition types.h:207
SrcLangExt getLanguageFromFileName(const QCString &fileName, SrcLangExt defLang)
Definition util.cpp:5107
QCString stripIndentation(const QCString &s, bool skipFirstLine)
Definition util.cpp:5861
QCString escapeCharsInString(const QCString &name, bool allowDots, bool allowUnderscore)
Definition util.cpp:3251
QCString stripExtensionGeneral(const QCString &fName, const QCString &ext)
Definition util.cpp:4835
bool isURL(const QCString &url)
Checks whether the given url starts with a supported protocol.
Definition util.cpp:5825
static QCString stripFromPath(const QCString &p, const StringVector &l)
Definition util.cpp:307
QCString detab(const QCString &s, size_t &refIndent)
Definition util.cpp:6630
StringVector split(const std::string &s, const std::string &delimiter)
split input string s by string delimiter delimiter.
Definition util.cpp:6528
QCString externalLinkTarget(const bool parent)
Definition util.cpp:5621
QCString getFileNameExtension(const QCString &fn)
Definition util.cpp:5149
FileDef * findFileDef(const FileNameLinkedMap *fnMap, const QCString &n, bool &ambig)
Definition util.cpp:2823
A bunch of utility functions.