Md4qt

parser.h
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2022-2025 Igor Mironchik <igor.mironchik@gmail.com>
3 SPDX-License-Identifier: MIT
4*/
5
6#ifndef MD4QT_MD_PARSER_HPP_INCLUDED
7#define MD4QT_MD_PARSER_HPP_INCLUDED
8
9// md4qt include.
10#include "doc.h"
11#include "entities_map.h"
12#include "traits.h"
13#include "utils.h"
14
15#ifdef MD4QT_QT_SUPPORT
16
17// Qt include.
18#include <QDir>
19#include <QFile>
20#include <QTextStream>
21
22#endif // MD4QT_QT_SUPPORT
23
24#ifdef MD4QT_ICU_STL_SUPPORT
25
26// C++ include.
27#include <exception>
28
29#endif // MD4QT_ICU_STL_SUPPORT
30
31// C++ include.
32#include <algorithm>
33#include <cassert>
34#include <cmath>
35#include <fstream>
36#include <functional>
37#include <memory>
38#include <set>
39#include <tuple>
40#include <unordered_map>
41#include <vector>
42
43namespace MD
44{
45
46//! Starting HTML comment string.
47static const char *s_startComment = "<!--";
48
49//! \return Is \p indent indent belongs to list with previous \p indents indents.
50inline bool
51indentInList(const std::vector<long long int> *indents,
52 long long int indent,
53 bool codeIndentedBySpaces)
54{
55 if (indents && !indents->empty()) {
56 return (std::find_if(indents->cbegin(),
57 indents->cend(),
58 [indent, codeIndentedBySpaces](const auto &v) {
59 return (indent >= v && (codeIndentedBySpaces ?
60 true : indent <= v + 3));
61 })
62 != indents->cend());
63 } else {
64 return false;
65 }
66}
67
68//! Skip spaces in line from position \p i.
69template<class Trait>
70inline long long int
71skipSpaces(long long int i, const typename Trait::String &line)
72{
73 const auto length = line.length();
74
75 while (i < length && line[i].isSpace()) {
76 ++i;
77 }
78
79 return i;
80}
81
82//! \return Last non-space character position.
83template<class String>
84inline long long int
85lastNonSpacePos(const String &line)
86{
87 long long int i = line.length() - 1;
88
89 while (i >= 0 && line[i].isSpace()) {
90 --i;
91 }
92
93 return i;
94}
95
96//! Remove spaces at the end of string \p s.
97template<class String>
98inline void
100{
101 const auto i = lastNonSpacePos(s);
102
103 if (i != s.length() - 1) {
104 s.remove(i + 1, s.length() - i - 1);
105 }
106}
107
108//! \return Starting sequence of the same characters.
109template<class Trait>
110inline typename Trait::String
111startSequence(const typename Trait::String &line)
112{
113 auto pos = skipSpaces<Trait>(0, line);
114
115 if (pos >= line.length()) {
116 return {};
117 }
118
119 const auto sch = line[pos];
120 const auto start = pos;
121
122 ++pos;
123
124 while (pos < line.length() && line[pos] == sch) {
125 ++pos;
126 }
127
128 return line.sliced(start, pos - start);
129}
130
131//! \return Is string an ordered list.
132template<class Trait>
133inline bool
134isOrderedList(const typename Trait::String &s,
135 int *num = nullptr,
136 int *len = nullptr,
137 typename Trait::Char *delim = nullptr,
138 bool *isFirstLineEmpty = nullptr)
139{
140 long long int p = skipSpaces<Trait>(0, s);
141
142 long long int dp = p;
143
144 for (; p < s.size(); ++p) {
145 if (!s[p].isDigit()) {
146 break;
147 }
148 }
149
150 if (dp != p && p < s.size()) {
151 const auto digits = s.sliced(dp, p - dp);
152
153 if (digits.size() > 9) {
154 return false;
155 }
156
157 const auto i = digits.toInt();
158
159 if (num) {
160 *num = i;
161 }
162
163 if (len) {
164 *len = p - dp;
165 }
166
167 if (s[p] == Trait::latin1ToChar('.') || s[p] == Trait::latin1ToChar(')')) {
168 if (delim) {
169 *delim = s[p];
170 }
171
172 ++p;
173
174 long long int tmp = skipSpaces<Trait>(p, s);
175
176 if (isFirstLineEmpty) {
177 *isFirstLineEmpty = (tmp == s.size());
178 }
179
180 if ((p < s.size() && s[p] == Trait::latin1ToChar(' ')) || p == s.size()) {
181 return true;
182 }
183 }
184 }
185
186 return false;
187}
188
189//
190// RawHtmlBlock
191//
192
193//! Internal structure for pre-storing HTML.
194template<class Trait>
196 std::shared_ptr<RawHtml<Trait>> m_html = {};
197 std::shared_ptr<Block<Trait>> m_parent = {};
198 std::shared_ptr<Block<Trait>> m_topParent = {};
199 using SequenceOfBlock = std::vector<std::pair<std::shared_ptr<Block<Trait>>, long long int>>;
201 std::unordered_map<std::shared_ptr<Block<Trait>>, SequenceOfBlock> m_toAdjustLastPos = {};
203 bool m_continueHtml = false;
204 bool m_onLine = false;
205
206 std::shared_ptr<Block<Trait>>
207 findParent(long long int indent) const
208 {
209 for (auto it = m_blocks.crbegin(), last = m_blocks.crend(); it != last; ++it) {
210 if (indent >= it->second) {
211 return it->first;
212 }
213 }
214
215 return nullptr;
216 }
217}; // struct RawHtmlBlock
218
219//
220// MdLineData
221//
222
223//! Internal structure for auxiliary information about a line in Markdown.
225 long long int m_lineNumber = -1;
226 using CommentData = std::pair<char, bool>;
227 using CommentDataMap = std::map<long long int, CommentData>;
228 // std::pair< closed, valid >
230 // May this line break a list?
231 bool m_mayBreakList = false;
232}; // struct MdLineData
233
234//
235// MdBlock
236//
237
238//! Internal structure for block of text in Markdown.
239template<class Trait>
240struct MdBlock {
241 using Line = std::pair<typename Trait::InternalString, MdLineData>;
242 using Data = typename Trait::template Vector<Line>;
243
245 long long int m_emptyLinesBefore = 0;
246 bool m_emptyLineAfter = true;
247}; // struct MdBlock
248
249template<class Trait>
250inline long long int
253{
254 long long int count = 0;
255
256 if (it != begin) {
257 while (it != begin) {
258 it = std::prev(it);
259
260 if (it->first.asString().simplified().isEmpty()) {
261 ++count;
262 } else {
263 break;
264 }
265 }
266 }
267
268 return count;
269}
270
271//
272// StringListStream
273//
274
275//! Wrapper for typename Trait::StringList to be behaved like a stream.
276template<class Trait>
278{
279public:
281 : m_stream(stream)
282 , m_pos(0)
283 {
284 }
285
286 bool atEnd() const
287 {
288 return (m_pos >= (long long int)m_stream.size());
289 }
290
291 std::pair<typename Trait::InternalString, bool> readLine()
292 {
293 const std::pair<typename Trait::InternalString, bool> ret =
294 {m_stream.at(m_pos).first, m_stream.at(m_pos).second.m_mayBreakList};
295
296 ++m_pos;
297
298 return ret;
299 }
300
301 long long int currentLineNumber() const
302 {
303 return (m_pos < size() ? m_stream.at(m_pos).second.m_lineNumber :
304 (size() > 0 ? m_stream.at(0).second.m_lineNumber + size() : -1));
305 }
306
307 long long int currentStreamPos() const
308 {
309 return m_pos;
310 }
311
312 typename Trait::InternalString lineAt(long long int pos)
313 {
314 return m_stream.at(pos).first;
315 }
316
317 long long int size() const
318 {
319 return m_stream.size();
320 }
321
322 void setLineNumber(long long int lineNumber)
323 {
324 m_pos = 0;
325
326 m_pos += lineNumber - currentLineNumber();
327 }
328
329private:
330 typename MdBlock<Trait>::Data &m_stream;
331 long long int m_pos;
332}; // class StringListStream
333
334//! \return Is string a footnote?
335template<class Trait>
336inline bool
337isFootnote(const typename Trait::String &s)
338{
339 long long int p = skipSpaces<Trait>(0, s);
340
341 if (s.size() - p < 5) {
342 return false;
343 }
344
345 if (s[p++] != Trait::latin1ToChar('[')) {
346 return false;
347 }
348
349 if (s[p++] != Trait::latin1ToChar('^')) {
350 return false;
351 }
352
353 if (s[p] == Trait::latin1ToChar(']') || s[p].isSpace()) {
354 return false;
355 }
356
357 for (; p < s.size(); ++p) {
358 if (s[p] == Trait::latin1ToChar(']')) {
359 break;
360 } else if (s[p].isSpace()) {
361 return false;
362 }
363 }
364
365 ++p;
366
367 if (p < s.size() && s[p] == Trait::latin1ToChar(':')) {
368 return true;
369 } else {
370 return false;
371 }
372}
373
374//! \return Is string a code fences?
375template<class Trait>
376inline bool
377isCodeFences(const typename Trait::String &s, bool closing = false)
378{
379 auto p = skipSpaces<Trait>(0, s);
380
381 if (p > 3 || p == s.length()) {
382 return false;
383 }
384
385 const auto ch = s[p];
386
387 if (ch != Trait::latin1ToChar('~') && ch != Trait::latin1ToChar('`')) {
388 return false;
389 }
390
391 bool space = false;
392
393 long long int c = 1;
394 ++p;
395
396 for (; p < s.length(); ++p) {
397 if (s[p].isSpace()) {
398 space = true;
399 } else if (s[p] == ch) {
400 if (space && (closing ? true : ch == Trait::latin1ToChar('`'))) {
401 return false;
402 }
403
404 if (!space) {
405 ++c;
406 }
407 } else if (closing) {
408 return false;
409 } else {
410 break;
411 }
412 }
413
414 if (c < 3) {
415 return false;
416 }
417
418 if (ch == Trait::latin1ToChar('`')) {
419 for (; p < s.length(); ++p) {
420 if (s[p] == Trait::latin1ToChar('`')) {
421 return false;
422 }
423 }
424 }
425
426 return true;
427}
428
429//! Skip escaped sequence of characters till first space.
430template<class Trait>
431inline typename Trait::String
432readEscapedSequence(long long int i,
433 const typename Trait::String &str,
434 long long int *endPos = nullptr)
435{
436 bool backslash = false;
437 const auto start = i;
438
439 if (start >= str.length()) {
440 return {};
441 }
442
443 while (i < str.length()) {
444 bool now = false;
445
446 if (str[i] == Trait::latin1ToChar('\\') && !backslash) {
447 backslash = true;
448 now = true;
449 } else if (str[i].isSpace() && !backslash) {
450 break;
451 }
452
453 if (!now) {
454 backslash = false;
455 }
456
457 ++i;
458 }
459
460 if (endPos) {
461 *endPos = i - 1;
462 }
463
464 return str.sliced(start, i - start);
465}
466
467//! Characters that can be escaped.
468template<class Trait>
469static const typename Trait::String s_canBeEscaped =
470 Trait::latin1ToString("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
471
472//! Remove backslashes from the string.
473template<class String, class Trait>
474inline String
475removeBackslashes(const String &s)
476{
477 String r = s;
478 bool backslash = false;
479 long long int extra = 0;
480
481 for (long long int i = 0; i < s.length(); ++i) {
482 bool now = false;
483
484 if (s[i] == Trait::latin1ToChar('\\') && !backslash && i != s.length() - 1) {
485 backslash = true;
486 now = true;
487 } else if (s_canBeEscaped<Trait>.contains(s[i]) && backslash) {
488 r.remove(i - extra - 1, 1);
489 ++extra;
490 }
491
492 if (!now) {
493 backslash = false;
494 }
495 }
496
497 return r;
498}
499
500//! \return Is string a start of code?
501template<class Trait>
502inline bool
503isStartOfCode(const typename Trait::String &str,
504 typename Trait::String *syntax = nullptr,
505 WithPosition *delim = nullptr,
506 WithPosition *syntaxPos = nullptr)
507{
508 long long int p = skipSpaces<Trait>(0, str);
509
510 if (delim) {
511 delim->setStartColumn(p);
512 }
513
514 if (p > 3) {
515 return false;
516 }
517
518 if (str.size() - p < 3) {
519 return false;
520 }
521
522 const bool c96 = str[p] == Trait::latin1ToChar('`');
523 const bool c126 = str[p] == Trait::latin1ToChar('~');
524
525 if (c96 || c126) {
526 ++p;
527 long long int c = 1;
528
529 while (p < str.length()) {
530 if (str[p] != (c96 ? Trait::latin1ToChar('`') : Trait::latin1ToChar('~'))) {
531 break;
532 }
533
534 ++c;
535 ++p;
536 }
537
538 if (delim) {
539 delim->setEndColumn(p - 1);
540 }
541
542 if (c < 3) {
543 return false;
544 }
545
546 if (syntax) {
547 p = skipSpaces<Trait>(p, str);
548 long long int endSyntaxPos = p;
549
550 if (p < str.size()) {
552 readEscapedSequence<Trait>(p, str, &endSyntaxPos));
553
554 if (syntaxPos) {
555 syntaxPos->setStartColumn(p);
556 syntaxPos->setEndColumn(endSyntaxPos);
557 }
558 }
559 }
560
561 return true;
562 }
563
564 return false;
565}
566
567//! \return Is string a horizontal line?
568template<class Trait>
569inline bool
570isHorizontalLine(const typename Trait::String &s)
571{
572 if (s.size() < 3) {
573 return false;
574 }
575
576 typename Trait::Char c;
577
578 if (s[0] == Trait::latin1ToChar('*')) {
579 c = Trait::latin1ToChar('*');
580 } else if (s[0] == Trait::latin1ToChar('-')) {
581 c = Trait::latin1ToChar('-');
582 } else if (s[0] == Trait::latin1ToChar('_')) {
583 c = Trait::latin1ToChar('_');
584 } else {
585 return false;
586 }
587
588 long long int p = 1;
589 long long int count = 1;
590
591 for (; p < s.size(); ++p) {
592 if (s[p] != c && !s[p].isSpace()) {
593 break;
594 } else if (s[p] == c) {
595 ++count;
596 }
597 }
598
599 if (count < 3) {
600 return false;
601 }
602
603 if (p == s.size()) {
604 return true;
605 }
606
607 return false;
608}
609
610//! \return Is string a column alignment?
611template<class Trait>
612inline bool
613isColumnAlignment(const typename Trait::String &s)
614{
615 long long int p = skipSpaces<Trait>(0, s);
616
617 static const typename Trait::String s_legitime = Trait::latin1ToString(":-");
618
619 if (p >= s.length()) {
620 return false;
621 }
622
623 if (!s_legitime.contains(s[p])) {
624 return false;
625 }
626
627 if (s[p] == Trait::latin1ToChar(':')) {
628 ++p;
629 }
630
631 for (; p < s.size(); ++p) {
632 if (s[p] != Trait::latin1ToChar('-')) {
633 break;
634 }
635 }
636
637 if (p == s.size()) {
638 return true;
639 }
640
641 if (s[p] != Trait::latin1ToChar(':') && !s[p].isSpace()) {
642 return false;
643 }
644
645 ++p;
646
647 for (; p < s.size(); ++p) {
648 if (!s[p].isSpace()) {
649 return false;
650 }
651 }
652
653 return true;
654}
655
656//! Split string.
657template<class Trait>
658typename Trait::StringList
659splitString(const typename Trait::String &str, const typename Trait::Char &ch);
660
661#ifdef MD4QT_ICU_STL_SUPPORT
662
663template<>
666{
667 return str.split(ch);
668}
669
670#endif
671
672#ifdef MD4QT_QT_SUPPORT
673
674template<>
676splitString<QStringTrait>(const QString &str, const QChar &ch)
677{
678 return str.split(ch, Qt::SkipEmptyParts);
679}
680
681#endif
682
683//! \return Number of columns?
684template<class Trait>
685inline int
686isTableAlignment(const typename Trait::String &s)
687{
688 const auto columns = splitString<Trait>(s.simplified(), Trait::latin1ToChar('|'));
689
690 for (const auto &c : columns) {
691 if (!isColumnAlignment<Trait>(c)) {
692 return 0;
693 }
694 }
695
696 return columns.size();
697}
698
699//! \return Is given string a HTML comment.
700template<class Trait>
701inline bool
702isHtmlComment(const typename Trait::String &s)
703{
704 auto c = s;
705
706 if (s.startsWith(Trait::latin1ToString(s_startComment))) {
707 c.remove(0, 4);
708 } else {
709 return false;
710 }
711
712 long long int p = -1;
713 bool endFound = false;
714
715 while ((p = c.indexOf(Trait::latin1ToString("--"), p + 1)) > -1) {
716 if (c.size() > p + 2 && c[p + 2] == Trait::latin1ToChar('>')) {
717 if (!endFound) {
718 endFound = true;
719 } else {
720 return false;
721 }
722 } else if (p - 2 >= 0 && c.sliced(p - 2, 4) == Trait::latin1ToString("<!--")) {
723 return false;
724 } else if (c.size() > p + 3 && c.sliced(p, 4) == Trait::latin1ToString("--!>")) {
725 return false;
726 }
727 }
728
729 return endFound;
730}
731
732//! Replace entities in the string with corresponding character.
733template<class Trait>
734inline typename Trait::String
735replaceEntity(const typename Trait::String &s)
736{
737 long long int p1 = 0;
738
739 typename Trait::String res;
740 long long int i = 0;
741
742 while ((p1 = s.indexOf(Trait::latin1ToChar('&'), p1)) != -1) {
743 if (p1 > 0 && s[p1 - 1] == Trait::latin1ToChar('\\')) {
744 ++p1;
745
746 continue;
747 }
748
749 const auto p2 = s.indexOf(Trait::latin1ToChar(';'), p1);
750
751 if (p2 != -1) {
752 const auto en = s.sliced(p1, p2 - p1 + 1);
753
754 if (en.size() > 2 && en[1] == Trait::latin1ToChar('#')) {
755 if (en.size() > 3 && en[2].toLower() == Trait::latin1ToChar('x')) {
756 const auto hex = en.sliced(3, en.size() - 4);
757
758 if (hex.size() <= 6 && hex.size() > 0) {
759 bool ok = false;
760
761 const char32_t c = hex.toInt(&ok, 16);
762
763 if (ok) {
764 res.push_back(s.sliced(i, p1 - i));
765 i = p2 + 1;
766
767 if (c) {
768 Trait::appendUcs4(res, c);
769 } else {
770 res.push_back(typename Trait::Char(0xFFFD));
771 }
772 }
773 }
774 } else {
775 const auto dec = en.sliced(2, en.size() - 3);
776
777 if (dec.size() <= 7 && dec.size() > 0) {
778 bool ok = false;
779
780 const char32_t c = dec.toInt(&ok, 10);
781
782 if (ok) {
783 res.push_back(s.sliced(i, p1 - i));
784 i = p2 + 1;
785
786 if (c) {
787 Trait::appendUcs4(res, c);
788 } else {
789 res.push_back(typename Trait::Char(0xFFFD));
790 }
791 }
792 }
793 }
794 } else {
795 const auto it = s_entityMap<Trait>.find(en);
796
797 if (it != s_entityMap<Trait>.cend()) {
798 res.push_back(s.sliced(i, p1 - i));
799 i = p2 + 1;
800 res.push_back(Trait::utf16ToString(it->second));
801 }
802 }
803 } else {
804 break;
805 }
806
807 p1 = p2 + 1;
808 }
809
810 res.push_back(s.sliced(i, s.size() - i));
811
812 return res;
813}
814
815//! Remove backslashes in block.
816template<class Trait>
817inline typename MdBlock<Trait>::Data
819{
820 auto tmp = d;
821
822 for (auto &line : tmp) {
824 }
825
826 return tmp;
827}
828
829//! Type of the paragraph's optimization.
831 //! Full optimization.
833 //! Semi optimization, optimization won't concatenate text
834 //! items if style delimiters will be in the middle.
836 //! Full optimization, but raw text data won't be concatenated (will be untouched).
838 //! Semi optimization, but raw text data won't be concatenated (will be untouched).
840};
841
842//
843// TextPlugin
844//
845
846//! ID of text plugin.
847enum TextPlugin : int {
848 //! Unknown plugin.
850 //! GitHub's autolinks plugin.
852 //! First user defined plugin ID.
854}; // enum TextPlugin
855
856//
857// Style
858//
859
860//! Emphasis type.
861enum class Style {
862 //! "*"
864 //! "_"
866 //! "**"
868 //! "__"
870 //! "~"
872 //! Unknown.
874};
875
876//! \return Text option from style.
877inline TextOption
879{
880 switch (s) {
881 case Style::Italic1:
882 case Style::Italic2:
883 return ItalicText;
884
885 case Style::Bold1:
886 case Style::Bold2:
887 return BoldText;
888
890 return StrikethroughText;
891
892 default:
893 return TextWithoutFormat;
894 }
895}
896
897//
898// TextPluginFunc
899//
900
901template<class Trait>
902struct TextParsingOpts;
903
904//! Functor type for text plugin.
905template<class Trait>
906using TextPluginFunc = std::function<void(std::shared_ptr<Paragraph<Trait>>,
908 const typename Trait::StringList &)>;
909
910//
911// TextPluginsMap
912//
913
914//! Type of the map of text plugins.
915template<class Trait>
916using TextPluginsMap = std::map<int, std::tuple<TextPluginFunc<Trait>,
917 bool,
918 typename Trait::StringList>>;
919
920//
921// TextParsingOpts
922//
923
924//! Internal structure for auxiliary options for parser.
925template<class Trait>
928 std::shared_ptr<Block<Trait>> m_parent;
929 std::shared_ptr<RawHtml<Trait>> m_tmpHtml;
930 std::shared_ptr<Document<Trait>> m_doc;
931 typename Trait::StringList &m_linksToParse;
932 typename Trait::String m_workingPath;
933 typename Trait::String m_fileName;
938 std::shared_ptr<Text<Trait>> m_lastText = {};
939 bool m_wasRefLink = false;
941 // This flag is set only in second step!
943 bool m_headingAllowed = false;
944
945 struct TextData {
946 typename Trait::String m_str;
947 long long int m_pos = -1;
948 long long int m_line = -1;
949 };
950
951 std::vector<TextData> m_rawTextData = {};
952
953 inline void
954 concatenateAuxText(long long int start, long long int end)
955 {
956 if (start < end && (end - start > 1)) {
957 for (auto i = start + 1; i < end; ++i) {
958 m_rawTextData[start].m_str += m_rawTextData[i].m_str;
959 }
960
961 m_rawTextData.erase(m_rawTextData.cbegin() + start + 1, m_rawTextData.cbegin() + end);
962 }
963 }
964
965 enum class Detected { Nothing, Table, HTML, Code, List, Blockquote }; // enum class Detected
966
968
969 inline bool
971 {
972 switch (m_detected) {
973 case Detected::Table:
974 case Detected::Code:
975 case Detected::List:
977 case Detected::HTML:
978 return true;
979
980 default:
981 return false;
982 }
983 }
984
985 long long int m_line = 0;
986 long long int m_pos = 0;
987 long long int m_startTableLine = -1;
988 long long int m_lastTextLine = -1;
989 long long int m_lastTextPos = -1;
992
993 struct StyleInfo {
995 long long int m_length;
997 };
998
999 std::vector<StyleInfo> m_styles = {};
1001 std::shared_ptr<ItemWithOpts<Trait>> m_lastItemWithStyle = nullptr;
1002}; // struct TextParsingOpts
1003
1004//! Reset pre-stored HTML.
1005template<class Trait>
1007{
1008 html.m_html.reset();
1009 html.m_parent.reset();
1010 html.m_htmlBlockType = -1;
1011 html.m_continueHtml = false;
1012 html.m_onLine = false;
1013
1014 if (po && po->m_detected == TextParsingOpts<Trait>::Detected::HTML) {
1016 }
1017}
1018
1019//
1020// virginSubstr
1021//
1022
1023//! \return Substring from fragment with given virgin positions.
1024template<class Trait>
1025inline typename Trait::String
1026virginSubstr(const MdBlock<Trait> &fr, const WithPosition &virginPos)
1027{
1028 if (fr.m_data.empty()) {
1029 return {};
1030 }
1031
1032 long long int startLine = virginPos.startLine() < fr.m_data.at(0).second.m_lineNumber ?
1033 (virginPos.endLine() < fr.m_data.at(0).second.m_lineNumber ? -1 : 0) :
1034 virginPos.startLine() - fr.m_data.at(0).second.m_lineNumber;
1035
1036 if (startLine >= static_cast<long long int>(fr.m_data.size()) || startLine < 0) {
1037 return {};
1038 }
1039
1040 auto spos = virginPos.startColumn() - fr.m_data.at(startLine).first.virginPos(0);
1041
1042 if (spos < 0) {
1043 spos = 0;
1044 }
1045
1046 long long int epos = 0;
1047 long long int linesCount = virginPos.endLine() - virginPos.startLine() -
1048 (virginPos.startLine() < fr.m_data.at(0).second.m_lineNumber ?
1049 fr.m_data.at(0).second.m_lineNumber - virginPos.startLine() : 0);
1050
1051 if (startLine + linesCount > static_cast<long long int>(fr.m_data.size())) {
1052 linesCount = fr.m_data.size() - startLine - 1;
1053 epos = fr.m_data.back().first.length();
1054 } else {
1055 epos = virginPos.endColumn() - fr.m_data.at(linesCount + startLine).first.virginPos(0) + 1;
1056 }
1057
1058 if (epos < 0) {
1059 epos = 0;
1060 }
1061
1062 if (epos > fr.m_data.at(linesCount + startLine).first.length()) {
1063 epos = fr.m_data.at(linesCount + startLine).first.length();
1064 }
1065
1066 typename Trait::String str =
1067 (linesCount ? fr.m_data.at(startLine).first.sliced(spos).asString() :
1068 fr.m_data.at(startLine).first.sliced(spos, epos - spos).asString());
1069
1070 long long int i = startLine + 1;
1071
1072 for (; i < startLine + linesCount; ++i) {
1073 str.push_back(Trait::latin1ToString("\n"));
1074 str.push_back(fr.m_data.at(i).first.asString());
1075 }
1076
1077 if (linesCount) {
1078 str.push_back(Trait::latin1ToString("\n"));
1079 str.push_back(fr.m_data.at(i).first.sliced(0, epos).asString());
1080 }
1081
1082 return str;
1083}
1084
1085//
1086// localPosFromVirgin
1087//
1088
1089//! \return Local position ( { column, line } ) in fragment for given virgin position if exists.
1090//! \return { -1, -1 } if there is no given position.
1091template<class Trait>
1092inline std::pair<long long int, long long int>
1093localPosFromVirgin(const MdBlock<Trait> &fr, long long int virginColumn, long long int virginLine)
1094{
1095 if (fr.m_data.empty()) {
1096 return {-1, -1};
1097 }
1098
1099 if (fr.m_data.front().second.m_lineNumber > virginLine ||
1100 fr.m_data.back().second.m_lineNumber < virginLine) {
1101 return {-1, -1};
1102 }
1103
1104 auto line = virginLine - fr.m_data.front().second.m_lineNumber;
1105
1106 if (fr.m_data.at(line).first.isEmpty()) {
1107 return {-1, -1};
1108 }
1109
1110 const auto vzpos = fr.m_data.at(line).first.virginPos(0);
1111
1112 if (vzpos > virginColumn || virginColumn > vzpos + fr.m_data.at(line).first.length() - 1) {
1113 return {-1, -1};
1114 }
1115
1116 return {virginColumn - vzpos, line};
1117}
1118
1119//
1120// GitHubAutolinkPlugin
1121//
1122
1123/*
1124 "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
1125 "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
1126*/
1127//! \return Is the given string a valid email?
1128template<class Trait>
1129inline bool
1130isEmail(const typename Trait::String &url)
1131{
1132 auto isAllowed = [](const typename Trait::Char &ch) -> bool {
1133 const auto unicode = ch.unicode();
1134 return ((unicode >= 48 && unicode <= 57) || (unicode >= 97 && unicode <= 122) ||
1135 (unicode >= 65 && unicode <= 90));
1136 };
1137
1138 auto isAdditional = [](const typename Trait::Char &ch) -> bool {
1139 const auto unicode = ch.unicode();
1140 return (unicode == 33 || (unicode >= 35 && unicode <= 39) ||
1141 unicode == 42 || unicode == 43 || (unicode >= 45 && unicode <= 47) ||
1142 unicode == 61 || unicode == 63 || (unicode >= 94 && unicode <= 96) ||
1143 (unicode >= 123 && unicode <= 126));
1144 };
1145
1146 static const auto s_delim = Trait::latin1ToChar('-');
1147 static const auto s_dog = Trait::latin1ToChar('@');
1148 static const auto s_dot = Trait::latin1ToChar('.');
1149
1150 long long int i = (url.startsWith(Trait::latin1ToString("mailto:")) ? 7 : 0);
1151 const auto dogPos = url.indexOf(s_dog, i);
1152
1153 if (dogPos != -1) {
1154 if (i == dogPos) {
1155 return false;
1156 }
1157
1158 for (; i < dogPos; ++i) {
1159 if (!isAllowed(url[i]) && !isAdditional(url[i])) {
1160 return false;
1161 }
1162 }
1163
1164 auto checkToDot = [&](long long int start, long long int dotPos) -> bool {
1165 static const long long int maxlen = 63;
1166
1167 if (dotPos - start > maxlen ||
1168 start + 1 > dotPos ||
1169 start >= url.length() ||
1170 dotPos > url.length()) {
1171 return false;
1172 }
1173
1174 if (url[start] == s_delim) {
1175 return false;
1176 }
1177
1178 if (url[dotPos - 1] == s_delim) {
1179 return false;
1180 }
1181
1182 for (; start < dotPos; ++start) {
1183 if (!isAllowed(url[start]) && url[start] != s_delim) {
1184 return false;
1185 }
1186 }
1187
1188 return true;
1189 };
1190
1191 long long int dotPos = url.indexOf(s_dot, dogPos + 1);
1192
1193 if (dotPos != -1) {
1194 i = dogPos + 1;
1195
1196 while (dotPos != -1) {
1197 if (!checkToDot(i, dotPos)) {
1198 return false;
1199 }
1200
1201 i = dotPos + 1;
1202 dotPos = url.indexOf(s_dot, i);
1203 }
1204
1205 if (!checkToDot(i, url.length())) {
1206 return false;
1207 }
1208
1209 return true;
1210 }
1211 }
1212
1213 return false;
1214}
1215
1216//! \return Is the fiven string a valid URL?
1217template<class Trait>
1218inline bool
1219isValidUrl(const typename Trait::String &url);
1220
1221//! \return Is the given string a GitHub autolink?
1222template<class Trait>
1223inline bool
1224isGitHubAutolink(const typename Trait::String &url);
1225
1226#ifdef MD4QT_QT_SUPPORT
1227
1228template<>
1229inline bool
1231{
1232 const QUrl u(url, QUrl::StrictMode);
1233
1234 return (u.isValid() && !u.isRelative());
1235}
1236
1237template<>
1238inline bool
1240{
1241 const QUrl u(url, QUrl::StrictMode);
1242
1243 return (u.isValid()
1244 && ((!u.scheme().isEmpty() && !u.host().isEmpty())
1245 || (url.startsWith(QStringLiteral("www.")) && url.length() >= 7 &&
1246 url.indexOf(QLatin1Char('.'), 4) != -1)));
1247}
1248
1249#endif
1250
1251#ifdef MD4QT_ICU_STL_SUPPORT
1252
1253template<>
1254inline bool
1256{
1257 const UrlUri u(url);
1258
1259 return (u.isValid() && !u.isRelative());
1260}
1261
1262template<>
1263inline bool
1265{
1266 const UrlUri u(url);
1267
1268 return (u.isValid()
1269 && ((!u.scheme().isEmpty() && !u.host().isEmpty())
1270 || (url.startsWith(UnicodeString("www.")) && url.length() >= 7 &&
1271 url.indexOf(UnicodeChar('.'), 4) != -1)));
1272}
1273
1274#endif
1275
1276//! Process GitHub autolinks for the text with index \p idx.
1277template<class Trait>
1278inline long long int
1281 long long int idx)
1282{
1283 if (idx < 0 || idx >= (long long int)po.m_rawTextData.size()) {
1284 return idx;
1285 }
1286
1287 static const auto s_delims = Trait::latin1ToString("*_~()<>");
1288 auto s = po.m_rawTextData[idx];
1289 bool first = true;
1290 long long int j = 0;
1291 auto end = typename Trait::Char(0x00);
1292 bool skipSpace = true;
1293 long long int ret = idx;
1294
1295 while (s.m_str.length()) {
1296 long long int i = 0;
1297 end = typename Trait::Char(0x00);
1298
1299 for (; i < s.m_str.length(); ++i) {
1300 if (first) {
1301 if (s.m_str[i] == Trait::latin1ToChar('(')) {
1302 end = Trait::latin1ToChar(')');
1303 }
1304
1305 if (s_delims.indexOf(s.m_str[i]) == -1 && !s.m_str[i].isSpace()) {
1306 first = false;
1307 j = i;
1308 }
1309 } else {
1310 if (s.m_str[i].isSpace() || i == s.m_str.length() - 1 || s.m_str[i] == end) {
1311 auto tmp = s.m_str.sliced(j, i - j +
1312 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1313 1 : 0));
1314 skipSpace = s.m_str[i].isSpace();
1315
1316 const auto email = isEmail<Trait>(tmp);
1317
1318 if (isGitHubAutolink<Trait>(tmp) || email) {
1319 auto ti = textAtIdx(p, idx);
1320
1321 if (ti >= 0 && ti < static_cast<long long int>(p->items().size())) {
1322 typename ItemWithOpts<Trait>::Styles openStyles, closeStyles;
1323 const auto opts = std::static_pointer_cast<Text<Trait>>(p->items().at(ti))->opts();
1324
1325 if (j == 0 || s.m_str.sliced(0, j).isEmpty()) {
1326 openStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->openStyles();
1327 closeStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->closeStyles();
1328 p->removeItemAt(ti);
1329 po.m_rawTextData.erase(po.m_rawTextData.cbegin() + idx);
1330 --ret;
1331 } else {
1332 const auto tmp = s.m_str.sliced(0, j);
1333
1334 auto t = std::static_pointer_cast<Text<Trait>>(p->items().at(ti));
1335 t->setEndColumn(po.m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j - 1));
1336 closeStyles = t->closeStyles();
1337 t->closeStyles() = {};
1338 po.m_rawTextData[idx].m_str = tmp;
1339 ++idx;
1341 ++ti;
1342 }
1343
1344 std::shared_ptr<Link<Trait>> lnk(new Link<Trait>);
1345 lnk->setStartColumn(po.m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j));
1346 lnk->setStartLine(po.m_fr.m_data.at(s.m_line).second.m_lineNumber);
1347 lnk->setEndColumn(
1348 po.m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + i -
1349 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1350 0 : 1)));
1351 lnk->setEndLine(po.m_fr.m_data.at(s.m_line).second.m_lineNumber);
1352 lnk->openStyles() = openStyles;
1353 lnk->setTextPos({lnk->startColumn(), lnk->startLine(), lnk->endColumn(), lnk->endLine()});
1354 lnk->setUrlPos(lnk->textPos());
1355
1356 if (email && !tmp.toLower().startsWith(Trait::latin1ToString("mailto:"))) {
1357 tmp = Trait::latin1ToString("mailto:") + tmp;
1358 }
1359
1360 if (!email && tmp.toLower().startsWith(Trait::latin1ToString("www."))) {
1361 tmp = Trait::latin1ToString("http://") + tmp;
1362 }
1363
1364 lnk->setUrl(tmp);
1365 lnk->setOpts(opts);
1366 p->insertItem(ti, lnk);
1367
1368 s.m_pos += i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1);
1369 s.m_str.remove(0, i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1));
1370 j = 0;
1371 i = 0;
1372
1373 if (!s.m_str.isEmpty()) {
1374 po.m_rawTextData.insert(po.m_rawTextData.cbegin() + idx, s);
1375 ++ret;
1376
1377 auto t = std::make_shared<Text<Trait>>();
1378 t->setStartColumn(po.m_fr.m_data[s.m_line].first.virginPos(s.m_pos));
1379 t->setStartLine(po.m_fr.m_data.at(s.m_line).second.m_lineNumber);
1380 t->setEndLine(po.m_fr.m_data.at(s.m_line).second.m_lineNumber);
1381 t->setEndColumn(po.m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + s.m_str.length() - 1));
1383 t->closeStyles() = closeStyles;
1384 p->insertItem(ti + 1, t);
1385 } else {
1386 lnk->closeStyles() = closeStyles;
1387 }
1388
1389 break;
1390 }
1391 }
1392
1393 j = i + (skipSpace ? 1 : 0);
1394 }
1395 }
1396 }
1397
1398 first = true;
1399
1400 if (i == s.m_str.length()) {
1401 break;
1402 }
1403 }
1404
1405 return ret;
1406}
1407
1408//! GitHub autolinks plugin.
1409template<class Trait>
1410inline void
1413 const typename Trait::StringList &)
1414{
1415 if (!po.m_collectRefLinks) {
1416 long long int i = 0;
1417
1418 while (i >= 0 && i < (long long int)po.m_rawTextData.size()) {
1419 i = processGitHubAutolinkExtension(p, po, i);
1420
1421 ++i;
1422 }
1423 }
1424}
1425
1426//
1427// Parser
1428//
1429
1430//! Markdown parser.
1431template<class Trait>
1432class Parser final
1433{
1434public:
1439
1440 ~Parser() = default;
1441
1442 //! \return Parsed Markdown document.
1443 std::shared_ptr<Document<Trait>>
1444 parse(
1445 //! File name of the Markdown document.
1446 const typename Trait::String &fileName,
1447 //! Should parsing be recursive? If recursive all links to existing Markdown
1448 //! files will be parsed and presented in the returned document.
1449 bool recursive = true,
1450 //! Allowed extensions for Markdonw document files. If Markdown file doesn't
1451 //! have given extension it will be ignored.
1452 const typename Trait::StringList &ext = {Trait::latin1ToString("md"), Trait::latin1ToString("markdown")},
1453 //! Make full optimization, or just semi one. In full optimization
1454 //! text items with one style but with some closing delimiters
1455 //! in the middle will be concatenated in one, like in **text* text*,
1456 //! here in full optimization will be "text text" with 2 open/close
1457 //! style delimiters, but one closing delimiter is in the middle.
1458 bool fullyOptimizeParagraphs = true);
1459
1460 //! \return Parsed Markdown document.
1461 std::shared_ptr<Document<Trait>>
1462 parse(
1463 //! Stream to parse.
1464 typename Trait::TextStream &stream,
1465 //! Absolute path to the root folder for the document.
1466 //! This path will be used to resolve local links.
1467 const typename Trait::String &path,
1468 //! This argument needed only for anchor.
1469 const typename Trait::String &fileName,
1470 //! Make full optimization, or just semi one. In full optimization
1471 //! text items with one style but with some closing delimiters
1472 //! in the middle will be concatenated in one, like in **text* text*,
1473 //! here in full optimization will be "text text" with 2 open/close
1474 //! style delimiters, but one closing delimiter is in the middle.
1475 bool fullyOptimizeParagraphs = true);
1476
1477 //! \return Parsed Markdown document.
1478 std::shared_ptr<Document<Trait>>
1479 parse(
1480 //! File name of the Markdown document (full path).
1481 const typename Trait::String &fileName,
1482 //! Absolute path to the working directory for the document.
1483 //! This path will be used to resolve local links.
1484 //! \warning This path should be in \p fileName path.
1485 const typename Trait::String &workingDirectory,
1486 //! Should parsing be recursive? If recursive all links to existing Markdown
1487 //! files will be parsed and presented in the returned document.
1488 bool recursive = true,
1489 //! Allowed extensions for Markdonw document files. If Markdown file doesn't
1490 //! have given extension it will be ignored.
1491 const typename Trait::StringList &ext = {Trait::latin1ToString("md"), Trait::latin1ToString("markdown")},
1492 //! Make full optimization, or just semi one. In full optimization
1493 //! text items with one style but with some closing delimiters
1494 //! in the middle will be concatenated in one, like in **text* text*,
1495 //! here in full optimization will be "text text" with 2 open/close
1496 //! style delimiters, but one closing delimiter is in the middle.
1497 bool fullyOptimizeParagraphs = true);
1498
1499 //! Add text plugin.
1500 void
1502 //! ID of a plugin. Use TextPlugin::UserDefinedPluginID value for start ID.
1503 int id,
1504 //! Function of a plugin, that will be invoked to processs raw text.
1505 TextPluginFunc<Trait> plugin,
1506 //! Should this plugin be used in parsing of internals of links?
1507 bool processInLinks,
1508 //! User data that will be passed to plugin function.
1509 const typename Trait::StringList &userData)
1510 {
1511 m_textPlugins.insert({id, {plugin, processInLinks, userData}});
1512 }
1513
1514 //! Remove text plugin.
1515 void
1517 //! ID of plugin that should be removed.
1518 int id)
1519 {
1520 m_textPlugins.erase(id);
1521 }
1522
1523private:
1524 void
1525 parseFile(const typename Trait::String &fileName,
1526 bool recursive,
1527 std::shared_ptr<Document<Trait>> doc,
1528 const typename Trait::StringList &ext,
1529 typename Trait::StringList *parentLinks = nullptr,
1530 typename Trait::String workingDirectory = {});
1531
1532 void
1533 parseStream(typename Trait::TextStream &stream,
1534 const typename Trait::String &workingPath,
1535 const typename Trait::String &fileName,
1536 bool recursive,
1537 std::shared_ptr<Document<Trait>> doc,
1538 const typename Trait::StringList &ext,
1539 typename Trait::StringList *parentLinks = nullptr,
1540 const typename Trait::String &workingDirectory = {});
1541
1542 void
1543 clearCache();
1544
1545 enum class BlockType {
1546 Unknown,
1547 EmptyLine,
1548 Text,
1549 List,
1550 ListWithFirstEmptyLine,
1551 CodeIndentedBySpaces,
1552 Code,
1553 Blockquote,
1554 Heading,
1555 SomethingInList,
1556 FensedCodeInList,
1557 Footnote
1558 }; // enum BlockType
1559
1560 struct ListIndent {
1561 long long int m_level = -1;
1562 long long int m_indent = -1;
1563 }; // struct ListIndent
1564
1565 BlockType
1566 whatIsTheLine(typename Trait::InternalString &str,
1567 bool inList = false,
1568 bool inListWithFirstEmptyLine = false,
1569 bool fensedCodeInList = false,
1570 typename Trait::String *startOfCode = nullptr,
1571 ListIndent *indent = nullptr,
1572 bool emptyLinePreceded = false,
1573 bool calcIndent = false,
1574 const std::vector<long long int> *indents = nullptr);
1575
1576 long long int
1577 parseFragment(MdBlock<Trait> &fr,
1578 std::shared_ptr<Block<Trait>> parent,
1579 std::shared_ptr<Document<Trait>> doc,
1580 typename Trait::StringList &linksToParse,
1581 const typename Trait::String &workingPath,
1582 const typename Trait::String &fileName,
1583 bool collectRefLinks,
1584 RawHtmlBlock<Trait> &html);
1585
1586 long long int
1587 parseText(MdBlock<Trait> &fr,
1588 std::shared_ptr<Block<Trait>> parent,
1589 std::shared_ptr<Document<Trait>> doc,
1590 typename Trait::StringList &linksToParse,
1591 const typename Trait::String &workingPath,
1592 const typename Trait::String &fileName,
1593 bool collectRefLinks,
1594 RawHtmlBlock<Trait> &html);
1595
1596 long long int
1597 parseBlockquote(MdBlock<Trait> &fr,
1598 std::shared_ptr<Block<Trait>> parent,
1599 std::shared_ptr<Document<Trait>> doc,
1600 typename Trait::StringList &linksToParse,
1601 const typename Trait::String &workingPath,
1602 const typename Trait::String &fileName,
1603 bool collectRefLinks,
1604 RawHtmlBlock<Trait> &html);
1605
1606 long long int
1607 parseList(MdBlock<Trait> &fr,
1608 std::shared_ptr<Block<Trait>> parent,
1609 std::shared_ptr<Document<Trait>> doc,
1610 typename Trait::StringList &linksToParse,
1611 const typename Trait::String &workingPath,
1612 const typename Trait::String &fileName,
1613 bool collectRefLinks,
1614 RawHtmlBlock<Trait> &html);
1615
1616 long long int
1617 parseCode(MdBlock<Trait> &fr,
1618 std::shared_ptr<Block<Trait>> parent,
1619 bool collectRefLinks);
1620
1621 long long int
1622 parseCodeIndentedBySpaces(MdBlock<Trait> &fr,
1623 std::shared_ptr<Block<Trait>> parent,
1624 bool collectRefLinks,
1625 int indent,
1626 const typename Trait::String &syntax,
1627 long long int emptyColumn,
1628 long long int startLine,
1629 bool fensedCode,
1630 const WithPosition &startDelim = {},
1631 const WithPosition &endDelim = {},
1632 const WithPosition &syntaxPos = {});
1633
1634 long long int
1635 parseListItem(MdBlock<Trait> &fr,
1636 std::shared_ptr<Block<Trait>> parent,
1637 std::shared_ptr<Document<Trait>> doc,
1638 typename Trait::StringList &linksToParse,
1639 const typename Trait::String &workingPath,
1640 const typename Trait::String &fileName,
1641 bool collectRefLinks,
1642 RawHtmlBlock<Trait> &html,
1643 std::shared_ptr<ListItem<Trait>> *resItem = nullptr);
1644
1645 void
1646 parseHeading(MdBlock<Trait> &fr,
1647 std::shared_ptr<Block<Trait>> parent,
1648 std::shared_ptr<Document<Trait>> doc,
1649 typename Trait::StringList &linksToParse,
1650 const typename Trait::String &workingPath,
1651 const typename Trait::String &fileName,
1652 bool collectRefLinks);
1653
1654 void
1655 parseFootnote(MdBlock<Trait> &fr,
1656 std::shared_ptr<Block<Trait>> parent,
1657 std::shared_ptr<Document<Trait>> doc,
1658 typename Trait::StringList &linksToParse,
1659 const typename Trait::String &workingPath,
1660 const typename Trait::String &fileName,
1661 bool collectRefLinks);
1662
1663 void
1664 parseTable(MdBlock<Trait> &fr,
1665 std::shared_ptr<Block<Trait>> parent,
1666 std::shared_ptr<Document<Trait>> doc,
1667 typename Trait::StringList &linksToParse,
1668 const typename Trait::String &workingPath,
1669 const typename Trait::String &fileName,
1670 bool collectRefLinks,
1671 int columnsCount);
1672
1673 long long int
1674 parseParagraph(MdBlock<Trait> &fr,
1675 std::shared_ptr<Block<Trait>> parent,
1676 std::shared_ptr<Document<Trait>> doc,
1677 typename Trait::StringList &linksToParse,
1678 const typename Trait::String &workingPath,
1679 const typename Trait::String &fileName,
1680 bool collectRefLinks,
1681 RawHtmlBlock<Trait> &html);
1682
1683 long long int
1684 parseFormattedTextLinksImages(MdBlock<Trait> &fr,
1685 std::shared_ptr<Block<Trait>> parent,
1686 std::shared_ptr<Document<Trait>> doc,
1687 typename Trait::StringList &linksToParse,
1688 const typename Trait::String &workingPath,
1689 const typename Trait::String &fileName,
1690 bool collectRefLinks,
1691 bool ignoreLineBreak,
1692 RawHtmlBlock<Trait> &html,
1693 bool inLink);
1694
1695 struct ParserContext {
1696 typename Trait::template Vector<MdBlock<Trait>> m_splitted;
1697 typename MdBlock<Trait>::Data m_fragment;
1698 bool m_emptyLineInList = false;
1699 bool m_fensedCodeInList = false;
1700 long long int m_emptyLinesCount = 0;
1701 long long int m_lineCounter = 0;
1702 std::vector<long long int> m_indents;
1703 ListIndent m_indent;
1704 RawHtmlBlock<Trait> m_html;
1705 long long int m_emptyLinesBefore = 0;
1706 MdLineData::CommentDataMap m_htmlCommentData;
1707 typename Trait::String m_startOfCode;
1708 typename Trait::String m_startOfCodeInList;
1709 BlockType m_type = BlockType::EmptyLine;
1710 BlockType m_lineType = BlockType::Unknown;
1711 BlockType m_prevLineType = BlockType::Unknown;
1712 }; // struct ParserContext
1713
1714 std::pair<long long int, bool>
1715 parseFirstStep(ParserContext &ctx,
1716 StringListStream<Trait> &stream,
1717 std::shared_ptr<Block<Trait>> parent,
1718 std::shared_ptr<Document<Trait>> doc,
1719 typename Trait::StringList &linksToParse,
1720 const typename Trait::String &workingPath,
1721 const typename Trait::String &fileName,
1722 bool collectRefLinks);
1723
1724 void
1725 parseSecondStep(ParserContext &ctx,
1726 std::shared_ptr<Block<Trait>> parent,
1727 std::shared_ptr<Document<Trait>> doc,
1728 typename Trait::StringList &linksToParse,
1729 const typename Trait::String &workingPath,
1730 const typename Trait::String &fileName,
1731 bool collectRefLinks,
1732 bool top,
1733 bool dontProcessLastFreeHtml);
1734
1735 std::pair<RawHtmlBlock<Trait>, long long int>
1736 parse(StringListStream<Trait> &stream,
1737 std::shared_ptr<Block<Trait>> parent,
1738 std::shared_ptr<Document<Trait>> doc,
1739 typename Trait::StringList &linksToParse,
1740 const typename Trait::String &workingPath,
1741 const typename Trait::String &fileName,
1742 bool collectRefLinks,
1743 bool top = false,
1744 bool dontProcessLastFreeHtml = false,
1745 bool stopOnMayBreakList = false);
1746
1747 std::pair<long long int, bool>
1748 parseFragment(ParserContext &ctx,
1749 std::shared_ptr<Block<Trait>> parent,
1750 std::shared_ptr<Document<Trait>> doc,
1751 typename Trait::StringList &linksToParse,
1752 const typename Trait::String &workingPath,
1753 const typename Trait::String &fileName,
1754 bool collectRefLinks);
1755
1756 void
1757 eatFootnote(ParserContext &ctx,
1758 StringListStream<Trait> &stream,
1759 std::shared_ptr<Block<Trait>> parent,
1760 std::shared_ptr<Document<Trait>> doc,
1761 typename Trait::StringList &linksToParse,
1762 const typename Trait::String &workingPath,
1763 const typename Trait::String &fileName,
1764 bool collectRefLinks);
1765
1766 void
1767 finishHtml(ParserContext &ctx,
1768 std::shared_ptr<Block<Trait>> parent,
1769 std::shared_ptr<Document<Trait>> doc,
1770 bool collectRefLinks,
1771 bool top,
1772 bool dontProcessLastFreeHtml);
1773
1774 void
1775 makeLineMain(ParserContext &ctx,
1776 const typename Trait::InternalString &line,
1777 long long int emptyLinesCount,
1778 const ListIndent &currentIndent,
1779 long long int ns,
1780 long long int currentLineNumber);
1781
1782 std::pair<long long int, bool>
1783 parseFragmentAndMakeNextLineMain(ParserContext &ctx,
1784 std::shared_ptr<Block<Trait>> parent,
1785 std::shared_ptr<Document<Trait>> doc,
1786 typename Trait::StringList &linksToParse,
1787 const typename Trait::String &workingPath,
1788 const typename Trait::String &fileName,
1789 bool collectRefLinks,
1790 const typename Trait::InternalString &line,
1791 const ListIndent &currentIndent,
1792 long long int ns,
1793 long long int currentLineNumber);
1794
1795 bool
1796 isListType(BlockType t);
1797
1798 std::pair<typename Trait::InternalString, bool>
1799 readLine(ParserContext &ctx, StringListStream<Trait> &stream);
1800
1801 std::shared_ptr<Image<Trait>>
1802 makeImage(const typename Trait::String &url,
1803 const typename MdBlock<Trait>::Data &text,
1804 TextParsingOpts<Trait> &po,
1805 bool doNotCreateTextOnFail,
1806 long long int startLine,
1807 long long int startPos,
1808 long long int lastLine,
1809 long long int lastPos,
1810 const WithPosition &textPos,
1811 const WithPosition &urlPos);
1812
1813 std::shared_ptr<Link<Trait>>
1814 makeLink(const typename Trait::String &url,
1815 const typename MdBlock<Trait>::Data &text,
1816 TextParsingOpts<Trait> &po,
1817 bool doNotCreateTextOnFail,
1818 long long int startLine,
1819 long long int startPos,
1820 long long int lastLine,
1821 long long int lastPos,
1822 const WithPosition &textPos,
1823 const WithPosition &urlPos);
1824
1825 struct Delimiter {
1826 enum DelimiterType {
1827 // (
1828 ParenthesesOpen,
1829 // )
1830 ParenthesesClose,
1831 // [
1832 SquareBracketsOpen,
1833 // ]
1834 SquareBracketsClose,
1835 // ![
1836 ImageOpen,
1837 // ~~
1838 Strikethrough,
1839 // *
1840 Emphasis1,
1841 // _
1842 Emphasis2,
1843 // `
1844 InlineCode,
1845 // <
1846 Less,
1847 // >
1848 Greater,
1849 // $
1850 Math,
1851 HorizontalLine,
1852 H1,
1853 H2,
1854 Unknown
1855 }; // enum DelimiterType
1856
1857 DelimiterType m_type = Unknown;
1858 long long int m_line = -1;
1859 long long int m_pos = -1;
1860 long long int m_len = 0;
1861 bool m_isWordBefore = false;
1862 bool m_backslashed = false;
1863 bool m_leftFlanking = false;
1864 bool m_rightFlanking = false;
1865 bool m_skip = false;
1866 }; // struct Delimiter
1867
1868 using Delims = typename Trait::template Vector<Delimiter>;
1869
1870 bool
1871 createShortcutImage(const typename MdBlock<Trait>::Data &text,
1872 TextParsingOpts<Trait> &po,
1873 long long int startLine,
1874 long long int startPos,
1875 long long int lastLineForText,
1876 long long int lastPosForText,
1877 typename Delims::iterator lastIt,
1878 const typename MdBlock<Trait>::Data &linkText,
1879 bool doNotCreateTextOnFail,
1880 const WithPosition &textPos,
1881 const WithPosition &linkTextPos);
1882
1883 typename Delims::iterator
1884 checkForImage(typename Delims::iterator it,
1885 typename Delims::iterator last,
1886 TextParsingOpts<Trait> &po);
1887
1888 bool
1889 createShortcutLink(const typename MdBlock<Trait>::Data &text,
1890 TextParsingOpts<Trait> &po,
1891 long long int startLine,
1892 long long int startPos,
1893 long long int lastLineForText,
1894 long long int lastPosForText,
1895 typename Delims::iterator lastIt,
1896 const typename MdBlock<Trait>::Data &linkText,
1897 bool doNotCreateTextOnFail,
1898 const WithPosition &textPos,
1899 const WithPosition &linkTextPos);
1900
1901 typename Delims::iterator
1902 checkForLink(typename Delims::iterator it,
1903 typename Delims::iterator last,
1904 TextParsingOpts<Trait> &po);
1905
1906 Delims
1907 collectDelimiters(const typename MdBlock<Trait>::Data &fr);
1908
1909 std::pair<typename Trait::String, bool>
1910 readHtmlTag(typename Delims::iterator it, TextParsingOpts<Trait> &po);
1911
1912 typename Delims::iterator
1913 findIt(typename Delims::iterator it,
1914 typename Delims::iterator last,
1915 TextParsingOpts<Trait> &po);
1916
1917 typename Delims::iterator
1918 eatRawHtmlTillEmptyLine(typename Delims::iterator it,
1919 typename Delims::iterator last,
1920 long long int line,
1921 long long int pos,
1922 TextParsingOpts<Trait> &po,
1923 int htmlRule,
1924 bool onLine,
1925 bool continueEating = false);
1926
1927 void
1928 finishRule1HtmlTag(typename Delims::iterator it,
1929 typename Delims::iterator last,
1930 TextParsingOpts<Trait> &po,
1931 bool skipFirst);
1932
1933 void
1934 finishRule2HtmlTag(typename Delims::iterator it,
1935 typename Delims::iterator last,
1936 TextParsingOpts<Trait> &po);
1937
1938 void
1939 finishRule3HtmlTag(typename Delims::iterator it,
1940 typename Delims::iterator last,
1941 TextParsingOpts<Trait> &po);
1942
1943 void
1944 finishRule4HtmlTag(typename Delims::iterator it,
1945 typename Delims::iterator last,
1946 TextParsingOpts<Trait> &po);
1947
1948 void
1949 finishRule5HtmlTag(typename Delims::iterator it,
1950 typename Delims::iterator last,
1951 TextParsingOpts<Trait> &po);
1952
1953 void
1954 finishRule6HtmlTag(typename Delims::iterator it,
1955 typename Delims::iterator last,
1956 TextParsingOpts<Trait> &po);
1957
1958 void
1959 finishRule7HtmlTag(typename Delims::iterator it,
1960 typename Delims::iterator last,
1961 TextParsingOpts<Trait> &po);
1962
1963 typename Delims::iterator
1964 finishRawHtmlTag(typename Delims::iterator it,
1965 typename Delims::iterator last,
1966 TextParsingOpts<Trait> &po,
1967 bool skipFirst);
1968
1969 int
1970 htmlTagRule(typename Delims::iterator it,
1971 typename Delims::iterator last,
1972 TextParsingOpts<Trait> &po);
1973
1974 typename Delims::iterator
1975 checkForRawHtml(typename Delims::iterator it,
1976 typename Delims::iterator last,
1977 TextParsingOpts<Trait> &po);
1978
1979 typename Delims::iterator
1980 checkForMath(typename Delims::iterator it,
1981 typename Delims::iterator last,
1982 TextParsingOpts<Trait> &po);
1983
1984 typename Delims::iterator
1985 checkForAutolinkHtml(typename Delims::iterator it,
1986 typename Delims::iterator last,
1987 TextParsingOpts<Trait> &po,
1988 bool updatePos);
1989
1990 typename Delims::iterator
1991 checkForInlineCode(typename Delims::iterator it,
1992 typename Delims::iterator last,
1993 TextParsingOpts<Trait> &po);
1994
1995 std::pair<typename MdBlock<Trait>::Data, typename Delims::iterator>
1996 readTextBetweenSquareBrackets(typename Delims::iterator start,
1997 typename Delims::iterator it,
1998 typename Delims::iterator last,
1999 TextParsingOpts<Trait> &po,
2000 bool doNotCreateTextOnFail,
2001 WithPosition *pos = nullptr);
2002
2003 std::pair<typename MdBlock<Trait>::Data, typename Delims::iterator>
2004 checkForLinkText(typename Delims::iterator it,
2005 typename Delims::iterator last,
2006 TextParsingOpts<Trait> &po,
2007 WithPosition *pos = nullptr);
2008
2009 std::pair<typename MdBlock<Trait>::Data, typename Delims::iterator>
2010 checkForLinkLabel(typename Delims::iterator it,
2011 typename Delims::iterator last,
2012 TextParsingOpts<Trait> &po,
2013 WithPosition *pos = nullptr);
2014
2015 std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
2016 checkForInlineLink(typename Delims::iterator it,
2017 typename Delims::iterator last,
2018 TextParsingOpts<Trait> &po,
2019 WithPosition *urlPos = nullptr);
2020
2021 inline std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
2022 checkForRefLink(typename Delims::iterator it,
2023 typename Delims::iterator last,
2024 TextParsingOpts<Trait> &po,
2025 WithPosition *urlPos = nullptr);
2026
2027 typename Trait::String
2028 toSingleLine(const typename MdBlock<Trait>::Data &d);
2029
2030 template<class Func>
2031 typename Delims::iterator
2032 checkShortcut(typename Delims::iterator it,
2033 typename Delims::iterator last,
2034 TextParsingOpts<Trait> &po,
2035 Func functor)
2036 {
2037 const auto start = it;
2038
2039 typename MdBlock<Trait>::Data text;
2040
2041 WithPosition labelPos;
2042 std::tie(text, it) = checkForLinkLabel(start, last, po, &labelPos);
2043
2044 if (it != start && !toSingleLine(text).simplified().isEmpty()) {
2045 if ((this->*functor)(text, po, start->m_line, start->m_pos, start->m_line,
2046 start->m_pos + start->m_len, it, {}, false, labelPos, {})) {
2047 return it;
2048 }
2049 }
2050
2051 return start;
2052 }
2053
2054 bool
2055 isSequence(typename Delims::iterator it,
2056 long long int itLine,
2057 long long int itPos,
2058 typename Delimiter::DelimiterType t);
2059
2060 std::pair<typename Delims::iterator, typename Delims::iterator>
2061 readSequence(typename Delims::iterator first,
2062 typename Delims::iterator it,
2063 typename Delims::iterator last,
2064 long long int &pos,
2065 long long int &length,
2066 long long int &itCount,
2067 long long int &lengthFromIt,
2068 long long int &itCountFromIt);
2069
2070 typename Delims::iterator
2071 readSequence(typename Delims::iterator it,
2072 typename Delims::iterator last,
2073 long long int &line,
2074 long long int &pos,
2075 long long int &len,
2076 long long int &itCount);
2077
2078 int
2079 emphasisToInt(typename Delimiter::DelimiterType t);
2080
2081 void
2082 createStyles(std::vector<std::pair<Style, long long int>> & styles,
2083 typename Delimiter::DelimiterType t,
2084 long long int style);
2085
2086 std::vector<std::pair<Style, long long int>>
2087 createStyles(typename Delimiter::DelimiterType t,
2088 const std::vector<long long int> &styles,
2089 long long int lastStyle);
2090
2091 std::tuple<bool, std::vector<std::pair<Style, long long int>>, long long int, long long int>
2092 isStyleClosed(typename Delims::iterator first,
2093 typename Delims::iterator it,
2094 typename Delims::iterator last,
2095 typename Delims::iterator &stackBottom,
2096 TextParsingOpts<Trait> &po);
2097
2098 typename Delims::iterator
2099 incrementIterator(typename Delims::iterator it,
2100 typename Delims::iterator last,
2101 long long int count);
2102
2103 typename Delims::iterator
2104 checkForStyle(typename Delims::iterator first,
2105 typename Delims::iterator it,
2106 typename Delims::iterator last,
2107 typename Delims::iterator &stackBottom,
2108 TextParsingOpts<Trait> &po);
2109
2110 bool
2111 isNewBlockIn(MdBlock<Trait> &fr,
2112 long long int startLine,
2113 long long int endLine);
2114
2115 void
2116 makeInlineCode(long long int startLine,
2117 long long int startPos,
2118 long long int lastLine,
2119 long long int lastPos,
2120 TextParsingOpts<Trait> &po,
2121 typename Delims::iterator startDelimIt,
2122 typename Delims::iterator endDelimIt);
2123
2125 defaultParagraphOptimization() const
2126 {
2127 return (m_fullyOptimizeParagraphs ? OptimizeParagraphType::Full :
2129 }
2130
2131private:
2132 //! Used in tests.
2133 friend struct PrivateAccess;
2134
2135private:
2136 typename Trait::StringList m_parsedFiles;
2137 TextPluginsMap<Trait> m_textPlugins;
2138 bool m_fullyOptimizeParagraphs = true;
2139
2141}; // class Parser
2142
2143//
2144// Parser
2145//
2146
2147template<class Trait>
2148inline std::shared_ptr<Document<Trait>>
2149Parser<Trait>::parse(const typename Trait::String &fileName,
2150 bool recursive,
2151 const typename Trait::StringList &ext,
2152 bool fullyOptimizeParagraphs)
2153{
2154 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2155
2156 std::shared_ptr<Document<Trait>> doc(new Document<Trait>);
2157
2158 parseFile(fileName, recursive, doc, ext);
2159
2160 clearCache();
2161
2162 return doc;
2163}
2164
2165template<class Trait>
2166inline std::shared_ptr<Document<Trait>>
2167Parser<Trait>::parse(typename Trait::TextStream &stream,
2168 const typename Trait::String &path,
2169 const typename Trait::String &fileName,
2170 bool fullyOptimizeParagraphs)
2171{
2172 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2173
2174 std::shared_ptr<Document<Trait>> doc(new Document<Trait>);
2175
2176 parseStream(stream, path, fileName, false, doc, typename Trait::StringList());
2177
2178 clearCache();
2179
2180 return doc;
2181}
2182
2183template<class Trait>
2184inline std::shared_ptr<Document<Trait>>
2185Parser<Trait>::parse(const typename Trait::String &fileName,
2186 const typename Trait::String &workingDirectory,
2187 bool recursive,
2188 const typename Trait::StringList &ext,
2189 bool fullyOptimizeParagraphs)
2190{
2191 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2192
2193 std::shared_ptr<Document<Trait>> doc(new Document<Trait>);
2194
2195 typename Trait::String wd;
2196 auto i = workingDirectory.length() - 1;
2197
2198 while (i > 0) {
2199 if (workingDirectory[i] != Trait::latin1ToChar('\\') &&
2200 workingDirectory[i] != Trait::latin1ToChar('/')) {
2201 break;
2202 } else {
2203 --i;
2204 }
2205 }
2206
2207 if (i == 0 && workingDirectory[i] == Trait::latin1ToChar('/')) {
2208 wd = Trait::latin1ToString("/");
2209 } else if (i > 0) {
2210 wd = workingDirectory.sliced(0, i + 1);
2211 }
2212
2213 parseFile(fileName, recursive, doc, ext, nullptr, wd);
2214
2215 clearCache();
2216
2217 return doc;
2218}
2219
2220template<class Trait>
2222
2223#ifdef MD4QT_QT_SUPPORT
2224
2225//! Wrapper for QTextStream.
2226template<>
2228{
2229public:
2231 : m_stream(stream)
2232 , m_lastBuf(false)
2233 , m_pos(0)
2234 {
2235 }
2236
2237 bool
2238 atEnd() const
2239 {
2240 return (m_lastBuf && m_pos == m_buf.size());
2241 }
2242
2243 QString
2245 {
2246 QString line;
2247 bool rFound = false;
2248
2249 while (!atEnd()) {
2250 const auto c = getChar();
2251
2252 if (rFound && c != QLatin1Char('\n')) {
2253 --m_pos;
2254
2255 return line;
2256 }
2257
2258 if (c == QLatin1Char('\r')) {
2259 rFound = true;
2260
2261 continue;
2262 } else if (c == QLatin1Char('\n')) {
2263 return line;
2264 }
2265
2266 if (!c.isNull()) {
2267 line.push_back(c);
2268 }
2269 }
2270
2271 return line;
2272 }
2273
2274private:
2275 void
2276 fillBuf()
2277 {
2278 m_buf = m_stream.read(512);
2279
2280 if (m_stream.atEnd()) {
2281 m_lastBuf = true;
2282 }
2283
2284 m_pos = 0;
2285 }
2286
2287 QChar
2288 getChar()
2289 {
2290 if (m_pos < m_buf.size()) {
2291 return m_buf.at(m_pos++);
2292 } else if (!atEnd()) {
2293 fillBuf();
2294
2295 return getChar();
2296 } else {
2297 return QChar();
2298 }
2299 }
2300
2301private:
2302 QTextStream &m_stream;
2303 QString m_buf;
2304 bool m_lastBuf;
2305 long long int m_pos;
2306}; // class TextStream
2307
2308#endif
2309
2310#ifdef MD4QT_ICU_STL_SUPPORT
2311
2312//! Wrapper for std::istream.
2313template<>
2315{
2316public:
2317 TextStream(std::istream &stream)
2318 : m_pos(0)
2319 {
2320 std::vector<unsigned char> content;
2321
2322 stream.seekg(0, std::ios::end);
2323 const auto ssize = stream.tellg();
2324 content.resize((size_t)ssize + 1);
2325 stream.seekg(0, std::ios::beg);
2326 stream.read((char *)&content[0], ssize);
2327 content[(size_t)ssize] = 0;
2328
2329 const auto z = std::count(content.cbegin(), content.cend(), 0);
2330
2331 if (z > 1) {
2332 std::vector<unsigned char> tmp;
2333 tmp.resize(content.size() + (z - 1) * 2);
2334
2335 for (size_t i = 0, j = 0; i < content.size() - 1; ++i, ++j) {
2336 if (content[i] == 0) {
2337 // 0xFFFD - replacement character in UTF-8.
2338 tmp[j++] = 0xEF;
2339 tmp[j++] = 0xBF;
2340 tmp[j] = 0xBD;
2341 } else {
2342 tmp[j] = content[i];
2343 }
2344 }
2345
2346 tmp[tmp.size() - 1] = 0;
2347
2348 std::swap(content, tmp);
2349 }
2350
2351 m_str = UnicodeString::fromUTF8((char *)&content[0]);
2352 }
2353
2354 bool
2355 atEnd() const
2356 {
2357 return m_pos == m_str.size();
2358 }
2359
2362 {
2363 UnicodeString line;
2364
2365 bool rFound = false;
2366
2367 while (!atEnd()) {
2368 const auto c = getChar();
2369
2370 if (rFound && c != UnicodeChar('\n')) {
2371 --m_pos;
2372
2373 return line;
2374 }
2375
2376 if (c == UnicodeChar('\r')) {
2377 rFound = true;
2378
2379 continue;
2380 } else if (c == UnicodeChar('\n')) {
2381 return line;
2382 }
2383
2384 if (!c.isNull()) {
2385 line.push_back(c);
2386 }
2387 }
2388
2389 return line;
2390 }
2391
2392private:
2394 getChar()
2395 {
2396 if (!atEnd()) {
2397 return m_str[m_pos++];
2398 } else {
2399 return UnicodeChar();
2400 }
2401 }
2402
2403private:
2404 UnicodeString m_str;
2405 long long int m_pos;
2406};
2407
2408#endif
2409
2410//! \return Is HTML comment closed?
2411template<class Trait>
2412inline bool
2413checkForEndHtmlComments(const typename Trait::String &line,
2414 long long int pos)
2415{
2416 const long long int e = line.indexOf(Trait::latin1ToString("-->"), pos);
2417
2418 if (e != -1) {
2419 return isHtmlComment<Trait>(line.sliced(0, e + 3));
2420 }
2421
2422 return false;
2423}
2424
2425//! Collect information about HTML comments.
2426template<class Trait>
2427inline void
2428checkForHtmlComments(const typename Trait::InternalString &line,
2431{
2432 long long int p = 0, l = stream.currentStreamPos();
2433
2434 const auto &str = line.asString();
2435
2436 while ((p = str.indexOf(Trait::latin1ToString(s_startComment), p)) != -1) {
2437 bool addNegative = false;
2438
2439 auto c = str.sliced(p);
2440
2441 if (c.startsWith(Trait::latin1ToString("<!-->"))) {
2442 res.insert({line.virginPos(p), {0, true}});
2443
2444 p += 5;
2445
2446 continue;
2447 } else if (c.startsWith(Trait::latin1ToString("<!--->"))) {
2448 res.insert({line.virginPos(p), {1, true}});
2449
2450 p += 6;
2451
2452 continue;
2453 }
2454
2456 res.insert({line.virginPos(p), {2, true}});
2457 } else {
2458 addNegative = true;
2459
2460 for (; l < stream.size(); ++l) {
2461 c.push_back(Trait::latin1ToChar(' '));
2462 c.push_back(stream.lineAt(l).asString());
2463
2465 res.insert({line.virginPos(p), {2, true}});
2466
2467 addNegative = false;
2468
2469 break;
2470 }
2471 }
2472 }
2473
2474 if (addNegative) {
2475 res.insert({line.virginPos(p), {-1, false}});
2476 }
2477
2478 ++p;
2479 }
2480}
2481
2482template<class Trait>
2483inline std::pair<long long int, bool>
2485 std::shared_ptr<Block<Trait>> parent,
2486 std::shared_ptr<Document<Trait>> doc,
2487 typename Trait::StringList &linksToParse,
2488 const typename Trait::String &workingPath,
2489 const typename Trait::String &fileName,
2490 bool collectRefLinks)
2491{
2492 auto clearCtx = [&ctx] () {
2493 ctx.m_fragment.clear();
2494 ctx.m_type = BlockType::EmptyLine;
2495 ctx.m_emptyLineInList = false;
2496 ctx.m_fensedCodeInList = false;
2497 ctx.m_emptyLinesCount = 0;
2498 ctx.m_lineCounter = 0;
2499 ctx.m_indents.clear();
2500 ctx.m_indent = {-1, -1};
2501 ctx.m_startOfCode.clear();
2502 ctx.m_startOfCodeInList.clear();
2503 };
2504
2505 if (!ctx.m_fragment.empty()) {
2506 MdBlock<Trait> block = {ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0};
2507
2508 const auto line = parseFragment(block, parent, doc, linksToParse, workingPath,
2509 fileName, collectRefLinks, ctx.m_html);
2510
2511 assert(line != ctx.m_fragment.front().second.m_lineNumber);
2512
2513 if (line > 0) {
2514 if (ctx.m_html.m_html) {
2515 if (!collectRefLinks) {
2516 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2517 }
2518
2519 resetHtmlTag<Trait>(ctx.m_html);
2520 }
2521
2522 const auto it = ctx.m_fragment.cbegin() + (line - ctx.m_fragment.cbegin()->second.m_lineNumber);
2523
2524 MdBlock<Trait> tmp = {{}, ctx.m_emptyLinesBefore, false};
2525 std::copy(ctx.m_fragment.cbegin(), it, std::back_inserter(tmp.m_data));
2526
2527 long long int emptyLines = 0;
2528
2529 while (!tmp.m_data.empty() && tmp.m_data.back().first.asString().simplified().isEmpty()) {
2530 tmp.m_data.pop_back();
2531 tmp.m_emptyLineAfter = true;
2532 ++emptyLines;
2533 }
2534
2535 if (!tmp.m_data.empty()) {
2536 ctx.m_splitted.push_back(tmp);
2537 }
2538
2539 const auto retLine = it->second.m_lineNumber;
2540 const auto retMayBreakList = it->second.m_mayBreakList;
2541
2542 clearCtx();
2543
2544 ctx.m_emptyLinesBefore = emptyLines;
2545
2546 return {retLine, retMayBreakList};
2547 }
2548
2549 ctx.m_splitted.push_back({ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0});
2550 }
2551
2552 clearCtx();
2553
2554 return {-1, false};
2555}
2556
2557//! Replace tabs with spaces (just for internal simpler use).
2558template<class Trait>
2559inline void
2560replaceTabs(typename Trait::InternalString &s)
2561{
2562 unsigned char size = 4;
2563 long long int len = s.length();
2564
2565 for (long long int i = 0; i < len; ++i, --size) {
2566 if (s[i] == Trait::latin1ToChar('\t')) {
2567 s.replaceOne(i, 1, typename Trait::String(size, Trait::latin1ToChar(' ')));
2568
2569 len += size - 1;
2570 i += size - 1;
2571 size = 5;
2572 }
2573
2574 if (size == 1) {
2575 size = 5;
2576 }
2577 }
2578}
2579
2580template<class Trait>
2581inline void
2583 StringListStream<Trait> &stream,
2584 std::shared_ptr<Block<Trait>> parent,
2585 std::shared_ptr<Document<Trait>> doc,
2586 typename Trait::StringList &linksToParse,
2587 const typename Trait::String &workingPath,
2588 const typename Trait::String &fileName,
2589 bool collectRefLinks)
2590{
2591 long long int emptyLinesCount = 0;
2592 bool wasEmptyLine = false;
2593
2594 while (!stream.atEnd()) {
2595 const auto currentLineNumber = stream.currentLineNumber();
2596
2597 typename Trait::InternalString line;
2598 bool mayBreak;
2599
2600 std::tie(line, mayBreak) = readLine(ctx, stream);
2601
2602 replaceTabs<Trait>(line);
2603
2604 const auto ns = skipSpaces<Trait>(0, line.asString());
2605
2606 if (ns == line.length() || line.asString().startsWith(Trait::latin1ToString(" "))) {
2607 if (ns == line.length()) {
2608 ++emptyLinesCount;
2609 wasEmptyLine = true;
2610 } else {
2611 emptyLinesCount = 0;
2612 }
2613
2614 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2615 } else if (!wasEmptyLine) {
2616 if (isFootnote<Trait>(line.sliced(ns).asString())) {
2617 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2618
2619 ctx.m_lineType = BlockType::Footnote;
2620
2621 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2622
2623 continue;
2624 } else {
2625 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2626 }
2627 } else {
2628 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2629
2630 ctx.m_lineType =
2631 whatIsTheLine(line, false, false, false, &ctx.m_startOfCodeInList, &ctx.m_indent,
2632 ctx.m_lineType == BlockType::EmptyLine, true, &ctx.m_indents);
2633
2634 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2635
2636 if (ctx.m_type == BlockType::Footnote) {
2637 wasEmptyLine = false;
2638
2639 continue;
2640 } else {
2641 break;
2642 }
2643 }
2644 }
2645
2646 if (stream.atEnd() && !ctx.m_fragment.empty()) {
2647 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2648 }
2649}
2650
2651template<class Trait>
2652inline void
2653Parser<Trait>::finishHtml(ParserContext &ctx,
2654 std::shared_ptr<Block<Trait>> parent,
2655 std::shared_ptr<Document<Trait>> doc,
2656 bool collectRefLinks,
2657 bool top,
2658 bool dontProcessLastFreeHtml)
2659{
2660 if (!collectRefLinks || top) {
2661 if (ctx.m_html.m_html->isFreeTag()) {
2662 if (!dontProcessLastFreeHtml) {
2663 if (ctx.m_html.m_parent) {
2664 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2665
2666 updateLastPosInList(ctx.m_html);
2667 } else {
2668 parent->appendItem(ctx.m_html.m_html);
2669 }
2670 }
2671 } else {
2672 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
2673 p->appendItem(ctx.m_html.m_html);
2674 p->setStartColumn(ctx.m_html.m_html->startColumn());
2675 p->setStartLine(ctx.m_html.m_html->startLine());
2676 p->setEndColumn(ctx.m_html.m_html->endColumn());
2677 p->setEndLine(ctx.m_html.m_html->endLine());
2678 doc->appendItem(p);
2679 }
2680 }
2681
2682 if (!dontProcessLastFreeHtml) {
2683 resetHtmlTag(ctx.m_html);
2684 }
2685
2686 ctx.m_html.m_toAdjustLastPos.clear();
2687}
2688
2689template<class Trait>
2690inline void
2691Parser<Trait>::makeLineMain(ParserContext &ctx,
2692 const typename Trait::InternalString &line,
2693 long long int emptyLinesCount,
2694 const ListIndent &currentIndent,
2695 long long int ns,
2696 long long int currentLineNumber)
2697{
2698 if (ctx.m_html.m_htmlBlockType >= 6) {
2699 ctx.m_html.m_continueHtml = (emptyLinesCount <= 0);
2700 }
2701
2702 ctx.m_type = ctx.m_lineType;
2703
2704 switch (ctx.m_type) {
2705 case BlockType::List:
2706 case BlockType::ListWithFirstEmptyLine: {
2707 if (ctx.m_indents.empty())
2708 ctx.m_indents.push_back(currentIndent.m_indent);
2709
2710 ctx.m_indent = currentIndent;
2711 } break;
2712
2713 case BlockType::Code:
2714 ctx.m_startOfCode = startSequence<Trait>(line.asString());
2715 break;
2716
2717 default:
2718 break;
2719 }
2720
2721 if (!line.isEmpty() && ns < line.length()) {
2722 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData}});
2723 }
2724
2725 ctx.m_lineCounter = 1;
2726 ctx.m_emptyLinesCount = 0;
2727 ctx.m_emptyLinesBefore = emptyLinesCount;
2728}
2729
2730template<class Trait>
2731inline std::pair<long long int, bool>
2732Parser<Trait>::parseFragmentAndMakeNextLineMain(ParserContext &ctx,
2733 std::shared_ptr<Block<Trait>> parent,
2734 std::shared_ptr<Document<Trait>> doc,
2735 typename Trait::StringList &linksToParse,
2736 const typename Trait::String &workingPath,
2737 const typename Trait::String &fileName,
2738 bool collectRefLinks,
2739 const typename Trait::InternalString &line,
2740 const ListIndent &currentIndent,
2741 long long int ns,
2742 long long int currentLineNumber)
2743{
2744 const auto empty = ctx.m_emptyLinesCount;
2745
2746 const auto ret = parseFragment(ctx, parent, doc, linksToParse, workingPath,
2747 fileName, collectRefLinks);
2748
2749 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2750
2751 return ret;
2752}
2753
2754template<class Trait>
2755inline bool
2756Parser<Trait>::isListType(BlockType t)
2757{
2758 switch (t) {
2759 case BlockType::List:
2760 case BlockType::ListWithFirstEmptyLine:
2761 return true;
2762
2763 default:
2764 return false;
2765 }
2766}
2767
2768template<class Trait>
2769std::pair<typename Trait::InternalString, bool>
2770Parser<Trait>::readLine(typename Parser<Trait>::ParserContext &ctx,
2772{
2773 ctx.m_htmlCommentData.clear();
2774
2775 auto line = stream.readLine();
2776
2777 static const char16_t c_zeroReplaceWith[2] = {0xFFFD, 0};
2778
2779 line.first.replace(typename Trait::Char(0), Trait::utf16ToString(&c_zeroReplaceWith[0]));
2780
2781 checkForHtmlComments(line.first, stream, ctx.m_htmlCommentData);
2782
2783 return line;
2784}
2785
2786template<class Trait>
2787inline std::pair<long long int, bool>
2788Parser<Trait>::parseFirstStep(ParserContext &ctx,
2790 std::shared_ptr<Block<Trait>> parent,
2791 std::shared_ptr<Document<Trait>> doc,
2792 typename Trait::StringList &linksToParse,
2793 const typename Trait::String &workingPath,
2794 const typename Trait::String &fileName,
2795 bool collectRefLinks)
2796{
2797 while (!stream.atEnd()) {
2798 const auto currentLineNumber = stream.currentLineNumber();
2799
2800 typename Trait::InternalString line;
2801 bool mayBreak;
2802
2803 std::tie(line, mayBreak) = readLine(ctx, stream);
2804
2805 if (ctx.m_lineType != BlockType::Unknown) {
2806 ctx.m_prevLineType = ctx.m_lineType;
2807 }
2808
2809 ctx.m_lineType = whatIsTheLine(line,
2810 (ctx.m_emptyLineInList || isListType(ctx.m_type)),
2811 ctx.m_prevLineType == BlockType::ListWithFirstEmptyLine,
2812 ctx.m_fensedCodeInList,
2813 &ctx.m_startOfCodeInList,
2814 &ctx.m_indent,
2815 ctx.m_lineType == BlockType::EmptyLine,
2816 true,
2817 &ctx.m_indents);
2818
2819 if (isListType(ctx.m_type) && ctx.m_lineType == BlockType::FensedCodeInList) {
2820 ctx.m_fensedCodeInList = !ctx.m_fensedCodeInList;
2821 }
2822
2823 const auto currentIndent = ctx.m_indent;
2824
2825 const auto ns = skipSpaces<Trait>(0, line.asString());
2826
2827 const auto indentInListValue = indentInList(&ctx.m_indents, ns, true);
2828
2829 if (isListType(ctx.m_lineType) && !ctx.m_fensedCodeInList && ctx.m_indent.m_level > -1) {
2830 if (ctx.m_indent.m_level < (long long int)ctx.m_indents.size()) {
2831 ctx.m_indents.erase(ctx.m_indents.cbegin() + ctx.m_indent.m_level, ctx.m_indents.cend());
2832 }
2833
2834 ctx.m_indents.push_back(ctx.m_indent.m_indent);
2835 }
2836
2837 if (ctx.m_type == BlockType::CodeIndentedBySpaces && ns > 3) {
2838 ctx.m_lineType = BlockType::CodeIndentedBySpaces;
2839 }
2840
2841 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2 &&
2842 !isListType(ctx.m_lineType)) {
2843 if (ctx.m_emptyLinesCount > 0) {
2844 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2845 parent,
2846 doc,
2847 linksToParse,
2848 workingPath,
2849 fileName,
2850 collectRefLinks,
2851 line,
2852 currentIndent,
2853 ns,
2854 currentLineNumber);
2855
2856 if (l.first != -1) {
2857 return l;
2858 }
2859
2860 continue;
2861 } else {
2862 ctx.m_emptyLineInList = false;
2863 ctx.m_emptyLinesCount = 0;
2864 }
2865 }
2866
2867 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2) {
2868 ctx.m_type = BlockType::List;
2869 }
2870
2871 // Footnote.
2872 if (ctx.m_lineType == BlockType::Footnote) {
2873 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2874 parent,
2875 doc,
2876 linksToParse,
2877 workingPath,
2878 fileName,
2879 collectRefLinks,
2880 line,
2881 currentIndent,
2882 ns,
2883 currentLineNumber);
2884
2885 if (l.first != -1) {
2886 return l;
2887 }
2888
2889 eatFootnote(ctx, stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2890
2891 continue;
2892 }
2893
2894 // First line of the fragment.
2895 if (ns != line.length() && ctx.m_type == BlockType::EmptyLine) {
2896 makeLineMain(ctx, line, ctx.m_emptyLinesCount, currentIndent, ns, currentLineNumber);
2897
2898 continue;
2899 } else if (ns == line.length() && ctx.m_type == BlockType::EmptyLine) {
2900 ++ctx.m_emptyLinesCount;
2901 continue;
2902 }
2903
2904 ++ctx.m_lineCounter;
2905
2906 // Got new empty line.
2907 if (ns == line.length()) {
2908 ++ctx.m_emptyLinesCount;
2909
2910 switch (ctx.m_type) {
2911 case BlockType::Blockquote: {
2912 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2913 collectRefLinks);
2914
2915 if (l.first != -1) {
2916 return l;
2917 }
2918
2919 continue;
2920 }
2921
2922 case BlockType::Text:
2923 case BlockType::CodeIndentedBySpaces:
2924 continue;
2925 break;
2926
2927 case BlockType::Code: {
2928 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2929 ctx.m_emptyLinesCount = 0;
2930
2931 continue;
2932 }
2933
2934 case BlockType::List:
2935 case BlockType::ListWithFirstEmptyLine: {
2936 ctx.m_emptyLineInList = true;
2937
2938 continue;
2939 }
2940
2941 default:
2942 break;
2943 }
2944 }
2945 // Empty new line in list.
2946 else if (ctx.m_emptyLineInList) {
2947 if (indentInListValue || isListType(ctx.m_lineType) || ctx.m_lineType == BlockType::SomethingInList) {
2948 for (long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2949 ctx.m_fragment.push_back({typename Trait::String(),
2950 {currentLineNumber - ctx.m_emptyLinesCount + i, {}, false}});
2951 }
2952
2953 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2954
2955 ctx.m_emptyLineInList = false;
2956 ctx.m_emptyLinesCount = 0;
2957
2958 continue;
2959 } else {
2960 const auto empty = ctx.m_emptyLinesCount;
2961
2962 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2963 collectRefLinks);
2964
2965 if (l.first != -1) {
2966 return l;
2967 }
2968
2969 ctx.m_lineType = whatIsTheLine(line, false, false, false, nullptr, nullptr,
2970 true, false, &ctx.m_indents);
2971
2972 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2973
2974 continue;
2975 }
2976 } else if (ctx.m_emptyLinesCount > 0) {
2977 if (ctx.m_type == BlockType::CodeIndentedBySpaces &&
2978 ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
2979 const auto indent = skipSpaces<Trait>(0, ctx.m_fragment.front().first.asString());
2980
2981 for (long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2982 ctx.m_fragment.push_back({typename Trait::String(indent, Trait::latin1ToChar(' ')),
2983 {currentLineNumber - ctx.m_emptyLinesCount + i, {}, false}});
2984 }
2985
2986 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2987 ctx.m_emptyLinesCount = 0;
2988 } else {
2989 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2990 parent,
2991 doc,
2992 linksToParse,
2993 workingPath,
2994 fileName,
2995 collectRefLinks,
2996 line,
2997 currentIndent,
2998 ns,
2999 currentLineNumber);
3000
3001 if (l.first != -1) {
3002 return l;
3003 }
3004 }
3005
3006 continue;
3007 }
3008
3009 // Something new and first block is not a code block or a list, blockquote.
3010 if (ctx.m_type != ctx.m_lineType && ctx.m_type != BlockType::Code &&
3011 !isListType(ctx.m_type) && ctx.m_type != BlockType::Blockquote) {
3012 if (ctx.m_type == BlockType::Text && ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
3013 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3014 }
3015 else {
3016 if (ctx.m_type == BlockType::Text && isListType(ctx.m_lineType)) {
3017 if (ctx.m_lineType != BlockType::ListWithFirstEmptyLine) {
3018 int num = 0;
3019
3020 if (isOrderedList<Trait>(line.asString(), &num)) {
3021 if (num != 1) {
3022 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3023
3024 continue;
3025 }
3026 }
3027 } else {
3028 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3029
3030 continue;
3031 }
3032 }
3033
3034 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3035 parent,
3036 doc,
3037 linksToParse,
3038 workingPath,
3039 fileName,
3040 collectRefLinks,
3041 line,
3042 currentIndent,
3043 ns,
3044 currentLineNumber);
3045
3046 if (l.first != -1) {
3047 return l;
3048 }
3049 }
3050 }
3051 // End of code block.
3052 else if (ctx.m_type == BlockType::Code && ctx.m_type == ctx.m_lineType &&
3053 !ctx.m_startOfCode.isEmpty() &&
3054 startSequence<Trait>(line.asString()).contains(ctx.m_startOfCode) &&
3055 isCodeFences<Trait>(line.asString(), true)) {
3056 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3057
3058 if (!stream.atEnd()) {
3059 typename Trait::InternalString line;
3060
3061 std::tie(line, std::ignore) = readLine(ctx, stream);
3062
3063 if (line.asString().simplified().isEmpty()) {
3064 ++ctx.m_emptyLinesCount;
3065 }
3066
3067 stream.setLineNumber(stream.currentLineNumber() - 1);
3068 } else {
3069 ++ctx.m_emptyLinesCount;
3070 }
3071
3072 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3073 collectRefLinks);
3074
3075 if (l.first != -1) {
3076 return l;
3077 }
3078 }
3079 // Not a continue of list.
3080 else if (ctx.m_type != ctx.m_lineType && isListType(ctx.m_type) &&
3081 ctx.m_lineType != BlockType::SomethingInList &&
3082 ctx.m_lineType != BlockType::FensedCodeInList && !isListType(ctx.m_lineType)) {
3083 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3084 parent,
3085 doc,
3086 linksToParse,
3087 workingPath,
3088 fileName,
3089 collectRefLinks,
3090 line,
3091 currentIndent,
3092 ns,
3093 currentLineNumber);
3094
3095 if (l.first != -1) {
3096 return l;
3097 }
3098 } else if (ctx.m_type == BlockType::Heading) {
3099 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3100 parent,
3101 doc,
3102 linksToParse,
3103 workingPath,
3104 fileName,
3105 collectRefLinks,
3106 line,
3107 currentIndent,
3108 ns,
3109 currentLineNumber);
3110
3111 if (l.first != -1) {
3112 return l;
3113 }
3114 } else {
3115 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3116 }
3117
3118 ctx.m_emptyLinesCount = 0;
3119 }
3120
3121 if (!ctx.m_fragment.empty()) {
3122 if (ctx.m_type == BlockType::Code && !ctx.m_html.m_html && !ctx.m_html.m_continueHtml) {
3123 ctx.m_fragment.push_back({ctx.m_startOfCode, {-1, {}, false}});
3124 }
3125
3126 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3127 collectRefLinks);
3128
3129 if (l.first != -1) {
3130 return l;
3131 }
3132 }
3133
3134 return {-1, false};
3135}
3136
3137template<class Trait>
3138inline void
3139Parser<Trait>::parseSecondStep(ParserContext &ctx,
3140 std::shared_ptr<Block<Trait>> parent,
3141 std::shared_ptr<Document<Trait>> doc,
3142 typename Trait::StringList &linksToParse,
3143 const typename Trait::String &workingPath,
3144 const typename Trait::String &fileName,
3145 bool collectRefLinks,
3146 bool top,
3147 bool dontProcessLastFreeHtml)
3148{
3149 if (top) {
3150 resetHtmlTag(ctx.m_html);
3151
3152 for (long long int i = 0; i < (long long int)ctx.m_splitted.size(); ++i) {
3153 parseFragment(ctx.m_splitted[i], parent, doc, linksToParse, workingPath, fileName, false,
3154 ctx.m_html);
3155
3156 if (ctx.m_html.m_htmlBlockType >= 6) {
3157 ctx.m_html.m_continueHtml = (!ctx.m_splitted[i].m_emptyLineAfter);
3158 }
3159
3160 if (ctx.m_html.m_html && !ctx.m_html.m_continueHtml) {
3161 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3162 } else if (!ctx.m_html.m_html) {
3163 ctx.m_html.m_toAdjustLastPos.clear();
3164 }
3165 }
3166 }
3167
3168 if (ctx.m_html.m_html) {
3169 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3170 }
3171}
3172
3173template<class Trait>
3174inline std::pair<RawHtmlBlock<Trait>, long long int>
3176 std::shared_ptr<Block<Trait>> parent,
3177 std::shared_ptr<Document<Trait>> doc,
3178 typename Trait::StringList &linksToParse,
3179 const typename Trait::String &workingPath,
3180 const typename Trait::String &fileName,
3181 bool collectRefLinks,
3182 bool top,
3183 bool dontProcessLastFreeHtml,
3184 bool stopOnMayBreakList)
3185{
3186 ParserContext ctx;
3187
3188 auto clearCtx = [&]()
3189 {
3190 ctx.m_fragment.clear();
3191 ctx.m_type = BlockType::EmptyLine;
3192 ctx.m_lineCounter = 0;
3193 };
3194
3195 auto line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3196 collectRefLinks);
3197
3198 clearCtx();
3199
3200 while (line.first != -1 && !(stopOnMayBreakList && line.second)) {
3201 stream.setLineNumber(line.first);
3202
3203 line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3204 collectRefLinks);
3205
3206 clearCtx();
3207 }
3208
3209 parseSecondStep(ctx, parent, doc, linksToParse, workingPath, fileName,
3210 collectRefLinks, top, dontProcessLastFreeHtml);
3211
3212 return {ctx.m_html, line.first};
3213}
3214
3215#ifdef MD4QT_QT_SUPPORT
3216
3217template<>
3218inline void
3219Parser<QStringTrait>::parseFile(const QString &fileName,
3220 bool recursive,
3221 std::shared_ptr<Document<QStringTrait>> doc,
3222 const QStringList &ext,
3223 QStringList *parentLinks,
3224 QString workingDirectory)
3225{
3226 QFileInfo fi(fileName);
3227
3228 if (fi.exists() && ext.contains(fi.suffix().toLower())) {
3229 QFile f(fileName);
3230
3231 if (f.open(QIODevice::ReadOnly)) {
3232 QTextStream s(f.readAll());
3233 f.close();
3234
3235 auto wd = fi.absolutePath();
3236 auto fn = fi.fileName();
3237
3238 workingDirectory.replace(QLatin1Char('\\'), QLatin1Char('/'));
3239
3240 if (!workingDirectory.isEmpty() && wd.contains(workingDirectory)) {
3241 QFileInfo folder(workingDirectory);
3242
3243 if (folder.exists() && folder.isDir()) {
3244 wd = folder.absoluteFilePath();
3245
3246 auto tmp = fi.absoluteFilePath();
3247 fn = tmp.remove(wd);
3248 fn.removeAt(0);
3249 }
3250 }
3251
3252 parseStream(s, wd, fn, recursive, doc, ext, parentLinks, workingDirectory);
3253 }
3254 }
3255}
3256
3257#endif
3258
3259#ifdef MD4QT_ICU_STL_SUPPORT
3260
3261template<>
3262inline void
3263Parser<UnicodeStringTrait>::parseFile(const UnicodeString &fileName,
3264 bool recursive,
3265 std::shared_ptr<Document<UnicodeStringTrait>> doc,
3266 const std::vector<UnicodeString> &ext,
3267 std::vector<UnicodeString> *parentLinks,
3268 UnicodeString workingDirectory)
3269{
3270 if (UnicodeStringTrait::fileExists(fileName)) {
3271 std::string fn;
3272 fileName.toUTF8String(fn);
3273
3274 try {
3275 auto e = UnicodeString::fromUTF8(std::filesystem::u8path(fn).extension().u8string());
3276
3277 if (!e.isEmpty()) {
3278 e.remove(0, 1);
3279 }
3280
3281 if (std::find(ext.cbegin(), ext.cend(), e.toLower()) != ext.cend()) {
3282 auto path = std::filesystem::canonical(std::filesystem::u8path(fn));
3283 std::ifstream file(path.c_str(), std::ios::in | std::ios::binary);
3284
3285 if (file.good()) {
3286 const auto fileNameS = path.filename().u8string();
3287 auto wd = path.remove_filename().u8string();
3288 auto ufn = UnicodeString::fromUTF8(fileNameS);
3289
3290 if (!wd.empty()) {
3291 wd.erase(wd.size() - 1, 1);
3292 }
3293
3294 std::replace(wd.begin(), wd.end(), '\\', '/');
3295 workingDirectory.findAndReplace(UnicodeStringTrait::latin1ToString("\\"),
3297
3298 if (!workingDirectory.isEmpty() &&
3299 UnicodeStringTrait::String(UnicodeString::fromUTF8(wd)).contains(workingDirectory)) {
3300 std::string folderPath;
3301 workingDirectory.toUTF8String(folderPath);
3302 auto folder = std::filesystem::directory_entry(folderPath);
3303
3304 if (folder.exists() && folder.is_directory()) {
3305 auto tmp = UnicodeString::fromUTF8(
3306 std::filesystem::canonical(std::filesystem::u8path(fn)).u8string());
3307 path = std::filesystem::canonical(folder.path());
3308 wd = path.u8string();
3309
3310 std::replace(wd.begin(), wd.end(), '\\', '/');
3311 tmp.findAndReplace(UnicodeStringTrait::latin1ToString("\\"),
3313
3314 ufn = tmp.findAndReplace(UnicodeString::fromUTF8(wd), {});
3315 ufn.remove(0, 1);
3316 }
3317 }
3318
3319 parseStream(file, UnicodeString::fromUTF8(wd), ufn,
3320 recursive, doc, ext, parentLinks, workingDirectory);
3321
3322 file.close();
3323 }
3324 }
3325 } catch (const std::exception &) {
3326 }
3327 }
3328}
3329
3330#endif
3331
3332//! Resolve links in the document.
3333template<class Trait>
3334void
3335resolveLinks(typename Trait::StringList &linksToParse,
3336 std::shared_ptr<Document<Trait>> doc)
3337{
3338 for (auto it = linksToParse.begin(), last = linksToParse.end(); it != last; ++it) {
3339 auto nextFileName = *it;
3340
3341 if (nextFileName.startsWith(Trait::latin1ToString("#"))) {
3342 const auto lit = doc->labeledLinks().find(nextFileName);
3343
3344 if (lit != doc->labeledLinks().cend()) {
3345 nextFileName = lit->second->url();
3346 } else {
3347 continue;
3348 }
3349 }
3350
3351 if (Trait::fileExists(nextFileName)) {
3352 *it = Trait::absoluteFilePath(nextFileName);
3353 }
3354 }
3355}
3356
3357template<class Trait>
3358inline void
3359Parser<Trait>::parseStream(typename Trait::TextStream &s,
3360 const typename Trait::String &workingPath,
3361 const typename Trait::String &fileName,
3362 bool recursive,
3363 std::shared_ptr<Document<Trait>> doc,
3364 const typename Trait::StringList &ext,
3365 typename Trait::StringList *parentLinks,
3366 const typename Trait::String &workingDirectory)
3367{
3368 typename Trait::StringList linksToParse;
3369
3370 const auto path = workingPath.isEmpty() ? typename Trait::String(fileName) :
3371 typename Trait::String(workingPath + Trait::latin1ToString("/") + fileName);
3372
3373 doc->appendItem(std::shared_ptr<Anchor<Trait>>(new Anchor<Trait>(path)));
3374
3375 typename MdBlock<Trait>::Data data;
3376
3377 {
3378 TextStream<Trait> stream(s);
3379
3380 long long int i = 0;
3381
3382 while (!stream.atEnd()) {
3383 data.push_back(std::pair<typename Trait::InternalString, MdLineData>(stream.readLine(), {i}));
3384 ++i;
3385 }
3386 }
3387
3388 StringListStream<Trait> stream(data);
3389
3390 parse(stream, doc, doc, linksToParse, workingPath, fileName, true, true);
3391
3392 m_parsedFiles.push_back(path);
3393
3394 resolveLinks<Trait>(linksToParse, doc);
3395
3396 // Parse all links if parsing is recursive.
3397 if (recursive && !linksToParse.empty()) {
3398 const auto tmpLinks = linksToParse;
3399
3400 while (!linksToParse.empty()) {
3401 auto nextFileName = linksToParse.front();
3402 linksToParse.erase(linksToParse.cbegin());
3403
3404 if (parentLinks) {
3405 const auto pit = std::find(parentLinks->cbegin(), parentLinks->cend(), nextFileName);
3406
3407 if (pit != parentLinks->cend()) {
3408 continue;
3409 }
3410 }
3411
3412 if (nextFileName.startsWith(Trait::latin1ToString("#"))) {
3413 continue;
3414 }
3415
3416 const auto pit = std::find(m_parsedFiles.cbegin(), m_parsedFiles.cend(), nextFileName);
3417
3418 if (pit == m_parsedFiles.cend()) {
3419 if (!doc->isEmpty() && doc->items().back()->type() != ItemType::PageBreak) {
3420 doc->appendItem(std::shared_ptr<PageBreak<Trait>>(new PageBreak<Trait>));
3421 }
3422
3423 parseFile(nextFileName, recursive, doc, ext, &linksToParse, workingDirectory);
3424 }
3425 }
3426
3427 if (parentLinks) {
3428 std::copy(tmpLinks.cbegin(), tmpLinks.cend(), std::back_inserter(*parentLinks));
3429 }
3430 }
3431}
3432
3433//! \return Position of first character in list item.
3434template<class Trait>
3435inline long long int
3436posOfListItem(const typename Trait::String &s,
3437 bool ordered)
3438{
3439 long long int p = 0;
3440
3441 for (; p < s.size(); ++p) {
3442 if (!s[p].isSpace()) {
3443 break;
3444 }
3445 }
3446
3447 if (ordered) {
3448 for (; p < s.size(); ++p) {
3449 if (!s[p].isDigit()) {
3450 break;
3451 }
3452 }
3453 }
3454
3455 ++p;
3456
3457 long long int sc = 0;
3458
3459 for (; p < s.size(); ++p) {
3460 if (!s[p].isSpace()) {
3461 break;
3462 } else {
3463 ++sc;
3464 }
3465 }
3466
3467 if (p == s.length() || sc > 4) {
3468 p = p - sc + 1;
3469 } else if (sc == 0) {
3470 ++p;
3471 }
3472
3473 return p;
3474}
3475
3476//! \return Level in indents for the given position.
3477inline long long int
3478listLevel(const std::vector<long long int> &indents,
3479 long long int pos)
3480{
3481 long long int level = indents.size();
3482
3483 for (auto it = indents.crbegin(), last = indents.crend(); it != last; ++it) {
3484 if (pos >= *it) {
3485 break;
3486 } else {
3487 --level;
3488 }
3489 }
3490
3491 return level;
3492}
3493
3494template<class Trait>
3495inline typename Parser<Trait>::BlockType
3496Parser<Trait>::whatIsTheLine(typename Trait::InternalString &str,
3497 bool inList,
3498 bool inListWithFirstEmptyLine,
3499 bool fensedCodeInList,
3500 typename Trait::String *startOfCode,
3501 ListIndent *indent,
3502 bool emptyLinePreceded,
3503 bool calcIndent,
3504 const std::vector<long long int> *indents)
3505{
3506 replaceTabs<Trait>(str);
3507
3508 const auto first = skipSpaces<Trait>(0, str.asString());
3509
3510 if (first < str.length()) {
3511 auto s = str.sliced(first);
3512
3513 const bool isBlockquote = s.asString().startsWith(Trait::latin1ToString(">"));
3514 const bool indentIn = indentInList(indents, first, false);
3515 bool isHeading = false;
3516
3517 if (first < 4 && isFootnote<Trait>(s.asString())) {
3518 return BlockType::Footnote;
3519 }
3520
3521 if (s.asString().startsWith(Trait::latin1ToString("#")) &&
3522 (indent ? first - indent->m_indent < 4 : first < 4)) {
3523 long long int c = 0;
3524
3525 while (c < s.length() && s[c] == Trait::latin1ToChar('#')) {
3526 ++c;
3527 }
3528
3529 if (c <= 6 && ((c < s.length() && s[c].isSpace()) || c == s.length())) {
3530 isHeading = true;
3531 }
3532 }
3533
3534 if (inList) {
3535 bool isFirstLineEmpty = false;
3536 const auto orderedList = isOrderedList<Trait>(str.asString(), nullptr, nullptr, nullptr,
3537 &isFirstLineEmpty);
3538 const bool fensedCode = isCodeFences<Trait>(s.asString());
3539 const auto codeIndentedBySpaces = emptyLinePreceded && first >= 4 &&
3540 !indentInList(indents, first, true);
3541
3542 if (fensedCodeInList) {
3543 if (indentInList(indents, first, true)) {
3544 if (fensedCode) {
3545 if (startOfCode && startSequence<Trait>(s.asString()).contains(*startOfCode)) {
3546 return BlockType::FensedCodeInList;
3547 }
3548 }
3549
3550 return BlockType::SomethingInList;
3551 }
3552 }
3553
3554 if (fensedCode && indentIn) {
3555 if (startOfCode) {
3556 *startOfCode = startSequence<Trait>(s.asString());
3557 }
3558
3559 return BlockType::FensedCodeInList;
3560 } else if ((((s.asString().startsWith(Trait::latin1ToString("-")) ||
3561 s.asString().startsWith(Trait::latin1ToString("+")) ||
3562 s.asString().startsWith(Trait::latin1ToString("*"))) &&
3563 ((s.length() > 1 && s[1] == Trait::latin1ToChar(' ')) || s.length() == 1)) ||
3564 orderedList) && (first < 4 || indentIn)) {
3565 if (codeIndentedBySpaces) {
3566 return BlockType::CodeIndentedBySpaces;
3567 }
3568
3569 if (indent && calcIndent) {
3570 indent->m_indent = posOfListItem<Trait>(str.asString(), orderedList);
3571 indent->m_level = (indents ? listLevel(*indents, first) : -1);
3572 }
3573
3574 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3575 return BlockType::ListWithFirstEmptyLine;
3576 } else {
3577 return BlockType::List;
3578 }
3579 } else if (indentInList(indents, first, true)) {
3580 return BlockType::SomethingInList;
3581 }
3582 else {
3583 if (!isHeading && !isBlockquote &&
3584 !(fensedCode && first < 4) && !emptyLinePreceded && !inListWithFirstEmptyLine) {
3585 return BlockType::SomethingInList;
3586 }
3587 }
3588 } else {
3589 bool isFirstLineEmpty = false;
3590
3591 const auto orderedList = isOrderedList<Trait>(str.asString(), nullptr, nullptr, nullptr,
3592 &isFirstLineEmpty);
3593 const bool isHLine = first < 4 && isHorizontalLine<Trait>(s.asString());
3594
3595 if (!isHLine &&
3596 (((s.asString().startsWith(Trait::latin1ToString("-")) || s.asString().startsWith(Trait::latin1ToString("+")) ||
3597 s.asString().startsWith(Trait::latin1ToString("*"))) &&
3598 ((s.length() > 1 && s[1] == Trait::latin1ToChar(' ')) || s.length() == 1)) ||
3599 orderedList) && first < 4) {
3600 if (indent && calcIndent) {
3601 indent->m_indent = posOfListItem<Trait>(str.asString(), orderedList);
3602 indent->m_level = (indents ? listLevel(*indents, first) : -1);
3603 }
3604
3605 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3606 return BlockType::ListWithFirstEmptyLine;
3607 } else {
3608 return BlockType::List;
3609 }
3610 }
3611 }
3612
3613 if (str.asString().startsWith(typename Trait::String(4, Trait::latin1ToChar(' ')))) {
3614 return BlockType::CodeIndentedBySpaces;
3615 } else if (isCodeFences<Trait>(str.asString())) {
3616 return BlockType::Code;
3617 } else if (isBlockquote) {
3618 return BlockType::Blockquote;
3619 } else if (isHeading) {
3620 return BlockType::Heading;
3621 }
3622 } else {
3623 return BlockType::EmptyLine;
3624 }
3625
3626 return BlockType::Text;
3627}
3628
3629template<class Trait>
3630inline long long int
3631Parser<Trait>::parseFragment(MdBlock<Trait> &fr,
3632 std::shared_ptr<Block<Trait>> parent,
3633 std::shared_ptr<Document<Trait>> doc,
3634 typename Trait::StringList &linksToParse,
3635 const typename Trait::String &workingPath,
3636 const typename Trait::String &fileName,
3637 bool collectRefLinks,
3638 RawHtmlBlock<Trait> &html)
3639{
3640 if (html.m_continueHtml) {
3641 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3642 } else {
3643 if (html.m_html) {
3644 if (!collectRefLinks) {
3645 parent->appendItem(html.m_html);
3646 }
3647
3648 resetHtmlTag(html);
3649 }
3650
3651 switch (whatIsTheLine(fr.m_data.front().first)) {
3652 case BlockType::Footnote:
3653 parseFootnote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3654 break;
3655
3656 case BlockType::Text:
3657 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3658 break;
3659
3660 case BlockType::Blockquote:
3661 return parseBlockquote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3662 break;
3663
3664 case BlockType::Code:
3665 return parseCode(fr, parent, collectRefLinks);
3666 break;
3667
3668 case BlockType::CodeIndentedBySpaces: {
3669 int indent = 1;
3670
3671 if (fr.m_data.front().first.asString().startsWith(Trait::latin1ToString(" "))) {
3672 indent = 4;
3673 }
3674
3675 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, {}, -1, -1, false);
3676 } break;
3677
3678 case BlockType::Heading:
3679 parseHeading(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3680 break;
3681
3682 case BlockType::List:
3683 case BlockType::ListWithFirstEmptyLine:
3684 return parseList(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3685
3686 default:
3687 break;
3688 }
3689 }
3690
3691 return -1;
3692}
3693
3694template<class Trait>
3695inline void
3696Parser<Trait>::clearCache()
3697{
3698 m_parsedFiles.clear();
3699}
3700
3701//! \return Number of columns in table, if the given string is a table header.
3702template<class Trait>
3703inline int
3704isTableHeader(const typename Trait::String &s)
3705{
3706 if (s.contains(Trait::latin1ToChar('|'))) {
3707 int c = 0;
3708
3709 const auto tmp = s.simplified();
3710 const auto p = tmp.startsWith(Trait::latin1ToString("|")) ? 1 : 0;
3711 const auto n = tmp.size() - p - (tmp.endsWith(Trait::latin1ToString("|")) && tmp.size() > 1 ? 1 : 0);
3712 const auto v = tmp.sliced(p, n);
3713
3714 bool backslash = false;
3715
3716 for (long long int i = 0; i < v.size(); ++i) {
3717 bool now = false;
3718
3719 if (v[i] == Trait::latin1ToChar('\\') && !backslash) {
3720 backslash = true;
3721 now = true;
3722 } else if (v[i] == Trait::latin1ToChar('|') && !backslash) {
3723 ++c;
3724 }
3725
3726 if (!now) {
3727 backslash = false;
3728 }
3729 }
3730
3731 ++c;
3732
3733 return c;
3734 } else {
3735 return 0;
3736 }
3737}
3738
3739template<class Trait>
3740inline long long int
3741Parser<Trait>::parseText(MdBlock<Trait> &fr,
3742 std::shared_ptr<Block<Trait>> parent,
3743 std::shared_ptr<Document<Trait>> doc,
3744 typename Trait::StringList &linksToParse,
3745 const typename Trait::String &workingPath,
3746 const typename Trait::String &fileName,
3747 bool collectRefLinks,
3748 RawHtmlBlock<Trait> &html)
3749{
3750 const auto h = isTableHeader<Trait>(fr.m_data.front().first.asString());
3751 const auto c = fr.m_data.size() > 1 ? isTableAlignment<Trait>(fr.m_data[1].first.asString()) : 0;
3752
3753 if (c && h && c == h && !html.m_continueHtml) {
3754 parseTable(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, c);
3755
3756 if (!fr.m_data.empty()) {
3757 return fr.m_data.front().second.m_lineNumber;
3758 } else {
3759 return -1;
3760 }
3761 } else {
3762 return parseParagraph(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3763 }
3764}
3765
3766//! Find and remove heading label.
3767template<class Trait>
3768inline std::pair<typename Trait::String, WithPosition>
3769findAndRemoveHeaderLabel(typename Trait::InternalString &s)
3770{
3771 const auto start = s.asString().indexOf(Trait::latin1ToString("{#"));
3772
3773 if (start >= 0) {
3774 long long int p = start + 2;
3775
3776 for (; p < s.length(); ++p) {
3777 if (s[p] == Trait::latin1ToChar('}')) {
3778 break;
3779 }
3780 }
3781
3782 if (p < s.length() && s[p] == Trait::latin1ToChar('}')) {
3783 WithPosition pos;
3784 pos.setStartColumn(s.virginPos(start));
3785 pos.setEndColumn(s.virginPos(p));
3786
3787 const auto label = s.sliced(start, p - start + 1).asString();
3788 s.remove(start, p - start + 1);
3789 return {label, pos};
3790 }
3791 }
3792
3793 return {};
3794}
3795
3796//! Convert string to label.
3797template<class Trait>
3798inline typename Trait::String
3799stringToLabel(const typename Trait::String &s)
3800{
3801 typename Trait::String res;
3802
3803 for (long long int i = 0; i < s.length(); ++i) {
3804 const auto c = s[i];
3805
3806 if (c.isLetter() || c.isDigit() || c == Trait::latin1ToChar('-') ||
3807 c == Trait::latin1ToChar('_')) {
3808 res.push_back(c);
3809 } else if (c.isSpace()) {
3810 res.push_back(Trait::latin1ToString("-"));
3811 }
3812 }
3813
3814 return res;
3815}
3816
3817//! Convert Paragraph to label.
3818template<class Trait>
3819inline typename Trait::String
3821{
3822 typename Trait::String l;
3823
3824 if (!p) {
3825 return l;
3826 }
3827
3828 for (auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
3829 switch ((*it)->type()) {
3830 case ItemType::Text: {
3831 auto t = static_cast<Text<Trait> *>(it->get());
3832 const auto text = t->text();
3833 l.push_back(stringToLabel<Trait>(text));
3834 } break;
3835
3836 case ItemType::Image: {
3837 auto i = static_cast<Image<Trait> *>(it->get());
3838
3839 if (!i->p()->isEmpty()) {
3840 l.push_back(paragraphToLabel(i->p().get()));
3841 } else if (!i->text().isEmpty()) {
3842 l.push_back(stringToLabel<Trait>(i->text()));
3843 }
3844 } break;
3845
3846 case ItemType::Link: {
3847 auto link = static_cast<Link<Trait> *>(it->get());
3848
3849 if (!link->p()->isEmpty()) {
3850 l.push_back(paragraphToLabel(link->p().get()));
3851 } else if (!link->text().isEmpty()) {
3852 l.push_back(stringToLabel<Trait>(link->text()));
3853 }
3854 } break;
3855
3856 case ItemType::Code: {
3857 auto c = static_cast<Code<Trait> *>(it->get());
3858
3859 if (!c->text().isEmpty()) {
3860 l.push_back(stringToLabel<Trait>(c->text()));
3861 }
3862 } break;
3863
3864 default:
3865 break;
3866 }
3867 }
3868
3869 return l;
3870}
3871
3872//! Find and remove closing sequence of "#" in heading.
3873template<class Trait>
3874inline WithPosition
3875findAndRemoveClosingSequence(typename Trait::InternalString &s)
3876{
3877 long long int end = -1;
3878 long long int start = -1;
3879
3880 for (long long int i = s.length() - 1; i >= 0; --i) {
3881 if (!s[i].isSpace() && s[i] != Trait::latin1ToChar('#') && end == -1) {
3882 return {};
3883 }
3884
3885 if (s[i] == Trait::latin1ToChar('#')) {
3886 if (end == -1) {
3887 end = i;
3888 }
3889
3890 if (i - 1 >= 0) {
3891 if (s[i - 1].isSpace()) {
3892 start = i;
3893 break;
3894 } else if (s[i - 1] != Trait::latin1ToChar('#')) {
3895 return {};
3896 }
3897 } else {
3898 start = 0;
3899 }
3900 }
3901 }
3902
3903 WithPosition ret;
3904
3905 if (start != -1 && end != -1) {
3906 ret.setStartColumn(s.virginPos(start));
3907 ret.setEndColumn(s.virginPos(end));
3908
3909 s.remove(start, end - start + 1);
3910 }
3911
3912 return ret;
3913}
3914
3915template<class Trait>
3916inline void
3917Parser<Trait>::parseHeading(MdBlock<Trait> &fr,
3918 std::shared_ptr<Block<Trait>> parent,
3919 std::shared_ptr<Document<Trait>> doc,
3920 typename Trait::StringList &linksToParse,
3921 const typename Trait::String &workingPath,
3922 const typename Trait::String &fileName,
3923 bool collectRefLinks)
3924{
3925 if (!fr.m_data.empty() && !collectRefLinks) {
3926 auto line = fr.m_data.front().first;
3927
3928 std::shared_ptr<Heading<Trait>> h(new Heading<Trait>);
3929 h->setStartColumn(line.virginPos(skipSpaces<Trait>(0, line.asString())));
3930 h->setStartLine(fr.m_data.front().second.m_lineNumber);
3931 h->setEndColumn(line.virginPos(line.length() - 1));
3932 h->setEndLine(h->startLine());
3933
3934 long long int pos = 0;
3935 pos = skipSpaces<Trait>(pos, line.asString());
3936
3937 if (pos > 0) {
3938 line = line.sliced(pos);
3939 }
3940
3941 pos = 0;
3942 int lvl = 0;
3943
3944 while (pos < line.length() && line[pos] == Trait::latin1ToChar('#')) {
3945 ++lvl;
3946 ++pos;
3947 }
3948
3949 WithPosition startDelim = {h->startColumn(), h->startLine(),
3950 line.virginPos(pos - 1), h->startLine()};
3951
3952 pos = skipSpaces<Trait>(pos, line.asString());
3953
3954 if (pos > 0) {
3955 fr.m_data.front().first = line.sliced(pos);
3956 }
3957
3958 auto label = findAndRemoveHeaderLabel<Trait>(fr.m_data.front().first);
3959
3960 typename Heading<Trait>::Delims delims = {startDelim};
3961
3962 auto endDelim = findAndRemoveClosingSequence<Trait>(fr.m_data.front().first);
3963
3964 if (endDelim.startColumn() != -1) {
3965 endDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
3966 endDelim.setEndLine(endDelim.startLine());
3967
3968 delims.push_back(endDelim);
3969 }
3970
3971 h->setDelims(delims);
3972
3973 h->setLevel(lvl);
3974
3975 if (!label.first.isEmpty()) {
3976 h->setLabel(label.first.sliced(1, label.first.length() - 2) + Trait::latin1ToString("/") +
3977 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString("/") :
3978 Trait::latin1ToString("")) + fileName);
3979
3980 label.second.setStartLine(fr.m_data.front().second.m_lineNumber);
3981 label.second.setEndLine(label.second.startLine());
3982
3983 h->setLabelPos(label.second);
3984 }
3985
3986 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
3987
3988 typename MdBlock<Trait>::Data tmp;
3990 tmp.push_back(fr.m_data.front());
3991 MdBlock<Trait> block = {tmp, 0};
3992
3994
3995 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
3996 false, false, html, false);
3997
3998 fr.m_data.erase(fr.m_data.cbegin());
3999
4000 if (p->items().size() && p->items().at(0)->type() == ItemType::Paragraph) {
4001 h->setText(std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0)));
4002 } else {
4003 h->setText(p);
4004 }
4005
4006 if (h->isLabeled()) {
4007 doc->insertLabeledHeading(h->label(), h);
4008 h->labelVariants().push_back(h->label());
4009 } else {
4010 typename Trait::String label = Trait::latin1ToString("#") +
4011 paragraphToLabel(h->text().get());
4012
4013 const auto path = Trait::latin1ToString("/") +
4014 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString("/") :
4015 Trait::latin1ToString("")) + fileName;
4016
4017 h->setLabel(label + path);
4018 h->labelVariants().push_back(h->label());
4019
4020 doc->insertLabeledHeading(label + path, h);
4021
4022 if (label != label.toLower()) {
4023 doc->insertLabeledHeading(label.toLower() + path, h);
4024 h->labelVariants().push_back(label.toLower() + path);
4025 }
4026 }
4027
4028 parent->appendItem(h);
4029 }
4030}
4031
4032//! Prepare data in table cell for parsing.
4033template<class Trait>
4034inline typename Trait::InternalString
4035prepareTableData(typename Trait::InternalString s)
4036{
4037 s.replace(Trait::latin1ToString("\\|"), Trait::latin1ToString("|"));
4038
4039 return s;
4040}
4041
4042//! Split table's row on cells.
4043template<class Trait>
4044inline std::pair<typename Trait::InternalStringList, std::vector<long long int>>
4045splitTableRow(const typename Trait::InternalString &s)
4046{
4047 typename Trait::InternalStringList res;
4048 std::vector<long long int> columns;
4049
4050 bool backslash = false;
4051 long long int start = 0;
4052
4053 for (long long int i = 0; i < s.length(); ++i) {
4054 bool now = false;
4055
4056 if (s[i] == Trait::latin1ToChar('\\') && !backslash) {
4057 backslash = true;
4058 now = true;
4059 } else if (s[i] == Trait::latin1ToChar('|') && !backslash) {
4060 res.push_back(prepareTableData<Trait>(s.sliced(start, i - start)));
4061 columns.push_back(s.virginPos(i));
4062 start = i + 1;
4063 }
4064
4065 if (!now) {
4066 backslash = false;
4067 }
4068 }
4069
4070 res.push_back(prepareTableData<Trait>(s.sliced(start, s.length() - start)));
4071
4072 return {res, columns};
4073}
4074
4075template<class Trait>
4076inline void
4077Parser<Trait>::parseTable(MdBlock<Trait> &fr,
4078 std::shared_ptr<Block<Trait>> parent,
4079 std::shared_ptr<Document<Trait>> doc,
4080 typename Trait::StringList &linksToParse,
4081 const typename Trait::String &workingPath,
4082 const typename Trait::String &fileName,
4083 bool collectRefLinks,
4084 int columnsCount)
4085{
4086 static const char sep = '|';
4087
4088 if (fr.m_data.size() >= 2) {
4089 std::shared_ptr<Table<Trait>> table(new Table<Trait>);
4090 table->setStartColumn(fr.m_data.front().first.virginPos(0));
4091 table->setStartLine(fr.m_data.front().second.m_lineNumber);
4092 table->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
4093 table->setEndLine(fr.m_data.back().second.m_lineNumber);
4094
4095 auto parseTableRow = [&](const typename MdBlock<Trait>::Line &lineData) -> bool {
4096 const auto &row = lineData.first;
4097
4098 if (row.asString().startsWith(Trait::latin1ToString(" "))) {
4099 return false;
4100 }
4101
4102 auto line = row;
4103 auto p = skipSpaces<Trait>(0, line.asString());
4104
4105 if (p == line.length()) {
4106 return false;
4107 }
4108
4109 if (line[p] == Trait::latin1ToChar(sep)) {
4110 line.remove(0, p + 1);
4111 }
4112
4113 for (p = line.length() - 1; p >= 0; --p) {
4114 if (!line[p].isSpace()) {
4115 break;
4116 }
4117 }
4118
4119 if (p < 0) {
4120 return false;
4121 }
4122
4123 if (line[p] == Trait::latin1ToChar(sep)) {
4124 line.remove(p, line.length() - p);
4125 }
4126
4127 auto columns = splitTableRow<Trait>(line);
4128 columns.second.insert(columns.second.begin(), row.virginPos(0));
4129 columns.second.push_back(row.virginPos(row.length() - 1));
4130
4131 std::shared_ptr<TableRow<Trait>> tr(new TableRow<Trait>);
4132 tr->setStartColumn(row.virginPos(0));
4133 tr->setStartLine(lineData.second.m_lineNumber);
4134 tr->setEndColumn(row.virginPos(row.length() - 1));
4135 tr->setEndLine(lineData.second.m_lineNumber);
4136
4137 int col = 0;
4138
4139 for (auto it = columns.first.begin(), last = columns.first.end(); it != last; ++it, ++col) {
4140 if (col == columnsCount) {
4141 break;
4142 }
4143
4144 std::shared_ptr<TableCell<Trait>> c(new TableCell<Trait>);
4145 c->setStartColumn(columns.second.at(col));
4146 c->setStartLine(lineData.second.m_lineNumber);
4147 c->setEndColumn(columns.second.at(col + 1));
4148 c->setEndLine(lineData.second.m_lineNumber);
4149
4150 if (!it->isEmpty()) {
4151 it->replace(Trait::latin1ToString("&#124;"), Trait::latin1ToChar(sep));
4152
4153 typename MdBlock<Trait>::Data fragment;
4154 fragment.push_back({*it, lineData.second});
4155 MdBlock<Trait> block = {fragment, 0};
4156
4157 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
4158
4160
4161 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
4162 collectRefLinks, false, html, false);
4163
4164 if (!p->isEmpty()) {
4165 for (auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it ) {
4166 switch ((*it)->type()) {
4167 case ItemType::Paragraph: {
4168 const auto pp = std::static_pointer_cast<Paragraph<Trait>>(*it);
4169
4170 for (auto it = pp->items().cbegin(), last = pp->items().cend(); it != last; ++it) {
4171 c->appendItem((*it));
4172 }
4173 }
4174 break;
4175
4176 default:
4177 c->appendItem((*it));
4178 break;
4179 }
4180 }
4181 }
4182
4183 if (html.m_html.get()) {
4184 c->appendItem(html.m_html);
4185 }
4186 }
4187
4188 tr->appendCell(c);
4189 }
4190
4191 if (!tr->isEmpty())
4192 table->appendRow(tr);
4193
4194 return true;
4195 };
4196
4197 {
4198 auto fmt = fr.m_data.at(1).first;
4199
4200 auto columns = fmt.split(typename Trait::InternalString(Trait::latin1ToChar(sep)));
4201
4202 for (auto it = columns.begin(), last = columns.end(); it != last; ++it) {
4203 *it = it->simplified();
4204
4205 if (!it->isEmpty()) {
4207
4208 if (it->asString().endsWith(Trait::latin1ToString(":")) &&
4209 it->asString().startsWith(Trait::latin1ToString(":"))) {
4211 } else if (it->asString().endsWith(Trait::latin1ToString(":"))) {
4213 }
4214
4215 table->setColumnAlignment(table->columnsCount(), a);
4216 }
4217 }
4218 }
4219
4220 fr.m_data.erase(fr.m_data.cbegin() + 1);
4221
4222 long long int r = 0;
4223
4224 for (const auto &line : std::as_const(fr.m_data)) {
4225 if (!parseTableRow(line)) {
4226 break;
4227 }
4228
4229 ++r;
4230 }
4231
4232 fr.m_data.erase(fr.m_data.cbegin(), fr.m_data.cbegin() + r);
4233
4234 if (!table->isEmpty() && !collectRefLinks) {
4235 parent->appendItem(table);
4236 }
4237 }
4238}
4239
4240//! \return Is the given string a heading's service sequence?
4241template<class Trait>
4242inline bool
4243isH(const typename Trait::String &s,
4244 const typename Trait::Char &c)
4245{
4246 long long int p = skipSpaces<Trait>(0, s);
4247
4248 if (p > 3) {
4249 return false;
4250 }
4251
4252 const auto start = p;
4253
4254 for (; p < s.size(); ++p) {
4255 if (s[p] != c) {
4256 break;
4257 }
4258 }
4259
4260 if (p - start < 1) {
4261 return false;
4262 }
4263
4264 for (; p < s.size(); ++p) {
4265 if (!s[p].isSpace()) {
4266 return false;
4267 }
4268 }
4269
4270 return true;
4271}
4272
4273//! \return Is the given string a heading's service sequence of level 1?
4274template<class Trait>
4275inline bool
4276isH1(const typename Trait::String &s)
4277{
4278 return isH<Trait>(s, Trait::latin1ToChar('='));
4279}
4280
4281//! \return Is the given string a heading's service sequence of level 2?
4282template<class Trait>
4283inline bool
4284isH2(const typename Trait::String &s)
4285{
4286 return isH<Trait>(s, Trait::latin1ToChar('-'));
4287}
4288
4289//! \return Previous position in the block.
4290template<class Trait>
4291inline std::pair<long long int, long long int>
4293 long long int pos,
4294 long long int line)
4295{
4296 if (pos > 0) {
4297 return {pos - 1, line};
4298 }
4299
4300 for (long long int i = 0; i < static_cast<long long int>(fr.m_data.size()); ++i) {
4301 if (fr.m_data.at(i).second.m_lineNumber == line) {
4302 if (i > 0) {
4303 return {fr.m_data.at(i - 1).first.virginPos(fr.m_data.at(i - 1).first.length() - 1),
4304 line - 1};
4305 }
4306 }
4307 }
4308
4309 return {pos, line};
4310}
4311
4312//! \return Next position in the block.
4313template<class Trait>
4314inline std::pair<long long int, long long int>
4316 long long int pos,
4317 long long int line)
4318{
4319 for (long long int i = 0; i < static_cast<long long int>(fr.m_data.size()); ++i) {
4320 if (fr.m_data.at(i).second.m_lineNumber == line) {
4321 if (fr.m_data.at(i).first.virginPos(fr.m_data.at(i).first.length() - 1) >= pos + 1) {
4322 return {pos + 1, line};
4323 } else if (i + 1 < static_cast<long long int>(fr.m_data.size())) {
4324 return {fr.m_data.at(i + 1).first.virginPos(0), fr.m_data.at(i + 1).second.m_lineNumber};
4325 } else {
4326 return {pos, line};
4327 }
4328 }
4329 }
4330
4331 return {pos, line};
4332}
4333
4334template<class Trait>
4335inline long long int
4336Parser<Trait>::parseParagraph(MdBlock<Trait> &fr,
4337 std::shared_ptr<Block<Trait>> parent,
4338 std::shared_ptr<Document<Trait>> doc,
4339 typename Trait::StringList &linksToParse,
4340 const typename Trait::String &workingPath,
4341 const typename Trait::String &fileName,
4342 bool collectRefLinks,
4343 RawHtmlBlock<Trait> &html)
4344{
4345 return parseFormattedTextLinksImages(fr, parent, doc, linksToParse, workingPath, fileName,
4346 collectRefLinks, false, html, false);
4347}
4348
4349template<class Trait>
4351 static bool
4352 isFreeTag(std::shared_ptr<RawHtml<Trait>> html)
4353 {
4354 return html->isFreeTag();
4355 }
4356
4357 static void
4358 setFreeTag(std::shared_ptr<RawHtml<Trait>> html, bool on)
4359 {
4360 html->setFreeTag(on);
4361 }
4362};
4363
4364template<class Trait>
4365inline typename Parser<Trait>::Delims
4367{
4368 Delims d;
4369
4370 for (long long int line = 0; line < (long long int)fr.size(); ++line) {
4371 const typename Trait::String &str = fr.at(line).first.asString();
4372 const auto p = skipSpaces<Trait>(0, str);
4373 const auto withoutSpaces = str.sliced(p);
4374
4375 if (isHorizontalLine<Trait>(withoutSpaces) && p < 4) {
4376 d.push_back({Delimiter::HorizontalLine, line, 0, str.length(), false, false, false});
4377 } else if (isH1<Trait>(withoutSpaces) && p < 4) {
4378 d.push_back({Delimiter::H1, line, 0, str.length(), false, false, false});
4379 } else if (isH2<Trait>(withoutSpaces) && p < 4) {
4380 d.push_back({Delimiter::H2, line, 0, str.length(), false, false, false});
4381 } else {
4382 bool backslash = false;
4383 bool word = false;
4384
4385 for (long long int i = p; i < str.size(); ++i) {
4386 bool now = false;
4387
4388 if (str[i] == Trait::latin1ToChar('\\') && !backslash) {
4389 backslash = true;
4390 now = true;
4391 }
4392 // * or _
4393 else if ((str[i] == Trait::latin1ToChar('_') || str[i] == Trait::latin1ToChar('*')) && !backslash) {
4394 typename Trait::String style;
4395
4396 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() : true);
4397 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4398 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4399 const bool alNumBefore = (i > 0 ? str[i - 1].isLetterOrNumber() : false);
4400
4401 const auto ch = str[i];
4402
4403 while (i < str.length() && str[i] == ch) {
4404 style.push_back(str[i]);
4405 ++i;
4406 }
4407
4408 typename Delimiter::DelimiterType dt = Delimiter::Unknown;
4409
4410 if (ch == Trait::latin1ToChar('*')) {
4411 dt = Delimiter::Emphasis1;
4412 } else {
4413 dt = Delimiter::Emphasis2;
4414 }
4415
4416 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() : true);
4417 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) : true);
4418 const bool alNumAfter = (i < str.length() ? str[i].isLetterOrNumber() : false);
4419 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore))
4420 && !(ch == Trait::latin1ToChar('_') && alNumBefore && alNumAfter);
4421 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)))
4422 && !(ch == Trait::latin1ToChar('_') && alNumBefore && alNumAfter);
4423
4424 if (leftFlanking || rightFlanking) {
4425 for (auto j = 0; j < style.length(); ++j) {
4426 d.push_back({dt, line, i - style.length() + j, 1,
4427 word, false, leftFlanking, rightFlanking});
4428 }
4429
4430 word = false;
4431 } else {
4432 word = true;
4433 }
4434
4435 --i;
4436 }
4437 // ~
4438 else if (str[i] == Trait::latin1ToChar('~') && !backslash) {
4439 typename Trait::String style;
4440
4441 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() : true);
4442 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4443 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4444
4445 while (i < str.length() && str[i] == Trait::latin1ToChar('~')) {
4446 style.push_back(str[i]);
4447 ++i;
4448 }
4449
4450 if (style.length() <= 2) {
4451 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() : true);
4452 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) : true);
4453 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore));
4454 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)));
4455
4456 if (leftFlanking || rightFlanking) {
4457 d.push_back({Delimiter::Strikethrough,
4458 line,
4459 i - style.length(),
4460 style.length(),
4461 word,
4462 false,
4463 leftFlanking,
4464 rightFlanking});
4465
4466 word = false;
4467 } else {
4468 word = true;
4469 }
4470 } else {
4471 word = true;
4472 }
4473
4474 --i;
4475 }
4476 // [
4477 else if (str[i] == Trait::latin1ToChar('[') && !backslash) {
4478 d.push_back({Delimiter::SquareBracketsOpen, line, i, 1, word, false});
4479
4480 word = false;
4481 }
4482 // !
4483 else if (str[i] == Trait::latin1ToChar('!') && !backslash) {
4484 if (i + 1 < str.length()) {
4485 if (str[i + 1] == Trait::latin1ToChar('[')) {
4486 d.push_back({Delimiter::ImageOpen, line, i, 2, word, false});
4487
4488 ++i;
4489
4490 word = false;
4491 } else {
4492 word = true;
4493 }
4494 } else {
4495 word = true;
4496 }
4497 }
4498 // (
4499 else if (str[i] == Trait::latin1ToChar('(') && !backslash) {
4500 d.push_back({Delimiter::ParenthesesOpen, line, i, 1, word, false});
4501
4502 word = false;
4503 }
4504 // ]
4505 else if (str[i] == Trait::latin1ToChar(']') && !backslash) {
4506 d.push_back({Delimiter::SquareBracketsClose, line, i, 1, word, false});
4507
4508 word = false;
4509 }
4510 // )
4511 else if (str[i] == Trait::latin1ToChar(')') && !backslash) {
4512 d.push_back({Delimiter::ParenthesesClose, line, i, 1, word, false});
4513
4514 word = false;
4515 }
4516 // <
4517 else if (str[i] == Trait::latin1ToChar('<') && !backslash) {
4518 d.push_back({Delimiter::Less, line, i, 1, word, false});
4519
4520 word = false;
4521 }
4522 // >
4523 else if (str[i] == Trait::latin1ToChar('>') && !backslash) {
4524 d.push_back({Delimiter::Greater, line, i, 1, word, false});
4525
4526 word = false;
4527 }
4528 // `
4529 else if (str[i] == Trait::latin1ToChar('`')) {
4530 typename Trait::String code;
4531
4532 while (i < str.length() && str[i] == Trait::latin1ToChar('`')) {
4533 code.push_back(str[i]);
4534 ++i;
4535 }
4536
4537 d.push_back({Delimiter::InlineCode,
4538 line,
4539 i - code.length() - (backslash ? 1 : 0),
4540 code.length() + (backslash ? 1 : 0),
4541 word,
4542 backslash});
4543
4544 word = false;
4545
4546 --i;
4547 }
4548 // $
4549 else if (str[i] == Trait::latin1ToChar('$')) {
4550 typename Trait::String m;
4551
4552 while (i < str.length() && str[i] == Trait::latin1ToChar('$')) {
4553 m.push_back(str[i]);
4554 ++i;
4555 }
4556
4557 if (m.length() <= 2 && !backslash) {
4558 d.push_back({Delimiter::Math, line, i - m.length(), m.length(),
4559 false, false, false, false});
4560 }
4561
4562 word = false;
4563
4564 --i;
4565 } else {
4566 word = true;
4567 }
4568
4569 if (!now) {
4570 backslash = false;
4571 }
4572 }
4573 }
4574 }
4575
4576 return d;
4577}
4578
4579//! \return Is the given string a line break.
4580template<class Trait>
4581inline bool
4582isLineBreak(const typename Trait::String &s)
4583{
4584 long long int count = 0, pos = s.length() - 1, end = s.length() - 1;
4585
4586 while ((pos = Trait::lastIndexOf(s, Trait::latin1ToString("\\"), pos)) != -1 && pos == end) {
4587 --end;
4588 --pos;
4589 ++count;
4590 }
4591
4592 return (s.endsWith(Trait::latin1ToString(" ")) || (count % 2 != 0));
4593}
4594
4595//! \return Length of line break.
4596template<class Trait>
4597inline long long int
4598lineBreakLength(const typename Trait::String &s)
4599{
4600 return (s.endsWith(Trait::latin1ToString(" ")) ? 2 : 1);
4601}
4602
4603//! Remove line break from the end of string.
4604template<class Trait>
4605inline typename Trait::String
4606removeLineBreak(const typename Trait::String &s)
4607{
4608 if (s.endsWith(Trait::latin1ToString("\\"))) {
4609 return s.sliced(0, s.size() - 1);
4610 } else {
4611 return s;
4612 }
4613}
4614
4615//! Initialize item with style information and set it as last item.
4616template<class Trait>
4617inline void
4619 std::shared_ptr<ItemWithOpts<Trait>> item)
4620{
4621 item->openStyles() = po.m_openStyles;
4622 po.m_openStyles.clear();
4623 po.m_lastItemWithStyle = item;
4624}
4625
4626//! Make text item.
4627template<class Trait>
4628inline void
4629makeTextObject(const typename Trait::String &text,
4631 long long int startPos,
4632 long long int startLine,
4633 long long int endPos,
4634 long long int endLine,
4635 bool doRemoveSpacesAtEnd = false)
4636{
4637 if (endPos < 0 && endLine - 1 >= 0) {
4638 endPos = po.m_fr.m_data.at(endLine - 1).first.length() - 1;
4639 --endLine;
4640 }
4641
4642 if (endPos == po.m_fr.m_data.at(endLine).first.length() - 1) {
4643 doRemoveSpacesAtEnd = true;
4644 }
4645
4647
4648 if (doRemoveSpacesAtEnd) {
4650 }
4651
4652 if (startPos == 0) {
4653 if (s.length()) {
4654 const auto p = skipSpaces<Trait>(0, s);
4655
4656 if (p > 0) {
4657 s.remove(0, p);
4658 }
4659 }
4660 }
4661
4662 if (!s.isEmpty()) {
4663 std::shared_ptr<Text<Trait>> t;
4664
4665 if (!po.m_collectRefLinks) {
4666 po.m_rawTextData.push_back({text, startPos, startLine});
4667
4668 t.reset(new Text<Trait>);
4669 t->setText(s);
4670 t->setOpts(po.m_opts);
4671 t->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
4672 t->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
4673 t->setEndColumn(po.m_fr.m_data.at(endLine).first.virginPos(endPos, true));
4674 t->setEndLine(po.m_fr.m_data.at(endLine).second.m_lineNumber);
4675
4677
4678 po.m_parent->setEndColumn(t->endColumn());
4679 po.m_parent->setEndLine(t->endLine());
4680 }
4681
4682 po.m_wasRefLink = false;
4683 po.m_firstInParagraph = false;
4684 po.m_headingAllowed = true;
4685
4686 if (!po.m_collectRefLinks) {
4687 po.m_parent->appendItem(t);
4688
4689 po.m_lastText = t;
4690 }
4691 } else {
4692 po.m_pos = startPos;
4693 }
4694}
4695
4696//! Make text item with line break.
4697template<class Trait>
4698inline void
4699makeTextObjectWithLineBreak(const typename Trait::String &text,
4701 long long int startPos,
4702 long long int startLine,
4703 long long int endPos,
4704 long long int endLine)
4705{
4706 makeTextObject(text, po, startPos, startLine, endPos, endLine, true);
4707
4708 std::shared_ptr<LineBreak<Trait>> hr;
4709
4710 if (!po.m_collectRefLinks) {
4711 hr.reset(new LineBreak<Trait>);
4712 hr->setText(po.m_fr.m_data.at(endLine).first.asString().sliced(endPos + 1));
4713 hr->setStartColumn(po.m_fr.m_data.at(endLine).first.virginPos(endPos + 1));
4714 hr->setStartLine(po.m_fr.m_data.at(endLine).second.m_lineNumber);
4715 hr->setEndColumn(po.m_fr.m_data.at(endLine).first.virginPos(po.m_fr.m_data.at(endLine).first.length() - 1));
4716 hr->setEndLine(po.m_fr.m_data.at(endLine).second.m_lineNumber);
4717 po.m_parent->setEndColumn(hr->endColumn());
4718 po.m_parent->setEndLine(hr->endLine());
4719 }
4720
4721 po.m_wasRefLink = false;
4722 po.m_firstInParagraph = false;
4723
4724 if (!po.m_collectRefLinks) {
4725 po.m_parent->appendItem(hr);
4726 }
4727}
4728
4729//! Check for table in paragraph.
4730template<class Trait>
4731inline void
4733 long long int lastLine)
4734{
4735 if (!po.m_opts) {
4736 long long int i = po.m_pos > 0 ? po.m_line + 1 : po.m_line;
4737
4738 for (; i <= lastLine; ++i) {
4739 const auto h = isTableHeader<Trait>(po.m_fr.m_data[i].first.asString());
4740 const auto c = i + 1 < static_cast<long long int>(po.m_fr.m_data.size()) ?
4741 isTableAlignment<Trait>(po.m_fr.m_data[i + 1].first.asString()) : 0;
4742
4743 if (h && c && c == h) {
4745 po.m_startTableLine = i;
4746 po.m_columnsCount = c;
4747 po.m_lastTextLine = i - 1;
4748 po.m_lastTextPos = po.m_fr.m_data[po.m_lastTextLine].first.length();
4749
4750 return;
4751 }
4752 }
4753 }
4754
4755 po.m_lastTextLine = po.m_fr.m_data.size() - 1;
4756 po.m_lastTextPos = po.m_fr.m_data.back().first.length();
4757}
4758
4759//! Make text item.
4760template<class Trait>
4761inline void
4763 // Inclusive. Don't pass lastLine > actual line position with 0 lastPos. Pass as is,
4764 // i.e. if line length is 18 and you need whole line then pass lastLine = index of line,
4765 // and lastPos = 18, or you may crash here if you will pass lastLine = index of line + 1
4766 // and lastPos = 0...
4767 long long int lastLine,
4768 // Not inclusive
4769 long long int lastPos,
4771{
4772 if (po.m_line > lastLine) {
4773 return;
4774 } else if (po.m_line == lastLine && po.m_pos >= lastPos) {
4775 return;
4776 }
4777
4778 typename Trait::String text;
4779
4780 const auto isLastChar = po.m_pos >= po.m_fr.m_data.at(po.m_line).first.length();
4781 long long int startPos = (isLastChar ? 0 : po.m_pos);
4782 long long int startLine = (isLastChar ? po.m_line + 1 : po.m_line);
4783
4784 bool lineBreak =
4785 (!po.m_ignoreLineBreak && po.m_line != (long long int)(po.m_fr.m_data.size() - 1) &&
4786 (po.m_line == lastLine ? (lastPos == po.m_fr.m_data.at(po.m_line).first.length() &&
4787 isLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.virginSubString())) :
4788 isLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.virginSubString())));
4789
4790 // makeTOWLB
4791 auto makeTOWLB = [&]() {
4792 if (po.m_line != (long long int)(po.m_fr.m_data.size() - 1)) {
4793 const auto &line = po.m_fr.m_data.at(po.m_line).first.asString();
4794
4795 makeTextObjectWithLineBreak(text, po, startPos, startLine,
4796 line.length() - lineBreakLength<Trait>(line) - 1, po.m_line);
4797
4798 startPos = 0;
4799 startLine = po.m_line + 1;
4800
4801 text.clear();
4802 }
4803 }; // makeTOWLB
4804
4805 if (lineBreak) {
4806 text.push_back(removeLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.virginSubString(po.m_pos)));
4807
4808 makeTOWLB();
4809 } else {
4810 const auto length = (po.m_line == lastLine ?
4811 lastPos - po.m_pos : po.m_fr.m_data.at(po.m_line).first.length() - po.m_pos);
4812 const auto s = po.m_fr.m_data.at(po.m_line).first.virginSubString(po.m_pos, length);
4813 text.push_back(s);
4814
4815 po.m_pos = (po.m_line == lastLine ? lastPos : po.m_fr.m_data.at(po.m_line).first.length());
4816
4817 makeTextObject(text,
4818 po,
4819 startPos,
4820 startLine,
4821 po.m_line == lastLine ? lastPos - 1 : po.m_fr.m_data.at(po.m_line).first.length() - 1,
4822 po.m_line);
4823
4824 text.clear();
4825 }
4826
4827 if (po.m_line != lastLine) {
4828 ++po.m_line;
4829
4830 for (; po.m_line < lastLine; ++po.m_line) {
4831 startPos = 0;
4832 startLine = po.m_line;
4833
4834 lineBreak = (!po.m_ignoreLineBreak && po.m_line != (long long int)(po.m_fr.m_data.size() - 1) &&
4835 isLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.asString()));
4836
4837 const auto s = (lineBreak ? removeLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.virginSubString()) :
4838 po.m_fr.m_data.at(po.m_line).first.virginSubString());
4839 text.push_back(s);
4840
4841 if (lineBreak) {
4842 makeTOWLB();
4843 } else {
4844 makeTextObject(text, po, 0, po.m_line,
4845 po.m_fr.m_data.at(po.m_line).first.length() - 1, po.m_line);
4846 }
4847
4848 text.clear();
4849 }
4850
4851 lineBreak = (!po.m_ignoreLineBreak && po.m_line != (long long int)(po.m_fr.m_data.size() - 1) &&
4852 lastPos == po.m_fr.m_data.at(po.m_line).first.length() &&
4853 isLineBreak<Trait>(po.m_fr.m_data.at(po.m_line).first.asString()));
4854
4855 auto s = po.m_fr.m_data.at(po.m_line).first.virginSubString(0, lastPos);
4856
4857 po.m_pos = lastPos;
4858
4859 if (!lineBreak) {
4860 text.push_back(s);
4861
4862 makeTextObject(text, po, 0, lastLine, lastPos - 1, lastLine);
4863 } else {
4865 text.push_back(s);
4866
4867 makeTOWLB();
4868 }
4869 }
4870}
4871
4872//! Skip spaces.
4873template<class Trait>
4874inline void
4875skipSpacesInHtml(long long int &l,
4876 long long int &p,
4877 const typename MdBlock<Trait>::Data &fr)
4878{
4879 while (l < (long long int)fr.size()) {
4880 p = skipSpaces<Trait>(p, fr[l].first.asString());
4881
4882 if (p < fr[l].first.length()) {
4883 return;
4884 }
4885
4886 p = 0;
4887 ++l;
4888 }
4889}
4890
4891//! Read HTML attribute value.
4892template<class Trait>
4893inline std::pair<bool, bool>
4895 long long int &p,
4896 const typename MdBlock<Trait>::Data &fr)
4897{
4898 static const typename Trait::String notAllowed = Trait::latin1ToString("\"`=<'");
4899
4900 const auto start = p;
4901
4902 for (; p < fr[l].first.length(); ++p) {
4903 if (fr[l].first[p].isSpace()) {
4904 break;
4905 } else if (notAllowed.contains(fr[l].first[p])) {
4906 return {false, false};
4907 } else if (fr[l].first[p] == Trait::latin1ToChar('>')) {
4908 return {p - start > 0, p - start > 0};
4909 }
4910 }
4911
4912 return {p - start > 0, p - start > 0};
4913}
4914
4915//! Read HTML attribute value.
4916template<class Trait>
4917inline std::pair<bool, bool>
4918readHtmlAttrValue(long long int &l,
4919 long long int &p,
4920 const typename MdBlock<Trait>::Data &fr)
4921{
4922 if (p < fr[l].first.length() && fr[l].first[p] != Trait::latin1ToChar('"') &&
4923 fr[l].first[p] != Trait::latin1ToChar('\'')) {
4924 return readUnquotedHtmlAttrValue<Trait>(l, p, fr);
4925 }
4926
4927 const auto s = fr[l].first[p];
4928
4929 ++p;
4930
4931 if (p >= fr[l].first.length()) {
4932 return {false, false};
4933 }
4934
4935 for (; l < (long long int)fr.size(); ++l) {
4936 bool doBreak = false;
4937
4938 for (; p < fr[l].first.length(); ++p) {
4939 const auto ch = fr[l].first[p];
4940
4941 if (ch == s) {
4942 doBreak = true;
4943
4944 break;
4945 }
4946 }
4947
4948 if (doBreak) {
4949 break;
4950 }
4951
4952 p = 0;
4953 }
4954
4955 if (l >= (long long int)fr.size()) {
4956 return {false, false};
4957 }
4958
4959 if (p >= fr[l].first.length()) {
4960 return {false, false};
4961 }
4962
4963 if (fr[l].first[p] != s) {
4964 return {false, false};
4965 }
4966
4967 ++p;
4968
4969 return {true, true};
4970}
4971
4972//! Read HTML attribute.
4973template<class Trait>
4974inline std::pair<bool, bool>
4975readHtmlAttr(long long int &l,
4976 long long int &p,
4977 const typename MdBlock<Trait>::Data &fr,
4978 bool checkForSpace)
4979{
4980 long long int tl = l, tp = p;
4981
4982 skipSpacesInHtml<Trait>(l, p, fr);
4983
4984 if (l >= (long long int)fr.size()) {
4985 return {false, false};
4986 }
4987
4988 // /
4989 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar('/')) {
4990 return {false, true};
4991 }
4992
4993 // >
4994 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar('>')) {
4995 return {false, true};
4996 }
4997
4998 if (checkForSpace) {
4999 if (tl == l && tp == p) {
5000 return {false, false};
5001 }
5002 }
5003
5004 const auto start = p;
5005
5006 for (; p < fr[l].first.length(); ++p) {
5007 const auto ch = fr[l].first[p];
5008
5009 if (ch.isSpace() || ch == Trait::latin1ToChar('>') || ch == Trait::latin1ToChar('=')) {
5010 break;
5011 }
5012 }
5013
5014 const typename Trait::String name = fr[l].first.asString().sliced(start, p - start).toLower();
5015
5016 if (!name.startsWith(Trait::latin1ToString("_")) && !name.startsWith(Trait::latin1ToString(":")) &&
5017 !name.isEmpty() && !(name[0].unicode() >= 97 && name[0].unicode() <= 122)) {
5018 return {false, false};
5019 }
5020
5021 static const typename Trait::String allowedInName =
5022 Trait::latin1ToString("abcdefghijklmnopqrstuvwxyz0123456789_.:-");
5023
5024 for (long long int i = 1; i < name.length(); ++i) {
5025 if (!allowedInName.contains(name[i])) {
5026 return {false, false};
5027 }
5028 }
5029
5030 // >
5031 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar('>')) {
5032 return {false, true};
5033 }
5034
5035 tl = l;
5036 tp = p;
5037
5038 skipSpacesInHtml<Trait>(l, p, fr);
5039
5040 if (l >= (long long int)fr.size()) {
5041 return {false, false};
5042 }
5043
5044 // =
5045 if (p < fr[l].first.length()) {
5046 if (fr[l].first[p] != Trait::latin1ToChar('=')) {
5047 l = tl;
5048 p = tp;
5049
5050 return {true, true};
5051 } else {
5052 ++p;
5053 }
5054 } else {
5055 return {true, false};
5056 }
5057
5058 skipSpacesInHtml<Trait>(l, p, fr);
5059
5060 if (l >= (long long int)fr.size()) {
5061 return {false, false};
5062 }
5063
5064 return readHtmlAttrValue<Trait>(l, p, fr);
5065}
5066
5067//! \return Is HTML tag at the given position?
5068template<class Trait>
5069inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
5070isHtmlTag(long long int line, long long int pos, TextParsingOpts<Trait> &po, int rule);
5071
5072//! \return Is after the given position only HTML tags?
5073template<class Trait>
5074inline bool
5076 long long int pos,
5078 int rule)
5079{
5080 static const std::set<typename Trait::String> s_rule1Finish = {Trait::latin1ToString("/pre"),
5081 Trait::latin1ToString("/script"),
5082 Trait::latin1ToString("/style"),
5083 Trait::latin1ToString("/textarea")};
5084
5085 auto p = skipSpaces<Trait>(pos, po.m_fr.m_data[line].first.asString());
5086
5087 while (p < po.m_fr.m_data[line].first.length()) {
5088 bool ok = false;
5089
5090 long long int l;
5091 typename Trait::String tag;
5092
5093 std::tie(ok, l, p, std::ignore, tag) = isHtmlTag(line, p, po, rule);
5094
5095 ++p;
5096
5097 if (rule != 1) {
5098 if (!ok) {
5099 return false;
5100 }
5101
5102 if (l > line) {
5103 return true;
5104 }
5105 } else {
5106 if (s_rule1Finish.find(tag.toLower()) != s_rule1Finish.cend() && l == line) {
5107 return true;
5108 }
5109
5110 if (l > line) {
5111 return false;
5112 }
5113 }
5114
5115 p = skipSpaces<Trait>(p, po.m_fr.m_data[line].first.asString());
5116 }
5117
5118 if (p >= po.m_fr.m_data[line].first.length()) {
5119 return true;
5120 }
5121
5122 return false;
5123}
5124
5125//! \return Is setext heading in the lines?
5126template<class Trait>
5127inline bool
5129 long long int startLine,
5130 long long int endLine)
5131{
5132 for (; startLine <= endLine; ++startLine) {
5133 const auto pos = skipSpaces<Trait>(0, po.m_fr.m_data.at(startLine).first.asString());
5134 const auto line = po.m_fr.m_data.at(startLine).first.asString().sliced(pos);
5135
5136 if ((isH1<Trait>(line) || isH2<Trait>(line)) && pos < 4) {
5137 return true;
5138 }
5139 }
5140
5141 return false;
5142}
5143
5144//! \return Is HTML tag at the given position?
5145template<class Trait>
5146inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
5147isHtmlTag(long long int line,
5148 long long int pos,
5150 int rule)
5151{
5152 if (po.m_fr.m_data[line].first[pos] != Trait::latin1ToChar('<')) {
5153 return {false, line, pos, false, {}};
5154 }
5155
5156 typename Trait::String tag;
5157
5158 long long int l = line;
5159 long long int p = pos + 1;
5160 bool first = false;
5161
5162 {
5163 const auto tmp = skipSpaces<Trait>(0, po.m_fr.m_data[l].first.asString());
5164 first = (tmp == pos);
5165 }
5166
5167 if (p >= po.m_fr.m_data[l].first.length()) {
5168 return {false, line, pos, first, tag};
5169 }
5170
5171 bool closing = false;
5172
5173 if (po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('/')) {
5174 closing = true;
5175
5176 tag.push_back(Trait::latin1ToChar('/'));
5177
5178 ++p;
5179 }
5180
5181 const auto start = p;
5182
5183 // tag
5184 for (; p < po.m_fr.m_data[l].first.length(); ++p) {
5185 const auto ch = po.m_fr.m_data[l].first[p];
5186
5187 if (ch.isSpace() || ch == Trait::latin1ToChar('>') || ch == Trait::latin1ToChar('/')) {
5188 break;
5189 }
5190 }
5191
5192 tag.push_back(po.m_fr.m_data[l].first.asString().sliced(start, p - start));
5193
5194 if (p < po.m_fr.m_data[l].first.length() && po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('/')) {
5195 if (p + 1 < po.m_fr.m_data[l].first.length() &&
5196 po.m_fr.m_data[l].first[p + 1] == Trait::latin1ToChar('>')) {
5197 long long int tmp = 0;
5198
5199 if (rule == 7) {
5200 tmp = skipSpaces<Trait>(p + 2, po.m_fr.m_data[l].first.asString());
5201 }
5202
5203 bool onLine = (first && (rule == 7 ? tmp == po.m_fr.m_data[l].first.length() :
5204 isOnlyHtmlTagsAfterOrClosedRule1(l, p + 2, po, rule == 1)));
5205
5206 if (!isSetextHeadingBetween(po, line, l)) {
5207 return {true, l, p + 1, onLine, tag};
5208 } else {
5209 return {false, line, pos, first, tag};
5210 }
5211 } else {
5212 return {false, line, pos, first, tag};
5213 }
5214 }
5215
5216 if (p < po.m_fr.m_data[l].first.length() && po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('>')) {
5217 long long int tmp = 0;
5218
5219 if (rule == 7) {
5220 tmp = skipSpaces<Trait>(p + 1, po.m_fr.m_data[l].first.asString());
5221 }
5222
5223 bool onLine = (first && (rule == 7 ? tmp == po.m_fr.m_data[l].first.length() :
5224 isOnlyHtmlTagsAfterOrClosedRule1(l, p + 1, po, rule == 1)));
5225
5226 if (!isSetextHeadingBetween(po, line, l)) {
5227 return {true, l, p, onLine, tag};
5228 } else {
5229 return {false, line, pos, first, tag};
5230 }
5231 }
5232
5233 skipSpacesInHtml<Trait>(l, p, po.m_fr.m_data);
5234
5235 if (l >= (long long int)po.m_fr.m_data.size()) {
5236 return {false, line, pos, first, tag};
5237 }
5238
5239 if (po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('>')) {
5240 long long int tmp = 0;
5241
5242 if (rule == 7) {
5243 tmp = skipSpaces<Trait>(p + 1, po.m_fr.m_data[l].first.asString());
5244 }
5245
5246 bool onLine = (first && (rule == 7 ? tmp == po.m_fr.m_data[l].first.length() :
5247 isOnlyHtmlTagsAfterOrClosedRule1(l, p + 1, po, rule == 1)));
5248
5249 if (!isSetextHeadingBetween(po, line, l)) {
5250 return {true, l, p, onLine, tag};
5251 } else {
5252 return {false, line, pos, first, tag};
5253 }
5254 }
5255
5256 bool attr = true;
5257 bool firstAttr = true;
5258
5259 while (attr) {
5260 bool ok = false;
5261
5262 std::tie(attr, ok) = readHtmlAttr<Trait>(l, p, po.m_fr.m_data, !firstAttr);
5263
5264 firstAttr = false;
5265
5266 if (closing && attr) {
5267 return {false, line, pos, first, tag};
5268 }
5269
5270 if (!ok) {
5271 return {false, line, pos, first, tag};
5272 }
5273 }
5274
5275 if (po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('/')) {
5276 ++p;
5277 } else {
5278 skipSpacesInHtml<Trait>(l, p, po.m_fr.m_data);
5279
5280 if (l >= (long long int)po.m_fr.m_data.size()) {
5281 return {false, line, pos, first, tag};
5282 }
5283 }
5284
5285 if (po.m_fr.m_data[l].first[p] == Trait::latin1ToChar('>')) {
5286 long long int tmp = 0;
5287
5288 if (rule == 7) {
5289 tmp = skipSpaces<Trait>(p + 1, po.m_fr.m_data[l].first.asString());
5290 }
5291
5292 bool onLine = (first && (rule == 7 ? tmp == po.m_fr.m_data[l].first.length() :
5293 isOnlyHtmlTagsAfterOrClosedRule1(l, p + 1, po, rule == 1)));
5294
5295 if (!isSetextHeadingBetween(po, line, l)) {
5296 return {true, l, p, onLine, tag};
5297 } else {
5298 return {false, line, pos, first, tag};
5299 }
5300 }
5301
5302 return {false, line, pos, first, {}};
5303}
5304
5305//! Read HTML tag.
5306template<class Trait>
5307inline std::pair<typename Trait::String, bool>
5308Parser<Trait>::readHtmlTag(typename Delims::iterator it,
5309 TextParsingOpts<Trait> &po)
5310{
5311 long long int i = it->m_pos + 1;
5312 const auto start = i;
5313
5314 if (start >= po.m_fr.m_data[it->m_line].first.length()) {
5315 return {{}, false};
5316 }
5317
5318 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5319 const auto ch = po.m_fr.m_data[it->m_line].first[i];
5320
5321 if (ch.isSpace() || ch == Trait::latin1ToChar('>')) {
5322 break;
5323 }
5324 }
5325
5326 return {po.m_fr.m_data[it->m_line].first.asString().sliced(start, i - start),
5327 i < po.m_fr.m_data[it->m_line].first.length() ?
5328 po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar('>') : false};
5329}
5330
5331template<class Trait>
5332inline typename Parser<Trait>::Delims::iterator
5333Parser<Trait>::findIt(typename Delims::iterator it,
5334 typename Delims::iterator last,
5335 TextParsingOpts<Trait> &po)
5336{
5337 auto ret = it;
5338
5339 for (; it != last; ++it) {
5340 if ((it->m_line == po.m_line && it->m_pos < po.m_pos) || it->m_line < po.m_line) {
5341 ret = it;
5342 } else {
5343 break;
5344 }
5345 }
5346
5347 return ret;
5348}
5349
5350//! Read HTML data.
5351template<class Trait>
5352inline void
5353eatRawHtml(long long int line,
5354 long long int pos,
5355 long long int toLine,
5356 long long int toPos,
5358 bool finish,
5359 int htmlRule,
5360 bool onLine,
5361 bool continueEating = false)
5362{
5363 MD_UNUSED(htmlRule)
5364
5365 if (line <= toLine) {
5366 typename Trait::String h = po.m_html.m_html->text();
5367
5368 if (!h.isEmpty() && !continueEating) {
5369 for (long long int i = 0; i < po.m_fr.m_emptyLinesBefore; ++i) {
5370 h.push_back(Trait::latin1ToChar('\n'));
5371 }
5372 }
5373
5374 const auto first = po.m_fr.m_data[line].first.asString().sliced(
5375 pos,
5376 (line == toLine ? (toPos >= 0 ? toPos - pos : po.m_fr.m_data[line].first.length() - pos) :
5377 po.m_fr.m_data[line].first.length() - pos));
5378
5379 if (!h.isEmpty() && !first.isEmpty() && po.m_html.m_html->endLine() != po.m_fr.m_data[line].second.m_lineNumber) {
5380 h.push_back(Trait::latin1ToChar('\n'));
5381 }
5382
5383 if (!first.isEmpty()) {
5384 h.push_back(first);
5385 }
5386
5387 ++line;
5388
5389 for (; line < toLine; ++line) {
5390 h.push_back(Trait::latin1ToChar('\n'));
5391 h.push_back(po.m_fr.m_data[line].first.asString());
5392 }
5393
5394 if (line == toLine && toPos != 0) {
5395 h.push_back(Trait::latin1ToChar('\n'));
5396 h.push_back(po.m_fr.m_data[line].first.asString().sliced(0, toPos > 0 ?
5397 toPos : po.m_fr.m_data[line].first.length()));
5398 }
5399
5400 auto endColumn = toPos;
5401 auto endLine = toLine;
5402
5403 if (endColumn == 0 && endLine > 0) {
5404 --endLine;
5405 endColumn = po.m_fr.m_data.at(endLine).first.length();
5406 }
5407
5408 po.m_html.m_html->setEndColumn(po.m_fr.m_data.at(endLine).first.virginPos(endColumn >= 0 ?
5409 endColumn - 1 : po.m_fr.m_data.at(endLine).first.length() - 1));
5410 po.m_html.m_html->setEndLine(po.m_fr.m_data.at(endLine).second.m_lineNumber);
5411
5412 po.m_line = (toPos >= 0 ? toLine : toLine + 1);
5413 po.m_pos = (toPos >= 0 ? toPos : 0);
5414
5415 if (po.m_line + 1 < static_cast<long long int>(po.m_fr.m_data.size()) &&
5416 po.m_pos >= po.m_fr.m_data.at(po.m_line).first.length()) {
5417 ++po.m_line;
5418 po.m_pos = 0;
5419 }
5420
5421 po.m_html.m_html->setText(h);
5422 }
5423
5425
5426 if (onLine) {
5427 po.m_headingAllowed = false;
5428 po.m_checkLineOnNewType = true;
5429 }
5430
5431 if (finish) {
5432 if (!po.m_collectRefLinks) {
5433 po.m_parent->appendItem(po.m_html.m_html);
5434 po.m_parent->setEndColumn(po.m_html.m_html->endColumn());
5435 po.m_parent->setEndLine(po.m_html.m_html->endLine());
5436 initLastItemWithOpts<Trait>(po, po.m_html.m_html);
5437 po.m_html.m_html->setOpts(po.m_opts);
5438 po.m_lastText = nullptr;
5439 } else {
5440 po.m_tmpHtml = po.m_html.m_html;
5441 }
5442
5443 const auto online = po.m_html.m_onLine;
5444
5445 resetHtmlTag(po.m_html, &po);
5446
5447 if (online) {
5449 }
5450 } else {
5451 po.m_html.m_continueHtml = true;
5452 }
5453}
5454
5455template<class Trait>
5456inline typename Parser<Trait>::Delims::iterator
5457Parser<Trait>::eatRawHtmlTillEmptyLine(typename Delims::iterator it,
5458 typename Delims::iterator last,
5459 long long int line,
5460 long long int pos,
5461 TextParsingOpts<Trait> &po,
5462 int htmlRule,
5463 bool onLine,
5464 bool continueEating)
5465{
5466 long long int emptyLine = line;
5467
5468 if (po.m_fr.m_emptyLinesBefore > 0 && po.m_html.m_html && po.m_html.m_continueHtml) {
5469 po.m_html.m_continueHtml = false;
5470 return it;
5471 }
5472
5473 for (auto it = po.m_fr.m_data.cbegin() + line, last = po.m_fr.m_data.cend(); it != last; ++it) {
5474 if (it->first.asString().simplified().isEmpty()) {
5475 break;
5476 }
5477
5478 ++emptyLine;
5479 }
5480
5481 if (emptyLine < static_cast<long long int>(po.m_fr.m_data.size())) {
5482 eatRawHtml(line, pos, emptyLine, 0, po, true, htmlRule, onLine, continueEating);
5483
5484 return findIt(it, last, po);
5485 } else {
5486 eatRawHtml(line, pos, po.m_fr.m_data.size() - 1, -1, po, false, htmlRule, onLine, continueEating);
5487
5488 if (it != last) {
5489 return std::prev(last);
5490 } else {
5491 return last;
5492 }
5493 }
5494}
5495
5496template<class Trait>
5497inline bool
5498Parser<Trait>::isNewBlockIn(MdBlock<Trait> &fr,
5499 long long int startLine,
5500 long long int endLine)
5501{
5502 for (auto i = startLine + 1; i <= endLine; ++i) {
5503 const auto type = whatIsTheLine(fr.m_data[i].first);
5504
5505 switch (type) {
5515 return true;
5516
5517 default:
5518 break;
5519 }
5520
5521 const auto ns = skipSpaces<Trait>(0, fr.m_data[i].first.asString());
5522
5523 if (ns < 4) {
5524 const auto s = fr.m_data[i].first.asString().sliced(ns);
5525
5526 if (isHorizontalLine<Trait>(s) || isH1<Trait>(s) || isH2<Trait>(s)) {
5527 return true;
5528 }
5529 }
5530 }
5531
5532 return false;
5533}
5534
5535template<class Trait>
5536inline void
5537Parser<Trait>::finishRule1HtmlTag(typename Delims::iterator it,
5538 typename Delims::iterator last,
5539 TextParsingOpts<Trait> &po,
5540 bool skipFirst)
5541{
5542 static const std::set<typename Trait::String> s_finish = {Trait::latin1ToString("/pre"),
5543 Trait::latin1ToString("/script"),
5544 Trait::latin1ToString("/style"),
5545 Trait::latin1ToString("/textarea")};
5546
5547 if (it != last) {
5548 bool ok = false;
5549 long long int l = -1, p = -1;
5550
5551 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less && skipFirst) {
5552 std::tie(ok, l, p, po.m_html.m_onLine, std::ignore) =
5553 isHtmlTag(it->m_line, it->m_pos, po, 1);
5554 }
5555
5556 if (po.m_html.m_onLine) {
5557 for (it = (skipFirst && it != last ? std::next(it) : it); it != last; ++it) {
5558 if (it->m_type == Delimiter::Less) {
5559 typename Trait::String tag;
5560 bool closed = false;
5561
5562 std::tie(tag, closed) = readHtmlTag(it, po);
5563
5564 if (closed) {
5565 if (s_finish.find(tag.toLower()) != s_finish.cend()) {
5566 eatRawHtml(po.m_line, po.m_pos, it->m_line, -1, po,
5567 true, 1, po.m_html.m_onLine);
5568
5569 return;
5570 }
5571 }
5572 }
5573 }
5574 } else if (ok && !isNewBlockIn(po.m_fr, it->m_line, l)) {
5575 eatRawHtml(po.m_line, po.m_pos, l, p + 1, po, true, 1, false);
5576
5577 return;
5578 } else {
5579 resetHtmlTag(po.m_html, &po);
5580
5581 return;
5582 }
5583 }
5584
5585 if (po.m_html.m_onLine) {
5586 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po, false, 1, po.m_html.m_onLine);
5587 } else {
5588 resetHtmlTag(po.m_html, &po);
5589 }
5590}
5591
5592template<class Trait>
5593inline void
5594Parser<Trait>::finishRule2HtmlTag(typename Delims::iterator it,
5595 typename Delims::iterator last,
5596 TextParsingOpts<Trait> &po)
5597{
5598 if (it != last) {
5599 const auto start = it;
5600
5601 MdLineData::CommentData commentData = {2, true};
5602 bool onLine = po.m_html.m_onLine;
5603
5604 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5605 long long int i = po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos);
5606
5607 commentData = po.m_fr.m_data[it->m_line].second.m_htmlCommentData[i];
5608
5609 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5610 po.m_html.m_onLine = onLine;
5611 }
5612
5613 if (commentData.first != -1 && commentData.second) {
5614 for (; it != last; ++it) {
5615 if (it->m_type == Delimiter::Greater) {
5616 auto p = it->m_pos;
5617
5618 bool doContinue = false;
5619
5620 for (char i = 0; i < commentData.first; ++i) {
5621 if (!(p > 0 && po.m_fr.m_data[it->m_line].first[p - 1] == Trait::latin1ToChar('-'))) {
5622 doContinue = true;
5623
5624 break;
5625 }
5626
5627 --p;
5628 }
5629
5630 if (doContinue) {
5631 continue;
5632 }
5633
5634 if (onLine || !isNewBlockIn(po.m_fr, start->m_line, it->m_line)) {
5635 eatRawHtml(po.m_line, po.m_pos, it->m_line,
5636 onLine ? po.m_fr.m_data[it->m_line].first.length() : it->m_pos + 1,
5637 po, true, 2, onLine);
5638 } else {
5639 resetHtmlTag(po.m_html, &po);
5640 }
5641
5642 return;
5643 }
5644 }
5645 }
5646 }
5647
5648 if (po.m_html.m_onLine) {
5649 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po, false, 2, po.m_html.m_onLine);
5650 } else {
5651 resetHtmlTag(po.m_html, &po);
5652 }
5653}
5654
5655template<class Trait>
5656inline void
5657Parser<Trait>::finishRule3HtmlTag(typename Delims::iterator it,
5658 typename Delims::iterator last,
5659 TextParsingOpts<Trait> &po)
5660{
5661 bool onLine = po.m_html.m_onLine;
5662
5663 if (it != last) {
5664 const auto start = it;
5665
5666 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5667 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5668 po.m_html.m_onLine = onLine;
5669 }
5670
5671 for (; it != last; ++it) {
5672 if (it->m_type == Delimiter::Greater) {
5673 if (it->m_pos > 0 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar('?')) {
5674 long long int i = it->m_pos + 1;
5675
5676 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5677 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar('<')) {
5678 break;
5679 }
5680 }
5681
5682 if (onLine || !isNewBlockIn(po.m_fr, start->m_line, it->m_line)) {
5683 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po, true, 3, onLine);
5684 } else {
5685 resetHtmlTag(po.m_html, &po);
5686 }
5687
5688 return;
5689 }
5690 }
5691 }
5692 }
5693
5694 if (po.m_html.m_onLine) {
5695 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po, false, 3, onLine);
5696 } else {
5697 resetHtmlTag(po.m_html, &po);
5698 }
5699}
5700
5701template<class Trait>
5702inline void
5703Parser<Trait>::finishRule4HtmlTag(typename Delims::iterator it,
5704 typename Delims::iterator last,
5705 TextParsingOpts<Trait> &po)
5706{
5707 if (it != last) {
5708 const auto start = it;
5709
5710 bool onLine = po.m_html.m_onLine;
5711
5712 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5713 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5714 po.m_html.m_onLine = onLine;
5715 }
5716
5717 for (; it != last; ++it) {
5718 if (it->m_type == Delimiter::Greater) {
5719 long long int i = it->m_pos + 1;
5720
5721 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5722 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar('<')) {
5723 break;
5724 }
5725 }
5726
5727 if (onLine || !isNewBlockIn(po.m_fr, start->m_line, it->m_line)) {
5728 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po, true, 4, onLine);
5729 } else {
5730 resetHtmlTag(po.m_html, &po);
5731 }
5732
5733 return;
5734 }
5735 }
5736 }
5737
5738 if (po.m_html.m_onLine) {
5739 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po, false, 4, true);
5740 } else {
5741 resetHtmlTag(po.m_html, &po);
5742 }
5743}
5744
5745template<class Trait>
5746inline void
5747Parser<Trait>::finishRule5HtmlTag(typename Delims::iterator it,
5748 typename Delims::iterator last,
5749 TextParsingOpts<Trait> &po)
5750{
5751 if (it != last) {
5752 const auto start = it;
5753
5754 bool onLine = po.m_html.m_onLine;
5755
5756 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5757 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5758 po.m_html.m_onLine = onLine;
5759 }
5760
5761 for (; it != last; ++it) {
5762 if (it->m_type == Delimiter::Greater) {
5763 if (it->m_pos > 1 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar(']') &&
5764 po.m_fr.m_data[it->m_line].first[it->m_pos - 2] == Trait::latin1ToChar(']')) {
5765 long long int i = it->m_pos + 1;
5766
5767 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5768 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar('<')) {
5769 break;
5770 }
5771 }
5772
5773 if (onLine || !isNewBlockIn(po.m_fr, start->m_line, it->m_line)) {
5774 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po, true, 5, onLine);
5775 } else {
5776 resetHtmlTag(po.m_html, &po);
5777 }
5778
5779 return;
5780 }
5781 }
5782 }
5783 }
5784
5785 if (po.m_html.m_onLine) {
5786 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po, false, 5, true);
5787 } else {
5788 resetHtmlTag(po.m_html, &po);
5789 }
5790}
5791
5792template<class Trait>
5793inline void
5794Parser<Trait>::finishRule6HtmlTag(typename Delims::iterator it,
5795 typename Delims::iterator last,
5796 TextParsingOpts<Trait> &po)
5797{
5798 if (!po.m_html.m_onLine) {
5799 po.m_html.m_onLine = (it != last ?
5800 it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()) : true);
5801 }
5802
5803 if (po.m_html.m_onLine) {
5804 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 6, true, true);
5805 } else {
5806 const auto nit = std::find_if(std::next(it), last, [](const auto &d) {
5807 return (d.m_type == Delimiter::Greater);
5808 });
5809
5810 if (nit != last && !isNewBlockIn(po.m_fr, it->m_line, nit->m_line)) {
5811 eatRawHtml(po.m_line, po.m_pos, nit->m_line, nit->m_pos + nit->m_len, po,
5812 true, 6, false);
5813 }
5814 }
5815}
5816
5817template<class Trait>
5818inline void
5819Parser<Trait>::finishRule7HtmlTag(typename Delims::iterator it,
5820 typename Delims::iterator last,
5821 TextParsingOpts<Trait> &po)
5822{
5823 if (po.m_html.m_onLine) {
5824 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 7, true, true);
5825 } else if (it != last) {
5826 const auto start = it;
5827 long long int l = -1, p = -1;
5828 bool onLine = false;
5829 bool ok = false;
5830
5831 std::tie(ok, l, p, onLine, std::ignore) = isHtmlTag(it->m_line, it->m_pos, po, 7);
5832
5833 onLine = onLine && it->m_line == 0 && l == start->m_line;
5834
5835 if (ok) {
5836 eatRawHtml(po.m_line, po.m_pos, l, ++p, po, !onLine, 7, onLine);
5837
5838 po.m_html.m_onLine = onLine;
5839
5840 if (onLine) {
5841 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 7, onLine, true);
5842 }
5843 }
5844 } else {
5845 resetHtmlTag(po.m_html, &po);
5846 }
5847}
5848
5849template<class Trait>
5850inline typename Parser<Trait>::Delims::iterator
5851Parser<Trait>::finishRawHtmlTag(typename Delims::iterator it,
5852 typename Delims::iterator last,
5853 TextParsingOpts<Trait> &po,
5854 bool skipFirst)
5855{
5856 po.m_detected = TextParsingOpts<Trait>::Detected::HTML;
5857
5858 switch (po.m_html.m_htmlBlockType) {
5859 case 1:
5860 finishRule1HtmlTag(it, last, po, skipFirst);
5861 break;
5862
5863 case 2:
5864 finishRule2HtmlTag(it, last, po);
5865 break;
5866
5867 case 3:
5868 finishRule3HtmlTag(it, last, po);
5869 break;
5870
5871 case 4:
5872 finishRule4HtmlTag(it, last, po);
5873 break;
5874
5875 case 5:
5876 finishRule5HtmlTag(it, last, po);
5877 break;
5878
5879 case 6:
5880 finishRule6HtmlTag(it, last, po);
5881 break;
5882
5883 case 7:
5884 finishRule7HtmlTag(it, last, po);
5885 break;
5886
5887 default:
5888 po.m_detected = TextParsingOpts<Trait>::Detected::Nothing;
5889 break;
5890 }
5891
5892 return findIt(it, last, po);
5893}
5894
5895template<class Trait>
5896inline int
5897Parser<Trait>::htmlTagRule(typename Delims::iterator it,
5898 typename Delims::iterator last,
5899 TextParsingOpts<Trait> &po)
5900{
5901 MD_UNUSED(last)
5902
5903 typename Trait::String tag;
5904
5905 std::tie(tag, std::ignore) = readHtmlTag(it, po);
5906
5907 if (tag.startsWith(Trait::latin1ToString("![CDATA["))) {
5908 return 5;
5909 }
5910
5911 tag = tag.toLower();
5912
5913 static const typename Trait::String s_validHtmlTagLetters =
5914 Trait::latin1ToString("abcdefghijklmnopqrstuvwxyz0123456789-");
5915
5916 bool closing = false;
5917
5918 if (tag.startsWith(Trait::latin1ToString("/"))) {
5919 tag.remove(0, 1);
5920 closing = true;
5921 }
5922
5923 if (tag.endsWith(Trait::latin1ToString("/"))) {
5924 tag.remove(tag.size() - 1, 1);
5925 }
5926
5927 if (tag.isEmpty()) {
5928 return -1;
5929 }
5930
5931 if (!tag.startsWith(Trait::latin1ToString("!")) &&
5932 !tag.startsWith(Trait::latin1ToString("?")) &&
5933 !(tag[0].unicode() >= 97 && tag[0].unicode() <= 122)) {
5934 return -1;
5935 }
5936
5937 static const std::set<typename Trait::String> s_rule1 = {Trait::latin1ToString("pre"),
5938 Trait::latin1ToString("script"),
5939 Trait::latin1ToString("style"),
5940 Trait::latin1ToString("textarea")};
5941
5942 if (!closing && s_rule1.find(tag) != s_rule1.cend()) {
5943 return 1;
5944 } else if (tag.startsWith(Trait::latin1ToString("!--"))) {
5945 return 2;
5946 } else if (tag.startsWith(Trait::latin1ToString("?"))) {
5947 return 3;
5948 } else if (tag.startsWith(Trait::latin1ToString("!")) && tag.size() > 1 &&
5949 ((tag[1].unicode() >= 65 && tag[1].unicode() <= 90) ||
5950 (tag[1].unicode() >= 97 && tag[1].unicode() <= 122))) {
5951 return 4;
5952 } else {
5953 static const std::set<typename Trait::String> s_rule6 = {
5954 Trait::latin1ToString("address"), Trait::latin1ToString("article"), Trait::latin1ToString("aside"), Trait::latin1ToString("base"),
5955 Trait::latin1ToString("basefont"), Trait::latin1ToString("blockquote"), Trait::latin1ToString("body"), Trait::latin1ToString("caption"),
5956 Trait::latin1ToString("center"), Trait::latin1ToString("col"), Trait::latin1ToString("colgroup"), Trait::latin1ToString("dd"),
5957 Trait::latin1ToString("details"), Trait::latin1ToString("dialog"), Trait::latin1ToString("dir"), Trait::latin1ToString("div"),
5958 Trait::latin1ToString("dl"), Trait::latin1ToString("dt"), Trait::latin1ToString("fieldset"), Trait::latin1ToString("figcaption"),
5959 Trait::latin1ToString("figure"), Trait::latin1ToString("footer"), Trait::latin1ToString("form"), Trait::latin1ToString("frame"),
5960 Trait::latin1ToString("frameset"), Trait::latin1ToString("h1"), Trait::latin1ToString("h2"), Trait::latin1ToString("h3"),
5961 Trait::latin1ToString("h4"), Trait::latin1ToString("h5"), Trait::latin1ToString("h6"), Trait::latin1ToString("head"),
5962 Trait::latin1ToString("header"), Trait::latin1ToString("hr"), Trait::latin1ToString("html"), Trait::latin1ToString("iframe"),
5963 Trait::latin1ToString("legend"), Trait::latin1ToString("li"), Trait::latin1ToString("link"), Trait::latin1ToString("main"),
5964 Trait::latin1ToString("menu"), Trait::latin1ToString("menuitem"), Trait::latin1ToString("nav"), Trait::latin1ToString("noframes"),
5965 Trait::latin1ToString("ol"), Trait::latin1ToString("optgroup"), Trait::latin1ToString("option"), Trait::latin1ToString("p"),
5966 Trait::latin1ToString("param"), Trait::latin1ToString("section"), Trait::latin1ToString("search"), Trait::latin1ToString("summary"),
5967 Trait::latin1ToString("table"), Trait::latin1ToString("tbody"), Trait::latin1ToString("td"), Trait::latin1ToString("tfoot"),
5968 Trait::latin1ToString("th"), Trait::latin1ToString("thead"), Trait::latin1ToString("title"), Trait::latin1ToString("tr"),
5969 Trait::latin1ToString("track"), Trait::latin1ToString("ul")};
5970
5971 for (long long int i = 1; i < tag.size(); ++i) {
5972 if (!s_validHtmlTagLetters.contains(tag[i])) {
5973 return -1;
5974 }
5975 }
5976
5977 if (s_rule6.find(tag) != s_rule6.cend()) {
5978 return 6;
5979 } else {
5980 bool tag = false;
5981
5982 std::tie(tag, std::ignore, std::ignore, std::ignore, std::ignore) =
5983 isHtmlTag(it->m_line, it->m_pos, po, 7);
5984
5985 if (tag) {
5986 return 7;
5987 }
5988 }
5989 }
5990
5991 return -1;
5992}
5993
5994template<class Trait>
5995inline typename Parser<Trait>::Delims::iterator
5996Parser<Trait>::checkForRawHtml(typename Delims::iterator it,
5997 typename Delims::iterator last,
5998 TextParsingOpts<Trait> &po)
5999{
6000 const auto rule = htmlTagRule(it, last, po);
6001
6002 if (rule == -1) {
6003 resetHtmlTag(po.m_html, &po);
6004
6005 po.m_firstInParagraph = false;
6006
6007 return it;
6008 }
6009
6010 po.m_html.m_htmlBlockType = rule;
6011 po.m_html.m_html.reset(new RawHtml<Trait>);
6012 po.m_html.m_html->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
6013 po.m_html.m_html->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
6014
6015 return finishRawHtmlTag(it, last, po, true);
6016}
6017
6018template<class Trait>
6019inline typename Parser<Trait>::Delims::iterator
6020Parser<Trait>::checkForMath(typename Delims::iterator it,
6021 typename Delims::iterator last,
6022 TextParsingOpts<Trait> &po)
6023{
6024 po.m_wasRefLink = false;
6025 po.m_firstInParagraph = false;
6026 po.m_headingAllowed = true;
6027
6028 const auto end = std::find_if(std::next(it), last, [&](const auto &d) {
6029 return (d.m_type == Delimiter::Math && d.m_len == it->m_len);
6030 });
6031
6032 if (end != last && end->m_line <= po.m_lastTextLine) {
6033 typename Trait::String math;
6034
6035 if (it->m_line == end->m_line) {
6036 math = po.m_fr.m_data[it->m_line].first.asString().sliced(
6037 it->m_pos + it->m_len, end->m_pos - (it->m_pos + it->m_len));
6038 } else {
6039 math = po.m_fr.m_data[it->m_line].first.asString().sliced(it->m_pos + it->m_len);
6040
6041 for (long long int i = it->m_line + 1; i < end->m_line; ++i) {
6042 math.push_back(Trait::latin1ToChar('\n'));
6043 math.push_back(po.m_fr.m_data[i].first.asString());
6044 }
6045
6046 math.push_back(Trait::latin1ToChar('\n'));
6047 math.push_back(po.m_fr.m_data[end->m_line].first.asString().sliced(0, end->m_pos));
6048 }
6049
6050 if (!po.m_collectRefLinks) {
6051 std::shared_ptr<Math<Trait>> m(new Math<Trait>);
6052
6053 auto startLine = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
6054 auto startColumn = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len);
6055
6056 if (it->m_pos + it->m_len >= po.m_fr.m_data.at(it->m_line).first.length()) {
6057 std::tie(startColumn, startLine) = nextPosition(po.m_fr, startColumn, startLine);
6058 }
6059
6060 auto endColumn = po.m_fr.m_data.at(end->m_line).first.virginPos(end->m_pos);
6061 auto endLine = po.m_fr.m_data.at(end->m_line).second.m_lineNumber;
6062
6063 if (endColumn == 0) {
6064 std::tie(endColumn, endLine) = prevPosition(po.m_fr, endColumn, endLine);
6065 } else {
6066 --endColumn;
6067 }
6068
6069 m->setStartColumn(startColumn);
6070 m->setStartLine(startLine);
6071 m->setEndColumn(endColumn);
6072 m->setEndLine(endLine);
6073 m->setInline(it->m_len == 1);
6074 m->setStartDelim({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6075 po.m_fr.m_data[it->m_line].second.m_lineNumber,
6076 po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
6077 po.m_fr.m_data[it->m_line].second.m_lineNumber});
6078 m->setEndDelim({po.m_fr.m_data[end->m_line].first.virginPos(end->m_pos),
6079 po.m_fr.m_data[end->m_line].second.m_lineNumber,
6080 po.m_fr.m_data[end->m_line].first.virginPos(end->m_pos + end->m_len - 1),
6081 po.m_fr.m_data[end->m_line].second.m_lineNumber});
6082 m->setFensedCode(false);
6083
6084 initLastItemWithOpts<Trait>(po, m);
6085
6086 if (math.startsWith(Trait::latin1ToString("`")) &&
6087 math.endsWith(Trait::latin1ToString("`")) &&
6088 !math.endsWith(Trait::latin1ToString("\\`")) &&
6089 math.length() > 1) {
6090 math = math.sliced(1, math.length() - 2);
6091 }
6092
6093 m->setExpr(math);
6094
6095 po.m_parent->appendItem(m);
6096
6097 po.m_pos = end->m_pos + end->m_len;
6098 po.m_line = end->m_line;
6099 po.m_lastText = nullptr;
6100 }
6101
6102 return end;
6103 }
6104
6105 return it;
6106}
6107
6108template<class Trait>
6109inline typename Parser<Trait>::Delims::iterator
6110Parser<Trait>::checkForAutolinkHtml(typename Delims::iterator it,
6111 typename Delims::iterator last,
6112 TextParsingOpts<Trait> &po,
6113 bool updatePos)
6114{
6115 const auto nit = std::find_if(std::next(it), last, [](const auto &d) {
6116 return (d.m_type == Delimiter::Greater);
6117 });
6118
6119 if (nit != last) {
6120 if (nit->m_line == it->m_line) {
6121 const auto url = po.m_fr.m_data.at(it->m_line).first.asString().sliced(
6122 it->m_pos + 1, nit->m_pos - it->m_pos - 1);
6123
6124 bool isUrl = true;
6125
6126 for (long long int i = 0; i < url.size(); ++i) {
6127 if (url[i].isSpace()) {
6128 isUrl = false;
6129
6130 break;
6131 }
6132 }
6133
6134 if (isUrl) {
6135 if (!isValidUrl<Trait>(url) && !isEmail<Trait>(url)) {
6136 isUrl = false;
6137 }
6138 }
6139
6140 if (isUrl) {
6141 if (!po.m_collectRefLinks) {
6142 std::shared_ptr<Link<Trait>> lnk(new Link<Trait>);
6143 lnk->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
6144 lnk->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
6145 lnk->setEndColumn(po.m_fr.m_data.at(nit->m_line).first.virginPos(nit->m_pos + nit->m_len - 1));
6146 lnk->setEndLine(po.m_fr.m_data.at(nit->m_line).second.m_lineNumber);
6147 lnk->setUrl(url);
6148 lnk->setOpts(po.m_opts);
6149 lnk->setTextPos({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + 1),
6150 po.m_fr.m_data[it->m_line].second.m_lineNumber,
6151 po.m_fr.m_data[nit->m_line].first.virginPos(nit->m_pos - 1),
6152 po.m_fr.m_data[nit->m_line].second.m_lineNumber});
6153 lnk->setUrlPos(lnk->textPos());
6154 po.m_parent->appendItem(lnk);
6155 }
6156
6157 po.m_wasRefLink = false;
6158 po.m_firstInParagraph = false;
6159 po.m_lastText = nullptr;
6160
6161 if (updatePos) {
6162 po.m_pos = nit->m_pos + nit->m_len;
6163 po.m_line = nit->m_line;
6164 }
6165
6166 return nit;
6167 } else {
6168 return checkForRawHtml(it, last, po);
6169 }
6170 } else {
6171 return checkForRawHtml(it, last, po);
6172 }
6173 } else {
6174 return checkForRawHtml(it, last, po);
6175 }
6176}
6177
6178template<class Trait>
6179inline void
6180Parser<Trait>::makeInlineCode(long long int startLine,
6181 long long int startPos,
6182 long long int lastLine,
6183 long long int lastPos,
6184 TextParsingOpts<Trait> &po,
6185 typename Delims::iterator startDelimIt,
6186 typename Delims::iterator endDelimIt)
6187{
6188 typename Trait::String c;
6189
6190 for (; po.m_line <= lastLine; ++po.m_line) {
6191 c.push_back(po.m_fr.m_data.at(po.m_line).first.asString().sliced(
6192 po.m_pos, (po.m_line == lastLine ? lastPos - po.m_pos :
6193 po.m_fr.m_data.at(po.m_line).first.length() - po.m_pos)));
6194
6195 if (po.m_line < lastLine) {
6196 c.push_back(Trait::latin1ToChar(' '));
6197 }
6198
6199 po.m_pos = 0;
6200 }
6201
6202 po.m_line = lastLine;
6203
6204 if (c[0] == Trait::latin1ToChar(' ') && c[c.size() - 1] == Trait::latin1ToChar(' ') &&
6205 skipSpaces<Trait>(0, c) < c.size()) {
6206 c.remove(0, 1);
6207 c.remove(c.size() - 1, 1);
6208 ++startPos;
6209 --lastPos;
6210 }
6211
6212 if (!c.isEmpty()) {
6213 auto code = std::make_shared<Code<Trait>>(c, false, true);
6214
6215 code->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6216 code->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6217 code->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6218 code->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6219 code->setStartDelim({po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6220 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)),
6221 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber,
6222 po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6223 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)) +
6224 startDelimIt->m_len - 1 - (startDelimIt->m_backslashed ? 1 : 0),
6225 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber});
6226 code->setEndDelim(
6227 {po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6228 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0)),
6229 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber,
6230 po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6231 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0) +
6232 endDelimIt->m_len - 1 - (endDelimIt->m_backslashed ? 1 : 0)),
6233 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber});
6234 code->setOpts(po.m_opts);
6235
6236 initLastItemWithOpts<Trait>(po, code);
6237
6238 po.m_parent->appendItem(code);
6239 }
6240
6241 po.m_wasRefLink = false;
6242 po.m_firstInParagraph = false;
6243 po.m_lastText = nullptr;
6244}
6245
6246template<class Trait>
6247inline typename Parser<Trait>::Delims::iterator
6248Parser<Trait>::checkForInlineCode(typename Delims::iterator it,
6249 typename Delims::iterator last,
6250 TextParsingOpts<Trait> &po)
6251{
6252 const auto len = it->m_len;
6253 const auto start = it;
6254
6255 po.m_wasRefLink = false;
6256 po.m_firstInParagraph = false;
6257 po.m_headingAllowed = true;
6258
6259 ++it;
6260
6261 for (; it != last; ++it) {
6262 if (it->m_line <= po.m_lastTextLine) {
6263 const auto p = skipSpaces<Trait>(0, po.m_fr.m_data.at(it->m_line).first.asString());
6264 const auto withoutSpaces = po.m_fr.m_data.at(it->m_line).first.asString().sliced(p);
6265
6266 if ((it->m_type == Delimiter::HorizontalLine && withoutSpaces[0] == Trait::latin1ToChar('-')) ||
6267 it->m_type == Delimiter::H1 || it->m_type == Delimiter::H2) {
6268 break;
6269 } else if (it->m_type == Delimiter::InlineCode && (it->m_len - (it->m_backslashed ? 1 : 0)) == len) {
6270 makeText(start->m_line, start->m_pos, po);
6271
6272 if (!po.m_collectRefLinks) {
6273 po.m_pos = start->m_pos + start->m_len;
6274
6275 makeInlineCode(start->m_line, start->m_pos + start->m_len, it->m_line,
6276 it->m_pos + (it->m_backslashed ? 1 : 0), po, start, it);
6277
6278 po.m_line = it->m_line;
6279 po.m_pos = it->m_pos + it->m_len;
6280 }
6281
6282 return it;
6283 }
6284 } else {
6285 break;
6286 }
6287 }
6288
6289 makeText(start->m_line, start->m_pos + start->m_len, po);
6290
6291 return start;
6292}
6293
6294template<class Trait>
6295inline std::pair<typename MdBlock<Trait>::Data, typename Parser<Trait>::Delims::iterator>
6296Parser<Trait>::readTextBetweenSquareBrackets(typename Delims::iterator start,
6297 typename Delims::iterator it,
6298 typename Delims::iterator last,
6299 TextParsingOpts<Trait> &po,
6300 bool doNotCreateTextOnFail,
6301 WithPosition *pos)
6302{
6303 if (it != last && it->m_line <= po.m_lastTextLine) {
6304 if (start->m_line == it->m_line) {
6305 const auto p = start->m_pos + start->m_len;
6306 const auto n = it->m_pos - p;
6307
6308 if (pos) {
6309 long long int startPos, startLine, endPos, endLine;
6310 std::tie(startPos, startLine) = nextPosition(po.m_fr,
6311 po.m_fr.m_data[start->m_line].first.virginPos(
6312 start->m_pos + start->m_len - 1),
6313 po.m_fr.m_data[start->m_line].second.m_lineNumber);
6314 std::tie(endPos, endLine) =
6315 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6316 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6317
6318 *pos = {startPos, startLine, endPos, endLine};
6319 }
6320
6321 return {{{po.m_fr.m_data.at(start->m_line).first.sliced(p, n),
6322 {po.m_fr.m_data.at(start->m_line).second.m_lineNumber}}}, it};
6323 } else {
6324 typename MdBlock<Trait>::Data res;
6325 res.push_back({po.m_fr.m_data.at(start->m_line).first.sliced(
6326 start->m_pos + start->m_len), po.m_fr.m_data.at(start->m_line).second});
6327
6328 long long int i = start->m_line + 1;
6329
6330 for (; i <= it->m_line; ++i) {
6331 if (i == it->m_line) {
6332 res.push_back({po.m_fr.m_data.at(i).first.sliced(0, it->m_pos),
6333 po.m_fr.m_data.at(i).second});
6334 } else {
6335 res.push_back({po.m_fr.m_data.at(i).first, po.m_fr.m_data.at(i).second});
6336 }
6337 }
6338
6339 if (pos) {
6340 long long int startPos, startLine, endPos, endLine;
6341 std::tie(startPos, startLine) = nextPosition(po.m_fr,
6342 po.m_fr.m_data[start->m_line].first.virginPos(
6343 start->m_pos + start->m_len - 1),
6344 po.m_fr.m_data[start->m_line].second.m_lineNumber);
6345 std::tie(endPos, endLine) =
6346 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6347 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6348
6349 *pos = {startPos, startLine, endPos, endLine};
6350 }
6351
6352 return {res, it};
6353 }
6354 } else {
6355 if (!doNotCreateTextOnFail) {
6356 makeText(start->m_line, start->m_pos + start->m_len, po);
6357 }
6358
6359 return {{}, start};
6360 }
6361}
6362
6363template<class Trait>
6364inline std::pair<typename MdBlock<Trait>::Data, typename Parser<Trait>::Delims::iterator>
6365Parser<Trait>::checkForLinkText(typename Delims::iterator it,
6366 typename Delims::iterator last,
6367 TextParsingOpts<Trait> &po,
6368 WithPosition *pos)
6369{
6370 const auto start = it;
6371
6372 long long int brackets = 0;
6373
6374 const bool collectRefLinks = po.m_collectRefLinks;
6375 po.m_collectRefLinks = true;
6376 long long int l = po.m_line, p = po.m_pos;
6377
6378 for (it = std::next(it); it != last; ++it) {
6379 bool quit = false;
6380
6381 switch (it->m_type) {
6382 case Delimiter::SquareBracketsClose: {
6383 if (!brackets)
6384 quit = true;
6385 else
6386 --brackets;
6387 } break;
6388
6389 case Delimiter::SquareBracketsOpen:
6390 case Delimiter::ImageOpen:
6391 ++brackets;
6392 break;
6393
6394 case Delimiter::InlineCode:
6395 it = checkForInlineCode(it, last, po);
6396 break;
6397
6398 case Delimiter::Less:
6399 it = checkForAutolinkHtml(it, last, po, false);
6400 break;
6401
6402 default:
6403 break;
6404 }
6405
6406 if (quit) {
6407 break;
6408 }
6409 }
6410
6411 const auto r = readTextBetweenSquareBrackets(start, it, last, po, false, pos);
6412
6413 po.m_collectRefLinks = collectRefLinks;
6414 resetHtmlTag(po.m_html, &po);
6415 po.m_line = l;
6416 po.m_pos = p;
6417
6418 return r;
6419}
6420
6421template<class Trait>
6422inline std::pair<typename MdBlock<Trait>::Data, typename Parser<Trait>::Delims::iterator>
6423Parser<Trait>::checkForLinkLabel(typename Delims::iterator it,
6424 typename Delims::iterator last,
6425 TextParsingOpts<Trait> &po,
6426 WithPosition *pos)
6427{
6428 const auto start = it;
6429
6430 for (it = std::next(it); it != last; ++it) {
6431 bool quit = false;
6432
6433 switch (it->m_type) {
6434 case Delimiter::SquareBracketsClose: {
6435 quit = true;
6436 } break;
6437
6438 case Delimiter::SquareBracketsOpen:
6439 case Delimiter::ImageOpen: {
6440 it = last;
6441 quit = true;
6442 } break;
6443
6444 default:
6445 break;
6446 }
6447
6448 if (quit)
6449 break;
6450 }
6451
6452 return readTextBetweenSquareBrackets(start, it, last, po, true, pos);
6453}
6454
6455template<class Trait>
6456inline typename Trait::String
6457Parser<Trait>::toSingleLine(const typename MdBlock<Trait>::Data &d)
6458{
6459 typename Trait::String res;
6460 bool first = true;
6461
6462 for (const auto &s : d) {
6463 if (!first) {
6464 res.push_back(Trait::latin1ToChar(' '));
6465 }
6466 res.push_back(s.first.asString().simplified());
6467 first = false;
6468 }
6469
6470 return res;
6471}
6472
6473template<class Trait>
6474inline std::shared_ptr<Link<Trait>>
6475Parser<Trait>::makeLink(const typename Trait::String &url,
6476 const typename MdBlock<Trait>::Data &text,
6477 TextParsingOpts<Trait> &po,
6478 bool doNotCreateTextOnFail,
6479 long long int startLine,
6480 long long int startPos,
6481 long long int lastLine,
6482 long long int lastPos,
6483 const WithPosition &textPos,
6484 const WithPosition &urlPos)
6485{
6486 MD_UNUSED(doNotCreateTextOnFail)
6487
6488 typename Trait::String u = (url.startsWith(Trait::latin1ToString("#")) ?
6489 url : removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6490
6491 if (!u.isEmpty()) {
6492 if (!u.startsWith(Trait::latin1ToString("#"))) {
6493 const auto checkForFile = [&](typename Trait::String &url,
6494 const typename Trait::String &ref = {}) -> bool {
6495 if (Trait::fileExists(url)) {
6496 url = Trait::absoluteFilePath(url);
6497
6498 if (!po.m_collectRefLinks) {
6499 po.m_linksToParse.push_back(url);
6500 }
6501
6502 if (!ref.isEmpty()) {
6503 url = ref + Trait::latin1ToString("/") + url;
6504 }
6505
6506 return true;
6507 } else if (Trait::fileExists(url, po.m_workingPath)) {
6508 url = Trait::absoluteFilePath(po.m_workingPath + Trait::latin1ToString("/") + url);
6509
6510 if (!po.m_collectRefLinks) {
6511 po.m_linksToParse.push_back(url);
6512 }
6513
6514 if (!ref.isEmpty()) {
6515 url = ref + Trait::latin1ToString("/") + url;
6516 }
6517
6518 return true;
6519 } else {
6520 return false;
6521 }
6522 };
6523
6524 if (!checkForFile(u) && u.contains(Trait::latin1ToChar('#'))) {
6525 const auto i = u.indexOf(Trait::latin1ToChar('#'));
6526 const auto ref = u.sliced(i);
6527 u = u.sliced(0, i);
6528
6529 if (!checkForFile(u, ref)) {
6530 u = u + ref;
6531 }
6532 }
6533 } else
6534 u = u + (po.m_workingPath.isEmpty() ? typename Trait::String() :
6535 Trait::latin1ToString("/") + po.m_workingPath) + Trait::latin1ToString("/") +
6536 po.m_fileName;
6537 }
6538
6539 std::shared_ptr<Link<Trait>> link(new Link<Trait>);
6540 link->setUrl(u);
6541 link->setOpts(po.m_opts);
6542 link->setTextPos(textPos);
6543 link->setUrlPos(urlPos);
6544
6545 MdBlock<Trait> block = {text, 0};
6546
6547 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
6548
6549 RawHtmlBlock<Trait> html;
6550
6551 parseFormattedTextLinksImages(block,
6552 std::static_pointer_cast<Block<Trait>>(p),
6553 po.m_doc,
6554 po.m_linksToParse,
6555 po.m_workingPath,
6556 po.m_fileName,
6557 po.m_collectRefLinks,
6558 true,
6559 html,
6560 true);
6561
6562 if (!p->isEmpty()) {
6563 std::shared_ptr<Image<Trait>> img;
6564
6565 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6566 const auto ip = std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0));
6567
6568 for (auto it = ip->items().cbegin(), last = ip->items().cend(); it != last; ++it) {
6569 switch ((*it)->type()) {
6570 case ItemType::Link:
6571 return {};
6572
6573 case ItemType::Image: {
6574 img = std::static_pointer_cast<Image<Trait>>(*it);
6575 } break;
6576
6577 default:
6578 break;
6579 }
6580 }
6581
6582 if (img.get()) {
6583 link->setImg(img);
6584 }
6585
6586 link->setP(ip);
6587 }
6588 }
6589
6590 if (html.m_html.get()) {
6591 link->p()->appendItem(html.m_html);
6592 }
6593
6594 link->setText(toSingleLine(removeBackslashes<Trait>(text)));
6595 link->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6596 link->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6597 link->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6598 link->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6599
6600 initLastItemWithOpts<Trait>(po, link);
6601
6602 po.m_lastText = nullptr;
6603
6604 return link;
6605}
6606
6607template<class Trait>
6608inline bool
6609Parser<Trait>::createShortcutLink(const typename MdBlock<Trait>::Data &text,
6610 TextParsingOpts<Trait> &po,
6611 long long int startLine,
6612 long long int startPos,
6613 long long int lastLineForText,
6614 long long int lastPosForText,
6615 typename Delims::iterator lastIt,
6616 const typename MdBlock<Trait>::Data &linkText,
6617 bool doNotCreateTextOnFail,
6618 const WithPosition &textPos,
6619 const WithPosition &linkTextPos)
6620{
6621 const auto u = Trait::latin1ToString("#") + toSingleLine(text).toCaseFolded().toUpper();
6622 const auto url = u + Trait::latin1ToString("/") + (po.m_workingPath.isEmpty() ?
6623 typename Trait::String() : po.m_workingPath + Trait::latin1ToString("/")) + po.m_fileName;
6624
6625 po.m_wasRefLink = false;
6626 po.m_firstInParagraph = false;
6627 po.m_headingAllowed = true;
6628
6629 if (po.m_doc->labeledLinks().find(url) != po.m_doc->labeledLinks().cend()) {
6630 if (!po.m_collectRefLinks) {
6631 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6632
6633 const auto link = makeLink(u,
6634 (isLinkTextEmpty ? text : linkText),
6635 po,
6636 doNotCreateTextOnFail,
6637 startLine,
6638 startPos,
6639 lastIt->m_line,
6640 lastIt->m_pos + lastIt->m_len,
6641 (isLinkTextEmpty ? textPos : linkTextPos),
6642 textPos);
6643
6644 if (link.get()) {
6645 po.m_linksToParse.push_back(url);
6646 po.m_parent->appendItem(link);
6647
6648 po.m_line = lastIt->m_line;
6649 po.m_pos = lastIt->m_pos + lastIt->m_len;
6650 } else {
6651 if (!doNotCreateTextOnFail) {
6652 makeText(lastLineForText, lastPosForText, po);
6653 }
6654
6655 return false;
6656 }
6657 }
6658
6659 return true;
6660 } else if (!doNotCreateTextOnFail) {
6661 makeText(lastLineForText, lastPosForText, po);
6662 }
6663
6664 return false;
6665}
6666
6667template<class Trait>
6668inline std::shared_ptr<Image<Trait>>
6669Parser<Trait>::makeImage(const typename Trait::String &url,
6670 const typename MdBlock<Trait>::Data &text,
6671 TextParsingOpts<Trait> &po,
6672 bool doNotCreateTextOnFail,
6673 long long int startLine,
6674 long long int startPos,
6675 long long int lastLine,
6676 long long int lastPos,
6677 const WithPosition &textPos,
6678 const WithPosition &urlPos)
6679{
6680 MD_UNUSED(doNotCreateTextOnFail)
6681
6682 std::shared_ptr<Image<Trait>> img(new Image<Trait>);
6683
6684 typename Trait::String u = (url.startsWith(Trait::latin1ToString("#")) ? url :
6685 removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6686
6687 if (Trait::fileExists(u)) {
6688 img->setUrl(u);
6689 } else if (Trait::fileExists(u, po.m_workingPath)) {
6690 img->setUrl(po.m_workingPath + Trait::latin1ToString("/") + u);
6691 } else {
6692 img->setUrl(u);
6693 }
6694
6695 MdBlock<Trait> block = {text, 0};
6696
6697 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
6698
6699 RawHtmlBlock<Trait> html;
6700
6701 parseFormattedTextLinksImages(block,
6702 std::static_pointer_cast<Block<Trait>>(p),
6703 po.m_doc,
6704 po.m_linksToParse,
6705 po.m_workingPath,
6706 po.m_fileName,
6707 po.m_collectRefLinks,
6708 true,
6709 html,
6710 true);
6711
6712 if (!p->isEmpty()) {
6713 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6714 img->setP(std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0)));
6715 }
6716 }
6717
6718 img->setText(toSingleLine(removeBackslashes<Trait>(text)));
6719 img->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6720 img->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6721 img->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6722 img->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6723 img->setTextPos(textPos);
6724 img->setUrlPos(urlPos);
6725
6726 initLastItemWithOpts<Trait>(po, img);
6727
6728 po.m_lastText = nullptr;
6729
6730 return img;
6731}
6732
6733template<class Trait>
6734inline bool
6735Parser<Trait>::createShortcutImage(const typename MdBlock<Trait>::Data &text,
6736 TextParsingOpts<Trait> &po,
6737 long long int startLine,
6738 long long int startPos,
6739 long long int lastLineForText,
6740 long long int lastPosForText,
6741 typename Delims::iterator lastIt,
6742 const typename MdBlock<Trait>::Data &linkText,
6743 bool doNotCreateTextOnFail,
6744 const WithPosition &textPos,
6745 const WithPosition &linkTextPos)
6746{
6747 const auto url = Trait::latin1ToString("#") + toSingleLine(text).toCaseFolded().toUpper() +
6748 Trait::latin1ToString("/") + (po.m_workingPath.isEmpty() ? typename Trait::String() :
6749 po.m_workingPath + Trait::latin1ToString("/")) + po.m_fileName;
6750
6751 po.m_wasRefLink = false;
6752 po.m_firstInParagraph = false;
6753 po.m_headingAllowed = true;
6754
6755 const auto iit = po.m_doc->labeledLinks().find(url);
6756
6757 if (iit != po.m_doc->labeledLinks().cend()) {
6758 if (!po.m_collectRefLinks) {
6759 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6760
6761 const auto img = makeImage(iit->second->url(),
6762 (isLinkTextEmpty ? text : linkText),
6763 po,
6764 doNotCreateTextOnFail,
6765 startLine,
6766 startPos,
6767 lastIt->m_line,
6768 lastIt->m_pos + lastIt->m_len,
6769 (isLinkTextEmpty ? textPos : linkTextPos),
6770 textPos);
6771
6772 po.m_parent->appendItem(img);
6773
6774 po.m_line = lastIt->m_line;
6775 po.m_pos = lastIt->m_pos + lastIt->m_len;
6776 }
6777
6778 return true;
6779 } else if (!doNotCreateTextOnFail) {
6780 makeText(lastLineForText, lastPosForText, po);
6781 }
6782
6783 return false;
6784}
6785
6786//! Skip space in the block up to 1 new line.
6787template<class Trait>
6788inline void
6789skipSpacesUpTo1Line(long long int &line,
6790 long long int &pos,
6791 const typename MdBlock<Trait>::Data &fr)
6792{
6793 pos = skipSpaces<Trait>(pos, fr.at(line).first.asString());
6794
6795 if (pos == fr.at(line).first.length() && line + 1 < (long long int)fr.size()) {
6796 ++line;
6797 pos = skipSpaces<Trait>(0, fr.at(line).first.asString());
6798 }
6799}
6800
6801//! Read link's destination.
6802template<class Trait>
6803inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6804readLinkDestination(long long int line,
6805 long long int pos,
6806 const TextParsingOpts<Trait> &po,
6807 WithPosition *urlPos = nullptr)
6808{
6809 skipSpacesUpTo1Line<Trait>(line, pos, po.m_fr.m_data);
6810
6811 const auto destLine = line;
6812 const auto &s = po.m_fr.m_data.at(line).first.asString();
6813 bool backslash = false;
6814
6815 if (pos < s.length() && line <= po.m_lastTextLine) {
6816 if (s[pos] == Trait::latin1ToChar('<')) {
6817 ++pos;
6818
6819 if (urlPos) {
6820 urlPos->setStartColumn(po.m_fr.m_data[line].first.virginPos(pos));
6821 urlPos->setStartLine(po.m_fr.m_data[line].second.m_lineNumber);
6822 }
6823
6824 const auto start = pos;
6825
6826 while (pos < s.size()) {
6827 bool now = false;
6828
6829 if (s[pos] == Trait::latin1ToChar('\\') && !backslash) {
6830 backslash = true;
6831 now = true;
6832 } else if (!backslash && s[pos] == Trait::latin1ToChar('<')) {
6833 return {line, pos, false, {}, destLine};
6834 } else if (!backslash && s[pos] == Trait::latin1ToChar('>')) {
6835 break;
6836 }
6837
6838 if (!now) {
6839 backslash = false;
6840 }
6841
6842 ++pos;
6843 }
6844
6845 if (pos < s.size() && s[pos] == Trait::latin1ToChar('>')) {
6846 if (urlPos) {
6847 urlPos->setEndColumn(po.m_fr.m_data[line].first.virginPos(pos - 1));
6848 urlPos->setEndLine(po.m_fr.m_data[line].second.m_lineNumber);
6849 }
6850
6851 ++pos;
6852
6853 return {line, pos, true, s.sliced(start, pos - start - 1), destLine};
6854 } else {
6855 return {line, pos, false, {}, destLine};
6856 }
6857 } else {
6858 long long int pc = 0;
6859
6860 const auto start = pos;
6861
6862 if (urlPos) {
6863 urlPos->setStartColumn(po.m_fr.m_data[line].first.virginPos(pos));
6864 urlPos->setStartLine(po.m_fr.m_data[line].second.m_lineNumber);
6865 }
6866
6867 while (pos < s.size()) {
6868 bool now = false;
6869
6870 if (s[pos] == Trait::latin1ToChar('\\') && !backslash) {
6871 backslash = true;
6872 now = true;
6873 } else if (!backslash && s[pos] == Trait::latin1ToChar(' ')) {
6874 if (!pc) {
6875 if (urlPos) {
6876 urlPos->setEndColumn(po.m_fr.m_data[line].first.virginPos(pos - 1));
6877 urlPos->setEndLine(po.m_fr.m_data[line].second.m_lineNumber);
6878 }
6879
6880 return {line, pos, true, s.sliced(start, pos - start), destLine};
6881 } else {
6882 return {line, pos, false, {}, destLine};
6883 }
6884 } else if (!backslash && s[pos] == Trait::latin1ToChar('(')) {
6885 ++pc;
6886 } else if (!backslash && s[pos] == Trait::latin1ToChar(')')) {
6887 if (!pc) {
6888 if (urlPos) {
6889 urlPos->setEndColumn(po.m_fr.m_data[line].first.virginPos(pos - 1));
6890 urlPos->setEndLine(po.m_fr.m_data[line].second.m_lineNumber);
6891 }
6892
6893 return {line, pos, true, s.sliced(start, pos - start), destLine};
6894 } else {
6895 --pc;
6896 }
6897 }
6898
6899 if (!now) {
6900 backslash = false;
6901 }
6902
6903 ++pos;
6904 }
6905
6906 if (urlPos) {
6907 urlPos->setEndColumn(po.m_fr.m_data[line].first.virginPos(pos - 1));
6908 urlPos->setEndLine(po.m_fr.m_data[line].second.m_lineNumber);
6909 }
6910
6911 return {line, pos, true, s.sliced(start, pos - start), destLine};
6912 }
6913 } else {
6914 return {line, pos, false, {}, destLine};
6915 }
6916}
6917
6918//! Read link's title.
6919template<class Trait>
6920inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6921readLinkTitle(long long int line,
6922 long long int pos,
6923 const TextParsingOpts<Trait> &po)
6924{
6925 const auto space = (pos < po.m_fr.m_data.at(line).first.length() ?
6926 po.m_fr.m_data.at(line).first[pos].isSpace() : true);
6927
6928 const auto firstLine = line;
6929
6930 skipSpacesUpTo1Line<Trait>(line, pos, po.m_fr.m_data);
6931
6932 if (pos >= po.m_fr.m_data.at(line).first.length()) {
6933 return {line, pos, true, {}, firstLine};
6934 }
6935
6936 const auto sc = po.m_fr.m_data.at(line).first[pos];
6937
6938 if (sc != Trait::latin1ToChar('"') && sc != Trait::latin1ToChar('\'') &&
6939 sc != Trait::latin1ToChar('(') && sc != Trait::latin1ToChar(')')) {
6940 return {line, pos, (firstLine != line && line <= po.m_lastTextLine), {}, firstLine};
6941 } else if (!space && sc != Trait::latin1ToChar(')')) {
6942 return {line, pos, false, {}, firstLine};
6943 }
6944
6945 if (sc == Trait::latin1ToChar(')')) {
6946 return {line, pos, line <= po.m_lastTextLine, {}, firstLine};
6947 }
6948
6949 const auto startLine = line;
6950
6951 bool backslash = false;
6952
6953 ++pos;
6954
6955 skipSpacesUpTo1Line<Trait>(line, pos, po.m_fr.m_data);
6956
6957 typename Trait::String title;
6958
6959 while (line < (long long int)po.m_fr.m_data.size() && pos < po.m_fr.m_data.at(line).first.length()) {
6960 bool now = false;
6961
6962 if (po.m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar('\\') && !backslash) {
6963 backslash = true;
6964 now = true;
6965 } else if (sc == Trait::latin1ToChar('(') &&
6966 po.m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(')') && !backslash) {
6967 ++pos;
6968 return {line, pos, line <= po.m_lastTextLine, title, startLine};
6969 } else if (sc == Trait::latin1ToChar('(') &&
6970 po.m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar('(') && !backslash) {
6971 return {line, pos, false, {}, startLine};
6972 } else if (sc != Trait::latin1ToChar('(') && po.m_fr.m_data.at(line).first[pos] == sc && !backslash) {
6973 ++pos;
6974 return {line, pos, line <= po.m_lastTextLine, title, startLine};
6975 } else {
6976 title.push_back(po.m_fr.m_data.at(line).first[pos]);
6977 }
6978
6979 if (!now) {
6980 backslash = false;
6981 }
6982
6983 ++pos;
6984
6985 if (pos == po.m_fr.m_data.at(line).first.length()) {
6986 skipSpacesUpTo1Line<Trait>(line, pos, po.m_fr.m_data);
6987 }
6988 }
6989
6990 return {line, pos, false, {}, startLine};
6991}
6992
6993template<class Trait>
6994inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator, bool>
6995Parser<Trait>::checkForInlineLink(typename Delims::iterator it,
6996 typename Delims::iterator last,
6997 TextParsingOpts<Trait> &po,
6998 WithPosition *urlPos)
6999{
7000 long long int p = it->m_pos + it->m_len;
7001 long long int l = it->m_line;
7002 bool ok = false;
7003 typename Trait::String dest, title;
7004 long long int destStartLine = 0;
7005
7006 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
7007
7008 if (!ok) {
7009 return {{}, {}, it, false};
7010 }
7011
7012 long long int s = 0;
7013
7014 std::tie(l, p, ok, title, s) = readLinkTitle<Trait>(l, p, po);
7015
7016 skipSpacesUpTo1Line<Trait>(l, p, po.m_fr.m_data);
7017
7018 if (!ok || (l >= (long long int)po.m_fr.m_data.size() || p >= po.m_fr.m_data.at(l).first.length() ||
7019 po.m_fr.m_data.at(l).first[p] != Trait::latin1ToChar(')'))) {
7020 return {{}, {}, it, false};
7021 }
7022
7023 for (; it != last; ++it) {
7024 if (it->m_line == l && it->m_pos == p) {
7025 return {dest, title, it, true};
7026 }
7027 }
7028
7029 return {{}, {}, it, false};
7030}
7031
7032template<class Trait>
7033inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator, bool>
7034Parser<Trait>::checkForRefLink(typename Delims::iterator it,
7035 typename Delims::iterator last,
7036 TextParsingOpts<Trait> &po,
7037 WithPosition *urlPos)
7038{
7039 long long int p = it->m_pos + it->m_len + 1;
7040 long long int l = it->m_line;
7041 bool ok = false;
7042 typename Trait::String dest, title;
7043 long long int destStartLine = 0;
7044
7045 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
7046
7047 if (!ok) {
7048 return {{}, {}, it, false};
7049 }
7050
7051 long long int titleStartLine = 0;
7052
7053 std::tie(l, p, ok, title, titleStartLine) = readLinkTitle<Trait>(l, p, po);
7054
7055 if (!ok) {
7056 return {{}, {}, it, false};
7057 }
7058
7059 if (!title.isEmpty()) {
7060 p = skipSpaces<Trait>(p, po.m_fr.m_data.at(l).first.asString());
7061
7062 if (titleStartLine == destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
7063 return {{}, {}, it, false};
7064 } else if (titleStartLine != destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
7065 l = destStartLine;
7066 p = po.m_fr.m_data.at(l).first.length();
7067 title.clear();
7068 }
7069 }
7070
7071 for (; it != last; ++it) {
7072 if (it->m_line > l || (it->m_line == l && it->m_pos >= p)) {
7073 break;
7074 }
7075 }
7076
7077 po.m_line = l;
7078 po.m_pos = p;
7079
7080 return {dest, title, std::prev(it), true};
7081}
7082
7083template<class Trait>
7084inline typename Parser<Trait>::Delims::iterator
7085Parser<Trait>::checkForImage(typename Delims::iterator it,
7086 typename Delims::iterator last,
7087 TextParsingOpts<Trait> &po)
7088{
7089 const auto start = it;
7090
7091 typename MdBlock<Trait>::Data text;
7092
7093 po.m_wasRefLink = false;
7094 po.m_firstInParagraph = false;
7095 po.m_headingAllowed = true;
7096
7097 WithPosition textPos;
7098 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
7099
7100 if (it != start) {
7101 if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7102 // Inline -> (
7103 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar('(')) {
7104 typename Trait::String url, title;
7105 typename Delims::iterator iit;
7106 bool ok;
7107
7108 WithPosition urlPos;
7109 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7110
7111 if (ok) {
7112 if (!po.m_collectRefLinks) {
7113 po.m_parent->appendItem(
7114 makeImage(url, text, po, false, start->m_line, start->m_pos,
7115 iit->m_line, iit->m_pos + iit->m_len, textPos, urlPos));
7116 }
7117
7118 po.m_line = iit->m_line;
7119 po.m_pos = iit->m_pos + iit->m_len;
7120
7121 return iit;
7122 } else if (createShortcutImage(text, po, start->m_line, start->m_pos, start->m_line,
7123 start->m_pos + start->m_len, it, {}, false, textPos, {})) {
7124 return it;
7125 }
7126 }
7127 // Reference -> [
7128 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar('[')) {
7129 typename MdBlock<Trait>::Data label;
7130 typename Delims::iterator lit;
7131
7132 WithPosition labelPos;
7133 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7134
7135 if (lit != std::next(it)) {
7136 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7137
7138 if (!isLabelEmpty
7139 && createShortcutImage(label,
7140 po,
7141 start->m_line,
7142 start->m_pos,
7143 start->m_line,
7144 start->m_pos + start->m_len,
7145 lit,
7146 text,
7147 true,
7148 labelPos,
7149 textPos)) {
7150 return lit;
7151 } else if (isLabelEmpty
7152 && createShortcutImage(text,
7153 po,
7154 start->m_line,
7155 start->m_pos,
7156 start->m_line,
7157 start->m_pos + start->m_len,
7158 lit,
7159 {},
7160 false,
7161 textPos,
7162 {})) {
7163 return lit;
7164 }
7165 } else if (createShortcutImage(text, po, start->m_line, start->m_pos, start->m_line,
7166 start->m_pos + start->m_len, it, {}, false, textPos, {})) {
7167 return it;
7168 }
7169 } else {
7170 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutImage);
7171 }
7172 } else {
7173 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutImage);
7174 }
7175 }
7176
7177 return start;
7178}
7179
7180template<class Trait>
7181inline typename Parser<Trait>::Delims::iterator
7182Parser<Trait>::checkForLink(typename Delims::iterator it,
7183 typename Delims::iterator last,
7184 TextParsingOpts<Trait> &po)
7185{
7186 const auto start = it;
7187
7188 typename MdBlock<Trait>::Data text;
7189
7190 const auto wasRefLink = po.m_wasRefLink;
7191 const auto firstInParagraph = po.m_firstInParagraph;
7192 po.m_wasRefLink = false;
7193 po.m_firstInParagraph = false;
7194 po.m_headingAllowed = true;
7195
7196 const auto ns = skipSpaces<Trait>(0, po.m_fr.m_data.at(po.m_line).first.asString());
7197
7198 WithPosition textPos;
7199 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
7200
7201 if (it != start) {
7202 // Footnote reference.
7203 if (text.front().first.asString().startsWith(Trait::latin1ToString("^")) &&
7204 text.front().first.asString().length() > 1 && text.size() == 1 &&
7205 start->m_line == it->m_line) {
7206 if (!po.m_collectRefLinks) {
7207 std::shared_ptr<FootnoteRef<Trait>> fnr(new FootnoteRef<Trait>(
7208 Trait::latin1ToString("#") + toSingleLine(text).toCaseFolded().toUpper() +
7209 Trait::latin1ToString("/") + (po.m_workingPath.isEmpty() ? typename Trait::String() :
7210 po.m_workingPath + Trait::latin1ToString("/")) + po.m_fileName));
7211 fnr->setStartColumn(po.m_fr.m_data.at(start->m_line).first.virginPos(start->m_pos));
7212 fnr->setStartLine(po.m_fr.m_data.at(start->m_line).second.m_lineNumber);
7213 fnr->setEndColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
7214 fnr->setEndLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
7215 fnr->setIdPos(textPos);
7216
7217 typename Trait::String fnrText = Trait::latin1ToString("[");
7218 bool firstFnrText = true;
7219
7220 for (const auto &t : text) {
7221 if (!firstFnrText) {
7222 fnrText.push_back(Trait::latin1ToString("\n"));
7223 }
7224
7225 firstFnrText = false;
7226
7227 fnrText.push_back(t.first.asString());
7228 }
7229
7230 fnrText.push_back(Trait::latin1ToString("]"));
7231 fnr->setText(fnrText);
7232 po.m_parent->appendItem(fnr);
7233
7234 initLastItemWithOpts<Trait>(po, fnr);
7235 }
7236
7237 po.m_line = it->m_line;
7238 po.m_pos = it->m_pos + it->m_len;
7239
7240 return it;
7241 } else if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7242 // Reference definition -> :
7243 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(':')) {
7244 // Reference definitions allowed only at start of paragraph.
7245 if ((po.m_line == 0 || wasRefLink || firstInParagraph) && ns < 4 && start->m_pos == ns) {
7246 typename Trait::String url, title;
7247 typename Delims::iterator iit;
7248 bool ok;
7249
7250 WithPosition labelPos;
7251
7252 std::tie(text, it) = checkForLinkLabel(start, last, po, &labelPos);
7253
7254 if (it != start && !toSingleLine(text).simplified().isEmpty()) {
7255 WithPosition urlPos;
7256 std::tie(url, title, iit, ok) = checkForRefLink(it, last, po, &urlPos);
7257
7258 if (ok) {
7259 const auto label = Trait::latin1ToString("#") +
7260 toSingleLine(text).toCaseFolded().toUpper() +
7261 Trait::latin1ToString("/") +
7262 (po.m_workingPath.isEmpty() ? typename Trait::String() :
7263 po.m_workingPath + Trait::latin1ToString("/")) + po.m_fileName;
7264
7265 std::shared_ptr<Link<Trait>> link(new Link<Trait>);
7266 link->setStartColumn(po.m_fr.m_data.at(start->m_line).first.virginPos(
7267 start->m_pos));
7268 link->setStartLine(po.m_fr.m_data.at(start->m_line).second.m_lineNumber);
7269
7270 const auto endPos = prevPosition(po.m_fr,
7271 po.m_fr.m_data.at(po.m_line).first.virginPos(po.m_pos),
7272 po.m_fr.m_data.at(po.m_line).second.m_lineNumber);
7273
7274 link->setEndColumn(endPos.first);
7275 link->setEndLine(endPos.second);
7276
7277 link->setTextPos(labelPos);
7278 link->setUrlPos(urlPos);
7279
7280 url = removeBackslashes<typename Trait::String, Trait>(
7281 replaceEntity<Trait>(url));
7282
7283 if (!url.isEmpty()) {
7284 if (Trait::fileExists(url)) {
7285 url = Trait::absoluteFilePath(url);
7286 } else if (Trait::fileExists(url, po.m_workingPath)) {
7287 url = Trait::absoluteFilePath(
7288 (po.m_workingPath.isEmpty() ? typename Trait::String() :
7289 po.m_workingPath + Trait::latin1ToString("/")) + url);
7290 }
7291 }
7292
7293 link->setUrl(url);
7294
7295 po.m_wasRefLink = true;
7296 po.m_headingAllowed = false;
7297
7298 if (po.m_doc->labeledLinks().find(label) == po.m_doc->labeledLinks().cend()) {
7299 po.m_doc->insertLabeledLink(label, link);
7300 }
7301
7302 return iit;
7303 } else {
7304 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutLink);
7305 }
7306 } else {
7307 return start;
7308 }
7309 } else {
7310 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutLink);
7311 }
7312 }
7313 // Inline -> (
7314 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar('(')) {
7315 typename Trait::String url, title;
7316 typename Delims::iterator iit;
7317 bool ok;
7318
7319 WithPosition urlPos;
7320 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7321
7322 if (ok) {
7323 const auto link = makeLink(url,
7324 text,
7325 po,
7326 false,
7327 start->m_line,
7328 start->m_pos,
7329 iit->m_line,
7330 iit->m_pos + iit->m_len,
7331 textPos,
7332 urlPos);
7333
7334 if (link.get()) {
7335 if (!po.m_collectRefLinks) {
7336 po.m_parent->appendItem(link);
7337 }
7338
7339 po.m_line = iit->m_line;
7340 po.m_pos = iit->m_pos + iit->m_len;
7341
7342 return iit;
7343 } else {
7344 return start;
7345 }
7346 } else if (createShortcutLink(text, po, start->m_line, start->m_pos, start->m_line,
7347 start->m_pos + start->m_len, it, {}, false, textPos, {})) {
7348 return it;
7349 }
7350 }
7351 // Reference -> [
7352 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar('[')) {
7353 typename MdBlock<Trait>::Data label;
7354 typename Delims::iterator lit;
7355
7356 WithPosition labelPos;
7357 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7358
7359 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7360
7361 if (lit != std::next(it)) {
7362 if (!isLabelEmpty
7363 && createShortcutLink(label,
7364 po,
7365 start->m_line,
7366 start->m_pos,
7367 start->m_line,
7368 start->m_pos + start->m_len,
7369 lit,
7370 text,
7371 true,
7372 labelPos,
7373 textPos)) {
7374 return lit;
7375 } else if (isLabelEmpty
7376 && createShortcutLink(text,
7377 po,
7378 start->m_line,
7379 start->m_pos,
7380 start->m_line,
7381 start->m_pos + start->m_len,
7382 lit,
7383 {},
7384 false,
7385 textPos,
7386 {})) {
7387 return lit;
7388 }
7389 } else if (createShortcutLink(text, po, start->m_line, start->m_pos, start->m_line,
7390 start->m_pos + start->m_len, it, {}, false, textPos, {})) {
7391 return it;
7392 }
7393 } else {
7394 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutLink);
7395 }
7396 } else {
7397 return checkShortcut(start, last, po, &Parser<Trait>::createShortcutLink);
7398 }
7399 }
7400
7401 return start;
7402}
7403
7404//! Close style.
7405template<class Trait>
7406inline void
7407closeStyle(std::vector<typename TextParsingOpts<Trait>::StyleInfo> &styles,
7408 Style s)
7409{
7410 const auto it = std::find_if(styles.crbegin(), styles.crend(), [&](const auto &p) {
7411 return (p.m_style == s);
7412 });
7413
7414 if (it != styles.crend()) {
7415 styles.erase(it.base() - 1);
7416 }
7417}
7418
7419//! Apply styles.
7420template<class Trait>
7421inline void
7422applyStyles(int &opts,
7423 std::vector<typename TextParsingOpts<Trait>::StyleInfo> &styles)
7424{
7425 opts = 0;
7426
7427 for (const auto &s : styles) {
7428 switch (s.m_style) {
7430 opts |= StrikethroughText;
7431 break;
7432
7433 case Style::Italic1:
7434 case Style::Italic2:
7435 opts |= ItalicText;
7436 break;
7437
7438 case Style::Bold1:
7439 case Style::Bold2:
7440 opts |= BoldText;
7441 break;
7442
7443 default:
7444 break;
7445 }
7446 }
7447}
7448
7449template<class Trait>
7450inline int
7451Parser<Trait>::emphasisToInt(typename Delimiter::DelimiterType t)
7452{
7453 switch (t) {
7454 case Delimiter::Strikethrough:
7455 return 0;
7456
7457 case Delimiter::Emphasis1:
7458 return 1;
7459
7460 case Delimiter::Emphasis2:
7461 return 2;
7462
7463 default:
7464 return -1;
7465 }
7466}
7467
7468template<class Trait>
7469inline void
7470Parser<Trait>::createStyles(std::vector<std::pair<Style, long long int>> & styles,
7471 typename Delimiter::DelimiterType t,
7472 long long int style)
7473{
7474 if (t != Delimiter::Strikethrough) {
7475 if (style % 2 == 1) {
7476 styles.push_back({t == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2, 1});
7477 }
7478
7479 if (style >= 2) {
7480 for (long long int i = 0; i < style / 2; ++i) {
7481 styles.push_back({t == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2, 2});
7482 }
7483 }
7484 } else {
7485 styles.push_back({Style::Strikethrough, style});
7486 }
7487}
7488
7489template<class Trait>
7490inline std::vector<std::pair<Style, long long int>>
7491Parser<Trait>::createStyles(typename Delimiter::DelimiterType t,
7492 const std::vector<long long int> &styles,
7493 long long int lastStyle)
7494{
7495 std::vector<std::pair<Style, long long int>> ret;
7496
7497 createStyles(ret, t, lastStyle);
7498
7499 for (auto it = styles.crbegin(), last = styles.crend(); it != last; ++it) {
7500 createStyles(ret, t, *it);
7501 }
7502
7503 return ret;
7504}
7505
7506template<class Trait>
7507inline bool
7508Parser<Trait>::isSequence(typename Delims::iterator it,
7509 long long int itLine,
7510 long long int itPos,
7511 typename Delimiter::DelimiterType t)
7512{
7513 return (itLine == it->m_line && itPos + it->m_len == it->m_pos && it->m_type == t);
7514}
7515
7516template<class Trait>
7517inline typename Parser<Trait>::Delims::iterator
7518Parser<Trait>::readSequence(typename Delims::iterator it,
7519 typename Delims::iterator last,
7520 long long int &line,
7521 long long int &pos,
7522 long long int &len,
7523 long long int &itCount)
7524{
7525 line = it->m_line;
7526 pos = it->m_pos;
7527 len = it->m_len;
7528 const auto t = it->m_type;
7529 itCount = 1;
7530
7531 it = std::next(it);
7532
7533 while (it != last && isSequence(it, line, pos, t)) {
7534 pos += it->m_len;
7535 len += it->m_len;
7536
7537 ++it;
7538 ++itCount;
7539 }
7540
7541 return std::prev(it);
7542}
7543
7544inline bool
7545isMult3(long long int i1, long long int i2)
7546{
7547 return ((((i1 + i2) % 3) == 0) && !((i1 % 3 == 0) && (i2 % 3 == 0)));
7548}
7549
7550template<class Trait>
7551inline std::tuple<bool, std::vector<std::pair<Style, long long int>>, long long int, long long int>
7552Parser<Trait>::isStyleClosed(typename Delims::iterator first,
7553 typename Delims::iterator it,
7554 typename Delims::iterator last,
7555 typename Delims::iterator &stackBottom,
7556 TextParsingOpts<Trait> &po)
7557{
7558 const auto open = it;
7559 long long int openPos, openLength, itCount, lengthFromIt, tmp;
7560
7561 it = std::next(readSequence(first, open, last, openPos, openLength, tmp, lengthFromIt, itCount).second);
7562
7563 const auto length = lengthFromIt;
7564 long long int itLine, itPos, itLength;
7565
7566 struct RollbackValues {
7567 RollbackValues(TextParsingOpts<Trait> &po,
7568 long long int line,
7569 long long int pos,
7570 bool collectRefLinks,
7571 typename Delims::iterator &stackBottom,
7572 typename Delims::iterator last)
7573 : m_po(po)
7574 , m_line(line)
7575 , m_pos(pos)
7576 , m_collectRefLinks(collectRefLinks)
7577 , m_stackBottom(stackBottom)
7578 , m_last(last)
7579 , m_it(m_last)
7580 {
7581 }
7582
7583 void setIterator(typename Delims::iterator it)
7584 {
7585 m_it = it;
7586 }
7587
7588 ~RollbackValues()
7589 {
7590 m_po.m_line = m_line;
7591 m_po.m_pos = m_pos;
7592 m_po.m_collectRefLinks = m_collectRefLinks;
7593
7594 if (m_it != m_last && (m_it > m_stackBottom || m_stackBottom == m_last)) {
7595 m_stackBottom = m_it;
7596 }
7597 }
7598
7599 TextParsingOpts<Trait> &m_po;
7600 long long int m_line;
7601 long long int m_pos;
7602 bool m_collectRefLinks;
7603 typename Delims::iterator &m_stackBottom;
7604 typename Delims::iterator m_last;
7605 typename Delims::iterator m_it;
7606 };
7607
7608 RollbackValues rollback(po, po.m_line, po.m_pos, po.m_collectRefLinks, stackBottom, last);
7609
7610 po.m_collectRefLinks = true;
7611
7612 std::vector<long long int> styles;
7613
7614 struct Opener {
7615 std::vector<typename Delims::iterator> m_its;
7616 long long int m_length;
7617 };
7618
7619 std::vector<Opener> openers;
7620
7621 std::function<void(long long int, long long int)> dropOpeners;
7622
7623 dropOpeners = [&openers](long long int pos, long long int line) {
7624 while (!openers.empty()) {
7625 if (openers.back().m_its.front()->m_line > line || (openers.back().m_its.front()->m_line == line &&
7626 openers.back().m_its.front()->m_pos > pos)) {
7627 std::for_each( openers.back().m_its.begin(), openers.back().m_its.end(),
7628 [](auto &i) { i->m_skip = true; });
7629 openers.pop_back();
7630 } else {
7631 break;
7632 }
7633 }
7634 };
7635
7636 auto tryCloseEmphasis = [&dropOpeners, this, &openers, &open](typename Delims::iterator first,
7637 typename Delims::iterator it,
7638 typename Delims::iterator last) -> bool
7639 {
7640 const auto type = it->m_type;
7641 const auto both = it->m_leftFlanking && it->m_rightFlanking;
7642 long long int tmp1, tmp2, tmp3, tmp4;
7643 long long int closeLength;
7644
7645 it = this->readSequence(first, it, last, tmp1, closeLength, tmp2, tmp3, tmp4).first;
7646 it = std::prev(it);
7647
7648 long long int tmpLength = closeLength;
7649
7650 for (;; --it) {
7651 switch (it->m_type) {
7652 case Delimiter::Strikethrough: {
7653 if (it->m_leftFlanking && it->m_len == closeLength && type == it->m_type) {
7654 dropOpeners(it->m_pos, it->m_line);
7655 return true;
7656 }
7657 } break;
7658
7659 case Delimiter::Emphasis1:
7660 case Delimiter::Emphasis2:
7661 {
7662 if (it->m_leftFlanking && type == it->m_type) {
7663 long long int pos, len;
7664 this->readSequence(first, it, last, pos, len, tmp1, tmp2, tmp3);
7665
7666 if ((both || (it->m_leftFlanking && it->m_rightFlanking)) && isMult3(len, closeLength)) {
7667 continue;
7668 }
7669
7670 dropOpeners(pos - len, it->m_line);
7671
7672 if (tmpLength >= len) {
7673 tmpLength -= len;
7674
7675 if (open->m_type == it->m_type) {
7676 openers.pop_back();
7677 }
7678
7679 if (!tmpLength) {
7680 return true;
7681 }
7682 } else {
7683 if (open->m_type == it->m_type) {
7684 openers.back().m_length -= tmpLength;
7685 }
7686
7687 return true;
7688 }
7689 }
7690 } break;
7691
7692 default:
7693 break;
7694 }
7695
7696 if (it == first) {
7697 break;
7698 }
7699 }
7700
7701 return false;
7702 };
7703
7704 auto fillIterators = [](typename Delims::iterator first,
7705 typename Delims::iterator last) -> std::vector<typename Delims::iterator>
7706 {
7707 std::vector<typename Delims::iterator> res;
7708
7709 for (; first != last; ++first) {
7710 res.push_back(first);
7711 }
7712
7713 res.push_back(last);
7714
7715 return res;
7716 };
7717
7718 for (; it != last; ++it) {
7719 if (it > stackBottom) {
7720 return {false, {{Style::Unknown, 0}}, open->m_len, 1};
7721 }
7722
7723 if (it->m_line <= po.m_lastTextLine) {
7724 po.m_line = it->m_line;
7725 po.m_pos = it->m_pos;
7726
7727 switch (it->m_type) {
7728 case Delimiter::SquareBracketsOpen:
7729 it = checkForLink(it, last, po);
7730 break;
7731
7732 case Delimiter::ImageOpen:
7733 it = checkForImage(it, last, po);
7734 break;
7735
7736 case Delimiter::Less:
7737 it = checkForAutolinkHtml(it, last, po, false);
7738 break;
7739
7740 case Delimiter::Strikethrough: {
7741 if (open->m_type == it->m_type && open->m_len == it->m_len && it->m_rightFlanking) {
7742 rollback.setIterator(it);
7743 return {true, createStyles(open->m_type, styles, open->m_len), open->m_len, 1};
7744 } else if (it->m_rightFlanking && tryCloseEmphasis(open, it, last)) {
7745 } else if (it->m_leftFlanking && open->m_type == it->m_type) {
7746 openers.push_back({{it}, it->m_len});
7747 }
7748 } break;
7749
7750 case Delimiter::Emphasis1:
7751 case Delimiter::Emphasis2: {
7752 if (open->m_type == it->m_type) {
7753 const auto itBoth = (it->m_leftFlanking && it->m_rightFlanking);
7754
7755 if (it->m_rightFlanking) {
7756 bool notCheck = (open->m_leftFlanking && open->m_rightFlanking) || itBoth;
7757
7758 long long int count;
7759 auto firstIt = it;
7760 it = readSequence(it, last, itLine, itPos, itLength, count);
7761
7762 if (notCheck) {
7763 notCheck = isMult3(openLength, itLength);
7764 }
7765
7766 if (!openers.empty()) {
7767 long long int i = openers.size() - 1;
7768 auto &top = openers[i];
7769
7770 while (!openers.empty()) {
7771 if (i >= 0) {
7772 top = openers[i];
7773 } else {
7774 break;
7775 }
7776
7777 if ((itBoth || (top.m_its.front()->m_rightFlanking && top.m_its.front()->m_leftFlanking))
7778 && isMult3(itLength, top.m_length)) {
7779 --i;
7780 continue;
7781 }
7782
7783 if (top.m_length <= itLength) {
7784 itLength -= top.m_length;
7785 openers.erase(openers.begin() + i);
7786 } else {
7787 top.m_length -= itLength;
7788 itLength = 0;
7789 }
7790
7791 --i;
7792
7793 if (!itLength) {
7794 break;
7795 }
7796 }
7797 }
7798
7799 if (itLength) {
7800 if (!notCheck) {
7801 if (itLength >= lengthFromIt) {
7802 rollback.setIterator(it);
7803 return {true, createStyles(open->m_type, styles, lengthFromIt), length, itCount};
7804 } else {
7805 styles.push_back(itLength);
7806 lengthFromIt -= itLength;
7807 }
7808 } else if (firstIt->m_leftFlanking) {
7809 openers.push_back({fillIterators(firstIt, it), itLength});
7810 }
7811 }
7812 } else {
7813 long long int count;
7814 auto firstIt = it;
7815 it = readSequence(it, last, itLine, itPos, itLength, count);
7816 openers.push_back({fillIterators(firstIt, it), itLength});
7817 }
7818 } else if (it->m_rightFlanking) {
7819 tryCloseEmphasis(open, it, last);
7820 }
7821 } break;
7822
7823 case Delimiter::InlineCode:
7824 it = checkForInlineCode(it, last, po);
7825 break;
7826
7827 default:
7828 break;
7829 }
7830 } else {
7831 break;
7832 }
7833 }
7834
7835 return {false, {{Style::Unknown, 0}}, open->m_len, 1};
7836}
7837
7838template<class Trait>
7839inline typename Parser<Trait>::Delims::iterator
7840Parser<Trait>::incrementIterator(typename Delims::iterator it,
7841 typename Delims::iterator last,
7842 long long int count)
7843{
7844 const auto len = std::distance(it, last);
7845
7846 if (count < len) {
7847 return it + count;
7848 } else {
7849 return it + (len - 1);
7850 }
7851}
7852
7853//! Append close style.
7854template<class Trait>
7855inline void
7857 const StyleDelim &s)
7858{
7859 if (po.m_lastItemWithStyle) {
7860 po.m_lastItemWithStyle->closeStyles().push_back(s);
7861 }
7862}
7863
7864template<class Trait>
7865inline std::pair<typename Parser<Trait>::Delims::iterator, typename Parser<Trait>::Delims::iterator>
7866Parser<Trait>::readSequence(typename Delims::iterator first,
7867 typename Delims::iterator it,
7868 typename Delims::iterator last,
7869 long long int &pos,
7870 long long int &length,
7871 long long int &itCount,
7872 long long int &lengthFromIt,
7873 long long int &itCountFromIt)
7874{
7875 long long int line = it->m_line;
7876 pos = it->m_pos + it->m_len;
7877 long long int ppos = it->m_pos;
7878 const auto t = it->m_type;
7879 lengthFromIt = it->m_len;
7880 itCountFromIt = 1;
7881
7882 auto retItLast = std::next(it);
7883
7884 for (; retItLast != last; ++retItLast) {
7885 if (retItLast->m_line == line && pos == retItLast->m_pos && retItLast->m_type == t) {
7886 lengthFromIt += retItLast->m_len;
7887 pos = retItLast->m_pos + retItLast->m_len;
7888 ++itCountFromIt;
7889 } else {
7890 break;
7891 }
7892 }
7893
7894 length = lengthFromIt;
7895 itCount = itCountFromIt;
7896
7897 auto retItFirst = it;
7898 bool useNext = false;
7899
7900 if (retItFirst != first) {
7901 retItFirst = std::prev(retItFirst);
7902 useNext = true;
7903
7904 for (;; --retItFirst) {
7905 if (retItFirst->m_line == line && ppos - retItFirst->m_len == retItFirst->m_pos && retItFirst->m_type == t) {
7906 length += retItFirst->m_len;
7907 ppos = retItFirst->m_pos;
7908 ++itCount;
7909 useNext = false;
7910 } else {
7911 useNext = true;
7912 break;
7913 }
7914
7915 if (retItFirst == first) {
7916 break;
7917 }
7918 }
7919 }
7920
7921 return {useNext ? std::next(retItFirst) : retItFirst, std::prev(retItLast)};
7922}
7923
7924template<class Trait>
7925inline typename Parser<Trait>::Delims::iterator
7926Parser<Trait>::checkForStyle(typename Delims::iterator first,
7927 typename Delims::iterator it,
7928 typename Delims::iterator last,
7929 typename Delims::iterator &stackBottom,
7930 TextParsingOpts<Trait> &po)
7931{
7932 long long int count = 1;
7933
7934 po.m_wasRefLink = false;
7935 po.m_firstInParagraph = false;
7936
7937 if (it->m_rightFlanking) {
7938 long long int pos, len, tmp1, tmp2;
7939 readSequence(first, it, last, pos, len, count, tmp1, tmp2);
7940 const auto t = it->m_type;
7941
7942 long long int opened = 0;
7943 bool bothFlanking = false;
7944
7945 for (auto it = po.m_styles.crbegin(), last = po.m_styles.crend(); it != last; ++it) {
7946 bool doBreak = false;
7947
7948 switch (t) {
7949 case Delimiter::Emphasis1: {
7950 if (it->m_style == Style::Italic1 || it->m_style == Style::Bold1) {
7951 opened = it->m_length;
7952 bothFlanking = it->m_bothFlanking;
7953 doBreak = true;
7954 }
7955 } break;
7956
7957 case Delimiter::Emphasis2: {
7958 if (it->m_style == Style::Italic2 || it->m_style == Style::Bold2) {
7959 opened = it->m_length;
7960 bothFlanking = it->m_bothFlanking;
7961 doBreak = true;
7962 }
7963 } break;
7964
7965 case Delimiter::Strikethrough: {
7966 if (it->m_style == Style::Strikethrough) {
7967 opened = it->m_length;
7968 bothFlanking = it->m_bothFlanking;
7969 doBreak = true;
7970 }
7971 } break;
7972
7973 default:
7974 break;
7975 }
7976
7977 if (doBreak)
7978 break;
7979 }
7980
7981 const bool sumMult3 = (it->m_leftFlanking || bothFlanking ? isMult3(opened, len) : false);
7982
7983 if (count && opened && !sumMult3) {
7984 if (count > opened) {
7985 count = opened;
7986 }
7987
7988 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
7989 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
7990
7991 if (it->m_type == Delimiter::Strikethrough) {
7992 const auto len = it->m_len;
7993
7994 for (auto i = 0; i < count; ++i) {
7995 closeStyle<Trait>(po.m_styles, Style::Strikethrough);
7996 appendCloseStyle(po, {StrikethroughText, pos, line, pos + len - 1, line});
7997 pos += len;
7998 }
7999 } else {
8000 if (count % 2 == 1) {
8001 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2);
8002
8003 closeStyle<Trait>(po.m_styles, st);
8004 appendCloseStyle(po, {ItalicText, pos, line, pos, line});
8005 ++pos;
8006 }
8007
8008 if (count >= 2) {
8009 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2);
8010
8011 for (auto i = 0; i < count / 2; ++i) {
8012 closeStyle<Trait>(po.m_styles, st);
8013 appendCloseStyle(po, {BoldText, pos, line, pos + 1, line});
8014 pos += 2;
8015 }
8016 }
8017 }
8018
8019 applyStyles<Trait>(po.m_opts, po.m_styles);
8020
8021 const auto j = incrementIterator(it, last, count - 1);
8022
8023 po.m_pos = j->m_pos + j->m_len;
8024 po.m_line = j->m_line;
8025
8026 return j;
8027 }
8028 }
8029
8030 count = 1;
8031
8032 if (it->m_leftFlanking) {
8033 switch (it->m_type) {
8034 case Delimiter::Strikethrough:
8035 case Delimiter::Emphasis1:
8036 case Delimiter::Emphasis2: {
8037 bool closed = false;
8038 std::vector<std::pair<Style, long long int>> styles;
8039 long long int len = 0;
8040
8041 if (it > stackBottom) {
8042 stackBottom = last;
8043 }
8044
8045 if (it->m_skip) {
8046 closed = false;
8047 long long int tmp1, tmp2, tmp3;
8048 readSequence(it, last, tmp1, tmp2, len, tmp3);
8049 } else {
8050 std::tie(closed, styles, len, count) = isStyleClosed(first, it, last, stackBottom, po);
8051 }
8052
8053 if (closed) {
8054 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
8055 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
8056
8057 for (const auto &p : styles) {
8058 po.m_styles.push_back({p.first, p.second, it->m_leftFlanking && it->m_rightFlanking});
8059
8060 if (!po.m_collectRefLinks) {
8061 po.m_openStyles.push_back({styleToTextOption(p.first), pos, line,
8062 pos + p.second - 1, line});
8063 }
8064
8065 pos += p.second;
8066 }
8067
8068 po.m_pos = it->m_pos + len;
8069 po.m_line = it->m_line;
8070
8071 applyStyles<Trait>(po.m_opts, po.m_styles);
8072 } else {
8073 makeText(it->m_line, it->m_pos + len, po);
8074 }
8075 } break;
8076
8077 default: {
8078 makeText(it->m_line, it->m_pos + it->m_len, po);
8079 } break;
8080 }
8081 }
8082
8083 if (!count) {
8084 count = 1;
8085 }
8086
8087 resetHtmlTag(po.m_html, &po);
8088
8089 return incrementIterator(it, last, count - 1);
8090}
8091
8092//! Concatenate texts in block.
8093template<class Trait>
8094inline std::shared_ptr<Text<Trait>>
8097{
8098 std::shared_ptr<Text<Trait>> t(new Text<Trait>);
8099 t->setOpts(std::static_pointer_cast<Text<Trait>>(*it)->opts());
8100 t->setStartColumn((*it)->startColumn());
8101 t->setStartLine((*it)->startLine());
8102
8103 typename ItemWithOpts<Trait>::Styles close;
8104
8105 typename Trait::String data;
8106
8107 for (; it != last; ++it) {
8108 const auto tt = std::static_pointer_cast<Text<Trait>>(*it);
8109
8110 data.push_back(tt->text());
8111
8112 if (!tt->openStyles().empty()) {
8113 std::copy(tt->openStyles().cbegin(), tt->openStyles().cend(),
8114 std::back_inserter(t->openStyles()));
8115 }
8116
8117 if (!tt->closeStyles().empty()) {
8118 std::copy(tt->closeStyles().cbegin(), tt->closeStyles().cend(),
8119 std::back_inserter(close));
8120 }
8121 }
8122
8123 it = std::prev(it);
8124
8125 t->setText(data);
8126 t->setEndColumn((*it)->endColumn());
8127 t->setEndLine((*it)->endLine());
8128 t->closeStyles() = close;
8129
8130 return t;
8131}
8132
8133//! \return Is optimization type a semi one.
8134inline bool
8136{
8137 switch (t) {
8140 return true;
8141
8142 default:
8143 return false;
8144 }
8145}
8146
8147//! \return Is optimization type without raw data optimization?
8148inline bool
8150{
8151 switch (t) {
8154 return true;
8155
8156 default:
8157 return false;
8158 }
8159}
8160
8161//! Optimize Paragraph.
8162template<class Trait>
8163inline std::shared_ptr<Paragraph<Trait>>
8167{
8168 std::shared_ptr<Paragraph<Trait>> np(new Paragraph<Trait>);
8169 np->setStartColumn(p->startColumn());
8170 np->setStartLine(p->startLine());
8171 np->setEndColumn(p->endColumn());
8172 np->setEndLine(p->endLine());
8173
8174 int opts = TextWithoutFormat;
8175 auto start = p->items().cend();
8176 long long int line = -1;
8177 long long int auxStart = 0, auxIt = 0;
8178 bool finished = false;
8179
8180 for (auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8181 if ((*it)->type() == ItemType::Text) {
8182 const auto t = std::static_pointer_cast<Text<Trait>>(*it);
8183
8184 if (start == last) {
8185 start = it;
8186 opts = t->opts();
8187 line = t->endLine();
8188 finished = (isSemiOptimization(type) && !t->closeStyles().empty());
8189 } else {
8190 if (opts != t->opts() || t->startLine() != line || finished ||
8191 (!t->openStyles().empty() && isSemiOptimization(type))) {
8192 if (!isWithoutRawDataOptimization(type)) {
8193 po.concatenateAuxText(auxStart, auxIt);
8194 auxIt = auxIt - (auxIt - auxStart) + 1;
8195 auxStart = auxIt;
8196 }
8197
8198 np->appendItem(concatenateText<Trait>(start, it));
8199 start = it;
8200 opts = t->opts();
8201 line = t->endLine();
8202 }
8203
8204 finished = (isSemiOptimization(type) && !t->closeStyles().empty());
8205 }
8206
8208 ++auxIt;
8209 } else {
8210 finished = false;
8211
8212 if (start != last) {
8213 if (!isWithoutRawDataOptimization(type)) {
8214 po.concatenateAuxText(auxStart, auxIt);
8215 auxIt = auxIt - (auxIt - auxStart) + 1;
8216 auxStart = auxIt;
8217 }
8218
8219 np->appendItem(concatenateText<Trait>(start, it));
8220 start = last;
8221 opts = TextWithoutFormat;
8222 line = (*it)->endLine();
8223 }
8224
8225 np->appendItem((*it));
8226 }
8227 }
8228
8229 if (start != p->items().cend()) {
8230 np->appendItem(concatenateText<Trait>(start, p->items().cend()));
8231
8232 if (!isWithoutRawDataOptimization(type)) {
8233 po.concatenateAuxText(auxStart, po.m_rawTextData.size());
8234 }
8235 }
8236
8237 p = np;
8238
8239 return p;
8240}
8241
8242//! Normalize position.
8243inline void
8244normalizePos(long long int &pos,
8245 long long int &line,
8246 long long int length,
8247 long long int linesCount)
8248{
8249 if (pos != 0 && line < linesCount && pos == length) {
8250 pos = 0;
8251 ++line;
8252 }
8253}
8254
8255//! Make Paragraph.
8256template<class Trait>
8257inline std::shared_ptr<Paragraph<Trait>>
8260{
8261 auto p = std::make_shared<Paragraph<Trait>>();
8262
8263 p->setStartColumn((*first)->startColumn());
8264 p->setStartLine((*first)->startLine());
8265
8266 for (; first != last; ++first) {
8267 p->appendItem(*first);
8268 p->setEndColumn((*first)->endColumn());
8269 p->setEndLine((*first)->endLine());
8270 }
8271
8272 return p;
8273}
8274
8275//! Split Paragraph and free HTML.
8276template<class Trait>
8277inline std::shared_ptr<Paragraph<Trait>>
8279 std::shared_ptr<Paragraph<Trait>> p,
8281 bool collectRefLinks,
8282 bool fullyOptimizeParagraphs = true)
8283{
8284 auto first = p->items().cbegin();
8285 auto it = first;
8286 auto last = p->items().cend();
8287
8288 for (; it != last; ++it) {
8289 if (first == last) {
8290 first = it;
8291 }
8292
8293 if ((*it)->type() == ItemType::RawHtml &&
8294 UnprotectedDocsMethods<Trait>::isFreeTag(std::static_pointer_cast<RawHtml<Trait>>(*it))) {
8295 auto p = makeParagraph<Trait>(first, it);
8296
8297 if (!collectRefLinks) {
8298 if (!p->isEmpty()) {
8299 parent->appendItem(optimizeParagraph<Trait>(p, po,
8300 fullyOptimizeParagraphs ?
8303 }
8304
8305 parent->appendItem(*it);
8306 }
8307
8308 first = last;
8309 }
8310 }
8311
8312 if (first != last) {
8313 if (first != p->items().cbegin()) {
8314 const auto c = std::count_if(first, last, [](const auto &i) {
8315 return (i->type() == MD::ItemType::Text);
8316 });
8317 po.m_rawTextData.erase(po.m_rawTextData.cbegin(), po.m_rawTextData.cbegin() +
8318 (po.m_rawTextData.size() - c));
8319
8320 return makeParagraph<Trait>(first, last);
8321 } else {
8322 return p;
8323 }
8324 } else {
8325 po.m_rawTextData.clear();
8326
8327 return std::make_shared<Paragraph<Trait>>();
8328 }
8329}
8330
8331//! \return Last virgin position of the item.
8332template<class Trait>
8333inline long long int
8335{
8336 switch (item->type()) {
8337 case ItemType::Text:
8338 case ItemType::Link:
8339 case ItemType::Image:
8341 case ItemType::RawHtml:
8342 {
8343 auto i = static_cast<ItemWithOpts<Trait> *>(item);
8344
8345 if (!i->closeStyles().empty()) {
8346 return i->closeStyles().back().endColumn();
8347 } else {
8348 return i->endColumn();
8349 }
8350 }
8351 break;
8352
8353 case ItemType::Code:
8354 case ItemType::Math:
8355 {
8356 auto c = static_cast<Code<Trait> *>(item);
8357
8358 if (!c->closeStyles().empty()) {
8359 return c->closeStyles().back().endColumn();
8360 } else {
8361 return c->endDelim().endColumn();
8362 }
8363 }
8364 break;
8365
8366 default:
8367 return -1;
8368 }
8369}
8370
8371//! Make heading.
8372template<class Trait>
8373inline void
8374makeHeading(std::shared_ptr<Block<Trait>> parent,
8375 std::shared_ptr<Document<Trait>> doc,
8376 std::shared_ptr<Paragraph<Trait>> p,
8377 long long int lastColumn,
8378 long long int lastLine,
8379 int level,
8380 const typename Trait::String &workingPath,
8381 const typename Trait::String &fileName,
8382 bool collectRefLinks,
8383 const WithPosition &delim,
8385{
8386 if (!collectRefLinks) {
8387 if (p->items().back()->type() == ItemType::LineBreak) {
8388 auto lb = std::static_pointer_cast<LineBreak<Trait>>(p->items().back());
8389 const auto lineBreakBySpaces = lb->text().simplified().isEmpty();
8390
8391 p = makeParagraph<Trait>(p->items().cbegin(), std::prev(p->items().cend()));
8392 const auto lineBreakPos = localPosFromVirgin(po.m_fr, lb->startColumn(), lb->startLine());
8393
8394 if (!p->isEmpty()) {
8395 if (p->items().back()->type() == ItemType::Text) {
8396 auto lt = std::static_pointer_cast<Text<Trait>>(p->items().back());
8397
8398 if (!lineBreakBySpaces) {
8399 auto text = po.m_fr.m_data.at(lineBreakPos.second).first.fullVirginString().sliced(
8400 lt->startColumn());
8401 po.m_rawTextData.back().m_str = text;
8402
8403 if (!lt->text()[0].isSpace()) {
8404 const auto notSpacePos = skipSpaces<Trait>(0, text);
8405
8406 text.remove(0, notSpacePos);
8407 }
8408
8410 }
8411
8412 lt->setEndColumn(lt->endColumn() + lb->text().length());
8413 } else {
8414 if (!lineBreakBySpaces) {
8415 const auto lastItemVirginPos = lastVirginPositionInParagraph<Trait>(p->items().back().get());
8416 const auto lastItemPos = localPosFromVirgin(po.m_fr, lastItemVirginPos, lineBreakPos.second);
8417 const auto endOfLine = po.m_fr.m_data.at(lineBreakPos.second).first.virginSubString(
8418 lastItemPos.first + 1);
8419 auto t = std::make_shared<Text<Trait>>();
8420 t->setText(endOfLine);
8421 t->setStartColumn(lastItemVirginPos + 1);
8422 t->setStartLine(lb->startLine());
8423 t->setEndColumn(lb->endColumn());
8424 t->setEndLine(lb->endLine());
8425
8426 p->appendItem(t);
8427
8428 const auto pos = localPosFromVirgin(po.m_fr, lb->startColumn(), lb->startLine());
8429
8430 po.m_rawTextData.push_back({lb->text(), pos.first, pos.second});
8431 }
8432 }
8433 }
8434 }
8435
8436 std::pair<typename Trait::String, WithPosition> label;
8437
8438 if (p->items().back()->type() == ItemType::Text) {
8439 auto t = std::static_pointer_cast<Text<Trait>>(p->items().back());
8440
8441 if (t->opts() == TextWithoutFormat) {
8442 auto text = po.m_rawTextData.back();
8443 typename Trait::InternalString tmp(text.m_str);
8445
8446 if (!label.first.isEmpty()) {
8447 label.first = label.first.sliced(1, label.first.length() - 2);
8448
8449 if (tmp.asString().simplified().isEmpty()) {
8450 p->removeItemAt(p->items().size() - 1);
8451 po.m_rawTextData.pop_back();
8452
8453 if (!p->items().empty()) {
8454 const auto last = std::static_pointer_cast<WithPosition>(p->items().back());
8455 p->setEndColumn(last->endColumn());
8456 p->setEndLine(last->endLine());
8457 }
8458 } else {
8459 const auto notSpacePos = tmp.virginPos(skipSpaces<Trait>(0, tmp.asString()));
8460 const auto virginLine = t->endLine();
8461
8462 if (label.second.startColumn() > notSpacePos) {
8463 auto text = tmp.fullVirginString().sliced(0, label.second.startColumn());
8464 po.m_rawTextData.back().m_str = text;
8465
8466 if (!t->text()[0].isSpace()) {
8467 const auto notSpacePos = skipSpaces<Trait>(0, text);
8468
8469 text.remove(0, notSpacePos);
8470 }
8471
8473 t->setEndColumn(label.second.startColumn() - 1);
8474
8475 const auto lastPos = t->endColumn();
8476 const auto pos = localPosFromVirgin(po.m_fr, label.second.endColumn() + 1, virginLine);
8477
8478 if (pos.first != -1) {
8479 t = std::make_shared<Text<Trait>>();
8480 t->setStartColumn(label.second.endColumn() + 1);
8481 t->setStartLine(virginLine);
8482 t->setEndColumn(lastPos);
8483 t->setEndLine(virginLine);
8484 p->appendItem(t);
8485
8486 po.m_rawTextData.push_back({po.m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8487 pos.first, pos.second});
8488 }
8489 }
8490
8491 const auto pos = localPosFromVirgin(po.m_fr, label.second.endColumn() + 1, virginLine);
8492
8493 if (pos.first != -1) {
8494 po.m_rawTextData.back() = {po.m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8495 pos.first, pos.second};
8496
8497 auto text = po.m_rawTextData.back().m_str;
8498
8499 if (!text.simplified().isEmpty()) {
8500 if (p->items().size() == 1) {
8501 const auto ns = skipSpaces<Trait>(0, text);
8502
8503 text.remove(0, ns);
8504 }
8505
8506 t->setStartColumn(label.second.endColumn() + 1);
8508 } else {
8509 po.m_rawTextData.pop_back();
8510 p->removeItemAt(p->items().size() - 1);
8511 }
8512 }
8513
8514 p->setEndColumn(t->endColumn());
8515 }
8516 } else {
8517 label.first.clear();
8518 }
8519
8520 label.second.setStartLine(t->startLine());
8521 label.second.setEndLine(t->endLine());
8522 }
8523 }
8524
8525 std::shared_ptr<Heading<Trait>> h(new Heading<Trait>);
8526 h->setStartColumn(p->startColumn());
8527 h->setStartLine(p->startLine());
8528 h->setEndColumn(lastColumn);
8529 h->setEndLine(lastLine);
8530 h->setLevel(level);
8531
8532 if (!p->items().empty()) {
8533 h->setText(p);
8534 }
8535
8536 h->setDelims({delim});
8537
8538 if (label.first.isEmpty() && !p->items().empty()) {
8539 label.first = Trait::latin1ToString("#") + paragraphToLabel(p.get());
8540 } else {
8541 h->setLabelPos(label.second);
8542 }
8543
8544 if (!label.first.isEmpty()) {
8545 const auto path = Trait::latin1ToString("/") + (!workingPath.isEmpty() ?
8546 workingPath + Trait::latin1ToString("/") : typename Trait::String()) + fileName;
8547
8548 h->setLabel(label.first + path);
8549
8550 doc->insertLabeledHeading(label.first + path, h);
8551 h->labelVariants().push_back(h->label());
8552
8553 if (label.first != label.first.toLower()) {
8554 doc->insertLabeledHeading(label.first.toLower() + path, h);
8555 h->labelVariants().push_back(label.first.toLower() + path);
8556 }
8557 }
8558
8559 parent->appendItem(h);
8560 }
8561}
8562
8563//! \return Index of text item for the given index in raw text data.
8564template<class Trait>
8565inline long long int
8566textAtIdx(std::shared_ptr<Paragraph<Trait>> p,
8567 size_t idx)
8568{
8569 size_t i = 0;
8570
8571 for (auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8572 if ((*it)->type() == ItemType::Text) {
8573 if (i == idx) {
8574 return std::distance(p->items().cbegin(), it);
8575 }
8576
8577 ++i;
8578 }
8579 }
8580
8581 return -1;
8582}
8583
8584//! Process text plugins.
8585template<class Trait>
8586inline void
8589 const TextPluginsMap<Trait> &textPlugins,
8590 bool inLink)
8591{
8592 for (const auto &plugin : textPlugins) {
8593 if (inLink && !std::get<bool>(plugin.second)) {
8594 continue;
8595 }
8596
8597 std::get<TextPluginFunc<Trait>>(plugin.second)(p, po,
8598 std::get<typename Trait::StringList>(plugin.second));
8599 }
8600}
8601
8602//! Make horizontal line.
8603template<class Trait>
8604inline void
8606 std::shared_ptr<Block<Trait>> parent)
8607{
8608 std::shared_ptr<Item<Trait>> hr(new HorizontalLine<Trait>);
8609 hr->setStartColumn(line.first.virginPos(skipSpaces<Trait>(0, line.first.asString())));
8610 hr->setStartLine(line.second.m_lineNumber);
8611 hr->setEndColumn(line.first.virginPos(line.first.length() - 1));
8612 hr->setEndLine(line.second.m_lineNumber);
8613 parent->appendItem(hr);
8614}
8615
8616template<class Trait>
8617inline long long int
8619 std::shared_ptr<Block<Trait>> parent,
8620 std::shared_ptr<Document<Trait>> doc,
8621 typename Trait::StringList &linksToParse,
8622 const typename Trait::String &workingPath,
8623 const typename Trait::String &fileName,
8624 bool collectRefLinks,
8625 bool ignoreLineBreak,
8626 RawHtmlBlock<Trait> &html,
8627 bool inLink)
8628
8629{
8630 if (fr.m_data.empty()) {
8631 return -1;
8632 }
8633
8634 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
8635 p->setStartColumn(fr.m_data.at(0).first.virginPos(0));
8636 p->setStartLine(fr.m_data.at(0).second.m_lineNumber);
8637
8638 auto delims = collectDelimiters(fr.m_data);
8639
8640 TextParsingOpts<Trait> po = {fr, p, nullptr, doc, linksToParse, workingPath, fileName,
8641 collectRefLinks, ignoreLineBreak, html, m_textPlugins};
8642 typename Delims::iterator styleStackBottom = delims.end();
8643
8644 if (html.m_html.get() && html.m_continueHtml) {
8645 finishRawHtmlTag(delims.begin(), delims.end(), po, false);
8646 } else if (!delims.empty()) {
8647 for (auto it = delims.begin(), last = delims.end(); it != last; ++it) {
8648 if (po.m_line > po.m_lastTextLine) {
8649 checkForTableInParagraph(po, fr.m_data.size() - 1);
8650 }
8651
8652 if (po.shouldStopParsing() && po.m_lastTextLine < it->m_line) {
8653 break;
8654 } else {
8655 makeText(po.m_lastTextLine < it->m_line ? po.m_lastTextLine : it->m_line,
8656 po.m_lastTextLine < it->m_line ? po.m_lastTextPos : it->m_pos, po);
8657 }
8658
8659 switch (it->m_type) {
8660 case Delimiter::SquareBracketsOpen: {
8661 it = checkForLink(it, last, po);
8662 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8663 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8664 } break;
8665
8666 case Delimiter::ImageOpen: {
8667 it = checkForImage(it, last, po);
8668 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8669 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8670 } break;
8671
8672 case Delimiter::Less: {
8673 it = checkForAutolinkHtml(it, last, po, true);
8674
8675 if (!html.m_html.get()) {
8676 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8677 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8678 }
8679 } break;
8680
8681 case Delimiter::Strikethrough:
8682 case Delimiter::Emphasis1:
8683 case Delimiter::Emphasis2: {
8684 if (!collectRefLinks) {
8685 it = checkForStyle(delims.begin(), it, last, styleStackBottom, po);
8686 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8687 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8688 }
8689 } break;
8690
8691 case Delimiter::Math: {
8692 it = checkForMath(it, last, po);
8693 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8694 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8695 } break;
8696
8697 case Delimiter::InlineCode: {
8698 if (!it->m_backslashed) {
8699 it = checkForInlineCode(it, last, po);
8700 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8701 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8702 }
8703 } break;
8704
8705 case Delimiter::HorizontalLine: {
8706 po.m_wasRefLink = false;
8707 po.m_firstInParagraph = false;
8708
8709 const auto pos = skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString());
8710 const auto withoutSpaces = po.m_fr.m_data[it->m_line].first.asString().sliced(pos);
8711
8712 auto h2 = isH2<Trait>(withoutSpaces);
8713
8714 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8715
8716 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8717
8718 if (it->m_line - 1 >= 0) {
8719 p->setEndColumn(fr.m_data.at(it->m_line - 1).first.virginPos(
8720 fr.m_data.at(it->m_line - 1).first.length() - 1));
8721 p->setEndLine(fr.m_data.at(it->m_line - 1).second.m_lineNumber);
8722 }
8723
8724 p = splitParagraphsAndFreeHtml(parent, p, po, collectRefLinks, m_fullyOptimizeParagraphs);
8725
8726 if (!h2 || !po.m_headingAllowed) {
8727 if (!collectRefLinks && !p->isEmpty()) {
8728 parent->appendItem(p);
8729 }
8730
8731 h2 = false;
8732 } else {
8733 makeHeading(parent,
8734 doc,
8735 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8736 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8737 fr.m_data[it->m_line].second.m_lineNumber,
8738 2,
8739 workingPath,
8740 fileName,
8741 collectRefLinks,
8742 {po.m_fr.m_data[it->m_line].first.virginPos(pos),
8743 fr.m_data[it->m_line].second.m_lineNumber,
8744 po.m_fr.m_data[it->m_line].first.virginPos(
8745 lastNonSpacePos(po.m_fr.m_data[it->m_line].first.asString())),
8746 fr.m_data[it->m_line].second.m_lineNumber},
8747 po);
8748
8749 po.m_checkLineOnNewType = true;
8750 }
8751
8752 p.reset(new Paragraph<Trait>);
8753 po.m_rawTextData.clear();
8754
8755 if (it->m_line + 1 < static_cast<long long int>(fr.m_data.size())) {
8756 p->setStartColumn(fr.m_data.at(it->m_line + 1).first.virginPos(0));
8757 p->setStartLine(fr.m_data.at(it->m_line + 1).second.m_lineNumber);
8758 }
8759
8760 po.m_parent = p;
8761 po.m_line = it->m_line;
8762 po.m_pos = it->m_pos + it->m_len;
8763
8764 if (!h2 && !collectRefLinks) {
8765 makeHorLine<Trait>(fr.m_data[it->m_line], parent);
8766 }
8767 } break;
8768
8769 case Delimiter::H1:
8770 case Delimiter::H2: {
8771 po.m_wasRefLink = false;
8772 po.m_firstInParagraph = false;
8773
8774 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8775
8776 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8777
8778 if (it->m_line - 1 >= 0) {
8779 p->setEndColumn(fr.m_data.at(it->m_line - 1).first.virginPos(
8780 fr.m_data.at(it->m_line - 1).first.length() - 1));
8781 p->setEndLine(fr.m_data.at(it->m_line - 1).second.m_lineNumber);
8782 }
8783
8784 p = splitParagraphsAndFreeHtml(parent, p, po, collectRefLinks,
8785 m_fullyOptimizeParagraphs);
8786
8787 if (po.m_headingAllowed) {
8788 makeHeading(parent,
8789 doc,
8790 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8791 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8792 fr.m_data[it->m_line].second.m_lineNumber,
8793 it->m_type == Delimiter::H1 ? 1 : 2,
8794 workingPath,
8795 fileName,
8796 collectRefLinks,
8797 {po.m_fr.m_data[it->m_line].first.virginPos(skipSpaces<Trait>(
8798 0, po.m_fr.m_data[it->m_line].first.asString())),
8799 fr.m_data[it->m_line].second.m_lineNumber,
8800 po.m_fr.m_data[it->m_line].first.virginPos(lastNonSpacePos(
8801 po.m_fr.m_data[it->m_line].first.asString())),
8802 fr.m_data[it->m_line].second.m_lineNumber},
8803 po);
8804
8805 po.m_checkLineOnNewType = true;
8806
8807 p.reset(new Paragraph<Trait>);
8808 po.m_rawTextData.clear();
8809
8810 if (it->m_line + 1 < static_cast<long long int>(fr.m_data.size())) {
8811 p->setStartColumn(fr.m_data.at(it->m_line + 1).first.virginPos(0));
8812 p->setStartLine(fr.m_data.at(it->m_line + 1).second.m_lineNumber);
8813 }
8814
8815 po.m_line = it->m_line;
8816 po.m_pos = it->m_pos + it->m_len;
8817 } else if (p->startColumn() == -1) {
8818 p->setStartColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
8819 p->setStartLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8820 }
8821
8822 po.m_parent = p;
8823 } break;
8824
8825 default: {
8826 if (!po.shouldStopParsing()) {
8827 po.m_wasRefLink = false;
8828 po.m_firstInParagraph = false;
8829
8830 makeText(it->m_line, it->m_pos + it->m_len, po);
8831 }
8832 } break;
8833 }
8834
8835 if (po.shouldStopParsing()) {
8836 break;
8837 }
8838
8839 if (po.m_checkLineOnNewType) {
8840 if (po.m_line + 1 < static_cast<long long int>(po.m_fr.m_data.size())) {
8841 const auto type = Parser<Trait>::whatIsTheLine(po.m_fr.m_data[po.m_line + 1].first);
8842
8843 bool doBreak = false;
8844
8845 switch (type) {
8847 po.m_detected = TextParsingOpts<Trait>::Detected::Code;
8848 doBreak = true;
8849 break;
8850
8853 po.m_detected = TextParsingOpts<Trait>::Detected::List;
8854 doBreak = true;
8855 break;
8856
8858 po.m_detected = TextParsingOpts<Trait>::Detected::Blockquote;
8859 doBreak = true;
8860 break;
8861
8862 default:
8863 break;
8864 }
8865
8866 if (doBreak) {
8867 break;
8868 }
8869 }
8870
8871 po.m_checkLineOnNewType = false;
8872 }
8873 }
8874 }
8875
8876 if (po.m_lastTextLine == -1) {
8877 checkForTableInParagraph(po, po.m_fr.m_data.size() - 1);
8878 }
8879
8880 switch(po.m_detected) {
8881 case TextParsingOpts<Trait>::Detected::Table:
8882 makeText(po.m_lastTextLine, po.m_lastTextPos, po);
8883 break;
8884
8885 case TextParsingOpts<Trait>::Detected::Nothing:
8886 {
8887 if(po.m_line <= static_cast<long long int>(po.m_fr.m_data.size() - 1)) {
8888 makeText(po.m_fr.m_data.size() - 1, po.m_fr.m_data.back().first.length(), po);
8889 }
8890 }
8891 break;
8892
8893 default:
8894 break;
8895 }
8896
8897 if (!p->isEmpty()) {
8898 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8899
8900 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8901
8902 p = splitParagraphsAndFreeHtml(parent, p, po, collectRefLinks, m_fullyOptimizeParagraphs);
8903
8904 if (!p->isEmpty() && !collectRefLinks) {
8905 parent->appendItem(optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()));
8906 }
8907
8908 po.m_rawTextData.clear();
8909 }
8910
8911 normalizePos(po.m_pos, po.m_line, po.m_line < static_cast<long long int>(po.m_fr.m_data.size()) ?
8912 po.m_fr.m_data[po.m_line].first.length() : 0, po.m_fr.m_data.size());
8913
8914 if (po.m_detected != TextParsingOpts<Trait>::Detected::Nothing) {
8915 if (po.m_line < static_cast<long long int>(po.m_fr.m_data.size())) {
8916 return po.m_fr.m_data.at(po.m_line).second.m_lineNumber;
8917 }
8918 }
8919
8920 return -1;
8921}
8922
8923template<class Trait>
8924inline void
8925Parser<Trait>::parseFootnote(MdBlock<Trait> &fr,
8926 std::shared_ptr<Block<Trait>>,
8927 std::shared_ptr<Document<Trait>> doc,
8928 typename Trait::StringList &linksToParse,
8929 const typename Trait::String &workingPath,
8930 const typename Trait::String &fileName,
8931 bool collectRefLinks)
8932{
8933 {
8934 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](const auto &s) {
8935 return !s.first.isEmpty();
8936 })).base();
8937
8938 if (it != fr.m_data.end()) {
8939 fr.m_data.erase(it, fr.m_data.end());
8940 }
8941 }
8942
8943 if (!fr.m_data.empty()) {
8944 std::shared_ptr<Footnote<Trait>> f(new Footnote<Trait>);
8945 f->setStartColumn(fr.m_data.front().first.virginPos(0));
8946 f->setStartLine(fr.m_data.front().second.m_lineNumber);
8947 f->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
8948 f->setEndLine(fr.m_data.back().second.m_lineNumber);
8949
8950 auto delims = collectDelimiters(fr.m_data);
8951
8952 RawHtmlBlock<Trait> html;
8953
8954 TextParsingOpts<Trait> po = {fr, f, nullptr, doc, linksToParse, workingPath, fileName,
8955 collectRefLinks, false, html, m_textPlugins};
8956 po.m_lastTextLine = fr.m_data.size();
8957 po.m_lastTextPos = fr.m_data.back().first.length();
8958
8959 if (!delims.empty() && delims.cbegin()->m_type == Delimiter::SquareBracketsOpen &&
8960 !delims.cbegin()->m_isWordBefore) {
8961 typename MdBlock<Trait>::Data id;
8962 typename Delims::iterator it = delims.end();
8963
8964 po.m_line = delims.cbegin()->m_line;
8965 po.m_pos = delims.cbegin()->m_pos;
8966
8967 std::tie(id, it) = checkForLinkText(delims.begin(), delims.end(), po);
8968
8969 if (!toSingleLine(id).isEmpty() &&
8970 id.front().first.asString().startsWith(Trait::latin1ToString("^")) &&
8971 it != delims.cend() &&
8972 fr.m_data.at(it->m_line).first.length() > it->m_pos + 2 &&
8973 fr.m_data.at(it->m_line).first[it->m_pos + 1] == Trait::latin1ToChar(':') &&
8974 fr.m_data.at(it->m_line).first[it->m_pos + 2].isSpace()) {
8975 f->setIdPos({fr.m_data[delims.cbegin()->m_line].first.virginPos(delims.cbegin()->m_pos),
8976 fr.m_data[delims.cbegin()->m_line].second.m_lineNumber,
8977 fr.m_data.at(it->m_line).first.virginPos(it->m_pos + 1),
8978 fr.m_data.at(it->m_line).second.m_lineNumber});
8979
8980 {
8981 typename MdBlock<Trait>::Data tmp;
8982 std::copy(fr.m_data.cbegin() + it->m_line, fr.m_data.cend(),
8983 std::back_inserter(tmp));
8984 fr.m_data = tmp;
8985 }
8986
8987 fr.m_data.front().first = fr.m_data.front().first.sliced(it->m_pos + 3);
8988
8989 for (auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it) {
8990 if (it->first.asString().startsWith(Trait::latin1ToString(" "))) {
8991 it->first = it->first.sliced(4);
8992 }
8993 }
8994
8995 StringListStream<Trait> stream(fr.m_data);
8996
8997 parse(stream, f, doc, linksToParse, workingPath, fileName, collectRefLinks);
8998
8999 if (!f->isEmpty()) {
9000 doc->insertFootnote(Trait::latin1ToString("#") + toSingleLine(id) +
9001 Trait::latin1ToString("/") + (!workingPath.isEmpty() ?
9002 workingPath + Trait::latin1ToString("/") : typename Trait::String()) + fileName,
9003 f);
9004 }
9005 }
9006 }
9007 }
9008}
9009
9010template<class Trait>
9011inline long long int
9012Parser<Trait>::parseBlockquote(MdBlock<Trait> &fr,
9013 std::shared_ptr<Block<Trait>> parent,
9014 std::shared_ptr<Document<Trait>> doc,
9015 typename Trait::StringList &linksToParse,
9016 const typename Trait::String &workingPath,
9017 const typename Trait::String &fileName,
9018 bool collectRefLinks,
9019 RawHtmlBlock<Trait> &)
9020{
9021 const long long int pos = fr.m_data.front().first.asString().indexOf(Trait::latin1ToChar('>'));
9022 long long int extra = 0;
9023
9024 long long int line = -1;
9025
9026 if (pos > -1) {
9027 typename Blockquote<Trait>::Delims delims;
9028
9029 long long int i = 0, j = 0;
9030
9031 BlockType bt = BlockType::EmptyLine;
9032
9033 for (auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it, ++i) {
9034 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9035 const auto gt = (ns < it->first.length() ? (it->first[ns] == Trait::latin1ToChar('>') ? ns : -1) : -1);
9036
9037 if (gt > -1) {
9038 const auto dp = it->first.virginPos(gt);
9039 delims.push_back({dp, it->second.m_lineNumber, dp, it->second.m_lineNumber});
9040
9041 if (it == fr.m_data.begin()) {
9042 extra = gt + (it->first.length() > gt + 1 ?
9043 (it->first[gt + 1] == Trait::latin1ToChar(' ') ? 1 : 0) : 0) + 1;
9044 }
9045
9046 it->first = it->first.sliced(gt + (it->first.length() > gt + 1 ?
9047 (it->first[gt + 1] == Trait::latin1ToChar(' ') ? 1 : 0) : 0) + 1);
9048
9049 bt = whatIsTheLine(it->first);
9050 }
9051 // Process lazyness...
9052 else {
9053 if (ns < 4 && isHorizontalLine<Trait>(it->first.asString().sliced(ns))) {
9054 line = it->second.m_lineNumber;
9055 break;
9056 }
9057
9058 const auto tmpBt = whatIsTheLine(it->first);
9059
9060 if (isListType(tmpBt)) {
9061 line = it->second.m_lineNumber;
9062 break;
9063 }
9064
9065 if (bt == BlockType::Text) {
9066 if (isH1<Trait>(it->first.asString())) {
9067 const auto p = it->first.asString().indexOf(Trait::latin1ToChar('='));
9068
9069 it->first.insert(p, Trait::latin1ToChar('\\'));
9070
9071 continue;
9072 } else if (isH2<Trait>(it->first.asString())) {
9073 const auto p = it->first.asString().indexOf(Trait::latin1ToChar('-'));
9074
9075 it->first.insert(p, Trait::latin1ToChar('\\'));
9076
9077 continue;
9078 }
9079 } else if ((bt == BlockType::Code || bt == BlockType::CodeIndentedBySpaces) &&
9080 it->second.m_mayBreakList) {
9081 line = it->second.m_lineNumber;
9082 break;
9083 }
9084
9085 if ((bt == BlockType::Text || bt == BlockType::Blockquote || bt == BlockType::List)
9086 && (tmpBt == BlockType::Text || tmpBt == BlockType::CodeIndentedBySpaces)) {
9087 continue;
9088 } else {
9089 line = it->second.m_lineNumber;
9090 break;
9091 }
9092 }
9093 }
9094
9095 typename MdBlock<Trait>::Data tmp;
9096
9097 for (; j < i; ++j) {
9098 tmp.push_back(fr.m_data.at(j));
9099 }
9100
9101 StringListStream<Trait> stream(tmp);
9102
9103 std::shared_ptr<Blockquote<Trait>> bq(new Blockquote<Trait>);
9104 bq->setStartColumn(fr.m_data.at(0).first.virginPos(0) - extra);
9105 bq->setStartLine(fr.m_data.at(0).second.m_lineNumber);
9106 bq->setEndColumn(fr.m_data.at(j - 1).first.virginPos(fr.m_data.at(j - 1).first.length() - 1));
9107 bq->setEndLine(fr.m_data.at(j - 1).second.m_lineNumber);
9108 bq->delims() = delims;
9109
9110 parse(stream, bq, doc, linksToParse, workingPath, fileName, collectRefLinks);
9111
9112 if (!collectRefLinks) {
9113 parent->appendItem(bq);
9114 }
9115 }
9116
9117 return line;
9118}
9119
9120//! \return Is the given string a new list item.
9121template<class Trait>
9122inline bool
9123isListItemAndNotNested(const typename Trait::String &s,
9124 long long int indent)
9125{
9126 long long int p = skipSpaces<Trait>(0, s);
9127
9128 if (p >= indent || p == s.size()) {
9129 return false;
9130 }
9131
9132 bool space = false;
9133
9134 if (p + 1 >= s.size()) {
9135 space = true;
9136 } else {
9137 space = s[p + 1].isSpace();
9138 }
9139
9140 if (p < 4) {
9141 if (s[p] == Trait::latin1ToChar('*') && space) {
9142 return true;
9143 } else if (s[p] == Trait::latin1ToChar('-') && space) {
9144 return true;
9145 } else if (s[p] == Trait::latin1ToChar('+') && space) {
9146 return true;
9147 } else {
9148 return isOrderedList<Trait>(s);
9149 }
9150 } else
9151 return false;
9152}
9153
9154//! \return Indent.
9155template<class Trait>
9156inline std::pair<long long int, long long int>
9157calculateIndent(const typename Trait::String &s,
9158 long long int p)
9159{
9160 return {0, skipSpaces<Trait>(p, s)};
9161}
9162
9163//! \return List item data.
9164template<class Trait>
9165inline std::tuple<bool, long long int, typename Trait::Char, bool>
9166listItemData(const typename Trait::String &s,
9167 bool wasText)
9168{
9169 long long int p = skipSpaces<Trait>(0, s);
9170
9171 if (p == s.size()) {
9172 return {false, 0, typename Trait::Char(), false};
9173 }
9174
9175 bool space = false;
9176
9177 if (p + 1 >= s.size()) {
9178 space = true;
9179 } else {
9180 space = s[p + 1].isSpace();
9181 }
9182
9183 if (p < 4) {
9184 if (s[p] == Trait::latin1ToChar('*') && space) {
9185 return {true, p + 2, Trait::latin1ToChar('*'),
9186 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() : false};
9187 } else if (s[p] == Trait::latin1ToChar('-')) {
9188 if (isH2<Trait>(s) && wasText) {
9189 return {false, p + 2, Trait::latin1ToChar('-'), false};
9190 } else if (space) {
9191 return {true, p + 2, Trait::latin1ToChar('-'),
9192 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() : false};
9193 }
9194 } else if (s[p] == Trait::latin1ToChar('+') && space) {
9195 return {true, p + 2, Trait::latin1ToChar('+'),
9196 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() : false};
9197 } else {
9198 int d = 0, l = 0;
9199 typename Trait::Char c;
9200
9201 if (isOrderedList<Trait>(s, &d, &l, &c)) {
9202 return {true, p + l + 2, c,
9203 p + l + 2 < s.size() ? !s.sliced(p + l + 2).isEmpty() : false};
9204 } else {
9205 return {false, 0, typename Trait::Char(), false};
9206 }
9207 }
9208 }
9209
9210 return {false, 0, typename Trait::Char(), false};
9211}
9212
9213//! Set last position of the item.
9214template<class Trait>
9215inline void
9216setLastPos(std::shared_ptr<Item<Trait>> item,
9217 long long int pos,
9218 long long int line)
9219{
9220 item->setEndColumn(pos);
9221 item->setEndLine(line);
9222}
9223
9224//! Update last position of all parent.
9225template<class Trait>
9226inline void
9228{
9229 if (html.m_parent != html.m_topParent) {
9230 const auto it = html.m_toAdjustLastPos.find(html.m_parent);
9231
9232 if (it != html.m_toAdjustLastPos.end()) {
9233 for (auto &i : it->second) {
9234 i.first->setEndColumn(html.m_html->endColumn());
9235 i.first->setEndLine(html.m_html->endLine());
9236 }
9237 }
9238 }
9239}
9240
9241template<class Trait>
9242inline long long int
9243Parser<Trait>::parseList(MdBlock<Trait> &fr,
9244 std::shared_ptr<Block<Trait>> parent,
9245 std::shared_ptr<Document<Trait>> doc,
9246 typename Trait::StringList &linksToParse,
9247 const typename Trait::String &workingPath,
9248 const typename Trait::String &fileName,
9249 bool collectRefLinks,
9250 RawHtmlBlock<Trait> &html)
9251{
9252 bool resetTopParent = false;
9253 long long int line = -1;
9254
9255 if (!html.m_topParent) {
9256 html.m_topParent = parent;
9257 resetTopParent = true;
9258 }
9259
9260 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9261
9262 if (p != fr.m_data.front().first.length()) {
9263 std::shared_ptr<List<Trait>> list(new List<Trait>);
9264
9265 typename MdBlock<Trait>::Data listItem;
9266 auto it = fr.m_data.begin();
9267 listItem.push_back(*it);
9268 list->setStartColumn(it->first.virginPos(p));
9269 list->setStartLine(it->second.m_lineNumber);
9270 ++it;
9271
9272 long long int indent = 0;
9273 typename Trait::Char marker;
9274
9275 std::tie(std::ignore, indent, marker, std::ignore) =
9276 listItemData<Trait>(listItem.front().first.asString(), false);
9277
9278 html.m_blocks.push_back({list, list->startColumn() + indent});
9279
9280 if (!collectRefLinks) {
9281 html.m_toAdjustLastPos.insert({list, html.m_blocks});
9282 }
9283
9284 bool updateIndent = false;
9285
9286 auto addListMakeNew = [&]() {
9287 if (!list->isEmpty() && !collectRefLinks) {
9288 parent->appendItem(list);
9289 }
9290
9291 html.m_blocks.pop_back();
9292
9293 list.reset(new List<Trait>);
9294
9295 html.m_blocks.push_back({list, indent});
9296
9297 if (!collectRefLinks) {
9298 html.m_toAdjustLastPos.insert({list, html.m_blocks});
9299 }
9300 };
9301
9302 auto processLastHtml = [&](std::shared_ptr<ListItem<Trait>> resItem) {
9303 if (html.m_html && resItem) {
9304 html.m_parent = (resItem->startLine() == html.m_html->startLine() ||
9305 html.m_html->startColumn() >= resItem->startColumn() + indent ?
9306 resItem : html.findParent(html.m_html->startColumn()));
9307
9308 if (!html.m_parent) {
9309 html.m_parent = html.m_topParent;
9310 }
9311
9312 if (html.m_parent != resItem) {
9313 addListMakeNew();
9314 }
9315
9316 const auto continueHtml = html.m_onLine && html.m_continueHtml && html.m_parent == html.m_topParent;
9317
9318 if (!collectRefLinks) {
9319 if (!continueHtml) {
9320 html.m_parent->appendItem(html.m_html);
9321 }
9322
9323 updateLastPosInList<Trait>(html);
9324 }
9325
9326 if (!continueHtml) {
9327 resetHtmlTag<Trait>(html);
9328 }
9329 }
9330 };
9331
9332 auto processListItem = [&]() {
9333 MdBlock<Trait> block = {listItem, 0};
9334
9335 std::shared_ptr<ListItem<Trait>> resItem;
9336
9337 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9338 collectRefLinks, html, &resItem);
9339 listItem.clear();
9340
9341 if (html.m_html) {
9342 processLastHtml(resItem);
9343 } else if (line >= 0) {
9344 addListMakeNew();
9345 }
9346 };
9347
9348 for (auto last = fr.m_data.end(); it != last; ++it) {
9349 if (updateIndent) {
9350 std::tie(std::ignore, indent, marker, std::ignore) =
9351 listItemData<Trait>(it->first.asString(), false);
9352
9353 if (!collectRefLinks) {
9354 html.m_blocks.back().second = indent;
9355 }
9356
9357 updateIndent = false;
9358 }
9359
9360 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9361
9362 if (isH1<Trait>(it->first.asString().sliced(ns)) && ns < indent && !listItem.empty()) {
9363 const auto p = it->first.asString().indexOf(Trait::latin1ToChar('='));
9364
9365 it->first.insert(p, Trait::latin1ToChar('\\'));
9366 } else if (isHorizontalLine<Trait>(it->first.asString().sliced(ns)) &&
9367 ns < indent && !listItem.empty()) {
9368 updateIndent = true;
9369
9370 processListItem();
9371
9372 if (!list->isEmpty()) {
9373 addListMakeNew();
9374 }
9375
9376 if (!collectRefLinks) {
9377 makeHorLine<Trait>(*it, parent);
9378 }
9379
9380 continue;
9381 } else if (isListItemAndNotNested<Trait>(it->first.asString(), indent) &&
9382 !listItem.empty() && !it->second.m_mayBreakList) {
9383 typename Trait::Char tmpMarker;
9384 std::tie(std::ignore, indent, tmpMarker, std::ignore) =
9385 listItemData<Trait>(it->first.asString(), false);
9386
9387 processListItem();
9388
9389 if (tmpMarker != marker) {
9390 if (!list->isEmpty()) {
9391 addListMakeNew();
9392 }
9393
9394 marker = tmpMarker;
9395 }
9396 }
9397
9398 if (line > 0) {
9399 break;
9400 }
9401
9402 listItem.push_back(*it);
9403
9404 if (list->startColumn() == -1) {
9405 list->setStartColumn(
9406 it->first.virginPos(std::min(it->first.length() ?
9407 it->first.length() - 1 : 0, skipSpaces<Trait>(0, it->first.asString()))));
9408 list->setStartLine(it->second.m_lineNumber);
9409
9410 if (!collectRefLinks) {
9411 html.m_blocks.back().second += list->startColumn();
9412 }
9413 }
9414 }
9415
9416 if (!listItem.empty()) {
9417 MdBlock<Trait> block = {listItem, 0};
9418 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9419 collectRefLinks, html);
9420 }
9421
9422 if (!list->isEmpty() && !collectRefLinks) {
9423 parent->appendItem(list);
9424 }
9425
9426 html.m_blocks.pop_back();
9427 }
9428
9429 if (resetTopParent) {
9430 html.m_topParent.reset();
9431 }
9432
9433 return line;
9434}
9435
9436template<class Trait>
9437inline long long int
9438Parser<Trait>::parseListItem(MdBlock<Trait> &fr,
9439 std::shared_ptr<Block<Trait>> parent,
9440 std::shared_ptr<Document<Trait>> doc,
9441 typename Trait::StringList &linksToParse,
9442 const typename Trait::String &workingPath,
9443 const typename Trait::String &fileName,
9444 bool collectRefLinks,
9445 RawHtmlBlock<Trait> &html,
9446 std::shared_ptr<ListItem<Trait>> *resItem)
9447{
9448 {
9449 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](const auto &s) {
9450 return !s.first.isEmpty();
9451 })).base();
9452
9453 if (it != fr.m_data.end()) {
9454 fr.m_data.erase(it, fr.m_data.end());
9455 }
9456 }
9457
9458 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9459
9460 std::shared_ptr<ListItem<Trait>> item(new ListItem<Trait>);
9461
9462 item->setStartColumn(fr.m_data.front().first.virginPos(p));
9463 item->setStartLine(fr.m_data.front().second.m_lineNumber);
9464
9465 int i = 0, len = 0;
9466
9467 if (isOrderedList<Trait>(fr.m_data.front().first.asString(), &i, &len)) {
9468 item->setListType(ListItem<Trait>::Ordered);
9469 item->setStartNumber(i);
9470 item->setDelim({item->startColumn(), item->startLine(), item->startColumn() + len, item->startLine()});
9471 } else {
9472 item->setListType(ListItem<Trait>::Unordered);
9473 item->setDelim({item->startColumn(), item->startLine(), item->startColumn(), item->startLine()});
9474 }
9475
9476 if (item->listType() == ListItem<Trait>::Ordered) {
9477 item->setOrderedListPreState(i == 1 ? ListItem<Trait>::Start : ListItem<Trait>::Continue);
9478 }
9479
9480 typename MdBlock<Trait>::Data data;
9481
9482 auto it = fr.m_data.begin();
9483 ++it;
9484
9485 int pos = 1;
9486
9487 long long int indent = 0;
9488 bool wasText = false;
9489
9490 std::tie(std::ignore, indent, std::ignore, wasText) =
9491 listItemData<Trait>(fr.m_data.front().first.asString(), wasText);
9492
9493 html.m_blocks.push_back({item, item->startColumn() + indent});
9494
9495 if (!collectRefLinks) {
9496 html.m_toAdjustLastPos.insert({item, html.m_blocks});
9497 }
9498
9499 const auto firstNonSpacePos = calculateIndent<Trait>(
9500 fr.m_data.front().first.asString(), indent).second;
9501
9502 if (firstNonSpacePos - indent < 4) {
9503 indent = firstNonSpacePos;
9504 }
9505
9506 if (indent < fr.m_data.front().first.length()) {
9507 data.push_back({fr.m_data.front().first.right(fr.m_data.front().first.length() - indent),
9508 fr.m_data.front().second});
9509 }
9510
9511 bool taskList = false;
9512 bool checked = false;
9513
9514 if (!data.empty()) {
9515 auto p = skipSpaces<Trait>(0, data.front().first.asString());
9516
9517 if (p < data.front().first.length()) {
9518 if (data.front().first[p] == Trait::latin1ToChar('[')) {
9519 const auto startTaskDelimPos = data.front().first.virginPos(p);
9520
9521 ++p;
9522
9523 if (p < data.front().first.length()) {
9524 if (data.front().first[p] == Trait::latin1ToChar(' ') ||
9525 data.front().first[p].toLower() == Trait::latin1ToChar('x')) {
9526 if (data.front().first[p].toLower() == Trait::latin1ToChar('x')) {
9527 checked = true;
9528 }
9529
9530 ++p;
9531
9532 if (p < data.front().first.length()) {
9533 if (data.front().first[p] == Trait::latin1ToChar(']')) {
9534 item->setTaskDelim({startTaskDelimPos, item->startLine(), data.front().first.virginPos(p), item->startLine()});
9535
9536 taskList = true;
9537
9538 data[0].first = data[0].first.sliced(p + 1);
9539 }
9540 }
9541 }
9542 }
9543 }
9544 }
9545 }
9546
9547 if (taskList) {
9548 item->setTaskList();
9549 item->setChecked(checked);
9550 }
9551
9552 bool fencedCode = false;
9553 typename Trait::String startOfCode;
9554 bool wasEmptyLine = false;
9555
9556 std::vector<std::pair<RawHtmlBlock<Trait>, long long int>> htmlToAdd;
9557 long long int line = -1;
9558
9559 auto parseStream = [&](StringListStream<Trait> &stream) -> long long int
9560 {
9561 const auto tmpHtml = html;
9562 long long int line = -1;
9563 std::tie(html, line) = parse(stream, item, doc, linksToParse, workingPath, fileName,
9564 collectRefLinks, false, true, true);
9565 html.m_topParent = tmpHtml.m_topParent;
9566 html.m_blocks = tmpHtml.m_blocks;
9567 html.m_toAdjustLastPos = tmpHtml.m_toAdjustLastPos;
9568
9569 return line;
9570 };
9571
9572 auto processHtml = [&](auto it) -> long long int
9573 {
9574 auto finishHtml = [&]()
9575 {
9576 if (html.m_html) {
9577 htmlToAdd.push_back({html, html.m_parent->items().size()});
9578 updateLastPosInList<Trait>(html);
9579 resetHtmlTag<Trait>(html);
9580 }
9581 };
9582
9583 if (html.m_html.get()) {
9584 html.m_parent = html.findParent(html.m_html->startColumn());
9585
9586 if (!html.m_parent) {
9587 html.m_parent = html.m_topParent;
9588 }
9589
9590 data.clear();
9591
9592 if (html.m_continueHtml) {
9593 MdBlock<Trait> tmp;
9594 tmp.m_emptyLineAfter = fr.m_emptyLineAfter;
9595 tmp.m_emptyLinesBefore = emptyLinesBeforeCount<Trait>(fr.m_data.begin(), it);
9596 std::copy(it, fr.m_data.end(), std::back_inserter(tmp.m_data));
9597
9598 const auto line = parseText(tmp, html.m_parent, doc, linksToParse, workingPath, fileName,
9599 collectRefLinks, html);
9600
9601 if (!html.m_continueHtml) {
9602 finishHtml();
9603 }
9604
9605 return line;
9606 }
9607
9608 finishHtml();
9609 }
9610
9611 return -2;
9612 };
9613
9614 if (processHtml(std::prev(it)) == -2) {
9615 for (auto last = fr.m_data.end(); it != last; ++it, ++pos) {
9616 if (!fencedCode) {
9617 fencedCode = isCodeFences<Trait>(it->first.asString().startsWith(
9618 typename Trait::String(indent, Trait::latin1ToChar(' '))) ?
9619 it->first.asString().sliced(indent) : it->first.asString());
9620
9621 if (fencedCode) {
9622 startOfCode = startSequence<Trait>(it->first.asString());
9623 }
9624 } else if (fencedCode &&
9625 isCodeFences<Trait>(it->first.asString().startsWith(
9626 typename Trait::String(indent, Trait::latin1ToChar(' '))) ?
9627 it->first.asString().sliced(indent) : it->first.asString(),
9628 true) && startSequence<Trait>(it->first.asString()).contains(startOfCode)) {
9629 fencedCode = false;
9630 }
9631
9632 if (!fencedCode) {
9633 long long int newIndent = 0;
9634 bool ok = false;
9635
9636 std::tie(ok, newIndent, std::ignore, wasText) = listItemData<Trait>(
9637 it->first.asString().startsWith(typename Trait::String(indent, Trait::latin1ToChar(' '))) ?
9638 it->first.asString().sliced(indent) : it->first.asString(),
9639 wasText);
9640
9641 if (ok && !it->second.m_mayBreakList) {
9642 StringListStream<Trait> stream(data);
9643
9644 line = parseStream(stream);
9645
9646 data.clear();
9647
9648 const auto lineAfterHtml = processHtml(it);
9649
9650 if (lineAfterHtml != -2) {
9651 if (lineAfterHtml == -1) {
9652 break;
9653 } else {
9654 if (html.m_parent == html.m_topParent) {
9655 line = lineAfterHtml;
9656 } else {
9657 it += (lineAfterHtml - it->second.m_lineNumber);
9658 }
9659 }
9660 }
9661
9662 if (line != -1) {
9663 break;
9664 }
9665
9666 if (!htmlToAdd.empty() && htmlToAdd.back().first.m_parent == html.m_topParent) {
9667 line = it->second.m_lineNumber;
9668
9669 break;
9670 } else {
9671 typename MdBlock<Trait>::Data nestedList;
9672 nestedList.push_back(*it);
9673 const auto emptyLinesBefore = emptyLinesBeforeCount<Trait>(fr.m_data.begin(), it);
9674 ++it;
9675
9676 wasEmptyLine = false;
9677
9678 for (; it != last; ++it) {
9679 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9680 std::tie(ok, std::ignore, std::ignore, wasText) =
9681 listItemData<Trait>((ns >= indent ? it->first.asString().sliced(indent) :
9682 it->first.asString()), wasText);
9683
9684 if (ok) {
9685 wasEmptyLine = false;
9686 }
9687
9688 auto currentStr = it->first.asString().startsWith(
9689 typename Trait::String(indent, Trait::latin1ToChar(' '))) ?
9690 it->first.sliced(indent) : it->first;
9691
9692 const auto type = whatIsTheLine(currentStr);
9693
9694 bool mayBreak = false;
9695
9696 switch (type) {
9697 case BlockType::Code:
9698 case BlockType::Blockquote:
9699 case BlockType::Heading:
9700 mayBreak = true;
9701 break;
9702
9703 default:
9704 break;
9705 }
9706
9707 if (ok || ns >= indent + newIndent || ns == it->first.length() || (!wasEmptyLine && !mayBreak)) {
9708 nestedList.push_back(*it);
9709 } else {
9710 break;
9711 }
9712
9713 wasEmptyLine = (ns == it->first.length());
9714
9715 wasText = (wasEmptyLine ? false : wasText);
9716 }
9717
9718 for (auto it = nestedList.begin(), last = nestedList.end(); it != last; ++it) {
9719 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9720
9721 if (ns < indent && ns != it->first.length()) {
9722 it->second.m_mayBreakList = true;
9723 } else {
9724 it->first = it->first.sliced(std::min(ns, indent));
9725 }
9726 }
9727
9728 while (!nestedList.empty() &&
9729 nestedList.back().first.asString().isEmpty()) {
9730 nestedList.pop_back();
9731 }
9732
9733 MdBlock<Trait> block = {nestedList, emptyLinesBefore, wasEmptyLine};
9734
9735 line = parseList(block, item, doc, linksToParse, workingPath, fileName,
9736 collectRefLinks, html);
9737
9738 if (line >= 0) {
9739 break;
9740 }
9741
9742 for (; it != last; ++it) {
9743 if (it->first.asString().startsWith(typename Trait::String(
9744 indent, Trait::latin1ToChar(' ')))) {
9745 it->first = it->first.sliced(indent);
9746 }
9747
9748 data.push_back(*it);
9749 }
9750
9751 break;
9752 }
9753 } else {
9754 if (!it->second.m_mayBreakList &&
9755 it->first.asString().startsWith(typename Trait::String(
9756 indent, Trait::latin1ToChar(' ')))) {
9757 it->first = it->first.sliced(indent);
9758 }
9759
9760 data.push_back(*it);
9761
9762 wasEmptyLine = (skipSpaces<Trait>(0, it->first.asString()) == it->first.length());
9763
9764 wasText = !wasEmptyLine;
9765 }
9766 } else {
9767 if (!it->second.m_mayBreakList &&
9768 it->first.asString().startsWith(typename Trait::String(
9769 indent, Trait::latin1ToChar(' ')))) {
9770 it->first = it->first.sliced(indent);
9771 }
9772
9773 data.push_back(*it);
9774 }
9775 }
9776
9777 if (!data.empty()) {
9778 StringListStream<Trait> stream(data);
9779
9780 line = parseStream(stream);
9781
9782 if (html.m_html) {
9783 html.m_parent = html.findParent(html.m_html->startColumn());
9784
9785 if (!html.m_parent) {
9786 html.m_parent = html.m_topParent;
9787 }
9788 }
9789 }
9790 } else {
9791 item.reset();
9792 }
9793
9794 if (!collectRefLinks) {
9795 if (item) {
9796 parent->appendItem(item);
9797 }
9798
9799 long long int i = 0;
9800
9801 for (auto &h : htmlToAdd) {
9802 if (h.first.m_parent != h.first.m_topParent) {
9803 h.first.m_parent->insertItem(h.second + i, h.first.m_html);
9804
9805 ++i;
9806
9807 updateLastPosInList(h.first);
9808 } else {
9809 html = h.first;
9810
9811 break;
9812 }
9813 }
9814
9815 if (item) {
9816 long long int htmlStartColumn = -1;
9817 long long int htmlStartLine = -1;
9818
9819 if (html.m_html) {
9820 std::tie(htmlStartColumn, htmlStartLine) =
9821 localPosFromVirgin<Trait>(fr, html.m_html->startColumn(), html.m_html->startLine());
9822 }
9823
9824 long long int localLine = (html.m_html ? htmlStartLine : fr.m_data.size() - 1);
9825
9826 if (html.m_html) {
9827 if (skipSpaces<Trait>(0, fr.m_data[localLine].first.asString()) >= htmlStartColumn) {
9828 --localLine;
9829 }
9830 }
9831
9832 const auto lastLine = fr.m_data[localLine].second.m_lineNumber;
9833
9834 const auto lastColumn = fr.m_data[localLine].first.virginPos(
9835 fr.m_data[localLine].first.length() ? fr.m_data[localLine].first.length() - 1 : 0);
9836
9837 item->setEndColumn(lastColumn);
9838 item->setEndLine(lastLine);
9839 parent->setEndColumn(lastColumn);
9840 parent->setEndLine(lastLine);
9841 }
9842 }
9843
9844 if (resItem) {
9845 *resItem = item;
9846 }
9847
9848 html.m_blocks.pop_back();
9849
9850 return line;
9851}
9852
9853template<class Trait>
9854inline long long int
9855Parser<Trait>::parseCode(MdBlock<Trait> &fr,
9856 std::shared_ptr<Block<Trait>> parent,
9857 bool collectRefLinks)
9858{
9859 const auto indent = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9860
9861 if (indent != fr.m_data.front().first.length()) {
9862 WithPosition startDelim, endDelim, syntaxPos;
9863 typename Trait::String syntax;
9864 isStartOfCode<Trait>(fr.m_data.front().first.asString(), &syntax, &startDelim, &syntaxPos);
9865 syntax = replaceEntity<Trait>(syntax);
9866 startDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
9867 startDelim.setEndLine(startDelim.startLine());
9868 startDelim.setStartColumn(fr.m_data.front().first.virginPos(startDelim.startColumn()));
9869 startDelim.setEndColumn(fr.m_data.front().first.virginPos(startDelim.endColumn()));
9870
9871 if (syntaxPos.startColumn() != -1) {
9872 syntaxPos.setStartLine(startDelim.startLine());
9873 syntaxPos.setEndLine(startDelim.startLine());
9874 syntaxPos.setStartColumn(fr.m_data.front().first.virginPos(syntaxPos.startColumn()));
9875 syntaxPos.setEndColumn(fr.m_data.front().first.virginPos(syntaxPos.endColumn()));
9876 }
9877
9878 const long long int startPos = fr.m_data.front().first.virginPos(indent);
9879 const long long int emptyColumn = fr.m_data.front().first.virginPos(fr.m_data.front().first.length());
9880 const long long int startLine = fr.m_data.front().second.m_lineNumber;
9881 const long long int endPos = fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1);
9882 const long long int endLine = fr.m_data.back().second.m_lineNumber;
9883
9884 fr.m_data.erase(fr.m_data.cbegin());
9885
9886 {
9887 const auto it = std::prev(fr.m_data.cend());
9888
9889 if (it->second.m_lineNumber > -1) {
9890 endDelim.setStartColumn(it->first.virginPos(skipSpaces<Trait>(0, it->first.asString())));
9891 endDelim.setStartLine(it->second.m_lineNumber);
9892 endDelim.setEndLine(endDelim.startLine());
9893 endDelim.setEndColumn(it->first.virginPos(it->first.length() - 1));
9894 }
9895
9896 fr.m_data.erase(it);
9897 }
9898
9899 if (syntax.toLower() == Trait::latin1ToString("math")) {
9900 typename Trait::String math;
9901 bool first = true;
9902
9903 for (const auto &l : std::as_const(fr.m_data)) {
9904 if (!first) {
9905 math.push_back(Trait::latin1ToChar('\n'));
9906 }
9907
9908 math.push_back(l.first.virginSubString());
9909
9910 first = false;
9911 }
9912
9913 if (!collectRefLinks) {
9914 std::shared_ptr<Paragraph<Trait>> p(new Paragraph<Trait>);
9915 p->setStartColumn(startPos);
9916 p->setStartLine(startLine);
9917 p->setEndColumn(endPos);
9918 p->setEndLine(endLine);
9919
9920 std::shared_ptr<Math<Trait>> m(new Math<Trait>);
9921
9922 if (!fr.m_data.empty()) {
9923 m->setStartColumn(fr.m_data.front().first.virginPos(0));
9924 m->setStartLine(fr.m_data.front().second.m_lineNumber);
9925 m->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
9926 m->setEndLine(fr.m_data.back().second.m_lineNumber);
9927 } else {
9928 m->setStartColumn(emptyColumn);
9929 m->setStartLine(startLine);
9930 m->setEndColumn(emptyColumn);
9931 m->setEndLine(startLine);
9932 }
9933
9934 m->setInline(false);
9935 m->setExpr(math);
9936 m->setStartDelim(startDelim);
9937 m->setEndDelim(endDelim);
9938 m->setSyntaxPos(syntaxPos);
9939 m->setFensedCode(true);
9940 p->appendItem(m);
9941
9942 parent->appendItem(p);
9943 }
9944 } else {
9945 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, syntax, emptyColumn,
9946 startLine, true, startDelim, endDelim, syntaxPos);
9947 }
9948 }
9949
9950 return -1;
9951}
9952
9953template<class Trait>
9954inline long long int
9956 std::shared_ptr<Block<Trait>> parent,
9957 bool collectRefLinks,
9958 int indent,
9959 const typename Trait::String &syntax,
9960 long long int emptyColumn,
9961 long long int startLine,
9962 bool fensedCode,
9963 const WithPosition &startDelim,
9964 const WithPosition &endDelim,
9965 const WithPosition &syntaxPos)
9966{
9967 typename Trait::String code;
9968 long long int startPos = 0;
9969 bool first = true;
9970
9971 auto it = fr.m_data.begin(), lastIt = fr.m_data.end();
9972
9973 for (; it != lastIt; ++it) {
9974 if (it->second.m_mayBreakList) {
9975 lastIt = it;
9976 break;
9977 }
9978
9979 if (!collectRefLinks) {
9980 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9981 if (first) {
9982 startPos = ns;
9983 }
9984 first = false;
9985
9986 code.push_back((indent > 0 ? it->first.virginSubString(ns < indent ? ns : indent) +
9987 typename Trait::String(Trait::latin1ToChar('\n')) :
9988 typename Trait::String(it->first.virginSubString()) +
9989 typename Trait::String(Trait::latin1ToChar('\n'))));
9990 }
9991 }
9992
9993 if (!collectRefLinks) {
9994 if (!code.isEmpty()) {
9995 code.remove(code.length() - 1, 1);
9996 }
9997
9998 std::shared_ptr<Code<Trait>> codeItem(new Code<Trait>(code, fensedCode, false));
9999 codeItem->setSyntax(syntax);
10000 codeItem->setStartDelim(startDelim);
10001 codeItem->setEndDelim(endDelim);
10002 codeItem->setSyntaxPos(syntaxPos);
10003
10004 if (lastIt != fr.m_data.end() || (it == fr.m_data.end() && !fr.m_data.empty())) {
10005 codeItem->setStartColumn(fr.m_data.front().first.virginPos(startPos));
10006 codeItem->setStartLine(fr.m_data.front().second.m_lineNumber);
10007 auto tmp = std::prev(lastIt);
10008 codeItem->setEndColumn(tmp->first.virginPos(tmp->first.length() - 1));
10009 codeItem->setEndLine(tmp->second.m_lineNumber);
10010 } else {
10011 codeItem->setStartColumn(emptyColumn);
10012 codeItem->setStartLine(startLine);
10013 codeItem->setEndColumn(emptyColumn);
10014 codeItem->setEndLine(startLine);
10015 }
10016
10017 if (fensedCode) {
10018 parent->appendItem(codeItem);
10019 } else if (!parent->items().empty() && parent->items().back()->type() == ItemType::Code) {
10020 auto c = std::static_pointer_cast<Code<Trait>>(parent->items().back());
10021
10022 if (!c->isFensedCode()) {
10023 auto line = c->endLine();
10024 auto text = c->text();
10025
10026 for (; line < codeItem->startLine(); ++line) {
10027 text.push_back(Trait::latin1ToString("\n"));
10028 }
10029
10030 text.push_back(codeItem->text());
10031 c->setText(text);
10032 c->setEndColumn(codeItem->endColumn());
10033 c->setEndLine(codeItem->endLine());
10034 } else {
10035 parent->appendItem(codeItem);
10036 }
10037 } else {
10038 parent->appendItem(codeItem);
10039 }
10040 }
10041
10042 if (lastIt != fr.m_data.end()) {
10043 return lastIt->second.m_lineNumber;
10044 }
10045
10046 return -1;
10047}
10048
10049} /* namespace MD */
10050
10051#endif // MD4QT_MD_PARSER_HPP_INCLUDED
Abstract block (storage of child items).
Definition doc.h:604
const Items & items() const
Definition doc.h:630
Code.
Definition doc.h:1296
Document.
Definition doc.h:1801
Heading.
Definition doc.h:711
typename Trait::template Vector< WithPosition > Delims
Type of list of service chanracters.
Definition doc.h:721
Horizontal line.
Definition doc.h:365
Image.
Definition doc.h:1210
Base class for items that can have style options.
Definition doc.h:260
void setOpts(int o)
Set style options.
Definition doc.h:288
const Styles & closeStyles() const
Definition doc.h:306
const Styles & openStyles() const
Definition doc.h:294
int opts() const
Definition doc.h:282
typename Trait::template Vector< StyleDelim > Styles
Type of list of emphasis.
Definition doc.h:279
Base class for item in Markdown document.
Definition doc.h:178
virtual ItemType type() const =0
Line break.
Definition doc.h:571
Page break.
Definition doc.h:335
Paragraph.
Definition doc.h:680
void removeTextPlugin(int id)
Remove text plugin.
Definition parser.h:1516
friend struct PrivateAccess
Used in tests.
Definition parser.h:2133
~Parser()=default
void addTextPlugin(int id, TextPluginFunc< Trait > plugin, bool processInLinks, const typename Trait::StringList &userData)
Add text plugin.
Definition parser.h:1501
std::shared_ptr< Document< Trait > > parse(const typename Trait::String &fileName, bool recursive=true, const typename Trait::StringList &ext={Trait::latin1ToString("md"), Trait::latin1ToString("markdown")}, bool fullyOptimizeParagraphs=true)
Definition parser.h:2149
Raw HTML.
Definition doc.h:441
Wrapper for typename Trait::StringList to be behaved like a stream.
Definition parser.h:278
Trait::InternalString lineAt(long long int pos)
Definition parser.h:312
std::pair< typename Trait::InternalString, bool > readLine()
Definition parser.h:291
bool atEnd() const
Definition parser.h:286
void setLineNumber(long long int lineNumber)
Definition parser.h:322
long long int size() const
Definition parser.h:317
StringListStream(typename MdBlock< Trait >::Data &stream)
Definition parser.h:280
long long int currentStreamPos() const
Definition parser.h:307
long long int currentLineNumber() const
Definition parser.h:301
Emphasis in the Markdown document.
Definition doc.h:217
Table cell.
Definition doc.h:1499
Table row.
Definition doc.h:1530
Alignment
Alignment.
Definition doc.h:1637
@ AlignCenter
Center.
Definition doc.h:1643
@ AlignLeft
Left.
Definition doc.h:1639
@ AlignRight
Right.
Definition doc.h:1641
TextStream(QTextStream &stream)
Definition parser.h:2230
TextStream(std::istream &stream)
Definition parser.h:2317
Text item in Paragraph.
Definition doc.h:514
Wrapper for UChar32 to be used with MD::Parser.
Definition traits.h:465
Wrapper for icu::UnicodeString to be used with MD::Parser.
Definition traits.h:600
void push_back(const UnicodeChar &ch)
Definition traits.h:648
std::vector< UnicodeString > split(const UnicodeChar &ch) const
Definition traits.h:729
bool isRelative() const
Definition traits.h:866
UnicodeString scheme() const
Definition traits.h:871
UnicodeString host() const
Definition traits.h:876
bool isValid() const
Definition traits.h:861
Base for any thing with start and end position.
Definition doc.h:77
void setEndColumn(long long int c)
Set end column.
Definition doc.h:138
long long int startColumn() const
Definition doc.h:102
void setStartColumn(long long int c)
Set start column.
Definition doc.h:126
long long int startLine() const
Definition doc.h:108
long long int endColumn() const
Definition doc.h:114
long long int endLine() const
Definition doc.h:120
Q_SCRIPTABLE QString start(QString train="")
Q_SCRIPTABLE Q_NOREPLY void start()
Type type(const QSqlDatabase &db)
QAction * quit(const QObject *recvr, const char *slot, QObject *parent)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT CopyJob * link(const QList< QUrl > &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
QList< QVariant > parse(const QString &message, const QDateTime &externalIssueDateTime=QDateTime())
QString path(const QString &relativePath)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem open()
KGuiItem back(BidiMode useBidi=IgnoreRTL)
QString label(StandardShortcut id)
Definition algo.h:17
TextOption
Text option.
Definition doc.h:200
@ ItalicText
Italic text.
Definition doc.h:206
@ StrikethroughText
Strikethrough.
Definition doc.h:208
@ TextWithoutFormat
No format.
Definition doc.h:202
@ BoldText
Bold text.
Definition doc.h:204
bool isOrderedList(const typename Trait::String &s, int *num=nullptr, int *len=nullptr, typename Trait::Char *delim=nullptr, bool *isFirstLineEmpty=nullptr)
Definition parser.h:134
Trait::String paragraphToLabel(Paragraph< Trait > *p)
Convert Paragraph to label.
Definition parser.h:3820
std::pair< long long int, long long int > prevPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
Definition parser.h:4292
bool isValidUrl< UnicodeStringTrait >(const UnicodeString &url)
Definition parser.h:1255
bool isMult3(long long int i1, long long int i2)
Definition parser.h:7545
std::pair< long long int, long long int > nextPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
Definition parser.h:4315
bool checkForEndHtmlComments(const typename Trait::String &line, long long int pos)
Definition parser.h:2413
bool isH1(const typename Trait::String &s)
Definition parser.h:4276
bool isEmail(const typename Trait::String &url)
Definition parser.h:1130
bool isLineBreak(const typename Trait::String &s)
Definition parser.h:4582
TextOption styleToTextOption(Style s)
Definition parser.h:878
std::shared_ptr< Text< Trait > > concatenateText(typename Block< Trait >::Items::const_iterator it, typename Block< Trait >::Items::const_iterator last)
Concatenate texts in block.
Definition parser.h:8095
bool isH(const typename Trait::String &s, const typename Trait::Char &c)
Definition parser.h:4243
std::tuple< bool, long long int, typename Trait::Char, bool > listItemData(const typename Trait::String &s, bool wasText)
Definition parser.h:9166
bool isGitHubAutolink< QStringTrait >(const QString &url)
Definition parser.h:1239
bool isSemiOptimization(OptimizeParagraphType t)
Definition parser.h:8135
long long int skipSpaces(long long int i, const typename Trait::String &line)
Skip spaces in line from position i.
Definition parser.h:71
Trait::InternalString prepareTableData(typename Trait::InternalString s)
Prepare data in table cell for parsing.
Definition parser.h:4035
void makeTextObject(const typename Trait::String &text, TextParsingOpts< Trait > &po, long long int startPos, long long int startLine, long long int endPos, long long int endLine, bool doRemoveSpacesAtEnd=false)
Make text item.
Definition parser.h:4629
static const Trait::String s_canBeEscaped
Characters that can be escaped.
Definition parser.h:469
int isTableHeader(const typename Trait::String &s)
Definition parser.h:3704
void initLastItemWithOpts(TextParsingOpts< Trait > &po, std::shared_ptr< ItemWithOpts< Trait > > item)
Initialize item with style information and set it as last item.
Definition parser.h:4618
std::tuple< long long int, long long int, bool, typename Trait::String, long long int > readLinkDestination(long long int line, long long int pos, const TextParsingOpts< Trait > &po, WithPosition *urlPos=nullptr)
Read link's destination.
Definition parser.h:6804
Trait::StringList splitString(const typename Trait::String &str, const typename Trait::Char &ch)
Split string.
void removeSpacesAtEnd(String &s)
Remove spaces at the end of string s.
Definition parser.h:99
bool isListItemAndNotNested(const typename Trait::String &s, long long int indent)
Definition parser.h:9123
std::pair< bool, bool > readUnquotedHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
Definition parser.h:4894
static const char * s_startComment
Starting HTML comment string.
Definition parser.h:47
long long int processGitHubAutolinkExtension(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, long long int idx)
Process GitHub autolinks for the text with index idx.
Definition parser.h:1279
void setLastPos(std::shared_ptr< Item< Trait > > item, long long int pos, long long int line)
Set last position of the item.
Definition parser.h:9216
long long int lastNonSpacePos(const String &line)
Definition parser.h:85
std::shared_ptr< Paragraph< Trait > > optimizeParagraph(std::shared_ptr< Paragraph< Trait > > &p, TextParsingOpts< Trait > &po, OptimizeParagraphType type=OptimizeParagraphType::Full)
Optimize Paragraph.
Definition parser.h:8164
WithPosition findAndRemoveClosingSequence(typename Trait::InternalString &s)
Find and remove closing sequence of "#" in heading.
Definition parser.h:3875
std::shared_ptr< Paragraph< Trait > > splitParagraphsAndFreeHtml(std::shared_ptr< Block< Trait > > parent, std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, bool collectRefLinks, bool fullyOptimizeParagraphs=true)
Split Paragraph and free HTML.
Definition parser.h:8278
bool isFootnote(const typename Trait::String &s)
Definition parser.h:337
void githubAutolinkPlugin(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const typename Trait::StringList &)
GitHub autolinks plugin.
Definition parser.h:1411
void replaceTabs(typename Trait::InternalString &s)
Replace tabs with spaces (just for internal simpler use).
Definition parser.h:2560
bool isCodeFences(const typename Trait::String &s, bool closing=false)
Definition parser.h:377
long long int lineBreakLength(const typename Trait::String &s)
Definition parser.h:4598
bool indentInList(const std::vector< long long int > *indents, long long int indent, bool codeIndentedBySpaces)
Definition parser.h:51
std::pair< long long int, long long int > localPosFromVirgin(const MdBlock< Trait > &fr, long long int virginColumn, long long int virginLine)
Definition parser.h:1093
OptimizeParagraphType
Type of the paragraph's optimization.
Definition parser.h:830
@ Semi
Semi optimization, optimization won't concatenate text items if style delimiters will be in the middl...
Definition parser.h:835
@ SemiWithoutRawData
Semi optimization, but raw text data won't be concatenated (will be untouched).
Definition parser.h:839
@ Full
Full optimization.
Definition parser.h:832
@ FullWithoutRawData
Full optimization, but raw text data won't be concatenated (will be untouched).
Definition parser.h:837
std::shared_ptr< Paragraph< Trait > > makeParagraph(typename Block< Trait >::Items::const_iterator first, typename Block< Trait >::Items::const_iterator last)
Make Paragraph.
Definition parser.h:8258
bool isH2(const typename Trait::String &s)
Definition parser.h:4284
Trait::String readEscapedSequence(long long int i, const typename Trait::String &str, long long int *endPos=nullptr)
Skip escaped sequence of characters till first space.
Definition parser.h:432
std::function< void(std::shared_ptr< Paragraph< Trait > >, TextParsingOpts< Trait > &, const typename Trait::StringList &)> TextPluginFunc
Functor type for text plugin.
Definition parser.h:906
bool isGitHubAutolink< UnicodeStringTrait >(const UnicodeString &url)
Definition parser.h:1264
void normalizePos(long long int &pos, long long int &line, long long int length, long long int linesCount)
Normalize position.
Definition parser.h:8244
std::pair< bool, bool > readHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
Definition parser.h:4918
std::pair< long long int, long long int > calculateIndent(const typename Trait::String &s, long long int p)
Definition parser.h:9157
void makeHorLine(const typename MdBlock< Trait >::Line &line, std::shared_ptr< Block< Trait > > parent)
Make horizontal line.
Definition parser.h:8605
long long int emptyLinesBeforeCount(typename MdBlock< Trait >::Data::iterator begin, typename MdBlock< Trait >::Data::iterator it)
Definition parser.h:251
void makeText(long long int lastLine, long long int lastPos, TextParsingOpts< Trait > &po)
Make text item.
Definition parser.h:4762
std::tuple< bool, long long int, long long int, bool, typename Trait::String > isHtmlTag(long long int line, long long int pos, TextParsingOpts< Trait > &po, int rule)
Definition parser.h:5147
void resetHtmlTag(RawHtmlBlock< Trait > &html, TextParsingOpts< Trait > *po=nullptr)
Reset pre-stored HTML.
Definition parser.h:1006
bool isStartOfCode(const typename Trait::String &str, typename Trait::String *syntax=nullptr, WithPosition *delim=nullptr, WithPosition *syntaxPos=nullptr)
Definition parser.h:503
Trait::String stringToLabel(const typename Trait::String &s)
Convert string to label.
Definition parser.h:3799
UnicodeStringTrait::StringList splitString< UnicodeStringTrait >(const UnicodeString &str, const UnicodeChar &ch)
Definition parser.h:665
int isTableAlignment(const typename Trait::String &s)
Definition parser.h:686
bool isColumnAlignment(const typename Trait::String &s)
Definition parser.h:613
void makeHeading(std::shared_ptr< Block< Trait > > parent, std::shared_ptr< Document< Trait > > doc, std::shared_ptr< Paragraph< Trait > > p, long long int lastColumn, long long int lastLine, int level, const typename Trait::String &workingPath, const typename Trait::String &fileName, bool collectRefLinks, const WithPosition &delim, TextParsingOpts< Trait > &po)
Make heading.
Definition parser.h:8374
void checkForTableInParagraph(TextParsingOpts< Trait > &po, long long int lastLine)
Check for table in paragraph.
Definition parser.h:4732
std::tuple< long long int, long long int, bool, typename Trait::String, long long int > readLinkTitle(long long int line, long long int pos, const TextParsingOpts< Trait > &po)
Read link's title.
Definition parser.h:6921
bool isOnlyHtmlTagsAfterOrClosedRule1(long long int line, long long int pos, TextParsingOpts< Trait > &po, int rule)
Definition parser.h:5075
void checkForTextPlugins(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const TextPluginsMap< Trait > &textPlugins, bool inLink)
Process text plugins.
Definition parser.h:8587
Style
Emphasis type.
Definition parser.h:861
@ Bold2
"__"
Definition parser.h:869
@ Unknown
Unknown.
Definition parser.h:873
@ Strikethrough
"~"
Definition parser.h:871
@ Bold1
"**"
Definition parser.h:867
@ Italic1
"*"
Definition parser.h:863
@ Italic2
"_"
Definition parser.h:865
Trait::String virginSubstr(const MdBlock< Trait > &fr, const WithPosition &virginPos)
Definition parser.h:1026
std::map< int, std::tuple< TextPluginFunc< Trait >, bool, typename Trait::StringList > > TextPluginsMap
Type of the map of text plugins.
Definition parser.h:916
void eatRawHtml(long long int line, long long int pos, long long int toLine, long long int toPos, TextParsingOpts< Trait > &po, bool finish, int htmlRule, bool onLine, bool continueEating=false)
Read HTML data.
Definition parser.h:5353
void skipSpacesInHtml(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Skip spaces.
Definition parser.h:4875
@ Document
Document.
Definition doc.h:56
@ FootnoteRef
Footnote ref.
Definition doc.h:52
@ Table
Table.
Definition doc.h:50
@ PageBreak
Page break.
Definition doc.h:58
@ Link
Link.
Definition doc.h:40
@ Text
Text.
Definition doc.h:28
@ Anchor
Anchor.
Definition doc.h:60
@ Math
Math expression.
Definition doc.h:66
@ ListItem
List item.
Definition doc.h:36
@ Image
Image.
Definition doc.h:42
@ Code
Code.
Definition doc.h:44
@ RawHtml
Raw HTML.
Definition doc.h:64
@ LineBreak
Line break.
Definition doc.h:32
@ Paragraph
Paragraph.
Definition doc.h:30
void appendCloseStyle(TextParsingOpts< Trait > &po, const StyleDelim &s)
Append close style.
Definition parser.h:7856
void makeTextObjectWithLineBreak(const typename Trait::String &text, TextParsingOpts< Trait > &po, long long int startPos, long long int startLine, long long int endPos, long long int endLine)
Make text item with line break.
Definition parser.h:4699
std::pair< typename Trait::InternalStringList, std::vector< long long int > > splitTableRow(const typename Trait::InternalString &s)
Split table's row on cells.
Definition parser.h:4045
bool isSetextHeadingBetween(const TextParsingOpts< Trait > &po, long long int startLine, long long int endLine)
Definition parser.h:5128
long long int textAtIdx(std::shared_ptr< Paragraph< Trait > > p, size_t idx)
Definition parser.h:8566
long long int listLevel(const std::vector< long long int > &indents, long long int pos)
Definition parser.h:3478
long long int posOfListItem(const typename Trait::String &s, bool ordered)
Definition parser.h:3436
bool isGitHubAutolink(const typename Trait::String &url)
bool isHorizontalLine(const typename Trait::String &s)
Definition parser.h:570
bool isValidUrl(const typename Trait::String &url)
Trait::String startSequence(const typename Trait::String &line)
Definition parser.h:111
bool isValidUrl< QStringTrait >(const QString &url)
Definition parser.h:1230
TextPlugin
ID of text plugin.
Definition parser.h:847
@ UnknownPluginID
Unknown plugin.
Definition parser.h:849
@ UserDefinedPluginID
First user defined plugin ID.
Definition parser.h:853
@ GitHubAutoLinkPluginID
GitHub's autolinks plugin.
Definition parser.h:851
void applyStyles(int &opts, std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles)
Apply styles.
Definition parser.h:7422
long long int lastVirginPositionInParagraph(Item< Trait > *item)
Definition parser.h:8334
void skipSpacesUpTo1Line(long long int &line, long long int &pos, const typename MdBlock< Trait >::Data &fr)
Skip space in the block up to 1 new line.
Definition parser.h:6789
bool isHtmlComment(const typename Trait::String &s)
Definition parser.h:702
void resolveLinks(typename Trait::StringList &linksToParse, std::shared_ptr< Document< Trait > > doc)
Resolve links in the document.
Definition parser.h:3335
QStringTrait::StringList splitString< QStringTrait >(const QString &str, const QChar &ch)
Definition parser.h:676
bool isWithoutRawDataOptimization(OptimizeParagraphType t)
Definition parser.h:8149
Trait::String replaceEntity(const typename Trait::String &s)
Replace entities in the string with corresponding character.
Definition parser.h:735
static const std::map< typename Trait::String, const char16_t * > s_entityMap
String removeBackslashes(const String &s)
Remove backslashes from the string.
Definition parser.h:475
Trait::String removeLineBreak(const typename Trait::String &s)
Remove line break from the end of string.
Definition parser.h:4606
void updateLastPosInList(const RawHtmlBlock< Trait > &html)
Update last position of all parent.
Definition parser.h:9227
void closeStyle(std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles, Style s)
Close style.
Definition parser.h:7407
std::pair< typename Trait::String, WithPosition > findAndRemoveHeaderLabel(typename Trait::InternalString &s)
Find and remove heading label.
Definition parser.h:3769
std::pair< bool, bool > readHtmlAttr(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr, bool checkForSpace)
Read HTML attribute.
Definition parser.h:4975
void checkForHtmlComments(const typename Trait::InternalString &line, StringListStream< Trait > &stream, MdLineData::CommentDataMap &res)
Collect information about HTML comments.
Definition parser.h:2428
bool isEmpty() const const
void push_back(parameter_type value)
void clear()
QString first(qsizetype n) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
void push_back(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString sliced(qsizetype pos) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toCaseFolded() const const
QString toLower() const const
QString toUpper() const const
const QChar * unicode() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype size() const const
SkipEmptyParts
QString host(ComponentFormattingOptions options) const const
bool isRelative() const const
bool isValid() const const
QString scheme() const const
Internal structure for block of text in Markdown.
Definition parser.h:240
Data m_data
Definition parser.h:244
typename Trait::template Vector< Line > Data
Definition parser.h:242
std::pair< typename Trait::InternalString, MdLineData > Line
Definition parser.h:241
long long int m_emptyLinesBefore
Definition parser.h:245
bool m_emptyLineAfter
Definition parser.h:246
Internal structure for auxiliary information about a line in Markdown.
Definition parser.h:224
long long int m_lineNumber
Definition parser.h:225
std::pair< char, bool > CommentData
Definition parser.h:226
bool m_mayBreakList
Definition parser.h:231
std::map< long long int, CommentData > CommentDataMap
Definition parser.h:227
CommentDataMap m_htmlCommentData
Definition parser.h:229
Trait to use this library with QString.
Definition traits.h:1017
QStringList StringList
Definition traits.h:1034
Internal structure for pre-storing HTML.
Definition parser.h:195
int m_htmlBlockType
Definition parser.h:202
std::unordered_map< std::shared_ptr< Block< Trait > >, SequenceOfBlock > m_toAdjustLastPos
Definition parser.h:201
SequenceOfBlock m_blocks
Definition parser.h:200
std::vector< std::pair< std::shared_ptr< Block< Trait > >, long long int > > SequenceOfBlock
Definition parser.h:199
std::shared_ptr< RawHtml< Trait > > m_html
Definition parser.h:196
std::shared_ptr< Block< Trait > > findParent(long long int indent) const
Definition parser.h:207
bool m_continueHtml
Definition parser.h:203
std::shared_ptr< Block< Trait > > m_topParent
Definition parser.h:198
std::shared_ptr< Block< Trait > > m_parent
Definition parser.h:197
Internal structure for auxiliary options for parser.
Definition parser.h:926
bool shouldStopParsing() const
Definition parser.h:970
RawHtmlBlock< Trait > & m_html
Definition parser.h:936
std::shared_ptr< Document< Trait > > m_doc
Definition parser.h:930
long long int m_pos
Definition parser.h:986
bool m_checkLineOnNewType
Definition parser.h:940
ItemWithOpts< Trait >::Styles m_openStyles
Definition parser.h:1000
void concatenateAuxText(long long int start, long long int end)
Definition parser.h:954
Trait::StringList & m_linksToParse
Definition parser.h:931
bool m_firstInParagraph
Definition parser.h:942
long long int m_lastTextPos
Definition parser.h:989
long long int m_line
Definition parser.h:985
Trait::String m_fileName
Definition parser.h:933
long long int m_startTableLine
Definition parser.h:987
std::shared_ptr< ItemWithOpts< Trait > > m_lastItemWithStyle
Definition parser.h:1001
std::shared_ptr< Block< Trait > > m_parent
Definition parser.h:928
std::shared_ptr< RawHtml< Trait > > m_tmpHtml
Definition parser.h:929
std::shared_ptr< Text< Trait > > m_lastText
Definition parser.h:938
const TextPluginsMap< Trait > & m_textPlugins
Definition parser.h:937
MdBlock< Trait > & m_fr
Definition parser.h:927
Trait::String m_workingPath
Definition parser.h:932
Detected m_detected
Definition parser.h:967
std::vector< StyleInfo > m_styles
Definition parser.h:999
std::vector< TextData > m_rawTextData
Definition parser.h:951
long long int m_lastTextLine
Definition parser.h:988
Trait to use this library with std::string.
Definition traits.h:893
static String latin1ToString(const char *latin1)
Convert Latin1 into trait's string.
Definition traits.h:935
UnicodeString String
Definition traits.h:900
std::vector< String > StringList
Definition traits.h:908
static bool fileExists(const String &fileName, const String &workingPath)
Definition traits.h:953
static bool isFreeTag(std::shared_ptr< RawHtml< Trait > > html)
Definition parser.h:4352
static void setFreeTag(std::shared_ptr< RawHtml< Trait > > html, bool on)
Definition parser.h:4358
#define MD_DISABLE_COPY(Class)
Macro for disabling copy.
Definition utils.h:17
#define MD_UNUSED(x)
Avoid "unused parameter" warnings.
Definition utils.h:26
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:47:13 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.