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