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