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