6#ifndef MD4QT_MD_PARSER_HPP_INCLUDED
7#define MD4QT_MD_PARSER_HPP_INCLUDED
15#ifdef MD4QT_QT_SUPPORT
24#ifdef MD4QT_ICU_STL_SUPPORT
40#include <unordered_map>
53 bool codeIndentedBySpaces)
55 if (indents && !indents->empty()) {
56 return (std::find_if(indents->cbegin(),
58 [indent, codeIndentedBySpaces](
const auto &v) {
59 return (indent >= v && (codeIndentedBySpaces ?
60 true : indent <= v + 3));
71skipSpaces(
long long int i,
const typename Trait::String &line)
73 const auto length = line.length();
75 while (i < length && line[i].isSpace()) {
87 long long int i = line.length() - 1;
89 while (i >= 0 && line[i].isSpace()) {
103 if (i != s.length() - 1) {
104 s.remove(i + 1, s.length() - i - 1);
110inline typename Trait::String
115 if (pos >= line.length()) {
119 const auto sch = line[pos];
120 const auto start = pos;
124 while (pos < line.length() && line[pos] == sch) {
137 typename Trait::Char *delim =
nullptr,
138 bool *isFirstLineEmpty =
nullptr)
142 long long int dp = p;
144 for (; p < s.size(); ++p) {
145 if (!s[p].isDigit()) {
150 if (dp != p && p < s.size()) {
151 const auto digits = s.sliced(dp, p - dp);
153 if (digits.size() > 9) {
157 const auto i = digits.toInt();
167 if (s[p] == Trait::latin1ToChar(
'.') || s[p] == Trait::latin1ToChar(
')')) {
176 if (isFirstLineEmpty) {
177 *isFirstLineEmpty = (tmp == s.size());
180 if ((p < s.size() && s[p] == Trait::latin1ToChar(
' ')) || p == s.size()) {
196 std::shared_ptr<RawHtml<Trait>>
m_html = {};
199 using SequenceOfBlock = std::vector<std::pair<std::shared_ptr<Block<Trait>>,
long long int>>;
206 std::shared_ptr<Block<Trait>>
209 for (
auto it =
m_blocks.crbegin(), last =
m_blocks.crend(); it != last; ++it) {
210 if (indent >= it->second) {
241 using Line = std::pair<typename Trait::InternalString, MdLineData>;
242 using Data =
typename Trait::template Vector<Line>;
254 long long int count = 0;
257 while (it != begin) {
260 if (it->first.asString().simplified().isEmpty()) {
288 return (m_pos >= (
long long int)m_stream.size());
291 std::pair<typename Trait::InternalString, bool>
readLine()
293 const std::pair<typename Trait::InternalString, bool> ret =
294 {m_stream.at(m_pos).first, m_stream.at(m_pos).second.m_mayBreakList};
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));
312 typename Trait::InternalString
lineAt(
long long int pos)
314 return m_stream.at(pos).first;
319 return m_stream.size();
341 if (s.size() - p < 5) {
345 if (s[p++] != Trait::latin1ToChar(
'[')) {
349 if (s[p++] != Trait::latin1ToChar(
'^')) {
353 if (s[p] == Trait::latin1ToChar(
']') || s[p].isSpace()) {
357 for (; p < s.size(); ++p) {
358 if (s[p] == Trait::latin1ToChar(
']')) {
360 }
else if (s[p].isSpace()) {
367 if (p < s.size() && s[p] == Trait::latin1ToChar(
':')) {
381 if (p > 3 || p == s.length()) {
385 const auto ch = s[p];
387 if (ch != Trait::latin1ToChar(
'~') && ch != Trait::latin1ToChar(
'`')) {
396 for (; p < s.length(); ++p) {
397 if (s[p].isSpace()) {
399 }
else if (s[p] == ch) {
400 if (space && (closing ?
true : ch == Trait::latin1ToChar(
'`'))) {
407 }
else if (closing) {
418 if (ch == Trait::latin1ToChar(
'`')) {
419 for (; p < s.length(); ++p) {
420 if (s[p] == Trait::latin1ToChar(
'`')) {
431inline typename Trait::String
433 const typename Trait::String &str,
434 long long int *endPos =
nullptr)
436 bool backslash =
false;
437 const auto start = i;
439 if (
start >= str.length()) {
443 while (i < str.length()) {
446 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
449 }
else if (str[i].isSpace() && !backslash) {
470 Trait::latin1ToString(
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
473template<
class String,
class Trait>
478 bool backslash =
false;
479 long long int extra = 0;
481 for (
long long int i = 0; i < s.length(); ++i) {
484 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash && i != s.length() - 1) {
488 r.remove(i - extra - 1, 1);
504 typename Trait::String *syntax =
nullptr,
511 delim->setStartColumn(p);
518 if (str.size() - p < 3) {
522 const bool c96 = str[p] == Trait::latin1ToChar(
'`');
523 const bool c126 = str[p] == Trait::latin1ToChar(
'~');
529 while (p < str.length()) {
530 if (str[p] != (c96 ? Trait::latin1ToChar(
'`') : Trait::latin1ToChar(
'~'))) {
539 delim->setEndColumn(p - 1);
548 long long int endSyntaxPos = p;
550 if (p < str.size()) {
555 syntaxPos->setStartColumn(p);
556 syntaxPos->setEndColumn(endSyntaxPos);
576 typename Trait::Char c;
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(
'_');
589 long long int count = 1;
591 for (; p < s.size(); ++p) {
592 if (s[p] != c && !s[p].isSpace()) {
594 }
else if (s[p] == c) {
617 static const typename Trait::String s_legitime = Trait::latin1ToString(
":-");
619 if (p >= s.length()) {
623 if (!s_legitime.contains(s[p])) {
627 if (s[p] == Trait::latin1ToChar(
':')) {
631 for (; p < s.size(); ++p) {
632 if (s[p] != Trait::latin1ToChar(
'-')) {
641 if (s[p] != Trait::latin1ToChar(
':') && !s[p].isSpace()) {
647 for (; p < s.size(); ++p) {
648 if (!s[p].isSpace()) {
658typename Trait::StringList
659splitString(
const typename Trait::String &str,
const typename Trait::Char &ch);
661#ifdef MD4QT_ICU_STL_SUPPORT
667 return str.
split(ch);
672#ifdef MD4QT_QT_SUPPORT
690 for (
const auto &c : columns) {
696 return columns.size();
712 long long int p = -1;
713 bool endFound =
false;
715 while ((p = c.indexOf(Trait::latin1ToString(
"--"), p + 1)) > -1) {
716 if (c.size() > p + 2 && c[p + 2] == Trait::latin1ToChar(
'>')) {
722 }
else if (p - 2 >= 0 && c.sliced(p - 2, 4) == Trait::latin1ToString(
"<!--")) {
724 }
else if (c.size() > p + 3 && c.sliced(p, 4) == Trait::latin1ToString(
"--!>")) {
734inline typename Trait::String
737 long long int p1 = 0;
739 typename Trait::String res;
742 while ((p1 = s.indexOf(Trait::latin1ToChar(
'&'), p1)) != -1) {
743 if (p1 > 0 && s[p1 - 1] == Trait::latin1ToChar(
'\\')) {
749 const auto p2 = s.indexOf(Trait::latin1ToChar(
';'), p1);
752 const auto en = s.sliced(p1, p2 - p1 + 1);
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);
758 if (hex.size() <= 6 && hex.size() > 0) {
761 const char32_t c = hex.toInt(&ok, 16);
764 res.push_back(s.sliced(i, p1 - i));
768 Trait::appendUcs4(res, c);
770 res.push_back(
typename Trait::Char(0xFFFD));
775 const auto dec = en.sliced(2, en.size() - 3);
777 if (dec.size() <= 7 && dec.size() > 0) {
780 const char32_t c = dec.toInt(&ok, 10);
783 res.push_back(s.sliced(i, p1 - i));
787 Trait::appendUcs4(res, c);
789 res.push_back(
typename Trait::Char(0xFFFD));
798 res.push_back(s.sliced(i, p1 - i));
800 res.push_back(Trait::utf16ToString(it->second));
810 res.push_back(s.sliced(i, s.size() - i));
822 for (
auto &line : tmp) {
902struct TextParsingOpts;
908 const typename Trait::StringList &)>;
918 typename Trait::StringList>>;
930 std::shared_ptr<Document<Trait>>
m_doc;
957 for (
auto i =
start + 1; i < end; ++i) {
1005template<
class Trait>
1024template<
class Trait>
1025inline typename Trait::String
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) :
1036 if (startLine >=
static_cast<long long int>(fr.
m_data.size()) || startLine < 0) {
1040 auto spos = virginPos.
startColumn() - fr.
m_data.at(startLine).first.virginPos(0);
1046 long long int epos = 0;
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();
1055 epos = virginPos.
endColumn() - fr.
m_data.at(linesCount + startLine).first.virginPos(0) + 1;
1062 if (epos > fr.
m_data.at(linesCount + startLine).first.length()) {
1063 epos = fr.
m_data.at(linesCount + startLine).first.length();
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());
1070 long long int i = startLine + 1;
1072 for (; i < startLine + linesCount; ++i) {
1073 str.push_back(Trait::latin1ToString(
"\n"));
1074 str.push_back(fr.
m_data.at(i).first.asString());
1078 str.push_back(Trait::latin1ToString(
"\n"));
1079 str.push_back(fr.
m_data.at(i).first.sliced(0, epos).asString());
1091template<
class Trait>
1092inline std::pair<long long int, long long int>
1099 if (fr.
m_data.front().second.m_lineNumber > virginLine ||
1100 fr.
m_data.back().second.m_lineNumber < virginLine) {
1104 auto line = virginLine - fr.
m_data.front().second.m_lineNumber;
1106 if (fr.
m_data.at(line).first.isEmpty()) {
1110 const auto vzpos = fr.
m_data.at(line).first.virginPos(0);
1112 if (vzpos > virginColumn || virginColumn > vzpos + fr.
m_data.at(line).first.length() - 1) {
1116 return {virginColumn - vzpos, line};
1128template<
class Trait>
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));
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));
1146 static const auto s_delim = Trait::latin1ToChar(
'-');
1147 static const auto s_dog = Trait::latin1ToChar(
'@');
1148 static const auto s_dot = Trait::latin1ToChar(
'.');
1150 long long int i = (url.startsWith(Trait::latin1ToString(
"mailto:")) ? 7 : 0);
1151 const auto dogPos = url.indexOf(s_dog, i);
1158 for (; i < dogPos; ++i) {
1159 if (!isAllowed(url[i]) && !isAdditional(url[i])) {
1164 auto checkToDot = [&](
long long int start,
long long int dotPos) ->
bool {
1165 static const long long int maxlen = 63;
1167 if (dotPos -
start > maxlen ||
1168 start + 1 > dotPos ||
1169 start >= url.length() ||
1170 dotPos > url.length()) {
1174 if (url[
start] == s_delim) {
1178 if (url[dotPos - 1] == s_delim) {
1183 if (!isAllowed(url[
start]) && url[
start] != s_delim) {
1191 long long int dotPos = url.indexOf(s_dot, dogPos + 1);
1196 while (dotPos != -1) {
1197 if (!checkToDot(i, dotPos)) {
1202 dotPos = url.indexOf(s_dot, i);
1205 if (!checkToDot(i, url.length())) {
1217template<
class Trait>
1222template<
class Trait>
1226#ifdef MD4QT_QT_SUPPORT
1251#ifdef MD4QT_ICU_STL_SUPPORT
1269 && ((!u.
scheme().isEmpty() && !u.
host().isEmpty())
1270 || (url.startsWith(
UnicodeString(
"www.")) && url.length() >= 7 &&
1277template<
class Trait>
1283 if (idx < 0 || idx >= (
long long int)po.
m_rawTextData.size()) {
1287 static const auto s_delims = Trait::latin1ToString(
"*_~()<>");
1290 long long int j = 0;
1291 auto end =
typename Trait::Char(0x00);
1292 bool skipSpace =
true;
1293 long long int ret = idx;
1295 while (s.m_str.length()) {
1296 long long int i = 0;
1297 end =
typename Trait::Char(0x00);
1299 for (; i < s.m_str.length(); ++i) {
1301 if (s.m_str[i] == Trait::latin1ToChar(
'(')) {
1302 end = Trait::latin1ToChar(
')');
1305 if (s_delims.indexOf(s.m_str[i]) == -1 && !s.m_str[i].isSpace()) {
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() ?
1314 skipSpace = s.m_str[i].isSpace();
1321 if (ti >= 0 && ti <
static_cast<long long int>(p->items().size())) {
1323 const auto opts = std::static_pointer_cast<Text<Trait>>(p->items().at(ti))->opts();
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);
1332 const auto tmp = s.m_str.sliced(0, j);
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));
1337 t->closeStyles() = {};
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);
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() ?
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());
1356 if (email && !tmp.toLower().startsWith(Trait::latin1ToString(
"mailto:"))) {
1357 tmp = Trait::latin1ToString(
"mailto:") + tmp;
1360 if (!email && tmp.toLower().startsWith(Trait::latin1ToString(
"www."))) {
1361 tmp = Trait::latin1ToString(
"http://") + tmp;
1366 p->insertItem(ti, lnk);
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));
1373 if (!s.m_str.isEmpty()) {
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);
1386 lnk->closeStyles() = closeStyles;
1393 j = i + (skipSpace ? 1 : 0);
1400 if (i == s.m_str.length()) {
1409template<
class Trait>
1413 const typename Trait::StringList &)
1416 long long int i = 0;
1418 while (i >= 0 && i < (
long long int)po.
m_rawTextData.size()) {
1431template<
class Trait>
1443 std::shared_ptr<Document<Trait>>
1446 const typename Trait::String &fileName,
1449 bool recursive =
true,
1452 const typename Trait::StringList &ext = {Trait::latin1ToString(
"md"), Trait::latin1ToString(
"markdown")},
1458 bool fullyOptimizeParagraphs =
true);
1461 std::shared_ptr<Document<Trait>>
1464 typename Trait::TextStream &stream,
1467 const typename Trait::String &path,
1469 const typename Trait::String &fileName,
1475 bool fullyOptimizeParagraphs =
true);
1478 std::shared_ptr<Document<Trait>>
1481 const typename Trait::String &fileName,
1485 const typename Trait::String &workingDirectory,
1488 bool recursive =
true,
1491 const typename Trait::StringList &ext = {Trait::latin1ToString(
"md"), Trait::latin1ToString(
"markdown")},
1497 bool fullyOptimizeParagraphs =
true);
1507 bool processInLinks,
1509 const typename Trait::StringList &userData)
1511 m_textPlugins.insert({id, {plugin, processInLinks, userData}});
1520 m_textPlugins.erase(
id);
1525 parseFile(
const typename Trait::String &fileName,
1528 const typename Trait::StringList &ext,
1529 typename Trait::StringList *parentLinks =
nullptr,
1530 typename Trait::String workingDirectory = {});
1533 parseStream(
typename Trait::TextStream &stream,
1534 const typename Trait::String &workingPath,
1535 const typename Trait::String &fileName,
1538 const typename Trait::StringList &ext,
1539 typename Trait::StringList *parentLinks =
nullptr,
1540 const typename Trait::String &workingDirectory = {});
1545 enum class BlockType {
1550 ListWithFirstEmptyLine,
1551 CodeIndentedBySpaces,
1561 long long int m_level = -1;
1562 long long int m_indent = -1;
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);
1577 parseFragment(MdBlock<Trait> &fr,
1578 std::shared_ptr<Block<Trait>> parent,
1580 typename Trait::StringList &linksToParse,
1581 const typename Trait::String &workingPath,
1582 const typename Trait::String &fileName,
1583 bool collectRefLinks,
1584 RawHtmlBlock<Trait> &html);
1587 parseText(MdBlock<Trait> &fr,
1588 std::shared_ptr<Block<Trait>> parent,
1590 typename Trait::StringList &linksToParse,
1591 const typename Trait::String &workingPath,
1592 const typename Trait::String &fileName,
1593 bool collectRefLinks,
1594 RawHtmlBlock<Trait> &html);
1597 parseBlockquote(MdBlock<Trait> &fr,
1598 std::shared_ptr<Block<Trait>> parent,
1600 typename Trait::StringList &linksToParse,
1601 const typename Trait::String &workingPath,
1602 const typename Trait::String &fileName,
1603 bool collectRefLinks,
1604 RawHtmlBlock<Trait> &html);
1607 parseList(MdBlock<Trait> &fr,
1608 std::shared_ptr<Block<Trait>> parent,
1610 typename Trait::StringList &linksToParse,
1611 const typename Trait::String &workingPath,
1612 const typename Trait::String &fileName,
1613 bool collectRefLinks,
1614 RawHtmlBlock<Trait> &html);
1617 parseCode(MdBlock<Trait> &fr,
1618 std::shared_ptr<Block<Trait>> parent,
1619 bool collectRefLinks);
1622 parseCodeIndentedBySpaces(MdBlock<Trait> &fr,
1623 std::shared_ptr<Block<Trait>> parent,
1624 bool collectRefLinks,
1626 const typename Trait::String &syntax,
1627 long long int emptyColumn,
1628 long long int startLine,
1630 const WithPosition &startDelim = {},
1631 const WithPosition &endDelim = {},
1632 const WithPosition &syntaxPos = {});
1635 parseListItem(MdBlock<Trait> &fr,
1636 std::shared_ptr<Block<Trait>> parent,
1638 typename Trait::StringList &linksToParse,
1639 const typename Trait::String &workingPath,
1640 const typename Trait::String &fileName,
1641 bool collectRefLinks,
1642 RawHtmlBlock<Trait> &html,
1646 parseHeading(MdBlock<Trait> &fr,
1647 std::shared_ptr<Block<Trait>> parent,
1649 typename Trait::StringList &linksToParse,
1650 const typename Trait::String &workingPath,
1651 const typename Trait::String &fileName,
1652 bool collectRefLinks);
1655 parseFootnote(MdBlock<Trait> &fr,
1656 std::shared_ptr<Block<Trait>> parent,
1658 typename Trait::StringList &linksToParse,
1659 const typename Trait::String &workingPath,
1660 const typename Trait::String &fileName,
1661 bool collectRefLinks);
1664 parseTable(MdBlock<Trait> &fr,
1665 std::shared_ptr<Block<Trait>> parent,
1667 typename Trait::StringList &linksToParse,
1668 const typename Trait::String &workingPath,
1669 const typename Trait::String &fileName,
1670 bool collectRefLinks,
1674 parseParagraph(MdBlock<Trait> &fr,
1675 std::shared_ptr<Block<Trait>> parent,
1677 typename Trait::StringList &linksToParse,
1678 const typename Trait::String &workingPath,
1679 const typename Trait::String &fileName,
1680 bool collectRefLinks,
1681 RawHtmlBlock<Trait> &html);
1684 parseFormattedTextLinksImages(MdBlock<Trait> &fr,
1685 std::shared_ptr<Block<Trait>> parent,
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,
1695 struct ParserContext {
1696 typename Trait::template Vector<MdBlock<Trait>> m_splitted;
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;
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;
1714 std::pair<long long int, bool>
1715 parseFirstStep(ParserContext &ctx,
1716 StringListStream<Trait> &stream,
1717 std::shared_ptr<Block<Trait>> parent,
1719 typename Trait::StringList &linksToParse,
1720 const typename Trait::String &workingPath,
1721 const typename Trait::String &fileName,
1722 bool collectRefLinks);
1725 parseSecondStep(ParserContext &ctx,
1726 std::shared_ptr<Block<Trait>> parent,
1728 typename Trait::StringList &linksToParse,
1729 const typename Trait::String &workingPath,
1730 const typename Trait::String &fileName,
1731 bool collectRefLinks,
1733 bool dontProcessLastFreeHtml);
1735 std::pair<RawHtmlBlock<Trait>,
long long int>
1736 parse(StringListStream<Trait> &stream,
1737 std::shared_ptr<Block<Trait>> parent,
1739 typename Trait::StringList &linksToParse,
1740 const typename Trait::String &workingPath,
1741 const typename Trait::String &fileName,
1742 bool collectRefLinks,
1744 bool dontProcessLastFreeHtml =
false,
1745 bool stopOnMayBreakList =
false);
1747 std::pair<long long int, bool>
1748 parseFragment(ParserContext &ctx,
1749 std::shared_ptr<Block<Trait>> parent,
1751 typename Trait::StringList &linksToParse,
1752 const typename Trait::String &workingPath,
1753 const typename Trait::String &fileName,
1754 bool collectRefLinks);
1757 eatFootnote(ParserContext &ctx,
1758 StringListStream<Trait> &stream,
1759 std::shared_ptr<Block<Trait>> parent,
1761 typename Trait::StringList &linksToParse,
1762 const typename Trait::String &workingPath,
1763 const typename Trait::String &fileName,
1764 bool collectRefLinks);
1767 finishHtml(ParserContext &ctx,
1768 std::shared_ptr<Block<Trait>> parent,
1770 bool collectRefLinks,
1772 bool dontProcessLastFreeHtml);
1775 makeLineMain(ParserContext &ctx,
1776 const typename Trait::InternalString &line,
1777 long long int emptyLinesCount,
1778 const ListIndent ¤tIndent,
1780 long long int currentLineNumber);
1782 std::pair<long long int, bool>
1783 parseFragmentAndMakeNextLineMain(ParserContext &ctx,
1784 std::shared_ptr<Block<Trait>> parent,
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 ¤tIndent,
1793 long long int currentLineNumber);
1796 isListType(BlockType t);
1798 std::pair<typename Trait::InternalString, bool>
1799 readLine(ParserContext &ctx, StringListStream<Trait> &stream);
1801 std::shared_ptr<Image<Trait>>
1802 makeImage(
const typename Trait::String &url,
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);
1813 std::shared_ptr<Link<Trait>>
1814 makeLink(
const typename Trait::String &url,
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);
1826 enum DelimiterType {
1834 SquareBracketsClose,
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;
1868 using Delims =
typename Trait::template Vector<Delimiter>;
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,
1879 bool doNotCreateTextOnFail,
1880 const WithPosition &textPos,
1881 const WithPosition &linkTextPos);
1883 typename Delims::iterator
1884 checkForImage(
typename Delims::iterator it,
1885 typename Delims::iterator last,
1886 TextParsingOpts<Trait> &po);
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,
1897 bool doNotCreateTextOnFail,
1898 const WithPosition &textPos,
1899 const WithPosition &linkTextPos);
1901 typename Delims::iterator
1902 checkForLink(
typename Delims::iterator it,
1903 typename Delims::iterator last,
1904 TextParsingOpts<Trait> &po);
1909 std::pair<typename Trait::String, bool>
1910 readHtmlTag(
typename Delims::iterator it, TextParsingOpts<Trait> &po);
1912 typename Delims::iterator
1913 findIt(
typename Delims::iterator it,
1914 typename Delims::iterator last,
1915 TextParsingOpts<Trait> &po);
1917 typename Delims::iterator
1918 eatRawHtmlTillEmptyLine(
typename Delims::iterator it,
1919 typename Delims::iterator last,
1922 TextParsingOpts<Trait> &po,
1925 bool continueEating =
false);
1928 finishRule1HtmlTag(
typename Delims::iterator it,
1929 typename Delims::iterator last,
1930 TextParsingOpts<Trait> &po,
1934 finishRule2HtmlTag(
typename Delims::iterator it,
1935 typename Delims::iterator last,
1936 TextParsingOpts<Trait> &po);
1939 finishRule3HtmlTag(
typename Delims::iterator it,
1940 typename Delims::iterator last,
1941 TextParsingOpts<Trait> &po);
1944 finishRule4HtmlTag(
typename Delims::iterator it,
1945 typename Delims::iterator last,
1946 TextParsingOpts<Trait> &po);
1949 finishRule5HtmlTag(
typename Delims::iterator it,
1950 typename Delims::iterator last,
1951 TextParsingOpts<Trait> &po);
1954 finishRule6HtmlTag(
typename Delims::iterator it,
1955 typename Delims::iterator last,
1956 TextParsingOpts<Trait> &po);
1959 finishRule7HtmlTag(
typename Delims::iterator it,
1960 typename Delims::iterator last,
1961 TextParsingOpts<Trait> &po);
1963 typename Delims::iterator
1964 finishRawHtmlTag(
typename Delims::iterator it,
1965 typename Delims::iterator last,
1966 TextParsingOpts<Trait> &po,
1970 htmlTagRule(
typename Delims::iterator it,
1971 typename Delims::iterator last,
1972 TextParsingOpts<Trait> &po);
1974 typename Delims::iterator
1975 checkForRawHtml(
typename Delims::iterator it,
1976 typename Delims::iterator last,
1977 TextParsingOpts<Trait> &po);
1979 typename Delims::iterator
1980 checkForMath(
typename Delims::iterator it,
1981 typename Delims::iterator last,
1982 TextParsingOpts<Trait> &po);
1984 typename Delims::iterator
1985 checkForAutolinkHtml(
typename Delims::iterator it,
1986 typename Delims::iterator last,
1987 TextParsingOpts<Trait> &po,
1990 typename Delims::iterator
1991 checkForInlineCode(
typename Delims::iterator it,
1992 typename Delims::iterator last,
1993 TextParsingOpts<Trait> &po);
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);
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);
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);
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);
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);
2027 typename Trait::String
2030 template<
class Func>
2031 typename Delims::iterator
2032 checkShortcut(
typename Delims::iterator it,
2033 typename Delims::iterator last,
2034 TextParsingOpts<Trait> &po,
2037 const auto start = it;
2041 WithPosition labelPos;
2042 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
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, {})) {
2055 isSequence(
typename Delims::iterator it,
2056 long long int itLine,
2057 long long int itPos,
2058 typename Delimiter::DelimiterType t);
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,
2065 long long int &length,
2066 long long int &itCount,
2067 long long int &lengthFromIt,
2068 long long int &itCountFromIt);
2070 typename Delims::iterator
2071 readSequence(
typename Delims::iterator it,
2072 typename Delims::iterator last,
2073 long long int &line,
2076 long long int &itCount);
2079 emphasisToInt(
typename Delimiter::DelimiterType t);
2082 createStyles(std::vector<std::pair<Style, long long int>> & styles,
2083 typename Delimiter::DelimiterType t,
2084 long long int style);
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);
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);
2098 typename Delims::iterator
2099 incrementIterator(
typename Delims::iterator it,
2100 typename Delims::iterator last,
2101 long long int count);
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);
2111 isNewBlockIn(MdBlock<Trait> &fr,
2112 long long int startLine,
2113 long long int endLine);
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);
2125 defaultParagraphOptimization()
const
2136 typename Trait::StringList m_parsedFiles;
2138 bool m_fullyOptimizeParagraphs =
true;
2147template<
class Trait>
2148inline std::shared_ptr<Document<Trait>>
2151 const typename Trait::StringList &ext,
2152 bool fullyOptimizeParagraphs)
2154 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2158 parseFile(fileName, recursive, doc, ext);
2165template<
class Trait>
2166inline std::shared_ptr<Document<Trait>>
2168 const typename Trait::String &path,
2169 const typename Trait::String &fileName,
2170 bool fullyOptimizeParagraphs)
2172 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2176 parseStream(stream, path, fileName,
false, doc,
typename Trait::StringList());
2183template<
class Trait>
2184inline std::shared_ptr<Document<Trait>>
2186 const typename Trait::String &workingDirectory,
2188 const typename Trait::StringList &ext,
2189 bool fullyOptimizeParagraphs)
2191 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2195 typename Trait::String wd;
2196 auto i = workingDirectory.length() - 1;
2199 if (workingDirectory[i] != Trait::latin1ToChar(
'\\') &&
2200 workingDirectory[i] != Trait::latin1ToChar(
'/')) {
2207 if (i == 0 && workingDirectory[i] == Trait::latin1ToChar(
'/')) {
2208 wd = Trait::latin1ToString(
"/");
2210 wd = workingDirectory.sliced(0, i + 1);
2213 parseFile(fileName, recursive, doc, ext,
nullptr, wd);
2220template<
class Trait>
2223#ifdef MD4QT_QT_SUPPORT
2240 return (m_lastBuf && m_pos == m_buf.size());
2247 bool rFound =
false;
2250 const auto c = getChar();
2278 m_buf = m_stream.read(512);
2280 if (m_stream.atEnd()) {
2290 if (m_pos < m_buf.size()) {
2291 return m_buf.at(m_pos++);
2292 }
else if (!atEnd()) {
2302 QTextStream &m_stream;
2305 long long int m_pos;
2310#ifdef MD4QT_ICU_STL_SUPPORT
2320 std::vector<unsigned char> content;
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;
2329 const auto z = std::count(content.cbegin(), content.cend(), 0);
2332 std::vector<unsigned char> tmp;
2333 tmp.resize(content.size() + (z - 1) * 2);
2335 for (
size_t i = 0, j = 0; i < content.size() - 1; ++i, ++j) {
2336 if (content[i] == 0) {
2342 tmp[j] = content[i];
2346 tmp[tmp.size() - 1] = 0;
2348 std::swap(content, tmp);
2351 m_str = UnicodeString::fromUTF8((
char *)&content[0]);
2357 return m_pos == m_str.size();
2365 bool rFound =
false;
2368 const auto c = getChar();
2397 return m_str[m_pos++];
2399 return UnicodeChar();
2404 UnicodeString m_str;
2405 long long int m_pos;
2411template<
class Trait>
2416 const long long int e = line.indexOf(Trait::latin1ToString(
"-->"), pos);
2426template<
class Trait>
2434 const auto &str = line.asString();
2436 while ((p = str.indexOf(Trait::latin1ToString(
s_startComment), p)) != -1) {
2437 bool addNegative =
false;
2439 auto c = str.sliced(p);
2441 if (c.startsWith(Trait::latin1ToString(
"<!-->"))) {
2442 res.insert({line.virginPos(p), {0,
true}});
2447 }
else if (c.startsWith(Trait::latin1ToString(
"<!--->"))) {
2448 res.insert({line.virginPos(p), {1,
true}});
2456 res.insert({line.virginPos(p), {2,
true}});
2460 for (; l < stream.
size(); ++l) {
2461 c.push_back(Trait::latin1ToChar(
' '));
2462 c.push_back(stream.
lineAt(l).asString());
2465 res.insert({line.virginPos(p), {2,
true}});
2467 addNegative =
false;
2475 res.insert({line.virginPos(p), {-1,
false}});
2482template<
class Trait>
2483inline std::pair<long long int, bool>
2485 std::shared_ptr<Block<Trait>> parent,
2487 typename Trait::StringList &linksToParse,
2488 const typename Trait::String &workingPath,
2489 const typename Trait::String &fileName,
2490 bool collectRefLinks)
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();
2505 if (!ctx.m_fragment.empty()) {
2506 MdBlock<Trait> block = {ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0};
2508 const auto line = parseFragment(block, parent, doc, linksToParse, workingPath,
2509 fileName, collectRefLinks, ctx.m_html);
2511 assert(line != ctx.m_fragment.front().second.m_lineNumber);
2514 if (ctx.m_html.m_html) {
2515 if (!collectRefLinks) {
2516 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2522 const auto it = ctx.m_fragment.cbegin() + (line - ctx.m_fragment.cbegin()->second.m_lineNumber);
2525 std::copy(ctx.m_fragment.cbegin(), it, std::back_inserter(tmp.m_data));
2527 long long int emptyLines = 0;
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;
2535 if (!tmp.m_data.empty()) {
2536 ctx.m_splitted.push_back(tmp);
2539 const auto retLine = it->second.m_lineNumber;
2540 const auto retMayBreakList = it->second.m_mayBreakList;
2544 ctx.m_emptyLinesBefore = emptyLines;
2546 return {retLine, retMayBreakList};
2549 ctx.m_splitted.push_back({ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0});
2558template<
class Trait>
2562 unsigned char size = 4;
2563 long long int len = s.length();
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(
' ')));
2580template<
class Trait>
2583 StringListStream<Trait> &stream,
2584 std::shared_ptr<Block<Trait>> parent,
2586 typename Trait::StringList &linksToParse,
2587 const typename Trait::String &workingPath,
2588 const typename Trait::String &fileName,
2589 bool collectRefLinks)
2591 long long int emptyLinesCount = 0;
2592 bool wasEmptyLine =
false;
2594 while (!stream.atEnd()) {
2595 const auto currentLineNumber = stream.currentLineNumber();
2597 typename Trait::InternalString line;
2600 std::tie(line, mayBreak) = readLine(ctx, stream);
2606 if (ns == line.length() || line.asString().startsWith(Trait::latin1ToString(
" "))) {
2607 if (ns == line.length()) {
2609 wasEmptyLine =
true;
2611 emptyLinesCount = 0;
2614 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2615 }
else if (!wasEmptyLine) {
2617 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2619 ctx.m_lineType = BlockType::Footnote;
2621 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2625 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2628 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2631 whatIsTheLine(line,
false,
false,
false, &ctx.m_startOfCodeInList, &ctx.m_indent,
2632 ctx.m_lineType == BlockType::EmptyLine,
true, &ctx.m_indents);
2634 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2636 if (ctx.m_type == BlockType::Footnote) {
2637 wasEmptyLine =
false;
2646 if (stream.atEnd() && !ctx.m_fragment.empty()) {
2647 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2651template<
class Trait>
2653Parser<Trait>::finishHtml(ParserContext &ctx,
2656 bool collectRefLinks,
2658 bool dontProcessLastFreeHtml)
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);
2668 parent->appendItem(ctx.m_html.m_html);
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());
2682 if (!dontProcessLastFreeHtml) {
2686 ctx.m_html.m_toAdjustLastPos.clear();
2689template<
class Trait>
2691Parser<Trait>::makeLineMain(ParserContext &ctx,
2692 const typename Trait::InternalString &line,
2693 long long int emptyLinesCount,
2694 const ListIndent ¤tIndent,
2696 long long int currentLineNumber)
2698 if (ctx.m_html.m_htmlBlockType >= 6) {
2699 ctx.m_html.m_continueHtml = (emptyLinesCount <= 0);
2702 ctx.m_type = ctx.m_lineType;
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);
2710 ctx.m_indent = currentIndent;
2713 case BlockType::Code:
2721 if (!line.isEmpty() && ns < line.length()) {
2722 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData}});
2725 ctx.m_lineCounter = 1;
2726 ctx.m_emptyLinesCount = 0;
2727 ctx.m_emptyLinesBefore = emptyLinesCount;
2730template<
class Trait>
2731inline std::pair<long long int, bool>
2732Parser<Trait>::parseFragmentAndMakeNextLineMain(ParserContext &ctx,
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 ¤tIndent,
2742 long long int currentLineNumber)
2744 const auto empty = ctx.m_emptyLinesCount;
2746 const auto ret = parseFragment(ctx, parent, doc, linksToParse, workingPath,
2747 fileName, collectRefLinks);
2749 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2754template<
class Trait>
2756Parser<Trait>::isListType(BlockType t)
2759 case BlockType::List:
2760 case BlockType::ListWithFirstEmptyLine:
2768template<
class Trait>
2769std::pair<typename Trait::InternalString, bool>
2770Parser<Trait>::readLine(
typename Parser<Trait>::ParserContext &ctx,
2773 ctx.m_htmlCommentData.clear();
2775 auto line = stream.readLine();
2777 static const char16_t c_zeroReplaceWith[2] = {0xFFFD, 0};
2779 line.first.replace(
typename Trait::Char(0), Trait::utf16ToString(&c_zeroReplaceWith[0]));
2786template<
class Trait>
2787inline std::pair<long long int, bool>
2788Parser<Trait>::parseFirstStep(ParserContext &ctx,
2792 typename Trait::StringList &linksToParse,
2793 const typename Trait::String &workingPath,
2794 const typename Trait::String &fileName,
2795 bool collectRefLinks)
2797 while (!stream.atEnd()) {
2798 const auto currentLineNumber = stream.currentLineNumber();
2800 typename Trait::InternalString line;
2803 std::tie(line, mayBreak) = readLine(ctx, stream);
2805 if (ctx.m_lineType != BlockType::Unknown) {
2806 ctx.m_prevLineType = ctx.m_lineType;
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,
2815 ctx.m_lineType == BlockType::EmptyLine,
2819 if (isListType(ctx.m_type) && ctx.m_lineType == BlockType::FensedCodeInList) {
2820 ctx.m_fensedCodeInList = !ctx.m_fensedCodeInList;
2823 const auto currentIndent = ctx.m_indent;
2827 const auto indentInListValue =
indentInList(&ctx.m_indents, ns,
true);
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());
2834 ctx.m_indents.push_back(ctx.m_indent.m_indent);
2837 if (ctx.m_type == BlockType::CodeIndentedBySpaces && ns > 3) {
2838 ctx.m_lineType = BlockType::CodeIndentedBySpaces;
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,
2856 if (l.first != -1) {
2862 ctx.m_emptyLineInList =
false;
2863 ctx.m_emptyLinesCount = 0;
2867 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2) {
2868 ctx.m_type = BlockType::List;
2872 if (ctx.m_lineType == BlockType::Footnote) {
2873 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2885 if (l.first != -1) {
2889 eatFootnote(ctx, stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2895 if (ns != line.length() && ctx.m_type == BlockType::EmptyLine) {
2896 makeLineMain(ctx, line, ctx.m_emptyLinesCount, currentIndent, ns, currentLineNumber);
2899 }
else if (ns == line.length() && ctx.m_type == BlockType::EmptyLine) {
2900 ++ctx.m_emptyLinesCount;
2904 ++ctx.m_lineCounter;
2907 if (ns == line.length()) {
2908 ++ctx.m_emptyLinesCount;
2910 switch (ctx.m_type) {
2911 case BlockType::Blockquote: {
2912 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2915 if (l.first != -1) {
2922 case BlockType::Text:
2923 case BlockType::CodeIndentedBySpaces:
2927 case BlockType::Code: {
2928 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2929 ctx.m_emptyLinesCount = 0;
2934 case BlockType::List:
2935 case BlockType::ListWithFirstEmptyLine: {
2936 ctx.m_emptyLineInList =
true;
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}});
2953 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2955 ctx.m_emptyLineInList =
false;
2956 ctx.m_emptyLinesCount = 0;
2960 const auto empty = ctx.m_emptyLinesCount;
2962 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2965 if (l.first != -1) {
2969 ctx.m_lineType = whatIsTheLine(line,
false,
false,
false,
nullptr,
nullptr,
2970 true,
false, &ctx.m_indents);
2972 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
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());
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}});
2986 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2987 ctx.m_emptyLinesCount = 0;
2989 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3001 if (l.first != -1) {
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}});
3016 if (ctx.m_type == BlockType::Text && isListType(ctx.m_lineType)) {
3017 if (ctx.m_lineType != BlockType::ListWithFirstEmptyLine) {
3022 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3028 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3034 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3046 if (l.first != -1) {
3052 else if (ctx.m_type == BlockType::Code && ctx.m_type == ctx.m_lineType &&
3053 !ctx.m_startOfCode.isEmpty() &&
3056 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3058 if (!stream.atEnd()) {
3059 typename Trait::InternalString line;
3061 std::tie(line, std::ignore) = readLine(ctx, stream);
3063 if (line.asString().simplified().isEmpty()) {
3064 ++ctx.m_emptyLinesCount;
3067 stream.setLineNumber(stream.currentLineNumber() - 1);
3069 ++ctx.m_emptyLinesCount;
3072 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3075 if (l.first != -1) {
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,
3095 if (l.first != -1) {
3098 }
else if (ctx.m_type == BlockType::Heading) {
3099 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3111 if (l.first != -1) {
3115 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3118 ctx.m_emptyLinesCount = 0;
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}});
3126 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3129 if (l.first != -1) {
3137template<
class Trait>
3139Parser<Trait>::parseSecondStep(ParserContext &ctx,
3142 typename Trait::StringList &linksToParse,
3143 const typename Trait::String &workingPath,
3144 const typename Trait::String &fileName,
3145 bool collectRefLinks,
3147 bool dontProcessLastFreeHtml)
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,
3156 if (ctx.m_html.m_htmlBlockType >= 6) {
3157 ctx.m_html.m_continueHtml = (!ctx.m_splitted[i].m_emptyLineAfter);
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();
3168 if (ctx.m_html.m_html) {
3169 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3173template<
class Trait>
3174inline std::pair<RawHtmlBlock<Trait>,
long long int>
3178 typename Trait::StringList &linksToParse,
3179 const typename Trait::String &workingPath,
3180 const typename Trait::String &fileName,
3181 bool collectRefLinks,
3183 bool dontProcessLastFreeHtml,
3184 bool stopOnMayBreakList)
3188 auto clearCtx = [&]()
3190 ctx.m_fragment.clear();
3191 ctx.m_type = BlockType::EmptyLine;
3192 ctx.m_lineCounter = 0;
3195 auto line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3200 while (line.first != -1 && !(stopOnMayBreakList && line.second)) {
3201 stream.setLineNumber(line.first);
3203 line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3209 parseSecondStep(ctx, parent, doc, linksToParse, workingPath, fileName,
3210 collectRefLinks, top, dontProcessLastFreeHtml);
3212 return {ctx.m_html, line.first};
3215#ifdef MD4QT_QT_SUPPORT
3219Parser<QStringTrait>::parseFile(
const QString &fileName,
3222 const QStringList &ext,
3223 QStringList *parentLinks,
3224 QString workingDirectory)
3226 QFileInfo fi(fileName);
3228 if (fi.exists() && ext.
contains(fi.suffix().toLower())) {
3232 QTextStream s(f.readAll());
3235 auto wd = fi.absolutePath();
3236 auto fn = fi.fileName();
3238 workingDirectory.
replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
3240 if (!workingDirectory.
isEmpty() && wd.contains(workingDirectory)) {
3241 QFileInfo folder(workingDirectory);
3243 if (folder.exists() && folder.isDir()) {
3244 wd = folder.absoluteFilePath();
3246 auto tmp = fi.absoluteFilePath();
3247 fn = tmp.remove(wd);
3252 parseStream(s, wd, fn, recursive, doc, ext, parentLinks, workingDirectory);
3259#ifdef MD4QT_ICU_STL_SUPPORT
3263Parser<UnicodeStringTrait>::parseFile(
const UnicodeString &fileName,
3266 const std::vector<UnicodeString> &ext,
3267 std::vector<UnicodeString> *parentLinks,
3272 fileName.toUTF8String(fn);
3275 auto e = UnicodeString::fromUTF8(std::filesystem::u8path(fn).extension().u8string());
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);
3286 const auto fileNameS =
path.filename().u8string();
3287 auto wd =
path.remove_filename().u8string();
3288 auto ufn = UnicodeString::fromUTF8(fileNameS);
3291 wd.erase(wd.size() - 1, 1);
3294 std::replace(wd.begin(), wd.end(),
'\\',
'/');
3298 if (!workingDirectory.isEmpty() &&
3300 std::string folderPath;
3301 workingDirectory.toUTF8String(folderPath);
3302 auto folder = std::filesystem::directory_entry(folderPath);
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();
3310 std::replace(wd.begin(), wd.end(),
'\\',
'/');
3314 ufn = tmp.findAndReplace(UnicodeString::fromUTF8(wd), {});
3319 parseStream(file, UnicodeString::fromUTF8(wd), ufn,
3320 recursive, doc, ext, parentLinks, workingDirectory);
3325 }
catch (
const std::exception &) {
3333template<
class Trait>
3338 for (
auto it = linksToParse.begin(), last = linksToParse.end(); it != last; ++it) {
3339 auto nextFileName = *it;
3341 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3342 const auto lit = doc->labeledLinks().find(nextFileName);
3344 if (lit != doc->labeledLinks().cend()) {
3345 nextFileName = lit->second->url();
3351 if (Trait::fileExists(nextFileName)) {
3352 *it = Trait::absoluteFilePath(nextFileName);
3357template<
class Trait>
3360 const typename Trait::String &workingPath,
3361 const typename Trait::String &fileName,
3364 const typename Trait::StringList &ext,
3365 typename Trait::StringList *parentLinks,
3366 const typename Trait::String &workingDirectory)
3368 typename Trait::StringList linksToParse;
3370 const auto path = workingPath.
isEmpty() ?
typename Trait::String(fileName) :
3371 typename Trait::String(workingPath + Trait::latin1ToString(
"/") + fileName);
3378 TextStream<Trait> stream(s);
3380 long long int i = 0;
3382 while (!stream.atEnd()) {
3383 data.push_back(std::pair<typename Trait::InternalString, MdLineData>(stream.readLine(), {i}));
3390 parse(stream, doc, doc, linksToParse, workingPath, fileName,
true,
true);
3392 m_parsedFiles.push_back(path);
3397 if (recursive && !linksToParse.empty()) {
3398 const auto tmpLinks = linksToParse;
3400 while (!linksToParse.empty()) {
3401 auto nextFileName = linksToParse.front();
3402 linksToParse.erase(linksToParse.cbegin());
3405 const auto pit = std::find(parentLinks->cbegin(), parentLinks->cend(), nextFileName);
3407 if (pit != parentLinks->cend()) {
3412 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3416 const auto pit = std::find(m_parsedFiles.cbegin(), m_parsedFiles.cend(), nextFileName);
3418 if (pit == m_parsedFiles.cend()) {
3423 parseFile(nextFileName, recursive, doc, ext, &linksToParse, workingDirectory);
3428 std::copy(tmpLinks.cbegin(), tmpLinks.cend(), std::back_inserter(*parentLinks));
3434template<
class Trait>
3439 long long int p = 0;
3441 for (; p < s.size(); ++p) {
3442 if (!s[p].isSpace()) {
3448 for (; p < s.size(); ++p) {
3449 if (!s[p].isDigit()) {
3457 long long int sc = 0;
3459 for (; p < s.size(); ++p) {
3460 if (!s[p].isSpace()) {
3467 if (p == s.length() || sc > 4) {
3469 }
else if (sc == 0) {
3481 long long int level = indents.
size();
3483 for (
auto it = indents.crbegin(), last = indents.crend(); it != last; ++it) {
3494template<
class Trait>
3498 bool inListWithFirstEmptyLine,
3499 bool fensedCodeInList,
3500 typename Trait::String *startOfCode,
3502 bool emptyLinePreceded,
3504 const std::vector<long long int> *indents)
3510 if (first < str.length()) {
3511 auto s = str.sliced(first);
3513 const bool isBlockquote = s.asString().startsWith(Trait::latin1ToString(
">"));
3514 const bool indentIn =
indentInList(indents, first,
false);
3515 bool isHeading =
false;
3518 return BlockType::Footnote;
3521 if (s.asString().startsWith(Trait::latin1ToString(
"#")) &&
3522 (indent ? first - indent->m_indent < 4 : first < 4)) {
3523 long long int c = 0;
3525 while (c < s.length() && s[c] == Trait::latin1ToChar(
'#')) {
3529 if (c <= 6 && ((c < s.length() && s[c].isSpace()) || c == s.length())) {
3535 bool isFirstLineEmpty =
false;
3539 const auto codeIndentedBySpaces = emptyLinePreceded && first >= 4 &&
3542 if (fensedCodeInList) {
3546 return BlockType::FensedCodeInList;
3550 return BlockType::SomethingInList;
3554 if (fensedCode && indentIn) {
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;
3569 if (indent && calcIndent) {
3571 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3574 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3575 return BlockType::ListWithFirstEmptyLine;
3577 return BlockType::List;
3580 return BlockType::SomethingInList;
3583 if (!isHeading && !isBlockquote &&
3584 !(fensedCode && first < 4) && !emptyLinePreceded && !inListWithFirstEmptyLine) {
3585 return BlockType::SomethingInList;
3589 bool isFirstLineEmpty =
false;
3593 const bool isHLine = first < 4 && isHorizontalLine<Trait>(s.asString());
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) {
3602 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3605 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3606 return BlockType::ListWithFirstEmptyLine;
3608 return BlockType::List;
3613 if (str.asString().startsWith(
typename Trait::String(4, Trait::latin1ToChar(
' ')))) {
3614 return BlockType::CodeIndentedBySpaces;
3616 return BlockType::Code;
3617 }
else if (isBlockquote) {
3618 return BlockType::Blockquote;
3619 }
else if (isHeading) {
3620 return BlockType::Heading;
3623 return BlockType::EmptyLine;
3626 return BlockType::Text;
3629template<
class Trait>
3634 typename Trait::StringList &linksToParse,
3635 const typename Trait::String &workingPath,
3636 const typename Trait::String &fileName,
3637 bool collectRefLinks,
3640 if (html.m_continueHtml) {
3641 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3644 if (!collectRefLinks) {
3645 parent->appendItem(html.m_html);
3651 switch (whatIsTheLine(fr.m_data.front().first)) {
3652 case BlockType::Footnote:
3653 parseFootnote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3656 case BlockType::Text:
3657 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3660 case BlockType::Blockquote:
3661 return parseBlockquote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3664 case BlockType::Code:
3665 return parseCode(fr, parent, collectRefLinks);
3668 case BlockType::CodeIndentedBySpaces: {
3671 if (fr.m_data.front().first.asString().startsWith(Trait::latin1ToString(
" "))) {
3675 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, {}, -1, -1,
false);
3678 case BlockType::Heading:
3679 parseHeading(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3682 case BlockType::List:
3683 case BlockType::ListWithFirstEmptyLine:
3684 return parseList(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3694template<
class Trait>
3696Parser<Trait>::clearCache()
3698 m_parsedFiles.clear();
3702template<
class Trait>
3706 if (s.contains(Trait::latin1ToChar(
'|'))) {
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);
3714 bool backslash =
false;
3716 for (
long long int i = 0; i < v.size(); ++i) {
3719 if (v[i] == Trait::latin1ToChar(
'\\') && !backslash) {
3722 }
else if (v[i] == Trait::latin1ToChar(
'|') && !backslash) {
3739template<
class Trait>
3742 std::shared_ptr<Block<Trait>> parent,
3744 typename Trait::StringList &linksToParse,
3745 const typename Trait::String &workingPath,
3746 const typename Trait::String &fileName,
3747 bool collectRefLinks,
3748 RawHtmlBlock<Trait> &html)
3753 if (c && h && c == h && !html.m_continueHtml) {
3754 parseTable(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, c);
3756 if (!fr.m_data.empty()) {
3757 return fr.m_data.front().second.m_lineNumber;
3762 return parseParagraph(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3767template<
class Trait>
3768inline std::pair<typename Trait::String, WithPosition>
3771 const auto start = s.asString().indexOf(Trait::latin1ToString(
"{#"));
3774 long long int p =
start + 2;
3776 for (; p < s.length(); ++p) {
3777 if (s[p] == Trait::latin1ToChar(
'}')) {
3782 if (p < s.length() && s[p] == Trait::latin1ToChar(
'}')) {
3789 return {label, pos};
3797template<
class Trait>
3798inline typename Trait::String
3801 typename Trait::String res;
3803 for (
long long int i = 0; i < s.length(); ++i) {
3804 const auto c = s[i];
3806 if (c.isLetter() || c.isDigit() || c == Trait::latin1ToChar(
'-') ||
3807 c == Trait::latin1ToChar(
'_')) {
3809 }
else if (c.isSpace()) {
3810 res.push_back(Trait::latin1ToString(
"-"));
3818template<
class Trait>
3819inline typename Trait::String
3822 typename Trait::String l;
3828 for (
auto it = p->
items().cbegin(), last = p->
items().cend(); it != last; ++it) {
3829 switch ((*it)->type()) {
3832 const auto text = t->text();
3839 if (!i->p()->isEmpty()) {
3841 }
else if (!i->text().isEmpty()) {
3847 auto link =
static_cast<Link<Trait> *
>(it->get());
3849 if (!link->p()->isEmpty()) {
3851 }
else if (!link->text().isEmpty()) {
3859 if (!c->text().isEmpty()) {
3873template<
class Trait>
3877 long long int end = -1;
3878 long long int start = -1;
3880 for (
long long int i = s.length() - 1; i >= 0; --i) {
3881 if (!s[i].isSpace() && s[i] != Trait::latin1ToChar(
'#') && end == -1) {
3885 if (s[i] == Trait::latin1ToChar(
'#')) {
3891 if (s[i - 1].isSpace()) {
3894 }
else if (s[i - 1] != Trait::latin1ToChar(
'#')) {
3905 if (
start != -1 && end != -1) {
3915template<
class Trait>
3918 std::shared_ptr<Block<Trait>> parent,
3920 typename Trait::StringList &linksToParse,
3921 const typename Trait::String &workingPath,
3922 const typename Trait::String &fileName,
3923 bool collectRefLinks)
3925 if (!fr.m_data.empty() && !collectRefLinks) {
3926 auto line = fr.m_data.front().first;
3930 h->setStartLine(fr.m_data.front().second.m_lineNumber);
3931 h->setEndColumn(line.virginPos(line.length() - 1));
3932 h->setEndLine(h->startLine());
3934 long long int pos = 0;
3938 line = line.sliced(pos);
3944 while (pos < line.length() && line[pos] == Trait::latin1ToChar(
'#')) {
3949 WithPosition startDelim = {h->startColumn(), h->startLine(),
3950 line.virginPos(pos - 1), h->startLine()};
3955 fr.m_data.front().first = line.sliced(pos);
3964 if (endDelim.startColumn() != -1) {
3965 endDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
3966 endDelim.setEndLine(endDelim.startLine());
3968 delims.push_back(endDelim);
3971 h->setDelims(delims);
3977 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
3978 Trait::latin1ToString(
"")) + fileName);
3980 label.second.setStartLine(fr.m_data.front().second.m_lineNumber);
3981 label.second.setEndLine(
label.second.startLine());
3983 h->setLabelPos(
label.second);
3990 tmp.push_back(fr.m_data.front());
3995 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
3996 false,
false, html,
false);
3998 fr.m_data.erase(fr.m_data.cbegin());
4006 if (h->isLabeled()) {
4007 doc->insertLabeledHeading(h->label(), h);
4008 h->labelVariants().push_back(h->label());
4010 typename Trait::String
label = Trait::latin1ToString(
"#") +
4013 const auto path = Trait::latin1ToString(
"/") +
4014 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
4015 Trait::latin1ToString(
"")) + fileName;
4017 h->setLabel(label + path);
4018 h->labelVariants().
push_back(h->label());
4020 doc->insertLabeledHeading(label + path, h);
4028 parent->appendItem(h);
4033template<
class Trait>
4034inline typename Trait::InternalString
4037 s.replace(Trait::latin1ToString(
"\\|"), Trait::latin1ToString(
"|"));
4043template<
class Trait>
4044inline std::pair<typename Trait::InternalStringList, std::vector<long long int>>
4047 typename Trait::InternalStringList res;
4048 std::vector<long long int> columns;
4050 bool backslash =
false;
4051 long long int start = 0;
4053 for (
long long int i = 0; i < s.length(); ++i) {
4056 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash) {
4059 }
else if (s[i] == Trait::latin1ToChar(
'|') && !backslash) {
4061 columns.push_back(s.virginPos(i));
4072 return {res, columns};
4075template<
class Trait>
4078 std::shared_ptr<Block<Trait>> parent,
4080 typename Trait::StringList &linksToParse,
4081 const typename Trait::String &workingPath,
4082 const typename Trait::String &fileName,
4083 bool collectRefLinks,
4086 static const char sep =
'|';
4088 if (fr.m_data.size() >= 2) {
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);
4096 const auto &row = lineData.first;
4098 if (row.asString().startsWith(Trait::latin1ToString(
" "))) {
4105 if (p == line.length()) {
4109 if (line[p] == Trait::latin1ToChar(sep)) {
4110 line.remove(0, p + 1);
4113 for (p = line.length() - 1; p >= 0; --p) {
4114 if (!line[p].isSpace()) {
4123 if (line[p] == Trait::latin1ToChar(sep)) {
4124 line.remove(p, line.length() - p);
4128 columns.second.insert(columns.second.begin(), row.virginPos(0));
4129 columns.second.push_back(row.virginPos(row.length() - 1));
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);
4139 for (
auto it = columns.first.begin(), last = columns.first.end(); it != last; ++it, ++col) {
4140 if (col == columnsCount) {
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);
4150 if (!it->isEmpty()) {
4151 it->replace(Trait::latin1ToString(
"|"), Trait::latin1ToChar(sep));
4154 fragment.push_back({*it, lineData.second});
4161 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
4162 collectRefLinks,
false, html,
false);
4164 if (!p->isEmpty()) {
4165 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it ) {
4166 switch ((*it)->type()) {
4168 const auto pp = std::static_pointer_cast<Paragraph<Trait>>(*it);
4170 for (
auto it = pp->items().cbegin(), last = pp->items().cend(); it != last; ++it) {
4171 c->appendItem((*it));
4177 c->appendItem((*it));
4183 if (html.m_html.get()) {
4184 c->appendItem(html.m_html);
4192 table->appendRow(tr);
4198 auto fmt = fr.m_data.at(1).first;
4200 auto columns = fmt.split(
typename Trait::InternalString(Trait::latin1ToChar(sep)));
4202 for (
auto it = columns.begin(), last = columns.end(); it != last; ++it) {
4203 *it = it->simplified();
4205 if (!it->isEmpty()) {
4208 if (it->asString().endsWith(Trait::latin1ToString(
":")) &&
4209 it->asString().startsWith(Trait::latin1ToString(
":"))) {
4211 }
else if (it->asString().endsWith(Trait::latin1ToString(
":"))) {
4215 table->setColumnAlignment(table->columnsCount(), a);
4220 fr.m_data.erase(fr.m_data.cbegin() + 1);
4222 long long int r = 0;
4224 for (
const auto &line : std::as_const(fr.m_data)) {
4225 if (!parseTableRow(line)) {
4232 fr.m_data.erase(fr.m_data.cbegin(), fr.m_data.cbegin() + r);
4234 if (!table->isEmpty() && !collectRefLinks) {
4235 parent->appendItem(table);
4241template<
class Trait>
4243isH(
const typename Trait::String &s,
4244 const typename Trait::Char &c)
4252 const auto start = p;
4254 for (; p < s.size(); ++p) {
4260 if (p -
start < 1) {
4264 for (; p < s.size(); ++p) {
4265 if (!s[p].isSpace()) {
4274template<
class Trait>
4276isH1(
const typename Trait::String &s)
4278 return isH<Trait>(s, Trait::latin1ToChar(
'='));
4282template<
class Trait>
4284isH2(
const typename Trait::String &s)
4286 return isH<Trait>(s, Trait::latin1ToChar(
'-'));
4290template<
class Trait>
4291inline std::pair<long long int, long long int>
4297 return {pos - 1, line};
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) {
4303 return {fr.
m_data.at(i - 1).first.virginPos(fr.
m_data.at(i - 1).first.length() - 1),
4313template<
class Trait>
4314inline std::pair<long long int, long long int>
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};
4334template<
class Trait>
4337 std::shared_ptr<Block<Trait>> parent,
4339 typename Trait::StringList &linksToParse,
4340 const typename Trait::String &workingPath,
4341 const typename Trait::String &fileName,
4342 bool collectRefLinks,
4343 RawHtmlBlock<Trait> &html)
4345 return parseFormattedTextLinksImages(fr, parent, doc, linksToParse, workingPath, fileName,
4346 collectRefLinks,
false, html,
false);
4349template<
class Trait>
4354 return html->isFreeTag();
4360 html->setFreeTag(on);
4364template<
class Trait>
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();
4373 const auto withoutSpaces = str.sliced(p);
4376 d.push_back({Delimiter::HorizontalLine, line, 0, str.length(),
false,
false,
false});
4378 d.push_back({Delimiter::H1, line, 0, str.length(),
false,
false,
false});
4380 d.push_back({Delimiter::H2, line, 0, str.length(),
false,
false,
false});
4382 bool backslash =
false;
4385 for (
long long int i = p; i < str.size(); ++i) {
4388 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
4393 else if ((str[i] == Trait::latin1ToChar(
'_') || str[i] == Trait::latin1ToChar(
'*')) && !backslash) {
4394 typename Trait::String style;
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);
4401 const auto ch = str[i];
4403 while (i < str.length() && str[i] == ch) {
4404 style.push_back(str[i]);
4408 typename Delimiter::DelimiterType dt = Delimiter::Unknown;
4410 if (ch == Trait::latin1ToChar(
'*')) {
4411 dt = Delimiter::Emphasis1;
4413 dt = Delimiter::Emphasis2;
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);
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});
4438 else if (str[i] == Trait::latin1ToChar(
'~') && !backslash) {
4439 typename Trait::String style;
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;
4445 while (i < str.length() && str[i] == Trait::latin1ToChar(
'~')) {
4446 style.push_back(str[i]);
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)));
4456 if (leftFlanking || rightFlanking) {
4457 d.push_back({Delimiter::Strikethrough,
4477 else if (str[i] == Trait::latin1ToChar(
'[') && !backslash) {
4478 d.push_back({Delimiter::SquareBracketsOpen, line, i, 1, word,
false});
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});
4499 else if (str[i] == Trait::latin1ToChar(
'(') && !backslash) {
4500 d.push_back({Delimiter::ParenthesesOpen, line, i, 1, word,
false});
4505 else if (str[i] == Trait::latin1ToChar(
']') && !backslash) {
4506 d.push_back({Delimiter::SquareBracketsClose, line, i, 1, word,
false});
4511 else if (str[i] == Trait::latin1ToChar(
')') && !backslash) {
4512 d.push_back({Delimiter::ParenthesesClose, line, i, 1, word,
false});
4517 else if (str[i] == Trait::latin1ToChar(
'<') && !backslash) {
4518 d.push_back({Delimiter::Less, line, i, 1, word,
false});
4523 else if (str[i] == Trait::latin1ToChar(
'>') && !backslash) {
4524 d.push_back({Delimiter::Greater, line, i, 1, word,
false});
4529 else if (str[i] == Trait::latin1ToChar(
'`')) {
4530 typename Trait::String code;
4532 while (i < str.length() && str[i] == Trait::latin1ToChar(
'`')) {
4533 code.push_back(str[i]);
4537 d.push_back({Delimiter::InlineCode,
4539 i - code.length() - (backslash ? 1 : 0),
4540 code.length() + (backslash ? 1 : 0),
4549 else if (str[i] == Trait::latin1ToChar(
'$')) {
4550 typename Trait::String m;
4552 while (i < str.length() && str[i] == Trait::latin1ToChar(
'$')) {
4553 m.push_back(str[i]);
4557 if (m.length() <= 2 && !backslash) {
4558 d.push_back({Delimiter::Math, line, i - m.length(), m.length(),
4559 false,
false,
false,
false});
4580template<
class Trait>
4584 long long int count = 0, pos = s.length() - 1, end = s.length() - 1;
4586 while ((pos = Trait::lastIndexOf(s, Trait::latin1ToString(
"\\"), pos)) != -1 && pos == end) {
4592 return (s.endsWith(Trait::latin1ToString(
" ")) || (count % 2 != 0));
4596template<
class Trait>
4600 return (s.endsWith(Trait::latin1ToString(
" ")) ? 2 : 1);
4604template<
class Trait>
4605inline typename Trait::String
4608 if (s.endsWith(Trait::latin1ToString(
"\\"))) {
4609 return s.sliced(0, s.size() - 1);
4616template<
class Trait>
4627template<
class Trait>
4631 long long int startPos,
4632 long long int startLine,
4633 long long int endPos,
4634 long long int endLine,
4635 bool doRemoveSpacesAtEnd =
false)
4637 if (endPos < 0 && endLine - 1 >= 0) {
4638 endPos = po.
m_fr.m_data.at(endLine - 1).first.length() - 1;
4642 if (endPos == po.
m_fr.m_data.at(endLine).first.length() - 1) {
4643 doRemoveSpacesAtEnd =
true;
4648 if (doRemoveSpacesAtEnd) {
4652 if (startPos == 0) {
4663 std::shared_ptr<Text<Trait>> t;
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);
4678 po.
m_parent->setEndColumn(t->endColumn());
4679 po.
m_parent->setEndLine(t->endLine());
4692 po.
m_pos = startPos;
4697template<
class Trait>
4701 long long int startPos,
4702 long long int startLine,
4703 long long int endPos,
4704 long long int endLine)
4706 makeTextObject(text, po, startPos, startLine, endPos, endLine,
true);
4708 std::shared_ptr<LineBreak<Trait>> hr;
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());
4730template<
class Trait>
4733 long long int lastLine)
4738 for (; i <= lastLine; ++i) {
4740 const auto c = i + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) ?
4743 if (h && c && c == h) {
4760template<
class Trait>
4767 long long int lastLine,
4769 long long int lastPos,
4772 if (po.
m_line > lastLine) {
4774 }
else if (po.
m_line == lastLine && po.
m_pos >= lastPos) {
4778 typename Trait::String text;
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);
4786 (po.
m_line == lastLine ? (lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
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();
4799 startLine = po.
m_line + 1;
4810 const auto length = (po.
m_line == lastLine ?
4812 const auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(po.
m_pos, length);
4821 po.
m_line == lastLine ? lastPos - 1 : po.
m_fr.m_data.at(po.
m_line).first.length() - 1,
4827 if (po.
m_line != lastLine) {
4838 po.
m_fr.m_data.at(po.
m_line).first.virginSubString());
4852 lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
4855 auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(0, lastPos);
4873template<
class Trait>
4879 while (l < (
long long int)fr.size()) {
4882 if (p < fr[l].first.length()) {
4892template<
class Trait>
4893inline std::pair<bool, bool>
4898 static const typename Trait::String notAllowed = Trait::latin1ToString(
"\"`=<'");
4900 const auto start = p;
4902 for (; p < fr[l].first.length(); ++p) {
4903 if (fr[l].first[p].isSpace()) {
4905 }
else if (notAllowed.contains(fr[l].first[p])) {
4906 return {
false,
false};
4907 }
else if (fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4916template<
class Trait>
4917inline std::pair<bool, bool>
4922 if (p < fr[l].first.length() && fr[l].first[p] != Trait::latin1ToChar(
'"') &&
4923 fr[l].first[p] != Trait::latin1ToChar(
'\'')) {
4927 const auto s = fr[l].first[p];
4931 if (p >= fr[l].first.length()) {
4932 return {
false,
false};
4935 for (; l < (
long long int)fr.size(); ++l) {
4936 bool doBreak =
false;
4938 for (; p < fr[l].first.length(); ++p) {
4939 const auto ch = fr[l].first[p];
4955 if (l >= (
long long int)fr.size()) {
4956 return {
false,
false};
4959 if (p >= fr[l].first.length()) {
4960 return {
false,
false};
4963 if (fr[l].first[p] != s) {
4964 return {
false,
false};
4969 return {
true,
true};
4973template<
class Trait>
4974inline std::pair<bool, bool>
4980 long long int tl = l, tp = p;
4984 if (l >= (
long long int)fr.size()) {
4985 return {
false,
false};
4989 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'/')) {
4990 return {
false,
true};
4994 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4995 return {
false,
true};
4998 if (checkForSpace) {
4999 if (tl == l && tp == p) {
5000 return {
false,
false};
5004 const auto start = p;
5006 for (; p < fr[l].first.length(); ++p) {
5007 const auto ch = fr[l].first[p];
5009 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'=')) {
5014 const typename Trait::String name = fr[l].first.asString().sliced(
start, p -
start).toLower();
5016 if (!name.
startsWith(Trait::latin1ToString(
"_")) && !name.
startsWith(Trait::latin1ToString(
":")) &&
5018 return {
false,
false};
5021 static const typename Trait::String allowedInName =
5022 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789_.:-");
5024 for (
long long int i = 1; i < name.
length(); ++i) {
5025 if (!allowedInName.contains(name[i])) {
5026 return {
false,
false};
5031 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
5032 return {
false,
true};
5040 if (l >= (
long long int)fr.size()) {
5041 return {
false,
false};
5045 if (p < fr[l].first.length()) {
5046 if (fr[l].first[p] != Trait::latin1ToChar(
'=')) {
5050 return {
true,
true};
5055 return {
true,
false};
5060 if (l >= (
long long int)fr.size()) {
5061 return {
false,
false};
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);
5073template<
class Trait>
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")};
5087 while (p < po.
m_fr.m_data[line].first.length()) {
5091 typename Trait::String tag;
5093 std::tie(ok, l, p, std::ignore, tag) =
isHtmlTag(line, p, po, rule);
5106 if (s_rule1Finish.find(tag.toLower()) != s_rule1Finish.cend() && l == line) {
5118 if (p >= po.
m_fr.m_data[line].first.length()) {
5126template<
class Trait>
5129 long long int startLine,
5130 long long int endLine)
5132 for (; startLine <= endLine; ++startLine) {
5134 const auto line = po.
m_fr.m_data.at(startLine).first.asString().sliced(pos);
5145template<
class Trait>
5146inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
5152 if (po.
m_fr.m_data[line].first[pos] != Trait::latin1ToChar(
'<')) {
5153 return {
false, line, pos,
false, {}};
5156 typename Trait::String tag;
5158 long long int l = line;
5159 long long int p = pos + 1;
5164 first = (tmp == pos);
5167 if (p >= po.
m_fr.m_data[l].first.length()) {
5168 return {
false, line, pos, first, tag};
5171 bool closing =
false;
5173 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5176 tag.push_back(Trait::latin1ToChar(
'/'));
5181 const auto start = p;
5184 for (; p < po.
m_fr.m_data[l].first.length(); ++p) {
5185 const auto ch = po.
m_fr.m_data[l].first[p];
5187 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'/')) {
5192 tag.push_back(po.
m_fr.m_data[l].first.asString().sliced(
start, p -
start));
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;
5203 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5207 return {
true, l, p + 1, onLine, tag};
5209 return {
false, line, pos, first, tag};
5212 return {
false, line, pos, first, tag};
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;
5223 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5227 return {
true, l, p, onLine, tag};
5229 return {
false, line, pos, first, tag};
5235 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5236 return {
false, line, pos, first, tag};
5239 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5240 long long int tmp = 0;
5246 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5250 return {
true, l, p, onLine, tag};
5252 return {
false, line, pos, first, tag};
5257 bool firstAttr =
true;
5266 if (closing && attr) {
5267 return {
false, line, pos, first, tag};
5271 return {
false, line, pos, first, tag};
5275 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5280 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5281 return {
false, line, pos, first, tag};
5285 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5286 long long int tmp = 0;
5292 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5296 return {
true, l, p, onLine, tag};
5298 return {
false, line, pos, first, tag};
5302 return {
false, line, pos, first, {}};
5306template<
class Trait>
5307inline std::pair<typename Trait::String, bool>
5309 TextParsingOpts<Trait> &po)
5311 long long int i = it->m_pos + 1;
5312 const auto start = i;
5314 if (
start >= po.m_fr.m_data[it->m_line].first.length()) {
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];
5321 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>')) {
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};
5331template<
class Trait>
5334 typename Delims::iterator last,
5335 TextParsingOpts<Trait> &po)
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) {
5351template<
class Trait>
5355 long long int toLine,
5356 long long int toPos,
5361 bool continueEating =
false)
5365 if (line <= toLine) {
5366 typename Trait::String h = po.
m_html.m_html->text();
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'));
5374 const auto first = po.
m_fr.m_data[line].first.asString().sliced(
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));
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'));
5383 if (!first.isEmpty()) {
5389 for (; line < toLine; ++line) {
5390 h.push_back(Trait::latin1ToChar(
'\n'));
5391 h.push_back(po.
m_fr.m_data[line].first.asString());
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()));
5400 auto endColumn = toPos;
5401 auto endLine = toLine;
5403 if (endColumn == 0 && endLine > 0) {
5405 endColumn = po.
m_fr.m_data.at(endLine).first.length();
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);
5412 po.
m_line = (toPos >= 0 ? toLine : toLine + 1);
5413 po.
m_pos = (toPos >= 0 ? toPos : 0);
5415 if (po.
m_line + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) &&
5421 po.
m_html.m_html->setText(h);
5443 const auto online = po.
m_html.m_onLine;
5451 po.
m_html.m_continueHtml =
true;
5455template<
class Trait>
5458 typename Delims::iterator last,
5461 TextParsingOpts<Trait> &po,
5464 bool continueEating)
5466 long long int emptyLine = line;
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;
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()) {
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);
5484 return findIt(it, last, po);
5486 eatRawHtml(line, pos, po.m_fr.m_data.size() - 1, -1, po,
false, htmlRule, onLine, continueEating);
5489 return std::prev(last);
5496template<
class Trait>
5499 long long int startLine,
5500 long long int endLine)
5502 for (
auto i = startLine + 1; i <= endLine; ++i) {
5503 const auto type = whatIsTheLine(fr.m_data[i].first);
5521 const auto ns = skipSpaces<Trait>(0, fr.m_data[i].first.asString());
5524 const auto s = fr.m_data[i].first.asString().sliced(ns);
5526 if (isHorizontalLine<Trait>(s) || isH1<Trait>(s) || isH2<Trait>(s)) {
5535template<
class Trait>
5538 typename Delims::iterator last,
5539 TextParsingOpts<Trait> &po,
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")};
5549 long long int l = -1, p = -1;
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);
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;
5562 std::tie(tag, closed) = readHtmlTag(it, po);
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);
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);
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);
5592template<
class Trait>
5595 typename Delims::iterator last,
5596 TextParsingOpts<Trait> &po)
5599 const auto start = it;
5601 MdLineData::CommentData commentData = {2,
true};
5602 bool onLine = po.m_html.m_onLine;
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);
5607 commentData = po.m_fr.m_data[it->m_line].second.m_htmlCommentData[i];
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;
5613 if (commentData.first != -1 && commentData.second) {
5614 for (; it != last; ++it) {
5615 if (it->m_type == Delimiter::Greater) {
5618 bool doContinue =
false;
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(
'-'))) {
5634 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5636 onLine ? po.m_fr.m_data[it->m_line].first.length() : it->m_pos + 1,
5637 po,
true, 2, onLine);
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);
5655template<
class Trait>
5658 typename Delims::iterator last,
5659 TextParsingOpts<Trait> &po)
5661 bool onLine = po.m_html.m_onLine;
5664 const auto start = it;
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;
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;
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(
'<')) {
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);
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);
5701template<
class Trait>
5704 typename Delims::iterator last,
5705 TextParsingOpts<Trait> &po)
5708 const auto start = it;
5710 bool onLine = po.m_html.m_onLine;
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;
5717 for (; it != last; ++it) {
5718 if (it->m_type == Delimiter::Greater) {
5719 long long int i = it->m_pos + 1;
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(
'<')) {
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);
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);
5745template<
class Trait>
5748 typename Delims::iterator last,
5749 TextParsingOpts<Trait> &po)
5752 const auto start = it;
5754 bool onLine = po.m_html.m_onLine;
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;
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;
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(
'<')) {
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);
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);
5792template<
class Trait>
5795 typename Delims::iterator last,
5796 TextParsingOpts<Trait> &po)
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);
5803 if (po.m_html.m_onLine) {
5804 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 6,
true,
true);
5806 const auto nit = std::find_if(std::next(it), last, [](
const auto &d) {
5807 return (d.m_type == Delimiter::Greater);
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,
5817template<
class Trait>
5820 typename Delims::iterator last,
5821 TextParsingOpts<Trait> &po)
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;
5831 std::tie(ok, l, p, onLine, std::ignore) =
isHtmlTag(it->m_line, it->m_pos, po, 7);
5833 onLine = onLine && it->m_line == 0 && l ==
start->m_line;
5836 eatRawHtml(po.m_line, po.m_pos, l, ++p, po, !onLine, 7, onLine);
5838 po.m_html.m_onLine = onLine;
5841 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 7, onLine,
true);
5849template<
class Trait>
5852 typename Delims::iterator last,
5853 TextParsingOpts<Trait> &po,
5856 po.m_detected = TextParsingOpts<Trait>::Detected::HTML;
5858 switch (po.m_html.m_htmlBlockType) {
5860 finishRule1HtmlTag(it, last, po, skipFirst);
5864 finishRule2HtmlTag(it, last, po);
5868 finishRule3HtmlTag(it, last, po);
5872 finishRule4HtmlTag(it, last, po);
5876 finishRule5HtmlTag(it, last, po);
5880 finishRule6HtmlTag(it, last, po);
5884 finishRule7HtmlTag(it, last, po);
5888 po.m_detected = TextParsingOpts<Trait>::Detected::Nothing;
5892 return findIt(it, last, po);
5895template<
class Trait>
5898 typename Delims::iterator last,
5899 TextParsingOpts<Trait> &po)
5903 typename Trait::String tag;
5905 std::tie(tag, std::ignore) = readHtmlTag(it, po);
5907 if (tag.startsWith(Trait::latin1ToString(
"![CDATA["))) {
5911 tag = tag.toLower();
5913 static const typename Trait::String s_validHtmlTagLetters =
5914 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789-");
5916 bool closing =
false;
5918 if (tag.startsWith(Trait::latin1ToString(
"/"))) {
5923 if (tag.endsWith(Trait::latin1ToString(
"/"))) {
5924 tag.remove(tag.size() - 1, 1);
5927 if (tag.isEmpty()) {
5931 if (!tag.startsWith(Trait::latin1ToString(
"!")) &&
5932 !tag.startsWith(Trait::latin1ToString(
"?")) &&
5933 !(tag[0].unicode() >= 97 && tag[0].unicode() <= 122)) {
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")};
5942 if (!closing && s_rule1.find(tag) != s_rule1.cend()) {
5944 }
else if (tag.startsWith(Trait::latin1ToString(
"!--"))) {
5946 }
else if (tag.startsWith(Trait::latin1ToString(
"?"))) {
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))) {
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")};
5971 for (
long long int i = 1; i < tag.size(); ++i) {
5972 if (!s_validHtmlTagLetters.contains(tag[i])) {
5977 if (s_rule6.find(tag) != s_rule6.cend()) {
5982 std::tie(tag, std::ignore, std::ignore, std::ignore, std::ignore) =
5983 isHtmlTag(it->m_line, it->m_pos, po, 7);
5994template<
class Trait>
5997 typename Delims::iterator last,
5998 TextParsingOpts<Trait> &po)
6000 const auto rule = htmlTagRule(it, last, po);
6005 po.m_firstInParagraph =
false;
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);
6015 return finishRawHtmlTag(it, last, po,
true);
6018template<
class Trait>
6021 typename Delims::iterator last,
6022 TextParsingOpts<Trait> &po)
6024 po.m_wasRefLink =
false;
6025 po.m_firstInParagraph =
false;
6026 po.m_headingAllowed =
true;
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);
6032 if (end != last &&
end->m_line <= po.m_lastTextLine) {
6033 typename Trait::String math;
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));
6039 math = po.m_fr.m_data[it->m_line].first.asString().sliced(it->m_pos + it->m_len);
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());
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));
6050 if (!po.m_collectRefLinks) {
6051 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
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);
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);
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;
6063 if (endColumn == 0) {
6064 std::tie(endColumn, endLine) =
prevPosition(po.m_fr, endColumn, endLine);
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);
6084 initLastItemWithOpts<Trait>(po, m);
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);
6095 po.m_parent->appendItem(m);
6097 po.m_pos =
end->m_pos +
end->m_len;
6098 po.m_line =
end->m_line;
6099 po.m_lastText =
nullptr;
6108template<
class Trait>
6111 typename Delims::iterator last,
6112 TextParsingOpts<Trait> &po,
6115 const auto nit = std::find_if(std::next(it), last, [](
const auto &d) {
6116 return (d.m_type == Delimiter::Greater);
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);
6126 for (
long long int i = 0; i < url.size(); ++i) {
6127 if (url[i].isSpace()) {
6135 if (!isValidUrl<Trait>(url) && !isEmail<Trait>(url)) {
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);
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);
6157 po.m_wasRefLink =
false;
6158 po.m_firstInParagraph =
false;
6159 po.m_lastText =
nullptr;
6162 po.m_pos = nit->m_pos + nit->m_len;
6163 po.m_line = nit->m_line;
6168 return checkForRawHtml(it, last, po);
6171 return checkForRawHtml(it, last, po);
6174 return checkForRawHtml(it, last, po);
6178template<
class Trait>
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)
6188 typename Trait::String c;
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)));
6195 if (po.m_line < lastLine) {
6196 c.push_back(Trait::latin1ToChar(
' '));
6202 po.m_line = lastLine;
6204 if (c[0] == Trait::latin1ToChar(
' ') && c[c.size() - 1] == Trait::latin1ToChar(
' ') &&
6205 skipSpaces<Trait>(0, c) < c.size()) {
6207 c.remove(c.size() - 1, 1);
6213 auto code = std::make_shared<Code<Trait>>(c,
false,
true);
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});
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);
6236 initLastItemWithOpts<Trait>(po, code);
6238 po.m_parent->appendItem(code);
6241 po.m_wasRefLink =
false;
6242 po.m_firstInParagraph =
false;
6243 po.m_lastText =
nullptr;
6246template<
class Trait>
6249 typename Delims::iterator last,
6250 TextParsingOpts<Trait> &po)
6252 const auto len = it->m_len;
6253 const auto start = it;
6255 po.m_wasRefLink =
false;
6256 po.m_firstInParagraph =
false;
6257 po.m_headingAllowed =
true;
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);
6266 if ((it->m_type == Delimiter::HorizontalLine && withoutSpaces[0] == Trait::latin1ToChar(
'-')) ||
6267 it->m_type == Delimiter::H1 || it->m_type == Delimiter::H2) {
6269 }
else if (it->m_type == Delimiter::InlineCode && (it->m_len - (it->m_backslashed ? 1 : 0)) == len) {
6272 if (!po.m_collectRefLinks) {
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);
6278 po.m_line = it->m_line;
6279 po.m_pos = it->m_pos + it->m_len;
6294template<
class Trait>
6297 typename Delims::iterator it,
6298 typename Delims::iterator last,
6299 TextParsingOpts<Trait> &po,
6300 bool doNotCreateTextOnFail,
6303 if (it != last && it->m_line <= po.m_lastTextLine) {
6304 if (
start->m_line == it->m_line) {
6306 const auto n = it->m_pos - p;
6309 long long int startPos, startLine, endPos, endLine;
6311 po.m_fr.m_data[
start->m_line].first.virginPos(
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);
6318 *pos = {startPos, startLine, endPos, endLine};
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};
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});
6328 long long int i =
start->m_line + 1;
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});
6335 res.push_back({po.m_fr.m_data.at(i).first, po.m_fr.m_data.at(i).second});
6340 long long int startPos, startLine, endPos, endLine;
6342 po.m_fr.m_data[
start->m_line].first.virginPos(
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);
6349 *pos = {startPos, startLine, endPos, endLine};
6355 if (!doNotCreateTextOnFail) {
6363template<
class Trait>
6366 typename Delims::iterator last,
6367 TextParsingOpts<Trait> &po,
6370 const auto start = it;
6372 long long int brackets = 0;
6374 const bool collectRefLinks = po.m_collectRefLinks;
6375 po.m_collectRefLinks =
true;
6376 long long int l = po.m_line, p = po.m_pos;
6378 for (it = std::next(it); it != last; ++it) {
6381 switch (it->m_type) {
6382 case Delimiter::SquareBracketsClose: {
6389 case Delimiter::SquareBracketsOpen:
6390 case Delimiter::ImageOpen:
6394 case Delimiter::InlineCode:
6395 it = checkForInlineCode(it, last, po);
6398 case Delimiter::Less:
6399 it = checkForAutolinkHtml(it, last, po,
false);
6411 const auto r = readTextBetweenSquareBrackets(
start, it, last, po,
false, pos);
6413 po.m_collectRefLinks = collectRefLinks;
6421template<
class Trait>
6424 typename Delims::iterator last,
6425 TextParsingOpts<Trait> &po,
6428 const auto start = it;
6430 for (it = std::next(it); it != last; ++it) {
6433 switch (it->m_type) {
6434 case Delimiter::SquareBracketsClose: {
6438 case Delimiter::SquareBracketsOpen:
6439 case Delimiter::ImageOpen: {
6452 return readTextBetweenSquareBrackets(
start, it, last, po,
true, pos);
6455template<
class Trait>
6456inline typename Trait::String
6459 typename Trait::String res;
6462 for (
const auto &s : d) {
6464 res.push_back(Trait::latin1ToChar(
' '));
6466 res.push_back(s.first.asString().simplified());
6473template<
class Trait>
6474inline std::shared_ptr<Link<Trait>>
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)
6488 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ?
6489 url : removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
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);
6498 if (!po.m_collectRefLinks) {
6499 po.m_linksToParse.push_back(url);
6502 if (!ref.isEmpty()) {
6503 url = ref + Trait::latin1ToString(
"/") + url;
6507 }
else if (Trait::fileExists(url, po.m_workingPath)) {
6508 url = Trait::absoluteFilePath(po.m_workingPath + Trait::latin1ToString(
"/") + url);
6510 if (!po.m_collectRefLinks) {
6511 po.m_linksToParse.push_back(url);
6514 if (!ref.isEmpty()) {
6515 url = ref + Trait::latin1ToString(
"/") + url;
6524 if (!checkForFile(u) && u.contains(Trait::latin1ToChar(
'#'))) {
6525 const auto i = u.indexOf(Trait::latin1ToChar(
'#'));
6526 const auto ref = u.sliced(i);
6529 if (!checkForFile(u, ref)) {
6534 u = u + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
6535 Trait::latin1ToString(
"/") + po.m_workingPath) + Trait::latin1ToString(
"/") +
6541 link->setOpts(po.m_opts);
6542 link->setTextPos(textPos);
6543 link->setUrlPos(urlPos);
6545 MdBlock<Trait> block = {text, 0};
6547 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6549 RawHtmlBlock<Trait> html;
6551 parseFormattedTextLinksImages(block,
6552 std::static_pointer_cast<Block<Trait>>(p),
6557 po.m_collectRefLinks,
6562 if (!p->isEmpty()) {
6563 std::shared_ptr<Image<Trait>> img;
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));
6568 for (
auto it = ip->items().cbegin(), last = ip->items().cend(); it != last; ++it) {
6569 switch ((*it)->type()) {
6570 case ItemType::Link:
6573 case ItemType::Image: {
6574 img = std::static_pointer_cast<Image<Trait>>(*it);
6590 if (html.m_html.get()) {
6591 link->p()->appendItem(html.m_html);
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);
6600 initLastItemWithOpts<Trait>(po, link);
6602 po.m_lastText =
nullptr;
6607template<
class Trait>
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)
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;
6625 po.m_wasRefLink =
false;
6626 po.m_firstInParagraph =
false;
6627 po.m_headingAllowed =
true;
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();
6633 const auto link = makeLink(u,
6634 (isLinkTextEmpty ? text : linkText),
6636 doNotCreateTextOnFail,
6640 lastIt->m_pos + lastIt->m_len,
6641 (isLinkTextEmpty ? textPos : linkTextPos),
6645 po.m_linksToParse.push_back(url);
6646 po.m_parent->appendItem(link);
6648 po.m_line = lastIt->m_line;
6649 po.m_pos = lastIt->m_pos + lastIt->m_len;
6651 if (!doNotCreateTextOnFail) {
6652 makeText(lastLineForText, lastPosForText, po);
6660 }
else if (!doNotCreateTextOnFail) {
6661 makeText(lastLineForText, lastPosForText, po);
6667template<
class Trait>
6668inline std::shared_ptr<Image<Trait>>
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)
6682 std::shared_ptr<Image<Trait>> img(
new Image<Trait>);
6684 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ? url :
6685 removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6687 if (Trait::fileExists(u)) {
6689 }
else if (Trait::fileExists(u, po.m_workingPath)) {
6690 img->setUrl(po.m_workingPath + Trait::latin1ToString(
"/") + u);
6695 MdBlock<Trait> block = {text, 0};
6697 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6699 RawHtmlBlock<Trait> html;
6701 parseFormattedTextLinksImages(block,
6702 std::static_pointer_cast<Block<Trait>>(p),
6707 po.m_collectRefLinks,
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)));
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);
6726 initLastItemWithOpts<Trait>(po, img);
6728 po.m_lastText =
nullptr;
6733template<
class Trait>
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)
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;
6751 po.m_wasRefLink =
false;
6752 po.m_firstInParagraph =
false;
6753 po.m_headingAllowed =
true;
6755 const auto iit = po.m_doc->labeledLinks().find(url);
6757 if (iit != po.m_doc->labeledLinks().cend()) {
6758 if (!po.m_collectRefLinks) {
6759 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6761 const auto img = makeImage(iit->second->url(),
6762 (isLinkTextEmpty ? text : linkText),
6764 doNotCreateTextOnFail,
6768 lastIt->m_pos + lastIt->m_len,
6769 (isLinkTextEmpty ? textPos : linkTextPos),
6772 po.m_parent->appendItem(img);
6774 po.m_line = lastIt->m_line;
6775 po.m_pos = lastIt->m_pos + lastIt->m_len;
6779 }
else if (!doNotCreateTextOnFail) {
6780 makeText(lastLineForText, lastPosForText, po);
6787template<
class Trait>
6795 if (pos == fr.at(line).first.length() && line + 1 < (
long long int)fr.size()) {
6802template<
class Trait>
6803inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6811 const auto destLine = line;
6812 const auto &s = po.
m_fr.m_data.at(line).first.asString();
6813 bool backslash =
false;
6816 if (s[pos] == Trait::latin1ToChar(
'<')) {
6820 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6821 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6824 const auto start = pos;
6826 while (pos < s.size()) {
6829 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6832 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'<')) {
6833 return {line, pos,
false, {}, destLine};
6834 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'>')) {
6845 if (pos < s.size() && s[pos] == Trait::latin1ToChar(
'>')) {
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);
6853 return {line, pos,
true, s.sliced(
start, pos -
start - 1), destLine};
6855 return {line, pos,
false, {}, destLine};
6858 long long int pc = 0;
6860 const auto start = pos;
6863 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6864 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6867 while (pos < s.size()) {
6870 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6873 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
' ')) {
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);
6880 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6882 return {line, pos,
false, {}, destLine};
6884 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'(')) {
6886 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
')')) {
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);
6893 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
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);
6911 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6914 return {line, pos,
false, {}, destLine};
6919template<
class Trait>
6920inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
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);
6928 const auto firstLine = line;
6932 if (pos >= po.
m_fr.m_data.at(line).first.length()) {
6933 return {line, pos,
true, {}, firstLine};
6936 const auto sc = po.
m_fr.m_data.at(line).first[pos];
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};
6945 if (sc == Trait::latin1ToChar(
')')) {
6949 const auto startLine = line;
6951 bool backslash =
false;
6957 typename Trait::String title;
6959 while (line < (
long long int)po.
m_fr.m_data.size() && pos < po.
m_fr.m_data.at(line).first.length()) {
6962 if (po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6965 }
else if (sc == Trait::latin1ToChar(
'(') &&
6966 po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
')') && !backslash) {
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) {
6976 title.push_back(po.
m_fr.m_data.at(line).first[pos]);
6985 if (pos == po.
m_fr.m_data.at(line).first.length()) {
6990 return {line, pos,
false, {}, startLine};
6993template<
class Trait>
6994inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
6996 typename Delims::iterator last,
6997 TextParsingOpts<Trait> &po,
6998 WithPosition *urlPos)
7000 long long int p = it->m_pos + it->m_len;
7001 long long int l = it->m_line;
7003 typename Trait::String dest, title;
7004 long long int destStartLine = 0;
7006 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
7009 return {{}, {}, it,
false};
7012 long long int s = 0;
7014 std::tie(l, p, ok, title, s) = readLinkTitle<Trait>(l, p, po);
7016 skipSpacesUpTo1Line<Trait>(l, p, po.m_fr.m_data);
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};
7023 for (; it != last; ++it) {
7024 if (it->m_line == l && it->m_pos == p) {
7025 return {dest, title, it,
true};
7029 return {{}, {}, it,
false};
7032template<
class Trait>
7033inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
7035 typename Delims::iterator last,
7036 TextParsingOpts<Trait> &po,
7037 WithPosition *urlPos)
7039 long long int p = it->m_pos + it->m_len + 1;
7040 long long int l = it->m_line;
7042 typename Trait::String dest, title;
7043 long long int destStartLine = 0;
7045 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
7048 return {{}, {}, it,
false};
7051 long long int titleStartLine = 0;
7053 std::tie(l, p, ok, title, titleStartLine) = readLinkTitle<Trait>(l, p, po);
7056 return {{}, {}, it,
false};
7059 if (!title.isEmpty()) {
7060 p = skipSpaces<Trait>(p, po.m_fr.m_data.at(l).first.asString());
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()) {
7066 p = po.m_fr.m_data.at(l).first.length();
7071 for (; it != last; ++it) {
7072 if (it->m_line > l || (it->m_line == l && it->m_pos >= p)) {
7080 return {dest, title, std::prev(it),
true};
7083template<
class Trait>
7086 typename Delims::iterator last,
7087 TextParsingOpts<Trait> &po)
7089 const auto start = it;
7091 typename MdBlock<Trait>::Data text;
7093 po.m_wasRefLink =
false;
7094 po.m_firstInParagraph =
false;
7095 po.m_headingAllowed =
true;
7097 WithPosition textPos;
7098 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
7101 if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
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;
7108 WithPosition urlPos;
7109 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
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));
7118 po.m_line = iit->m_line;
7119 po.m_pos = iit->m_pos + iit->m_len;
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, {})) {
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;
7132 WithPosition labelPos;
7133 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7135 if (lit != std::next(it)) {
7136 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7139 && createShortcutImage(label,
7151 }
else if (isLabelEmpty
7152 && createShortcutImage(text,
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, {})) {
7180template<
class Trait>
7183 typename Delims::iterator last,
7184 TextParsingOpts<Trait> &po)
7186 const auto start = it;
7188 typename MdBlock<Trait>::Data text;
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;
7196 const auto ns = skipSpaces<Trait>(0, po.m_fr.m_data.at(po.m_line).first.asString());
7198 WithPosition textPos;
7199 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
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);
7217 typename Trait::String fnrText = Trait::latin1ToString(
"[");
7218 bool firstFnrText =
true;
7220 for (
const auto &t : text) {
7221 if (!firstFnrText) {
7222 fnrText.push_back(Trait::latin1ToString(
"\n"));
7225 firstFnrText =
false;
7227 fnrText.push_back(t.first.asString());
7230 fnrText.push_back(Trait::latin1ToString(
"]"));
7231 fnr->setText(fnrText);
7232 po.m_parent->appendItem(fnr);
7234 initLastItemWithOpts<Trait>(po, fnr);
7237 po.m_line = it->m_line;
7238 po.m_pos = it->m_pos + it->m_len;
7241 }
else if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7243 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
':')) {
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;
7250 WithPosition labelPos;
7252 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
7254 if (it !=
start && !toSingleLine(text).simplified().isEmpty()) {
7255 WithPosition urlPos;
7256 std::tie(url, title, iit, ok) = checkForRefLink(it, last, po, &urlPos);
7259 const auto label = Trait::latin1ToString(
"#") +
7261 Trait::latin1ToString(
"/") +
7262 (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7263 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
7266 link->setStartColumn(po.m_fr.m_data.at(
start->m_line).first.virginPos(
7268 link->setStartLine(po.m_fr.m_data.at(
start->m_line).second.m_lineNumber);
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);
7274 link->setEndColumn(endPos.first);
7275 link->setEndLine(endPos.second);
7277 link->setTextPos(labelPos);
7278 link->setUrlPos(urlPos);
7280 url = removeBackslashes<typename Trait::String, Trait>(
7281 replaceEntity<Trait>(url));
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);
7295 po.m_wasRefLink =
true;
7296 po.m_headingAllowed =
false;
7298 if (po.m_doc->labeledLinks().find(label) == po.m_doc->labeledLinks().cend()) {
7299 po.m_doc->insertLabeledLink(label, link);
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;
7319 WithPosition urlPos;
7320 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7323 const auto link = makeLink(url,
7330 iit->m_pos + iit->m_len,
7335 if (!po.m_collectRefLinks) {
7336 po.m_parent->appendItem(link);
7339 po.m_line = iit->m_line;
7340 po.m_pos = iit->m_pos + iit->m_len;
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, {})) {
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;
7356 WithPosition labelPos;
7357 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7359 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7361 if (lit != std::next(it)) {
7363 && createShortcutLink(label,
7375 }
else if (isLabelEmpty
7376 && createShortcutLink(text,
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, {})) {
7405template<
class Trait>
7410 const auto it = std::find_if(styles.crbegin(), styles.crend(), [&](
const auto &p) {
7411 return (p.m_style == s);
7414 if (it != styles.crend()) {
7415 styles.erase(it.base() - 1);
7420template<
class Trait>
7427 for (
const auto &s : styles) {
7428 switch (s.m_style) {
7449template<
class Trait>
7454 case Delimiter::Strikethrough:
7457 case Delimiter::Emphasis1:
7460 case Delimiter::Emphasis2:
7468template<
class Trait>
7471 typename Delimiter::DelimiterType t,
7472 long long int style)
7474 if (t != Delimiter::Strikethrough) {
7475 if (style % 2 == 1) {
7476 styles.push_back({t == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2, 1});
7480 for (
long long int i = 0; i < style / 2; ++i) {
7481 styles.push_back({t == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2, 2});
7485 styles.push_back({Style::Strikethrough, style});
7489template<
class Trait>
7490inline std::vector<std::pair<Style, long long int>>
7492 const std::vector<long long int> &styles,
7493 long long int lastStyle)
7495 std::vector<std::pair<Style, long long int>> ret;
7497 createStyles(ret, t, lastStyle);
7499 for (
auto it = styles.crbegin(), last = styles.crend(); it != last; ++it) {
7500 createStyles(ret, t, *it);
7506template<
class Trait>
7509 long long int itLine,
7510 long long int itPos,
7511 typename Delimiter::DelimiterType t)
7513 return (itLine == it->m_line && itPos + it->m_len == it->m_pos && it->m_type == t);
7516template<
class Trait>
7519 typename Delims::iterator last,
7520 long long int &line,
7523 long long int &itCount)
7528 const auto t = it->m_type;
7533 while (it != last && isSequence(it, line, pos, t)) {
7541 return std::prev(it);
7547 return ((((i1 + i2) % 3) == 0) && !((i1 % 3 == 0) && (i2 % 3 == 0)));
7550template<
class Trait>
7551inline std::tuple<bool, std::vector<std::pair<Style, long long int>>,
long long int,
long long int>
7553 typename Delims::iterator it,
7554 typename Delims::iterator last,
7555 typename Delims::iterator &stackBottom,
7556 TextParsingOpts<Trait> &po)
7558 const auto open = it;
7559 long long int openPos, openLength, itCount, lengthFromIt, tmp;
7561 it = std::next(readSequence(first, open, last, openPos, openLength, tmp, lengthFromIt, itCount).second);
7563 const auto length = lengthFromIt;
7564 long long int itLine, itPos, itLength;
7566 struct RollbackValues {
7567 RollbackValues(TextParsingOpts<Trait> &po,
7570 bool collectRefLinks,
7571 typename Delims::iterator &stackBottom,
7572 typename Delims::iterator last)
7576 , m_collectRefLinks(collectRefLinks)
7577 , m_stackBottom(stackBottom)
7583 void setIterator(
typename Delims::iterator it)
7590 m_po.m_line = m_line;
7592 m_po.m_collectRefLinks = m_collectRefLinks;
7594 if (m_it != m_last && (m_it > m_stackBottom || m_stackBottom == m_last)) {
7595 m_stackBottom = m_it;
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;
7608 RollbackValues rollback(po, po.m_line, po.m_pos, po.m_collectRefLinks, stackBottom, last);
7610 po.m_collectRefLinks =
true;
7612 std::vector<long long int> styles;
7615 std::vector<typename Delims::iterator> m_its;
7616 long long int m_length;
7619 std::vector<Opener> openers;
7621 std::function<void(
long long int,
long long int)> dropOpeners;
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; });
7636 auto tryCloseEmphasis = [&dropOpeners,
this, &openers, &
open](
typename Delims::iterator first,
7637 typename Delims::iterator it,
7638 typename Delims::iterator last) ->
bool
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;
7645 it = this->readSequence(first, it, last, tmp1, closeLength, tmp2, tmp3, tmp4).first;
7648 long long int tmpLength = closeLength;
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);
7659 case Delimiter::Emphasis1:
7660 case Delimiter::Emphasis2:
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);
7666 if ((both || (it->m_leftFlanking && it->m_rightFlanking)) &&
isMult3(len, closeLength)) {
7670 dropOpeners(pos - len, it->m_line);
7672 if (tmpLength >= len) {
7675 if (
open->m_type == it->m_type) {
7683 if (
open->m_type == it->m_type) {
7684 openers.back().m_length -= tmpLength;
7704 auto fillIterators = [](
typename Delims::iterator first,
7705 typename Delims::iterator last) -> std::vector<typename Delims::iterator>
7707 std::vector<typename Delims::iterator> res;
7709 for (; first != last; ++first) {
7710 res.push_back(first);
7713 res.push_back(last);
7718 for (; it != last; ++it) {
7719 if (it > stackBottom) {
7720 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7723 if (it->m_line <= po.m_lastTextLine) {
7724 po.m_line = it->m_line;
7725 po.m_pos = it->m_pos;
7727 switch (it->m_type) {
7728 case Delimiter::SquareBracketsOpen:
7729 it = checkForLink(it, last, po);
7732 case Delimiter::ImageOpen:
7733 it = checkForImage(it, last, po);
7736 case Delimiter::Less:
7737 it = checkForAutolinkHtml(it, last, po,
false);
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});
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);
7755 if (it->m_rightFlanking) {
7756 bool notCheck = (
open->m_leftFlanking &&
open->m_rightFlanking) || itBoth;
7758 long long int count;
7760 it = readSequence(it, last, itLine, itPos, itLength, count);
7763 notCheck =
isMult3(openLength, itLength);
7766 if (!openers.empty()) {
7767 long long int i = openers.size() - 1;
7768 auto &top = openers[i];
7770 while (!openers.empty()) {
7777 if ((itBoth || (top.m_its.front()->m_rightFlanking && top.m_its.front()->m_leftFlanking))
7778 &&
isMult3(itLength, top.m_length)) {
7783 if (top.m_length <= itLength) {
7784 itLength -= top.m_length;
7785 openers.erase(openers.begin() + i);
7787 top.m_length -= itLength;
7801 if (itLength >= lengthFromIt) {
7802 rollback.setIterator(it);
7803 return {
true, createStyles(
open->m_type, styles, lengthFromIt), length, itCount};
7805 styles.push_back(itLength);
7806 lengthFromIt -= itLength;
7808 }
else if (firstIt->m_leftFlanking) {
7809 openers.push_back({fillIterators(firstIt, it), itLength});
7813 long long int count;
7815 it = readSequence(it, last, itLine, itPos, itLength, count);
7816 openers.push_back({fillIterators(firstIt, it), itLength});
7818 }
else if (it->m_rightFlanking) {
7819 tryCloseEmphasis(open, it, last);
7823 case Delimiter::InlineCode:
7824 it = checkForInlineCode(it, last, po);
7835 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7838template<
class Trait>
7841 typename Delims::iterator last,
7842 long long int count)
7844 const auto len = std::distance(it, last);
7849 return it + (len - 1);
7854template<
class Trait>
7864template<
class Trait>
7867 typename Delims::iterator it,
7868 typename Delims::iterator last,
7870 long long int &length,
7871 long long int &itCount,
7872 long long int &lengthFromIt,
7873 long long int &itCountFromIt)
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;
7882 auto retItLast = std::next(it);
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;
7894 length = lengthFromIt;
7895 itCount = itCountFromIt;
7897 auto retItFirst = it;
7898 bool useNext =
false;
7900 if (retItFirst != first) {
7901 retItFirst = std::prev(retItFirst);
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;
7915 if (retItFirst == first) {
7921 return {useNext ? std::next(retItFirst) : retItFirst, std::prev(retItLast)};
7924template<
class Trait>
7927 typename Delims::iterator it,
7928 typename Delims::iterator last,
7929 typename Delims::iterator &stackBottom,
7930 TextParsingOpts<Trait> &po)
7932 long long int count = 1;
7934 po.m_wasRefLink =
false;
7935 po.m_firstInParagraph =
false;
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;
7942 long long int opened = 0;
7943 bool bothFlanking =
false;
7945 for (
auto it = po.m_styles.crbegin(), last = po.m_styles.crend(); it != last; ++it) {
7946 bool doBreak =
false;
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;
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;
7965 case Delimiter::Strikethrough: {
7966 if (it->m_style == Style::Strikethrough) {
7967 opened = it->m_length;
7968 bothFlanking = it->m_bothFlanking;
7981 const bool sumMult3 = (it->m_leftFlanking || bothFlanking ?
isMult3(opened, len) : false);
7983 if (count && opened && !sumMult3) {
7984 if (count > opened) {
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;
7991 if (it->m_type == Delimiter::Strikethrough) {
7992 const auto len = it->m_len;
7994 for (
auto i = 0; i < count; ++i) {
7995 closeStyle<Trait>(po.m_styles, Style::Strikethrough);
8000 if (count % 2 == 1) {
8001 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2);
8003 closeStyle<Trait>(po.m_styles, st);
8009 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2);
8011 for (
auto i = 0; i < count / 2; ++i) {
8012 closeStyle<Trait>(po.m_styles, st);
8019 applyStyles<Trait>(po.m_opts, po.m_styles);
8021 const auto j = incrementIterator(it, last, count - 1);
8023 po.m_pos = j->m_pos + j->m_len;
8024 po.m_line = j->m_line;
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;
8041 if (it > stackBottom) {
8047 long long int tmp1, tmp2, tmp3;
8048 readSequence(it, last, tmp1, tmp2, len, tmp3);
8050 std::tie(closed, styles, len, count) = isStyleClosed(first, it, last, stackBottom, po);
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;
8057 for (
const auto &p : styles) {
8058 po.m_styles.push_back({p.first, p.second, it->m_leftFlanking && it->m_rightFlanking});
8060 if (!po.m_collectRefLinks) {
8062 pos + p.second - 1, line});
8068 po.m_pos = it->m_pos + len;
8069 po.m_line = it->m_line;
8071 applyStyles<Trait>(po.m_opts, po.m_styles);
8073 makeText(it->m_line, it->m_pos + len, po);
8078 makeText(it->m_line, it->m_pos + it->m_len, po);
8089 return incrementIterator(it, last, count - 1);
8093template<
class Trait>
8094inline std::shared_ptr<Text<Trait>>
8100 t->setStartColumn((*it)->startColumn());
8101 t->setStartLine((*it)->startLine());
8105 typename Trait::String data;
8107 for (; it != last; ++it) {
8108 const auto tt = std::static_pointer_cast<Text<Trait>>(*it);
8110 data.push_back(tt->text());
8112 if (!tt->openStyles().empty()) {
8113 std::copy(tt->openStyles().cbegin(), tt->openStyles().cend(),
8114 std::back_inserter(t->openStyles()));
8117 if (!tt->closeStyles().empty()) {
8118 std::copy(tt->closeStyles().cbegin(), tt->closeStyles().cend(),
8119 std::back_inserter(close));
8126 t->setEndColumn((*it)->endColumn());
8127 t->setEndLine((*it)->endLine());
8128 t->closeStyles() = close;
8162template<
class Trait>
8163inline std::shared_ptr<Paragraph<Trait>>
8169 np->setStartColumn(p->startColumn());
8170 np->setStartLine(p->startLine());
8171 np->setEndColumn(p->endColumn());
8172 np->setEndLine(p->endLine());
8175 auto start = p->items().cend();
8176 long long int line = -1;
8177 long long int auxStart = 0, auxIt = 0;
8178 bool finished =
false;
8180 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8182 const auto t = std::static_pointer_cast<Text<Trait>>(*it);
8184 if (
start == last) {
8187 line = t->endLine();
8190 if (opts != t->opts() || t->startLine() != line || finished ||
8194 auxIt = auxIt - (auxIt - auxStart) + 1;
8201 line = t->endLine();
8212 if (
start != last) {
8215 auxIt = auxIt - (auxIt - auxStart) + 1;
8222 line = (*it)->endLine();
8225 np->appendItem((*it));
8229 if (
start != p->items().cend()) {
8245 long long int &line,
8246 long long int length,
8247 long long int linesCount)
8249 if (pos != 0 && line < linesCount && pos == length) {
8256template<
class Trait>
8257inline std::shared_ptr<Paragraph<Trait>>
8261 auto p = std::make_shared<Paragraph<Trait>>();
8263 p->setStartColumn((*first)->startColumn());
8264 p->setStartLine((*first)->startLine());
8266 for (; first != last; ++first) {
8267 p->appendItem(*first);
8268 p->setEndColumn((*first)->endColumn());
8269 p->setEndLine((*first)->endLine());
8276template<
class Trait>
8277inline std::shared_ptr<Paragraph<Trait>>
8281 bool collectRefLinks,
8282 bool fullyOptimizeParagraphs =
true)
8284 auto first = p->items().cbegin();
8286 auto last = p->items().cend();
8288 for (; it != last; ++it) {
8289 if (first == last) {
8297 if (!collectRefLinks) {
8298 if (!p->isEmpty()) {
8300 fullyOptimizeParagraphs ?
8305 parent->appendItem(*it);
8312 if (first != last) {
8313 if (first != p->items().cbegin()) {
8314 const auto c = std::count_if(first, last, [](
const auto &i) {
8327 return std::make_shared<Paragraph<Trait>>();
8332template<
class Trait>
8336 switch (item->
type()) {
8345 if (!i->closeStyles().empty()) {
8346 return i->closeStyles().back().endColumn();
8348 return i->endColumn();
8358 if (!c->closeStyles().empty()) {
8359 return c->closeStyles().back().endColumn();
8361 return c->endDelim().endColumn();
8372template<
class Trait>
8377 long long int lastColumn,
8378 long long int lastLine,
8380 const typename Trait::String &workingPath,
8381 const typename Trait::String &fileName,
8382 bool collectRefLinks,
8386 if (!collectRefLinks) {
8388 auto lb = std::static_pointer_cast<LineBreak<Trait>>(p->items().back());
8389 const auto lineBreakBySpaces = lb->text().simplified().isEmpty();
8394 if (!p->isEmpty()) {
8396 auto lt = std::static_pointer_cast<Text<Trait>>(p->items().back());
8398 if (!lineBreakBySpaces) {
8399 auto text = po.
m_fr.m_data.at(lineBreakPos.second).first.fullVirginString().sliced(
8403 if (!lt->text()[0].isSpace()) {
8406 text.remove(0, notSpacePos);
8412 lt->setEndColumn(lt->endColumn() + lb->text().length());
8414 if (!lineBreakBySpaces) {
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());
8430 po.
m_rawTextData.push_back({lb->text(), pos.first, pos.second});
8436 std::pair<typename Trait::String, WithPosition> label;
8439 auto t = std::static_pointer_cast<Text<Trait>>(p->items().back());
8443 typename Trait::InternalString tmp(text.m_str);
8449 if (tmp.asString().simplified().isEmpty()) {
8450 p->removeItemAt(p->items().size() - 1);
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());
8460 const auto virginLine = t->endLine();
8462 if (label.second.startColumn() > notSpacePos) {
8463 auto text = tmp.fullVirginString().sliced(0, label.second.startColumn());
8466 if (!t->text()[0].isSpace()) {
8469 text.remove(0, notSpacePos);
8473 t->setEndColumn(label.second.startColumn() - 1);
8475 const auto lastPos = t->endColumn();
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);
8486 po.
m_rawTextData.push_back({po.
m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8487 pos.first, pos.second});
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};
8499 if (!text.simplified().isEmpty()) {
8500 if (p->items().size() == 1) {
8506 t->setStartColumn(label.second.endColumn() + 1);
8510 p->removeItemAt(p->items().size() - 1);
8514 p->setEndColumn(t->endColumn());
8520 label.second.setStartLine(t->startLine());
8521 label.second.setEndLine(t->endLine());
8526 h->setStartColumn(p->startColumn());
8527 h->setStartLine(p->startLine());
8528 h->setEndColumn(lastColumn);
8529 h->setEndLine(lastLine);
8532 if (!p->items().empty()) {
8536 h->setDelims({delim});
8541 h->setLabelPos(label.second);
8545 const auto path = Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
8546 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName;
8548 h->setLabel(label.
first + path);
8550 doc->insertLabeledHeading(label.
first + path, h);
8551 h->labelVariants().
push_back(h->label());
8554 doc->insertLabeledHeading(label.
first.
toLower() + path, h);
8555 h->labelVariants().push_back(label.
first.
toLower() + path);
8559 parent->appendItem(h);
8564template<
class Trait>
8571 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8574 return std::distance(p->items().cbegin(), it);
8585template<
class Trait>
8592 for (
const auto &plugin : textPlugins) {
8593 if (inLink && !std::get<bool>(plugin.second)) {
8597 std::get<TextPluginFunc<Trait>>(plugin.second)(p, po,
8598 std::get<typename Trait::StringList>(plugin.second));
8603template<
class 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);
8616template<
class Trait>
8619 std::shared_ptr<Block<Trait>> parent,
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,
8630 if (fr.m_data.empty()) {
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);
8638 auto delims = collectDelimiters(fr.m_data);
8640 TextParsingOpts<Trait> po = {fr, p,
nullptr, doc, linksToParse, workingPath, fileName,
8641 collectRefLinks, ignoreLineBreak, html, m_textPlugins};
8642 typename Delims::iterator styleStackBottom = delims.end();
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) {
8652 if (po.shouldStopParsing() && po.m_lastTextLine < it->m_line) {
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);
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);
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);
8672 case Delimiter::Less: {
8673 it = checkForAutolinkHtml(it, last, po,
true);
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);
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);
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);
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);
8705 case Delimiter::HorizontalLine: {
8706 po.m_wasRefLink =
false;
8707 po.m_firstInParagraph =
false;
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);
8712 auto h2 = isH2<Trait>(withoutSpaces);
8714 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8716 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
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);
8726 if (!h2 || !po.m_headingAllowed) {
8727 if (!collectRefLinks && !p->isEmpty()) {
8728 parent->appendItem(p);
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,
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},
8749 po.m_checkLineOnNewType =
true;
8752 p.reset(
new Paragraph<Trait>);
8753 po.m_rawTextData.clear();
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);
8761 po.m_line = it->m_line;
8762 po.m_pos = it->m_pos + it->m_len;
8764 if (!h2 && !collectRefLinks) {
8765 makeHorLine<Trait>(fr.m_data[it->m_line], parent);
8770 case Delimiter::H2: {
8771 po.m_wasRefLink =
false;
8772 po.m_firstInParagraph =
false;
8774 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8776 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
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);
8785 m_fullyOptimizeParagraphs);
8787 if (po.m_headingAllowed) {
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,
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},
8805 po.m_checkLineOnNewType =
true;
8807 p.reset(
new Paragraph<Trait>);
8808 po.m_rawTextData.clear();
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);
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);
8826 if (!po.shouldStopParsing()) {
8827 po.m_wasRefLink =
false;
8828 po.m_firstInParagraph =
false;
8830 makeText(it->m_line, it->m_pos + it->m_len, po);
8835 if (po.shouldStopParsing()) {
8839 if (po.m_checkLineOnNewType) {
8840 if (po.m_line + 1 <
static_cast<long long int>(po.m_fr.m_data.size())) {
8843 bool doBreak =
false;
8847 po.m_detected = TextParsingOpts<Trait>::Detected::Code;
8853 po.m_detected = TextParsingOpts<Trait>::Detected::List;
8858 po.m_detected = TextParsingOpts<Trait>::Detected::Blockquote;
8871 po.m_checkLineOnNewType =
false;
8876 if (po.m_lastTextLine == -1) {
8880 switch(po.m_detected) {
8881 case TextParsingOpts<Trait>::Detected::Table:
8882 makeText(po.m_lastTextLine, po.m_lastTextPos, po);
8885 case TextParsingOpts<Trait>::Detected::Nothing:
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);
8897 if (!p->isEmpty()) {
8898 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8900 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8904 if (!p->isEmpty() && !collectRefLinks) {
8905 parent->appendItem(optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()));
8908 po.m_rawTextData.clear();
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());
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;
8923template<
class Trait>
8926 std::shared_ptr<Block<Trait>>,
8928 typename Trait::StringList &linksToParse,
8929 const typename Trait::String &workingPath,
8930 const typename Trait::String &fileName,
8931 bool collectRefLinks)
8934 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
8935 return !s.first.isEmpty();
8938 if (it != fr.m_data.end()) {
8939 fr.m_data.erase(it, fr.m_data.end());
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);
8950 auto delims = collectDelimiters(fr.m_data);
8952 RawHtmlBlock<Trait> html;
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();
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();
8964 po.m_line = delims.cbegin()->m_line;
8965 po.m_pos = delims.cbegin()->m_pos;
8967 std::tie(
id, it) = checkForLinkText(delims.begin(), delims.end(), po);
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});
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));
8987 fr.m_data.front().first = fr.m_data.front().first.sliced(it->m_pos + 3);
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);
8995 StringListStream<Trait> stream(fr.m_data);
8997 parse(stream, f, doc, linksToParse, workingPath, fileName, collectRefLinks);
8999 if (!f->isEmpty()) {
9000 doc->insertFootnote(Trait::latin1ToString(
"#") + toSingleLine(
id) +
9001 Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
9002 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName,
9010template<
class Trait>
9013 std::shared_ptr<Block<Trait>> parent,
9015 typename Trait::StringList &linksToParse,
9016 const typename Trait::String &workingPath,
9017 const typename Trait::String &fileName,
9018 bool collectRefLinks,
9019 RawHtmlBlock<Trait> &)
9021 const long long int pos = fr.m_data.front().first.asString().indexOf(Trait::latin1ToChar(
'>'));
9022 long long int extra = 0;
9024 long long int line = -1;
9027 typename Blockquote<Trait>::Delims delims;
9029 long long int i = 0, j = 0;
9031 BlockType bt = BlockType::EmptyLine;
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);
9038 const auto dp = it->first.virginPos(gt);
9039 delims.push_back({dp, it->second.m_lineNumber, dp, it->second.m_lineNumber});
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;
9046 it->first = it->first.sliced(gt + (it->first.length() > gt + 1 ?
9047 (it->first[gt + 1] == Trait::latin1ToChar(
' ') ? 1 : 0) : 0) + 1);
9049 bt = whatIsTheLine(it->first);
9053 if (ns < 4 && isHorizontalLine<Trait>(it->first.asString().sliced(ns))) {
9054 line = it->second.m_lineNumber;
9058 const auto tmpBt = whatIsTheLine(it->first);
9060 if (isListType(tmpBt)) {
9061 line = it->second.m_lineNumber;
9065 if (bt == BlockType::Text) {
9066 if (isH1<Trait>(it->first.asString())) {
9067 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
9069 it->first.insert(p, Trait::latin1ToChar(
'\\'));
9072 }
else if (isH2<Trait>(it->first.asString())) {
9073 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'-'));
9075 it->first.insert(p, Trait::latin1ToChar(
'\\'));
9079 }
else if ((bt == BlockType::Code || bt == BlockType::CodeIndentedBySpaces) &&
9080 it->second.m_mayBreakList) {
9081 line = it->second.m_lineNumber;
9085 if ((bt == BlockType::Text || bt == BlockType::Blockquote || bt == BlockType::List)
9086 && (tmpBt == BlockType::Text || tmpBt == BlockType::CodeIndentedBySpaces)) {
9089 line = it->second.m_lineNumber;
9095 typename MdBlock<Trait>::Data tmp;
9097 for (; j < i; ++j) {
9098 tmp.push_back(fr.m_data.at(j));
9101 StringListStream<Trait> stream(tmp);
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;
9110 parse(stream, bq, doc, linksToParse, workingPath, fileName, collectRefLinks);
9112 if (!collectRefLinks) {
9113 parent->appendItem(bq);
9121template<
class Trait>
9124 long long int indent)
9128 if (p >= indent || p == s.size()) {
9134 if (p + 1 >= s.size()) {
9137 space = s[p + 1].isSpace();
9141 if (s[p] == Trait::latin1ToChar(
'*') && space) {
9143 }
else if (s[p] == Trait::latin1ToChar(
'-') && space) {
9145 }
else if (s[p] == Trait::latin1ToChar(
'+') && space) {
9155template<
class Trait>
9156inline std::pair<long long int, long long int>
9164template<
class Trait>
9165inline std::tuple<bool, long long int, typename Trait::Char, bool>
9171 if (p == s.size()) {
9172 return {
false, 0,
typename Trait::Char(),
false};
9177 if (p + 1 >= s.size()) {
9180 space = s[p + 1].isSpace();
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(
'-')) {
9189 return {
false, p + 2, Trait::latin1ToChar(
'-'),
false};
9191 return {
true, p + 2, Trait::latin1ToChar(
'-'),
9192 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
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};
9199 typename Trait::Char c;
9202 return {
true, p + l + 2, c,
9203 p + l + 2 < s.size() ? !s.sliced(p + l + 2).isEmpty() :
false};
9205 return {
false, 0,
typename Trait::Char(),
false};
9210 return {
false, 0,
typename Trait::Char(),
false};
9214template<
class Trait>
9220 item->setEndColumn(pos);
9221 item->setEndLine(line);
9225template<
class Trait>
9233 for (
auto &i : it->second) {
9234 i.first->setEndColumn(html.
m_html->endColumn());
9235 i.first->setEndLine(html.
m_html->endLine());
9241template<
class Trait>
9244 std::shared_ptr<Block<Trait>> parent,
9246 typename Trait::StringList &linksToParse,
9247 const typename Trait::String &workingPath,
9248 const typename Trait::String &fileName,
9249 bool collectRefLinks,
9250 RawHtmlBlock<Trait> &html)
9252 bool resetTopParent =
false;
9253 long long int line = -1;
9255 if (!html.m_topParent) {
9256 html.m_topParent = parent;
9257 resetTopParent =
true;
9260 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9262 if (p != fr.m_data.front().first.length()) {
9263 std::shared_ptr<List<Trait>>
list(
new List<Trait>);
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);
9272 long long int indent = 0;
9273 typename Trait::Char marker;
9275 std::tie(std::ignore, indent, marker, std::ignore) =
9276 listItemData<Trait>(listItem.front().first.asString(),
false);
9278 html.m_blocks.push_back({
list,
list->startColumn() + indent});
9280 if (!collectRefLinks) {
9281 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
9284 bool updateIndent =
false;
9286 auto addListMakeNew = [&]() {
9288 parent->appendItem(list);
9291 html.m_blocks.pop_back();
9293 list.reset(
new List<Trait>);
9297 if (!collectRefLinks) {
9298 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
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()));
9308 if (!html.m_parent) {
9309 html.m_parent = html.m_topParent;
9312 if (html.m_parent != resItem) {
9316 const auto continueHtml = html.m_onLine && html.m_continueHtml && html.m_parent == html.m_topParent;
9318 if (!collectRefLinks) {
9319 if (!continueHtml) {
9320 html.m_parent->appendItem(html.m_html);
9323 updateLastPosInList<Trait>(html);
9326 if (!continueHtml) {
9327 resetHtmlTag<Trait>(html);
9332 auto processListItem = [&]() {
9333 MdBlock<Trait> block = {listItem, 0};
9335 std::shared_ptr<ListItem<Trait>> resItem;
9337 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9338 collectRefLinks, html, &resItem);
9342 processLastHtml(resItem);
9343 }
else if (line >= 0) {
9348 for (
auto last = fr.m_data.end(); it != last; ++it) {
9350 std::tie(std::ignore, indent, marker, std::ignore) =
9351 listItemData<Trait>(it->first.asString(),
false);
9353 if (!collectRefLinks) {
9354 html.m_blocks.back().second = indent;
9357 updateIndent =
false;
9360 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9362 if (isH1<Trait>(it->first.asString().sliced(ns)) && ns < indent && !listItem.empty()) {
9363 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
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;
9376 if (!collectRefLinks) {
9377 makeHorLine<Trait>(*it, parent);
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);
9389 if (tmpMarker != marker) {
9402 listItem.push_back(*it);
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);
9410 if (!collectRefLinks) {
9411 html.m_blocks.back().second +=
list->startColumn();
9416 if (!listItem.empty()) {
9417 MdBlock<Trait> block = {listItem, 0};
9418 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9419 collectRefLinks, html);
9423 parent->appendItem(list);
9426 html.m_blocks.pop_back();
9429 if (resetTopParent) {
9430 html.m_topParent.reset();
9436template<
class Trait>
9439 std::shared_ptr<Block<Trait>> parent,
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)
9449 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
9450 return !s.first.isEmpty();
9453 if (it != fr.m_data.end()) {
9454 fr.m_data.erase(it, fr.m_data.end());
9458 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9460 std::shared_ptr<ListItem<Trait>> item(
new ListItem<Trait>);
9462 item->setStartColumn(fr.m_data.front().first.virginPos(p));
9463 item->setStartLine(fr.m_data.front().second.m_lineNumber);
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()});
9472 item->setListType(ListItem<Trait>::Unordered);
9473 item->setDelim({item->startColumn(), item->startLine(), item->startColumn(), item->startLine()});
9476 if (item->listType() == ListItem<Trait>::Ordered) {
9477 item->setOrderedListPreState(i == 1 ? ListItem<Trait>::Start : ListItem<Trait>::Continue);
9480 typename MdBlock<Trait>::Data data;
9482 auto it = fr.m_data.begin();
9487 long long int indent = 0;
9488 bool wasText =
false;
9490 std::tie(std::ignore, indent, std::ignore, wasText) =
9491 listItemData<Trait>(fr.m_data.front().first.asString(), wasText);
9493 html.m_blocks.push_back({item, item->startColumn() + indent});
9495 if (!collectRefLinks) {
9496 html.m_toAdjustLastPos.insert({item, html.m_blocks});
9499 const auto firstNonSpacePos = calculateIndent<Trait>(
9500 fr.m_data.front().first.asString(), indent).second;
9502 if (firstNonSpacePos - indent < 4) {
9503 indent = firstNonSpacePos;
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});
9511 bool taskList =
false;
9512 bool checked =
false;
9514 if (!data.empty()) {
9515 auto p = skipSpaces<Trait>(0, data.front().first.asString());
9517 if (p < data.front().first.length()) {
9518 if (data.front().first[p] == Trait::latin1ToChar(
'[')) {
9519 const auto startTaskDelimPos = data.front().first.virginPos(p);
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')) {
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()});
9538 data[0].first = data[0].first.sliced(p + 1);
9548 item->setTaskList();
9549 item->setChecked(checked);
9552 bool fencedCode =
false;
9553 typename Trait::String startOfCode;
9554 bool wasEmptyLine =
false;
9556 std::vector<std::pair<RawHtmlBlock<Trait>,
long long int>> htmlToAdd;
9557 long long int line = -1;
9559 auto parseStream = [&](StringListStream<Trait> &stream) ->
long long int
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;
9572 auto processHtml = [&](
auto it) ->
long long int
9574 auto finishHtml = [&]()
9577 htmlToAdd.push_back({html, html.m_parent->items().size()});
9578 updateLastPosInList<Trait>(html);
9579 resetHtmlTag<Trait>(html);
9583 if (html.m_html.get()) {
9584 html.m_parent = html.findParent(html.m_html->startColumn());
9586 if (!html.m_parent) {
9587 html.m_parent = html.m_topParent;
9592 if (html.m_continueHtml) {
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));
9598 const auto line = parseText(tmp, html.m_parent, doc, linksToParse, workingPath, fileName,
9599 collectRefLinks, html);
9601 if (!html.m_continueHtml) {
9614 if (processHtml(std::prev(it)) == -2) {
9615 for (
auto last = fr.m_data.end(); it != last; ++it, ++pos) {
9617 fencedCode = isCodeFences<Trait>(it->first.asString().startsWith(
9618 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9619 it->first.asString().sliced(indent) : it->first.asString());
9622 startOfCode = startSequence<Trait>(it->first.asString());
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)) {
9633 long long int newIndent = 0;
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(),
9641 if (ok && !it->second.m_mayBreakList) {
9642 StringListStream<Trait> stream(data);
9644 line = parseStream(stream);
9648 const auto lineAfterHtml = processHtml(it);
9650 if (lineAfterHtml != -2) {
9651 if (lineAfterHtml == -1) {
9654 if (html.m_parent == html.m_topParent) {
9655 line = lineAfterHtml;
9657 it += (lineAfterHtml - it->second.m_lineNumber);
9666 if (!htmlToAdd.empty() && htmlToAdd.back().first.m_parent == html.m_topParent) {
9667 line = it->second.m_lineNumber;
9671 typename MdBlock<Trait>::Data nestedList;
9672 nestedList.push_back(*it);
9673 const auto emptyLinesBefore = emptyLinesBeforeCount<Trait>(fr.m_data.begin(), it);
9676 wasEmptyLine =
false;
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);
9685 wasEmptyLine =
false;
9688 auto currentStr = it->first.asString().startsWith(
9689 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9690 it->first.sliced(indent) : it->first;
9692 const auto type = whatIsTheLine(currentStr);
9694 bool mayBreak =
false;
9697 case BlockType::Code:
9698 case BlockType::Blockquote:
9699 case BlockType::Heading:
9707 if (ok || ns >= indent + newIndent || ns == it->first.length() || (!wasEmptyLine && !mayBreak)) {
9708 nestedList.push_back(*it);
9713 wasEmptyLine = (ns == it->first.length());
9715 wasText = (wasEmptyLine ? false : wasText);
9718 for (
auto it = nestedList.begin(), last = nestedList.end(); it != last; ++it) {
9719 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9721 if (ns < indent && ns != it->first.length()) {
9722 it->second.m_mayBreakList =
true;
9724 it->first = it->first.sliced(std::min(ns, indent));
9728 while (!nestedList.empty() &&
9729 nestedList.back().first.asString().isEmpty()) {
9730 nestedList.pop_back();
9733 MdBlock<Trait> block = {nestedList, emptyLinesBefore, wasEmptyLine};
9735 line = parseList(block, item, doc, linksToParse, workingPath, fileName,
9736 collectRefLinks, html);
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);
9748 data.push_back(*it);
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);
9760 data.push_back(*it);
9762 wasEmptyLine = (skipSpaces<Trait>(0, it->first.asString()) == it->first.length());
9764 wasText = !wasEmptyLine;
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);
9773 data.push_back(*it);
9777 if (!data.empty()) {
9778 StringListStream<Trait> stream(data);
9780 line = parseStream(stream);
9783 html.m_parent = html.findParent(html.m_html->startColumn());
9785 if (!html.m_parent) {
9786 html.m_parent = html.m_topParent;
9794 if (!collectRefLinks) {
9796 parent->appendItem(item);
9799 long long int i = 0;
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);
9816 long long int htmlStartColumn = -1;
9817 long long int htmlStartLine = -1;
9820 std::tie(htmlStartColumn, htmlStartLine) =
9821 localPosFromVirgin<Trait>(fr, html.m_html->startColumn(), html.m_html->startLine());
9824 long long int localLine = (html.m_html ? htmlStartLine : fr.m_data.size() - 1);
9827 if (skipSpaces<Trait>(0, fr.m_data[localLine].first.asString()) >= htmlStartColumn) {
9832 const auto lastLine = fr.m_data[localLine].second.m_lineNumber;
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);
9837 item->setEndColumn(lastColumn);
9838 item->setEndLine(lastLine);
9839 parent->setEndColumn(lastColumn);
9840 parent->setEndLine(lastLine);
9848 html.m_blocks.pop_back();
9853template<
class Trait>
9856 std::shared_ptr<Block<Trait>> parent,
9857 bool collectRefLinks)
9859 const auto indent = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
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()));
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()));
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;
9884 fr.m_data.erase(fr.m_data.cbegin());
9887 const auto it = std::prev(fr.m_data.cend());
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));
9896 fr.m_data.erase(it);
9899 if (syntax.toLower() == Trait::latin1ToString(
"math")) {
9900 typename Trait::String math;
9903 for (
const auto &l : std::as_const(fr.m_data)) {
9905 math.push_back(Trait::latin1ToChar(
'\n'));
9908 math.push_back(l.first.virginSubString());
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);
9920 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
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);
9928 m->setStartColumn(emptyColumn);
9929 m->setStartLine(startLine);
9930 m->setEndColumn(emptyColumn);
9931 m->setEndLine(startLine);
9934 m->setInline(
false);
9936 m->setStartDelim(startDelim);
9937 m->setEndDelim(endDelim);
9938 m->setSyntaxPos(syntaxPos);
9939 m->setFensedCode(
true);
9942 parent->appendItem(p);
9945 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, syntax, emptyColumn,
9946 startLine,
true, startDelim, endDelim, syntaxPos);
9953template<
class Trait>
9956 std::shared_ptr<Block<Trait>> parent,
9957 bool collectRefLinks,
9959 const typename Trait::String &syntax,
9960 long long int emptyColumn,
9961 long long int startLine,
9963 const WithPosition &startDelim,
9964 const WithPosition &endDelim,
9965 const WithPosition &syntaxPos)
9967 typename Trait::String code;
9968 long long int startPos = 0;
9971 auto it = fr.m_data.begin(), lastIt = fr.m_data.end();
9973 for (; it != lastIt; ++it) {
9974 if (it->second.m_mayBreakList) {
9979 if (!collectRefLinks) {
9980 const auto ns = skipSpaces<Trait>(0, it->first.asString());
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'))));
9993 if (!collectRefLinks) {
9994 if (!code.isEmpty()) {
9995 code.remove(code.length() - 1, 1);
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);
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);
10011 codeItem->setStartColumn(emptyColumn);
10012 codeItem->setStartLine(startLine);
10013 codeItem->setEndColumn(emptyColumn);
10014 codeItem->setEndLine(startLine);
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());
10022 if (!c->isFensedCode()) {
10023 auto line = c->endLine();
10024 auto text = c->text();
10026 for (; line < codeItem->startLine(); ++line) {
10027 text.push_back(Trait::latin1ToString(
"\n"));
10030 text.push_back(codeItem->text());
10032 c->setEndColumn(codeItem->endColumn());
10033 c->setEndLine(codeItem->endLine());
10035 parent->appendItem(codeItem);
10038 parent->appendItem(codeItem);
10042 if (lastIt != fr.m_data.end()) {
10043 return lastIt->second.m_lineNumber;
Abstract block (storage of child items).
const Items & items() const
typename Trait::template Vector< WithPosition > Delims
Type of list of service chanracters.
Base class for items that can have style options.
void setOpts(int o)
Set style options.
const Styles & closeStyles() const
const Styles & openStyles() const
typename Trait::template Vector< StyleDelim > Styles
Type of list of emphasis.
Base class for item in Markdown document.
virtual ItemType type() const =0
void removeTextPlugin(int id)
Remove text plugin.
friend struct PrivateAccess
Used in tests.
void addTextPlugin(int id, TextPluginFunc< Trait > plugin, bool processInLinks, const typename Trait::StringList &userData)
Add text plugin.
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)
Wrapper for typename Trait::StringList to be behaved like a stream.
Trait::InternalString lineAt(long long int pos)
std::pair< typename Trait::InternalString, bool > readLine()
void setLineNumber(long long int lineNumber)
long long int size() const
StringListStream(typename MdBlock< Trait >::Data &stream)
long long int currentStreamPos() const
long long int currentLineNumber() const
Emphasis in the Markdown document.
TextStream(QTextStream &stream)
TextStream(std::istream &stream)
Wrapper for UChar32 to be used with MD::Parser.
Wrapper for icu::UnicodeString to be used with MD::Parser.
void push_back(const UnicodeChar &ch)
std::vector< UnicodeString > split(const UnicodeChar &ch) const
UnicodeString scheme() const
UnicodeString host() const
Base for any thing with start and end position.
void setEndColumn(long long int c)
Set end column.
long long int startColumn() const
void setStartColumn(long long int c)
Set start column.
long long int startLine() const
long long int endColumn() const
long long int endLine() const
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 back(BidiMode useBidi=IgnoreRTL)
QString label(StandardShortcut id)
@ StrikethroughText
Strikethrough.
@ TextWithoutFormat
No format.
bool isOrderedList(const typename Trait::String &s, int *num=nullptr, int *len=nullptr, typename Trait::Char *delim=nullptr, bool *isFirstLineEmpty=nullptr)
Trait::String paragraphToLabel(Paragraph< Trait > *p)
Convert Paragraph to label.
std::pair< long long int, long long int > prevPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
bool isValidUrl< UnicodeStringTrait >(const UnicodeString &url)
bool isMult3(long long int i1, long long int i2)
std::pair< long long int, long long int > nextPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
bool checkForEndHtmlComments(const typename Trait::String &line, long long int pos)
bool isH1(const typename Trait::String &s)
bool isEmail(const typename Trait::String &url)
bool isLineBreak(const typename Trait::String &s)
TextOption styleToTextOption(Style s)
std::shared_ptr< Text< Trait > > concatenateText(typename Block< Trait >::Items::const_iterator it, typename Block< Trait >::Items::const_iterator last)
Concatenate texts in block.
bool isH(const typename Trait::String &s, const typename Trait::Char &c)
std::tuple< bool, long long int, typename Trait::Char, bool > listItemData(const typename Trait::String &s, bool wasText)
bool isGitHubAutolink< QStringTrait >(const QString &url)
bool isSemiOptimization(OptimizeParagraphType t)
long long int skipSpaces(long long int i, const typename Trait::String &line)
Skip spaces in line from position i.
Trait::InternalString prepareTableData(typename Trait::InternalString s)
Prepare data in table cell for parsing.
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.
static const Trait::String s_canBeEscaped
Characters that can be escaped.
int isTableHeader(const typename Trait::String &s)
void initLastItemWithOpts(TextParsingOpts< Trait > &po, std::shared_ptr< ItemWithOpts< Trait > > item)
Initialize item with style information and set it as last item.
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.
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.
bool isListItemAndNotNested(const typename Trait::String &s, long long int indent)
std::pair< bool, bool > readUnquotedHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
static const char * s_startComment
Starting HTML comment string.
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.
void setLastPos(std::shared_ptr< Item< Trait > > item, long long int pos, long long int line)
Set last position of the item.
long long int lastNonSpacePos(const String &line)
std::shared_ptr< Paragraph< Trait > > optimizeParagraph(std::shared_ptr< Paragraph< Trait > > &p, TextParsingOpts< Trait > &po, OptimizeParagraphType type=OptimizeParagraphType::Full)
Optimize Paragraph.
WithPosition findAndRemoveClosingSequence(typename Trait::InternalString &s)
Find and remove closing sequence of "#" in heading.
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.
bool isFootnote(const typename Trait::String &s)
void githubAutolinkPlugin(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const typename Trait::StringList &)
GitHub autolinks plugin.
void replaceTabs(typename Trait::InternalString &s)
Replace tabs with spaces (just for internal simpler use).
bool isCodeFences(const typename Trait::String &s, bool closing=false)
long long int lineBreakLength(const typename Trait::String &s)
bool indentInList(const std::vector< long long int > *indents, long long int indent, bool codeIndentedBySpaces)
std::pair< long long int, long long int > localPosFromVirgin(const MdBlock< Trait > &fr, long long int virginColumn, long long int virginLine)
OptimizeParagraphType
Type of the paragraph's optimization.
@ Semi
Semi optimization, optimization won't concatenate text items if style delimiters will be in the middl...
@ SemiWithoutRawData
Semi optimization, but raw text data won't be concatenated (will be untouched).
@ FullWithoutRawData
Full optimization, but raw text data won't be concatenated (will be untouched).
std::shared_ptr< Paragraph< Trait > > makeParagraph(typename Block< Trait >::Items::const_iterator first, typename Block< Trait >::Items::const_iterator last)
Make Paragraph.
bool isH2(const typename Trait::String &s)
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.
std::function< void(std::shared_ptr< Paragraph< Trait > >, TextParsingOpts< Trait > &, const typename Trait::StringList &)> TextPluginFunc
Functor type for text plugin.
bool isGitHubAutolink< UnicodeStringTrait >(const UnicodeString &url)
void normalizePos(long long int &pos, long long int &line, long long int length, long long int linesCount)
Normalize position.
std::pair< bool, bool > readHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
std::pair< long long int, long long int > calculateIndent(const typename Trait::String &s, long long int p)
void makeHorLine(const typename MdBlock< Trait >::Line &line, std::shared_ptr< Block< Trait > > parent)
Make horizontal line.
long long int emptyLinesBeforeCount(typename MdBlock< Trait >::Data::iterator begin, typename MdBlock< Trait >::Data::iterator it)
void makeText(long long int lastLine, long long int lastPos, TextParsingOpts< Trait > &po)
Make text item.
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)
void resetHtmlTag(RawHtmlBlock< Trait > &html, TextParsingOpts< Trait > *po=nullptr)
Reset pre-stored HTML.
bool isStartOfCode(const typename Trait::String &str, typename Trait::String *syntax=nullptr, WithPosition *delim=nullptr, WithPosition *syntaxPos=nullptr)
Trait::String stringToLabel(const typename Trait::String &s)
Convert string to label.
UnicodeStringTrait::StringList splitString< UnicodeStringTrait >(const UnicodeString &str, const UnicodeChar &ch)
int isTableAlignment(const typename Trait::String &s)
bool isColumnAlignment(const typename Trait::String &s)
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.
void checkForTableInParagraph(TextParsingOpts< Trait > &po, long long int lastLine)
Check for table in paragraph.
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.
bool isOnlyHtmlTagsAfterOrClosedRule1(long long int line, long long int pos, TextParsingOpts< Trait > &po, int rule)
void checkForTextPlugins(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const TextPluginsMap< Trait > &textPlugins, bool inLink)
Process text plugins.
Trait::String virginSubstr(const MdBlock< Trait > &fr, const WithPosition &virginPos)
std::map< int, std::tuple< TextPluginFunc< Trait >, bool, typename Trait::StringList > > TextPluginsMap
Type of the map of text plugins.
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.
void skipSpacesInHtml(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Skip spaces.
@ FootnoteRef
Footnote ref.
void appendCloseStyle(TextParsingOpts< Trait > &po, const StyleDelim &s)
Append close style.
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.
std::pair< typename Trait::InternalStringList, std::vector< long long int > > splitTableRow(const typename Trait::InternalString &s)
Split table's row on cells.
bool isSetextHeadingBetween(const TextParsingOpts< Trait > &po, long long int startLine, long long int endLine)
long long int textAtIdx(std::shared_ptr< Paragraph< Trait > > p, size_t idx)
long long int listLevel(const std::vector< long long int > &indents, long long int pos)
long long int posOfListItem(const typename Trait::String &s, bool ordered)
bool isGitHubAutolink(const typename Trait::String &url)
bool isHorizontalLine(const typename Trait::String &s)
bool isValidUrl(const typename Trait::String &url)
Trait::String startSequence(const typename Trait::String &line)
bool isValidUrl< QStringTrait >(const QString &url)
TextPlugin
ID of text plugin.
@ UnknownPluginID
Unknown plugin.
@ UserDefinedPluginID
First user defined plugin ID.
@ GitHubAutoLinkPluginID
GitHub's autolinks plugin.
void applyStyles(int &opts, std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles)
Apply styles.
long long int lastVirginPositionInParagraph(Item< Trait > *item)
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.
bool isHtmlComment(const typename Trait::String &s)
void resolveLinks(typename Trait::StringList &linksToParse, std::shared_ptr< Document< Trait > > doc)
Resolve links in the document.
QStringTrait::StringList splitString< QStringTrait >(const QString &str, const QChar &ch)
bool isWithoutRawDataOptimization(OptimizeParagraphType t)
Trait::String replaceEntity(const typename Trait::String &s)
Replace entities in the string with corresponding character.
static const std::map< typename Trait::String, const char16_t * > s_entityMap
String removeBackslashes(const String &s)
Remove backslashes from the string.
Trait::String removeLineBreak(const typename Trait::String &s)
Remove line break from the end of string.
void updateLastPosInList(const RawHtmlBlock< Trait > &html)
Update last position of all parent.
void closeStyle(std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles, Style s)
Close style.
std::pair< typename Trait::String, WithPosition > findAndRemoveHeaderLabel(typename Trait::InternalString &s)
Find and remove heading label.
std::pair< bool, bool > readHtmlAttr(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr, bool checkForSpace)
Read HTML attribute.
void checkForHtmlComments(const typename Trait::InternalString &line, StringListStream< Trait > &stream, MdLineData::CommentDataMap &res)
Collect information about HTML comments.
bool isEmpty() const const
void push_back(parameter_type value)
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
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
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.
typename Trait::template Vector< Line > Data
std::pair< typename Trait::InternalString, MdLineData > Line
long long int m_emptyLinesBefore
Internal structure for auxiliary information about a line in Markdown.
long long int m_lineNumber
std::pair< char, bool > CommentData
std::map< long long int, CommentData > CommentDataMap
CommentDataMap m_htmlCommentData
Trait to use this library with QString.
Internal structure for pre-storing HTML.
std::unordered_map< std::shared_ptr< Block< Trait > >, SequenceOfBlock > m_toAdjustLastPos
std::vector< std::pair< std::shared_ptr< Block< Trait > >, long long int > > SequenceOfBlock
std::shared_ptr< RawHtml< Trait > > m_html
std::shared_ptr< Block< Trait > > findParent(long long int indent) const
std::shared_ptr< Block< Trait > > m_topParent
std::shared_ptr< Block< Trait > > m_parent
Internal structure for auxiliary options for parser.
bool shouldStopParsing() const
RawHtmlBlock< Trait > & m_html
std::shared_ptr< Document< Trait > > m_doc
bool m_checkLineOnNewType
ItemWithOpts< Trait >::Styles m_openStyles
void concatenateAuxText(long long int start, long long int end)
Trait::StringList & m_linksToParse
long long int m_lastTextPos
long long int m_startTableLine
std::shared_ptr< ItemWithOpts< Trait > > m_lastItemWithStyle
std::shared_ptr< Block< Trait > > m_parent
std::shared_ptr< RawHtml< Trait > > m_tmpHtml
std::shared_ptr< Text< Trait > > m_lastText
const TextPluginsMap< Trait > & m_textPlugins
Trait::String m_workingPath
std::vector< StyleInfo > m_styles
std::vector< TextData > m_rawTextData
long long int m_lastTextLine
Trait to use this library with std::string.
static String latin1ToString(const char *latin1)
Convert Latin1 into trait's string.
std::vector< String > StringList
static bool fileExists(const String &fileName, const String &workingPath)
static bool isFreeTag(std::shared_ptr< RawHtml< Trait > > html)
static void setFreeTag(std::shared_ptr< RawHtml< Trait > > html, bool on)
#define MD_DISABLE_COPY(Class)
Macro for disabling copy.
#define MD_UNUSED(x)
Avoid "unused parameter" warnings.