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