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