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