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) {
252 using Line = std::pair<typename Trait::InternalString, MdLineData>;
253 using Data =
typename Trait::template Vector<Line>;
277 return (m_pos >= (
long long int)m_stream.size());
280 std::pair<typename Trait::InternalString, bool>
readLine()
282 const std::pair<typename Trait::InternalString, bool> ret =
283 {m_stream.at(m_pos).first, m_stream.at(m_pos).second.m_mayBreakList};
292 return (m_pos <
size() ? m_stream.at(m_pos).second.m_lineNumber :
size());
295 typename Trait::InternalString
lineAt(
long long int pos)
297 return m_stream.at(pos).first;
302 return m_stream.size();
324 if (s.size() - p < 5) {
328 if (s[p++] != Trait::latin1ToChar(
'[')) {
332 if (s[p++] != Trait::latin1ToChar(
'^')) {
336 if (s[p] == Trait::latin1ToChar(
']') || s[p].isSpace()) {
340 for (; p < s.size(); ++p) {
341 if (s[p] == Trait::latin1ToChar(
']')) {
343 }
else if (s[p].isSpace()) {
350 if (p < s.size() && s[p] == Trait::latin1ToChar(
':')) {
364 if (p > 3 || p == s.length()) {
368 const auto ch = s[p];
370 if (ch != Trait::latin1ToChar(
'~') && ch != Trait::latin1ToChar(
'`')) {
379 for (; p < s.length(); ++p) {
380 if (s[p].isSpace()) {
382 }
else if (s[p] == ch) {
383 if (space && (closing ?
true : ch == Trait::latin1ToChar(
'`'))) {
390 }
else if (closing) {
401 if (ch == Trait::latin1ToChar(
'`')) {
402 for (; p < s.length(); ++p) {
403 if (s[p] == Trait::latin1ToChar(
'`')) {
414inline typename Trait::String
416 const typename Trait::String &str,
417 long long int *endPos =
nullptr)
419 bool backslash =
false;
420 const auto start = i;
422 if (
start >= str.length()) {
426 while (i < str.length()) {
429 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
432 }
else if (str[i].isSpace() && !backslash) {
453 Trait::latin1ToString(
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
456template<
class String,
class Trait>
461 bool backslash =
false;
462 long long int extra = 0;
464 for (
long long int i = 0; i < s.length(); ++i) {
467 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash && i != s.length() - 1) {
471 r.remove(i - extra - 1, 1);
487 typename Trait::String *syntax =
nullptr,
494 delim->setStartColumn(p);
501 if (str.size() - p < 3) {
505 const bool c96 = str[p] == Trait::latin1ToChar(
'`');
506 const bool c126 = str[p] == Trait::latin1ToChar(
'~');
512 while (p < str.length()) {
513 if (str[p] != (c96 ? Trait::latin1ToChar(
'`') : Trait::latin1ToChar(
'~'))) {
522 delim->setEndColumn(p - 1);
531 long long int endSyntaxPos = p;
533 if (p < str.size()) {
538 syntaxPos->setStartColumn(p);
539 syntaxPos->setEndColumn(endSyntaxPos);
559 typename Trait::Char c;
561 if (s[0] == Trait::latin1ToChar(
'*')) {
562 c = Trait::latin1ToChar(
'*');
563 }
else if (s[0] == Trait::latin1ToChar(
'-')) {
564 c = Trait::latin1ToChar(
'-');
565 }
else if (s[0] == Trait::latin1ToChar(
'_')) {
566 c = Trait::latin1ToChar(
'_');
572 long long int count = 1;
574 for (; p < s.size(); ++p) {
575 if (s[p] != c && !s[p].isSpace()) {
577 }
else if (s[p] == c) {
600 static const typename Trait::String s_legitime = Trait::latin1ToString(
":-");
602 if (p >= s.length()) {
606 if (!s_legitime.contains(s[p])) {
610 if (s[p] == Trait::latin1ToChar(
':')) {
614 for (; p < s.size(); ++p) {
615 if (s[p] != Trait::latin1ToChar(
'-')) {
624 if (s[p] != Trait::latin1ToChar(
':') && !s[p].isSpace()) {
630 for (; p < s.size(); ++p) {
631 if (!s[p].isSpace()) {
641typename Trait::StringList
642splitString(
const typename Trait::String &str,
const typename Trait::Char &ch);
644#ifdef MD4QT_ICU_STL_SUPPORT
650 return str.
split(ch);
655#ifdef MD4QT_QT_SUPPORT
673 for (
const auto &c : columns) {
679 return columns.size();
695 long long int p = -1;
696 bool endFound =
false;
698 while ((p = c.indexOf(Trait::latin1ToString(
"--"), p + 1)) > -1) {
699 if (c.size() > p + 2 && c[p + 2] == Trait::latin1ToChar(
'>')) {
705 }
else if (p - 2 >= 0 && c.sliced(p - 2, 4) == Trait::latin1ToString(
"<!--")) {
707 }
else if (c.size() > p + 3 && c.sliced(p, 4) == Trait::latin1ToString(
"--!>")) {
717inline typename Trait::String
720 long long int p1 = 0;
722 typename Trait::String res;
725 while ((p1 = s.indexOf(Trait::latin1ToChar(
'&'), p1)) != -1) {
726 if (p1 > 0 && s[p1 - 1] == Trait::latin1ToChar(
'\\')) {
732 const auto p2 = s.indexOf(Trait::latin1ToChar(
';'), p1);
735 const auto en = s.sliced(p1, p2 - p1 + 1);
737 if (en.size() > 2 && en[1] == Trait::latin1ToChar(
'#')) {
738 if (en.size() > 3 && en[2].toLower() == Trait::latin1ToChar(
'x')) {
739 const auto hex = en.sliced(3, en.size() - 4);
741 if (hex.size() <= 6 && hex.size() > 0) {
744 const char32_t c = hex.toInt(&ok, 16);
747 res.push_back(s.sliced(i, p1 - i));
751 Trait::appendUcs4(res, c);
753 res.push_back(
typename Trait::Char(0xFFFD));
758 const auto dec = en.sliced(2, en.size() - 3);
760 if (dec.size() <= 7 && dec.size() > 0) {
763 const char32_t c = dec.toInt(&ok, 10);
766 res.push_back(s.sliced(i, p1 - i));
770 Trait::appendUcs4(res, c);
772 res.push_back(
typename Trait::Char(0xFFFD));
781 res.push_back(s.sliced(i, p1 - i));
783 res.push_back(Trait::utf16ToString(it->second));
793 res.push_back(s.sliced(i, s.size() - i));
805 for (
auto &line : tmp) {
885struct TextParsingOpts;
891 const typename Trait::StringList &)>;
901 typename Trait::StringList>>;
913 std::shared_ptr<Document<Trait>>
m_doc;
938 for (
auto i =
start + 1; i < end; ++i) {
989inline typename Trait::String
996 long long int startLine = virginPos.
startLine() < fr.
m_data.at(0).second.m_lineNumber ?
997 (virginPos.
endLine() < fr.
m_data.at(0).second.m_lineNumber ? -1 : 0) :
1000 if (startLine >=
static_cast<long long int>(fr.
m_data.size()) || startLine < 0) {
1004 auto spos = virginPos.
startColumn() - fr.
m_data.at(startLine).first.virginPos(0);
1010 long long int epos = 0;
1015 if (startLine + linesCount >
static_cast<long long int>(fr.
m_data.size())) {
1016 linesCount = fr.
m_data.size() - startLine - 1;
1017 epos = fr.
m_data.back().first.length();
1019 epos = virginPos.
endColumn() - fr.
m_data.at(linesCount + startLine).first.virginPos(0) + 1;
1026 if (epos > fr.
m_data.at(linesCount + startLine).first.length()) {
1027 epos = fr.
m_data.at(linesCount + startLine).first.length();
1030 typename Trait::String str =
1031 (linesCount ? fr.
m_data.at(startLine).first.sliced(spos).asString() :
1032 fr.
m_data.at(startLine).first.sliced(spos, epos - spos).asString());
1034 long long int i = startLine + 1;
1036 for (; i < startLine + linesCount; ++i) {
1037 str.push_back(Trait::latin1ToString(
"\n"));
1038 str.push_back(fr.
m_data.at(i).first.asString());
1042 str.push_back(Trait::latin1ToString(
"\n"));
1043 str.push_back(fr.
m_data.at(i).first.sliced(0, epos).asString());
1055template<
class Trait>
1056inline std::pair<long long int, long long int>
1063 if (fr.
m_data.front().second.m_lineNumber > virginLine ||
1064 fr.
m_data.back().second.m_lineNumber < virginLine) {
1068 auto line = virginLine - fr.
m_data.front().second.m_lineNumber;
1070 if (fr.
m_data.at(line).first.isEmpty()) {
1074 const auto vzpos = fr.
m_data.at(line).first.virginPos(0);
1076 if (vzpos > virginColumn || virginColumn > vzpos + fr.
m_data.at(line).first.length() - 1) {
1080 return {virginColumn - vzpos, line};
1092template<
class Trait>
1096 auto isAllowed = [](
const typename Trait::Char &ch) ->
bool {
1097 const auto unicode = ch.unicode();
1098 return ((unicode >= 48 && unicode <= 57) || (unicode >= 97 && unicode <= 122) ||
1099 (unicode >= 65 && unicode <= 90));
1102 auto isAdditional = [](
const typename Trait::Char &ch) ->
bool {
1103 const auto unicode = ch.unicode();
1104 return (unicode == 33 || (unicode >= 35 && unicode <= 39) ||
1105 unicode == 42 || unicode == 43 || (unicode >= 45 && unicode <= 47) ||
1106 unicode == 61 || unicode == 63 || (unicode >= 94 && unicode <= 96) ||
1107 (unicode >= 123 && unicode <= 126));
1110 static const auto s_delim = Trait::latin1ToChar(
'-');
1111 static const auto s_dog = Trait::latin1ToChar(
'@');
1112 static const auto s_dot = Trait::latin1ToChar(
'.');
1114 long long int i = (url.startsWith(Trait::latin1ToString(
"mailto:")) ? 7 : 0);
1115 const auto dogPos = url.indexOf(s_dog, i);
1122 for (; i < dogPos; ++i) {
1123 if (!isAllowed(url[i]) && !isAdditional(url[i])) {
1128 auto checkToDot = [&](
long long int start,
long long int dotPos) ->
bool {
1129 static const long long int maxlen = 63;
1131 if (dotPos -
start > maxlen ||
1132 start + 1 > dotPos ||
1133 start >= url.length() ||
1134 dotPos > url.length()) {
1138 if (url[
start] == s_delim) {
1142 if (url[dotPos - 1] == s_delim) {
1147 if (!isAllowed(url[
start]) && url[
start] != s_delim) {
1155 long long int dotPos = url.indexOf(s_dot, dogPos + 1);
1160 while (dotPos != -1) {
1161 if (!checkToDot(i, dotPos)) {
1166 dotPos = url.indexOf(s_dot, i);
1169 if (!checkToDot(i, url.length())) {
1181template<
class Trait>
1186template<
class Trait>
1190#ifdef MD4QT_QT_SUPPORT
1215#ifdef MD4QT_ICU_STL_SUPPORT
1233 && ((!u.
scheme().isEmpty() && !u.
host().isEmpty())
1234 || (url.startsWith(
UnicodeString(
"www.")) && url.length() >= 7 &&
1241template<
class Trait>
1247 if (idx < 0 || idx >= (
long long int)po.
m_rawTextData.size()) {
1251 static const auto s_delims = Trait::latin1ToString(
"*_~()<>");
1254 long long int j = 0;
1255 auto end =
typename Trait::Char(0x00);
1256 bool skipSpace =
true;
1257 long long int ret = idx;
1259 while (s.m_str.length()) {
1260 long long int i = 0;
1261 end =
typename Trait::Char(0x00);
1263 for (; i < s.m_str.length(); ++i) {
1265 if (s.m_str[i] == Trait::latin1ToChar(
'(')) {
1266 end = Trait::latin1ToChar(
')');
1269 if (s_delims.indexOf(s.m_str[i]) == -1 && !s.m_str[i].isSpace()) {
1274 if (s.m_str[i].isSpace() || i == s.m_str.length() - 1 || s.m_str[i] == end) {
1275 auto tmp = s.m_str.sliced(j, i - j +
1276 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1278 skipSpace = s.m_str[i].isSpace();
1285 if (ti >= 0 && ti <
static_cast<long long int>(p->items().size())) {
1287 const auto opts = std::static_pointer_cast<Text<Trait>>(p->items().at(ti))->opts();
1289 if (j == 0 || s.m_str.sliced(0, j).isEmpty()) {
1290 openStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->
openStyles();
1291 closeStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->
closeStyles();
1292 p->removeItemAt(ti);
1296 const auto tmp = s.m_str.sliced(0, j);
1298 auto t = std::static_pointer_cast<Text<Trait>>(p->items().at(ti));
1299 t->setEndColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j - 1));
1301 t->closeStyles() = {};
1308 std::shared_ptr<Link<Trait>> lnk(
new Link<Trait>);
1309 lnk->setStartColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j));
1310 lnk->setStartLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1312 po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + i -
1313 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1315 lnk->setEndLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1316 lnk->openStyles() = openStyles;
1317 lnk->setTextPos({lnk->startColumn(), lnk->startLine(), lnk->endColumn(), lnk->endLine()});
1318 lnk->setUrlPos(lnk->textPos());
1320 if (email && !tmp.toLower().startsWith(Trait::latin1ToString(
"mailto:"))) {
1321 tmp = Trait::latin1ToString(
"mailto:") + tmp;
1324 if (!email && tmp.toLower().startsWith(Trait::latin1ToString(
"www."))) {
1325 tmp = Trait::latin1ToString(
"http://") + tmp;
1330 p->insertItem(ti, lnk);
1332 s.m_pos += i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1);
1333 s.m_str.remove(0, i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1));
1337 if (!s.m_str.isEmpty()) {
1341 auto t = std::make_shared<Text<Trait>>();
1342 t->setStartColumn(po.
m_fr.m_data[s.m_line].first.virginPos(s.m_pos));
1343 t->setStartLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1344 t->setEndLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1345 t->setEndColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + s.m_str.length() - 1));
1347 t->closeStyles() = closeStyles;
1348 p->insertItem(ti + 1, t);
1350 lnk->closeStyles() = closeStyles;
1357 j = i + (skipSpace ? 1 : 0);
1364 if (i == s.m_str.length()) {
1373template<
class Trait>
1377 const typename Trait::StringList &)
1380 long long int i = 0;
1382 while (i >= 0 && i < (
long long int)po.
m_rawTextData.size()) {
1395template<
class Trait>
1407 std::shared_ptr<Document<Trait>>
1410 const typename Trait::String &fileName,
1413 bool recursive =
true,
1416 const typename Trait::StringList &ext = {Trait::latin1ToString(
"md"), Trait::latin1ToString(
"markdown")},
1422 bool fullyOptimizeParagraphs =
true);
1425 std::shared_ptr<Document<Trait>>
1428 typename Trait::TextStream &stream,
1431 const typename Trait::String &path,
1433 const typename Trait::String &fileName,
1439 bool fullyOptimizeParagraphs =
true);
1449 bool processInLinks,
1451 const typename Trait::StringList &userData)
1453 m_textPlugins.insert({id, {plugin, processInLinks, userData}});
1462 m_textPlugins.erase(
id);
1467 parseFile(
const typename Trait::String &fileName,
1470 const typename Trait::StringList &ext,
1471 typename Trait::StringList *parentLinks =
nullptr);
1474 parseStream(
typename Trait::TextStream &stream,
1475 const typename Trait::String &workingPath,
1476 const typename Trait::String &fileName,
1479 const typename Trait::StringList &ext,
1480 typename Trait::StringList *parentLinks =
nullptr);
1485 enum class BlockType {
1490 ListWithFirstEmptyLine,
1491 CodeIndentedBySpaces,
1501 long long int m_level = -1;
1502 long long int m_indent = -1;
1506 whatIsTheLine(
typename Trait::InternalString &str,
1507 bool inList =
false,
1508 bool inListWithFirstEmptyLine =
false,
1509 bool fensedCodeInList =
false,
1510 typename Trait::String *startOfCode =
nullptr,
1511 ListIndent *indent =
nullptr,
1512 bool emptyLinePreceded =
false,
1513 bool calcIndent =
false,
1514 const std::vector<long long int> *indents =
nullptr);
1517 parseFragment(MdBlock<Trait> &fr,
1518 std::shared_ptr<Block<Trait>> parent,
1520 typename Trait::StringList &linksToParse,
1521 const typename Trait::String &workingPath,
1522 const typename Trait::String &fileName,
1523 bool collectRefLinks,
1524 RawHtmlBlock<Trait> &html);
1527 parseText(MdBlock<Trait> &fr,
1528 std::shared_ptr<Block<Trait>> parent,
1530 typename Trait::StringList &linksToParse,
1531 const typename Trait::String &workingPath,
1532 const typename Trait::String &fileName,
1533 bool collectRefLinks,
1534 RawHtmlBlock<Trait> &html);
1537 parseBlockquote(MdBlock<Trait> &fr,
1538 std::shared_ptr<Block<Trait>> parent,
1540 typename Trait::StringList &linksToParse,
1541 const typename Trait::String &workingPath,
1542 const typename Trait::String &fileName,
1543 bool collectRefLinks,
1544 RawHtmlBlock<Trait> &html);
1547 parseList(MdBlock<Trait> &fr,
1548 std::shared_ptr<Block<Trait>> parent,
1550 typename Trait::StringList &linksToParse,
1551 const typename Trait::String &workingPath,
1552 const typename Trait::String &fileName,
1553 bool collectRefLinks,
1554 RawHtmlBlock<Trait> &html);
1557 parseCode(MdBlock<Trait> &fr,
1558 std::shared_ptr<Block<Trait>> parent,
1559 bool collectRefLinks);
1562 parseCodeIndentedBySpaces(MdBlock<Trait> &fr,
1563 std::shared_ptr<Block<Trait>> parent,
1564 bool collectRefLinks,
1566 const typename Trait::String &syntax,
1567 long long int emptyColumn,
1568 long long int startLine,
1570 const WithPosition &startDelim = {},
1571 const WithPosition &endDelim = {},
1572 const WithPosition &syntaxPos = {});
1575 parseListItem(MdBlock<Trait> &fr,
1576 std::shared_ptr<Block<Trait>> parent,
1578 typename Trait::StringList &linksToParse,
1579 const typename Trait::String &workingPath,
1580 const typename Trait::String &fileName,
1581 bool collectRefLinks,
1582 RawHtmlBlock<Trait> &html,
1586 parseHeading(MdBlock<Trait> &fr,
1587 std::shared_ptr<Block<Trait>> parent,
1589 typename Trait::StringList &linksToParse,
1590 const typename Trait::String &workingPath,
1591 const typename Trait::String &fileName,
1592 bool collectRefLinks);
1595 parseFootnote(MdBlock<Trait> &fr,
1596 std::shared_ptr<Block<Trait>> parent,
1598 typename Trait::StringList &linksToParse,
1599 const typename Trait::String &workingPath,
1600 const typename Trait::String &fileName,
1601 bool collectRefLinks);
1604 parseTable(MdBlock<Trait> &fr,
1605 std::shared_ptr<Block<Trait>> parent,
1607 typename Trait::StringList &linksToParse,
1608 const typename Trait::String &workingPath,
1609 const typename Trait::String &fileName,
1610 bool collectRefLinks,
1614 parseParagraph(MdBlock<Trait> &fr,
1615 std::shared_ptr<Block<Trait>> parent,
1617 typename Trait::StringList &linksToParse,
1618 const typename Trait::String &workingPath,
1619 const typename Trait::String &fileName,
1620 bool collectRefLinks,
1621 RawHtmlBlock<Trait> &html);
1624 parseFormattedTextLinksImages(MdBlock<Trait> &fr,
1625 std::shared_ptr<Block<Trait>> parent,
1627 typename Trait::StringList &linksToParse,
1628 const typename Trait::String &workingPath,
1629 const typename Trait::String &fileName,
1630 bool collectRefLinks,
1631 bool ignoreLineBreak,
1632 RawHtmlBlock<Trait> &html,
1635 struct ParserContext {
1636 typename Trait::template Vector<MdBlock<Trait>> m_splitted;
1638 bool m_emptyLineInList =
false;
1639 bool m_fensedCodeInList =
false;
1640 long long int m_emptyLinesCount = 0;
1641 long long int m_lineCounter = 0;
1642 std::vector<long long int> m_indents;
1643 ListIndent m_indent;
1644 RawHtmlBlock<Trait> m_html;
1645 long long int m_emptyLinesBefore = 0;
1647 typename Trait::String m_startOfCode;
1648 typename Trait::String m_startOfCodeInList;
1649 BlockType m_type = BlockType::EmptyLine;
1650 BlockType m_lineType = BlockType::Unknown;
1651 BlockType m_prevLineType = BlockType::Unknown;
1654 std::pair<long long int, bool>
1655 parseFirstStep(ParserContext &ctx,
1656 StringListStream<Trait> &stream,
1657 std::shared_ptr<Block<Trait>> parent,
1659 typename Trait::StringList &linksToParse,
1660 const typename Trait::String &workingPath,
1661 const typename Trait::String &fileName,
1662 bool collectRefLinks);
1665 parseSecondStep(ParserContext &ctx,
1666 std::shared_ptr<Block<Trait>> parent,
1668 typename Trait::StringList &linksToParse,
1669 const typename Trait::String &workingPath,
1670 const typename Trait::String &fileName,
1671 bool collectRefLinks,
1673 bool dontProcessLastFreeHtml);
1675 std::pair<RawHtmlBlock<Trait>,
long long int>
1676 parse(StringListStream<Trait> &stream,
1677 std::shared_ptr<Block<Trait>> parent,
1679 typename Trait::StringList &linksToParse,
1680 const typename Trait::String &workingPath,
1681 const typename Trait::String &fileName,
1682 bool collectRefLinks,
1684 bool dontProcessLastFreeHtml =
false,
1685 bool stopOnMayBreakList =
false);
1687 std::pair<long long int, bool>
1688 parseFragment(ParserContext &ctx,
1689 std::shared_ptr<Block<Trait>> parent,
1691 typename Trait::StringList &linksToParse,
1692 const typename Trait::String &workingPath,
1693 const typename Trait::String &fileName,
1694 bool collectRefLinks);
1697 eatFootnote(ParserContext &ctx,
1698 StringListStream<Trait> &stream,
1699 std::shared_ptr<Block<Trait>> parent,
1701 typename Trait::StringList &linksToParse,
1702 const typename Trait::String &workingPath,
1703 const typename Trait::String &fileName,
1704 bool collectRefLinks);
1707 finishHtml(ParserContext &ctx,
1708 std::shared_ptr<Block<Trait>> parent,
1710 bool collectRefLinks,
1712 bool dontProcessLastFreeHtml);
1715 makeLineMain(ParserContext &ctx,
1716 const typename Trait::InternalString &line,
1717 long long int emptyLinesCount,
1718 const ListIndent ¤tIndent,
1720 long long int currentLineNumber);
1722 std::pair<long long int, bool>
1723 parseFragmentAndMakeNextLineMain(ParserContext &ctx,
1724 std::shared_ptr<Block<Trait>> parent,
1726 typename Trait::StringList &linksToParse,
1727 const typename Trait::String &workingPath,
1728 const typename Trait::String &fileName,
1729 bool collectRefLinks,
1730 const typename Trait::InternalString &line,
1731 const ListIndent ¤tIndent,
1733 long long int currentLineNumber);
1736 isListType(BlockType t);
1738 std::pair<typename Trait::InternalString, bool>
1739 readLine(ParserContext &ctx, StringListStream<Trait> &stream);
1741 std::shared_ptr<Image<Trait>>
1742 makeImage(
const typename Trait::String &url,
1744 TextParsingOpts<Trait> &po,
1745 bool doNotCreateTextOnFail,
1746 long long int startLine,
1747 long long int startPos,
1748 long long int lastLine,
1749 long long int lastPos,
1750 const WithPosition &textPos,
1751 const WithPosition &urlPos);
1753 std::shared_ptr<Link<Trait>>
1754 makeLink(
const typename Trait::String &url,
1756 TextParsingOpts<Trait> &po,
1757 bool doNotCreateTextOnFail,
1758 long long int startLine,
1759 long long int startPos,
1760 long long int lastLine,
1761 long long int lastPos,
1762 const WithPosition &textPos,
1763 const WithPosition &urlPos);
1766 enum DelimiterType {
1774 SquareBracketsClose,
1797 DelimiterType m_type = Unknown;
1798 long long int m_line = -1;
1799 long long int m_pos = -1;
1800 long long int m_len = 0;
1801 bool m_isWordBefore =
false;
1802 bool m_backslashed =
false;
1803 bool m_leftFlanking =
false;
1804 bool m_rightFlanking =
false;
1805 bool m_skip =
false;
1808 using Delims =
typename Trait::template Vector<Delimiter>;
1812 TextParsingOpts<Trait> &po,
1813 long long int startLine,
1814 long long int startPos,
1815 long long int lastLineForText,
1816 long long int lastPosForText,
1817 typename Delims::iterator lastIt,
1819 bool doNotCreateTextOnFail,
1820 const WithPosition &textPos,
1821 const WithPosition &linkTextPos);
1823 typename Delims::iterator
1824 checkForImage(
typename Delims::iterator it,
1825 typename Delims::iterator last,
1826 TextParsingOpts<Trait> &po);
1830 TextParsingOpts<Trait> &po,
1831 long long int startLine,
1832 long long int startPos,
1833 long long int lastLineForText,
1834 long long int lastPosForText,
1835 typename Delims::iterator lastIt,
1837 bool doNotCreateTextOnFail,
1838 const WithPosition &textPos,
1839 const WithPosition &linkTextPos);
1841 typename Delims::iterator
1842 checkForLink(
typename Delims::iterator it,
1843 typename Delims::iterator last,
1844 TextParsingOpts<Trait> &po);
1849 std::pair<typename Trait::String, bool>
1850 readHtmlTag(
typename Delims::iterator it, TextParsingOpts<Trait> &po);
1852 typename Delims::iterator
1853 findIt(
typename Delims::iterator it,
1854 typename Delims::iterator last,
1855 TextParsingOpts<Trait> &po);
1858 finishRule1HtmlTag(
typename Delims::iterator it,
1859 typename Delims::iterator last,
1860 TextParsingOpts<Trait> &po,
1864 finishRule2HtmlTag(
typename Delims::iterator it,
1865 typename Delims::iterator last,
1866 TextParsingOpts<Trait> &po);
1869 finishRule3HtmlTag(
typename Delims::iterator it,
1870 typename Delims::iterator last,
1871 TextParsingOpts<Trait> &po);
1874 finishRule4HtmlTag(
typename Delims::iterator it,
1875 typename Delims::iterator last,
1876 TextParsingOpts<Trait> &po);
1879 finishRule5HtmlTag(
typename Delims::iterator it,
1880 typename Delims::iterator last,
1881 TextParsingOpts<Trait> &po);
1884 finishRule6HtmlTag(
typename Delims::iterator it,
1885 typename Delims::iterator last,
1886 TextParsingOpts<Trait> &po);
1889 finishRule7HtmlTag(
typename Delims::iterator it,
1890 typename Delims::iterator last,
1891 TextParsingOpts<Trait> &po);
1893 typename Delims::iterator
1894 finishRawHtmlTag(
typename Delims::iterator it,
1895 typename Delims::iterator last,
1896 TextParsingOpts<Trait> &po,
1900 htmlTagRule(
typename Delims::iterator it,
1901 typename Delims::iterator last,
1902 TextParsingOpts<Trait> &po);
1904 typename Delims::iterator
1905 checkForRawHtml(
typename Delims::iterator it,
1906 typename Delims::iterator last,
1907 TextParsingOpts<Trait> &po);
1909 typename Delims::iterator
1910 checkForMath(
typename Delims::iterator it,
1911 typename Delims::iterator last,
1912 TextParsingOpts<Trait> &po);
1914 typename Delims::iterator
1915 checkForAutolinkHtml(
typename Delims::iterator it,
1916 typename Delims::iterator last,
1917 TextParsingOpts<Trait> &po,
1920 typename Delims::iterator
1921 checkForInlineCode(
typename Delims::iterator it,
1922 typename Delims::iterator last,
1923 TextParsingOpts<Trait> &po);
1925 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1926 readTextBetweenSquareBrackets(
typename Delims::iterator
start,
1927 typename Delims::iterator it,
1928 typename Delims::iterator last,
1929 TextParsingOpts<Trait> &po,
1930 bool doNotCreateTextOnFail,
1931 WithPosition *pos =
nullptr);
1933 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1934 checkForLinkText(
typename Delims::iterator it,
1935 typename Delims::iterator last,
1936 TextParsingOpts<Trait> &po,
1937 WithPosition *pos =
nullptr);
1939 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1940 checkForLinkLabel(
typename Delims::iterator it,
1941 typename Delims::iterator last,
1942 TextParsingOpts<Trait> &po,
1943 WithPosition *pos =
nullptr);
1945 std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
1946 checkForInlineLink(
typename Delims::iterator it,
1947 typename Delims::iterator last,
1948 TextParsingOpts<Trait> &po,
1949 WithPosition *urlPos =
nullptr);
1951 inline std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
1952 checkForRefLink(
typename Delims::iterator it,
1953 typename Delims::iterator last,
1954 TextParsingOpts<Trait> &po,
1955 WithPosition *urlPos =
nullptr);
1957 typename Trait::String
1960 template<
class Func>
1961 typename Delims::iterator
1962 checkShortcut(
typename Delims::iterator it,
1963 typename Delims::iterator last,
1964 TextParsingOpts<Trait> &po,
1967 const auto start = it;
1971 WithPosition labelPos;
1972 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
1974 if (it !=
start && !toSingleLine(text).simplified().isEmpty()) {
1975 if ((this->*functor)(text, po,
start->m_line,
start->m_pos,
start->m_line,
1976 start->m_pos +
start->m_len, it, {},
false, labelPos, {})) {
1985 isSequence(
typename Delims::iterator it,
1986 long long int itLine,
1987 long long int itPos,
1988 typename Delimiter::DelimiterType t);
1990 std::pair<typename Delims::iterator, typename Delims::iterator>
1991 readSequence(
typename Delims::iterator first,
1992 typename Delims::iterator it,
1993 typename Delims::iterator last,
1995 long long int &length,
1996 long long int &itCount,
1997 long long int &lengthFromIt,
1998 long long int &itCountFromIt);
2000 typename Delims::iterator
2001 readSequence(
typename Delims::iterator it,
2002 typename Delims::iterator last,
2003 long long int &line,
2006 long long int &itCount);
2009 emphasisToInt(
typename Delimiter::DelimiterType t);
2012 createStyles(std::vector<std::pair<Style, long long int>> & styles,
2013 typename Delimiter::DelimiterType t,
2014 long long int style);
2016 std::vector<std::pair<Style, long long int>>
2017 createStyles(
typename Delimiter::DelimiterType t,
2018 const std::vector<long long int> &styles,
2019 long long int lastStyle);
2021 std::tuple<bool, std::vector<std::pair<Style, long long int>>,
long long int,
long long int>
2022 isStyleClosed(
typename Delims::iterator first,
2023 typename Delims::iterator it,
2024 typename Delims::iterator last,
2025 typename Delims::iterator &stackBottom,
2026 TextParsingOpts<Trait> &po);
2028 typename Delims::iterator
2029 incrementIterator(
typename Delims::iterator it,
2030 typename Delims::iterator last,
2031 long long int count);
2033 typename Delims::iterator
2034 checkForStyle(
typename Delims::iterator first,
2035 typename Delims::iterator it,
2036 typename Delims::iterator last,
2037 typename Delims::iterator &stackBottom,
2038 TextParsingOpts<Trait> &po);
2041 isListOrQuoteAfterHtml(TextParsingOpts<Trait> &po);
2044 parseTableInParagraph(TextParsingOpts<Trait> &po,
2047 typename Trait::StringList &linksToParse,
2048 const typename Trait::String &workingPath,
2049 const typename Trait::String &fileName,
2050 bool collectRefLinks);
2053 isNewBlockIn(MdBlock<Trait> &fr,
2054 long long int startLine,
2055 long long int endLine);
2058 makeInlineCode(
long long int startLine,
2059 long long int startPos,
2060 long long int lastLine,
2061 long long int lastPos,
2062 TextParsingOpts<Trait> &po,
2063 typename Delims::iterator startDelimIt,
2064 typename Delims::iterator endDelimIt);
2067 defaultParagraphOptimization()
const
2078 typename Trait::StringList m_parsedFiles;
2080 bool m_fullyOptimizeParagraphs =
true;
2089template<
class Trait>
2090inline std::shared_ptr<Document<Trait>>
2093 const typename Trait::StringList &ext,
2094 bool fullyOptimizeParagraphs)
2096 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2100 parseFile(fileName, recursive, doc, ext);
2107template<
class Trait>
2108inline std::shared_ptr<Document<Trait>>
2110 const typename Trait::String &path,
2111 const typename Trait::String &fileName,
2112 bool fullyOptimizeParagraphs)
2114 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2118 parseStream(stream, path, fileName,
false, doc,
typename Trait::StringList());
2125template<
class Trait>
2128#ifdef MD4QT_QT_SUPPORT
2145 return (m_lastBuf && m_pos == m_buf.size());
2152 bool rFound =
false;
2155 const auto c = getChar();
2183 m_buf = m_stream.read(512);
2185 if (m_stream.atEnd()) {
2195 if (m_pos < m_buf.size()) {
2196 return m_buf.at(m_pos++);
2197 }
else if (!atEnd()) {
2207 QTextStream &m_stream;
2210 long long int m_pos;
2215#ifdef MD4QT_ICU_STL_SUPPORT
2225 std::vector<unsigned char> content;
2227 stream.seekg(0, std::ios::end);
2228 const auto ssize = stream.tellg();
2229 content.resize((
size_t)ssize + 1);
2230 stream.seekg(0, std::ios::beg);
2231 stream.read((
char *)&content[0], ssize);
2232 content[(size_t)ssize] = 0;
2234 const auto z = std::count(content.cbegin(), content.cend(), 0);
2237 std::vector<unsigned char> tmp;
2238 tmp.resize(content.size() + (z - 1) * 2);
2240 for (
size_t i = 0, j = 0; i < content.size() - 1; ++i, ++j) {
2241 if (content[i] == 0) {
2247 tmp[j] = content[i];
2251 tmp[tmp.size() - 1] = 0;
2253 std::swap(content, tmp);
2256 m_str = UnicodeString::fromUTF8((
char *)&content[0]);
2262 return m_pos == m_str.size();
2270 bool rFound =
false;
2273 const auto c = getChar();
2302 return m_str[m_pos++];
2304 return UnicodeChar();
2309 UnicodeString m_str;
2310 long long int m_pos;
2316template<
class Trait>
2321 const long long int e = line.indexOf(Trait::latin1ToString(
"-->"), pos);
2331template<
class Trait>
2339 const auto &str = line.asString();
2341 while ((p = str.indexOf(Trait::latin1ToString(
s_startComment), p)) != -1) {
2342 bool addNegative =
false;
2344 auto c = str.sliced(p);
2346 if (c.startsWith(Trait::latin1ToString(
"<!-->"))) {
2347 res.insert({line.virginPos(p), {0,
true}});
2352 }
else if (c.startsWith(Trait::latin1ToString(
"<!--->"))) {
2353 res.insert({line.virginPos(p), {1,
true}});
2361 res.insert({line.virginPos(p), {2,
true}});
2365 for (; l < stream.
size(); ++l) {
2366 c.push_back(Trait::latin1ToChar(
' '));
2367 c.push_back(stream.
lineAt(l).asString());
2370 res.insert({line.virginPos(p), {2,
true}});
2372 addNegative =
false;
2380 res.insert({line.virginPos(p), {-1,
false}});
2387template<
class Trait>
2388inline std::pair<long long int, bool>
2390 std::shared_ptr<Block<Trait>> parent,
2392 typename Trait::StringList &linksToParse,
2393 const typename Trait::String &workingPath,
2394 const typename Trait::String &fileName,
2395 bool collectRefLinks)
2397 auto clearCtx = [&ctx] () {
2398 ctx.m_fragment.clear();
2399 ctx.m_type = BlockType::EmptyLine;
2400 ctx.m_emptyLineInList =
false;
2401 ctx.m_fensedCodeInList =
false;
2402 ctx.m_emptyLinesCount = 0;
2403 ctx.m_lineCounter = 0;
2404 ctx.m_indents.clear();
2405 ctx.m_indent = {-1, -1};
2406 ctx.m_startOfCode.clear();
2407 ctx.m_startOfCodeInList.clear();
2410 if (!ctx.m_fragment.empty()) {
2411 MdBlock<Trait> block = {ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0};
2413 const auto line = parseFragment(block, parent, doc, linksToParse, workingPath,
2414 fileName, collectRefLinks, ctx.m_html);
2416 assert(line != ctx.m_fragment.front().second.m_lineNumber);
2419 if (ctx.m_html.m_html) {
2420 if (!collectRefLinks) {
2421 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2427 const auto it = ctx.m_fragment.cbegin() + (line - ctx.m_fragment.cbegin()->second.m_lineNumber);
2430 std::copy(ctx.m_fragment.cbegin(), it, std::back_inserter(tmp.m_data));
2432 long long int emptyLines = 0;
2434 while (!tmp.m_data.empty() && tmp.m_data.back().first.asString().simplified().isEmpty()) {
2435 tmp.m_data.pop_back();
2436 tmp.m_emptyLineAfter =
true;
2440 if (!tmp.m_data.empty()) {
2441 ctx.m_splitted.push_back(tmp);
2444 const auto retLine = it->second.m_lineNumber;
2445 const auto retMayBreakList = it->second.m_mayBreakList;
2449 ctx.m_emptyLinesBefore = emptyLines;
2451 return {retLine, retMayBreakList};
2454 ctx.m_splitted.push_back({ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0});
2463template<
class Trait>
2467 unsigned char size = 4;
2468 long long int len = s.length();
2470 for (
long long int i = 0; i < len; ++i, --size) {
2471 if (s[i] == Trait::latin1ToChar(
'\t')) {
2472 s.replaceOne(i, 1,
typename Trait::String(size, Trait::latin1ToChar(
' ')));
2485template<
class Trait>
2488 StringListStream<Trait> &stream,
2489 std::shared_ptr<Block<Trait>> parent,
2491 typename Trait::StringList &linksToParse,
2492 const typename Trait::String &workingPath,
2493 const typename Trait::String &fileName,
2494 bool collectRefLinks)
2496 long long int emptyLinesCount = 0;
2497 bool wasEmptyLine =
false;
2499 while (!stream.atEnd()) {
2500 const auto currentLineNumber = stream.currentLineNumber();
2502 typename Trait::InternalString line;
2505 std::tie(line, mayBreak) = readLine(ctx, stream);
2511 if (ns == line.length() || line.asString().startsWith(Trait::latin1ToString(
" "))) {
2512 if (ns == line.length()) {
2514 wasEmptyLine =
true;
2516 emptyLinesCount = 0;
2519 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2520 }
else if (!wasEmptyLine) {
2522 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2524 ctx.m_lineType = BlockType::Footnote;
2526 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2530 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2533 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2536 whatIsTheLine(line,
false,
false,
false, &ctx.m_startOfCodeInList, &ctx.m_indent,
2537 ctx.m_lineType == BlockType::EmptyLine,
true, &ctx.m_indents);
2539 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2541 if (ctx.m_type == BlockType::Footnote) {
2542 wasEmptyLine =
false;
2551 if (stream.atEnd() && !ctx.m_fragment.empty()) {
2552 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2556template<
class Trait>
2558Parser<Trait>::finishHtml(ParserContext &ctx,
2561 bool collectRefLinks,
2563 bool dontProcessLastFreeHtml)
2565 if (!collectRefLinks || top) {
2566 if (ctx.m_html.m_html->isFreeTag()) {
2567 if (!dontProcessLastFreeHtml) {
2568 if (ctx.m_html.m_parent) {
2569 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2573 parent->appendItem(ctx.m_html.m_html);
2578 p->appendItem(ctx.m_html.m_html);
2579 p->setStartColumn(ctx.m_html.m_html->startColumn());
2580 p->setStartLine(ctx.m_html.m_html->startLine());
2581 p->setEndColumn(ctx.m_html.m_html->endColumn());
2582 p->setEndLine(ctx.m_html.m_html->endLine());
2587 if (!dontProcessLastFreeHtml) {
2591 ctx.m_html.m_toAdjustLastPos.clear();
2594template<
class Trait>
2596Parser<Trait>::makeLineMain(ParserContext &ctx,
2597 const typename Trait::InternalString &line,
2598 long long int emptyLinesCount,
2599 const ListIndent ¤tIndent,
2601 long long int currentLineNumber)
2603 if (ctx.m_html.m_htmlBlockType >= 6) {
2604 ctx.m_html.m_continueHtml = (emptyLinesCount <= 0);
2607 ctx.m_type = ctx.m_lineType;
2609 switch (ctx.m_type) {
2610 case BlockType::List:
2611 case BlockType::ListWithFirstEmptyLine: {
2612 if (ctx.m_indents.empty())
2613 ctx.m_indents.push_back(currentIndent.m_indent);
2615 ctx.m_indent = currentIndent;
2618 case BlockType::Code:
2626 if (!line.isEmpty() && ns < line.length()) {
2627 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData}});
2630 ctx.m_lineCounter = 1;
2631 ctx.m_emptyLinesCount = 0;
2632 ctx.m_emptyLinesBefore = emptyLinesCount;
2635template<
class Trait>
2636inline std::pair<long long int, bool>
2637Parser<Trait>::parseFragmentAndMakeNextLineMain(ParserContext &ctx,
2640 typename Trait::StringList &linksToParse,
2641 const typename Trait::String &workingPath,
2642 const typename Trait::String &fileName,
2643 bool collectRefLinks,
2644 const typename Trait::InternalString &line,
2645 const ListIndent ¤tIndent,
2647 long long int currentLineNumber)
2649 const auto empty = ctx.m_emptyLinesCount;
2651 const auto ret = parseFragment(ctx, parent, doc, linksToParse, workingPath,
2652 fileName, collectRefLinks);
2654 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2659template<
class Trait>
2661Parser<Trait>::isListType(BlockType t)
2664 case BlockType::List:
2665 case BlockType::ListWithFirstEmptyLine:
2673template<
class Trait>
2674std::pair<typename Trait::InternalString, bool>
2675Parser<Trait>::readLine(
typename Parser<Trait>::ParserContext &ctx,
2678 ctx.m_htmlCommentData.clear();
2680 auto line = stream.readLine();
2682 static const char16_t c_zeroReplaceWith[2] = {0xFFFD, 0};
2684 line.first.replace(
typename Trait::Char(0), Trait::utf16ToString(&c_zeroReplaceWith[0]));
2691template<
class Trait>
2692inline std::pair<long long int, bool>
2693Parser<Trait>::parseFirstStep(ParserContext &ctx,
2697 typename Trait::StringList &linksToParse,
2698 const typename Trait::String &workingPath,
2699 const typename Trait::String &fileName,
2700 bool collectRefLinks)
2702 while (!stream.atEnd()) {
2703 const auto currentLineNumber = stream.currentLineNumber();
2705 typename Trait::InternalString line;
2708 std::tie(line, mayBreak) = readLine(ctx, stream);
2710 if (ctx.m_lineType != BlockType::Unknown) {
2711 ctx.m_prevLineType = ctx.m_lineType;
2714 ctx.m_lineType = whatIsTheLine(line,
2715 (ctx.m_emptyLineInList || isListType(ctx.m_type)),
2716 ctx.m_prevLineType == BlockType::ListWithFirstEmptyLine,
2717 ctx.m_fensedCodeInList,
2718 &ctx.m_startOfCodeInList,
2720 ctx.m_lineType == BlockType::EmptyLine,
2724 if (isListType(ctx.m_type) && ctx.m_lineType == BlockType::FensedCodeInList) {
2725 ctx.m_fensedCodeInList = !ctx.m_fensedCodeInList;
2728 const auto currentIndent = ctx.m_indent;
2732 const auto indentInListValue =
indentInList(&ctx.m_indents, ns,
true);
2734 if (isListType(ctx.m_lineType) && !ctx.m_fensedCodeInList && ctx.m_indent.m_level > -1) {
2735 if (ctx.m_indent.m_level < (
long long int)ctx.m_indents.size()) {
2736 ctx.m_indents.erase(ctx.m_indents.cbegin() + ctx.m_indent.m_level, ctx.m_indents.cend());
2739 ctx.m_indents.push_back(ctx.m_indent.m_indent);
2742 if (ctx.m_type == BlockType::CodeIndentedBySpaces && ns > 3) {
2743 ctx.m_lineType = BlockType::CodeIndentedBySpaces;
2746 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2 &&
2747 !isListType(ctx.m_lineType)) {
2748 if (ctx.m_emptyLinesCount > 0) {
2749 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2761 if (l.first != -1) {
2767 ctx.m_emptyLineInList =
false;
2768 ctx.m_emptyLinesCount = 0;
2772 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2) {
2773 ctx.m_type = BlockType::List;
2777 if (ctx.m_lineType == BlockType::Footnote) {
2778 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2790 if (l.first != -1) {
2794 eatFootnote(ctx, stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2800 if (ns != line.length() && ctx.m_type == BlockType::EmptyLine) {
2801 makeLineMain(ctx, line, ctx.m_emptyLinesCount, currentIndent, ns, currentLineNumber);
2804 }
else if (ns == line.length() && ctx.m_type == BlockType::EmptyLine) {
2808 ++ctx.m_lineCounter;
2811 if (ns == line.length()) {
2812 ++ctx.m_emptyLinesCount;
2814 switch (ctx.m_type) {
2815 case BlockType::Blockquote: {
2816 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2819 if (l.first != -1) {
2826 case BlockType::Text:
2827 case BlockType::CodeIndentedBySpaces:
2831 case BlockType::Code: {
2832 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2833 ctx.m_emptyLinesCount = 0;
2838 case BlockType::List:
2839 case BlockType::ListWithFirstEmptyLine: {
2840 ctx.m_emptyLineInList =
true;
2850 else if (ctx.m_emptyLineInList) {
2851 if (indentInListValue || isListType(ctx.m_lineType) || ctx.m_lineType == BlockType::SomethingInList) {
2852 for (
long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2853 ctx.m_fragment.push_back({
typename Trait::String(),
2854 {currentLineNumber - ctx.m_emptyLinesCount + i, {},
false}});
2857 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2859 ctx.m_emptyLineInList =
false;
2860 ctx.m_emptyLinesCount = 0;
2864 const auto empty = ctx.m_emptyLinesCount;
2866 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2869 if (l.first != -1) {
2873 ctx.m_lineType = whatIsTheLine(line,
false,
false,
false,
nullptr,
nullptr,
2874 true,
false, &ctx.m_indents);
2876 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2880 }
else if (ctx.m_emptyLinesCount > 0) {
2881 if (ctx.m_type == BlockType::CodeIndentedBySpaces &&
2882 ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
2883 const auto indent =
skipSpaces<Trait>(0, ctx.m_fragment.front().first.asString());
2885 for (
long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2886 ctx.m_fragment.push_back({
typename Trait::String(indent, Trait::latin1ToChar(
' ')),
2887 {currentLineNumber - ctx.m_emptyLinesCount + i, {},
false}});
2890 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2891 ctx.m_emptyLinesCount = 0;
2893 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2905 if (l.first != -1) {
2914 if (ctx.m_type != ctx.m_lineType && ctx.m_type != BlockType::Code &&
2915 !isListType(ctx.m_type) && ctx.m_type != BlockType::Blockquote) {
2916 if (ctx.m_type == BlockType::Text && ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
2917 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2920 if (ctx.m_type == BlockType::Text && isListType(ctx.m_lineType)) {
2921 if (ctx.m_lineType != BlockType::ListWithFirstEmptyLine) {
2926 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2932 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2938 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2950 if (l.first != -1) {
2956 else if (ctx.m_type == BlockType::Code && ctx.m_type == ctx.m_lineType &&
2957 !ctx.m_startOfCode.isEmpty() &&
2960 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2962 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2965 if (l.first != -1) {
2970 else if (ctx.m_type != ctx.m_lineType && isListType(ctx.m_type) &&
2971 ctx.m_lineType != BlockType::SomethingInList &&
2972 ctx.m_lineType != BlockType::FensedCodeInList && !isListType(ctx.m_lineType)) {
2973 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2985 if (l.first != -1) {
2988 }
else if (ctx.m_type == BlockType::Heading) {
2989 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3001 if (l.first != -1) {
3005 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3008 ctx.m_emptyLinesCount = 0;
3011 if (!ctx.m_fragment.empty()) {
3012 if (ctx.m_type == BlockType::Code) {
3013 ctx.m_fragment.push_back({ctx.m_startOfCode, {-1, {},
false}});
3016 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3019 if (l.first != -1) {
3027template<
class Trait>
3029Parser<Trait>::parseSecondStep(ParserContext &ctx,
3032 typename Trait::StringList &linksToParse,
3033 const typename Trait::String &workingPath,
3034 const typename Trait::String &fileName,
3035 bool collectRefLinks,
3037 bool dontProcessLastFreeHtml)
3042 for (
long long int i = 0; i < (
long long int)ctx.m_splitted.size(); ++i) {
3043 parseFragment(ctx.m_splitted[i], parent, doc, linksToParse, workingPath, fileName,
false,
3046 if (ctx.m_html.m_htmlBlockType >= 6) {
3047 ctx.m_html.m_continueHtml = (!ctx.m_splitted[i].m_emptyLineAfter);
3050 if (ctx.m_html.m_html && !ctx.m_html.m_continueHtml) {
3051 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3052 }
else if (!ctx.m_html.m_html) {
3053 ctx.m_html.m_toAdjustLastPos.clear();
3058 if (ctx.m_html.m_html) {
3059 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3063template<
class Trait>
3064inline std::pair<RawHtmlBlock<Trait>,
long long int>
3068 typename Trait::StringList &linksToParse,
3069 const typename Trait::String &workingPath,
3070 const typename Trait::String &fileName,
3071 bool collectRefLinks,
3073 bool dontProcessLastFreeHtml,
3074 bool stopOnMayBreakList)
3078 auto line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3081 while (line.first != -1 && !(stopOnMayBreakList && line.second)) {
3082 stream.setLineNumber(line.first);
3084 line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3088 parseSecondStep(ctx, parent, doc, linksToParse, workingPath, fileName,
3089 collectRefLinks, top, dontProcessLastFreeHtml);
3091 return {ctx.m_html, line.first};
3094#ifdef MD4QT_QT_SUPPORT
3098Parser<QStringTrait>::parseFile(
const QString &fileName,
3101 const QStringList &ext,
3102 QStringList *parentLinks)
3104 QFileInfo fi(fileName);
3106 if (fi.exists() && ext.
contains(fi.suffix().toLower())) {
3110 QTextStream s(f.readAll());
3113 parseStream(s, fi.absolutePath(), fi.fileName(), recursive, doc, ext, parentLinks);
3120#ifdef MD4QT_ICU_STL_SUPPORT
3124Parser<UnicodeStringTrait>::parseFile(
const UnicodeString &fileName,
3127 const std::vector<UnicodeString> &ext,
3128 std::vector<UnicodeString> *parentLinks)
3132 fileName.toUTF8String(fn);
3135 auto e = UnicodeString::fromUTF8(std::filesystem::u8path(fn).extension().u8string());
3141 if (std::find(ext.cbegin(), ext.cend(), e.toLower()) != ext.cend()) {
3142 auto path = std::filesystem::canonical(std::filesystem::u8path(fn));
3143 std::ifstream file(
path.c_str(), std::ios::in | std::ios::binary);
3146 const auto fileNameS =
path.filename().u8string();
3147 auto workingDirectory =
path.remove_filename().u8string();
3149 if (!workingDirectory.empty()) {
3150 workingDirectory.erase(workingDirectory.size() - 1, 1);
3153 std::replace(workingDirectory.begin(), workingDirectory.end(),
'\\',
'/');
3155 parseStream(file, UnicodeString::fromUTF8(workingDirectory),
3156 UnicodeString::fromUTF8(fileNameS), recursive, doc, ext, parentLinks);
3161 }
catch (
const std::exception &) {
3169template<
class Trait>
3174 for (
auto it = linksToParse.begin(), last = linksToParse.end(); it != last; ++it) {
3175 auto nextFileName = *it;
3177 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3178 const auto lit = doc->labeledLinks().find(nextFileName);
3180 if (lit != doc->labeledLinks().cend()) {
3181 nextFileName = lit->second->url();
3187 if (Trait::fileExists(nextFileName)) {
3188 *it = Trait::absoluteFilePath(nextFileName);
3193template<
class Trait>
3196 const typename Trait::String &workingPath,
3197 const typename Trait::String &fileName,
3200 const typename Trait::StringList &ext,
3201 typename Trait::StringList *parentLinks)
3203 typename Trait::StringList linksToParse;
3205 const auto path = workingPath.
isEmpty() ?
typename Trait::String(fileName) :
3206 typename Trait::String(workingPath + Trait::latin1ToString(
"/") + fileName);
3213 TextStream<Trait> stream(s);
3215 long long int i = 0;
3217 while (!stream.atEnd()) {
3218 data.push_back(std::pair<typename Trait::InternalString, MdLineData>(stream.readLine(), {i}));
3225 parse(stream, doc, doc, linksToParse, workingPath, fileName,
true,
true);
3227 m_parsedFiles.push_back(path);
3232 if (recursive && !linksToParse.empty()) {
3233 const auto tmpLinks = linksToParse;
3235 while (!linksToParse.empty()) {
3236 auto nextFileName = linksToParse.front();
3237 linksToParse.erase(linksToParse.cbegin());
3240 const auto pit = std::find(parentLinks->cbegin(), parentLinks->cend(), nextFileName);
3242 if (pit != parentLinks->cend()) {
3247 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3251 const auto pit = std::find(m_parsedFiles.cbegin(), m_parsedFiles.cend(), nextFileName);
3253 if (pit == m_parsedFiles.cend()) {
3258 parseFile(nextFileName, recursive, doc, ext, &linksToParse);
3263 std::copy(tmpLinks.cbegin(), tmpLinks.cend(), std::back_inserter(*parentLinks));
3269template<
class Trait>
3274 long long int p = 0;
3276 for (; p < s.size(); ++p) {
3277 if (!s[p].isSpace()) {
3283 for (; p < s.size(); ++p) {
3284 if (!s[p].isDigit()) {
3292 long long int sc = 0;
3294 for (; p < s.size(); ++p) {
3295 if (!s[p].isSpace()) {
3302 if (p == s.length() || sc > 4) {
3304 }
else if (sc == 0) {
3316 long long int level = indents.
size();
3318 for (
auto it = indents.crbegin(), last = indents.crend(); it != last; ++it) {
3329template<
class Trait>
3333 bool inListWithFirstEmptyLine,
3334 bool fensedCodeInList,
3335 typename Trait::String *startOfCode,
3337 bool emptyLinePreceded,
3339 const std::vector<long long int> *indents)
3345 if (first < str.length()) {
3346 auto s = str.sliced(first);
3348 const bool isBlockquote = s.asString().startsWith(Trait::latin1ToString(
">"));
3349 const bool indentIn =
indentInList(indents, first,
false);
3350 bool isHeading =
false;
3353 return BlockType::Footnote;
3356 if (s.asString().startsWith(Trait::latin1ToString(
"#")) &&
3357 (indent ? first - indent->m_indent < 4 : first < 4)) {
3358 long long int c = 0;
3360 while (c < s.length() && s[c] == Trait::latin1ToChar(
'#')) {
3364 if (c <= 6 && ((c < s.length() && s[c].isSpace()) || c == s.length())) {
3370 bool isFirstLineEmpty =
false;
3374 const auto codeIndentedBySpaces = emptyLinePreceded && first >= 4 &&
3377 if (fensedCodeInList) {
3381 return BlockType::FensedCodeInList;
3385 return BlockType::SomethingInList;
3389 if (fensedCode && indentIn) {
3394 return BlockType::FensedCodeInList;
3395 }
else if ((((s.asString().startsWith(Trait::latin1ToString(
"-")) ||
3396 s.asString().startsWith(Trait::latin1ToString(
"+")) ||
3397 s.asString().startsWith(Trait::latin1ToString(
"*"))) &&
3398 ((s.length() > 1 && s[1] == Trait::latin1ToChar(
' ')) || s.length() == 1)) ||
3399 orderedList) && (first < 4 || indentIn)) {
3400 if (codeIndentedBySpaces) {
3401 return BlockType::CodeIndentedBySpaces;
3404 if (indent && calcIndent) {
3406 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3409 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3410 return BlockType::ListWithFirstEmptyLine;
3412 return BlockType::List;
3415 return BlockType::SomethingInList;
3418 if (!isHeading && !isBlockquote &&
3419 !(fensedCode && first < 4) && !emptyLinePreceded && !inListWithFirstEmptyLine) {
3420 return BlockType::SomethingInList;
3424 bool isFirstLineEmpty =
false;
3428 const bool isHLine = first < 4 && isHorizontalLine<Trait>(s.asString());
3431 (((s.asString().startsWith(Trait::latin1ToString(
"-")) || s.asString().startsWith(Trait::latin1ToString(
"+")) ||
3432 s.asString().startsWith(Trait::latin1ToString(
"*"))) &&
3433 ((s.length() > 1 && s[1] == Trait::latin1ToChar(
' ')) || s.length() == 1)) ||
3434 orderedList) && first < 4) {
3435 if (indent && calcIndent) {
3437 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3440 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3441 return BlockType::ListWithFirstEmptyLine;
3443 return BlockType::List;
3448 if (str.asString().startsWith(
typename Trait::String(4, Trait::latin1ToChar(
' ')))) {
3449 return BlockType::CodeIndentedBySpaces;
3451 return BlockType::Code;
3452 }
else if (isBlockquote) {
3453 return BlockType::Blockquote;
3454 }
else if (isHeading) {
3455 return BlockType::Heading;
3458 return BlockType::EmptyLine;
3461 return BlockType::Text;
3464template<
class Trait>
3469 typename Trait::StringList &linksToParse,
3470 const typename Trait::String &workingPath,
3471 const typename Trait::String &fileName,
3472 bool collectRefLinks,
3475 if (html.m_continueHtml) {
3476 parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3479 if (!collectRefLinks) {
3480 parent->appendItem(html.m_html);
3486 switch (whatIsTheLine(fr.m_data.front().first)) {
3487 case BlockType::Footnote:
3488 parseFootnote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3491 case BlockType::Text:
3492 parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3495 case BlockType::Blockquote:
3496 return parseBlockquote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3499 case BlockType::Code:
3500 return parseCode(fr, parent, collectRefLinks);
3503 case BlockType::CodeIndentedBySpaces: {
3506 if (fr.m_data.front().first.asString().startsWith(Trait::latin1ToString(
" "))) {
3510 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, {}, -1, -1,
false);
3513 case BlockType::Heading:
3514 parseHeading(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3517 case BlockType::List:
3518 case BlockType::ListWithFirstEmptyLine:
3519 return parseList(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3529template<
class Trait>
3531Parser<Trait>::clearCache()
3533 m_parsedFiles.clear();
3537template<
class Trait>
3541 if (s.contains(Trait::latin1ToChar(
'|'))) {
3544 const auto tmp = s.simplified();
3545 const auto p = tmp.startsWith(Trait::latin1ToString(
"|")) ? 1 : 0;
3546 const auto n = tmp.size() - p - (tmp.endsWith(Trait::latin1ToString(
"|")) && tmp.size() > 1 ? 1 : 0);
3547 const auto v = tmp.sliced(p, n);
3549 bool backslash =
false;
3551 for (
long long int i = 0; i < v.size(); ++i) {
3554 if (v[i] == Trait::latin1ToChar(
'\\') && !backslash) {
3557 }
else if (v[i] == Trait::latin1ToChar(
'|') && !backslash) {
3574template<
class Trait>
3577 std::shared_ptr<Block<Trait>> parent,
3579 typename Trait::StringList &linksToParse,
3580 const typename Trait::String &workingPath,
3581 const typename Trait::String &fileName,
3582 bool collectRefLinks,
3583 RawHtmlBlock<Trait> &html)
3588 if (c && h && c == h && !html.m_continueHtml) {
3589 parseTable(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, c);
3591 if (!fr.m_data.empty()) {
3592 StringListStream<Trait> stream(fr.m_data);
3594 Parser<Trait>::parse(stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3597 parseParagraph(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3602template<
class Trait>
3603inline std::pair<typename Trait::String, WithPosition>
3606 const auto start = s.asString().indexOf(Trait::latin1ToString(
"{#"));
3609 long long int p =
start + 2;
3611 for (; p < s.length(); ++p) {
3612 if (s[p] == Trait::latin1ToChar(
'}')) {
3617 if (p < s.length() && s[p] == Trait::latin1ToChar(
'}')) {
3624 return {label, pos};
3632template<
class Trait>
3633inline typename Trait::String
3636 typename Trait::String res;
3638 for (
long long int i = 0; i < s.length(); ++i) {
3639 const auto c = s[i];
3641 if (c.isLetter() || c.isDigit() || c == Trait::latin1ToChar(
'-') ||
3642 c == Trait::latin1ToChar(
'_')) {
3643 res.push_back(c.toLower());
3644 }
else if (c.isSpace()) {
3645 res.push_back(Trait::latin1ToString(
"-"));
3653template<
class Trait>
3654inline typename Trait::String
3657 typename Trait::String l;
3663 for (
auto it = p->
items().cbegin(), last = p->
items().cend(); it != last; ++it) {
3664 switch ((*it)->type()) {
3667 const auto text = t->text();
3674 if (!i->p()->isEmpty()) {
3676 }
else if (!i->text().isEmpty()) {
3682 auto link =
static_cast<Link<Trait> *
>(it->get());
3684 if (!link->p()->isEmpty()) {
3686 }
else if (!link->text().isEmpty()) {
3694 if (!c->text().isEmpty()) {
3708template<
class Trait>
3712 long long int end = -1;
3713 long long int start = -1;
3715 for (
long long int i = s.length() - 1; i >= 0; --i) {
3716 if (!s[i].isSpace() && s[i] != Trait::latin1ToChar(
'#') && end == -1) {
3720 if (s[i] == Trait::latin1ToChar(
'#')) {
3726 if (s[i - 1].isSpace()) {
3729 }
else if (s[i - 1] != Trait::latin1ToChar(
'#')) {
3740 if (
start != -1 && end != -1) {
3750template<
class Trait>
3753 std::shared_ptr<Block<Trait>> parent,
3755 typename Trait::StringList &linksToParse,
3756 const typename Trait::String &workingPath,
3757 const typename Trait::String &fileName,
3758 bool collectRefLinks)
3760 if (!fr.m_data.empty() && !collectRefLinks) {
3761 auto line = fr.m_data.front().first;
3765 h->setStartLine(fr.m_data.front().second.m_lineNumber);
3766 h->setEndColumn(line.virginPos(line.length() - 1));
3767 h->setEndLine(h->startLine());
3769 long long int pos = 0;
3773 line = line.sliced(pos);
3779 while (pos < line.length() && line[pos] == Trait::latin1ToChar(
'#')) {
3784 WithPosition startDelim = {h->startColumn(), h->startLine(),
3785 line.virginPos(pos - 1), h->startLine()};
3790 fr.m_data.front().first = line.sliced(pos);
3799 if (endDelim.startColumn() != -1) {
3800 endDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
3801 endDelim.setEndLine(endDelim.startLine());
3803 delims.push_back(endDelim);
3806 h->setDelims(delims);
3812 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
3813 Trait::latin1ToString(
"")) + fileName);
3815 label.second.setStartLine(fr.m_data.front().second.m_lineNumber);
3816 label.second.setEndLine(
label.second.startLine());
3818 h->setLabelPos(
label.second);
3825 tmp.push_back(fr.m_data.front());
3830 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
3831 false,
false, html,
false);
3833 fr.m_data.erase(fr.m_data.cbegin());
3841 if (h->isLabeled()) {
3842 doc->insertLabeledHeading(h->label(), h);
3844 typename Trait::String
label = Trait::latin1ToString(
"#") +
3847 label += Trait::latin1ToString(
"/") +
3848 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
3849 Trait::latin1ToString(
"")) + fileName;
3853 doc->insertLabeledHeading(label, h);
3856 parent->appendItem(h);
3861template<
class Trait>
3862inline typename Trait::InternalString
3865 s.replace(Trait::latin1ToString(
"\\|"), Trait::latin1ToString(
"|"));
3871template<
class Trait>
3872inline std::pair<typename Trait::InternalStringList, std::vector<long long int>>
3875 typename Trait::InternalStringList res;
3876 std::vector<long long int> columns;
3878 bool backslash =
false;
3879 long long int start = 0;
3881 for (
long long int i = 0; i < s.length(); ++i) {
3884 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash) {
3887 }
else if (s[i] == Trait::latin1ToChar(
'|') && !backslash) {
3889 columns.push_back(s.virginPos(i));
3900 return {res, columns};
3903template<
class Trait>
3906 std::shared_ptr<Block<Trait>> parent,
3908 typename Trait::StringList &linksToParse,
3909 const typename Trait::String &workingPath,
3910 const typename Trait::String &fileName,
3911 bool collectRefLinks,
3914 static const char sep =
'|';
3916 if (fr.m_data.size() >= 2) {
3918 table->setStartColumn(fr.m_data.front().first.virginPos(0));
3919 table->setStartLine(fr.m_data.front().second.m_lineNumber);
3920 table->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
3921 table->setEndLine(fr.m_data.back().second.m_lineNumber);
3924 const auto &row = lineData.first;
3926 if (row.asString().startsWith(Trait::latin1ToString(
" "))) {
3933 if (p == line.length()) {
3937 if (line[p] == Trait::latin1ToChar(sep)) {
3938 line.remove(0, p + 1);
3941 for (p = line.length() - 1; p >= 0; --p) {
3942 if (!line[p].isSpace()) {
3951 if (line[p] == Trait::latin1ToChar(sep)) {
3952 line.remove(p, line.length() - p);
3956 columns.second.insert(columns.second.begin(), row.virginPos(0));
3957 columns.second.push_back(row.virginPos(row.length() - 1));
3960 tr->setStartColumn(row.virginPos(0));
3961 tr->setStartLine(lineData.second.m_lineNumber);
3962 tr->setEndColumn(row.virginPos(row.length() - 1));
3963 tr->setEndLine(lineData.second.m_lineNumber);
3967 for (
auto it = columns.first.begin(), last = columns.first.end(); it != last; ++it, ++col) {
3968 if (col == columnsCount) {
3973 c->setStartColumn(columns.second.at(col));
3974 c->setStartLine(lineData.second.m_lineNumber);
3975 c->setEndColumn(columns.second.at(col + 1));
3976 c->setEndLine(lineData.second.m_lineNumber);
3978 if (!it->isEmpty()) {
3979 it->replace(Trait::latin1ToString(
"|"), Trait::latin1ToChar(sep));
3982 fragment.push_back({*it, lineData.second});
3989 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
3990 collectRefLinks,
false, html,
false);
3992 if (!p->isEmpty()) {
3993 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it ) {
3994 switch ((*it)->type()) {
3996 const auto pp = std::static_pointer_cast<Paragraph<Trait>>(*it);
3998 for (
auto it = pp->items().cbegin(), last = pp->items().cend(); it != last; ++it) {
3999 c->appendItem((*it));
4005 c->appendItem((*it));
4011 if (html.m_html.get()) {
4012 c->appendItem(html.m_html);
4020 table->appendRow(tr);
4026 auto fmt = fr.m_data.at(1).first;
4028 auto columns = fmt.split(
typename Trait::InternalString(Trait::latin1ToChar(sep)));
4030 for (
auto it = columns.begin(), last = columns.end(); it != last; ++it) {
4031 *it = it->simplified();
4033 if (!it->isEmpty()) {
4036 if (it->asString().endsWith(Trait::latin1ToString(
":")) &&
4037 it->asString().startsWith(Trait::latin1ToString(
":"))) {
4039 }
else if (it->asString().endsWith(Trait::latin1ToString(
":"))) {
4043 table->setColumnAlignment(table->columnsCount(), a);
4048 fr.m_data.erase(fr.m_data.cbegin() + 1);
4050 long long int r = 0;
4052 for (
const auto &line : std::as_const(fr.m_data)) {
4053 if (!parseTableRow(line)) {
4060 fr.m_data.erase(fr.m_data.cbegin(), fr.m_data.cbegin() + r);
4062 if (!table->isEmpty() && !collectRefLinks) {
4063 parent->appendItem(table);
4069template<
class Trait>
4071isH(
const typename Trait::String &s,
4072 const typename Trait::Char &c)
4080 const auto start = p;
4082 for (; p < s.size(); ++p) {
4088 if (p -
start < 1) {
4092 for (; p < s.size(); ++p) {
4093 if (!s[p].isSpace()) {
4102template<
class Trait>
4104isH1(
const typename Trait::String &s)
4106 return isH<Trait>(s, Trait::latin1ToChar(
'='));
4110template<
class Trait>
4112isH2(
const typename Trait::String &s)
4114 return isH<Trait>(s, Trait::latin1ToChar(
'-'));
4118template<
class Trait>
4119inline std::pair<long long int, long long int>
4125 return {pos - 1, line};
4128 for (
long long int i = 0; i < static_cast<long long int>(fr.
m_data.size()); ++i) {
4129 if (fr.
m_data.at(i).second.m_lineNumber == line) {
4131 return {fr.
m_data.at(i - 1).first.virginPos(fr.
m_data.at(i - 1).first.length() - 1),
4141template<
class Trait>
4142inline std::pair<long long int, long long int>
4147 for (
long long int i = 0; i < static_cast<long long int>(fr.
m_data.size()); ++i) {
4148 if (fr.
m_data.at(i).second.m_lineNumber == line) {
4149 if (fr.
m_data.at(i).first.virginPos(fr.
m_data.at(i).first.length() - 1) >= pos + 1) {
4150 return {pos + 1, line};
4151 }
else if (i + 1 <
static_cast<long long int>(fr.
m_data.size())) {
4152 return {fr.
m_data.at(i + 1).first.virginPos(0), fr.
m_data.at(i + 1).second.m_lineNumber};
4162template<
class Trait>
4165 std::shared_ptr<Block<Trait>> parent,
4167 typename Trait::StringList &linksToParse,
4168 const typename Trait::String &workingPath,
4169 const typename Trait::String &fileName,
4170 bool collectRefLinks,
4171 RawHtmlBlock<Trait> &html)
4173 parseFormattedTextLinksImages(fr, parent, doc, linksToParse, workingPath, fileName,
4174 collectRefLinks,
false, html,
false);
4177template<
class Trait>
4182 return html->isFreeTag();
4188 html->setFreeTag(on);
4192template<
class Trait>
4198 for (
long long int line = 0; line < (
long long int)fr.size(); ++line) {
4199 const typename Trait::String &str = fr.at(line).first.asString();
4201 const auto withoutSpaces = str.sliced(p);
4204 d.push_back({Delimiter::HorizontalLine, line, 0, str.length(),
false,
false,
false});
4206 d.push_back({Delimiter::H1, line, 0, str.length(),
false,
false,
false});
4208 d.push_back({Delimiter::H2, line, 0, str.length(),
false,
false,
false});
4210 bool backslash =
false;
4213 for (
long long int i = p; i < str.size(); ++i) {
4216 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
4221 else if ((str[i] == Trait::latin1ToChar(
'_') || str[i] == Trait::latin1ToChar(
'*')) && !backslash) {
4222 typename Trait::String style;
4224 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() :
true);
4225 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4226 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4227 const bool alNumBefore = (i > 0 ? str[i - 1].isLetterOrNumber() :
false);
4229 const auto ch = str[i];
4231 while (i < str.length() && str[i] == ch) {
4232 style.push_back(str[i]);
4236 typename Delimiter::DelimiterType dt = Delimiter::Unknown;
4238 if (ch == Trait::latin1ToChar(
'*')) {
4239 dt = Delimiter::Emphasis1;
4241 dt = Delimiter::Emphasis2;
4244 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() :
true);
4245 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) :
true);
4246 const bool alNumAfter = (i < str.length() ? str[i].isLetterOrNumber() :
false);
4247 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore))
4248 && !(ch == Trait::latin1ToChar(
'_') && alNumBefore && alNumAfter);
4249 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)))
4250 && !(ch == Trait::latin1ToChar(
'_') && alNumBefore && alNumAfter);
4252 if (leftFlanking || rightFlanking) {
4253 for (
auto j = 0; j < style.length(); ++j) {
4254 d.push_back({dt, line, i - style.length() + j, 1,
4255 word,
false, leftFlanking, rightFlanking});
4266 else if (str[i] == Trait::latin1ToChar(
'~') && !backslash) {
4267 typename Trait::String style;
4269 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() :
true);
4270 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4271 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4273 while (i < str.length() && str[i] == Trait::latin1ToChar(
'~')) {
4274 style.push_back(str[i]);
4278 if (style.length() <= 2) {
4279 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() : true);
4280 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) : true);
4281 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore));
4282 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)));
4284 if (leftFlanking || rightFlanking) {
4285 d.push_back({Delimiter::Strikethrough,
4305 else if (str[i] == Trait::latin1ToChar(
'[') && !backslash) {
4306 d.push_back({Delimiter::SquareBracketsOpen, line, i, 1, word,
false});
4311 else if (str[i] == Trait::latin1ToChar(
'!') && !backslash) {
4312 if (i + 1 < str.length()) {
4313 if (str[i + 1] == Trait::latin1ToChar(
'[')) {
4314 d.push_back({Delimiter::ImageOpen, line, i, 2, word,
false});
4327 else if (str[i] == Trait::latin1ToChar(
'(') && !backslash) {
4328 d.push_back({Delimiter::ParenthesesOpen, line, i, 1, word,
false});
4333 else if (str[i] == Trait::latin1ToChar(
']') && !backslash) {
4334 d.push_back({Delimiter::SquareBracketsClose, line, i, 1, word,
false});
4339 else if (str[i] == Trait::latin1ToChar(
')') && !backslash) {
4340 d.push_back({Delimiter::ParenthesesClose, line, i, 1, word,
false});
4345 else if (str[i] == Trait::latin1ToChar(
'<') && !backslash) {
4346 d.push_back({Delimiter::Less, line, i, 1, word,
false});
4351 else if (str[i] == Trait::latin1ToChar(
'>') && !backslash) {
4352 d.push_back({Delimiter::Greater, line, i, 1, word,
false});
4357 else if (str[i] == Trait::latin1ToChar(
'`')) {
4358 typename Trait::String code;
4360 while (i < str.length() && str[i] == Trait::latin1ToChar(
'`')) {
4361 code.push_back(str[i]);
4365 d.push_back({Delimiter::InlineCode,
4367 i - code.length() - (backslash ? 1 : 0),
4368 code.length() + (backslash ? 1 : 0),
4377 else if (str[i] == Trait::latin1ToChar(
'$')) {
4378 typename Trait::String m;
4380 while (i < str.length() && str[i] == Trait::latin1ToChar(
'$')) {
4381 m.push_back(str[i]);
4385 if (m.length() <= 2 && !backslash) {
4386 d.push_back({Delimiter::Math, line, i - m.length(), m.length(),
4387 false,
false,
false,
false});
4408template<
class Trait>
4412 long long int count = 0, pos = s.length() - 1, end = s.length() - 1;
4414 while ((pos = Trait::lastIndexOf(s, Trait::latin1ToString(
"\\"), pos)) != -1 && pos == end) {
4420 return (s.endsWith(Trait::latin1ToString(
" ")) || (count % 2 != 0));
4424template<
class Trait>
4428 return (s.endsWith(Trait::latin1ToString(
" ")) ? 2 : 1);
4432template<
class Trait>
4433inline typename Trait::String
4436 if (s.endsWith(Trait::latin1ToString(
"\\"))) {
4437 return s.sliced(0, s.size() - 1);
4444template<
class Trait>
4455template<
class Trait>
4459 long long int startPos,
4460 long long int startLine,
4461 long long int endPos,
4462 long long int endLine,
4463 bool doRemoveSpacesAtEnd =
false)
4465 if (endPos < 0 && endLine - 1 >= 0) {
4466 endPos = po.
m_fr.m_data.at(endLine - 1).first.length() - 1;
4470 if (endPos == po.
m_fr.m_data.at(endLine).first.length() - 1) {
4471 doRemoveSpacesAtEnd =
true;
4476 if (doRemoveSpacesAtEnd) {
4480 if (startPos == 0) {
4496 t->setStartColumn(po.
m_fr.m_data.at(startLine).first.virginPos(startPos));
4497 t->setStartLine(po.
m_fr.m_data.at(startLine).second.m_lineNumber);
4498 t->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endPos,
true));
4499 t->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4503 po.
m_parent->setEndColumn(t->endColumn());
4504 po.
m_parent->setEndLine(t->endLine());
4512 po.
m_pos = startPos;
4517template<
class Trait>
4521 long long int startPos,
4522 long long int startLine,
4523 long long int endPos,
4524 long long int endLine)
4526 makeTextObject(text, po, startPos, startLine, endPos, endLine,
true);
4529 hr->setText(po.
m_fr.m_data.at(endLine).first.asString().sliced(endPos + 1));
4530 hr->setStartColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endPos + 1));
4531 hr->setStartLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4532 hr->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(po.
m_fr.m_data.at(endLine).first.length() - 1));
4533 hr->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4534 po.
m_parent->setEndColumn(hr->endColumn());
4535 po.
m_parent->setEndLine(hr->endLine());
4542template<
class Trait>
4545 long long int lastLine)
4550 for (; i <= lastLine; ++i) {
4552 const auto c = i + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) ?
4555 if (h && c && c == h) {
4572template<
class Trait>
4579 long long int lastLine,
4581 long long int lastPos,
4584 if (po.
m_line > lastLine) {
4586 }
else if (po.
m_line == lastLine && po.
m_pos >= lastPos) {
4590 typename Trait::String text;
4592 const auto isLastChar = po.
m_pos >= po.
m_fr.m_data.at(po.
m_line).first.length();
4593 long long int startPos = (isLastChar ? 0 : po.
m_pos);
4594 long long int startLine = (isLastChar ? po.
m_line + 1 : po.
m_line);
4598 (po.
m_line == lastLine ? (lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
4603 auto makeTOWLB = [&]() {
4604 if (po.
m_line != (
long long int)(po.
m_fr.m_data.size() - 1)) {
4605 const auto &line = po.
m_fr.m_data.at(po.
m_line).first.asString();
4611 startLine = po.
m_line + 1;
4622 const auto length = (po.
m_line == lastLine ?
4624 const auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(po.
m_pos, length);
4633 po.
m_line == lastLine ? lastPos - 1 : po.
m_fr.m_data.at(po.
m_line).first.length() - 1,
4639 if (po.
m_line != lastLine) {
4650 po.
m_fr.m_data.at(po.
m_line).first.virginSubString());
4664 lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
4667 auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(0, lastPos);
4685template<
class Trait>
4691 while (l < (
long long int)fr.size()) {
4694 if (p < fr[l].first.length()) {
4704template<
class Trait>
4705inline std::pair<bool, bool>
4710 static const typename Trait::String notAllowed = Trait::latin1ToString(
"\"`=<'");
4712 const auto start = p;
4714 for (; p < fr[l].first.length(); ++p) {
4715 if (fr[l].first[p].isSpace()) {
4717 }
else if (notAllowed.contains(fr[l].first[p])) {
4718 return {
false,
false};
4719 }
else if (fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4728template<
class Trait>
4729inline std::pair<bool, bool>
4734 if (p < fr[l].first.length() && fr[l].first[p] != Trait::latin1ToChar(
'"') &&
4735 fr[l].first[p] != Trait::latin1ToChar(
'\'')) {
4739 const auto s = fr[l].first[p];
4743 if (p >= fr[l].first.length()) {
4744 return {
false,
false};
4747 for (; l < (
long long int)fr.size(); ++l) {
4748 bool doBreak =
false;
4750 for (; p < fr[l].first.length(); ++p) {
4751 const auto ch = fr[l].first[p];
4767 if (l >= (
long long int)fr.size()) {
4768 return {
false,
false};
4771 if (p >= fr[l].first.length()) {
4772 return {
false,
false};
4775 if (fr[l].first[p] != s) {
4776 return {
false,
false};
4781 return {
true,
true};
4785template<
class Trait>
4786inline std::pair<bool, bool>
4792 long long int tl = l, tp = p;
4796 if (l >= (
long long int)fr.size()) {
4797 return {
false,
false};
4801 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'/')) {
4802 return {
false,
true};
4806 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4807 return {
false,
true};
4810 if (checkForSpace) {
4811 if (tl == l && tp == p) {
4812 return {
false,
false};
4816 const auto start = p;
4818 for (; p < fr[l].first.length(); ++p) {
4819 const auto ch = fr[l].first[p];
4821 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'=')) {
4826 const typename Trait::String name = fr[l].first.asString().sliced(
start, p -
start).toLower();
4828 if (!name.
startsWith(Trait::latin1ToString(
"_")) && !name.
startsWith(Trait::latin1ToString(
":")) &&
4830 return {
false,
false};
4833 static const typename Trait::String allowedInName =
4834 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789_.:-");
4836 for (
long long int i = 1; i < name.
length(); ++i) {
4837 if (!allowedInName.contains(name[i])) {
4838 return {
false,
false};
4843 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4844 return {
false,
true};
4852 if (l >= (
long long int)fr.size()) {
4853 return {
false,
false};
4857 if (p < fr[l].first.length()) {
4858 if (fr[l].first[p] != Trait::latin1ToChar(
'=')) {
4862 return {
true,
true};
4867 return {
true,
false};
4872 if (l >= (
long long int)fr.size()) {
4873 return {
false,
false};
4880template<
class Trait>
4881inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
4882isHtmlTag(
long long int line,
long long int pos, TextParsingOpts<Trait> &po,
int rule);
4885template<
class Trait>
4892 static const std::set<typename Trait::String> s_rule1Finish = {Trait::latin1ToString(
"/pre"),
4893 Trait::latin1ToString(
"/script"),
4894 Trait::latin1ToString(
"/style"),
4895 Trait::latin1ToString(
"/textarea")};
4899 while (p < po.
m_fr.m_data[line].first.length()) {
4903 typename Trait::String tag;
4905 std::tie(ok, l, p, std::ignore, tag) =
isHtmlTag(line, p, po, rule);
4918 if (s_rule1Finish.find(tag.toLower()) != s_rule1Finish.cend() && l == line) {
4930 if (p >= po.
m_fr.m_data[line].first.length()) {
4938template<
class Trait>
4941 long long int startLine,
4942 long long int endLine)
4944 for (; startLine <= endLine; ++startLine) {
4946 const auto line = po.
m_fr.m_data.at(startLine).first.asString().sliced(pos);
4957template<
class Trait>
4958inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
4964 if (po.
m_fr.m_data[line].first[pos] != Trait::latin1ToChar(
'<')) {
4965 return {
false, line, pos,
false, {}};
4968 typename Trait::String tag;
4970 long long int l = line;
4971 long long int p = pos + 1;
4976 first = (tmp == pos);
4979 if (p >= po.
m_fr.m_data[l].first.length()) {
4980 return {
false, line, pos, first, tag};
4983 bool closing =
false;
4985 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
4988 tag.push_back(Trait::latin1ToChar(
'/'));
4993 const auto start = p;
4996 for (; p < po.
m_fr.m_data[l].first.length(); ++p) {
4997 const auto ch = po.
m_fr.m_data[l].first[p];
4999 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'/')) {
5004 tag.push_back(po.
m_fr.m_data[l].first.asString().sliced(
start, p -
start));
5006 if (p < po.
m_fr.m_data[l].first.length() && po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5007 if (p + 1 < po.
m_fr.m_data[l].first.length() &&
5008 po.
m_fr.m_data[l].first[p + 1] == Trait::latin1ToChar(
'>')) {
5009 long long int tmp = 0;
5015 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5019 return {
true, l, p + 1, onLine, tag};
5021 return {
false, line, pos, first, tag};
5024 return {
false, line, pos, first, tag};
5028 if (p < po.
m_fr.m_data[l].first.length() && po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5029 long long int tmp = 0;
5035 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5039 return {
true, l, p, onLine, tag};
5041 return {
false, line, pos, first, tag};
5047 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5048 return {
false, line, pos, first, tag};
5051 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5052 long long int tmp = 0;
5058 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5062 return {
true, l, p, onLine, tag};
5064 return {
false, line, pos, first, tag};
5069 bool firstAttr =
true;
5078 if (closing && attr) {
5079 return {
false, line, pos, first, tag};
5083 return {
false, line, pos, first, tag};
5087 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5092 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5093 return {
false, line, pos, first, tag};
5097 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5098 long long int tmp = 0;
5104 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5108 return {
true, l, p, onLine, tag};
5110 return {
false, line, pos, first, tag};
5114 return {
false, line, pos, first, {}};
5118template<
class Trait>
5119inline std::pair<typename Trait::String, bool>
5121 TextParsingOpts<Trait> &po)
5123 long long int i = it->m_pos + 1;
5124 const auto start = i;
5126 if (
start >= po.m_fr.m_data[it->m_line].first.length()) {
5130 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5131 const auto ch = po.m_fr.m_data[it->m_line].first[i];
5133 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>')) {
5138 return {po.m_fr.m_data[it->m_line].first.asString().sliced(
start, i -
start),
5139 i < po.m_fr.m_data[it->m_line].first.length() ?
5140 po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'>') : false};
5143template<
class Trait>
5146 typename Delims::iterator last,
5147 TextParsingOpts<Trait> &po)
5151 for (; it != last; ++it) {
5152 if ((it->m_line == po.m_line && it->m_pos < po.m_pos) || it->m_line < po.m_line) {
5163template<
class Trait>
5167 long long int toLine,
5168 long long int toPos,
5173 bool continueEating =
false)
5175 if (line <= toLine) {
5176 typename Trait::String h = po.
m_html.m_html->text();
5178 if (!h.isEmpty() && !continueEating) {
5179 for (
long long int i = 0; i < po.
m_fr.m_emptyLinesBefore; ++i) {
5180 h.push_back(Trait::latin1ToChar(
'\n'));
5184 const auto first = po.
m_fr.m_data[line].first.asString().sliced(
5186 (line == toLine ? (toPos >= 0 ? toPos - pos : po.
m_fr.m_data[line].first.length() - pos) :
5187 po.
m_fr.m_data[line].first.length() - pos));
5189 if (!h.isEmpty() && !first.isEmpty() && po.
m_html.m_html->endLine() != po.
m_fr.m_data[line].second.m_lineNumber) {
5190 h.push_back(Trait::latin1ToChar(
'\n'));
5193 if (!first.isEmpty()) {
5199 for (; line < toLine; ++line) {
5200 h.push_back(Trait::latin1ToChar(
'\n'));
5201 h.push_back(po.
m_fr.m_data[line].first.asString());
5204 if (line == toLine && toPos != 0) {
5205 h.push_back(Trait::latin1ToChar(
'\n'));
5206 h.push_back(po.
m_fr.m_data[line].first.asString().sliced(0, toPos > 0 ?
5207 toPos : po.
m_fr.m_data[line].first.length()));
5210 auto endColumn = toPos;
5211 auto endLine = toLine;
5213 if (endColumn == 0 && endLine > 0) {
5215 endColumn = po.
m_fr.m_data.at(endLine).first.length();
5218 po.
m_html.m_html->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endColumn >= 0 ?
5219 endColumn - 1 : po.
m_fr.m_data.at(endLine).first.length() - 1));
5220 po.
m_html.m_html->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
5222 po.
m_line = (toPos >= 0 ? toLine : toLine + 1);
5223 po.
m_pos = (toPos >= 0 ? toPos : 0);
5225 if (po.
m_line + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) &&
5231 po.
m_html.m_html->setText(h);
5237 if (po.
m_html.m_onLine || htmlRule == 7 || po.
m_line < (
long long int)po.
m_fr.m_data.size()) {
5252 po.
m_html.m_continueHtml =
true;
5256template<
class Trait>
5259 long long int startLine,
5260 long long int endLine)
5262 for (
auto i = startLine + 1; i <= endLine; ++i) {
5263 const auto type = whatIsTheLine(fr.m_data[i].first);
5281 const auto ns = skipSpaces<Trait>(0, fr.m_data[i].first.asString());
5284 const auto s = fr.m_data[i].first.asString().sliced(ns);
5286 if (isHorizontalLine<Trait>(s) || isH1<Trait>(s) || isH2<Trait>(s)) {
5295template<
class Trait>
5298 typename Delims::iterator last,
5299 TextParsingOpts<Trait> &po,
5302 static const std::set<typename Trait::String> s_finish = {Trait::latin1ToString(
"/pre"),
5303 Trait::latin1ToString(
"/script"),
5304 Trait::latin1ToString(
"/style"),
5305 Trait::latin1ToString(
"/textarea")};
5309 long long int l = -1, p = -1;
5311 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less && skipFirst) {
5312 std::tie(ok, l, p, po.m_html.m_onLine, std::ignore) =
5313 isHtmlTag(it->m_line, it->m_pos, po, 1);
5316 if (po.m_html.m_onLine) {
5317 for (it = (skipFirst && it != last ? std::next(it) : it); it != last; ++it) {
5318 if (it->m_type == Delimiter::Less) {
5319 typename Trait::String tag;
5320 bool closed =
false;
5322 std::tie(tag, closed) = readHtmlTag(it, po);
5325 if (s_finish.find(tag.toLower()) != s_finish.cend()) {
5326 eatRawHtml(po.m_line, po.m_pos, it->m_line, -1, po,
5327 true, 1, po.m_html.m_onLine);
5334 }
else if (ok && !isNewBlockIn(po.m_fr, it->m_line, l)) {
5335 eatRawHtml(po.m_line, po.m_pos, l, p + 1, po,
true, 1,
false);
5345 if (po.m_html.m_onLine) {
5346 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 1, po.m_html.m_onLine);
5352template<
class Trait>
5355 typename Delims::iterator last,
5356 TextParsingOpts<Trait> &po)
5359 const auto start = it;
5361 MdLineData::CommentData commentData = {2,
true};
5362 bool onLine = po.m_html.m_onLine;
5364 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5365 long long int i = po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos);
5367 commentData = po.m_fr.m_data[it->m_line].second.m_htmlCommentData[i];
5369 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5370 po.m_html.m_onLine = onLine;
5373 if (commentData.first != -1 && commentData.second) {
5374 for (; it != last; ++it) {
5375 if (it->m_type == Delimiter::Greater) {
5378 bool doContinue =
false;
5380 for (
char i = 0; i < commentData.first; ++i) {
5381 if (!(p > 0 && po.m_fr.m_data[it->m_line].first[p - 1] == Trait::latin1ToChar(
'-'))) {
5394 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5396 onLine ? po.m_fr.m_data[it->m_line].first.length() : it->m_pos + 1,
5397 po,
true, 2, onLine);
5408 if (po.m_html.m_onLine) {
5409 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 2, po.m_html.m_onLine);
5415template<
class Trait>
5418 typename Delims::iterator last,
5419 TextParsingOpts<Trait> &po)
5421 bool onLine = po.m_html.m_onLine;
5424 const auto start = it;
5426 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5427 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5428 po.m_html.m_onLine = onLine;
5431 for (; it != last; ++it) {
5432 if (it->m_type == Delimiter::Greater) {
5433 if (it->m_pos > 0 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar(
'?')) {
5434 long long int i = it->m_pos + 1;
5436 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5437 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5442 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5443 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 3, onLine);
5454 if (po.m_html.m_onLine) {
5455 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 3, onLine);
5461template<
class Trait>
5464 typename Delims::iterator last,
5465 TextParsingOpts<Trait> &po)
5468 const auto start = it;
5470 bool onLine = po.m_html.m_onLine;
5472 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5473 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5474 po.m_html.m_onLine = onLine;
5477 for (; it != last; ++it) {
5478 if (it->m_type == Delimiter::Greater) {
5479 long long int i = it->m_pos + 1;
5481 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5482 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5487 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5488 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 4, onLine);
5498 if (po.m_html.m_onLine) {
5499 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 4,
true);
5505template<
class Trait>
5508 typename Delims::iterator last,
5509 TextParsingOpts<Trait> &po)
5512 const auto start = it;
5514 bool onLine = po.m_html.m_onLine;
5516 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5517 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5518 po.m_html.m_onLine = onLine;
5521 for (; it != last; ++it) {
5522 if (it->m_type == Delimiter::Greater) {
5523 if (it->m_pos > 1 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar(
']') &&
5524 po.m_fr.m_data[it->m_line].first[it->m_pos - 2] == Trait::latin1ToChar(
']')) {
5525 long long int i = it->m_pos + 1;
5527 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5528 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5533 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5534 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 5, onLine);
5545 if (po.m_html.m_onLine) {
5546 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 5,
true);
5552template<
class Trait>
5555 typename Delims::iterator last,
5556 TextParsingOpts<Trait> &po)
5558 po.m_html.m_onLine = (it != last ?
5559 it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()) : true);
5561 if (po.m_html.m_onLine) {
5562 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
5563 false, 6, po.m_html.m_onLine);
5565 const auto nit = std::find_if(std::next(it), last, [](const auto &d) {
5566 return (d.m_type == Delimiter::Greater);
5569 if (nit != last && !isNewBlockIn(po.m_fr, it->m_line, nit->m_line)) {
5570 eatRawHtml(po.m_line, po.m_pos, nit->m_line, nit->m_pos + nit->m_len, po,
5575 if (po.m_fr.m_emptyLineAfter && po.m_html.m_html) {
5576 po.m_html.m_continueHtml =
false;
5580template<
class Trait>
5583 typename Delims::iterator last,
5584 TextParsingOpts<Trait> &po,
5587 po.m_detected = TextParsingOpts<Trait>::Detected::HTML;
5589 switch (po.m_html.m_htmlBlockType) {
5591 finishRule1HtmlTag(it, last, po, skipFirst);
5595 finishRule2HtmlTag(it, last, po);
5599 finishRule3HtmlTag(it, last, po);
5603 finishRule4HtmlTag(it, last, po);
5607 finishRule5HtmlTag(it, last, po);
5611 finishRule6HtmlTag(it, last, po);
5615 return finishRule7HtmlTag(it, last, po);
5618 po.m_detected = TextParsingOpts<Trait>::Detected::Nothing;
5622 return findIt(it, last, po);
5625template<
class Trait>
5628 typename Delims::iterator last,
5629 TextParsingOpts<Trait> &po)
5633 typename Trait::String tag;
5635 std::tie(tag, std::ignore) = readHtmlTag(it, po);
5637 if (tag.startsWith(Trait::latin1ToString(
"![CDATA["))) {
5641 tag = tag.toLower();
5643 static const typename Trait::String s_validHtmlTagLetters =
5644 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789-");
5646 bool closing =
false;
5648 if (tag.startsWith(Trait::latin1ToString(
"/"))) {
5653 if (tag.endsWith(Trait::latin1ToString(
"/"))) {
5654 tag.remove(tag.size() - 1, 1);
5657 if (tag.isEmpty()) {
5661 if (!tag.startsWith(Trait::latin1ToString(
"!")) &&
5662 !tag.startsWith(Trait::latin1ToString(
"?")) &&
5663 !(tag[0].unicode() >= 97 && tag[0].unicode() <= 122)) {
5667 static const std::set<typename Trait::String> s_rule1 = {Trait::latin1ToString(
"pre"),
5668 Trait::latin1ToString(
"script"),
5669 Trait::latin1ToString(
"style"),
5670 Trait::latin1ToString(
"textarea")};
5672 if (!closing && s_rule1.find(tag) != s_rule1.cend()) {
5674 }
else if (tag.startsWith(Trait::latin1ToString(
"!--"))) {
5676 }
else if (tag.startsWith(Trait::latin1ToString(
"?"))) {
5678 }
else if (tag.startsWith(Trait::latin1ToString(
"!")) && tag.size() > 1 &&
5679 ((tag[1].unicode() >= 65 && tag[1].unicode() <= 90) ||
5680 (tag[1].unicode() >= 97 && tag[1].unicode() <= 122))) {
5683 static const std::set<typename Trait::String> s_rule6 = {
5684 Trait::latin1ToString(
"address"), Trait::latin1ToString(
"article"), Trait::latin1ToString(
"aside"), Trait::latin1ToString(
"base"),
5685 Trait::latin1ToString(
"basefont"), Trait::latin1ToString(
"blockquote"), Trait::latin1ToString(
"body"), Trait::latin1ToString(
"caption"),
5686 Trait::latin1ToString(
"center"), Trait::latin1ToString(
"col"), Trait::latin1ToString(
"colgroup"), Trait::latin1ToString(
"dd"),
5687 Trait::latin1ToString(
"details"), Trait::latin1ToString(
"dialog"), Trait::latin1ToString(
"dir"), Trait::latin1ToString(
"div"),
5688 Trait::latin1ToString(
"dl"), Trait::latin1ToString(
"dt"), Trait::latin1ToString(
"fieldset"), Trait::latin1ToString(
"figcaption"),
5689 Trait::latin1ToString(
"figure"), Trait::latin1ToString(
"footer"), Trait::latin1ToString(
"form"), Trait::latin1ToString(
"frame"),
5690 Trait::latin1ToString(
"frameset"), Trait::latin1ToString(
"h1"), Trait::latin1ToString(
"h2"), Trait::latin1ToString(
"h3"),
5691 Trait::latin1ToString(
"h4"), Trait::latin1ToString(
"h5"), Trait::latin1ToString(
"h6"), Trait::latin1ToString(
"head"),
5692 Trait::latin1ToString(
"header"), Trait::latin1ToString(
"hr"), Trait::latin1ToString(
"html"), Trait::latin1ToString(
"iframe"),
5693 Trait::latin1ToString(
"legend"), Trait::latin1ToString(
"li"), Trait::latin1ToString(
"link"), Trait::latin1ToString(
"main"),
5694 Trait::latin1ToString(
"menu"), Trait::latin1ToString(
"menuitem"), Trait::latin1ToString(
"nav"), Trait::latin1ToString(
"noframes"),
5695 Trait::latin1ToString(
"ol"), Trait::latin1ToString(
"optgroup"), Trait::latin1ToString(
"option"), Trait::latin1ToString(
"p"),
5696 Trait::latin1ToString(
"param"), Trait::latin1ToString(
"section"), Trait::latin1ToString(
"search"), Trait::latin1ToString(
"summary"),
5697 Trait::latin1ToString(
"table"), Trait::latin1ToString(
"tbody"), Trait::latin1ToString(
"td"), Trait::latin1ToString(
"tfoot"),
5698 Trait::latin1ToString(
"th"), Trait::latin1ToString(
"thead"), Trait::latin1ToString(
"title"), Trait::latin1ToString(
"tr"),
5699 Trait::latin1ToString(
"track"), Trait::latin1ToString(
"ul")};
5701 for (
long long int i = 1; i < tag.size(); ++i) {
5702 if (!s_validHtmlTagLetters.contains(tag[i])) {
5707 if (s_rule6.find(tag) != s_rule6.cend()) {
5712 std::tie(tag, std::ignore, std::ignore, std::ignore, std::ignore) =
5713 isHtmlTag(it->m_line, it->m_pos, po, 7);
5724template<
class Trait>
5727 typename Delims::iterator last,
5728 TextParsingOpts<Trait> &po)
5730 const auto rule = htmlTagRule(it, last, po);
5735 po.m_firstInParagraph =
false;
5740 po.m_html.m_htmlBlockType = rule;
5741 po.m_html.m_html.reset(
new RawHtml<Trait>);
5742 po.m_html.m_html->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
5743 po.m_html.m_html->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
5745 return finishRawHtmlTag(it, last, po,
true);
5748template<
class Trait>
5751 typename Delims::iterator last,
5752 TextParsingOpts<Trait> &po)
5755 const auto start = it;
5756 long long int l = -1, p = -1;
5757 bool onLine =
false;
5760 std::tie(ok, l, p, onLine, std::ignore) =
isHtmlTag(it->m_line, it->m_pos, po, 7);
5762 onLine = onLine && it->m_line == 0 && l ==
start->m_line;
5765 eatRawHtml(po.m_line, po.m_pos, l, ++p, po, !onLine, 7, onLine);
5767 po.m_html.m_onLine = onLine;
5769 it = findIt(it, last, po);
5772 for (; it != last; ++it) {
5773 if (it->m_type == Delimiter::Less) {
5774 const auto rule = htmlTagRule(it, last, po);
5776 if (rule != -1 && rule != 7) {
5777 eatRawHtml(po.m_line, po.m_pos, it->m_line, it->m_pos, po,
true, 7, onLine,
true);
5779 return std::prev(it);
5784 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 7, onLine,
true);
5786 return std::prev(last);
5794 if (po.m_html.m_onLine) {
5795 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
true, 7,
true);
5806template<
class Trait>
5809 typename Delims::iterator last,
5810 TextParsingOpts<Trait> &po)
5812 po.m_wasRefLink =
false;
5813 po.m_firstInParagraph =
false;
5815 const auto end = std::find_if(std::next(it), last, [&](
const auto &d) {
5816 return (d.m_type == Delimiter::Math && d.m_len == it->m_len);
5819 if (end != last &&
end->m_line <= po.m_lastTextLine) {
5820 typename Trait::String math;
5822 if (it->m_line ==
end->m_line) {
5823 math = po.m_fr.m_data[it->m_line].first.asString().sliced(
5824 it->m_pos + it->m_len,
end->m_pos - (it->m_pos + it->m_len));
5826 math = po.m_fr.m_data[it->m_line].first.asString().sliced(it->m_pos + it->m_len);
5828 for (
long long int i = it->m_line + 1; i < end->m_line; ++i) {
5829 math.push_back(Trait::latin1ToChar(
'\n'));
5830 math.push_back(po.m_fr.m_data[i].first.asString());
5833 math.push_back(Trait::latin1ToChar(
'\n'));
5834 math.push_back(po.m_fr.m_data[
end->m_line].first.asString().sliced(0,
end->m_pos));
5837 if (!po.m_collectRefLinks) {
5838 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
5840 auto startLine = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
5841 auto startColumn = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len);
5843 if (it->m_pos + it->m_len >= po.m_fr.m_data.at(it->m_line).first.length()) {
5844 std::tie(startColumn, startLine) =
nextPosition(po.m_fr, startColumn, startLine);
5847 auto endColumn = po.m_fr.m_data.at(
end->m_line).first.virginPos(
end->m_pos);
5848 auto endLine = po.m_fr.m_data.at(
end->m_line).second.m_lineNumber;
5850 if (endColumn == 0) {
5851 std::tie(endColumn, endLine) =
prevPosition(po.m_fr, endColumn, endLine);
5856 m->setStartColumn(startColumn);
5857 m->setStartLine(startLine);
5858 m->setEndColumn(endColumn);
5859 m->setEndLine(endLine);
5860 m->setInline(it->m_len == 1);
5861 m->setStartDelim({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
5862 po.m_fr.m_data[it->m_line].second.m_lineNumber,
5863 po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
5864 po.m_fr.m_data[it->m_line].second.m_lineNumber});
5865 m->setEndDelim({po.m_fr.m_data[
end->m_line].first.virginPos(
end->m_pos),
5866 po.m_fr.m_data[
end->m_line].second.m_lineNumber,
5867 po.m_fr.m_data[
end->m_line].first.virginPos(
end->m_pos +
end->m_len - 1),
5868 po.m_fr.m_data[
end->m_line].second.m_lineNumber});
5869 m->setFensedCode(
false);
5871 initLastItemWithOpts<Trait>(po, m);
5873 if (math.startsWith(Trait::latin1ToString(
"`")) &&
5874 math.endsWith(Trait::latin1ToString(
"`")) &&
5875 !math.endsWith(Trait::latin1ToString(
"\\`")) &&
5876 math.length() > 1) {
5877 math = math.sliced(1, math.length() - 2);
5882 po.m_parent->appendItem(m);
5884 po.m_pos =
end->m_pos +
end->m_len;
5885 po.m_line =
end->m_line;
5886 po.m_lastText =
nullptr;
5895template<
class Trait>
5898 typename Delims::iterator last,
5899 TextParsingOpts<Trait> &po,
5902 const auto nit = std::find_if(std::next(it), last, [](
const auto &d) {
5903 return (d.m_type == Delimiter::Greater);
5907 if (nit->m_line == it->m_line) {
5908 const auto url = po.m_fr.m_data.at(it->m_line).first.asString().sliced(
5909 it->m_pos + 1, nit->m_pos - it->m_pos - 1);
5913 for (
long long int i = 0; i < url.size(); ++i) {
5914 if (url[i].isSpace()) {
5922 if (!isValidUrl<Trait>(url) && !isEmail<Trait>(url)) {
5928 if (!po.m_collectRefLinks) {
5929 std::shared_ptr<Link<Trait>> lnk(
new Link<Trait>);
5930 lnk->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
5931 lnk->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
5932 lnk->setEndColumn(po.m_fr.m_data.at(nit->m_line).first.virginPos(nit->m_pos + nit->m_len - 1));
5933 lnk->setEndLine(po.m_fr.m_data.at(nit->m_line).second.m_lineNumber);
5935 lnk->setOpts(po.m_opts);
5936 lnk->setTextPos({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + 1),
5937 po.m_fr.m_data[it->m_line].second.m_lineNumber,
5938 po.m_fr.m_data[nit->m_line].first.virginPos(nit->m_pos - 1),
5939 po.m_fr.m_data[nit->m_line].second.m_lineNumber});
5940 lnk->setUrlPos(lnk->textPos());
5941 po.m_parent->appendItem(lnk);
5944 po.m_wasRefLink =
false;
5945 po.m_firstInParagraph =
false;
5946 po.m_lastText =
nullptr;
5949 po.m_pos = nit->m_pos + nit->m_len;
5950 po.m_line = nit->m_line;
5955 return checkForRawHtml(it, last, po);
5958 return checkForRawHtml(it, last, po);
5961 return checkForRawHtml(it, last, po);
5965template<
class Trait>
5968 long long int startPos,
5969 long long int lastLine,
5970 long long int lastPos,
5971 TextParsingOpts<Trait> &po,
5972 typename Delims::iterator startDelimIt,
5973 typename Delims::iterator endDelimIt)
5975 typename Trait::String c;
5977 for (; po.m_line <= lastLine; ++po.m_line) {
5978 c.push_back(po.m_fr.m_data.at(po.m_line).first.asString().sliced(
5979 po.m_pos, (po.m_line == lastLine ? lastPos - po.m_pos :
5980 po.m_fr.m_data.at(po.m_line).first.length() - po.m_pos)));
5982 if (po.m_line < lastLine) {
5983 c.push_back(Trait::latin1ToChar(
' '));
5989 po.m_line = lastLine;
5991 if (c[0] == Trait::latin1ToChar(
' ') && c[c.size() - 1] == Trait::latin1ToChar(
' ') &&
5992 skipSpaces<Trait>(0, c) < c.size()) {
5994 c.remove(c.size() - 1, 1);
6000 auto code = std::make_shared<Code<Trait>>(c,
false,
true);
6002 code->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6003 code->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6004 code->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6005 code->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6006 code->setStartDelim({po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6007 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)),
6008 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber,
6009 po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6010 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)) +
6011 startDelimIt->m_len - 1 - (startDelimIt->m_backslashed ? 1 : 0),
6012 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber});
6014 {po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6015 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0)),
6016 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber,
6017 po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6018 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0) +
6019 endDelimIt->m_len - 1 - (endDelimIt->m_backslashed ? 1 : 0)),
6020 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber});
6021 code->setOpts(po.m_opts);
6023 initLastItemWithOpts<Trait>(po, code);
6025 po.m_parent->appendItem(code);
6028 po.m_wasRefLink =
false;
6029 po.m_firstInParagraph =
false;
6030 po.m_lastText =
nullptr;
6033template<
class Trait>
6036 typename Delims::iterator last,
6037 TextParsingOpts<Trait> &po)
6039 const auto len = it->m_len;
6040 const auto start = it;
6042 po.m_wasRefLink =
false;
6043 po.m_firstInParagraph =
false;
6047 for (; it != last; ++it) {
6048 if (it->m_line <= po.m_lastTextLine) {
6049 const auto p = skipSpaces<Trait>(0, po.m_fr.m_data.at(it->m_line).first.asString());
6050 const auto withoutSpaces = po.m_fr.m_data.at(it->m_line).first.asString().sliced(p);
6052 if ((it->m_type == Delimiter::HorizontalLine && withoutSpaces[0] == Trait::latin1ToChar(
'-')) ||
6053 it->m_type == Delimiter::H1 || it->m_type == Delimiter::H2) {
6055 }
else if (it->m_type == Delimiter::InlineCode && (it->m_len - (it->m_backslashed ? 1 : 0)) == len) {
6056 if (!po.m_collectRefLinks) {
6061 makeInlineCode(
start->m_line,
start->m_pos +
start->m_len, it->m_line,
6062 it->m_pos + (it->m_backslashed ? 1 : 0), po,
start, it);
6064 po.m_line = it->m_line;
6065 po.m_pos = it->m_pos + it->m_len;
6075 if (!po.m_collectRefLinks) {
6082template<
class Trait>
6085 typename Delims::iterator it,
6086 typename Delims::iterator last,
6087 TextParsingOpts<Trait> &po,
6088 bool doNotCreateTextOnFail,
6091 if (it != last && it->m_line <= po.m_lastTextLine) {
6092 if (
start->m_line == it->m_line) {
6094 const auto n = it->m_pos - p;
6097 long long int startPos, startLine, endPos, endLine;
6099 po.m_fr.m_data[
start->m_line].first.virginPos(
6101 po.m_fr.m_data[
start->m_line].second.m_lineNumber);
6102 std::tie(endPos, endLine) =
6103 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6104 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6106 *pos = {startPos, startLine, endPos, endLine};
6109 return {{{po.m_fr.m_data.at(
start->m_line).first.sliced(p, n),
6110 {po.m_fr.m_data.at(
start->m_line).second.m_lineNumber}}}, it};
6112 if (it->m_line -
start->m_line < 3) {
6113 typename MdBlock<Trait>::Data res;
6114 res.push_back({po.m_fr.m_data.at(
start->m_line).first.sliced(
6115 start->m_pos +
start->m_len), po.m_fr.m_data.at(
start->m_line).second});
6117 long long int i =
start->m_line + 1;
6119 for (; i <= it->m_line; ++i) {
6120 if (i == it->m_line) {
6121 res.push_back({po.m_fr.m_data.at(i).first.sliced(0, it->m_pos),
6122 po.m_fr.m_data.at(i).second});
6124 res.push_back({po.m_fr.m_data.at(i).first, po.m_fr.m_data.at(i).second});
6129 long long int startPos, startLine, endPos, endLine;
6131 po.m_fr.m_data[
start->m_line].first.virginPos(
6133 po.m_fr.m_data[
start->m_line].second.m_lineNumber);
6134 std::tie(endPos, endLine) =
6135 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6136 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6138 *pos = {startPos, startLine, endPos, endLine};
6143 if (!po.m_collectRefLinks && !doNotCreateTextOnFail) {
6151 if (!po.m_collectRefLinks && !doNotCreateTextOnFail) {
6159template<
class Trait>
6162 typename Delims::iterator last,
6163 TextParsingOpts<Trait> &po,
6166 const auto start = it;
6168 long long int brackets = 0;
6170 const bool collectRefLinks = po.m_collectRefLinks;
6171 po.m_collectRefLinks =
true;
6172 long long int l = po.m_line, p = po.m_pos;
6174 for (it = std::next(it); it != last; ++it) {
6177 switch (it->m_type) {
6178 case Delimiter::SquareBracketsClose: {
6185 case Delimiter::SquareBracketsOpen:
6186 case Delimiter::ImageOpen:
6190 case Delimiter::InlineCode:
6191 it = checkForInlineCode(it, last, po);
6194 case Delimiter::Less:
6195 it = checkForAutolinkHtml(it, last, po,
false);
6207 const auto r = readTextBetweenSquareBrackets(
start, it, last, po,
false, pos);
6209 po.m_collectRefLinks = collectRefLinks;
6217template<
class Trait>
6220 typename Delims::iterator last,
6221 TextParsingOpts<Trait> &po,
6224 const auto start = it;
6226 for (it = std::next(it); it != last; ++it) {
6229 switch (it->m_type) {
6230 case Delimiter::SquareBracketsClose: {
6234 case Delimiter::SquareBracketsOpen:
6235 case Delimiter::ImageOpen: {
6248 return readTextBetweenSquareBrackets(
start, it, last, po,
true, pos);
6251template<
class Trait>
6252inline typename Trait::String
6255 typename Trait::String res;
6258 for (
const auto &s : d) {
6260 res.push_back(Trait::latin1ToChar(
' '));
6262 res.push_back(s.first.asString().simplified());
6269template<
class Trait>
6270inline std::shared_ptr<Link<Trait>>
6272 const typename MdBlock<Trait>::Data &text,
6273 TextParsingOpts<Trait> &po,
6274 bool doNotCreateTextOnFail,
6275 long long int startLine,
6276 long long int startPos,
6277 long long int lastLine,
6278 long long int lastPos,
6279 const WithPosition &textPos,
6280 const WithPosition &urlPos)
6284 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ?
6285 url : removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6288 if (!u.startsWith(Trait::latin1ToString(
"#"))) {
6289 const auto checkForFile = [&](
typename Trait::String &url,
6290 const typename Trait::String &ref = {}) ->
bool {
6291 if (Trait::fileExists(url)) {
6292 url = Trait::absoluteFilePath(url);
6294 if (!po.m_collectRefLinks) {
6295 po.m_linksToParse.push_back(url);
6298 if (!ref.isEmpty()) {
6299 url = ref + Trait::latin1ToString(
"/") + url;
6303 }
else if (Trait::fileExists(url, po.m_workingPath)) {
6304 url = Trait::absoluteFilePath(po.m_workingPath + Trait::latin1ToString(
"/") + url);
6306 if (!po.m_collectRefLinks) {
6307 po.m_linksToParse.push_back(url);
6310 if (!ref.isEmpty()) {
6311 url = ref + Trait::latin1ToString(
"/") + url;
6320 if (!checkForFile(u) && u.contains(Trait::latin1ToChar(
'#'))) {
6321 const auto i = u.indexOf(Trait::latin1ToChar(
'#'));
6322 const auto ref = u.sliced(i);
6325 if (!checkForFile(u, ref)) {
6330 u = u + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
6331 Trait::latin1ToString(
"/") + po.m_workingPath) + Trait::latin1ToString(
"/") +
6337 link->setOpts(po.m_opts);
6338 link->setTextPos(textPos);
6339 link->setUrlPos(urlPos);
6341 MdBlock<Trait> block = {text, 0};
6343 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6345 RawHtmlBlock<Trait> html;
6347 parseFormattedTextLinksImages(block,
6348 std::static_pointer_cast<Block<Trait>>(p),
6353 po.m_collectRefLinks,
6358 if (!p->isEmpty()) {
6359 std::shared_ptr<Image<Trait>> img;
6361 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6362 const auto ip = std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0));
6364 for (
auto it = ip->items().cbegin(), last = ip->items().cend(); it != last; ++it) {
6365 switch ((*it)->type()) {
6366 case ItemType::Link:
6369 case ItemType::Image: {
6370 img = std::static_pointer_cast<Image<Trait>>(*it);
6386 if (html.m_html.get()) {
6387 link->p()->appendItem(html.m_html);
6390 link->setText(toSingleLine(text));
6391 link->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6392 link->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6393 link->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6394 link->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6396 initLastItemWithOpts<Trait>(po, link);
6398 po.m_lastText =
nullptr;
6403template<
class Trait>
6406 TextParsingOpts<Trait> &po,
6407 long long int startLine,
6408 long long int startPos,
6409 long long int lastLineForText,
6410 long long int lastPosForText,
6411 typename Delims::iterator lastIt,
6412 const typename MdBlock<Trait>::Data &linkText,
6413 bool doNotCreateTextOnFail,
6414 const WithPosition &textPos,
6415 const WithPosition &linkTextPos)
6417 const auto u = Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper();
6418 const auto url = u + Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
6419 typename Trait::String() : po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
6421 po.m_wasRefLink =
false;
6422 po.m_firstInParagraph =
false;
6424 if (po.m_doc->labeledLinks().find(url) != po.m_doc->labeledLinks().cend()) {
6425 if (!po.m_collectRefLinks) {
6426 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6428 const auto link = makeLink(u,
6429 removeBackslashes<Trait>(isLinkTextEmpty ? text : linkText),
6431 doNotCreateTextOnFail,
6435 lastIt->m_pos + lastIt->m_len,
6436 (isLinkTextEmpty ? textPos : linkTextPos),
6440 po.m_linksToParse.push_back(url);
6441 po.m_parent->appendItem(link);
6443 po.m_line = lastIt->m_line;
6444 po.m_pos = lastIt->m_pos + lastIt->m_len;
6446 if (!po.m_collectRefLinks && !doNotCreateTextOnFail) {
6447 makeText(lastLineForText, lastPosForText, po);
6455 }
else if (!po.m_collectRefLinks && !doNotCreateTextOnFail) {
6456 makeText(lastLineForText, lastPosForText, po);
6462template<
class Trait>
6463inline std::shared_ptr<Image<Trait>>
6465 const typename MdBlock<Trait>::Data &text,
6466 TextParsingOpts<Trait> &po,
6467 bool doNotCreateTextOnFail,
6468 long long int startLine,
6469 long long int startPos,
6470 long long int lastLine,
6471 long long int lastPos,
6472 const WithPosition &textPos,
6473 const WithPosition &urlPos)
6477 std::shared_ptr<Image<Trait>> img(
new Image<Trait>);
6479 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ? url :
6480 removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6482 if (Trait::fileExists(u)) {
6484 }
else if (Trait::fileExists(u, po.m_workingPath)) {
6485 img->setUrl(po.m_workingPath + Trait::latin1ToString(
"/") + u);
6490 MdBlock<Trait> block = {text, 0};
6492 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6494 RawHtmlBlock<Trait> html;
6496 parseFormattedTextLinksImages(block,
6497 std::static_pointer_cast<Block<Trait>>(p),
6502 po.m_collectRefLinks,
6507 if (!p->isEmpty()) {
6508 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6509 img->setP(std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0)));
6513 img->setText(toSingleLine(removeBackslashes<Trait>(text)));
6514 img->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6515 img->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6516 img->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6517 img->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6518 img->setTextPos(textPos);
6519 img->setUrlPos(urlPos);
6521 initLastItemWithOpts<Trait>(po, img);
6523 po.m_lastText =
nullptr;
6528template<
class Trait>
6531 TextParsingOpts<Trait> &po,
6532 long long int startLine,
6533 long long int startPos,
6534 long long int lastLineForText,
6535 long long int lastPosForText,
6536 typename Delims::iterator lastIt,
6537 const typename MdBlock<Trait>::Data &linkText,
6538 bool doNotCreateTextOnFail,
6539 const WithPosition &textPos,
6540 const WithPosition &linkTextPos)
6542 const auto url = Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper() +
6543 Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
6544 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
6546 po.m_wasRefLink =
false;
6547 po.m_firstInParagraph =
false;
6549 const auto iit = po.m_doc->labeledLinks().find(url);
6551 if (iit != po.m_doc->labeledLinks().cend()) {
6552 if (!po.m_collectRefLinks) {
6553 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6555 const auto img = makeImage(iit->second->url(),
6556 (isLinkTextEmpty ? text : linkText),
6558 doNotCreateTextOnFail,
6562 lastIt->m_pos + lastIt->m_len,
6563 (isLinkTextEmpty ? textPos : linkTextPos),
6566 po.m_parent->appendItem(img);
6568 po.m_line = lastIt->m_line;
6569 po.m_pos = lastIt->m_pos + lastIt->m_len;
6573 }
else if (!po.m_collectRefLinks && !doNotCreateTextOnFail) {
6574 makeText(lastLineForText, lastPosForText, po);
6581template<
class Trait>
6589 if (pos == fr.at(line).first.length() && line + 1 < (
long long int)fr.size()) {
6596template<
class Trait>
6597inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6605 const auto destLine = line;
6606 const auto &s = po.
m_fr.m_data.at(line).first.asString();
6607 bool backslash =
false;
6610 if (s[pos] == Trait::latin1ToChar(
'<')) {
6614 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6615 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6618 const auto start = pos;
6620 while (pos < s.size()) {
6623 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6626 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'<')) {
6627 return {line, pos,
false, {}, destLine};
6628 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'>')) {
6639 if (pos < s.size() && s[pos] == Trait::latin1ToChar(
'>')) {
6641 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6642 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6647 return {line, pos,
true, s.sliced(
start, pos -
start - 1), destLine};
6649 return {line, pos,
false, {}, destLine};
6652 long long int pc = 0;
6654 const auto start = pos;
6657 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6658 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6661 while (pos < s.size()) {
6664 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6667 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
' ')) {
6670 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6671 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6674 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6676 return {line, pos,
false, {}, destLine};
6678 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'(')) {
6680 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
')')) {
6683 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6684 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6687 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6701 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6702 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6705 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6708 return {line, pos,
false, {}, destLine};
6713template<
class Trait>
6714inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6719 const auto space = (pos < po.
m_fr.m_data.at(line).first.length() ?
6720 po.
m_fr.m_data.at(line).first[pos].isSpace() :
true);
6722 const auto firstLine = line;
6726 if (pos >= po.
m_fr.m_data.at(line).first.length()) {
6727 return {line, pos,
true, {}, firstLine};
6730 const auto sc = po.
m_fr.m_data.at(line).first[pos];
6732 if (sc != Trait::latin1ToChar(
'"') && sc != Trait::latin1ToChar(
'\'') &&
6733 sc != Trait::latin1ToChar(
'(') && sc != Trait::latin1ToChar(
')')) {
6734 return {line, pos, (firstLine != line && line <= po.
m_lastTextLine), {}, firstLine};
6735 }
else if (!space && sc != Trait::latin1ToChar(
')')) {
6736 return {line, pos,
false, {}, firstLine};
6739 if (sc == Trait::latin1ToChar(
')')) {
6743 const auto startLine = line;
6745 bool backslash =
false;
6751 typename Trait::String title;
6753 while (line < (
long long int)po.
m_fr.m_data.size() && pos < po.
m_fr.m_data.at(line).first.length()) {
6756 if (po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6759 }
else if (sc == Trait::latin1ToChar(
'(') &&
6760 po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
')') && !backslash) {
6763 }
else if (sc == Trait::latin1ToChar(
'(') &&
6764 po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
'(') && !backslash) {
6765 return {line, pos,
false, {}, startLine};
6766 }
else if (sc != Trait::latin1ToChar(
'(') && po.
m_fr.m_data.at(line).first[pos] == sc && !backslash) {
6770 title.push_back(po.
m_fr.m_data.at(line).first[pos]);
6779 if (pos == po.
m_fr.m_data.at(line).first.length()) {
6784 return {line, pos,
false, {}, startLine};
6787template<
class Trait>
6788inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
6790 typename Delims::iterator last,
6791 TextParsingOpts<Trait> &po,
6792 WithPosition *urlPos)
6794 long long int p = it->m_pos + it->m_len;
6795 long long int l = it->m_line;
6797 typename Trait::String dest, title;
6798 long long int destStartLine = 0;
6800 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
6803 return {{}, {}, it,
false};
6806 long long int s = 0;
6808 std::tie(l, p, ok, title, s) = readLinkTitle<Trait>(l, p, po);
6810 skipSpacesUpTo1Line<Trait>(l, p, po.m_fr.m_data);
6812 if (!ok || (l >= (
long long int)po.m_fr.m_data.size() || p >= po.m_fr.m_data.at(l).first.length() ||
6813 po.m_fr.m_data.at(l).first[p] != Trait::latin1ToChar(
')'))) {
6814 return {{}, {}, it,
false};
6817 for (; it != last; ++it) {
6818 if (it->m_line == l && it->m_pos == p) {
6819 return {dest, title, it,
true};
6823 return {{}, {}, it,
false};
6826template<
class Trait>
6827inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
6829 typename Delims::iterator last,
6830 TextParsingOpts<Trait> &po,
6831 WithPosition *urlPos)
6833 long long int p = it->m_pos + it->m_len + 1;
6834 long long int l = it->m_line;
6836 typename Trait::String dest, title;
6837 long long int destStartLine = 0;
6839 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
6842 return {{}, {}, it,
false};
6845 long long int titleStartLine = 0;
6847 std::tie(l, p, ok, title, titleStartLine) = readLinkTitle<Trait>(l, p, po);
6850 return {{}, {}, it,
false};
6853 if (!title.isEmpty()) {
6854 p = skipSpaces<Trait>(p, po.m_fr.m_data.at(l).first.asString());
6856 if (titleStartLine == destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
6857 return {{}, {}, it,
false};
6858 }
else if (titleStartLine != destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
6860 p = po.m_fr.m_data.at(l).first.length();
6865 for (; it != last; ++it) {
6866 if (it->m_line > l || (it->m_line == l && it->m_pos >= p)) {
6874 return {dest, title, std::prev(it),
true};
6877template<
class Trait>
6880 typename Delims::iterator last,
6881 TextParsingOpts<Trait> &po)
6883 const auto start = it;
6885 typename MdBlock<Trait>::Data text;
6887 po.m_wasRefLink =
false;
6888 po.m_firstInParagraph =
false;
6890 WithPosition textPos;
6891 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
6894 if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
6896 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'(')) {
6897 typename Trait::String url, title;
6898 typename Delims::iterator iit;
6901 WithPosition urlPos;
6902 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
6905 if (!po.m_collectRefLinks) {
6906 po.m_parent->appendItem(
6907 makeImage(url, text, po,
false,
start->m_line,
start->m_pos,
6908 iit->m_line, iit->m_pos + iit->m_len, textPos, urlPos));
6911 po.m_line = iit->m_line;
6912 po.m_pos = iit->m_pos + iit->m_len;
6915 }
else if (createShortcutImage(text, po,
start->m_line,
start->m_pos,
start->m_line,
6916 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
6921 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'[')) {
6922 typename MdBlock<Trait>::Data
label;
6923 typename Delims::iterator lit;
6925 WithPosition labelPos;
6926 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
6928 if (lit != std::next(it)) {
6929 const auto isLabelEmpty = toSingleLine(label).isEmpty();
6932 && createShortcutImage(label,
6944 }
else if (isLabelEmpty
6945 && createShortcutImage(text,
6958 }
else if (createShortcutImage(text, po,
start->m_line,
start->m_pos,
start->m_line,
6959 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
6973template<
class Trait>
6976 typename Delims::iterator last,
6977 TextParsingOpts<Trait> &po)
6979 const auto start = it;
6981 typename MdBlock<Trait>::Data text;
6983 const auto wasRefLink = po.m_wasRefLink;
6984 const auto firstInParagraph = po.m_firstInParagraph;
6985 po.m_wasRefLink =
false;
6986 po.m_firstInParagraph =
false;
6988 const auto ns = skipSpaces<Trait>(0, po.m_fr.m_data.at(po.m_line).first.asString());
6990 WithPosition textPos;
6991 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
6995 if (text.front().first.asString().startsWith(Trait::latin1ToString(
"^")) &&
6996 text.front().first.asString().length() > 1 && text.size() == 1 &&
6997 start->m_line == it->m_line) {
6998 if (!po.m_collectRefLinks) {
6999 std::shared_ptr<FootnoteRef<Trait>> fnr(
new FootnoteRef<Trait>(
7000 Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper() +
7001 Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7002 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName));
7003 fnr->setStartColumn(po.m_fr.m_data.at(
start->m_line).first.virginPos(
start->m_pos));
7004 fnr->setStartLine(po.m_fr.m_data.at(
start->m_line).second.m_lineNumber);
7005 fnr->setEndColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
7006 fnr->setEndLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
7007 fnr->setIdPos(textPos);
7009 typename Trait::String fnrText = Trait::latin1ToString(
"[");
7010 bool firstFnrText =
true;
7012 for (
const auto &t : text) {
7013 if (!firstFnrText) {
7014 fnrText.push_back(Trait::latin1ToString(
"\n"));
7017 firstFnrText =
false;
7019 fnrText.push_back(t.first.asString());
7022 fnrText.push_back(Trait::latin1ToString(
"]"));
7023 fnr->setText(fnrText);
7024 po.m_parent->appendItem(fnr);
7026 initLastItemWithOpts<Trait>(po, fnr);
7029 po.m_line = it->m_line;
7030 po.m_pos = it->m_pos + it->m_len;
7033 }
else if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7035 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
':')) {
7037 if ((po.m_line == 0 || wasRefLink || firstInParagraph) && ns < 4 && start->m_pos == ns) {
7038 typename Trait::String url, title;
7039 typename Delims::iterator iit;
7042 WithPosition labelPos;
7044 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
7046 if (it !=
start && !toSingleLine(text).simplified().isEmpty()) {
7047 WithPosition urlPos;
7048 std::tie(url, title, iit, ok) = checkForRefLink(it, last, po, &urlPos);
7051 const auto label = Trait::latin1ToString(
"#") +
7053 Trait::latin1ToString(
"/") +
7054 (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7055 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
7058 link->setStartColumn(po.m_fr.m_data.at(
start->m_line).first.virginPos(
7060 link->setStartLine(po.m_fr.m_data.at(
start->m_line).second.m_lineNumber);
7063 po.m_fr.m_data.at(po.m_line).first.virginPos(po.m_pos),
7064 po.m_fr.m_data.at(po.m_line).second.m_lineNumber);
7066 link->setEndColumn(endPos.first);
7067 link->setEndLine(endPos.second);
7069 link->setTextPos(labelPos);
7070 link->setUrlPos(urlPos);
7072 url = removeBackslashes<typename Trait::String, Trait>(
7073 replaceEntity<Trait>(url));
7075 if (!url.isEmpty()) {
7076 if (Trait::fileExists(url)) {
7077 url = Trait::absoluteFilePath(url);
7078 }
else if (Trait::fileExists(url, po.m_workingPath)) {
7079 url = Trait::absoluteFilePath(
7080 (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7081 po.m_workingPath + Trait::latin1ToString(
"/")) + url);
7087 po.m_wasRefLink =
true;
7089 if (po.m_doc->labeledLinks().find(label) == po.m_doc->labeledLinks().cend()) {
7090 po.m_doc->insertLabeledLink(label, link);
7105 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'(')) {
7106 typename Trait::String url, title;
7107 typename Delims::iterator iit;
7110 WithPosition urlPos;
7111 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7114 const auto link = makeLink(url,
7115 removeBackslashes<Trait>(text),
7121 iit->m_pos + iit->m_len,
7126 if (!po.m_collectRefLinks) {
7127 po.m_parent->appendItem(link);
7130 po.m_line = iit->m_line;
7131 po.m_pos = iit->m_pos + iit->m_len;
7137 }
else if (createShortcutLink(text, po,
start->m_line,
start->m_pos,
start->m_line,
7138 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7143 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'[')) {
7144 typename MdBlock<Trait>::Data
label;
7145 typename Delims::iterator lit;
7147 WithPosition labelPos;
7148 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7150 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7152 if (lit != std::next(it)) {
7154 && createShortcutLink(label,
7166 }
else if (isLabelEmpty
7167 && createShortcutLink(text,
7180 }
else if (createShortcutLink(text, po,
start->m_line,
start->m_pos,
start->m_line,
7181 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7196template<
class Trait>
7201 const auto it = std::find_if(styles.crbegin(), styles.crend(), [&](
const auto &p) {
7202 return (p.m_style == s);
7205 if (it != styles.crend()) {
7206 styles.erase(it.base() - 1);
7211template<
class Trait>
7218 for (
const auto &s : styles) {
7219 switch (s.m_style) {
7240template<
class Trait>
7245 case Delimiter::Strikethrough:
7248 case Delimiter::Emphasis1:
7251 case Delimiter::Emphasis2:
7259template<
class Trait>
7262 typename Delimiter::DelimiterType t,
7263 long long int style)
7265 if (t != Delimiter::Strikethrough) {
7266 if (style % 2 == 1) {
7267 styles.push_back({t == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2, 1});
7271 for (
long long int i = 0; i < style / 2; ++i) {
7272 styles.push_back({t == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2, 2});
7276 styles.push_back({Style::Strikethrough, style});
7280template<
class Trait>
7281inline std::vector<std::pair<Style, long long int>>
7283 const std::vector<long long int> &styles,
7284 long long int lastStyle)
7286 std::vector<std::pair<Style, long long int>> ret;
7288 createStyles(ret, t, lastStyle);
7290 for (
auto it = styles.crbegin(), last = styles.crend(); it != last; ++it) {
7291 createStyles(ret, t, *it);
7297template<
class Trait>
7300 long long int itLine,
7301 long long int itPos,
7302 typename Delimiter::DelimiterType t)
7304 return (itLine == it->m_line && itPos + it->m_len == it->m_pos && it->m_type == t);
7307template<
class Trait>
7310 typename Delims::iterator last,
7311 long long int &line,
7314 long long int &itCount)
7319 const auto t = it->m_type;
7324 while (it != last && isSequence(it, line, pos, t)) {
7332 return std::prev(it);
7338 return ((((i1 + i2) % 3) == 0) && !((i1 % 3 == 0) && (i2 % 3 == 0)));
7341template<
class Trait>
7342inline std::tuple<bool, std::vector<std::pair<Style, long long int>>,
long long int,
long long int>
7344 typename Delims::iterator it,
7345 typename Delims::iterator last,
7346 typename Delims::iterator &stackBottom,
7347 TextParsingOpts<Trait> &po)
7349 const auto open = it;
7350 long long int openPos, openLength, itCount, lengthFromIt, tmp;
7352 it = std::next(readSequence(first, open, last, openPos, openLength, tmp, lengthFromIt, itCount).second);
7354 const auto length = lengthFromIt;
7355 long long int itLine, itPos, itLength;
7357 struct RollbackValues {
7358 RollbackValues(TextParsingOpts<Trait> &po,
7361 bool collectRefLinks,
7362 typename Delims::iterator &stackBottom,
7363 typename Delims::iterator last)
7367 , m_collectRefLinks(collectRefLinks)
7368 , m_stackBottom(stackBottom)
7374 void setIterator(
typename Delims::iterator it)
7381 m_po.m_line = m_line;
7383 m_po.m_collectRefLinks = m_collectRefLinks;
7385 if (m_it != m_last && (m_it > m_stackBottom || m_stackBottom == m_last)) {
7386 m_stackBottom = m_it;
7390 TextParsingOpts<Trait> &m_po;
7391 long long int m_line;
7392 long long int m_pos;
7393 bool m_collectRefLinks;
7394 typename Delims::iterator &m_stackBottom;
7395 typename Delims::iterator m_last;
7396 typename Delims::iterator m_it;
7399 RollbackValues rollback(po, po.m_line, po.m_pos, po.m_collectRefLinks, stackBottom, last);
7401 po.m_collectRefLinks =
true;
7403 std::vector<long long int> styles;
7406 std::vector<typename Delims::iterator> m_its;
7407 long long int m_length;
7410 std::vector<Opener> openers;
7412 std::function<void(
long long int,
long long int)> dropOpeners;
7414 dropOpeners = [&openers](
long long int pos,
long long int line) {
7415 while (!openers.empty()) {
7416 if (openers.back().m_its.front()->m_line > line || (openers.back().m_its.front()->m_line == line &&
7417 openers.back().m_its.front()->m_pos > pos)) {
7418 std::for_each( openers.back().m_its.begin(), openers.back().m_its.end(),
7419 [](
auto &i) { i->m_skip = true; });
7427 auto tryCloseEmphasis = [&dropOpeners,
this, &openers, &
open](
typename Delims::iterator first,
7428 typename Delims::iterator it,
7429 typename Delims::iterator last) ->
bool
7431 const auto type = it->m_type;
7432 const auto both = it->m_leftFlanking && it->m_rightFlanking;
7433 long long int tmp1, tmp2, tmp3, tmp4;
7434 long long int closeLength;
7436 it = this->readSequence(first, it, last, tmp1, closeLength, tmp2, tmp3, tmp4).first;
7439 long long int tmpLength = closeLength;
7442 switch (it->m_type) {
7443 case Delimiter::Strikethrough: {
7444 if (it->m_leftFlanking && it->m_len == closeLength && type == it->m_type) {
7445 dropOpeners(it->m_pos, it->m_line);
7450 case Delimiter::Emphasis1:
7451 case Delimiter::Emphasis2:
7453 if (it->m_leftFlanking && type == it->m_type) {
7454 long long int pos, len;
7455 this->readSequence(first, it, last, pos, len, tmp1, tmp2, tmp3);
7457 if ((both || (it->m_leftFlanking && it->m_rightFlanking)) &&
isMult3(len, closeLength)) {
7461 dropOpeners(pos - len, it->m_line);
7463 if (tmpLength >= len) {
7466 if (
open->m_type == it->m_type) {
7474 if (
open->m_type == it->m_type) {
7475 openers.back().m_length -= tmpLength;
7495 auto fillIterators = [](
typename Delims::iterator first,
7496 typename Delims::iterator last) -> std::vector<typename Delims::iterator>
7498 std::vector<typename Delims::iterator> res;
7500 for (; first != last; ++first) {
7501 res.push_back(first);
7504 res.push_back(last);
7509 for (; it != last; ++it) {
7510 if (it > stackBottom) {
7511 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7514 if (it->m_line <= po.m_lastTextLine) {
7515 po.m_line = it->m_line;
7517 switch (it->m_type) {
7518 case Delimiter::SquareBracketsOpen:
7519 it = checkForLink(it, last, po);
7522 case Delimiter::ImageOpen:
7523 it = checkForImage(it, last, po);
7526 case Delimiter::Less:
7527 it = checkForAutolinkHtml(it, last, po,
false);
7530 case Delimiter::Strikethrough: {
7531 if (
open->m_type == it->m_type &&
open->m_len == it->m_len && it->m_rightFlanking) {
7532 rollback.setIterator(it);
7533 return {
true, createStyles(
open->m_type, styles,
open->m_len),
open->m_len, 1};
7534 }
else if (it->m_rightFlanking && tryCloseEmphasis(open, it, last)) {
7535 }
else if (it->m_leftFlanking &&
open->m_type == it->m_type) {
7536 openers.push_back({{it}, it->m_len});
7540 case Delimiter::Emphasis1:
7541 case Delimiter::Emphasis2: {
7542 if (
open->m_type == it->m_type) {
7543 const auto itBoth = (it->m_leftFlanking && it->m_rightFlanking);
7545 if (it->m_rightFlanking) {
7546 bool notCheck = (
open->m_leftFlanking &&
open->m_rightFlanking) || itBoth;
7548 long long int count;
7550 it = readSequence(it, last, itLine, itPos, itLength, count);
7553 notCheck =
isMult3(openLength, itLength);
7556 if (!openers.empty()) {
7557 long long int i = openers.size() - 1;
7558 auto &top = openers[i];
7560 while (!openers.empty()) {
7567 if ((itBoth || (top.m_its.front()->m_rightFlanking && top.m_its.front()->m_leftFlanking))
7568 &&
isMult3(itLength, top.m_length)) {
7573 if (top.m_length <= itLength) {
7574 itLength -= top.m_length;
7575 openers.erase(openers.begin() + i);
7577 top.m_length -= itLength;
7591 if (itLength >= lengthFromIt) {
7592 rollback.setIterator(it);
7593 return {
true, createStyles(
open->m_type, styles, lengthFromIt), length, itCount};
7595 styles.push_back(itLength);
7596 lengthFromIt -= itLength;
7598 }
else if (firstIt->m_leftFlanking) {
7599 openers.push_back({fillIterators(firstIt, it), itLength});
7603 long long int count;
7605 it = readSequence(it, last, itLine, itPos, itLength, count);
7606 openers.push_back({fillIterators(firstIt, it), itLength});
7608 }
else if (it->m_rightFlanking) {
7609 tryCloseEmphasis(open, it, last);
7613 case Delimiter::InlineCode:
7614 it = checkForInlineCode(it, last, po);
7625 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7628template<
class Trait>
7631 typename Delims::iterator last,
7632 long long int count)
7634 const auto len = std::distance(it, last);
7639 return it + (len - 1);
7644template<
class Trait>
7654template<
class Trait>
7657 typename Delims::iterator it,
7658 typename Delims::iterator last,
7660 long long int &length,
7661 long long int &itCount,
7662 long long int &lengthFromIt,
7663 long long int &itCountFromIt)
7665 long long int line = it->m_line;
7666 pos = it->m_pos + it->m_len;
7667 long long int ppos = it->m_pos;
7668 const auto t = it->m_type;
7669 lengthFromIt = it->m_len;
7672 auto retItLast = std::next(it);
7674 for (; retItLast != last; ++retItLast) {
7675 if (retItLast->m_line == line && pos == retItLast->m_pos && retItLast->m_type == t) {
7676 lengthFromIt += retItLast->m_len;
7677 pos = retItLast->m_pos + retItLast->m_len;
7684 length = lengthFromIt;
7685 itCount = itCountFromIt;
7687 auto retItFirst = it;
7688 bool useNext =
false;
7690 if (retItFirst != first) {
7691 retItFirst = std::prev(retItFirst);
7694 for (;; --retItFirst) {
7695 if (retItFirst->m_line == line && ppos - retItFirst->m_len == retItFirst->m_pos && retItFirst->m_type == t) {
7696 length += retItFirst->m_len;
7697 ppos = retItFirst->m_pos;
7705 if (retItFirst == first) {
7711 return {useNext ? std::next(retItFirst) : retItFirst, std::prev(retItLast)};
7714template<
class Trait>
7717 typename Delims::iterator it,
7718 typename Delims::iterator last,
7719 typename Delims::iterator &stackBottom,
7720 TextParsingOpts<Trait> &po)
7722 long long int count = 1;
7724 po.m_wasRefLink =
false;
7725 po.m_firstInParagraph =
false;
7727 if (it->m_rightFlanking) {
7728 long long int pos, len, tmp1, tmp2;
7729 readSequence(first, it, last, pos, len, count, tmp1, tmp2);
7730 const auto t = it->m_type;
7732 long long int opened = 0;
7733 bool bothFlanking =
false;
7735 for (
auto it = po.m_styles.crbegin(), last = po.m_styles.crend(); it != last; ++it) {
7736 bool doBreak =
false;
7739 case Delimiter::Emphasis1: {
7740 if (it->m_style == Style::Italic1 || it->m_style == Style::Bold1) {
7741 opened = it->m_length;
7742 bothFlanking = it->m_bothFlanking;
7747 case Delimiter::Emphasis2: {
7748 if (it->m_style == Style::Italic2 || it->m_style == Style::Bold2) {
7749 opened = it->m_length;
7750 bothFlanking = it->m_bothFlanking;
7755 case Delimiter::Strikethrough: {
7756 if (it->m_style == Style::Strikethrough) {
7757 opened = it->m_length;
7758 bothFlanking = it->m_bothFlanking;
7771 const bool sumMult3 = (it->m_leftFlanking || bothFlanking ?
isMult3(opened, len) : false);
7773 if (count && opened && !sumMult3) {
7774 if (count > opened) {
7778 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
7779 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
7781 if (it->m_type == Delimiter::Strikethrough) {
7782 const auto len = it->m_len;
7784 for (
auto i = 0; i < count; ++i) {
7785 closeStyle<Trait>(po.m_styles, Style::Strikethrough);
7790 if (count % 2 == 1) {
7791 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2);
7793 closeStyle<Trait>(po.m_styles, st);
7799 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2);
7801 for (
auto i = 0; i < count / 2; ++i) {
7802 closeStyle<Trait>(po.m_styles, st);
7809 applyStyles<Trait>(po.m_opts, po.m_styles);
7811 const auto j = incrementIterator(it, last, count - 1);
7813 po.m_pos = j->m_pos + j->m_len;
7814 po.m_line = j->m_line;
7822 if (it->m_leftFlanking) {
7823 switch (it->m_type) {
7824 case Delimiter::Strikethrough:
7825 case Delimiter::Emphasis1:
7826 case Delimiter::Emphasis2: {
7827 bool closed =
false;
7828 std::vector<std::pair<Style, long long int>> styles;
7829 long long int len = 0;
7831 if (it > stackBottom) {
7837 long long int tmp1, tmp2, tmp3;
7838 readSequence(it, last, tmp1, tmp2, len, tmp3);
7840 std::tie(closed, styles, len, count) = isStyleClosed(first, it, last, stackBottom, po);
7844 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
7845 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
7847 for (
const auto &p : styles) {
7848 po.m_styles.push_back({p.first, p.second, it->m_leftFlanking && it->m_rightFlanking});
7850 if (!po.m_collectRefLinks) {
7852 pos + p.second - 1, line});
7858 po.m_pos = it->m_pos + len;
7859 po.m_line = it->m_line;
7861 applyStyles<Trait>(po.m_opts, po.m_styles);
7862 }
else if (!po.m_collectRefLinks) {
7863 makeText(it->m_line, it->m_pos + len, po);
7868 if (!po.m_collectRefLinks) {
7869 makeText(it->m_line, it->m_pos + it->m_len, po);
7881 return incrementIterator(it, last, count - 1);
7885template<
class Trait>
7886inline std::shared_ptr<Text<Trait>>
7892 t->setStartColumn((*it)->startColumn());
7893 t->setStartLine((*it)->startLine());
7897 typename Trait::String data;
7899 for (; it != last; ++it) {
7900 const auto tt = std::static_pointer_cast<Text<Trait>>(*it);
7902 data.push_back(tt->text());
7904 if (!tt->openStyles().empty()) {
7905 std::copy(tt->openStyles().cbegin(), tt->openStyles().cend(),
7906 std::back_inserter(t->openStyles()));
7909 if (!tt->closeStyles().empty()) {
7910 std::copy(tt->closeStyles().cbegin(), tt->closeStyles().cend(),
7911 std::back_inserter(close));
7918 t->setEndColumn((*it)->endColumn());
7919 t->setEndLine((*it)->endLine());
7920 t->closeStyles() = close;
7954template<
class Trait>
7955inline std::shared_ptr<Paragraph<Trait>>
7961 np->setStartColumn(p->startColumn());
7962 np->setStartLine(p->startLine());
7963 np->setEndColumn(p->endColumn());
7964 np->setEndLine(p->endLine());
7967 auto start = p->items().cend();
7968 long long int line = -1;
7969 long long int auxStart = 0, auxIt = 0;
7970 bool finished =
false;
7972 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
7974 const auto t = std::static_pointer_cast<Text<Trait>>(*it);
7976 if (
start == last) {
7979 line = t->endLine();
7982 if (opts != t->opts() || t->startLine() != line || finished ||
7986 auxIt = auxIt - (auxIt - auxStart) + 1;
7993 line = t->endLine();
8004 if (
start != last) {
8007 auxIt = auxIt - (auxIt - auxStart) + 1;
8014 line = (*it)->endLine();
8017 np->appendItem((*it));
8021 if (
start != p->items().cend()) {
8034template<
class Trait>
8037 std::shared_ptr<Paragraph<Trait>> parent,
8039 typename Trait::StringList &linksToParse,
8040 const typename Trait::String &workingPath,
8041 const typename Trait::String &fileName,
8042 bool collectRefLinks)
8045 std::copy(po.m_fr.m_data.cbegin() + po.m_startTableLine, po.m_fr.m_data.cend(),
8046 std::back_inserter(fr.m_data));
8047 fr.m_emptyLineAfter = po.m_fr.m_emptyLineAfter;
8049 parseTable(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks,
8052 po.m_line = po.m_fr.m_data.size() - fr.m_data.size();
8055 if (!fr.m_data.empty()) {
8056 po.m_detected = TextParsingOpts<Trait>::Detected::Code;
8063 long long int &line,
8064 long long int length,
8065 long long int linesCount)
8067 if (pos != 0 && line < linesCount && pos == length) {
8073template<
class Trait>
8077 if (po.m_detected == TextParsingOpts<Trait>::Detected::HTML &&
8078 ((!po.m_parent->items().empty() &&
8079 po.m_parent->items().back()->type() == ItemType::RawHtml) || po.m_tmpHtml.get())) {
8080 auto html = (po.m_tmpHtml.get() ? po.m_tmpHtml :
8081 std::static_pointer_cast<RawHtml<Trait>>(po.m_parent->items().back()));
8083 bool dontClearDetection =
false;
8085 long long int line = po.m_line;
8086 long long int pos = po.m_pos;
8088 normalizePos(pos, line, line <
static_cast<long long int>(po.m_fr.m_data.size()) ?
8089 po.m_fr.m_data[line].first.length() : 0, po.m_fr.m_data.size());
8092 if (line <
static_cast<long long int>(po.m_fr.m_data.size())) {
8093 const auto type = whatIsTheLine(po.m_fr.m_data[line].first);
8099 if (isOrderedList<Trait>(po.m_fr.m_data[line].first.asString(), &num)) {
8111 if (UnprotectedDocsMethods<Trait>::isFreeTag(html)) {
8117 dontClearDetection =
true;
8126 if (!dontClearDetection) {
8127 po.m_detected = TextParsingOpts<Trait>::Detected::Nothing;
8131 po.m_tmpHtml.reset();
8137template<
class Trait>
8138inline std::shared_ptr<Paragraph<Trait>>
8142 auto p = std::make_shared<Paragraph<Trait>>();
8144 p->setStartColumn((*first)->startColumn());
8145 p->setStartLine((*first)->startLine());
8147 for (; first != last; ++first) {
8148 p->appendItem(*first);
8149 p->setEndColumn((*first)->endColumn());
8150 p->setEndLine((*first)->endLine());
8157template<
class Trait>
8158inline std::shared_ptr<Paragraph<Trait>>
8162 bool collectRefLinks,
8163 bool fullyOptimizeParagraphs =
true)
8165 auto first = p->items().cbegin();
8167 auto last = p->items().cend();
8169 for (; it != last; ++it) {
8170 if (first == last) {
8178 if (!collectRefLinks) {
8179 if (!p->isEmpty()) {
8181 fullyOptimizeParagraphs ?
8186 parent->appendItem(*it);
8193 if (first != last) {
8194 if (first != p->items().cbegin()) {
8195 const auto c = std::count_if(first, last, [](
const auto &i) {
8208 return std::make_shared<Paragraph<Trait>>();
8213template<
class Trait>
8217 switch (item->
type()) {
8226 if (!i->closeStyles().empty()) {
8227 return i->closeStyles().back().endColumn();
8229 return i->endColumn();
8239 if (!c->closeStyles().empty()) {
8240 return c->closeStyles().back().endColumn();
8242 return c->endDelim().endColumn();
8253template<
class Trait>
8258 long long int lastColumn,
8259 long long int lastLine,
8261 const typename Trait::String &workingPath,
8262 const typename Trait::String &fileName,
8263 bool collectRefLinks,
8267 if (!collectRefLinks) {
8269 auto lb = std::static_pointer_cast<LineBreak<Trait>>(p->items().back());
8270 const auto lineBreakBySpaces = lb->text().simplified().isEmpty();
8275 if (!p->isEmpty()) {
8277 auto lt = std::static_pointer_cast<Text<Trait>>(p->items().back());
8279 if (!lineBreakBySpaces) {
8280 auto text = po.
m_fr.m_data.at(lineBreakPos.second).first.fullVirginString().sliced(
8284 if (!lt->text()[0].isSpace()) {
8287 text.remove(0, notSpacePos);
8293 lt->setEndColumn(lt->endColumn() + lb->text().length());
8295 if (!lineBreakBySpaces) {
8298 const auto endOfLine = po.
m_fr.m_data.at(lineBreakPos.second).first.virginSubString(
8299 lastItemPos.first + 1);
8300 auto t = std::make_shared<Text<Trait>>();
8301 t->setText(endOfLine);
8302 t->setStartColumn(lastItemVirginPos + 1);
8303 t->setStartLine(lb->startLine());
8304 t->setEndColumn(lb->endColumn());
8305 t->setEndLine(lb->endLine());
8311 po.
m_rawTextData.push_back({lb->text(), pos.first, pos.second});
8317 std::pair<typename Trait::String, WithPosition> label;
8320 auto t = std::static_pointer_cast<Text<Trait>>(p->items().back());
8324 typename Trait::InternalString tmp(text.m_str);
8330 if (tmp.asString().simplified().isEmpty()) {
8331 p->removeItemAt(p->items().size() - 1);
8334 if (!p->items().empty()) {
8335 const auto last = std::static_pointer_cast<WithPosition>(p->items().back());
8336 p->setEndColumn(last->endColumn());
8337 p->setEndLine(last->endLine());
8341 const auto virginLine = t->endLine();
8343 if (label.second.startColumn() > notSpacePos) {
8344 auto text = tmp.fullVirginString().sliced(0, label.second.startColumn());
8347 if (!t->text()[0].isSpace()) {
8350 text.remove(0, notSpacePos);
8354 t->setEndColumn(label.second.startColumn() - 1);
8356 const auto lastPos = t->endColumn();
8359 if (pos.first != -1) {
8360 t = std::make_shared<Text<Trait>>();
8361 t->setStartColumn(label.second.endColumn() + 1);
8362 t->setStartLine(virginLine);
8363 t->setEndColumn(lastPos);
8364 t->setEndLine(virginLine);
8367 po.
m_rawTextData.push_back({po.
m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8368 pos.first, pos.second});
8374 if (pos.first != -1) {
8375 po.
m_rawTextData.back() = {po.
m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8376 pos.first, pos.second};
8380 if (!text.simplified().isEmpty()) {
8381 if (p->items().size() == 1) {
8387 t->setStartColumn(label.second.endColumn() + 1);
8391 p->removeItemAt(p->items().size() - 1);
8395 p->setEndColumn(t->endColumn());
8401 label.second.setStartLine(t->startLine());
8402 label.second.setEndLine(t->endLine());
8407 h->setStartColumn(p->startColumn());
8408 h->setStartLine(p->startLine());
8409 h->setEndColumn(lastColumn);
8410 h->setEndLine(lastLine);
8413 if (!p->items().empty()) {
8417 h->setDelims({delim});
8422 h->setLabelPos(label.second);
8426 label.
first += Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
8427 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName;
8429 h->setLabel(label.
first);
8431 doc->insertLabeledHeading(label.
first, h);
8434 parent->appendItem(h);
8439template<
class Trait>
8446 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8449 return std::distance(p->items().cbegin(), it);
8460template<
class Trait>
8467 for (
const auto &plugin : textPlugins) {
8468 if (inLink && !std::get<bool>(plugin.second)) {
8472 std::get<TextPluginFunc<Trait>>(plugin.second)(p, po,
8473 std::get<typename Trait::StringList>(plugin.second));
8478template<
class Trait>
8484 hr->setStartColumn(line.first.virginPos(
skipSpaces<Trait>(0, line.first.asString())));
8485 hr->setStartLine(line.second.m_lineNumber);
8486 hr->setEndColumn(line.first.virginPos(line.first.length() - 1));
8487 hr->setEndLine(line.second.m_lineNumber);
8488 parent->appendItem(hr);
8491template<
class Trait>
8494 std::shared_ptr<Block<Trait>> parent,
8496 typename Trait::StringList &linksToParse,
8497 const typename Trait::String &workingPath,
8498 const typename Trait::String &fileName,
8499 bool collectRefLinks,
8500 bool ignoreLineBreak,
8501 RawHtmlBlock<Trait> &html,
8505 if (fr.m_data.empty()) {
8509 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
8510 p->setStartColumn(fr.m_data.at(0).first.virginPos(0));
8511 p->setStartLine(fr.m_data.at(0).second.m_lineNumber);
8512 std::shared_ptr<Paragraph<Trait>> pt(
new Paragraph<Trait>);
8514 auto delims = collectDelimiters(fr.m_data);
8516 TextParsingOpts<Trait> po = {fr, p,
nullptr, doc, linksToParse, workingPath, fileName,
8517 collectRefLinks, ignoreLineBreak, html, m_textPlugins};
8518 typename Delims::iterator styleStackBottom = delims.end();
8520 if (!delims.empty()) {
8521 for (
auto it = delims.begin(), last = delims.end(); it != last; ++it) {
8522 if (html.m_html.get() && html.m_continueHtml) {
8523 it = finishRawHtmlTag(it, last, po,
false);
8525 if (isListOrQuoteAfterHtml(po)) {
8529 if (po.m_line > po.m_lastTextLine) {
8533 if (po.shouldStopParsing() && po.m_lastTextLine < it->m_line) {
8535 }
else if (!collectRefLinks) {
8536 makeText(po.m_lastTextLine < it->m_line ? po.m_lastTextLine : it->m_line,
8537 po.m_lastTextLine < it->m_line ? po.m_lastTextPos : it->m_pos, po);
8539 const auto prevLine = po.m_line;
8541 po.m_line = (po.m_lastTextLine < it->m_line ? po.m_lastTextLine : it->m_line);
8542 po.m_pos = (po.m_lastTextLine < it->m_line ? po.m_lastTextPos : it->m_pos);
8544 if (po.m_line > prevLine) {
8545 po.m_firstInParagraph =
false;
8546 }
else if (po.m_pos > skipSpaces<Trait>(0, po.m_fr.m_data[po.m_line].first.asString())) {
8547 po.m_firstInParagraph =
false;
8551 switch (it->m_type) {
8552 case Delimiter::SquareBracketsOpen: {
8553 it = checkForLink(it, last, po);
8554 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8555 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8558 case Delimiter::ImageOpen: {
8559 it = checkForImage(it, last, po);
8560 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8561 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8564 case Delimiter::Less: {
8565 it = checkForAutolinkHtml(it, last, po,
true);
8567 if (!html.m_html.get()) {
8568 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8569 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8573 case Delimiter::Strikethrough:
8574 case Delimiter::Emphasis1:
8575 case Delimiter::Emphasis2: {
8576 if (!collectRefLinks) {
8577 it = checkForStyle(delims.begin(), it, last, styleStackBottom, po);
8578 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8579 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8583 case Delimiter::Math: {
8584 it = checkForMath(it, last, po);
8585 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8586 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8589 case Delimiter::InlineCode: {
8590 if (!it->m_backslashed) {
8591 it = checkForInlineCode(it, last, po);
8592 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8593 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8597 case Delimiter::HorizontalLine: {
8598 po.m_wasRefLink =
false;
8599 po.m_firstInParagraph =
false;
8601 const auto pos = skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString());
8602 const auto withoutSpaces = po.m_fr.m_data[it->m_line].first.asString().sliced(pos);
8604 auto h2 = isH2<Trait>(withoutSpaces);
8606 if (!p->isEmpty()) {
8607 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8609 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8611 if (it->m_line - 1 >= 0) {
8612 p->setEndColumn(fr.m_data.at(it->m_line - 1).first.virginPos(
8613 fr.m_data.at(it->m_line - 1).first.length() - 1));
8614 p->setEndLine(fr.m_data.at(it->m_line - 1).second.m_lineNumber);
8619 if (!p->isEmpty()) {
8620 if (!collectRefLinks) {
8621 if (!h2 || (p->items().size() == 1 &&
8622 p->items().front()->type() == ItemType::LineBreak)) {
8623 parent->appendItem(p);
8629 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8630 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8631 fr.m_data[it->m_line].second.m_lineNumber,
8636 {po.m_fr.m_data[it->m_line].first.virginPos(pos),
8637 fr.m_data[it->m_line].second.m_lineNumber,
8638 po.m_fr.m_data[it->m_line].first.virginPos(
8639 lastNonSpacePos(po.m_fr.m_data[it->m_line].first.asString())),
8640 fr.m_data[it->m_line].second.m_lineNumber},
8643 po.m_checkLineOnNewType =
true;
8653 p.reset(
new Paragraph<Trait>);
8654 po.m_rawTextData.clear();
8656 if (it->m_line + 1 <
static_cast<long long int>(fr.m_data.size())) {
8657 p->setStartColumn(fr.m_data.at(it->m_line + 1).first.virginPos(0));
8658 p->setStartLine(fr.m_data.at(it->m_line + 1).second.m_lineNumber);
8662 po.m_line = it->m_line;
8663 po.m_pos = it->m_pos + it->m_len;
8665 if (!h2 && !collectRefLinks) {
8666 makeHorLine<Trait>(fr.m_data[it->m_line], parent);
8671 case Delimiter::H2: {
8672 po.m_wasRefLink =
false;
8673 po.m_firstInParagraph =
false;
8675 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8677 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8679 if (it->m_line - 1 >= 0) {
8680 p->setEndColumn(fr.m_data.at(it->m_line - 1).first.virginPos(
8681 fr.m_data.at(it->m_line - 1).first.length() - 1));
8682 p->setEndLine(fr.m_data.at(it->m_line - 1).second.m_lineNumber);
8686 m_fullyOptimizeParagraphs);
8688 if (!p->isEmpty() && !((p->items().size() == 1 &&
8689 p->items().front()->type() == ItemType::LineBreak))) {
8692 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8693 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8694 fr.m_data[it->m_line].second.m_lineNumber,
8695 it->m_type == Delimiter::H1 ? 1 : 2,
8699 {po.m_fr.m_data[it->m_line].first.virginPos(skipSpaces<Trait>(
8700 0, po.m_fr.m_data[it->m_line].first.asString())),
8701 fr.m_data[it->m_line].second.m_lineNumber,
8702 po.m_fr.m_data[it->m_line].first.virginPos(lastNonSpacePos(
8703 po.m_fr.m_data[it->m_line].first.asString())),
8704 fr.m_data[it->m_line].second.m_lineNumber},
8707 po.m_checkLineOnNewType =
true;
8709 p.reset(
new Paragraph<Trait>);
8710 po.m_rawTextData.clear();
8712 if (it->m_line + 1 <
static_cast<long long int>(fr.m_data.size())) {
8713 p->setStartColumn(fr.m_data.at(it->m_line + 1).first.virginPos(0));
8714 p->setStartLine(fr.m_data.at(it->m_line + 1).second.m_lineNumber);
8717 po.m_line = it->m_line;
8718 po.m_pos = it->m_pos + it->m_len;
8719 }
else if (p->startColumn() == -1) {
8720 p->setStartColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
8721 p->setStartLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8728 if (!po.shouldStopParsing()) {
8729 po.m_wasRefLink =
false;
8730 po.m_firstInParagraph =
false;
8732 if (!collectRefLinks) {
8733 makeText(it->m_line, it->m_pos + it->m_len, po);
8735 po.m_line = it->m_line;
8736 po.m_pos = it->m_pos + it->m_len;
8742 if (po.shouldStopParsing()) {
8746 if (po.m_checkLineOnNewType) {
8747 if (po.m_line + 1 <
static_cast<long long int>(po.m_fr.m_data.size())) {
8751 po.m_detected = TextParsingOpts<Trait>::Detected::Code;
8757 po.m_checkLineOnNewType =
false;
8762 if (html.m_html.get() && html.m_continueHtml) {
8763 finishRawHtmlTag(delims.end(), delims.end(), po,
false);
8767 if (po.m_lastTextLine == -1) {
8771 if (po.m_detected == TextParsingOpts<Trait>::Detected::Table) {
8772 if (!collectRefLinks) {
8773 makeText(po.m_lastTextLine, po.m_lastTextPos, po);
8776 parseTableInParagraph(po, pt, doc, linksToParse, workingPath, fileName, collectRefLinks);
8779 while (po.m_detected == TextParsingOpts<Trait>::Detected::HTML &&
8780 po.m_line <
static_cast<long long int>(po.m_fr.m_data.size())) {
8781 if (!isListOrQuoteAfterHtml(po)) {
8782 if (!collectRefLinks) {
8783 makeText(po.m_line, po.m_fr.m_data[po.m_line].first.length(), po);
8793 if (po.m_detected == TextParsingOpts<Trait>::Detected::Nothing &&
8794 po.m_line <=
static_cast<long long int>(po.m_fr.m_data.size() - 1)) {
8795 if (!collectRefLinks) {
8796 makeText(po.m_fr.m_data.size() - 1, po.m_fr.m_data.back().first.length(), po);
8800 if (!p->isEmpty()) {
8801 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8803 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8807 if (!p->isEmpty() && !collectRefLinks) {
8808 parent->appendItem(optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()));
8811 po.m_rawTextData.clear();
8814 if (!pt->isEmpty() && !collectRefLinks) {
8815 parent->appendItem(pt->items().front());
8818 normalizePos(po.m_pos, po.m_line, po.m_line <
static_cast<long long int>(po.m_fr.m_data.size()) ?
8819 po.m_fr.m_data[po.m_line].first.length() : 0, po.m_fr.m_data.size());
8821 if (po.m_detected != TextParsingOpts<Trait>::Detected::Nothing &&
8822 po.m_line <
static_cast<long long int>(po.m_fr.m_data.size())) {
8823 typename MdBlock<Trait>::Data tmp;
8824 std::copy(fr.m_data.cbegin() + po.m_line, fr.m_data.cend(), std::back_inserter(tmp));
8826 StringListStream<Trait> stream(tmp);
8828 Parser<Trait>::parse(stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
8832template<
class Trait>
8835 std::shared_ptr<Block<Trait>>,
8837 typename Trait::StringList &linksToParse,
8838 const typename Trait::String &workingPath,
8839 const typename Trait::String &fileName,
8840 bool collectRefLinks)
8843 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
8844 return !s.first.isEmpty();
8847 if (it != fr.m_data.end()) {
8848 fr.m_data.erase(it, fr.m_data.end());
8852 if (!fr.m_data.empty()) {
8853 std::shared_ptr<Footnote<Trait>> f(
new Footnote<Trait>);
8854 f->setStartColumn(fr.m_data.front().first.virginPos(0));
8855 f->setStartLine(fr.m_data.front().second.m_lineNumber);
8856 f->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
8857 f->setEndLine(fr.m_data.back().second.m_lineNumber);
8859 auto delims = collectDelimiters(fr.m_data);
8861 RawHtmlBlock<Trait> html;
8863 TextParsingOpts<Trait> po = {fr, f,
nullptr, doc, linksToParse, workingPath, fileName,
8864 collectRefLinks,
false, html, m_textPlugins};
8865 po.m_lastTextLine = fr.m_data.size();
8866 po.m_lastTextPos = fr.m_data.back().first.length();
8868 if (!delims.empty() && delims.cbegin()->m_type == Delimiter::SquareBracketsOpen &&
8869 !delims.cbegin()->m_isWordBefore) {
8870 typename MdBlock<Trait>::Data id;
8871 typename Delims::iterator it = delims.end();
8873 po.m_line = delims.cbegin()->m_line;
8874 po.m_pos = delims.cbegin()->m_pos;
8876 std::tie(
id, it) = checkForLinkText(delims.begin(), delims.end(), po);
8878 if (!toSingleLine(
id).isEmpty() &&
8879 id.front().first.asString().startsWith(Trait::latin1ToString(
"^")) &&
8880 it != delims.cend() &&
8881 fr.m_data.at(it->m_line).first.length() > it->m_pos + 2 &&
8882 fr.m_data.at(it->m_line).first[it->m_pos + 1] == Trait::latin1ToChar(
':') &&
8883 fr.m_data.at(it->m_line).first[it->m_pos + 2].isSpace()) {
8884 f->setIdPos({fr.m_data[delims.cbegin()->m_line].first.virginPos(delims.cbegin()->m_pos),
8885 fr.m_data[delims.cbegin()->m_line].second.m_lineNumber,
8886 fr.m_data.at(it->m_line).first.virginPos(it->m_pos + 1),
8887 fr.m_data.at(it->m_line).second.m_lineNumber});
8890 typename MdBlock<Trait>::Data tmp;
8891 std::copy(fr.m_data.cbegin() + it->m_line, fr.m_data.cend(),
8892 std::back_inserter(tmp));
8896 fr.m_data.front().first = fr.m_data.front().first.sliced(it->m_pos + 3);
8898 for (
auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it) {
8899 if (it->first.asString().startsWith(Trait::latin1ToString(
" "))) {
8900 it->first = it->first.sliced(4);
8904 StringListStream<Trait> stream(fr.m_data);
8906 parse(stream, f, doc, linksToParse, workingPath, fileName, collectRefLinks);
8908 if (!f->isEmpty()) {
8909 doc->insertFootnote(Trait::latin1ToString(
"#") + toSingleLine(
id) +
8910 Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
8911 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName,
8919template<
class Trait>
8922 std::shared_ptr<Block<Trait>> parent,
8924 typename Trait::StringList &linksToParse,
8925 const typename Trait::String &workingPath,
8926 const typename Trait::String &fileName,
8927 bool collectRefLinks,
8928 RawHtmlBlock<Trait> &)
8930 const long long int pos = fr.m_data.front().first.asString().indexOf(Trait::latin1ToChar(
'>'));
8931 long long int extra = 0;
8933 long long int line = -1;
8936 typename Blockquote<Trait>::Delims delims;
8938 long long int i = 0, j = 0;
8940 BlockType bt = BlockType::EmptyLine;
8942 for (
auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it, ++i) {
8943 const auto ns = skipSpaces<Trait>(0, it->first.asString());
8944 const auto gt = (ns < it->first.length() ? (it->first[ns] == Trait::latin1ToChar(
'>') ? ns : -1) : -1);
8947 const auto dp = it->first.virginPos(gt);
8948 delims.push_back({dp, it->second.m_lineNumber, dp, it->second.m_lineNumber});
8950 if (it == fr.m_data.begin()) {
8951 extra = gt + (it->first.length() > gt + 1 ?
8952 (it->first[gt + 1] == Trait::latin1ToChar(
' ') ? 1 : 0) : 0) + 1;
8955 it->first = it->first.sliced(gt + (it->first.length() > gt + 1 ?
8956 (it->first[gt + 1] == Trait::latin1ToChar(
' ') ? 1 : 0) : 0) + 1);
8958 bt = whatIsTheLine(it->first);
8962 if (ns < 4 && isHorizontalLine<Trait>(it->first.asString().sliced(ns))) {
8963 line = it->second.m_lineNumber;
8967 const auto tmpBt = whatIsTheLine(it->first);
8969 if (isListType(tmpBt)) {
8970 line = it->second.m_lineNumber;
8974 if (bt == BlockType::Text) {
8975 if (isH1<Trait>(it->first.asString())) {
8976 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
8978 it->first.insert(p, Trait::latin1ToChar(
'\\'));
8981 }
else if (isH2<Trait>(it->first.asString())) {
8982 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'-'));
8984 it->first.insert(p, Trait::latin1ToChar(
'\\'));
8988 }
else if ((bt == BlockType::Code || bt == BlockType::CodeIndentedBySpaces) &&
8989 it->second.m_mayBreakList) {
8990 line = it->second.m_lineNumber;
8994 if ((bt == BlockType::Text || bt == BlockType::Blockquote || bt == BlockType::List)
8995 && (tmpBt == BlockType::Text || tmpBt == BlockType::CodeIndentedBySpaces)) {
8998 line = it->second.m_lineNumber;
9004 typename MdBlock<Trait>::Data tmp;
9006 for (; j < i; ++j) {
9007 tmp.push_back(fr.m_data.at(j));
9010 StringListStream<Trait> stream(tmp);
9012 std::shared_ptr<Blockquote<Trait>> bq(
new Blockquote<Trait>);
9013 bq->setStartColumn(fr.m_data.at(0).first.virginPos(0) - extra);
9014 bq->setStartLine(fr.m_data.at(0).second.m_lineNumber);
9015 bq->setEndColumn(fr.m_data.at(j - 1).first.virginPos(fr.m_data.at(j - 1).first.length() - 1));
9016 bq->setEndLine(fr.m_data.at(j - 1).second.m_lineNumber);
9017 bq->delims() = delims;
9019 parse(stream, bq, doc, linksToParse, workingPath, fileName, collectRefLinks);
9021 if (!collectRefLinks) {
9022 parent->appendItem(bq);
9030template<
class Trait>
9033 long long int indent)
9037 if (p >= indent || p == s.size()) {
9043 if (p + 1 >= s.size()) {
9046 space = s[p + 1].isSpace();
9050 if (s[p] == Trait::latin1ToChar(
'*') && space) {
9052 }
else if (s[p] == Trait::latin1ToChar(
'-') && space) {
9054 }
else if (s[p] == Trait::latin1ToChar(
'+') && space) {
9064template<
class Trait>
9065inline std::pair<long long int, long long int>
9073template<
class Trait>
9074inline std::tuple<bool, long long int, typename Trait::Char, bool>
9080 if (p == s.size()) {
9081 return {
false, 0,
typename Trait::Char(),
false};
9086 if (p + 1 >= s.size()) {
9089 space = s[p + 1].isSpace();
9093 if (s[p] == Trait::latin1ToChar(
'*') && space) {
9094 return {
true, p + 2, Trait::latin1ToChar(
'*'),
9095 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9096 }
else if (s[p] == Trait::latin1ToChar(
'-')) {
9098 return {
false, p + 2, Trait::latin1ToChar(
'-'),
false};
9100 return {
true, p + 2, Trait::latin1ToChar(
'-'),
9101 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9103 }
else if (s[p] == Trait::latin1ToChar(
'+') && space) {
9104 return {
true, p + 2, Trait::latin1ToChar(
'+'),
9105 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9108 typename Trait::Char c;
9111 return {
true, p + l + 2, c,
9112 p + l + 2 < s.size() ? !s.sliced(p + l + 2).isEmpty() :
false};
9114 return {
false, 0,
typename Trait::Char(),
false};
9119 return {
false, 0,
typename Trait::Char(),
false};
9123template<
class Trait>
9129 item->setEndColumn(pos);
9130 item->setEndLine(line);
9134template<
class Trait>
9142 for (
auto &i : it->second) {
9143 i.first->setEndColumn(html.
m_html->endColumn());
9144 i.first->setEndLine(html.
m_html->endLine());
9150template<
class Trait>
9153 std::shared_ptr<Block<Trait>> parent,
9155 typename Trait::StringList &linksToParse,
9156 const typename Trait::String &workingPath,
9157 const typename Trait::String &fileName,
9158 bool collectRefLinks,
9159 RawHtmlBlock<Trait> &html)
9161 bool resetTopParent =
false;
9162 long long int line = -1;
9164 if (!html.m_topParent) {
9165 html.m_topParent = parent;
9166 resetTopParent =
true;
9169 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9171 if (p != fr.m_data.front().first.length()) {
9172 std::shared_ptr<List<Trait>>
list(
new List<Trait>);
9174 typename MdBlock<Trait>::Data listItem;
9175 auto it = fr.m_data.begin();
9176 listItem.push_back(*it);
9177 list->setStartColumn(it->first.virginPos(p));
9178 list->setStartLine(it->second.m_lineNumber);
9181 long long int indent = 0;
9182 typename Trait::Char marker;
9184 std::tie(std::ignore, indent, marker, std::ignore) =
9185 listItemData<Trait>(listItem.front().first.asString(),
false);
9187 html.m_blocks.push_back({
list,
list->startColumn() + indent});
9189 if (!collectRefLinks) {
9190 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
9193 bool updateIndent =
false;
9195 auto addListMakeNew = [&]() {
9197 parent->appendItem(list);
9200 html.m_blocks.pop_back();
9202 list.reset(
new List<Trait>);
9206 if (!collectRefLinks) {
9207 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
9211 auto processLastHtml = [&](std::shared_ptr<ListItem<Trait>> resItem) {
9212 if (html.m_html && resItem) {
9213 html.m_parent = (resItem->startLine() == html.m_html->startLine() ||
9214 html.m_html->startColumn() >= resItem->startColumn() + indent ?
9215 resItem : html.findParent(html.m_html->startColumn()));
9217 if (!html.m_parent) {
9218 html.m_parent = html.m_topParent;
9221 if (html.m_parent != resItem) {
9225 const auto continueHtml = html.m_onLine && html.m_continueHtml && html.m_parent == html.m_topParent;
9227 if (!collectRefLinks) {
9228 if (!continueHtml) {
9229 html.m_parent->appendItem(html.m_html);
9232 updateLastPosInList<Trait>(html);
9235 if (!continueHtml) {
9236 resetHtmlTag<Trait>(html);
9241 auto processListItem = [&]() {
9242 MdBlock<Trait> block = {listItem, 0};
9244 std::shared_ptr<ListItem<Trait>> resItem;
9246 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9247 collectRefLinks, html, &resItem);
9251 processLastHtml(resItem);
9252 }
else if (line >= 0) {
9257 for (
auto last = fr.m_data.end(); it != last; ++it) {
9259 std::tie(std::ignore, indent, marker, std::ignore) =
9260 listItemData<Trait>(it->first.asString(),
false);
9262 if (!collectRefLinks) {
9263 html.m_blocks.back().second = indent;
9266 updateIndent =
false;
9269 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9271 if (isH1<Trait>(it->first.asString().sliced(ns)) && ns < indent && !listItem.empty()) {
9272 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
9274 it->first.insert(p, Trait::latin1ToChar(
'\\'));
9275 }
else if (isHorizontalLine<Trait>(it->first.asString().sliced(ns)) &&
9276 ns < indent && !listItem.empty()) {
9277 updateIndent =
true;
9285 if (!collectRefLinks) {
9286 makeHorLine<Trait>(*it, parent);
9290 }
else if (isListItemAndNotNested<Trait>(it->first.asString(), indent) &&
9291 !listItem.empty() && !it->second.m_mayBreakList) {
9292 typename Trait::Char tmpMarker;
9293 std::tie(std::ignore, indent, tmpMarker, std::ignore) =
9294 listItemData<Trait>(it->first.asString(),
false);
9298 if (tmpMarker != marker) {
9311 listItem.push_back(*it);
9313 if (
list->startColumn() == -1) {
9314 list->setStartColumn(
9315 it->first.virginPos(std::min(it->first.length() ?
9316 it->first.length() - 1 : 0, skipSpaces<Trait>(0, it->first.asString()))));
9317 list->setStartLine(it->second.m_lineNumber);
9319 if (!collectRefLinks) {
9320 html.m_blocks.back().second +=
list->startColumn();
9325 if (!listItem.empty()) {
9326 MdBlock<Trait> block = {listItem, 0};
9327 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9328 collectRefLinks, html);
9332 parent->appendItem(list);
9335 html.m_blocks.pop_back();
9338 if (resetTopParent) {
9339 html.m_topParent.reset();
9345template<
class Trait>
9348 std::shared_ptr<Block<Trait>> parent,
9350 typename Trait::StringList &linksToParse,
9351 const typename Trait::String &workingPath,
9352 const typename Trait::String &fileName,
9353 bool collectRefLinks,
9354 RawHtmlBlock<Trait> &html,
9355 std::shared_ptr<ListItem<Trait>> *resItem)
9358 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
9359 return !s.first.isEmpty();
9362 if (it != fr.m_data.end()) {
9363 fr.m_data.erase(it, fr.m_data.end());
9367 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9369 std::shared_ptr<ListItem<Trait>> item(
new ListItem<Trait>);
9371 item->setStartColumn(fr.m_data.front().first.virginPos(p));
9372 item->setStartLine(fr.m_data.front().second.m_lineNumber);
9376 if (isOrderedList<Trait>(fr.m_data.front().first.asString(), &i, &len)) {
9377 item->setListType(ListItem<Trait>::Ordered);
9378 item->setStartNumber(i);
9379 item->setDelim({item->startColumn(), item->startLine(), item->startColumn() + len, item->startLine()});
9381 item->setListType(ListItem<Trait>::Unordered);
9382 item->setDelim({item->startColumn(), item->startLine(), item->startColumn(), item->startLine()});
9385 if (item->listType() == ListItem<Trait>::Ordered) {
9386 item->setOrderedListPreState(i == 1 ? ListItem<Trait>::Start : ListItem<Trait>::Continue);
9389 typename MdBlock<Trait>::Data data;
9391 auto it = fr.m_data.begin();
9396 long long int indent = 0;
9397 bool wasText =
false;
9399 std::tie(std::ignore, indent, std::ignore, wasText) =
9400 listItemData<Trait>(fr.m_data.front().first.asString(), wasText);
9402 html.m_blocks.push_back({item, item->startColumn() + indent});
9404 if (!collectRefLinks) {
9405 html.m_toAdjustLastPos.insert({item, html.m_blocks});
9408 const auto firstNonSpacePos = calculateIndent<Trait>(
9409 fr.m_data.front().first.asString(), indent).second;
9411 if (firstNonSpacePos - indent < 4) {
9412 indent = firstNonSpacePos;
9415 if (indent < fr.m_data.front().first.length()) {
9416 data.push_back({fr.m_data.front().first.right(fr.m_data.front().first.length() - indent),
9417 fr.m_data.front().second});
9420 bool taskList =
false;
9421 bool checked =
false;
9423 if (!data.empty()) {
9424 auto p = skipSpaces<Trait>(0, data.front().first.asString());
9426 if (p < data.front().first.length()) {
9427 if (data.front().first[p] == Trait::latin1ToChar(
'[')) {
9428 const auto startTaskDelimPos = data.front().first.virginPos(p);
9432 if (p < data.front().first.length()) {
9433 if (data.front().first[p] == Trait::latin1ToChar(
' ') ||
9434 data.front().first[p].toLower() == Trait::latin1ToChar(
'x')) {
9435 if (data.front().first[p].toLower() == Trait::latin1ToChar(
'x')) {
9441 if (p < data.front().first.length()) {
9442 if (data.front().first[p] == Trait::latin1ToChar(
']')) {
9443 item->setTaskDelim({startTaskDelimPos, item->startLine(), data.front().first.virginPos(p), item->startLine()});
9447 data[0].first = data[0].first.sliced(p + 1);
9457 item->setTaskList();
9458 item->setChecked(checked);
9461 bool fensedCode =
false;
9462 typename Trait::String startOfCode;
9463 bool wasEmptyLine =
false;
9465 std::vector<std::pair<RawHtmlBlock<Trait>,
long long int>> htmlToAdd;
9466 long long int line = -1;
9468 auto parseStream = [&](StringListStream<Trait> &stream) ->
long long int
9470 const auto tmpHtml = html;
9471 long long int line = -1;
9472 std::tie(html, line) =
parse(stream, item, doc, linksToParse, workingPath, fileName,
9473 collectRefLinks,
false,
true,
true);
9474 html.m_topParent = tmpHtml.m_topParent;
9475 html.m_blocks = tmpHtml.m_blocks;
9476 html.m_toAdjustLastPos = tmpHtml.m_toAdjustLastPos;
9481 auto processHtml = [&](
auto it) ->
bool
9483 if (html.m_html.get()) {
9484 html.m_parent = html.findParent(html.m_html->startColumn());
9486 if (!html.m_parent) {
9487 html.m_parent = html.m_topParent;
9492 if (html.m_continueHtml) {
9494 tmp.m_emptyLineAfter = fr.m_emptyLineAfter;
9495 std::copy(it, fr.m_data.end(), std::back_inserter(tmp.m_data));
9497 parseText(tmp, html.m_parent, doc, linksToParse, workingPath, fileName,
9498 collectRefLinks, html);
9503 htmlToAdd.push_back({html, html.m_parent->items().size()});
9504 updateLastPosInList<Trait>(html);
9505 resetHtmlTag<Trait>(html);
9511 if (!processHtml(std::prev(it))) {
9512 for (
auto last = fr.m_data.end(); it != last; ++it, ++pos) {
9514 fensedCode = isCodeFences<Trait>(it->first.asString().startsWith(
9515 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9516 it->first.asString().sliced(indent) : it->first.asString());
9519 startOfCode = startSequence<Trait>(it->first.asString());
9521 }
else if (fensedCode &&
9522 isCodeFences<Trait>(it->first.asString().startsWith(
9523 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9524 it->first.asString().sliced(indent) : it->first.asString(),
9525 true) && startSequence<Trait>(it->first.asString()).contains(startOfCode)) {
9530 long long int newIndent = 0;
9533 std::tie(ok, newIndent, std::ignore, wasText) = listItemData<Trait>(
9534 it->first.asString().startsWith(
typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9535 it->first.asString().sliced(indent) : it->first.asString(),
9538 if (ok && !it->second.m_mayBreakList) {
9539 StringListStream<Trait> stream(data);
9541 line = parseStream(stream);
9545 if (processHtml(it)) {
9553 if (!htmlToAdd.empty() && htmlToAdd.back().first.m_parent == html.m_topParent) {
9554 line = it->second.m_lineNumber;
9558 typename MdBlock<Trait>::Data nestedList;
9559 nestedList.push_back(*it);
9562 wasEmptyLine =
false;
9564 for (; it != last; ++it) {
9565 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9566 std::tie(ok, std::ignore, std::ignore, wasText) =
9567 listItemData<Trait>((ns >= indent ? it->first.asString().sliced(indent) :
9568 it->first.asString()), wasText);
9571 wasEmptyLine =
false;
9574 if (ok || ns >= indent + newIndent || ns == it->first.length() || !wasEmptyLine) {
9575 nestedList.push_back(*it);
9580 wasEmptyLine = (ns == it->first.length());
9582 wasText = (wasEmptyLine ? false : wasText);
9585 for (
auto it = nestedList.begin(), last = nestedList.end(); it != last; ++it) {
9586 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9588 if (ns < indent && ns != it->first.length()) {
9589 it->second.m_mayBreakList =
true;
9591 it->first = it->first.sliced(std::min(ns, indent));
9595 while (!nestedList.empty() &&
9596 nestedList.back().first.asString().isEmpty()) {
9597 nestedList.pop_back();
9600 MdBlock<Trait> block = {nestedList, 0};
9602 line = parseList(block, item, doc, linksToParse, workingPath, fileName,
9603 collectRefLinks, html);
9609 for (; it != last; ++it) {
9610 if (it->first.asString().startsWith(
typename Trait::String(
9611 indent, Trait::latin1ToChar(
' ')))) {
9612 it->first = it->first.sliced(indent);
9615 data.push_back(*it);
9621 if (!it->second.m_mayBreakList &&
9622 it->first.asString().startsWith(
typename Trait::String(
9623 indent, Trait::latin1ToChar(
' ')))) {
9624 it->first = it->first.sliced(indent);
9627 data.push_back(*it);
9629 wasEmptyLine = (skipSpaces<Trait>(0, it->first.asString()) == it->first.length());
9631 wasText = !wasEmptyLine;
9634 if (!it->second.m_mayBreakList &&
9635 it->first.asString().startsWith(
typename Trait::String(
9636 indent, Trait::latin1ToChar(
' ')))) {
9637 it->first = it->first.sliced(indent);
9640 data.push_back(*it);
9644 if (!data.empty()) {
9645 StringListStream<Trait> stream(data);
9647 line = parseStream(stream);
9650 html.m_parent = html.findParent(html.m_html->startColumn());
9652 if (!html.m_parent) {
9653 html.m_parent = html.m_topParent;
9661 if (!collectRefLinks) {
9663 parent->appendItem(item);
9666 long long int i = 0;
9668 for (
auto &h : htmlToAdd) {
9669 if (h.first.m_parent != h.first.m_topParent) {
9670 h.first.m_parent->insertItem(h.second + i, h.first.m_html);
9683 long long int htmlStartColumn = -1;
9684 long long int htmlStartLine = -1;
9687 std::tie(htmlStartColumn, htmlStartLine) =
9688 localPosFromVirgin<Trait>(fr, html.m_html->startColumn(), html.m_html->startLine());
9691 long long int localLine = (html.m_html ? htmlStartLine : fr.m_data.size() - 1);
9694 if (skipSpaces<Trait>(0, fr.m_data[localLine].first.asString()) >= htmlStartColumn) {
9699 const auto lastLine = fr.m_data[localLine].second.m_lineNumber;
9701 const auto lastColumn = fr.m_data[localLine].first.virginPos(
9702 fr.m_data[localLine].first.length() ? fr.m_data[localLine].first.length() - 1 : 0);
9704 item->setEndColumn(lastColumn);
9705 item->setEndLine(lastLine);
9706 parent->setEndColumn(lastColumn);
9707 parent->setEndLine(lastLine);
9715 html.m_blocks.pop_back();
9720template<
class Trait>
9723 std::shared_ptr<Block<Trait>> parent,
9724 bool collectRefLinks)
9726 const auto indent = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9728 if (indent != fr.m_data.front().first.length()) {
9729 WithPosition startDelim, endDelim, syntaxPos;
9730 typename Trait::String syntax;
9731 isStartOfCode<Trait>(fr.m_data.front().first.asString(), &syntax, &startDelim, &syntaxPos);
9732 syntax = replaceEntity<Trait>(syntax);
9733 startDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
9734 startDelim.setEndLine(startDelim.startLine());
9735 startDelim.setStartColumn(fr.m_data.front().first.virginPos(startDelim.startColumn()));
9736 startDelim.setEndColumn(fr.m_data.front().first.virginPos(startDelim.endColumn()));
9738 if (syntaxPos.startColumn() != -1) {
9739 syntaxPos.setStartLine(startDelim.startLine());
9740 syntaxPos.setEndLine(startDelim.startLine());
9741 syntaxPos.setStartColumn(fr.m_data.front().first.virginPos(syntaxPos.startColumn()));
9742 syntaxPos.setEndColumn(fr.m_data.front().first.virginPos(syntaxPos.endColumn()));
9745 const long long int startPos = fr.m_data.front().first.virginPos(indent);
9746 const long long int emptyColumn = fr.m_data.front().first.virginPos(fr.m_data.front().first.length());
9747 const long long int startLine = fr.m_data.front().second.m_lineNumber;
9748 const long long int endPos = fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1);
9749 const long long int endLine = fr.m_data.back().second.m_lineNumber;
9751 fr.m_data.erase(fr.m_data.cbegin());
9754 const auto it = std::prev(fr.m_data.cend());
9756 if (it->second.m_lineNumber > -1) {
9757 endDelim.setStartColumn(it->first.virginPos(skipSpaces<Trait>(0, it->first.asString())));
9758 endDelim.setStartLine(it->second.m_lineNumber);
9759 endDelim.setEndLine(endDelim.startLine());
9760 endDelim.setEndColumn(it->first.virginPos(it->first.length() - 1));
9763 fr.m_data.erase(it);
9766 if (syntax.toLower() == Trait::latin1ToString(
"math")) {
9767 typename Trait::String math;
9770 for (
const auto &l : std::as_const(fr.m_data)) {
9772 math.push_back(Trait::latin1ToChar(
'\n'));
9775 math.push_back(l.first.virginSubString());
9780 if (!collectRefLinks) {
9781 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
9782 p->setStartColumn(startPos);
9783 p->setStartLine(startLine);
9784 p->setEndColumn(endPos);
9785 p->setEndLine(endLine);
9787 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
9789 if (!fr.m_data.empty()) {
9790 m->setStartColumn(fr.m_data.front().first.virginPos(0));
9791 m->setStartLine(fr.m_data.front().second.m_lineNumber);
9792 m->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
9793 m->setEndLine(fr.m_data.back().second.m_lineNumber);
9795 m->setStartColumn(emptyColumn);
9796 m->setStartLine(startLine);
9797 m->setEndColumn(emptyColumn);
9798 m->setEndLine(startLine);
9801 m->setInline(
false);
9803 m->setStartDelim(startDelim);
9804 m->setEndDelim(endDelim);
9805 m->setSyntaxPos(syntaxPos);
9806 m->setFensedCode(
true);
9809 parent->appendItem(p);
9812 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, syntax, emptyColumn,
9813 startLine,
true, startDelim, endDelim, syntaxPos);
9820template<
class Trait>
9823 std::shared_ptr<Block<Trait>> parent,
9824 bool collectRefLinks,
9826 const typename Trait::String &syntax,
9827 long long int emptyColumn,
9828 long long int startLine,
9830 const WithPosition &startDelim,
9831 const WithPosition &endDelim,
9832 const WithPosition &syntaxPos)
9834 typename Trait::String code;
9835 long long int startPos = 0;
9838 auto it = fr.m_data.begin(), lastIt = fr.m_data.end();
9840 for (; it != lastIt; ++it) {
9841 if (it->second.m_mayBreakList) {
9846 if (!collectRefLinks) {
9847 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9853 code.push_back((indent > 0 ? it->first.virginSubString(ns < indent ? ns : indent) +
9854 typename Trait::String(Trait::latin1ToChar(
'\n')) :
9855 typename Trait::String(it->first.virginSubString()) +
9856 typename Trait::String(Trait::latin1ToChar(
'\n'))));
9860 if (!collectRefLinks) {
9861 if (!code.isEmpty()) {
9862 code.remove(code.length() - 1, 1);
9865 std::shared_ptr<Code<Trait>> codeItem(
new Code<Trait>(code, fensedCode,
false));
9866 codeItem->setSyntax(syntax);
9867 codeItem->setStartDelim(startDelim);
9868 codeItem->setEndDelim(endDelim);
9869 codeItem->setSyntaxPos(syntaxPos);
9871 if (lastIt != fr.m_data.end() || (it == fr.m_data.end() && !fr.m_data.empty())) {
9872 codeItem->setStartColumn(fr.m_data.front().first.virginPos(startPos));
9873 codeItem->setStartLine(fr.m_data.front().second.m_lineNumber);
9874 auto tmp = std::prev(lastIt);
9875 codeItem->setEndColumn(tmp->first.virginPos(tmp->first.length() - 1));
9876 codeItem->setEndLine(tmp->second.m_lineNumber);
9878 codeItem->setStartColumn(emptyColumn);
9879 codeItem->setStartLine(startLine);
9880 codeItem->setEndColumn(emptyColumn);
9881 codeItem->setEndLine(startLine);
9885 parent->appendItem(codeItem);
9886 }
else if (!parent->items().empty() && parent->items().back()->type() == ItemType::Code) {
9887 auto c = std::static_pointer_cast<Code<Trait>>(parent->items().
back());
9889 if (!c->isFensedCode()) {
9890 auto line = c->endLine();
9891 auto text = c->text();
9893 for (; line < codeItem->startLine(); ++line) {
9894 text.push_back(Trait::latin1ToString(
"\n"));
9897 text.push_back(codeItem->text());
9899 c->setEndColumn(codeItem->endColumn());
9900 c->setEndLine(codeItem->endLine());
9902 parent->appendItem(codeItem);
9905 parent->appendItem(codeItem);
9909 if (lastIt != fr.m_data.end()) {
9910 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 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()
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)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem back(BidiMode useBidi=IgnoreRTL)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
@ 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.
void resetHtmlTag(RawHtmlBlock< Trait > &html)
Reset pre-stored HTML.
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.
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)
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 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 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.
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.