6#ifndef MD4QT_MD_PARSER_HPP_INCLUDED
7#define MD4QT_MD_PARSER_HPP_INCLUDED
15#ifdef MD4QT_QT_SUPPORT
24#ifdef MD4QT_ICU_STL_SUPPORT
40#include <unordered_map>
53 bool codeIndentedBySpaces)
55 if (indents && !indents->empty()) {
56 return (std::find_if(indents->cbegin(),
58 [indent, codeIndentedBySpaces](
const auto &v) {
59 return (indent >= v && (codeIndentedBySpaces ?
60 true : indent <= v + 3));
71skipSpaces(
long long int i,
const typename Trait::String &line)
73 const auto length = line.length();
75 while (i < length && line[i].isSpace()) {
87 long long int i = line.length() - 1;
89 while (i >= 0 && line[i].isSpace()) {
103 if (i != s.length() - 1) {
104 s.remove(i + 1, s.length() - i - 1);
110inline typename Trait::String
115 if (pos >= line.length()) {
119 const auto sch = line[pos];
120 const auto start = pos;
124 while (pos < line.length() && line[pos] == sch) {
137 typename Trait::Char *delim =
nullptr,
138 bool *isFirstLineEmpty =
nullptr)
142 long long int dp = p;
144 for (; p < s.size(); ++p) {
145 if (!s[p].isDigit()) {
150 if (dp != p && p < s.size()) {
151 const auto digits = s.sliced(dp, p - dp);
153 if (digits.size() > 9) {
157 const auto i = digits.toInt();
167 if (s[p] == Trait::latin1ToChar(
'.') || s[p] == Trait::latin1ToChar(
')')) {
176 if (isFirstLineEmpty) {
177 *isFirstLineEmpty = (tmp == s.size());
180 if ((p < s.size() && s[p] == Trait::latin1ToChar(
' ')) || p == s.size()) {
196 std::shared_ptr<RawHtml<Trait>>
m_html = {};
199 using SequenceOfBlock = std::vector<std::pair<std::shared_ptr<Block<Trait>>,
long long int>>;
206 std::shared_ptr<Block<Trait>>
209 for (
auto it =
m_blocks.crbegin(), last =
m_blocks.crend(); it != last; ++it) {
210 if (indent >= it->second) {
241 using Line = std::pair<typename Trait::InternalString, MdLineData>;
242 using Data =
typename Trait::template Vector<Line>;
254 long long int count = 0;
257 while (it != begin) {
260 if (it->first.asString().simplified().isEmpty()) {
288 return (m_pos >= (
long long int)m_stream.size());
291 std::pair<typename Trait::InternalString, bool>
readLine()
293 const std::pair<typename Trait::InternalString, bool> ret =
294 {m_stream.at(m_pos).first, m_stream.at(m_pos).second.m_mayBreakList};
303 return (m_pos <
size() ? m_stream.at(m_pos).second.m_lineNumber :
304 (
size() > 0 ? m_stream.at(0).second.m_lineNumber +
size() : -1));
312 typename Trait::InternalString
lineAt(
long long int pos)
314 return m_stream.at(pos).first;
319 return m_stream.size();
341 if (s.size() - p < 5) {
345 if (s[p++] != Trait::latin1ToChar(
'[')) {
349 if (s[p++] != Trait::latin1ToChar(
'^')) {
353 if (s[p] == Trait::latin1ToChar(
']') || s[p].isSpace()) {
357 for (; p < s.size(); ++p) {
358 if (s[p] == Trait::latin1ToChar(
']')) {
360 }
else if (s[p].isSpace()) {
367 if (p < s.size() && s[p] == Trait::latin1ToChar(
':')) {
381 if (p > 3 || p == s.length()) {
385 const auto ch = s[p];
387 if (ch != Trait::latin1ToChar(
'~') && ch != Trait::latin1ToChar(
'`')) {
396 for (; p < s.length(); ++p) {
397 if (s[p].isSpace()) {
399 }
else if (s[p] == ch) {
400 if (space && (closing ?
true : ch == Trait::latin1ToChar(
'`'))) {
407 }
else if (closing) {
418 if (ch == Trait::latin1ToChar(
'`')) {
419 for (; p < s.length(); ++p) {
420 if (s[p] == Trait::latin1ToChar(
'`')) {
431inline typename Trait::String
433 const typename Trait::String &str,
434 long long int *endPos =
nullptr)
436 bool backslash =
false;
437 const auto start = i;
439 if (
start >= str.length()) {
443 while (i < str.length()) {
446 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
449 }
else if (str[i].isSpace() && !backslash) {
470 Trait::latin1ToString(
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
473template<
class String,
class Trait>
478 bool backslash =
false;
479 long long int extra = 0;
481 for (
long long int i = 0; i < s.length(); ++i) {
484 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash && i != s.length() - 1) {
488 r.remove(i - extra - 1, 1);
504 typename Trait::String *syntax =
nullptr,
511 delim->setStartColumn(p);
518 if (str.size() - p < 3) {
522 const bool c96 = str[p] == Trait::latin1ToChar(
'`');
523 const bool c126 = str[p] == Trait::latin1ToChar(
'~');
529 while (p < str.length()) {
530 if (str[p] != (c96 ? Trait::latin1ToChar(
'`') : Trait::latin1ToChar(
'~'))) {
539 delim->setEndColumn(p - 1);
548 long long int endSyntaxPos = p;
550 if (p < str.size()) {
555 syntaxPos->setStartColumn(p);
556 syntaxPos->setEndColumn(endSyntaxPos);
576 typename Trait::Char c;
578 if (s[0] == Trait::latin1ToChar(
'*')) {
579 c = Trait::latin1ToChar(
'*');
580 }
else if (s[0] == Trait::latin1ToChar(
'-')) {
581 c = Trait::latin1ToChar(
'-');
582 }
else if (s[0] == Trait::latin1ToChar(
'_')) {
583 c = Trait::latin1ToChar(
'_');
589 long long int count = 1;
591 for (; p < s.size(); ++p) {
592 if (s[p] != c && !s[p].isSpace()) {
594 }
else if (s[p] == c) {
617 static const typename Trait::String s_legitime = Trait::latin1ToString(
":-");
619 if (p >= s.length()) {
623 if (!s_legitime.contains(s[p])) {
627 if (s[p] == Trait::latin1ToChar(
':')) {
631 for (; p < s.size(); ++p) {
632 if (s[p] != Trait::latin1ToChar(
'-')) {
641 if (s[p] != Trait::latin1ToChar(
':') && !s[p].isSpace()) {
647 for (; p < s.size(); ++p) {
648 if (!s[p].isSpace()) {
658typename Trait::StringList
659splitString(
const typename Trait::String &str,
const typename Trait::Char &ch);
661#ifdef MD4QT_ICU_STL_SUPPORT
667 return str.
split(ch);
672#ifdef MD4QT_QT_SUPPORT
690 for (
const auto &c : columns) {
696 return columns.size();
712 long long int p = -1;
713 bool endFound =
false;
715 while ((p = c.indexOf(Trait::latin1ToString(
"--"), p + 1)) > -1) {
716 if (c.size() > p + 2 && c[p + 2] == Trait::latin1ToChar(
'>')) {
722 }
else if (p - 2 >= 0 && c.sliced(p - 2, 4) == Trait::latin1ToString(
"<!--")) {
724 }
else if (c.size() > p + 3 && c.sliced(p, 4) == Trait::latin1ToString(
"--!>")) {
734inline typename Trait::String
737 long long int p1 = 0;
739 typename Trait::String res;
742 while ((p1 = s.indexOf(Trait::latin1ToChar(
'&'), p1)) != -1) {
743 if (p1 > 0 && s[p1 - 1] == Trait::latin1ToChar(
'\\')) {
749 const auto p2 = s.indexOf(Trait::latin1ToChar(
';'), p1);
752 const auto en = s.sliced(p1, p2 - p1 + 1);
754 if (en.size() > 2 && en[1] == Trait::latin1ToChar(
'#')) {
755 if (en.size() > 3 && en[2].toLower() == Trait::latin1ToChar(
'x')) {
756 const auto hex = en.sliced(3, en.size() - 4);
758 if (hex.size() <= 6 && hex.size() > 0) {
761 const char32_t c = hex.toInt(&ok, 16);
764 res.push_back(s.sliced(i, p1 - i));
768 Trait::appendUcs4(res, c);
770 res.push_back(
typename Trait::Char(0xFFFD));
775 const auto dec = en.sliced(2, en.size() - 3);
777 if (dec.size() <= 7 && dec.size() > 0) {
780 const char32_t c = dec.toInt(&ok, 10);
783 res.push_back(s.sliced(i, p1 - i));
787 Trait::appendUcs4(res, c);
789 res.push_back(
typename Trait::Char(0xFFFD));
798 res.push_back(s.sliced(i, p1 - i));
800 res.push_back(Trait::utf16ToString(it->second));
810 res.push_back(s.sliced(i, s.size() - i));
822 for (
auto &line : tmp) {
902struct TextParsingOpts;
908 const typename Trait::StringList &)>;
918 typename Trait::StringList>>;
930 std::shared_ptr<Document<Trait>>
m_doc;
957 for (
auto i =
start + 1; i < end; ++i) {
1005template<
class Trait>
1024template<
class Trait>
1025inline typename Trait::String
1032 long long int startLine = virginPos.
startLine() < fr.
m_data.at(0).second.m_lineNumber ?
1033 (virginPos.
endLine() < fr.
m_data.at(0).second.m_lineNumber ? -1 : 0) :
1036 if (startLine >=
static_cast<long long int>(fr.
m_data.size()) || startLine < 0) {
1040 auto spos = virginPos.
startColumn() - fr.
m_data.at(startLine).first.virginPos(0);
1046 long long int epos = 0;
1051 if (startLine + linesCount >
static_cast<long long int>(fr.
m_data.size())) {
1052 linesCount = fr.
m_data.size() - startLine - 1;
1053 epos = fr.
m_data.back().first.length();
1055 epos = virginPos.
endColumn() - fr.
m_data.at(linesCount + startLine).first.virginPos(0) + 1;
1062 if (epos > fr.
m_data.at(linesCount + startLine).first.length()) {
1063 epos = fr.
m_data.at(linesCount + startLine).first.length();
1066 typename Trait::String str =
1067 (linesCount ? fr.
m_data.at(startLine).first.sliced(spos).asString() :
1068 fr.
m_data.at(startLine).first.sliced(spos, epos - spos).asString());
1070 long long int i = startLine + 1;
1072 for (; i < startLine + linesCount; ++i) {
1073 str.push_back(Trait::latin1ToString(
"\n"));
1074 str.push_back(fr.
m_data.at(i).first.asString());
1078 str.push_back(Trait::latin1ToString(
"\n"));
1079 str.push_back(fr.
m_data.at(i).first.sliced(0, epos).asString());
1091template<
class Trait>
1092inline std::pair<long long int, long long int>
1099 if (fr.
m_data.front().second.m_lineNumber > virginLine ||
1100 fr.
m_data.back().second.m_lineNumber < virginLine) {
1104 auto line = virginLine - fr.
m_data.front().second.m_lineNumber;
1106 if (fr.
m_data.at(line).first.isEmpty()) {
1110 const auto vzpos = fr.
m_data.at(line).first.virginPos(0);
1112 if (vzpos > virginColumn || virginColumn > vzpos + fr.
m_data.at(line).first.length() - 1) {
1116 return {virginColumn - vzpos, line};
1128template<
class Trait>
1132 auto isAllowed = [](
const typename Trait::Char &ch) ->
bool {
1133 const auto unicode = ch.unicode();
1134 return ((unicode >= 48 && unicode <= 57) || (unicode >= 97 && unicode <= 122) ||
1135 (unicode >= 65 && unicode <= 90));
1138 auto isAdditional = [](
const typename Trait::Char &ch) ->
bool {
1139 const auto unicode = ch.unicode();
1140 return (unicode == 33 || (unicode >= 35 && unicode <= 39) ||
1141 unicode == 42 || unicode == 43 || (unicode >= 45 && unicode <= 47) ||
1142 unicode == 61 || unicode == 63 || (unicode >= 94 && unicode <= 96) ||
1143 (unicode >= 123 && unicode <= 126));
1146 static const auto s_delim = Trait::latin1ToChar(
'-');
1147 static const auto s_dog = Trait::latin1ToChar(
'@');
1148 static const auto s_dot = Trait::latin1ToChar(
'.');
1150 long long int i = (url.startsWith(Trait::latin1ToString(
"mailto:")) ? 7 : 0);
1151 const auto dogPos = url.indexOf(s_dog, i);
1158 for (; i < dogPos; ++i) {
1159 if (!isAllowed(url[i]) && !isAdditional(url[i])) {
1164 auto checkToDot = [&](
long long int start,
long long int dotPos) ->
bool {
1165 static const long long int maxlen = 63;
1167 if (dotPos -
start > maxlen ||
1168 start + 1 > dotPos ||
1169 start >= url.length() ||
1170 dotPos > url.length()) {
1174 if (url[
start] == s_delim) {
1178 if (url[dotPos - 1] == s_delim) {
1183 if (!isAllowed(url[
start]) && url[
start] != s_delim) {
1191 long long int dotPos = url.indexOf(s_dot, dogPos + 1);
1196 while (dotPos != -1) {
1197 if (!checkToDot(i, dotPos)) {
1202 dotPos = url.indexOf(s_dot, i);
1205 if (!checkToDot(i, url.length())) {
1217template<
class Trait>
1222template<
class Trait>
1226#ifdef MD4QT_QT_SUPPORT
1251#ifdef MD4QT_ICU_STL_SUPPORT
1269 && ((!u.
scheme().isEmpty() && !u.
host().isEmpty())
1270 || (url.startsWith(
UnicodeString(
"www.")) && url.length() >= 7 &&
1277template<
class Trait>
1283 if (idx < 0 || idx >= (
long long int)po.
m_rawTextData.size()) {
1287 static const auto s_delims = Trait::latin1ToString(
"*_~()<>");
1290 long long int j = 0;
1291 auto end =
typename Trait::Char(0x00);
1292 bool skipSpace =
true;
1293 long long int ret = idx;
1295 while (s.m_str.length()) {
1296 long long int i = 0;
1297 end =
typename Trait::Char(0x00);
1299 for (; i < s.m_str.length(); ++i) {
1301 if (s.m_str[i] == Trait::latin1ToChar(
'(')) {
1302 end = Trait::latin1ToChar(
')');
1305 if (s_delims.indexOf(s.m_str[i]) == -1 && !s.m_str[i].isSpace()) {
1310 if (s.m_str[i].isSpace() || i == s.m_str.length() - 1 || s.m_str[i] == end) {
1311 auto tmp = s.m_str.sliced(j, i - j +
1312 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1314 skipSpace = s.m_str[i].isSpace();
1321 if (ti >= 0 && ti <
static_cast<long long int>(p->items().size())) {
1323 const auto opts = std::static_pointer_cast<Text<Trait>>(p->items().at(ti))->opts();
1325 if (j == 0 || s.m_str.sliced(0, j).isEmpty()) {
1326 openStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->
openStyles();
1327 closeStyles = std::static_pointer_cast<ItemWithOpts<Trait>>(p->items().at(ti))->
closeStyles();
1328 p->removeItemAt(ti);
1332 const auto tmp = s.m_str.sliced(0, j);
1334 auto t = std::static_pointer_cast<Text<Trait>>(p->items().at(ti));
1335 t->setEndColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j - 1));
1337 t->closeStyles() = {};
1344 std::shared_ptr<Link<Trait>> lnk(
new Link<Trait>);
1345 lnk->setStartColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + j));
1346 lnk->setStartLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1348 po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + i -
1349 (i == s.m_str.length() - 1 && s.m_str[i] != end && !s.m_str[i].isSpace() ?
1351 lnk->setEndLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1352 lnk->openStyles() = openStyles;
1353 lnk->setTextPos({lnk->startColumn(), lnk->startLine(), lnk->endColumn(), lnk->endLine()});
1354 lnk->setUrlPos(lnk->textPos());
1356 if (email && !tmp.toLower().startsWith(Trait::latin1ToString(
"mailto:"))) {
1357 tmp = Trait::latin1ToString(
"mailto:") + tmp;
1360 if (!email && tmp.toLower().startsWith(Trait::latin1ToString(
"www."))) {
1361 tmp = Trait::latin1ToString(
"http://") + tmp;
1366 p->insertItem(ti, lnk);
1368 s.m_pos += i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1);
1369 s.m_str.remove(0, i + (s.m_str[i] == end || s.m_str[i].isSpace() ? 0 : 1));
1373 if (!s.m_str.isEmpty()) {
1377 auto t = std::make_shared<Text<Trait>>();
1378 t->setStartColumn(po.
m_fr.m_data[s.m_line].first.virginPos(s.m_pos));
1379 t->setStartLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1380 t->setEndLine(po.
m_fr.m_data.at(s.m_line).second.m_lineNumber);
1381 t->setEndColumn(po.
m_fr.m_data.at(s.m_line).first.virginPos(s.m_pos + s.m_str.length() - 1));
1383 t->closeStyles() = closeStyles;
1384 p->insertItem(ti + 1, t);
1386 lnk->closeStyles() = closeStyles;
1393 j = i + (skipSpace ? 1 : 0);
1400 if (i == s.m_str.length()) {
1409template<
class Trait>
1413 const typename Trait::StringList &)
1416 long long int i = 0;
1418 while (i >= 0 && i < (
long long int)po.
m_rawTextData.size()) {
1431template<
class Trait>
1443 std::shared_ptr<Document<Trait>>
1446 const typename Trait::String &fileName,
1449 bool recursive =
true,
1452 const typename Trait::StringList &ext = {Trait::latin1ToString(
"md"), Trait::latin1ToString(
"markdown")},
1458 bool fullyOptimizeParagraphs =
true);
1461 std::shared_ptr<Document<Trait>>
1464 typename Trait::TextStream &stream,
1467 const typename Trait::String &path,
1469 const typename Trait::String &fileName,
1475 bool fullyOptimizeParagraphs =
true);
1485 bool processInLinks,
1487 const typename Trait::StringList &userData)
1489 m_textPlugins.insert({id, {plugin, processInLinks, userData}});
1498 m_textPlugins.erase(
id);
1503 parseFile(
const typename Trait::String &fileName,
1506 const typename Trait::StringList &ext,
1507 typename Trait::StringList *parentLinks =
nullptr);
1510 parseStream(
typename Trait::TextStream &stream,
1511 const typename Trait::String &workingPath,
1512 const typename Trait::String &fileName,
1515 const typename Trait::StringList &ext,
1516 typename Trait::StringList *parentLinks =
nullptr);
1521 enum class BlockType {
1526 ListWithFirstEmptyLine,
1527 CodeIndentedBySpaces,
1537 long long int m_level = -1;
1538 long long int m_indent = -1;
1542 whatIsTheLine(
typename Trait::InternalString &str,
1543 bool inList =
false,
1544 bool inListWithFirstEmptyLine =
false,
1545 bool fensedCodeInList =
false,
1546 typename Trait::String *startOfCode =
nullptr,
1547 ListIndent *indent =
nullptr,
1548 bool emptyLinePreceded =
false,
1549 bool calcIndent =
false,
1550 const std::vector<long long int> *indents =
nullptr);
1553 parseFragment(MdBlock<Trait> &fr,
1554 std::shared_ptr<Block<Trait>> parent,
1556 typename Trait::StringList &linksToParse,
1557 const typename Trait::String &workingPath,
1558 const typename Trait::String &fileName,
1559 bool collectRefLinks,
1560 RawHtmlBlock<Trait> &html);
1563 parseText(MdBlock<Trait> &fr,
1564 std::shared_ptr<Block<Trait>> parent,
1566 typename Trait::StringList &linksToParse,
1567 const typename Trait::String &workingPath,
1568 const typename Trait::String &fileName,
1569 bool collectRefLinks,
1570 RawHtmlBlock<Trait> &html);
1573 parseBlockquote(MdBlock<Trait> &fr,
1574 std::shared_ptr<Block<Trait>> parent,
1576 typename Trait::StringList &linksToParse,
1577 const typename Trait::String &workingPath,
1578 const typename Trait::String &fileName,
1579 bool collectRefLinks,
1580 RawHtmlBlock<Trait> &html);
1583 parseList(MdBlock<Trait> &fr,
1584 std::shared_ptr<Block<Trait>> parent,
1586 typename Trait::StringList &linksToParse,
1587 const typename Trait::String &workingPath,
1588 const typename Trait::String &fileName,
1589 bool collectRefLinks,
1590 RawHtmlBlock<Trait> &html);
1593 parseCode(MdBlock<Trait> &fr,
1594 std::shared_ptr<Block<Trait>> parent,
1595 bool collectRefLinks);
1598 parseCodeIndentedBySpaces(MdBlock<Trait> &fr,
1599 std::shared_ptr<Block<Trait>> parent,
1600 bool collectRefLinks,
1602 const typename Trait::String &syntax,
1603 long long int emptyColumn,
1604 long long int startLine,
1606 const WithPosition &startDelim = {},
1607 const WithPosition &endDelim = {},
1608 const WithPosition &syntaxPos = {});
1611 parseListItem(MdBlock<Trait> &fr,
1612 std::shared_ptr<Block<Trait>> parent,
1614 typename Trait::StringList &linksToParse,
1615 const typename Trait::String &workingPath,
1616 const typename Trait::String &fileName,
1617 bool collectRefLinks,
1618 RawHtmlBlock<Trait> &html,
1622 parseHeading(MdBlock<Trait> &fr,
1623 std::shared_ptr<Block<Trait>> parent,
1625 typename Trait::StringList &linksToParse,
1626 const typename Trait::String &workingPath,
1627 const typename Trait::String &fileName,
1628 bool collectRefLinks);
1631 parseFootnote(MdBlock<Trait> &fr,
1632 std::shared_ptr<Block<Trait>> parent,
1634 typename Trait::StringList &linksToParse,
1635 const typename Trait::String &workingPath,
1636 const typename Trait::String &fileName,
1637 bool collectRefLinks);
1640 parseTable(MdBlock<Trait> &fr,
1641 std::shared_ptr<Block<Trait>> parent,
1643 typename Trait::StringList &linksToParse,
1644 const typename Trait::String &workingPath,
1645 const typename Trait::String &fileName,
1646 bool collectRefLinks,
1650 parseParagraph(MdBlock<Trait> &fr,
1651 std::shared_ptr<Block<Trait>> parent,
1653 typename Trait::StringList &linksToParse,
1654 const typename Trait::String &workingPath,
1655 const typename Trait::String &fileName,
1656 bool collectRefLinks,
1657 RawHtmlBlock<Trait> &html);
1660 parseFormattedTextLinksImages(MdBlock<Trait> &fr,
1661 std::shared_ptr<Block<Trait>> parent,
1663 typename Trait::StringList &linksToParse,
1664 const typename Trait::String &workingPath,
1665 const typename Trait::String &fileName,
1666 bool collectRefLinks,
1667 bool ignoreLineBreak,
1668 RawHtmlBlock<Trait> &html,
1671 struct ParserContext {
1672 typename Trait::template Vector<MdBlock<Trait>> m_splitted;
1674 bool m_emptyLineInList =
false;
1675 bool m_fensedCodeInList =
false;
1676 long long int m_emptyLinesCount = 0;
1677 long long int m_lineCounter = 0;
1678 std::vector<long long int> m_indents;
1679 ListIndent m_indent;
1680 RawHtmlBlock<Trait> m_html;
1681 long long int m_emptyLinesBefore = 0;
1683 typename Trait::String m_startOfCode;
1684 typename Trait::String m_startOfCodeInList;
1685 BlockType m_type = BlockType::EmptyLine;
1686 BlockType m_lineType = BlockType::Unknown;
1687 BlockType m_prevLineType = BlockType::Unknown;
1690 std::pair<long long int, bool>
1691 parseFirstStep(ParserContext &ctx,
1692 StringListStream<Trait> &stream,
1693 std::shared_ptr<Block<Trait>> parent,
1695 typename Trait::StringList &linksToParse,
1696 const typename Trait::String &workingPath,
1697 const typename Trait::String &fileName,
1698 bool collectRefLinks);
1701 parseSecondStep(ParserContext &ctx,
1702 std::shared_ptr<Block<Trait>> parent,
1704 typename Trait::StringList &linksToParse,
1705 const typename Trait::String &workingPath,
1706 const typename Trait::String &fileName,
1707 bool collectRefLinks,
1709 bool dontProcessLastFreeHtml);
1711 std::pair<RawHtmlBlock<Trait>,
long long int>
1712 parse(StringListStream<Trait> &stream,
1713 std::shared_ptr<Block<Trait>> parent,
1715 typename Trait::StringList &linksToParse,
1716 const typename Trait::String &workingPath,
1717 const typename Trait::String &fileName,
1718 bool collectRefLinks,
1720 bool dontProcessLastFreeHtml =
false,
1721 bool stopOnMayBreakList =
false);
1723 std::pair<long long int, bool>
1724 parseFragment(ParserContext &ctx,
1725 std::shared_ptr<Block<Trait>> parent,
1727 typename Trait::StringList &linksToParse,
1728 const typename Trait::String &workingPath,
1729 const typename Trait::String &fileName,
1730 bool collectRefLinks);
1733 eatFootnote(ParserContext &ctx,
1734 StringListStream<Trait> &stream,
1735 std::shared_ptr<Block<Trait>> parent,
1737 typename Trait::StringList &linksToParse,
1738 const typename Trait::String &workingPath,
1739 const typename Trait::String &fileName,
1740 bool collectRefLinks);
1743 finishHtml(ParserContext &ctx,
1744 std::shared_ptr<Block<Trait>> parent,
1746 bool collectRefLinks,
1748 bool dontProcessLastFreeHtml);
1751 makeLineMain(ParserContext &ctx,
1752 const typename Trait::InternalString &line,
1753 long long int emptyLinesCount,
1754 const ListIndent ¤tIndent,
1756 long long int currentLineNumber);
1758 std::pair<long long int, bool>
1759 parseFragmentAndMakeNextLineMain(ParserContext &ctx,
1760 std::shared_ptr<Block<Trait>> parent,
1762 typename Trait::StringList &linksToParse,
1763 const typename Trait::String &workingPath,
1764 const typename Trait::String &fileName,
1765 bool collectRefLinks,
1766 const typename Trait::InternalString &line,
1767 const ListIndent ¤tIndent,
1769 long long int currentLineNumber);
1772 isListType(BlockType t);
1774 std::pair<typename Trait::InternalString, bool>
1775 readLine(ParserContext &ctx, StringListStream<Trait> &stream);
1777 std::shared_ptr<Image<Trait>>
1778 makeImage(
const typename Trait::String &url,
1780 TextParsingOpts<Trait> &po,
1781 bool doNotCreateTextOnFail,
1782 long long int startLine,
1783 long long int startPos,
1784 long long int lastLine,
1785 long long int lastPos,
1786 const WithPosition &textPos,
1787 const WithPosition &urlPos);
1789 std::shared_ptr<Link<Trait>>
1790 makeLink(
const typename Trait::String &url,
1792 TextParsingOpts<Trait> &po,
1793 bool doNotCreateTextOnFail,
1794 long long int startLine,
1795 long long int startPos,
1796 long long int lastLine,
1797 long long int lastPos,
1798 const WithPosition &textPos,
1799 const WithPosition &urlPos);
1802 enum DelimiterType {
1810 SquareBracketsClose,
1833 DelimiterType m_type = Unknown;
1834 long long int m_line = -1;
1835 long long int m_pos = -1;
1836 long long int m_len = 0;
1837 bool m_isWordBefore =
false;
1838 bool m_backslashed =
false;
1839 bool m_leftFlanking =
false;
1840 bool m_rightFlanking =
false;
1841 bool m_skip =
false;
1844 using Delims =
typename Trait::template Vector<Delimiter>;
1848 TextParsingOpts<Trait> &po,
1849 long long int startLine,
1850 long long int startPos,
1851 long long int lastLineForText,
1852 long long int lastPosForText,
1853 typename Delims::iterator lastIt,
1855 bool doNotCreateTextOnFail,
1856 const WithPosition &textPos,
1857 const WithPosition &linkTextPos);
1859 typename Delims::iterator
1860 checkForImage(
typename Delims::iterator it,
1861 typename Delims::iterator last,
1862 TextParsingOpts<Trait> &po);
1866 TextParsingOpts<Trait> &po,
1867 long long int startLine,
1868 long long int startPos,
1869 long long int lastLineForText,
1870 long long int lastPosForText,
1871 typename Delims::iterator lastIt,
1873 bool doNotCreateTextOnFail,
1874 const WithPosition &textPos,
1875 const WithPosition &linkTextPos);
1877 typename Delims::iterator
1878 checkForLink(
typename Delims::iterator it,
1879 typename Delims::iterator last,
1880 TextParsingOpts<Trait> &po);
1885 std::pair<typename Trait::String, bool>
1886 readHtmlTag(
typename Delims::iterator it, TextParsingOpts<Trait> &po);
1888 typename Delims::iterator
1889 findIt(
typename Delims::iterator it,
1890 typename Delims::iterator last,
1891 TextParsingOpts<Trait> &po);
1893 typename Delims::iterator
1894 eatRawHtmlTillEmptyLine(
typename Delims::iterator it,
1895 typename Delims::iterator last,
1898 TextParsingOpts<Trait> &po,
1901 bool continueEating =
false);
1904 finishRule1HtmlTag(
typename Delims::iterator it,
1905 typename Delims::iterator last,
1906 TextParsingOpts<Trait> &po,
1910 finishRule2HtmlTag(
typename Delims::iterator it,
1911 typename Delims::iterator last,
1912 TextParsingOpts<Trait> &po);
1915 finishRule3HtmlTag(
typename Delims::iterator it,
1916 typename Delims::iterator last,
1917 TextParsingOpts<Trait> &po);
1920 finishRule4HtmlTag(
typename Delims::iterator it,
1921 typename Delims::iterator last,
1922 TextParsingOpts<Trait> &po);
1925 finishRule5HtmlTag(
typename Delims::iterator it,
1926 typename Delims::iterator last,
1927 TextParsingOpts<Trait> &po);
1930 finishRule6HtmlTag(
typename Delims::iterator it,
1931 typename Delims::iterator last,
1932 TextParsingOpts<Trait> &po);
1935 finishRule7HtmlTag(
typename Delims::iterator it,
1936 typename Delims::iterator last,
1937 TextParsingOpts<Trait> &po);
1939 typename Delims::iterator
1940 finishRawHtmlTag(
typename Delims::iterator it,
1941 typename Delims::iterator last,
1942 TextParsingOpts<Trait> &po,
1946 htmlTagRule(
typename Delims::iterator it,
1947 typename Delims::iterator last,
1948 TextParsingOpts<Trait> &po);
1950 typename Delims::iterator
1951 checkForRawHtml(
typename Delims::iterator it,
1952 typename Delims::iterator last,
1953 TextParsingOpts<Trait> &po);
1955 typename Delims::iterator
1956 checkForMath(
typename Delims::iterator it,
1957 typename Delims::iterator last,
1958 TextParsingOpts<Trait> &po);
1960 typename Delims::iterator
1961 checkForAutolinkHtml(
typename Delims::iterator it,
1962 typename Delims::iterator last,
1963 TextParsingOpts<Trait> &po,
1966 typename Delims::iterator
1967 checkForInlineCode(
typename Delims::iterator it,
1968 typename Delims::iterator last,
1969 TextParsingOpts<Trait> &po);
1971 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1972 readTextBetweenSquareBrackets(
typename Delims::iterator
start,
1973 typename Delims::iterator it,
1974 typename Delims::iterator last,
1975 TextParsingOpts<Trait> &po,
1976 bool doNotCreateTextOnFail,
1977 WithPosition *pos =
nullptr);
1979 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1980 checkForLinkText(
typename Delims::iterator it,
1981 typename Delims::iterator last,
1982 TextParsingOpts<Trait> &po,
1983 WithPosition *pos =
nullptr);
1985 std::pair<typename MdBlock<Trait>::Data,
typename Delims::iterator>
1986 checkForLinkLabel(
typename Delims::iterator it,
1987 typename Delims::iterator last,
1988 TextParsingOpts<Trait> &po,
1989 WithPosition *pos =
nullptr);
1991 std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
1992 checkForInlineLink(
typename Delims::iterator it,
1993 typename Delims::iterator last,
1994 TextParsingOpts<Trait> &po,
1995 WithPosition *urlPos =
nullptr);
1997 inline std::tuple<typename Trait::String, typename Trait::String, typename Delims::iterator, bool>
1998 checkForRefLink(
typename Delims::iterator it,
1999 typename Delims::iterator last,
2000 TextParsingOpts<Trait> &po,
2001 WithPosition *urlPos =
nullptr);
2003 typename Trait::String
2006 template<
class Func>
2007 typename Delims::iterator
2008 checkShortcut(
typename Delims::iterator it,
2009 typename Delims::iterator last,
2010 TextParsingOpts<Trait> &po,
2013 const auto start = it;
2017 WithPosition labelPos;
2018 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
2020 if (it !=
start && !toSingleLine(text).simplified().isEmpty()) {
2021 if ((this->*functor)(text, po,
start->m_line,
start->m_pos,
start->m_line,
2022 start->m_pos +
start->m_len, it, {},
false, labelPos, {})) {
2031 isSequence(
typename Delims::iterator it,
2032 long long int itLine,
2033 long long int itPos,
2034 typename Delimiter::DelimiterType t);
2036 std::pair<typename Delims::iterator, typename Delims::iterator>
2037 readSequence(
typename Delims::iterator first,
2038 typename Delims::iterator it,
2039 typename Delims::iterator last,
2041 long long int &length,
2042 long long int &itCount,
2043 long long int &lengthFromIt,
2044 long long int &itCountFromIt);
2046 typename Delims::iterator
2047 readSequence(
typename Delims::iterator it,
2048 typename Delims::iterator last,
2049 long long int &line,
2052 long long int &itCount);
2055 emphasisToInt(
typename Delimiter::DelimiterType t);
2058 createStyles(std::vector<std::pair<Style, long long int>> & styles,
2059 typename Delimiter::DelimiterType t,
2060 long long int style);
2062 std::vector<std::pair<Style, long long int>>
2063 createStyles(
typename Delimiter::DelimiterType t,
2064 const std::vector<long long int> &styles,
2065 long long int lastStyle);
2067 std::tuple<bool, std::vector<std::pair<Style, long long int>>,
long long int,
long long int>
2068 isStyleClosed(
typename Delims::iterator first,
2069 typename Delims::iterator it,
2070 typename Delims::iterator last,
2071 typename Delims::iterator &stackBottom,
2072 TextParsingOpts<Trait> &po);
2074 typename Delims::iterator
2075 incrementIterator(
typename Delims::iterator it,
2076 typename Delims::iterator last,
2077 long long int count);
2079 typename Delims::iterator
2080 checkForStyle(
typename Delims::iterator first,
2081 typename Delims::iterator it,
2082 typename Delims::iterator last,
2083 typename Delims::iterator &stackBottom,
2084 TextParsingOpts<Trait> &po);
2087 isNewBlockIn(MdBlock<Trait> &fr,
2088 long long int startLine,
2089 long long int endLine);
2092 makeInlineCode(
long long int startLine,
2093 long long int startPos,
2094 long long int lastLine,
2095 long long int lastPos,
2096 TextParsingOpts<Trait> &po,
2097 typename Delims::iterator startDelimIt,
2098 typename Delims::iterator endDelimIt);
2101 defaultParagraphOptimization()
const
2112 typename Trait::StringList m_parsedFiles;
2114 bool m_fullyOptimizeParagraphs =
true;
2123template<
class Trait>
2124inline std::shared_ptr<Document<Trait>>
2127 const typename Trait::StringList &ext,
2128 bool fullyOptimizeParagraphs)
2130 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2134 parseFile(fileName, recursive, doc, ext);
2141template<
class Trait>
2142inline std::shared_ptr<Document<Trait>>
2144 const typename Trait::String &path,
2145 const typename Trait::String &fileName,
2146 bool fullyOptimizeParagraphs)
2148 m_fullyOptimizeParagraphs = fullyOptimizeParagraphs;
2152 parseStream(stream, path, fileName,
false, doc,
typename Trait::StringList());
2159template<
class Trait>
2162#ifdef MD4QT_QT_SUPPORT
2179 return (m_lastBuf && m_pos == m_buf.size());
2186 bool rFound =
false;
2189 const auto c = getChar();
2217 m_buf = m_stream.read(512);
2219 if (m_stream.atEnd()) {
2229 if (m_pos < m_buf.size()) {
2230 return m_buf.at(m_pos++);
2231 }
else if (!atEnd()) {
2241 QTextStream &m_stream;
2244 long long int m_pos;
2249#ifdef MD4QT_ICU_STL_SUPPORT
2259 std::vector<unsigned char> content;
2261 stream.seekg(0, std::ios::end);
2262 const auto ssize = stream.tellg();
2263 content.resize((
size_t)ssize + 1);
2264 stream.seekg(0, std::ios::beg);
2265 stream.read((
char *)&content[0], ssize);
2266 content[(size_t)ssize] = 0;
2268 const auto z = std::count(content.cbegin(), content.cend(), 0);
2271 std::vector<unsigned char> tmp;
2272 tmp.resize(content.size() + (z - 1) * 2);
2274 for (
size_t i = 0, j = 0; i < content.size() - 1; ++i, ++j) {
2275 if (content[i] == 0) {
2281 tmp[j] = content[i];
2285 tmp[tmp.size() - 1] = 0;
2287 std::swap(content, tmp);
2290 m_str = UnicodeString::fromUTF8((
char *)&content[0]);
2296 return m_pos == m_str.size();
2304 bool rFound =
false;
2307 const auto c = getChar();
2336 return m_str[m_pos++];
2338 return UnicodeChar();
2343 UnicodeString m_str;
2344 long long int m_pos;
2350template<
class Trait>
2355 const long long int e = line.indexOf(Trait::latin1ToString(
"-->"), pos);
2365template<
class Trait>
2373 const auto &str = line.asString();
2375 while ((p = str.indexOf(Trait::latin1ToString(
s_startComment), p)) != -1) {
2376 bool addNegative =
false;
2378 auto c = str.sliced(p);
2380 if (c.startsWith(Trait::latin1ToString(
"<!-->"))) {
2381 res.insert({line.virginPos(p), {0,
true}});
2386 }
else if (c.startsWith(Trait::latin1ToString(
"<!--->"))) {
2387 res.insert({line.virginPos(p), {1,
true}});
2395 res.insert({line.virginPos(p), {2,
true}});
2399 for (; l < stream.
size(); ++l) {
2400 c.push_back(Trait::latin1ToChar(
' '));
2401 c.push_back(stream.
lineAt(l).asString());
2404 res.insert({line.virginPos(p), {2,
true}});
2406 addNegative =
false;
2414 res.insert({line.virginPos(p), {-1,
false}});
2421template<
class Trait>
2422inline std::pair<long long int, bool>
2424 std::shared_ptr<Block<Trait>> parent,
2426 typename Trait::StringList &linksToParse,
2427 const typename Trait::String &workingPath,
2428 const typename Trait::String &fileName,
2429 bool collectRefLinks)
2431 auto clearCtx = [&ctx] () {
2432 ctx.m_fragment.clear();
2433 ctx.m_type = BlockType::EmptyLine;
2434 ctx.m_emptyLineInList =
false;
2435 ctx.m_fensedCodeInList =
false;
2436 ctx.m_emptyLinesCount = 0;
2437 ctx.m_lineCounter = 0;
2438 ctx.m_indents.clear();
2439 ctx.m_indent = {-1, -1};
2440 ctx.m_startOfCode.clear();
2441 ctx.m_startOfCodeInList.clear();
2444 if (!ctx.m_fragment.empty()) {
2445 MdBlock<Trait> block = {ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0};
2447 const auto line = parseFragment(block, parent, doc, linksToParse, workingPath,
2448 fileName, collectRefLinks, ctx.m_html);
2450 assert(line != ctx.m_fragment.front().second.m_lineNumber);
2453 if (ctx.m_html.m_html) {
2454 if (!collectRefLinks) {
2455 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2461 const auto it = ctx.m_fragment.cbegin() + (line - ctx.m_fragment.cbegin()->second.m_lineNumber);
2464 std::copy(ctx.m_fragment.cbegin(), it, std::back_inserter(tmp.m_data));
2466 long long int emptyLines = 0;
2468 while (!tmp.m_data.empty() && tmp.m_data.back().first.asString().simplified().isEmpty()) {
2469 tmp.m_data.pop_back();
2470 tmp.m_emptyLineAfter =
true;
2474 if (!tmp.m_data.empty()) {
2475 ctx.m_splitted.push_back(tmp);
2478 const auto retLine = it->second.m_lineNumber;
2479 const auto retMayBreakList = it->second.m_mayBreakList;
2483 ctx.m_emptyLinesBefore = emptyLines;
2485 return {retLine, retMayBreakList};
2488 ctx.m_splitted.push_back({ctx.m_fragment, ctx.m_emptyLinesBefore, ctx.m_emptyLinesCount > 0});
2497template<
class Trait>
2501 unsigned char size = 4;
2502 long long int len = s.length();
2504 for (
long long int i = 0; i < len; ++i, --size) {
2505 if (s[i] == Trait::latin1ToChar(
'\t')) {
2506 s.replaceOne(i, 1,
typename Trait::String(size, Trait::latin1ToChar(
' ')));
2519template<
class Trait>
2522 StringListStream<Trait> &stream,
2523 std::shared_ptr<Block<Trait>> parent,
2525 typename Trait::StringList &linksToParse,
2526 const typename Trait::String &workingPath,
2527 const typename Trait::String &fileName,
2528 bool collectRefLinks)
2530 long long int emptyLinesCount = 0;
2531 bool wasEmptyLine =
false;
2533 while (!stream.atEnd()) {
2534 const auto currentLineNumber = stream.currentLineNumber();
2536 typename Trait::InternalString line;
2539 std::tie(line, mayBreak) = readLine(ctx, stream);
2545 if (ns == line.length() || line.asString().startsWith(Trait::latin1ToString(
" "))) {
2546 if (ns == line.length()) {
2548 wasEmptyLine =
true;
2550 emptyLinesCount = 0;
2553 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2554 }
else if (!wasEmptyLine) {
2556 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2558 ctx.m_lineType = BlockType::Footnote;
2560 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2564 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2567 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2570 whatIsTheLine(line,
false,
false,
false, &ctx.m_startOfCodeInList, &ctx.m_indent,
2571 ctx.m_lineType == BlockType::EmptyLine,
true, &ctx.m_indents);
2573 makeLineMain(ctx, line, emptyLinesCount, ctx.m_indent, ns, currentLineNumber);
2575 if (ctx.m_type == BlockType::Footnote) {
2576 wasEmptyLine =
false;
2585 if (stream.atEnd() && !ctx.m_fragment.empty()) {
2586 parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2590template<
class Trait>
2592Parser<Trait>::finishHtml(ParserContext &ctx,
2595 bool collectRefLinks,
2597 bool dontProcessLastFreeHtml)
2599 if (!collectRefLinks || top) {
2600 if (ctx.m_html.m_html->isFreeTag()) {
2601 if (!dontProcessLastFreeHtml) {
2602 if (ctx.m_html.m_parent) {
2603 ctx.m_html.m_parent->appendItem(ctx.m_html.m_html);
2607 parent->appendItem(ctx.m_html.m_html);
2612 p->appendItem(ctx.m_html.m_html);
2613 p->setStartColumn(ctx.m_html.m_html->startColumn());
2614 p->setStartLine(ctx.m_html.m_html->startLine());
2615 p->setEndColumn(ctx.m_html.m_html->endColumn());
2616 p->setEndLine(ctx.m_html.m_html->endLine());
2621 if (!dontProcessLastFreeHtml) {
2625 ctx.m_html.m_toAdjustLastPos.clear();
2628template<
class Trait>
2630Parser<Trait>::makeLineMain(ParserContext &ctx,
2631 const typename Trait::InternalString &line,
2632 long long int emptyLinesCount,
2633 const ListIndent ¤tIndent,
2635 long long int currentLineNumber)
2637 if (ctx.m_html.m_htmlBlockType >= 6) {
2638 ctx.m_html.m_continueHtml = (emptyLinesCount <= 0);
2641 ctx.m_type = ctx.m_lineType;
2643 switch (ctx.m_type) {
2644 case BlockType::List:
2645 case BlockType::ListWithFirstEmptyLine: {
2646 if (ctx.m_indents.empty())
2647 ctx.m_indents.push_back(currentIndent.m_indent);
2649 ctx.m_indent = currentIndent;
2652 case BlockType::Code:
2660 if (!line.isEmpty() && ns < line.length()) {
2661 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData}});
2664 ctx.m_lineCounter = 1;
2665 ctx.m_emptyLinesCount = 0;
2666 ctx.m_emptyLinesBefore = emptyLinesCount;
2669template<
class Trait>
2670inline std::pair<long long int, bool>
2671Parser<Trait>::parseFragmentAndMakeNextLineMain(ParserContext &ctx,
2674 typename Trait::StringList &linksToParse,
2675 const typename Trait::String &workingPath,
2676 const typename Trait::String &fileName,
2677 bool collectRefLinks,
2678 const typename Trait::InternalString &line,
2679 const ListIndent ¤tIndent,
2681 long long int currentLineNumber)
2683 const auto empty = ctx.m_emptyLinesCount;
2685 const auto ret = parseFragment(ctx, parent, doc, linksToParse, workingPath,
2686 fileName, collectRefLinks);
2688 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2693template<
class Trait>
2695Parser<Trait>::isListType(BlockType t)
2698 case BlockType::List:
2699 case BlockType::ListWithFirstEmptyLine:
2707template<
class Trait>
2708std::pair<typename Trait::InternalString, bool>
2709Parser<Trait>::readLine(
typename Parser<Trait>::ParserContext &ctx,
2712 ctx.m_htmlCommentData.clear();
2714 auto line = stream.readLine();
2716 static const char16_t c_zeroReplaceWith[2] = {0xFFFD, 0};
2718 line.first.replace(
typename Trait::Char(0), Trait::utf16ToString(&c_zeroReplaceWith[0]));
2725template<
class Trait>
2726inline std::pair<long long int, bool>
2727Parser<Trait>::parseFirstStep(ParserContext &ctx,
2731 typename Trait::StringList &linksToParse,
2732 const typename Trait::String &workingPath,
2733 const typename Trait::String &fileName,
2734 bool collectRefLinks)
2736 while (!stream.atEnd()) {
2737 const auto currentLineNumber = stream.currentLineNumber();
2739 typename Trait::InternalString line;
2742 std::tie(line, mayBreak) = readLine(ctx, stream);
2744 if (ctx.m_lineType != BlockType::Unknown) {
2745 ctx.m_prevLineType = ctx.m_lineType;
2748 ctx.m_lineType = whatIsTheLine(line,
2749 (ctx.m_emptyLineInList || isListType(ctx.m_type)),
2750 ctx.m_prevLineType == BlockType::ListWithFirstEmptyLine,
2751 ctx.m_fensedCodeInList,
2752 &ctx.m_startOfCodeInList,
2754 ctx.m_lineType == BlockType::EmptyLine,
2758 if (isListType(ctx.m_type) && ctx.m_lineType == BlockType::FensedCodeInList) {
2759 ctx.m_fensedCodeInList = !ctx.m_fensedCodeInList;
2762 const auto currentIndent = ctx.m_indent;
2766 const auto indentInListValue =
indentInList(&ctx.m_indents, ns,
true);
2768 if (isListType(ctx.m_lineType) && !ctx.m_fensedCodeInList && ctx.m_indent.m_level > -1) {
2769 if (ctx.m_indent.m_level < (
long long int)ctx.m_indents.size()) {
2770 ctx.m_indents.erase(ctx.m_indents.cbegin() + ctx.m_indent.m_level, ctx.m_indents.cend());
2773 ctx.m_indents.push_back(ctx.m_indent.m_indent);
2776 if (ctx.m_type == BlockType::CodeIndentedBySpaces && ns > 3) {
2777 ctx.m_lineType = BlockType::CodeIndentedBySpaces;
2780 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2 &&
2781 !isListType(ctx.m_lineType)) {
2782 if (ctx.m_emptyLinesCount > 0) {
2783 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2795 if (l.first != -1) {
2801 ctx.m_emptyLineInList =
false;
2802 ctx.m_emptyLinesCount = 0;
2806 if (ctx.m_type == BlockType::ListWithFirstEmptyLine && ctx.m_lineCounter == 2) {
2807 ctx.m_type = BlockType::List;
2811 if (ctx.m_lineType == BlockType::Footnote) {
2812 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2824 if (l.first != -1) {
2828 eatFootnote(ctx, stream, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
2834 if (ns != line.length() && ctx.m_type == BlockType::EmptyLine) {
2835 makeLineMain(ctx, line, ctx.m_emptyLinesCount, currentIndent, ns, currentLineNumber);
2838 }
else if (ns == line.length() && ctx.m_type == BlockType::EmptyLine) {
2839 ++ctx.m_emptyLinesCount;
2843 ++ctx.m_lineCounter;
2846 if (ns == line.length()) {
2847 ++ctx.m_emptyLinesCount;
2849 switch (ctx.m_type) {
2850 case BlockType::Blockquote: {
2851 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2854 if (l.first != -1) {
2861 case BlockType::Text:
2862 case BlockType::CodeIndentedBySpaces:
2866 case BlockType::Code: {
2867 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2868 ctx.m_emptyLinesCount = 0;
2873 case BlockType::List:
2874 case BlockType::ListWithFirstEmptyLine: {
2875 ctx.m_emptyLineInList =
true;
2885 else if (ctx.m_emptyLineInList) {
2886 if (indentInListValue || isListType(ctx.m_lineType) || ctx.m_lineType == BlockType::SomethingInList) {
2887 for (
long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2888 ctx.m_fragment.push_back({
typename Trait::String(),
2889 {currentLineNumber - ctx.m_emptyLinesCount + i, {},
false}});
2892 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2894 ctx.m_emptyLineInList =
false;
2895 ctx.m_emptyLinesCount = 0;
2899 const auto empty = ctx.m_emptyLinesCount;
2901 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
2904 if (l.first != -1) {
2908 ctx.m_lineType = whatIsTheLine(line,
false,
false,
false,
nullptr,
nullptr,
2909 true,
false, &ctx.m_indents);
2911 makeLineMain(ctx, line, empty, currentIndent, ns, currentLineNumber);
2915 }
else if (ctx.m_emptyLinesCount > 0) {
2916 if (ctx.m_type == BlockType::CodeIndentedBySpaces &&
2917 ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
2918 const auto indent =
skipSpaces<Trait>(0, ctx.m_fragment.front().first.asString());
2920 for (
long long int i = 0; i < ctx.m_emptyLinesCount; ++i) {
2921 ctx.m_fragment.push_back({
typename Trait::String(indent, Trait::latin1ToChar(
' ')),
2922 {currentLineNumber - ctx.m_emptyLinesCount + i, {},
false}});
2925 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2926 ctx.m_emptyLinesCount = 0;
2928 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2940 if (l.first != -1) {
2949 if (ctx.m_type != ctx.m_lineType && ctx.m_type != BlockType::Code &&
2950 !isListType(ctx.m_type) && ctx.m_type != BlockType::Blockquote) {
2951 if (ctx.m_type == BlockType::Text && ctx.m_lineType == BlockType::CodeIndentedBySpaces) {
2952 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2955 if (ctx.m_type == BlockType::Text && isListType(ctx.m_lineType)) {
2956 if (ctx.m_lineType != BlockType::ListWithFirstEmptyLine) {
2961 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2967 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2973 const auto l = parseFragmentAndMakeNextLineMain(ctx,
2985 if (l.first != -1) {
2991 else if (ctx.m_type == BlockType::Code && ctx.m_type == ctx.m_lineType &&
2992 !ctx.m_startOfCode.isEmpty() &&
2995 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
2997 if (!stream.atEnd()) {
2998 typename Trait::InternalString line;
3000 std::tie(line, std::ignore) = readLine(ctx, stream);
3002 if (line.asString().simplified().isEmpty()) {
3003 ++ctx.m_emptyLinesCount;
3006 stream.setLineNumber(stream.currentLineNumber() - 1);
3008 ++ctx.m_emptyLinesCount;
3011 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3014 if (l.first != -1) {
3019 else if (ctx.m_type != ctx.m_lineType && isListType(ctx.m_type) &&
3020 ctx.m_lineType != BlockType::SomethingInList &&
3021 ctx.m_lineType != BlockType::FensedCodeInList && !isListType(ctx.m_lineType)) {
3022 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3034 if (l.first != -1) {
3037 }
else if (ctx.m_type == BlockType::Heading) {
3038 const auto l = parseFragmentAndMakeNextLineMain(ctx,
3050 if (l.first != -1) {
3054 ctx.m_fragment.push_back({line, {currentLineNumber, ctx.m_htmlCommentData, mayBreak}});
3057 ctx.m_emptyLinesCount = 0;
3060 if (!ctx.m_fragment.empty()) {
3061 if (ctx.m_type == BlockType::Code && !ctx.m_html.m_html && !ctx.m_html.m_continueHtml) {
3062 ctx.m_fragment.push_back({ctx.m_startOfCode, {-1, {},
false}});
3065 const auto l = parseFragment(ctx, parent, doc, linksToParse, workingPath, fileName,
3068 if (l.first != -1) {
3076template<
class Trait>
3078Parser<Trait>::parseSecondStep(ParserContext &ctx,
3081 typename Trait::StringList &linksToParse,
3082 const typename Trait::String &workingPath,
3083 const typename Trait::String &fileName,
3084 bool collectRefLinks,
3086 bool dontProcessLastFreeHtml)
3091 for (
long long int i = 0; i < (
long long int)ctx.m_splitted.size(); ++i) {
3092 parseFragment(ctx.m_splitted[i], parent, doc, linksToParse, workingPath, fileName,
false,
3095 if (ctx.m_html.m_htmlBlockType >= 6) {
3096 ctx.m_html.m_continueHtml = (!ctx.m_splitted[i].m_emptyLineAfter);
3099 if (ctx.m_html.m_html && !ctx.m_html.m_continueHtml) {
3100 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3101 }
else if (!ctx.m_html.m_html) {
3102 ctx.m_html.m_toAdjustLastPos.clear();
3107 if (ctx.m_html.m_html) {
3108 finishHtml(ctx, parent, doc, collectRefLinks, top, dontProcessLastFreeHtml);
3112template<
class Trait>
3113inline std::pair<RawHtmlBlock<Trait>,
long long int>
3117 typename Trait::StringList &linksToParse,
3118 const typename Trait::String &workingPath,
3119 const typename Trait::String &fileName,
3120 bool collectRefLinks,
3122 bool dontProcessLastFreeHtml,
3123 bool stopOnMayBreakList)
3127 auto clearCtx = [&]()
3129 ctx.m_fragment.clear();
3130 ctx.m_type = BlockType::EmptyLine;
3131 ctx.m_lineCounter = 0;
3134 auto line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3139 while (line.first != -1 && !(stopOnMayBreakList && line.second)) {
3140 stream.setLineNumber(line.first);
3142 line = parseFirstStep(ctx, stream, parent, doc, linksToParse, workingPath, fileName,
3148 parseSecondStep(ctx, parent, doc, linksToParse, workingPath, fileName,
3149 collectRefLinks, top, dontProcessLastFreeHtml);
3151 return {ctx.m_html, line.first};
3154#ifdef MD4QT_QT_SUPPORT
3158Parser<QStringTrait>::parseFile(
const QString &fileName,
3161 const QStringList &ext,
3162 QStringList *parentLinks)
3164 QFileInfo fi(fileName);
3166 if (fi.exists() && ext.
contains(fi.suffix().toLower())) {
3170 QTextStream s(f.readAll());
3173 parseStream(s, fi.absolutePath(), fi.fileName(), recursive, doc, ext, parentLinks);
3180#ifdef MD4QT_ICU_STL_SUPPORT
3184Parser<UnicodeStringTrait>::parseFile(
const UnicodeString &fileName,
3187 const std::vector<UnicodeString> &ext,
3188 std::vector<UnicodeString> *parentLinks)
3192 fileName.toUTF8String(fn);
3195 auto e = UnicodeString::fromUTF8(std::filesystem::u8path(fn).extension().u8string());
3201 if (std::find(ext.cbegin(), ext.cend(), e.toLower()) != ext.cend()) {
3202 auto path = std::filesystem::canonical(std::filesystem::u8path(fn));
3203 std::ifstream file(
path.c_str(), std::ios::in | std::ios::binary);
3206 const auto fileNameS =
path.filename().u8string();
3207 auto workingDirectory =
path.remove_filename().u8string();
3209 if (!workingDirectory.empty()) {
3210 workingDirectory.erase(workingDirectory.size() - 1, 1);
3213 std::replace(workingDirectory.begin(), workingDirectory.end(),
'\\',
'/');
3215 parseStream(file, UnicodeString::fromUTF8(workingDirectory),
3216 UnicodeString::fromUTF8(fileNameS), recursive, doc, ext, parentLinks);
3221 }
catch (
const std::exception &) {
3229template<
class Trait>
3234 for (
auto it = linksToParse.begin(), last = linksToParse.end(); it != last; ++it) {
3235 auto nextFileName = *it;
3237 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3238 const auto lit = doc->labeledLinks().find(nextFileName);
3240 if (lit != doc->labeledLinks().cend()) {
3241 nextFileName = lit->second->url();
3247 if (Trait::fileExists(nextFileName)) {
3248 *it = Trait::absoluteFilePath(nextFileName);
3253template<
class Trait>
3256 const typename Trait::String &workingPath,
3257 const typename Trait::String &fileName,
3260 const typename Trait::StringList &ext,
3261 typename Trait::StringList *parentLinks)
3263 typename Trait::StringList linksToParse;
3265 const auto path = workingPath.
isEmpty() ?
typename Trait::String(fileName) :
3266 typename Trait::String(workingPath + Trait::latin1ToString(
"/") + fileName);
3273 TextStream<Trait> stream(s);
3275 long long int i = 0;
3277 while (!stream.atEnd()) {
3278 data.push_back(std::pair<typename Trait::InternalString, MdLineData>(stream.readLine(), {i}));
3285 parse(stream, doc, doc, linksToParse, workingPath, fileName,
true,
true);
3287 m_parsedFiles.push_back(path);
3292 if (recursive && !linksToParse.empty()) {
3293 const auto tmpLinks = linksToParse;
3295 while (!linksToParse.empty()) {
3296 auto nextFileName = linksToParse.front();
3297 linksToParse.erase(linksToParse.cbegin());
3300 const auto pit = std::find(parentLinks->cbegin(), parentLinks->cend(), nextFileName);
3302 if (pit != parentLinks->cend()) {
3307 if (nextFileName.startsWith(Trait::latin1ToString(
"#"))) {
3311 const auto pit = std::find(m_parsedFiles.cbegin(), m_parsedFiles.cend(), nextFileName);
3313 if (pit == m_parsedFiles.cend()) {
3318 parseFile(nextFileName, recursive, doc, ext, &linksToParse);
3323 std::copy(tmpLinks.cbegin(), tmpLinks.cend(), std::back_inserter(*parentLinks));
3329template<
class Trait>
3334 long long int p = 0;
3336 for (; p < s.size(); ++p) {
3337 if (!s[p].isSpace()) {
3343 for (; p < s.size(); ++p) {
3344 if (!s[p].isDigit()) {
3352 long long int sc = 0;
3354 for (; p < s.size(); ++p) {
3355 if (!s[p].isSpace()) {
3362 if (p == s.length() || sc > 4) {
3364 }
else if (sc == 0) {
3376 long long int level = indents.
size();
3378 for (
auto it = indents.crbegin(), last = indents.crend(); it != last; ++it) {
3389template<
class Trait>
3393 bool inListWithFirstEmptyLine,
3394 bool fensedCodeInList,
3395 typename Trait::String *startOfCode,
3397 bool emptyLinePreceded,
3399 const std::vector<long long int> *indents)
3405 if (first < str.length()) {
3406 auto s = str.sliced(first);
3408 const bool isBlockquote = s.asString().startsWith(Trait::latin1ToString(
">"));
3409 const bool indentIn =
indentInList(indents, first,
false);
3410 bool isHeading =
false;
3413 return BlockType::Footnote;
3416 if (s.asString().startsWith(Trait::latin1ToString(
"#")) &&
3417 (indent ? first - indent->m_indent < 4 : first < 4)) {
3418 long long int c = 0;
3420 while (c < s.length() && s[c] == Trait::latin1ToChar(
'#')) {
3424 if (c <= 6 && ((c < s.length() && s[c].isSpace()) || c == s.length())) {
3430 bool isFirstLineEmpty =
false;
3434 const auto codeIndentedBySpaces = emptyLinePreceded && first >= 4 &&
3437 if (fensedCodeInList) {
3441 return BlockType::FensedCodeInList;
3445 return BlockType::SomethingInList;
3449 if (fensedCode && indentIn) {
3454 return BlockType::FensedCodeInList;
3455 }
else if ((((s.asString().startsWith(Trait::latin1ToString(
"-")) ||
3456 s.asString().startsWith(Trait::latin1ToString(
"+")) ||
3457 s.asString().startsWith(Trait::latin1ToString(
"*"))) &&
3458 ((s.length() > 1 && s[1] == Trait::latin1ToChar(
' ')) || s.length() == 1)) ||
3459 orderedList) && (first < 4 || indentIn)) {
3460 if (codeIndentedBySpaces) {
3461 return BlockType::CodeIndentedBySpaces;
3464 if (indent && calcIndent) {
3466 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3469 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3470 return BlockType::ListWithFirstEmptyLine;
3472 return BlockType::List;
3475 return BlockType::SomethingInList;
3478 if (!isHeading && !isBlockquote &&
3479 !(fensedCode && first < 4) && !emptyLinePreceded && !inListWithFirstEmptyLine) {
3480 return BlockType::SomethingInList;
3484 bool isFirstLineEmpty =
false;
3488 const bool isHLine = first < 4 && isHorizontalLine<Trait>(s.asString());
3491 (((s.asString().startsWith(Trait::latin1ToString(
"-")) || s.asString().startsWith(Trait::latin1ToString(
"+")) ||
3492 s.asString().startsWith(Trait::latin1ToString(
"*"))) &&
3493 ((s.length() > 1 && s[1] == Trait::latin1ToChar(
' ')) || s.length() == 1)) ||
3494 orderedList) && first < 4) {
3495 if (indent && calcIndent) {
3497 indent->m_level = (indents ?
listLevel(*indents, first) : -1);
3500 if (s.simplified().length() == 1 || isFirstLineEmpty) {
3501 return BlockType::ListWithFirstEmptyLine;
3503 return BlockType::List;
3508 if (str.asString().startsWith(
typename Trait::String(4, Trait::latin1ToChar(
' ')))) {
3509 return BlockType::CodeIndentedBySpaces;
3511 return BlockType::Code;
3512 }
else if (isBlockquote) {
3513 return BlockType::Blockquote;
3514 }
else if (isHeading) {
3515 return BlockType::Heading;
3518 return BlockType::EmptyLine;
3521 return BlockType::Text;
3524template<
class Trait>
3529 typename Trait::StringList &linksToParse,
3530 const typename Trait::String &workingPath,
3531 const typename Trait::String &fileName,
3532 bool collectRefLinks,
3535 if (html.m_continueHtml) {
3536 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3539 if (!collectRefLinks) {
3540 parent->appendItem(html.m_html);
3546 switch (whatIsTheLine(fr.m_data.front().first)) {
3547 case BlockType::Footnote:
3548 parseFootnote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3551 case BlockType::Text:
3552 return parseText(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3555 case BlockType::Blockquote:
3556 return parseBlockquote(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3559 case BlockType::Code:
3560 return parseCode(fr, parent, collectRefLinks);
3563 case BlockType::CodeIndentedBySpaces: {
3566 if (fr.m_data.front().first.asString().startsWith(Trait::latin1ToString(
" "))) {
3570 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, {}, -1, -1,
false);
3573 case BlockType::Heading:
3574 parseHeading(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks);
3577 case BlockType::List:
3578 case BlockType::ListWithFirstEmptyLine:
3579 return parseList(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3589template<
class Trait>
3591Parser<Trait>::clearCache()
3593 m_parsedFiles.clear();
3597template<
class Trait>
3601 if (s.contains(Trait::latin1ToChar(
'|'))) {
3604 const auto tmp = s.simplified();
3605 const auto p = tmp.startsWith(Trait::latin1ToString(
"|")) ? 1 : 0;
3606 const auto n = tmp.size() - p - (tmp.endsWith(Trait::latin1ToString(
"|")) && tmp.size() > 1 ? 1 : 0);
3607 const auto v = tmp.sliced(p, n);
3609 bool backslash =
false;
3611 for (
long long int i = 0; i < v.size(); ++i) {
3614 if (v[i] == Trait::latin1ToChar(
'\\') && !backslash) {
3617 }
else if (v[i] == Trait::latin1ToChar(
'|') && !backslash) {
3634template<
class Trait>
3637 std::shared_ptr<Block<Trait>> parent,
3639 typename Trait::StringList &linksToParse,
3640 const typename Trait::String &workingPath,
3641 const typename Trait::String &fileName,
3642 bool collectRefLinks,
3643 RawHtmlBlock<Trait> &html)
3648 if (c && h && c == h && !html.m_continueHtml) {
3649 parseTable(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, c);
3651 if (!fr.m_data.empty()) {
3652 return fr.m_data.front().second.m_lineNumber;
3657 return parseParagraph(fr, parent, doc, linksToParse, workingPath, fileName, collectRefLinks, html);
3662template<
class Trait>
3663inline std::pair<typename Trait::String, WithPosition>
3666 const auto start = s.asString().indexOf(Trait::latin1ToString(
"{#"));
3669 long long int p =
start + 2;
3671 for (; p < s.length(); ++p) {
3672 if (s[p] == Trait::latin1ToChar(
'}')) {
3677 if (p < s.length() && s[p] == Trait::latin1ToChar(
'}')) {
3684 return {label, pos};
3692template<
class Trait>
3693inline typename Trait::String
3696 typename Trait::String res;
3698 for (
long long int i = 0; i < s.length(); ++i) {
3699 const auto c = s[i];
3701 if (c.isLetter() || c.isDigit() || c == Trait::latin1ToChar(
'-') ||
3702 c == Trait::latin1ToChar(
'_')) {
3704 }
else if (c.isSpace()) {
3705 res.push_back(Trait::latin1ToString(
"-"));
3713template<
class Trait>
3714inline typename Trait::String
3717 typename Trait::String l;
3723 for (
auto it = p->
items().cbegin(), last = p->
items().cend(); it != last; ++it) {
3724 switch ((*it)->type()) {
3727 const auto text = t->text();
3734 if (!i->p()->isEmpty()) {
3736 }
else if (!i->text().isEmpty()) {
3742 auto link =
static_cast<Link<Trait> *
>(it->get());
3744 if (!link->p()->isEmpty()) {
3746 }
else if (!link->text().isEmpty()) {
3754 if (!c->text().isEmpty()) {
3768template<
class Trait>
3772 long long int end = -1;
3773 long long int start = -1;
3775 for (
long long int i = s.length() - 1; i >= 0; --i) {
3776 if (!s[i].isSpace() && s[i] != Trait::latin1ToChar(
'#') && end == -1) {
3780 if (s[i] == Trait::latin1ToChar(
'#')) {
3786 if (s[i - 1].isSpace()) {
3789 }
else if (s[i - 1] != Trait::latin1ToChar(
'#')) {
3800 if (
start != -1 && end != -1) {
3810template<
class Trait>
3813 std::shared_ptr<Block<Trait>> parent,
3815 typename Trait::StringList &linksToParse,
3816 const typename Trait::String &workingPath,
3817 const typename Trait::String &fileName,
3818 bool collectRefLinks)
3820 if (!fr.m_data.empty() && !collectRefLinks) {
3821 auto line = fr.m_data.front().first;
3825 h->setStartLine(fr.m_data.front().second.m_lineNumber);
3826 h->setEndColumn(line.virginPos(line.length() - 1));
3827 h->setEndLine(h->startLine());
3829 long long int pos = 0;
3833 line = line.sliced(pos);
3839 while (pos < line.length() && line[pos] == Trait::latin1ToChar(
'#')) {
3844 WithPosition startDelim = {h->startColumn(), h->startLine(),
3845 line.virginPos(pos - 1), h->startLine()};
3850 fr.m_data.front().first = line.sliced(pos);
3859 if (endDelim.startColumn() != -1) {
3860 endDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
3861 endDelim.setEndLine(endDelim.startLine());
3863 delims.push_back(endDelim);
3866 h->setDelims(delims);
3872 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
3873 Trait::latin1ToString(
"")) + fileName);
3875 label.second.setStartLine(fr.m_data.front().second.m_lineNumber);
3876 label.second.setEndLine(
label.second.startLine());
3878 h->setLabelPos(
label.second);
3885 tmp.push_back(fr.m_data.front());
3890 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
3891 false,
false, html,
false);
3893 fr.m_data.erase(fr.m_data.cbegin());
3901 if (h->isLabeled()) {
3902 doc->insertLabeledHeading(h->label(), h);
3903 h->labelVariants().push_back(h->label());
3905 typename Trait::String
label = Trait::latin1ToString(
"#") +
3908 const auto path = Trait::latin1ToString(
"/") +
3909 (!workingPath.isEmpty() ? workingPath + Trait::latin1ToString(
"/") :
3910 Trait::latin1ToString(
"")) + fileName;
3912 h->setLabel(label + path);
3913 h->labelVariants().
push_back(h->label());
3915 doc->insertLabeledHeading(label + path, h);
3923 parent->appendItem(h);
3928template<
class Trait>
3929inline typename Trait::InternalString
3932 s.replace(Trait::latin1ToString(
"\\|"), Trait::latin1ToString(
"|"));
3938template<
class Trait>
3939inline std::pair<typename Trait::InternalStringList, std::vector<long long int>>
3942 typename Trait::InternalStringList res;
3943 std::vector<long long int> columns;
3945 bool backslash =
false;
3946 long long int start = 0;
3948 for (
long long int i = 0; i < s.length(); ++i) {
3951 if (s[i] == Trait::latin1ToChar(
'\\') && !backslash) {
3954 }
else if (s[i] == Trait::latin1ToChar(
'|') && !backslash) {
3956 columns.push_back(s.virginPos(i));
3967 return {res, columns};
3970template<
class Trait>
3973 std::shared_ptr<Block<Trait>> parent,
3975 typename Trait::StringList &linksToParse,
3976 const typename Trait::String &workingPath,
3977 const typename Trait::String &fileName,
3978 bool collectRefLinks,
3981 static const char sep =
'|';
3983 if (fr.m_data.size() >= 2) {
3985 table->setStartColumn(fr.m_data.front().first.virginPos(0));
3986 table->setStartLine(fr.m_data.front().second.m_lineNumber);
3987 table->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
3988 table->setEndLine(fr.m_data.back().second.m_lineNumber);
3991 const auto &row = lineData.first;
3993 if (row.asString().startsWith(Trait::latin1ToString(
" "))) {
4000 if (p == line.length()) {
4004 if (line[p] == Trait::latin1ToChar(sep)) {
4005 line.remove(0, p + 1);
4008 for (p = line.length() - 1; p >= 0; --p) {
4009 if (!line[p].isSpace()) {
4018 if (line[p] == Trait::latin1ToChar(sep)) {
4019 line.remove(p, line.length() - p);
4023 columns.second.insert(columns.second.begin(), row.virginPos(0));
4024 columns.second.push_back(row.virginPos(row.length() - 1));
4027 tr->setStartColumn(row.virginPos(0));
4028 tr->setStartLine(lineData.second.m_lineNumber);
4029 tr->setEndColumn(row.virginPos(row.length() - 1));
4030 tr->setEndLine(lineData.second.m_lineNumber);
4034 for (
auto it = columns.first.begin(), last = columns.first.end(); it != last; ++it, ++col) {
4035 if (col == columnsCount) {
4040 c->setStartColumn(columns.second.at(col));
4041 c->setStartLine(lineData.second.m_lineNumber);
4042 c->setEndColumn(columns.second.at(col + 1));
4043 c->setEndLine(lineData.second.m_lineNumber);
4045 if (!it->isEmpty()) {
4046 it->replace(Trait::latin1ToString(
"|"), Trait::latin1ToChar(sep));
4049 fragment.push_back({*it, lineData.second});
4056 parseFormattedTextLinksImages(block, p, doc, linksToParse, workingPath, fileName,
4057 collectRefLinks,
false, html,
false);
4059 if (!p->isEmpty()) {
4060 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it ) {
4061 switch ((*it)->type()) {
4063 const auto pp = std::static_pointer_cast<Paragraph<Trait>>(*it);
4065 for (
auto it = pp->items().cbegin(), last = pp->items().cend(); it != last; ++it) {
4066 c->appendItem((*it));
4072 c->appendItem((*it));
4078 if (html.m_html.get()) {
4079 c->appendItem(html.m_html);
4087 table->appendRow(tr);
4093 auto fmt = fr.m_data.at(1).first;
4095 auto columns = fmt.split(
typename Trait::InternalString(Trait::latin1ToChar(sep)));
4097 for (
auto it = columns.begin(), last = columns.end(); it != last; ++it) {
4098 *it = it->simplified();
4100 if (!it->isEmpty()) {
4103 if (it->asString().endsWith(Trait::latin1ToString(
":")) &&
4104 it->asString().startsWith(Trait::latin1ToString(
":"))) {
4106 }
else if (it->asString().endsWith(Trait::latin1ToString(
":"))) {
4110 table->setColumnAlignment(table->columnsCount(), a);
4115 fr.m_data.erase(fr.m_data.cbegin() + 1);
4117 long long int r = 0;
4119 for (
const auto &line : std::as_const(fr.m_data)) {
4120 if (!parseTableRow(line)) {
4127 fr.m_data.erase(fr.m_data.cbegin(), fr.m_data.cbegin() + r);
4129 if (!table->isEmpty() && !collectRefLinks) {
4130 parent->appendItem(table);
4136template<
class Trait>
4138isH(
const typename Trait::String &s,
4139 const typename Trait::Char &c)
4147 const auto start = p;
4149 for (; p < s.size(); ++p) {
4155 if (p -
start < 1) {
4159 for (; p < s.size(); ++p) {
4160 if (!s[p].isSpace()) {
4169template<
class Trait>
4171isH1(
const typename Trait::String &s)
4173 return isH<Trait>(s, Trait::latin1ToChar(
'='));
4177template<
class Trait>
4179isH2(
const typename Trait::String &s)
4181 return isH<Trait>(s, Trait::latin1ToChar(
'-'));
4185template<
class Trait>
4186inline std::pair<long long int, long long int>
4192 return {pos - 1, line};
4195 for (
long long int i = 0; i < static_cast<long long int>(fr.
m_data.size()); ++i) {
4196 if (fr.
m_data.at(i).second.m_lineNumber == line) {
4198 return {fr.
m_data.at(i - 1).first.virginPos(fr.
m_data.at(i - 1).first.length() - 1),
4208template<
class Trait>
4209inline std::pair<long long int, long long int>
4214 for (
long long int i = 0; i < static_cast<long long int>(fr.
m_data.size()); ++i) {
4215 if (fr.
m_data.at(i).second.m_lineNumber == line) {
4216 if (fr.
m_data.at(i).first.virginPos(fr.
m_data.at(i).first.length() - 1) >= pos + 1) {
4217 return {pos + 1, line};
4218 }
else if (i + 1 <
static_cast<long long int>(fr.
m_data.size())) {
4219 return {fr.
m_data.at(i + 1).first.virginPos(0), fr.
m_data.at(i + 1).second.m_lineNumber};
4229template<
class Trait>
4232 std::shared_ptr<Block<Trait>> parent,
4234 typename Trait::StringList &linksToParse,
4235 const typename Trait::String &workingPath,
4236 const typename Trait::String &fileName,
4237 bool collectRefLinks,
4238 RawHtmlBlock<Trait> &html)
4240 return parseFormattedTextLinksImages(fr, parent, doc, linksToParse, workingPath, fileName,
4241 collectRefLinks,
false, html,
false);
4244template<
class Trait>
4249 return html->isFreeTag();
4255 html->setFreeTag(on);
4259template<
class Trait>
4265 for (
long long int line = 0; line < (
long long int)fr.size(); ++line) {
4266 const typename Trait::String &str = fr.at(line).first.asString();
4268 const auto withoutSpaces = str.sliced(p);
4271 d.push_back({Delimiter::HorizontalLine, line, 0, str.length(),
false,
false,
false});
4273 d.push_back({Delimiter::H1, line, 0, str.length(),
false,
false,
false});
4275 d.push_back({Delimiter::H2, line, 0, str.length(),
false,
false,
false});
4277 bool backslash =
false;
4280 for (
long long int i = p; i < str.size(); ++i) {
4283 if (str[i] == Trait::latin1ToChar(
'\\') && !backslash) {
4288 else if ((str[i] == Trait::latin1ToChar(
'_') || str[i] == Trait::latin1ToChar(
'*')) && !backslash) {
4289 typename Trait::String style;
4291 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() :
true);
4292 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4293 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4294 const bool alNumBefore = (i > 0 ? str[i - 1].isLetterOrNumber() :
false);
4296 const auto ch = str[i];
4298 while (i < str.length() && str[i] == ch) {
4299 style.push_back(str[i]);
4303 typename Delimiter::DelimiterType dt = Delimiter::Unknown;
4305 if (ch == Trait::latin1ToChar(
'*')) {
4306 dt = Delimiter::Emphasis1;
4308 dt = Delimiter::Emphasis2;
4311 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() :
true);
4312 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) :
true);
4313 const bool alNumAfter = (i < str.length() ? str[i].isLetterOrNumber() :
false);
4314 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore))
4315 && !(ch == Trait::latin1ToChar(
'_') && alNumBefore && alNumAfter);
4316 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)))
4317 && !(ch == Trait::latin1ToChar(
'_') && alNumBefore && alNumAfter);
4319 if (leftFlanking || rightFlanking) {
4320 for (
auto j = 0; j < style.length(); ++j) {
4321 d.push_back({dt, line, i - style.length() + j, 1,
4322 word,
false, leftFlanking, rightFlanking});
4333 else if (str[i] == Trait::latin1ToChar(
'~') && !backslash) {
4334 typename Trait::String style;
4336 const bool punctBefore = (i > 0 ? str[i - 1].isPunct() || str[i - 1].isSymbol() :
true);
4337 const bool uWhitespaceBefore = (i > 0 ? Trait::isUnicodeWhitespace(str[i - 1]) : true);
4338 const bool uWhitespaceOrPunctBefore = uWhitespaceBefore || punctBefore;
4340 while (i < str.length() && str[i] == Trait::latin1ToChar(
'~')) {
4341 style.push_back(str[i]);
4345 if (style.length() <= 2) {
4346 const bool punctAfter = (i < str.length() ? str[i].isPunct() || str[i].isSymbol() : true);
4347 const bool uWhitespaceAfter = (i < str.length() ? Trait::isUnicodeWhitespace(str[i]) : true);
4348 const bool leftFlanking = !uWhitespaceAfter && (!punctAfter || (punctAfter && uWhitespaceOrPunctBefore));
4349 const bool rightFlanking = !uWhitespaceBefore && (!punctBefore || (punctBefore && (uWhitespaceAfter || punctAfter)));
4351 if (leftFlanking || rightFlanking) {
4352 d.push_back({Delimiter::Strikethrough,
4372 else if (str[i] == Trait::latin1ToChar(
'[') && !backslash) {
4373 d.push_back({Delimiter::SquareBracketsOpen, line, i, 1, word,
false});
4378 else if (str[i] == Trait::latin1ToChar(
'!') && !backslash) {
4379 if (i + 1 < str.length()) {
4380 if (str[i + 1] == Trait::latin1ToChar(
'[')) {
4381 d.push_back({Delimiter::ImageOpen, line, i, 2, word,
false});
4394 else if (str[i] == Trait::latin1ToChar(
'(') && !backslash) {
4395 d.push_back({Delimiter::ParenthesesOpen, line, i, 1, word,
false});
4400 else if (str[i] == Trait::latin1ToChar(
']') && !backslash) {
4401 d.push_back({Delimiter::SquareBracketsClose, line, i, 1, word,
false});
4406 else if (str[i] == Trait::latin1ToChar(
')') && !backslash) {
4407 d.push_back({Delimiter::ParenthesesClose, line, i, 1, word,
false});
4412 else if (str[i] == Trait::latin1ToChar(
'<') && !backslash) {
4413 d.push_back({Delimiter::Less, line, i, 1, word,
false});
4418 else if (str[i] == Trait::latin1ToChar(
'>') && !backslash) {
4419 d.push_back({Delimiter::Greater, line, i, 1, word,
false});
4424 else if (str[i] == Trait::latin1ToChar(
'`')) {
4425 typename Trait::String code;
4427 while (i < str.length() && str[i] == Trait::latin1ToChar(
'`')) {
4428 code.push_back(str[i]);
4432 d.push_back({Delimiter::InlineCode,
4434 i - code.length() - (backslash ? 1 : 0),
4435 code.length() + (backslash ? 1 : 0),
4444 else if (str[i] == Trait::latin1ToChar(
'$')) {
4445 typename Trait::String m;
4447 while (i < str.length() && str[i] == Trait::latin1ToChar(
'$')) {
4448 m.push_back(str[i]);
4452 if (m.length() <= 2 && !backslash) {
4453 d.push_back({Delimiter::Math, line, i - m.length(), m.length(),
4454 false,
false,
false,
false});
4475template<
class Trait>
4479 long long int count = 0, pos = s.length() - 1, end = s.length() - 1;
4481 while ((pos = Trait::lastIndexOf(s, Trait::latin1ToString(
"\\"), pos)) != -1 && pos == end) {
4487 return (s.endsWith(Trait::latin1ToString(
" ")) || (count % 2 != 0));
4491template<
class Trait>
4495 return (s.endsWith(Trait::latin1ToString(
" ")) ? 2 : 1);
4499template<
class Trait>
4500inline typename Trait::String
4503 if (s.endsWith(Trait::latin1ToString(
"\\"))) {
4504 return s.sliced(0, s.size() - 1);
4511template<
class Trait>
4522template<
class Trait>
4526 long long int startPos,
4527 long long int startLine,
4528 long long int endPos,
4529 long long int endLine,
4530 bool doRemoveSpacesAtEnd =
false)
4532 if (endPos < 0 && endLine - 1 >= 0) {
4533 endPos = po.
m_fr.m_data.at(endLine - 1).first.length() - 1;
4537 if (endPos == po.
m_fr.m_data.at(endLine).first.length() - 1) {
4538 doRemoveSpacesAtEnd =
true;
4543 if (doRemoveSpacesAtEnd) {
4547 if (startPos == 0) {
4558 std::shared_ptr<Text<Trait>> t;
4566 t->setStartColumn(po.
m_fr.m_data.at(startLine).first.virginPos(startPos));
4567 t->setStartLine(po.
m_fr.m_data.at(startLine).second.m_lineNumber);
4568 t->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endPos,
true));
4569 t->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4573 po.
m_parent->setEndColumn(t->endColumn());
4574 po.
m_parent->setEndLine(t->endLine());
4587 po.
m_pos = startPos;
4592template<
class Trait>
4596 long long int startPos,
4597 long long int startLine,
4598 long long int endPos,
4599 long long int endLine)
4601 makeTextObject(text, po, startPos, startLine, endPos, endLine,
true);
4603 std::shared_ptr<LineBreak<Trait>> hr;
4607 hr->setText(po.
m_fr.m_data.at(endLine).first.asString().sliced(endPos + 1));
4608 hr->setStartColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endPos + 1));
4609 hr->setStartLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4610 hr->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(po.
m_fr.m_data.at(endLine).first.length() - 1));
4611 hr->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
4612 po.
m_parent->setEndColumn(hr->endColumn());
4613 po.
m_parent->setEndLine(hr->endLine());
4625template<
class Trait>
4628 long long int lastLine)
4633 for (; i <= lastLine; ++i) {
4635 const auto c = i + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) ?
4638 if (h && c && c == h) {
4655template<
class Trait>
4662 long long int lastLine,
4664 long long int lastPos,
4667 if (po.
m_line > lastLine) {
4669 }
else if (po.
m_line == lastLine && po.
m_pos >= lastPos) {
4673 typename Trait::String text;
4675 const auto isLastChar = po.
m_pos >= po.
m_fr.m_data.at(po.
m_line).first.length();
4676 long long int startPos = (isLastChar ? 0 : po.
m_pos);
4677 long long int startLine = (isLastChar ? po.
m_line + 1 : po.
m_line);
4681 (po.
m_line == lastLine ? (lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
4686 auto makeTOWLB = [&]() {
4687 if (po.
m_line != (
long long int)(po.
m_fr.m_data.size() - 1)) {
4688 const auto &line = po.
m_fr.m_data.at(po.
m_line).first.asString();
4694 startLine = po.
m_line + 1;
4705 const auto length = (po.
m_line == lastLine ?
4707 const auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(po.
m_pos, length);
4716 po.
m_line == lastLine ? lastPos - 1 : po.
m_fr.m_data.at(po.
m_line).first.length() - 1,
4722 if (po.
m_line != lastLine) {
4733 po.
m_fr.m_data.at(po.
m_line).first.virginSubString());
4747 lastPos == po.
m_fr.m_data.at(po.
m_line).first.length() &&
4750 auto s = po.
m_fr.m_data.at(po.
m_line).first.virginSubString(0, lastPos);
4768template<
class Trait>
4774 while (l < (
long long int)fr.size()) {
4777 if (p < fr[l].first.length()) {
4787template<
class Trait>
4788inline std::pair<bool, bool>
4793 static const typename Trait::String notAllowed = Trait::latin1ToString(
"\"`=<'");
4795 const auto start = p;
4797 for (; p < fr[l].first.length(); ++p) {
4798 if (fr[l].first[p].isSpace()) {
4800 }
else if (notAllowed.contains(fr[l].first[p])) {
4801 return {
false,
false};
4802 }
else if (fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4811template<
class Trait>
4812inline std::pair<bool, bool>
4817 if (p < fr[l].first.length() && fr[l].first[p] != Trait::latin1ToChar(
'"') &&
4818 fr[l].first[p] != Trait::latin1ToChar(
'\'')) {
4822 const auto s = fr[l].first[p];
4826 if (p >= fr[l].first.length()) {
4827 return {
false,
false};
4830 for (; l < (
long long int)fr.size(); ++l) {
4831 bool doBreak =
false;
4833 for (; p < fr[l].first.length(); ++p) {
4834 const auto ch = fr[l].first[p];
4850 if (l >= (
long long int)fr.size()) {
4851 return {
false,
false};
4854 if (p >= fr[l].first.length()) {
4855 return {
false,
false};
4858 if (fr[l].first[p] != s) {
4859 return {
false,
false};
4864 return {
true,
true};
4868template<
class Trait>
4869inline std::pair<bool, bool>
4875 long long int tl = l, tp = p;
4879 if (l >= (
long long int)fr.size()) {
4880 return {
false,
false};
4884 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'/')) {
4885 return {
false,
true};
4889 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4890 return {
false,
true};
4893 if (checkForSpace) {
4894 if (tl == l && tp == p) {
4895 return {
false,
false};
4899 const auto start = p;
4901 for (; p < fr[l].first.length(); ++p) {
4902 const auto ch = fr[l].first[p];
4904 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'=')) {
4909 const typename Trait::String name = fr[l].first.asString().sliced(
start, p -
start).toLower();
4911 if (!name.
startsWith(Trait::latin1ToString(
"_")) && !name.
startsWith(Trait::latin1ToString(
":")) &&
4913 return {
false,
false};
4916 static const typename Trait::String allowedInName =
4917 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789_.:-");
4919 for (
long long int i = 1; i < name.
length(); ++i) {
4920 if (!allowedInName.contains(name[i])) {
4921 return {
false,
false};
4926 if (p < fr[l].first.length() && fr[l].first[p] == Trait::latin1ToChar(
'>')) {
4927 return {
false,
true};
4935 if (l >= (
long long int)fr.size()) {
4936 return {
false,
false};
4940 if (p < fr[l].first.length()) {
4941 if (fr[l].first[p] != Trait::latin1ToChar(
'=')) {
4945 return {
true,
true};
4950 return {
true,
false};
4955 if (l >= (
long long int)fr.size()) {
4956 return {
false,
false};
4963template<
class Trait>
4964inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
4965isHtmlTag(
long long int line,
long long int pos, TextParsingOpts<Trait> &po,
int rule);
4968template<
class Trait>
4975 static const std::set<typename Trait::String> s_rule1Finish = {Trait::latin1ToString(
"/pre"),
4976 Trait::latin1ToString(
"/script"),
4977 Trait::latin1ToString(
"/style"),
4978 Trait::latin1ToString(
"/textarea")};
4982 while (p < po.
m_fr.m_data[line].first.length()) {
4986 typename Trait::String tag;
4988 std::tie(ok, l, p, std::ignore, tag) =
isHtmlTag(line, p, po, rule);
5001 if (s_rule1Finish.find(tag.toLower()) != s_rule1Finish.cend() && l == line) {
5013 if (p >= po.
m_fr.m_data[line].first.length()) {
5021template<
class Trait>
5024 long long int startLine,
5025 long long int endLine)
5027 for (; startLine <= endLine; ++startLine) {
5029 const auto line = po.
m_fr.m_data.at(startLine).first.asString().sliced(pos);
5040template<
class Trait>
5041inline std::tuple<bool, long long int, long long int, bool, typename Trait::String>
5047 if (po.
m_fr.m_data[line].first[pos] != Trait::latin1ToChar(
'<')) {
5048 return {
false, line, pos,
false, {}};
5051 typename Trait::String tag;
5053 long long int l = line;
5054 long long int p = pos + 1;
5059 first = (tmp == pos);
5062 if (p >= po.
m_fr.m_data[l].first.length()) {
5063 return {
false, line, pos, first, tag};
5066 bool closing =
false;
5068 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5071 tag.push_back(Trait::latin1ToChar(
'/'));
5076 const auto start = p;
5079 for (; p < po.
m_fr.m_data[l].first.length(); ++p) {
5080 const auto ch = po.
m_fr.m_data[l].first[p];
5082 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>') || ch == Trait::latin1ToChar(
'/')) {
5087 tag.push_back(po.
m_fr.m_data[l].first.asString().sliced(
start, p -
start));
5089 if (p < po.
m_fr.m_data[l].first.length() && po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5090 if (p + 1 < po.
m_fr.m_data[l].first.length() &&
5091 po.
m_fr.m_data[l].first[p + 1] == Trait::latin1ToChar(
'>')) {
5092 long long int tmp = 0;
5098 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5102 return {
true, l, p + 1, onLine, tag};
5104 return {
false, line, pos, first, tag};
5107 return {
false, line, pos, first, tag};
5111 if (p < po.
m_fr.m_data[l].first.length() && po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5112 long long int tmp = 0;
5118 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5122 return {
true, l, p, onLine, tag};
5124 return {
false, line, pos, first, tag};
5130 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5131 return {
false, line, pos, first, tag};
5134 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5135 long long int tmp = 0;
5141 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5145 return {
true, l, p, onLine, tag};
5147 return {
false, line, pos, first, tag};
5152 bool firstAttr =
true;
5161 if (closing && attr) {
5162 return {
false, line, pos, first, tag};
5166 return {
false, line, pos, first, tag};
5170 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'/')) {
5175 if (l >= (
long long int)po.
m_fr.m_data.size()) {
5176 return {
false, line, pos, first, tag};
5180 if (po.
m_fr.m_data[l].first[p] == Trait::latin1ToChar(
'>')) {
5181 long long int tmp = 0;
5187 bool onLine = (first && (rule == 7 ? tmp == po.
m_fr.m_data[l].first.length() :
5191 return {
true, l, p, onLine, tag};
5193 return {
false, line, pos, first, tag};
5197 return {
false, line, pos, first, {}};
5201template<
class Trait>
5202inline std::pair<typename Trait::String, bool>
5204 TextParsingOpts<Trait> &po)
5206 long long int i = it->m_pos + 1;
5207 const auto start = i;
5209 if (
start >= po.m_fr.m_data[it->m_line].first.length()) {
5213 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5214 const auto ch = po.m_fr.m_data[it->m_line].first[i];
5216 if (ch.isSpace() || ch == Trait::latin1ToChar(
'>')) {
5221 return {po.m_fr.m_data[it->m_line].first.asString().sliced(
start, i -
start),
5222 i < po.m_fr.m_data[it->m_line].first.length() ?
5223 po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'>') : false};
5226template<
class Trait>
5229 typename Delims::iterator last,
5230 TextParsingOpts<Trait> &po)
5234 for (; it != last; ++it) {
5235 if ((it->m_line == po.m_line && it->m_pos < po.m_pos) || it->m_line < po.m_line) {
5246template<
class Trait>
5250 long long int toLine,
5251 long long int toPos,
5256 bool continueEating =
false)
5260 if (line <= toLine) {
5261 typename Trait::String h = po.
m_html.m_html->text();
5263 if (!h.isEmpty() && !continueEating) {
5264 for (
long long int i = 0; i < po.
m_fr.m_emptyLinesBefore; ++i) {
5265 h.push_back(Trait::latin1ToChar(
'\n'));
5269 const auto first = po.
m_fr.m_data[line].first.asString().sliced(
5271 (line == toLine ? (toPos >= 0 ? toPos - pos : po.
m_fr.m_data[line].first.length() - pos) :
5272 po.
m_fr.m_data[line].first.length() - pos));
5274 if (!h.isEmpty() && !first.isEmpty() && po.
m_html.m_html->endLine() != po.
m_fr.m_data[line].second.m_lineNumber) {
5275 h.push_back(Trait::latin1ToChar(
'\n'));
5278 if (!first.isEmpty()) {
5284 for (; line < toLine; ++line) {
5285 h.push_back(Trait::latin1ToChar(
'\n'));
5286 h.push_back(po.
m_fr.m_data[line].first.asString());
5289 if (line == toLine && toPos != 0) {
5290 h.push_back(Trait::latin1ToChar(
'\n'));
5291 h.push_back(po.
m_fr.m_data[line].first.asString().sliced(0, toPos > 0 ?
5292 toPos : po.
m_fr.m_data[line].first.length()));
5295 auto endColumn = toPos;
5296 auto endLine = toLine;
5298 if (endColumn == 0 && endLine > 0) {
5300 endColumn = po.
m_fr.m_data.at(endLine).first.length();
5303 po.
m_html.m_html->setEndColumn(po.
m_fr.m_data.at(endLine).first.virginPos(endColumn >= 0 ?
5304 endColumn - 1 : po.
m_fr.m_data.at(endLine).first.length() - 1));
5305 po.
m_html.m_html->setEndLine(po.
m_fr.m_data.at(endLine).second.m_lineNumber);
5307 po.
m_line = (toPos >= 0 ? toLine : toLine + 1);
5308 po.
m_pos = (toPos >= 0 ? toPos : 0);
5310 if (po.
m_line + 1 <
static_cast<long long int>(po.
m_fr.m_data.size()) &&
5316 po.
m_html.m_html->setText(h);
5338 const auto online = po.
m_html.m_onLine;
5346 po.
m_html.m_continueHtml =
true;
5350template<
class Trait>
5353 typename Delims::iterator last,
5356 TextParsingOpts<Trait> &po,
5359 bool continueEating)
5361 long long int emptyLine = line;
5363 if (po.m_fr.m_emptyLinesBefore > 0 && po.m_html.m_html && po.m_html.m_continueHtml) {
5364 po.m_html.m_continueHtml =
false;
5368 for (
auto it = po.m_fr.m_data.cbegin() + line, last = po.m_fr.m_data.cend(); it != last; ++it) {
5369 if (it->first.asString().simplified().isEmpty()) {
5376 if (emptyLine <
static_cast<long long int>(po.m_fr.m_data.size())) {
5377 eatRawHtml(line, pos, emptyLine, 0, po,
true, htmlRule, onLine, continueEating);
5379 return findIt(it, last, po);
5381 eatRawHtml(line, pos, po.m_fr.m_data.size() - 1, -1, po,
false, htmlRule, onLine, continueEating);
5384 return std::prev(last);
5391template<
class Trait>
5394 long long int startLine,
5395 long long int endLine)
5397 for (
auto i = startLine + 1; i <= endLine; ++i) {
5398 const auto type = whatIsTheLine(fr.m_data[i].first);
5416 const auto ns = skipSpaces<Trait>(0, fr.m_data[i].first.asString());
5419 const auto s = fr.m_data[i].first.asString().sliced(ns);
5421 if (isHorizontalLine<Trait>(s) || isH1<Trait>(s) || isH2<Trait>(s)) {
5430template<
class Trait>
5433 typename Delims::iterator last,
5434 TextParsingOpts<Trait> &po,
5437 static const std::set<typename Trait::String> s_finish = {Trait::latin1ToString(
"/pre"),
5438 Trait::latin1ToString(
"/script"),
5439 Trait::latin1ToString(
"/style"),
5440 Trait::latin1ToString(
"/textarea")};
5444 long long int l = -1, p = -1;
5446 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less && skipFirst) {
5447 std::tie(ok, l, p, po.m_html.m_onLine, std::ignore) =
5448 isHtmlTag(it->m_line, it->m_pos, po, 1);
5451 if (po.m_html.m_onLine) {
5452 for (it = (skipFirst && it != last ? std::next(it) : it); it != last; ++it) {
5453 if (it->m_type == Delimiter::Less) {
5454 typename Trait::String tag;
5455 bool closed =
false;
5457 std::tie(tag, closed) = readHtmlTag(it, po);
5460 if (s_finish.find(tag.toLower()) != s_finish.cend()) {
5461 eatRawHtml(po.m_line, po.m_pos, it->m_line, -1, po,
5462 true, 1, po.m_html.m_onLine);
5469 }
else if (ok && !isNewBlockIn(po.m_fr, it->m_line, l)) {
5470 eatRawHtml(po.m_line, po.m_pos, l, p + 1, po,
true, 1,
false);
5480 if (po.m_html.m_onLine) {
5481 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 1, po.m_html.m_onLine);
5487template<
class Trait>
5490 typename Delims::iterator last,
5491 TextParsingOpts<Trait> &po)
5494 const auto start = it;
5496 MdLineData::CommentData commentData = {2,
true};
5497 bool onLine = po.m_html.m_onLine;
5499 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5500 long long int i = po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos);
5502 commentData = po.m_fr.m_data[it->m_line].second.m_htmlCommentData[i];
5504 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5505 po.m_html.m_onLine = onLine;
5508 if (commentData.first != -1 && commentData.second) {
5509 for (; it != last; ++it) {
5510 if (it->m_type == Delimiter::Greater) {
5513 bool doContinue =
false;
5515 for (
char i = 0; i < commentData.first; ++i) {
5516 if (!(p > 0 && po.m_fr.m_data[it->m_line].first[p - 1] == Trait::latin1ToChar(
'-'))) {
5529 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5531 onLine ? po.m_fr.m_data[it->m_line].first.length() : it->m_pos + 1,
5532 po,
true, 2, onLine);
5543 if (po.m_html.m_onLine) {
5544 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 2, po.m_html.m_onLine);
5550template<
class Trait>
5553 typename Delims::iterator last,
5554 TextParsingOpts<Trait> &po)
5556 bool onLine = po.m_html.m_onLine;
5559 const auto start = it;
5561 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5562 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5563 po.m_html.m_onLine = onLine;
5566 for (; it != last; ++it) {
5567 if (it->m_type == Delimiter::Greater) {
5568 if (it->m_pos > 0 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar(
'?')) {
5569 long long int i = it->m_pos + 1;
5571 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5572 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5577 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5578 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 3, onLine);
5589 if (po.m_html.m_onLine) {
5590 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 3, onLine);
5596template<
class Trait>
5599 typename Delims::iterator last,
5600 TextParsingOpts<Trait> &po)
5603 const auto start = it;
5605 bool onLine = po.m_html.m_onLine;
5607 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5608 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5609 po.m_html.m_onLine = onLine;
5612 for (; it != last; ++it) {
5613 if (it->m_type == Delimiter::Greater) {
5614 long long int i = it->m_pos + 1;
5616 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5617 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5622 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5623 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 4, onLine);
5633 if (po.m_html.m_onLine) {
5634 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 4,
true);
5640template<
class Trait>
5643 typename Delims::iterator last,
5644 TextParsingOpts<Trait> &po)
5647 const auto start = it;
5649 bool onLine = po.m_html.m_onLine;
5651 if (po.m_html.m_html->text().isEmpty() && it->m_type == Delimiter::Less) {
5652 onLine = (it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()));
5653 po.m_html.m_onLine = onLine;
5656 for (; it != last; ++it) {
5657 if (it->m_type == Delimiter::Greater) {
5658 if (it->m_pos > 1 && po.m_fr.m_data[it->m_line].first[it->m_pos - 1] == Trait::latin1ToChar(
']') &&
5659 po.m_fr.m_data[it->m_line].first[it->m_pos - 2] == Trait::latin1ToChar(
']')) {
5660 long long int i = it->m_pos + 1;
5662 for (; i < po.m_fr.m_data[it->m_line].first.length(); ++i) {
5663 if (po.m_fr.m_data[it->m_line].first[i] == Trait::latin1ToChar(
'<')) {
5668 if (onLine || !isNewBlockIn(po.m_fr,
start->m_line, it->m_line)) {
5669 eatRawHtml(po.m_line, po.m_pos, it->m_line, i, po,
true, 5, onLine);
5680 if (po.m_html.m_onLine) {
5681 eatRawHtml(po.m_line, po.m_pos, po.m_fr.m_data.size() - 1, -1, po,
false, 5,
true);
5687template<
class Trait>
5690 typename Delims::iterator last,
5691 TextParsingOpts<Trait> &po)
5693 po.m_html.m_onLine = (it != last ?
5694 it->m_pos == skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString()) : true);
5696 if (po.m_html.m_onLine) {
5697 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 6, true, true);
5699 const auto nit = std::find_if(std::next(it), last, [](const auto &d) {
5700 return (d.m_type == Delimiter::Greater);
5703 if (nit != last && !isNewBlockIn(po.m_fr, it->m_line, nit->m_line)) {
5704 eatRawHtml(po.m_line, po.m_pos, nit->m_line, nit->m_pos + nit->m_len, po,
5710template<
class Trait>
5713 typename Delims::iterator last,
5714 TextParsingOpts<Trait> &po)
5716 if (po.m_html.m_onLine) {
5717 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 7,
true,
true);
5718 }
else if (it != last) {
5719 const auto start = it;
5720 long long int l = -1, p = -1;
5721 bool onLine =
false;
5724 std::tie(ok, l, p, onLine, std::ignore) =
isHtmlTag(it->m_line, it->m_pos, po, 7);
5726 onLine = onLine && it->m_line == 0 && l ==
start->m_line;
5729 eatRawHtml(po.m_line, po.m_pos, l, ++p, po, !onLine, 7, onLine);
5731 po.m_html.m_onLine = onLine;
5734 eatRawHtmlTillEmptyLine(it, last, po.m_line, po.m_pos, po, 7, onLine,
true);
5742template<
class Trait>
5745 typename Delims::iterator last,
5746 TextParsingOpts<Trait> &po,
5749 po.m_detected = TextParsingOpts<Trait>::Detected::HTML;
5751 switch (po.m_html.m_htmlBlockType) {
5753 finishRule1HtmlTag(it, last, po, skipFirst);
5757 finishRule2HtmlTag(it, last, po);
5761 finishRule3HtmlTag(it, last, po);
5765 finishRule4HtmlTag(it, last, po);
5769 finishRule5HtmlTag(it, last, po);
5773 finishRule6HtmlTag(it, last, po);
5777 finishRule7HtmlTag(it, last, po);
5781 po.m_detected = TextParsingOpts<Trait>::Detected::Nothing;
5785 return findIt(it, last, po);
5788template<
class Trait>
5791 typename Delims::iterator last,
5792 TextParsingOpts<Trait> &po)
5796 typename Trait::String tag;
5798 std::tie(tag, std::ignore) = readHtmlTag(it, po);
5800 if (tag.startsWith(Trait::latin1ToString(
"![CDATA["))) {
5804 tag = tag.toLower();
5806 static const typename Trait::String s_validHtmlTagLetters =
5807 Trait::latin1ToString(
"abcdefghijklmnopqrstuvwxyz0123456789-");
5809 bool closing =
false;
5811 if (tag.startsWith(Trait::latin1ToString(
"/"))) {
5816 if (tag.endsWith(Trait::latin1ToString(
"/"))) {
5817 tag.remove(tag.size() - 1, 1);
5820 if (tag.isEmpty()) {
5824 if (!tag.startsWith(Trait::latin1ToString(
"!")) &&
5825 !tag.startsWith(Trait::latin1ToString(
"?")) &&
5826 !(tag[0].unicode() >= 97 && tag[0].unicode() <= 122)) {
5830 static const std::set<typename Trait::String> s_rule1 = {Trait::latin1ToString(
"pre"),
5831 Trait::latin1ToString(
"script"),
5832 Trait::latin1ToString(
"style"),
5833 Trait::latin1ToString(
"textarea")};
5835 if (!closing && s_rule1.find(tag) != s_rule1.cend()) {
5837 }
else if (tag.startsWith(Trait::latin1ToString(
"!--"))) {
5839 }
else if (tag.startsWith(Trait::latin1ToString(
"?"))) {
5841 }
else if (tag.startsWith(Trait::latin1ToString(
"!")) && tag.size() > 1 &&
5842 ((tag[1].unicode() >= 65 && tag[1].unicode() <= 90) ||
5843 (tag[1].unicode() >= 97 && tag[1].unicode() <= 122))) {
5846 static const std::set<typename Trait::String> s_rule6 = {
5847 Trait::latin1ToString(
"address"), Trait::latin1ToString(
"article"), Trait::latin1ToString(
"aside"), Trait::latin1ToString(
"base"),
5848 Trait::latin1ToString(
"basefont"), Trait::latin1ToString(
"blockquote"), Trait::latin1ToString(
"body"), Trait::latin1ToString(
"caption"),
5849 Trait::latin1ToString(
"center"), Trait::latin1ToString(
"col"), Trait::latin1ToString(
"colgroup"), Trait::latin1ToString(
"dd"),
5850 Trait::latin1ToString(
"details"), Trait::latin1ToString(
"dialog"), Trait::latin1ToString(
"dir"), Trait::latin1ToString(
"div"),
5851 Trait::latin1ToString(
"dl"), Trait::latin1ToString(
"dt"), Trait::latin1ToString(
"fieldset"), Trait::latin1ToString(
"figcaption"),
5852 Trait::latin1ToString(
"figure"), Trait::latin1ToString(
"footer"), Trait::latin1ToString(
"form"), Trait::latin1ToString(
"frame"),
5853 Trait::latin1ToString(
"frameset"), Trait::latin1ToString(
"h1"), Trait::latin1ToString(
"h2"), Trait::latin1ToString(
"h3"),
5854 Trait::latin1ToString(
"h4"), Trait::latin1ToString(
"h5"), Trait::latin1ToString(
"h6"), Trait::latin1ToString(
"head"),
5855 Trait::latin1ToString(
"header"), Trait::latin1ToString(
"hr"), Trait::latin1ToString(
"html"), Trait::latin1ToString(
"iframe"),
5856 Trait::latin1ToString(
"legend"), Trait::latin1ToString(
"li"), Trait::latin1ToString(
"link"), Trait::latin1ToString(
"main"),
5857 Trait::latin1ToString(
"menu"), Trait::latin1ToString(
"menuitem"), Trait::latin1ToString(
"nav"), Trait::latin1ToString(
"noframes"),
5858 Trait::latin1ToString(
"ol"), Trait::latin1ToString(
"optgroup"), Trait::latin1ToString(
"option"), Trait::latin1ToString(
"p"),
5859 Trait::latin1ToString(
"param"), Trait::latin1ToString(
"section"), Trait::latin1ToString(
"search"), Trait::latin1ToString(
"summary"),
5860 Trait::latin1ToString(
"table"), Trait::latin1ToString(
"tbody"), Trait::latin1ToString(
"td"), Trait::latin1ToString(
"tfoot"),
5861 Trait::latin1ToString(
"th"), Trait::latin1ToString(
"thead"), Trait::latin1ToString(
"title"), Trait::latin1ToString(
"tr"),
5862 Trait::latin1ToString(
"track"), Trait::latin1ToString(
"ul")};
5864 for (
long long int i = 1; i < tag.size(); ++i) {
5865 if (!s_validHtmlTagLetters.contains(tag[i])) {
5870 if (s_rule6.find(tag) != s_rule6.cend()) {
5875 std::tie(tag, std::ignore, std::ignore, std::ignore, std::ignore) =
5876 isHtmlTag(it->m_line, it->m_pos, po, 7);
5887template<
class Trait>
5890 typename Delims::iterator last,
5891 TextParsingOpts<Trait> &po)
5893 const auto rule = htmlTagRule(it, last, po);
5898 po.m_firstInParagraph =
false;
5903 po.m_html.m_htmlBlockType = rule;
5904 po.m_html.m_html.reset(
new RawHtml<Trait>);
5905 po.m_html.m_html->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
5906 po.m_html.m_html->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
5908 return finishRawHtmlTag(it, last, po,
true);
5911template<
class Trait>
5914 typename Delims::iterator last,
5915 TextParsingOpts<Trait> &po)
5917 po.m_wasRefLink =
false;
5918 po.m_firstInParagraph =
false;
5919 po.m_headingAllowed =
true;
5921 const auto end = std::find_if(std::next(it), last, [&](
const auto &d) {
5922 return (d.m_type == Delimiter::Math && d.m_len == it->m_len);
5925 if (end != last &&
end->m_line <= po.m_lastTextLine) {
5926 typename Trait::String math;
5928 if (it->m_line ==
end->m_line) {
5929 math = po.m_fr.m_data[it->m_line].first.asString().sliced(
5930 it->m_pos + it->m_len,
end->m_pos - (it->m_pos + it->m_len));
5932 math = po.m_fr.m_data[it->m_line].first.asString().sliced(it->m_pos + it->m_len);
5934 for (
long long int i = it->m_line + 1; i < end->m_line; ++i) {
5935 math.push_back(Trait::latin1ToChar(
'\n'));
5936 math.push_back(po.m_fr.m_data[i].first.asString());
5939 math.push_back(Trait::latin1ToChar(
'\n'));
5940 math.push_back(po.m_fr.m_data[
end->m_line].first.asString().sliced(0,
end->m_pos));
5943 if (!po.m_collectRefLinks) {
5944 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
5946 auto startLine = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
5947 auto startColumn = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len);
5949 if (it->m_pos + it->m_len >= po.m_fr.m_data.at(it->m_line).first.length()) {
5950 std::tie(startColumn, startLine) =
nextPosition(po.m_fr, startColumn, startLine);
5953 auto endColumn = po.m_fr.m_data.at(
end->m_line).first.virginPos(
end->m_pos);
5954 auto endLine = po.m_fr.m_data.at(
end->m_line).second.m_lineNumber;
5956 if (endColumn == 0) {
5957 std::tie(endColumn, endLine) =
prevPosition(po.m_fr, endColumn, endLine);
5962 m->setStartColumn(startColumn);
5963 m->setStartLine(startLine);
5964 m->setEndColumn(endColumn);
5965 m->setEndLine(endLine);
5966 m->setInline(it->m_len == 1);
5967 m->setStartDelim({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
5968 po.m_fr.m_data[it->m_line].second.m_lineNumber,
5969 po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
5970 po.m_fr.m_data[it->m_line].second.m_lineNumber});
5971 m->setEndDelim({po.m_fr.m_data[
end->m_line].first.virginPos(
end->m_pos),
5972 po.m_fr.m_data[
end->m_line].second.m_lineNumber,
5973 po.m_fr.m_data[
end->m_line].first.virginPos(
end->m_pos +
end->m_len - 1),
5974 po.m_fr.m_data[
end->m_line].second.m_lineNumber});
5975 m->setFensedCode(
false);
5977 initLastItemWithOpts<Trait>(po, m);
5979 if (math.startsWith(Trait::latin1ToString(
"`")) &&
5980 math.endsWith(Trait::latin1ToString(
"`")) &&
5981 !math.endsWith(Trait::latin1ToString(
"\\`")) &&
5982 math.length() > 1) {
5983 math = math.sliced(1, math.length() - 2);
5988 po.m_parent->appendItem(m);
5990 po.m_pos =
end->m_pos +
end->m_len;
5991 po.m_line =
end->m_line;
5992 po.m_lastText =
nullptr;
6001template<
class Trait>
6004 typename Delims::iterator last,
6005 TextParsingOpts<Trait> &po,
6008 const auto nit = std::find_if(std::next(it), last, [](
const auto &d) {
6009 return (d.m_type == Delimiter::Greater);
6013 if (nit->m_line == it->m_line) {
6014 const auto url = po.m_fr.m_data.at(it->m_line).first.asString().sliced(
6015 it->m_pos + 1, nit->m_pos - it->m_pos - 1);
6019 for (
long long int i = 0; i < url.size(); ++i) {
6020 if (url[i].isSpace()) {
6028 if (!isValidUrl<Trait>(url) && !isEmail<Trait>(url)) {
6034 if (!po.m_collectRefLinks) {
6035 std::shared_ptr<Link<Trait>> lnk(
new Link<Trait>);
6036 lnk->setStartColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
6037 lnk->setStartLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
6038 lnk->setEndColumn(po.m_fr.m_data.at(nit->m_line).first.virginPos(nit->m_pos + nit->m_len - 1));
6039 lnk->setEndLine(po.m_fr.m_data.at(nit->m_line).second.m_lineNumber);
6041 lnk->setOpts(po.m_opts);
6042 lnk->setTextPos({po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos + 1),
6043 po.m_fr.m_data[it->m_line].second.m_lineNumber,
6044 po.m_fr.m_data[nit->m_line].first.virginPos(nit->m_pos - 1),
6045 po.m_fr.m_data[nit->m_line].second.m_lineNumber});
6046 lnk->setUrlPos(lnk->textPos());
6047 po.m_parent->appendItem(lnk);
6050 po.m_wasRefLink =
false;
6051 po.m_firstInParagraph =
false;
6052 po.m_lastText =
nullptr;
6055 po.m_pos = nit->m_pos + nit->m_len;
6056 po.m_line = nit->m_line;
6061 return checkForRawHtml(it, last, po);
6064 return checkForRawHtml(it, last, po);
6067 return checkForRawHtml(it, last, po);
6071template<
class Trait>
6074 long long int startPos,
6075 long long int lastLine,
6076 long long int lastPos,
6077 TextParsingOpts<Trait> &po,
6078 typename Delims::iterator startDelimIt,
6079 typename Delims::iterator endDelimIt)
6081 typename Trait::String c;
6083 for (; po.m_line <= lastLine; ++po.m_line) {
6084 c.push_back(po.m_fr.m_data.at(po.m_line).first.asString().sliced(
6085 po.m_pos, (po.m_line == lastLine ? lastPos - po.m_pos :
6086 po.m_fr.m_data.at(po.m_line).first.length() - po.m_pos)));
6088 if (po.m_line < lastLine) {
6089 c.push_back(Trait::latin1ToChar(
' '));
6095 po.m_line = lastLine;
6097 if (c[0] == Trait::latin1ToChar(
' ') && c[c.size() - 1] == Trait::latin1ToChar(
' ') &&
6098 skipSpaces<Trait>(0, c) < c.size()) {
6100 c.remove(c.size() - 1, 1);
6106 auto code = std::make_shared<Code<Trait>>(c,
false,
true);
6108 code->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6109 code->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6110 code->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6111 code->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6112 code->setStartDelim({po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6113 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)),
6114 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber,
6115 po.m_fr.m_data.at(startDelimIt->m_line).first.virginPos(
6116 startDelimIt->m_pos + (startDelimIt->m_backslashed ? 1 : 0)) +
6117 startDelimIt->m_len - 1 - (startDelimIt->m_backslashed ? 1 : 0),
6118 po.m_fr.m_data.at(startDelimIt->m_line).second.m_lineNumber});
6120 {po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6121 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0)),
6122 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber,
6123 po.m_fr.m_data.at(endDelimIt->m_line).first.virginPos(
6124 endDelimIt->m_pos + (endDelimIt->m_backslashed ? 1 : 0) +
6125 endDelimIt->m_len - 1 - (endDelimIt->m_backslashed ? 1 : 0)),
6126 po.m_fr.m_data.at(endDelimIt->m_line).second.m_lineNumber});
6127 code->setOpts(po.m_opts);
6129 initLastItemWithOpts<Trait>(po, code);
6131 po.m_parent->appendItem(code);
6134 po.m_wasRefLink =
false;
6135 po.m_firstInParagraph =
false;
6136 po.m_lastText =
nullptr;
6139template<
class Trait>
6142 typename Delims::iterator last,
6143 TextParsingOpts<Trait> &po)
6145 const auto len = it->m_len;
6146 const auto start = it;
6148 po.m_wasRefLink =
false;
6149 po.m_firstInParagraph =
false;
6150 po.m_headingAllowed =
true;
6154 for (; it != last; ++it) {
6155 if (it->m_line <= po.m_lastTextLine) {
6156 const auto p = skipSpaces<Trait>(0, po.m_fr.m_data.at(it->m_line).first.asString());
6157 const auto withoutSpaces = po.m_fr.m_data.at(it->m_line).first.asString().sliced(p);
6159 if ((it->m_type == Delimiter::HorizontalLine && withoutSpaces[0] == Trait::latin1ToChar(
'-')) ||
6160 it->m_type == Delimiter::H1 || it->m_type == Delimiter::H2) {
6162 }
else if (it->m_type == Delimiter::InlineCode && (it->m_len - (it->m_backslashed ? 1 : 0)) == len) {
6165 if (!po.m_collectRefLinks) {
6168 makeInlineCode(
start->m_line,
start->m_pos +
start->m_len, it->m_line,
6169 it->m_pos + (it->m_backslashed ? 1 : 0), po,
start, it);
6171 po.m_line = it->m_line;
6172 po.m_pos = it->m_pos + it->m_len;
6187template<
class Trait>
6190 typename Delims::iterator it,
6191 typename Delims::iterator last,
6192 TextParsingOpts<Trait> &po,
6193 bool doNotCreateTextOnFail,
6196 if (it != last && it->m_line <= po.m_lastTextLine) {
6197 if (
start->m_line == it->m_line) {
6199 const auto n = it->m_pos - p;
6202 long long int startPos, startLine, endPos, endLine;
6204 po.m_fr.m_data[
start->m_line].first.virginPos(
6206 po.m_fr.m_data[
start->m_line].second.m_lineNumber);
6207 std::tie(endPos, endLine) =
6208 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6209 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6211 *pos = {startPos, startLine, endPos, endLine};
6214 return {{{po.m_fr.m_data.at(
start->m_line).first.sliced(p, n),
6215 {po.m_fr.m_data.at(
start->m_line).second.m_lineNumber}}}, it};
6217 if (it->m_line -
start->m_line < 3) {
6218 typename MdBlock<Trait>::Data res;
6219 res.push_back({po.m_fr.m_data.at(
start->m_line).first.sliced(
6220 start->m_pos +
start->m_len), po.m_fr.m_data.at(
start->m_line).second});
6222 long long int i =
start->m_line + 1;
6224 for (; i <= it->m_line; ++i) {
6225 if (i == it->m_line) {
6226 res.push_back({po.m_fr.m_data.at(i).first.sliced(0, it->m_pos),
6227 po.m_fr.m_data.at(i).second});
6229 res.push_back({po.m_fr.m_data.at(i).first, po.m_fr.m_data.at(i).second});
6234 long long int startPos, startLine, endPos, endLine;
6236 po.m_fr.m_data[
start->m_line].first.virginPos(
6238 po.m_fr.m_data[
start->m_line].second.m_lineNumber);
6239 std::tie(endPos, endLine) =
6240 prevPosition(po.m_fr, po.m_fr.m_data[it->m_line].first.virginPos(it->m_pos),
6241 po.m_fr.m_data[it->m_line].second.m_lineNumber);
6243 *pos = {startPos, startLine, endPos, endLine};
6248 if (!doNotCreateTextOnFail) {
6256 if (!doNotCreateTextOnFail) {
6264template<
class Trait>
6267 typename Delims::iterator last,
6268 TextParsingOpts<Trait> &po,
6271 const auto start = it;
6273 long long int brackets = 0;
6275 const bool collectRefLinks = po.m_collectRefLinks;
6276 po.m_collectRefLinks =
true;
6277 long long int l = po.m_line, p = po.m_pos;
6279 for (it = std::next(it); it != last; ++it) {
6282 switch (it->m_type) {
6283 case Delimiter::SquareBracketsClose: {
6290 case Delimiter::SquareBracketsOpen:
6291 case Delimiter::ImageOpen:
6295 case Delimiter::InlineCode:
6296 it = checkForInlineCode(it, last, po);
6299 case Delimiter::Less:
6300 it = checkForAutolinkHtml(it, last, po,
false);
6312 const auto r = readTextBetweenSquareBrackets(
start, it, last, po,
false, pos);
6314 po.m_collectRefLinks = collectRefLinks;
6322template<
class Trait>
6325 typename Delims::iterator last,
6326 TextParsingOpts<Trait> &po,
6329 const auto start = it;
6331 for (it = std::next(it); it != last; ++it) {
6334 switch (it->m_type) {
6335 case Delimiter::SquareBracketsClose: {
6339 case Delimiter::SquareBracketsOpen:
6340 case Delimiter::ImageOpen: {
6353 return readTextBetweenSquareBrackets(
start, it, last, po,
true, pos);
6356template<
class Trait>
6357inline typename Trait::String
6360 typename Trait::String res;
6363 for (
const auto &s : d) {
6365 res.push_back(Trait::latin1ToChar(
' '));
6367 res.push_back(s.first.asString().simplified());
6374template<
class Trait>
6375inline std::shared_ptr<Link<Trait>>
6377 const typename MdBlock<Trait>::Data &text,
6378 TextParsingOpts<Trait> &po,
6379 bool doNotCreateTextOnFail,
6380 long long int startLine,
6381 long long int startPos,
6382 long long int lastLine,
6383 long long int lastPos,
6384 const WithPosition &textPos,
6385 const WithPosition &urlPos)
6389 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ?
6390 url : removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6393 if (!u.startsWith(Trait::latin1ToString(
"#"))) {
6394 const auto checkForFile = [&](
typename Trait::String &url,
6395 const typename Trait::String &ref = {}) ->
bool {
6396 if (Trait::fileExists(url)) {
6397 url = Trait::absoluteFilePath(url);
6399 if (!po.m_collectRefLinks) {
6400 po.m_linksToParse.push_back(url);
6403 if (!ref.isEmpty()) {
6404 url = ref + Trait::latin1ToString(
"/") + url;
6408 }
else if (Trait::fileExists(url, po.m_workingPath)) {
6409 url = Trait::absoluteFilePath(po.m_workingPath + Trait::latin1ToString(
"/") + url);
6411 if (!po.m_collectRefLinks) {
6412 po.m_linksToParse.push_back(url);
6415 if (!ref.isEmpty()) {
6416 url = ref + Trait::latin1ToString(
"/") + url;
6425 if (!checkForFile(u) && u.contains(Trait::latin1ToChar(
'#'))) {
6426 const auto i = u.indexOf(Trait::latin1ToChar(
'#'));
6427 const auto ref = u.sliced(i);
6430 if (!checkForFile(u, ref)) {
6435 u = u + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
6436 Trait::latin1ToString(
"/") + po.m_workingPath) + Trait::latin1ToString(
"/") +
6442 link->setOpts(po.m_opts);
6443 link->setTextPos(textPos);
6444 link->setUrlPos(urlPos);
6446 MdBlock<Trait> block = {text, 0};
6448 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6450 RawHtmlBlock<Trait> html;
6452 parseFormattedTextLinksImages(block,
6453 std::static_pointer_cast<Block<Trait>>(p),
6458 po.m_collectRefLinks,
6463 if (!p->isEmpty()) {
6464 std::shared_ptr<Image<Trait>> img;
6466 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6467 const auto ip = std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0));
6469 for (
auto it = ip->items().cbegin(), last = ip->items().cend(); it != last; ++it) {
6470 switch ((*it)->type()) {
6471 case ItemType::Link:
6474 case ItemType::Image: {
6475 img = std::static_pointer_cast<Image<Trait>>(*it);
6491 if (html.m_html.get()) {
6492 link->p()->appendItem(html.m_html);
6495 link->setText(toSingleLine(removeBackslashes<Trait>(text)));
6496 link->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6497 link->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6498 link->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6499 link->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6501 initLastItemWithOpts<Trait>(po, link);
6503 po.m_lastText =
nullptr;
6508template<
class Trait>
6511 TextParsingOpts<Trait> &po,
6512 long long int startLine,
6513 long long int startPos,
6514 long long int lastLineForText,
6515 long long int lastPosForText,
6516 typename Delims::iterator lastIt,
6517 const typename MdBlock<Trait>::Data &linkText,
6518 bool doNotCreateTextOnFail,
6519 const WithPosition &textPos,
6520 const WithPosition &linkTextPos)
6522 const auto u = Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper();
6523 const auto url = u + Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
6524 typename Trait::String() : po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
6526 po.m_wasRefLink =
false;
6527 po.m_firstInParagraph =
false;
6528 po.m_headingAllowed =
true;
6530 if (po.m_doc->labeledLinks().find(url) != po.m_doc->labeledLinks().cend()) {
6531 if (!po.m_collectRefLinks) {
6532 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6534 const auto link = makeLink(u,
6535 (isLinkTextEmpty ? text : linkText),
6537 doNotCreateTextOnFail,
6541 lastIt->m_pos + lastIt->m_len,
6542 (isLinkTextEmpty ? textPos : linkTextPos),
6546 po.m_linksToParse.push_back(url);
6547 po.m_parent->appendItem(link);
6549 po.m_line = lastIt->m_line;
6550 po.m_pos = lastIt->m_pos + lastIt->m_len;
6552 if (!doNotCreateTextOnFail) {
6553 makeText(lastLineForText, lastPosForText, po);
6561 }
else if (!doNotCreateTextOnFail) {
6562 makeText(lastLineForText, lastPosForText, po);
6568template<
class Trait>
6569inline std::shared_ptr<Image<Trait>>
6571 const typename MdBlock<Trait>::Data &text,
6572 TextParsingOpts<Trait> &po,
6573 bool doNotCreateTextOnFail,
6574 long long int startLine,
6575 long long int startPos,
6576 long long int lastLine,
6577 long long int lastPos,
6578 const WithPosition &textPos,
6579 const WithPosition &urlPos)
6583 std::shared_ptr<Image<Trait>> img(
new Image<Trait>);
6585 typename Trait::String u = (url.startsWith(Trait::latin1ToString(
"#")) ? url :
6586 removeBackslashes<typename Trait::String, Trait>(replaceEntity<Trait>(url)));
6588 if (Trait::fileExists(u)) {
6590 }
else if (Trait::fileExists(u, po.m_workingPath)) {
6591 img->setUrl(po.m_workingPath + Trait::latin1ToString(
"/") + u);
6596 MdBlock<Trait> block = {text, 0};
6598 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
6600 RawHtmlBlock<Trait> html;
6602 parseFormattedTextLinksImages(block,
6603 std::static_pointer_cast<Block<Trait>>(p),
6608 po.m_collectRefLinks,
6613 if (!p->isEmpty()) {
6614 if (p->items().size() == 1 && p->items().at(0)->type() == ItemType::Paragraph) {
6615 img->setP(std::static_pointer_cast<Paragraph<Trait>>(p->items().at(0)));
6619 img->setText(toSingleLine(removeBackslashes<Trait>(text)));
6620 img->setStartColumn(po.m_fr.m_data.at(startLine).first.virginPos(startPos));
6621 img->setStartLine(po.m_fr.m_data.at(startLine).second.m_lineNumber);
6622 img->setEndColumn(po.m_fr.m_data.at(lastLine).first.virginPos(lastPos - 1));
6623 img->setEndLine(po.m_fr.m_data.at(lastLine).second.m_lineNumber);
6624 img->setTextPos(textPos);
6625 img->setUrlPos(urlPos);
6627 initLastItemWithOpts<Trait>(po, img);
6629 po.m_lastText =
nullptr;
6634template<
class Trait>
6637 TextParsingOpts<Trait> &po,
6638 long long int startLine,
6639 long long int startPos,
6640 long long int lastLineForText,
6641 long long int lastPosForText,
6642 typename Delims::iterator lastIt,
6643 const typename MdBlock<Trait>::Data &linkText,
6644 bool doNotCreateTextOnFail,
6645 const WithPosition &textPos,
6646 const WithPosition &linkTextPos)
6648 const auto url = Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper() +
6649 Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
6650 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
6652 po.m_wasRefLink =
false;
6653 po.m_firstInParagraph =
false;
6654 po.m_headingAllowed =
true;
6656 const auto iit = po.m_doc->labeledLinks().find(url);
6658 if (iit != po.m_doc->labeledLinks().cend()) {
6659 if (!po.m_collectRefLinks) {
6660 const auto isLinkTextEmpty = toSingleLine(linkText).isEmpty();
6662 const auto img = makeImage(iit->second->url(),
6663 (isLinkTextEmpty ? text : linkText),
6665 doNotCreateTextOnFail,
6669 lastIt->m_pos + lastIt->m_len,
6670 (isLinkTextEmpty ? textPos : linkTextPos),
6673 po.m_parent->appendItem(img);
6675 po.m_line = lastIt->m_line;
6676 po.m_pos = lastIt->m_pos + lastIt->m_len;
6680 }
else if (!doNotCreateTextOnFail) {
6681 makeText(lastLineForText, lastPosForText, po);
6688template<
class Trait>
6696 if (pos == fr.at(line).first.length() && line + 1 < (
long long int)fr.size()) {
6703template<
class Trait>
6704inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6712 const auto destLine = line;
6713 const auto &s = po.
m_fr.m_data.at(line).first.asString();
6714 bool backslash =
false;
6717 if (s[pos] == Trait::latin1ToChar(
'<')) {
6721 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6722 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6725 const auto start = pos;
6727 while (pos < s.size()) {
6730 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6733 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'<')) {
6734 return {line, pos,
false, {}, destLine};
6735 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'>')) {
6746 if (pos < s.size() && s[pos] == Trait::latin1ToChar(
'>')) {
6748 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6749 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6754 return {line, pos,
true, s.sliced(
start, pos -
start - 1), destLine};
6756 return {line, pos,
false, {}, destLine};
6759 long long int pc = 0;
6761 const auto start = pos;
6764 urlPos->setStartColumn(po.
m_fr.m_data[line].first.virginPos(pos));
6765 urlPos->setStartLine(po.
m_fr.m_data[line].second.m_lineNumber);
6768 while (pos < s.size()) {
6771 if (s[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6774 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
' ')) {
6777 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6778 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6781 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6783 return {line, pos,
false, {}, destLine};
6785 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
'(')) {
6787 }
else if (!backslash && s[pos] == Trait::latin1ToChar(
')')) {
6790 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6791 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6794 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6808 urlPos->setEndColumn(po.
m_fr.m_data[line].first.virginPos(pos - 1));
6809 urlPos->setEndLine(po.
m_fr.m_data[line].second.m_lineNumber);
6812 return {line, pos,
true, s.sliced(
start, pos -
start), destLine};
6815 return {line, pos,
false, {}, destLine};
6820template<
class Trait>
6821inline std::tuple<long long int, long long int, bool, typename Trait::String, long long int>
6826 const auto space = (pos < po.
m_fr.m_data.at(line).first.length() ?
6827 po.
m_fr.m_data.at(line).first[pos].isSpace() :
true);
6829 const auto firstLine = line;
6833 if (pos >= po.
m_fr.m_data.at(line).first.length()) {
6834 return {line, pos,
true, {}, firstLine};
6837 const auto sc = po.
m_fr.m_data.at(line).first[pos];
6839 if (sc != Trait::latin1ToChar(
'"') && sc != Trait::latin1ToChar(
'\'') &&
6840 sc != Trait::latin1ToChar(
'(') && sc != Trait::latin1ToChar(
')')) {
6841 return {line, pos, (firstLine != line && line <= po.
m_lastTextLine), {}, firstLine};
6842 }
else if (!space && sc != Trait::latin1ToChar(
')')) {
6843 return {line, pos,
false, {}, firstLine};
6846 if (sc == Trait::latin1ToChar(
')')) {
6850 const auto startLine = line;
6852 bool backslash =
false;
6858 typename Trait::String title;
6860 while (line < (
long long int)po.
m_fr.m_data.size() && pos < po.
m_fr.m_data.at(line).first.length()) {
6863 if (po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
'\\') && !backslash) {
6866 }
else if (sc == Trait::latin1ToChar(
'(') &&
6867 po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
')') && !backslash) {
6870 }
else if (sc == Trait::latin1ToChar(
'(') &&
6871 po.
m_fr.m_data.at(line).first[pos] == Trait::latin1ToChar(
'(') && !backslash) {
6872 return {line, pos,
false, {}, startLine};
6873 }
else if (sc != Trait::latin1ToChar(
'(') && po.
m_fr.m_data.at(line).first[pos] == sc && !backslash) {
6877 title.push_back(po.
m_fr.m_data.at(line).first[pos]);
6886 if (pos == po.
m_fr.m_data.at(line).first.length()) {
6891 return {line, pos,
false, {}, startLine};
6894template<
class Trait>
6895inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
6897 typename Delims::iterator last,
6898 TextParsingOpts<Trait> &po,
6899 WithPosition *urlPos)
6901 long long int p = it->m_pos + it->m_len;
6902 long long int l = it->m_line;
6904 typename Trait::String dest, title;
6905 long long int destStartLine = 0;
6907 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
6910 return {{}, {}, it,
false};
6913 long long int s = 0;
6915 std::tie(l, p, ok, title, s) = readLinkTitle<Trait>(l, p, po);
6917 skipSpacesUpTo1Line<Trait>(l, p, po.m_fr.m_data);
6919 if (!ok || (l >= (
long long int)po.m_fr.m_data.size() || p >= po.m_fr.m_data.at(l).first.length() ||
6920 po.m_fr.m_data.at(l).first[p] != Trait::latin1ToChar(
')'))) {
6921 return {{}, {}, it,
false};
6924 for (; it != last; ++it) {
6925 if (it->m_line == l && it->m_pos == p) {
6926 return {dest, title, it,
true};
6930 return {{}, {}, it,
false};
6933template<
class Trait>
6934inline std::tuple<typename Trait::String, typename Trait::String, typename Parser<Trait>::Delims::iterator,
bool>
6936 typename Delims::iterator last,
6937 TextParsingOpts<Trait> &po,
6938 WithPosition *urlPos)
6940 long long int p = it->m_pos + it->m_len + 1;
6941 long long int l = it->m_line;
6943 typename Trait::String dest, title;
6944 long long int destStartLine = 0;
6946 std::tie(l, p, ok, dest, destStartLine) = readLinkDestination<Trait>(l, p, po, urlPos);
6949 return {{}, {}, it,
false};
6952 long long int titleStartLine = 0;
6954 std::tie(l, p, ok, title, titleStartLine) = readLinkTitle<Trait>(l, p, po);
6957 return {{}, {}, it,
false};
6960 if (!title.isEmpty()) {
6961 p = skipSpaces<Trait>(p, po.m_fr.m_data.at(l).first.asString());
6963 if (titleStartLine == destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
6964 return {{}, {}, it,
false};
6965 }
else if (titleStartLine != destStartLine && p < po.m_fr.m_data.at(l).first.length()) {
6967 p = po.m_fr.m_data.at(l).first.length();
6972 for (; it != last; ++it) {
6973 if (it->m_line > l || (it->m_line == l && it->m_pos >= p)) {
6981 return {dest, title, std::prev(it),
true};
6984template<
class Trait>
6987 typename Delims::iterator last,
6988 TextParsingOpts<Trait> &po)
6990 const auto start = it;
6992 typename MdBlock<Trait>::Data text;
6994 po.m_wasRefLink =
false;
6995 po.m_firstInParagraph =
false;
6996 po.m_headingAllowed =
true;
6998 WithPosition textPos;
6999 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
7002 if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7004 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'(')) {
7005 typename Trait::String url, title;
7006 typename Delims::iterator iit;
7009 WithPosition urlPos;
7010 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7013 if (!po.m_collectRefLinks) {
7014 po.m_parent->appendItem(
7015 makeImage(url, text, po,
false,
start->m_line,
start->m_pos,
7016 iit->m_line, iit->m_pos + iit->m_len, textPos, urlPos));
7019 po.m_line = iit->m_line;
7020 po.m_pos = iit->m_pos + iit->m_len;
7023 }
else if (createShortcutImage(text, po,
start->m_line,
start->m_pos,
start->m_line,
7024 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7029 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'[')) {
7030 typename MdBlock<Trait>::Data
label;
7031 typename Delims::iterator lit;
7033 WithPosition labelPos;
7034 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7036 if (lit != std::next(it)) {
7037 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7040 && createShortcutImage(label,
7052 }
else if (isLabelEmpty
7053 && createShortcutImage(text,
7066 }
else if (createShortcutImage(text, po,
start->m_line,
start->m_pos,
start->m_line,
7067 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7081template<
class Trait>
7084 typename Delims::iterator last,
7085 TextParsingOpts<Trait> &po)
7087 const auto start = it;
7089 typename MdBlock<Trait>::Data text;
7091 const auto wasRefLink = po.m_wasRefLink;
7092 const auto firstInParagraph = po.m_firstInParagraph;
7093 po.m_wasRefLink =
false;
7094 po.m_firstInParagraph =
false;
7095 po.m_headingAllowed =
true;
7097 const auto ns = skipSpaces<Trait>(0, po.m_fr.m_data.at(po.m_line).first.asString());
7099 WithPosition textPos;
7100 std::tie(text, it) = checkForLinkText(it, last, po, &textPos);
7104 if (text.front().first.asString().startsWith(Trait::latin1ToString(
"^")) &&
7105 text.front().first.asString().length() > 1 && text.size() == 1 &&
7106 start->m_line == it->m_line) {
7107 if (!po.m_collectRefLinks) {
7108 std::shared_ptr<FootnoteRef<Trait>> fnr(
new FootnoteRef<Trait>(
7109 Trait::latin1ToString(
"#") + toSingleLine(text).toCaseFolded().toUpper() +
7110 Trait::latin1ToString(
"/") + (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7111 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName));
7112 fnr->setStartColumn(po.m_fr.m_data.at(
start->m_line).first.virginPos(
start->m_pos));
7113 fnr->setStartLine(po.m_fr.m_data.at(
start->m_line).second.m_lineNumber);
7114 fnr->setEndColumn(po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
7115 fnr->setEndLine(po.m_fr.m_data.at(it->m_line).second.m_lineNumber);
7116 fnr->setIdPos(textPos);
7118 typename Trait::String fnrText = Trait::latin1ToString(
"[");
7119 bool firstFnrText =
true;
7121 for (
const auto &t : text) {
7122 if (!firstFnrText) {
7123 fnrText.push_back(Trait::latin1ToString(
"\n"));
7126 firstFnrText =
false;
7128 fnrText.push_back(t.first.asString());
7131 fnrText.push_back(Trait::latin1ToString(
"]"));
7132 fnr->setText(fnrText);
7133 po.m_parent->appendItem(fnr);
7135 initLastItemWithOpts<Trait>(po, fnr);
7138 po.m_line = it->m_line;
7139 po.m_pos = it->m_pos + it->m_len;
7142 }
else if (it->m_pos + it->m_len < po.m_fr.m_data.at(it->m_line).first.length()) {
7144 if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
':')) {
7146 if ((po.m_line == 0 || wasRefLink || firstInParagraph) && ns < 4 && start->m_pos == ns) {
7147 typename Trait::String url, title;
7148 typename Delims::iterator iit;
7151 WithPosition labelPos;
7153 std::tie(text, it) = checkForLinkLabel(
start, last, po, &labelPos);
7155 if (it !=
start && !toSingleLine(text).simplified().isEmpty()) {
7156 WithPosition urlPos;
7157 std::tie(url, title, iit, ok) = checkForRefLink(it, last, po, &urlPos);
7160 const auto label = Trait::latin1ToString(
"#") +
7162 Trait::latin1ToString(
"/") +
7163 (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7164 po.m_workingPath + Trait::latin1ToString(
"/")) + po.m_fileName;
7167 link->setStartColumn(po.m_fr.m_data.at(
start->m_line).first.virginPos(
7169 link->setStartLine(po.m_fr.m_data.at(
start->m_line).second.m_lineNumber);
7172 po.m_fr.m_data.at(po.m_line).first.virginPos(po.m_pos),
7173 po.m_fr.m_data.at(po.m_line).second.m_lineNumber);
7175 link->setEndColumn(endPos.first);
7176 link->setEndLine(endPos.second);
7178 link->setTextPos(labelPos);
7179 link->setUrlPos(urlPos);
7181 url = removeBackslashes<typename Trait::String, Trait>(
7182 replaceEntity<Trait>(url));
7184 if (!url.isEmpty()) {
7185 if (Trait::fileExists(url)) {
7186 url = Trait::absoluteFilePath(url);
7187 }
else if (Trait::fileExists(url, po.m_workingPath)) {
7188 url = Trait::absoluteFilePath(
7189 (po.m_workingPath.isEmpty() ?
typename Trait::String() :
7190 po.m_workingPath + Trait::latin1ToString(
"/")) + url);
7196 po.m_wasRefLink =
true;
7197 po.m_headingAllowed =
false;
7199 if (po.m_doc->labeledLinks().find(label) == po.m_doc->labeledLinks().cend()) {
7200 po.m_doc->insertLabeledLink(label, link);
7215 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'(')) {
7216 typename Trait::String url, title;
7217 typename Delims::iterator iit;
7220 WithPosition urlPos;
7221 std::tie(url, title, iit, ok) = checkForInlineLink(std::next(it), last, po, &urlPos);
7224 const auto link = makeLink(url,
7231 iit->m_pos + iit->m_len,
7236 if (!po.m_collectRefLinks) {
7237 po.m_parent->appendItem(link);
7240 po.m_line = iit->m_line;
7241 po.m_pos = iit->m_pos + iit->m_len;
7247 }
else if (createShortcutLink(text, po,
start->m_line,
start->m_pos,
start->m_line,
7248 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7253 else if (po.m_fr.m_data.at(it->m_line).first[it->m_pos + it->m_len] == Trait::latin1ToChar(
'[')) {
7254 typename MdBlock<Trait>::Data
label;
7255 typename Delims::iterator lit;
7257 WithPosition labelPos;
7258 std::tie(label, lit) = checkForLinkLabel(std::next(it), last, po, &labelPos);
7260 const auto isLabelEmpty = toSingleLine(label).isEmpty();
7262 if (lit != std::next(it)) {
7264 && createShortcutLink(label,
7276 }
else if (isLabelEmpty
7277 && createShortcutLink(text,
7290 }
else if (createShortcutLink(text, po,
start->m_line,
start->m_pos,
start->m_line,
7291 start->m_pos +
start->m_len, it, {},
false, textPos, {})) {
7306template<
class Trait>
7311 const auto it = std::find_if(styles.crbegin(), styles.crend(), [&](
const auto &p) {
7312 return (p.m_style == s);
7315 if (it != styles.crend()) {
7316 styles.erase(it.base() - 1);
7321template<
class Trait>
7328 for (
const auto &s : styles) {
7329 switch (s.m_style) {
7350template<
class Trait>
7355 case Delimiter::Strikethrough:
7358 case Delimiter::Emphasis1:
7361 case Delimiter::Emphasis2:
7369template<
class Trait>
7372 typename Delimiter::DelimiterType t,
7373 long long int style)
7375 if (t != Delimiter::Strikethrough) {
7376 if (style % 2 == 1) {
7377 styles.push_back({t == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2, 1});
7381 for (
long long int i = 0; i < style / 2; ++i) {
7382 styles.push_back({t == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2, 2});
7386 styles.push_back({Style::Strikethrough, style});
7390template<
class Trait>
7391inline std::vector<std::pair<Style, long long int>>
7393 const std::vector<long long int> &styles,
7394 long long int lastStyle)
7396 std::vector<std::pair<Style, long long int>> ret;
7398 createStyles(ret, t, lastStyle);
7400 for (
auto it = styles.crbegin(), last = styles.crend(); it != last; ++it) {
7401 createStyles(ret, t, *it);
7407template<
class Trait>
7410 long long int itLine,
7411 long long int itPos,
7412 typename Delimiter::DelimiterType t)
7414 return (itLine == it->m_line && itPos + it->m_len == it->m_pos && it->m_type == t);
7417template<
class Trait>
7420 typename Delims::iterator last,
7421 long long int &line,
7424 long long int &itCount)
7429 const auto t = it->m_type;
7434 while (it != last && isSequence(it, line, pos, t)) {
7442 return std::prev(it);
7448 return ((((i1 + i2) % 3) == 0) && !((i1 % 3 == 0) && (i2 % 3 == 0)));
7451template<
class Trait>
7452inline std::tuple<bool, std::vector<std::pair<Style, long long int>>,
long long int,
long long int>
7454 typename Delims::iterator it,
7455 typename Delims::iterator last,
7456 typename Delims::iterator &stackBottom,
7457 TextParsingOpts<Trait> &po)
7459 const auto open = it;
7460 long long int openPos, openLength, itCount, lengthFromIt, tmp;
7462 it = std::next(readSequence(first, open, last, openPos, openLength, tmp, lengthFromIt, itCount).second);
7464 const auto length = lengthFromIt;
7465 long long int itLine, itPos, itLength;
7467 struct RollbackValues {
7468 RollbackValues(TextParsingOpts<Trait> &po,
7471 bool collectRefLinks,
7472 typename Delims::iterator &stackBottom,
7473 typename Delims::iterator last)
7477 , m_collectRefLinks(collectRefLinks)
7478 , m_stackBottom(stackBottom)
7484 void setIterator(
typename Delims::iterator it)
7491 m_po.m_line = m_line;
7493 m_po.m_collectRefLinks = m_collectRefLinks;
7495 if (m_it != m_last && (m_it > m_stackBottom || m_stackBottom == m_last)) {
7496 m_stackBottom = m_it;
7500 TextParsingOpts<Trait> &m_po;
7501 long long int m_line;
7502 long long int m_pos;
7503 bool m_collectRefLinks;
7504 typename Delims::iterator &m_stackBottom;
7505 typename Delims::iterator m_last;
7506 typename Delims::iterator m_it;
7509 RollbackValues rollback(po, po.m_line, po.m_pos, po.m_collectRefLinks, stackBottom, last);
7511 po.m_collectRefLinks =
true;
7513 std::vector<long long int> styles;
7516 std::vector<typename Delims::iterator> m_its;
7517 long long int m_length;
7520 std::vector<Opener> openers;
7522 std::function<void(
long long int,
long long int)> dropOpeners;
7524 dropOpeners = [&openers](
long long int pos,
long long int line) {
7525 while (!openers.empty()) {
7526 if (openers.back().m_its.front()->m_line > line || (openers.back().m_its.front()->m_line == line &&
7527 openers.back().m_its.front()->m_pos > pos)) {
7528 std::for_each( openers.back().m_its.begin(), openers.back().m_its.end(),
7529 [](
auto &i) { i->m_skip = true; });
7537 auto tryCloseEmphasis = [&dropOpeners,
this, &openers, &
open](
typename Delims::iterator first,
7538 typename Delims::iterator it,
7539 typename Delims::iterator last) ->
bool
7541 const auto type = it->m_type;
7542 const auto both = it->m_leftFlanking && it->m_rightFlanking;
7543 long long int tmp1, tmp2, tmp3, tmp4;
7544 long long int closeLength;
7546 it = this->readSequence(first, it, last, tmp1, closeLength, tmp2, tmp3, tmp4).first;
7549 long long int tmpLength = closeLength;
7552 switch (it->m_type) {
7553 case Delimiter::Strikethrough: {
7554 if (it->m_leftFlanking && it->m_len == closeLength && type == it->m_type) {
7555 dropOpeners(it->m_pos, it->m_line);
7560 case Delimiter::Emphasis1:
7561 case Delimiter::Emphasis2:
7563 if (it->m_leftFlanking && type == it->m_type) {
7564 long long int pos, len;
7565 this->readSequence(first, it, last, pos, len, tmp1, tmp2, tmp3);
7567 if ((both || (it->m_leftFlanking && it->m_rightFlanking)) &&
isMult3(len, closeLength)) {
7571 dropOpeners(pos - len, it->m_line);
7573 if (tmpLength >= len) {
7576 if (
open->m_type == it->m_type) {
7584 if (
open->m_type == it->m_type) {
7585 openers.back().m_length -= tmpLength;
7605 auto fillIterators = [](
typename Delims::iterator first,
7606 typename Delims::iterator last) -> std::vector<typename Delims::iterator>
7608 std::vector<typename Delims::iterator> res;
7610 for (; first != last; ++first) {
7611 res.push_back(first);
7614 res.push_back(last);
7619 for (; it != last; ++it) {
7620 if (it > stackBottom) {
7621 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7624 if (it->m_line <= po.m_lastTextLine) {
7625 po.m_line = it->m_line;
7626 po.m_pos = it->m_pos;
7628 switch (it->m_type) {
7629 case Delimiter::SquareBracketsOpen:
7630 it = checkForLink(it, last, po);
7633 case Delimiter::ImageOpen:
7634 it = checkForImage(it, last, po);
7637 case Delimiter::Less:
7638 it = checkForAutolinkHtml(it, last, po,
false);
7641 case Delimiter::Strikethrough: {
7642 if (
open->m_type == it->m_type &&
open->m_len == it->m_len && it->m_rightFlanking) {
7643 rollback.setIterator(it);
7644 return {
true, createStyles(
open->m_type, styles,
open->m_len),
open->m_len, 1};
7645 }
else if (it->m_rightFlanking && tryCloseEmphasis(open, it, last)) {
7646 }
else if (it->m_leftFlanking &&
open->m_type == it->m_type) {
7647 openers.push_back({{it}, it->m_len});
7651 case Delimiter::Emphasis1:
7652 case Delimiter::Emphasis2: {
7653 if (
open->m_type == it->m_type) {
7654 const auto itBoth = (it->m_leftFlanking && it->m_rightFlanking);
7656 if (it->m_rightFlanking) {
7657 bool notCheck = (
open->m_leftFlanking &&
open->m_rightFlanking) || itBoth;
7659 long long int count;
7661 it = readSequence(it, last, itLine, itPos, itLength, count);
7664 notCheck =
isMult3(openLength, itLength);
7667 if (!openers.empty()) {
7668 long long int i = openers.size() - 1;
7669 auto &top = openers[i];
7671 while (!openers.empty()) {
7678 if ((itBoth || (top.m_its.front()->m_rightFlanking && top.m_its.front()->m_leftFlanking))
7679 &&
isMult3(itLength, top.m_length)) {
7684 if (top.m_length <= itLength) {
7685 itLength -= top.m_length;
7686 openers.erase(openers.begin() + i);
7688 top.m_length -= itLength;
7702 if (itLength >= lengthFromIt) {
7703 rollback.setIterator(it);
7704 return {
true, createStyles(
open->m_type, styles, lengthFromIt), length, itCount};
7706 styles.push_back(itLength);
7707 lengthFromIt -= itLength;
7709 }
else if (firstIt->m_leftFlanking) {
7710 openers.push_back({fillIterators(firstIt, it), itLength});
7714 long long int count;
7716 it = readSequence(it, last, itLine, itPos, itLength, count);
7717 openers.push_back({fillIterators(firstIt, it), itLength});
7719 }
else if (it->m_rightFlanking) {
7720 tryCloseEmphasis(open, it, last);
7724 case Delimiter::InlineCode:
7725 it = checkForInlineCode(it, last, po);
7736 return {
false, {{Style::Unknown, 0}},
open->m_len, 1};
7739template<
class Trait>
7742 typename Delims::iterator last,
7743 long long int count)
7745 const auto len = std::distance(it, last);
7750 return it + (len - 1);
7755template<
class Trait>
7765template<
class Trait>
7768 typename Delims::iterator it,
7769 typename Delims::iterator last,
7771 long long int &length,
7772 long long int &itCount,
7773 long long int &lengthFromIt,
7774 long long int &itCountFromIt)
7776 long long int line = it->m_line;
7777 pos = it->m_pos + it->m_len;
7778 long long int ppos = it->m_pos;
7779 const auto t = it->m_type;
7780 lengthFromIt = it->m_len;
7783 auto retItLast = std::next(it);
7785 for (; retItLast != last; ++retItLast) {
7786 if (retItLast->m_line == line && pos == retItLast->m_pos && retItLast->m_type == t) {
7787 lengthFromIt += retItLast->m_len;
7788 pos = retItLast->m_pos + retItLast->m_len;
7795 length = lengthFromIt;
7796 itCount = itCountFromIt;
7798 auto retItFirst = it;
7799 bool useNext =
false;
7801 if (retItFirst != first) {
7802 retItFirst = std::prev(retItFirst);
7805 for (;; --retItFirst) {
7806 if (retItFirst->m_line == line && ppos - retItFirst->m_len == retItFirst->m_pos && retItFirst->m_type == t) {
7807 length += retItFirst->m_len;
7808 ppos = retItFirst->m_pos;
7816 if (retItFirst == first) {
7822 return {useNext ? std::next(retItFirst) : retItFirst, std::prev(retItLast)};
7825template<
class Trait>
7828 typename Delims::iterator it,
7829 typename Delims::iterator last,
7830 typename Delims::iterator &stackBottom,
7831 TextParsingOpts<Trait> &po)
7833 long long int count = 1;
7835 po.m_wasRefLink =
false;
7836 po.m_firstInParagraph =
false;
7838 if (it->m_rightFlanking) {
7839 long long int pos, len, tmp1, tmp2;
7840 readSequence(first, it, last, pos, len, count, tmp1, tmp2);
7841 const auto t = it->m_type;
7843 long long int opened = 0;
7844 bool bothFlanking =
false;
7846 for (
auto it = po.m_styles.crbegin(), last = po.m_styles.crend(); it != last; ++it) {
7847 bool doBreak =
false;
7850 case Delimiter::Emphasis1: {
7851 if (it->m_style == Style::Italic1 || it->m_style == Style::Bold1) {
7852 opened = it->m_length;
7853 bothFlanking = it->m_bothFlanking;
7858 case Delimiter::Emphasis2: {
7859 if (it->m_style == Style::Italic2 || it->m_style == Style::Bold2) {
7860 opened = it->m_length;
7861 bothFlanking = it->m_bothFlanking;
7866 case Delimiter::Strikethrough: {
7867 if (it->m_style == Style::Strikethrough) {
7868 opened = it->m_length;
7869 bothFlanking = it->m_bothFlanking;
7882 const bool sumMult3 = (it->m_leftFlanking || bothFlanking ?
isMult3(opened, len) : false);
7884 if (count && opened && !sumMult3) {
7885 if (count > opened) {
7889 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
7890 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
7892 if (it->m_type == Delimiter::Strikethrough) {
7893 const auto len = it->m_len;
7895 for (
auto i = 0; i < count; ++i) {
7896 closeStyle<Trait>(po.m_styles, Style::Strikethrough);
7901 if (count % 2 == 1) {
7902 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Italic1 : Style::Italic2);
7904 closeStyle<Trait>(po.m_styles, st);
7910 const auto st = (it->m_type == Delimiter::Emphasis1 ? Style::Bold1 : Style::Bold2);
7912 for (
auto i = 0; i < count / 2; ++i) {
7913 closeStyle<Trait>(po.m_styles, st);
7920 applyStyles<Trait>(po.m_opts, po.m_styles);
7922 const auto j = incrementIterator(it, last, count - 1);
7924 po.m_pos = j->m_pos + j->m_len;
7925 po.m_line = j->m_line;
7933 if (it->m_leftFlanking) {
7934 switch (it->m_type) {
7935 case Delimiter::Strikethrough:
7936 case Delimiter::Emphasis1:
7937 case Delimiter::Emphasis2: {
7938 bool closed =
false;
7939 std::vector<std::pair<Style, long long int>> styles;
7940 long long int len = 0;
7942 if (it > stackBottom) {
7948 long long int tmp1, tmp2, tmp3;
7949 readSequence(it, last, tmp1, tmp2, len, tmp3);
7951 std::tie(closed, styles, len, count) = isStyleClosed(first, it, last, stackBottom, po);
7955 auto pos = po.m_fr.m_data.at(it->m_line).first.virginPos(it->m_pos);
7956 const auto line = po.m_fr.m_data.at(it->m_line).second.m_lineNumber;
7958 for (
const auto &p : styles) {
7959 po.m_styles.push_back({p.first, p.second, it->m_leftFlanking && it->m_rightFlanking});
7961 if (!po.m_collectRefLinks) {
7963 pos + p.second - 1, line});
7969 po.m_pos = it->m_pos + len;
7970 po.m_line = it->m_line;
7972 applyStyles<Trait>(po.m_opts, po.m_styles);
7974 makeText(it->m_line, it->m_pos + len, po);
7979 makeText(it->m_line, it->m_pos + it->m_len, po);
7990 return incrementIterator(it, last, count - 1);
7994template<
class Trait>
7995inline std::shared_ptr<Text<Trait>>
8001 t->setStartColumn((*it)->startColumn());
8002 t->setStartLine((*it)->startLine());
8006 typename Trait::String data;
8008 for (; it != last; ++it) {
8009 const auto tt = std::static_pointer_cast<Text<Trait>>(*it);
8011 data.push_back(tt->text());
8013 if (!tt->openStyles().empty()) {
8014 std::copy(tt->openStyles().cbegin(), tt->openStyles().cend(),
8015 std::back_inserter(t->openStyles()));
8018 if (!tt->closeStyles().empty()) {
8019 std::copy(tt->closeStyles().cbegin(), tt->closeStyles().cend(),
8020 std::back_inserter(close));
8027 t->setEndColumn((*it)->endColumn());
8028 t->setEndLine((*it)->endLine());
8029 t->closeStyles() = close;
8063template<
class Trait>
8064inline std::shared_ptr<Paragraph<Trait>>
8070 np->setStartColumn(p->startColumn());
8071 np->setStartLine(p->startLine());
8072 np->setEndColumn(p->endColumn());
8073 np->setEndLine(p->endLine());
8076 auto start = p->items().cend();
8077 long long int line = -1;
8078 long long int auxStart = 0, auxIt = 0;
8079 bool finished =
false;
8081 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8083 const auto t = std::static_pointer_cast<Text<Trait>>(*it);
8085 if (
start == last) {
8088 line = t->endLine();
8091 if (opts != t->opts() || t->startLine() != line || finished ||
8095 auxIt = auxIt - (auxIt - auxStart) + 1;
8102 line = t->endLine();
8113 if (
start != last) {
8116 auxIt = auxIt - (auxIt - auxStart) + 1;
8123 line = (*it)->endLine();
8126 np->appendItem((*it));
8130 if (
start != p->items().cend()) {
8146 long long int &line,
8147 long long int length,
8148 long long int linesCount)
8150 if (pos != 0 && line < linesCount && pos == length) {
8157template<
class Trait>
8158inline std::shared_ptr<Paragraph<Trait>>
8162 auto p = std::make_shared<Paragraph<Trait>>();
8164 p->setStartColumn((*first)->startColumn());
8165 p->setStartLine((*first)->startLine());
8167 for (; first != last; ++first) {
8168 p->appendItem(*first);
8169 p->setEndColumn((*first)->endColumn());
8170 p->setEndLine((*first)->endLine());
8177template<
class Trait>
8178inline std::shared_ptr<Paragraph<Trait>>
8182 bool collectRefLinks,
8183 bool fullyOptimizeParagraphs =
true)
8185 auto first = p->items().cbegin();
8187 auto last = p->items().cend();
8189 for (; it != last; ++it) {
8190 if (first == last) {
8198 if (!collectRefLinks) {
8199 if (!p->isEmpty()) {
8201 fullyOptimizeParagraphs ?
8206 parent->appendItem(*it);
8213 if (first != last) {
8214 if (first != p->items().cbegin()) {
8215 const auto c = std::count_if(first, last, [](
const auto &i) {
8228 return std::make_shared<Paragraph<Trait>>();
8233template<
class Trait>
8237 switch (item->
type()) {
8246 if (!i->closeStyles().empty()) {
8247 return i->closeStyles().back().endColumn();
8249 return i->endColumn();
8259 if (!c->closeStyles().empty()) {
8260 return c->closeStyles().back().endColumn();
8262 return c->endDelim().endColumn();
8273template<
class Trait>
8278 long long int lastColumn,
8279 long long int lastLine,
8281 const typename Trait::String &workingPath,
8282 const typename Trait::String &fileName,
8283 bool collectRefLinks,
8287 if (!collectRefLinks) {
8289 auto lb = std::static_pointer_cast<LineBreak<Trait>>(p->items().back());
8290 const auto lineBreakBySpaces = lb->text().simplified().isEmpty();
8295 if (!p->isEmpty()) {
8297 auto lt = std::static_pointer_cast<Text<Trait>>(p->items().back());
8299 if (!lineBreakBySpaces) {
8300 auto text = po.
m_fr.m_data.at(lineBreakPos.second).first.fullVirginString().sliced(
8304 if (!lt->text()[0].isSpace()) {
8307 text.remove(0, notSpacePos);
8313 lt->setEndColumn(lt->endColumn() + lb->text().length());
8315 if (!lineBreakBySpaces) {
8318 const auto endOfLine = po.
m_fr.m_data.at(lineBreakPos.second).first.virginSubString(
8319 lastItemPos.first + 1);
8320 auto t = std::make_shared<Text<Trait>>();
8321 t->setText(endOfLine);
8322 t->setStartColumn(lastItemVirginPos + 1);
8323 t->setStartLine(lb->startLine());
8324 t->setEndColumn(lb->endColumn());
8325 t->setEndLine(lb->endLine());
8331 po.
m_rawTextData.push_back({lb->text(), pos.first, pos.second});
8337 std::pair<typename Trait::String, WithPosition> label;
8340 auto t = std::static_pointer_cast<Text<Trait>>(p->items().back());
8344 typename Trait::InternalString tmp(text.m_str);
8350 if (tmp.asString().simplified().isEmpty()) {
8351 p->removeItemAt(p->items().size() - 1);
8354 if (!p->items().empty()) {
8355 const auto last = std::static_pointer_cast<WithPosition>(p->items().back());
8356 p->setEndColumn(last->endColumn());
8357 p->setEndLine(last->endLine());
8361 const auto virginLine = t->endLine();
8363 if (label.second.startColumn() > notSpacePos) {
8364 auto text = tmp.fullVirginString().sliced(0, label.second.startColumn());
8367 if (!t->text()[0].isSpace()) {
8370 text.remove(0, notSpacePos);
8374 t->setEndColumn(label.second.startColumn() - 1);
8376 const auto lastPos = t->endColumn();
8379 if (pos.first != -1) {
8380 t = std::make_shared<Text<Trait>>();
8381 t->setStartColumn(label.second.endColumn() + 1);
8382 t->setStartLine(virginLine);
8383 t->setEndColumn(lastPos);
8384 t->setEndLine(virginLine);
8387 po.
m_rawTextData.push_back({po.
m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8388 pos.first, pos.second});
8394 if (pos.first != -1) {
8395 po.
m_rawTextData.back() = {po.
m_fr.m_data[pos.second].first.asString().sliced(pos.first),
8396 pos.first, pos.second};
8400 if (!text.simplified().isEmpty()) {
8401 if (p->items().size() == 1) {
8407 t->setStartColumn(label.second.endColumn() + 1);
8411 p->removeItemAt(p->items().size() - 1);
8415 p->setEndColumn(t->endColumn());
8421 label.second.setStartLine(t->startLine());
8422 label.second.setEndLine(t->endLine());
8427 h->setStartColumn(p->startColumn());
8428 h->setStartLine(p->startLine());
8429 h->setEndColumn(lastColumn);
8430 h->setEndLine(lastLine);
8433 if (!p->items().empty()) {
8437 h->setDelims({delim});
8442 h->setLabelPos(label.second);
8446 const auto path = Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
8447 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName;
8449 h->setLabel(label.
first + path);
8451 doc->insertLabeledHeading(label.
first + path, h);
8452 h->labelVariants().
push_back(h->label());
8455 doc->insertLabeledHeading(label.
first.
toLower() + path, h);
8456 h->labelVariants().push_back(label.
first.
toLower() + path);
8460 parent->appendItem(h);
8465template<
class Trait>
8472 for (
auto it = p->items().cbegin(), last = p->items().cend(); it != last; ++it) {
8475 return std::distance(p->items().cbegin(), it);
8486template<
class Trait>
8493 for (
const auto &plugin : textPlugins) {
8494 if (inLink && !std::get<bool>(plugin.second)) {
8498 std::get<TextPluginFunc<Trait>>(plugin.second)(p, po,
8499 std::get<typename Trait::StringList>(plugin.second));
8504template<
class Trait>
8510 hr->setStartColumn(line.first.virginPos(
skipSpaces<Trait>(0, line.first.asString())));
8511 hr->setStartLine(line.second.m_lineNumber);
8512 hr->setEndColumn(line.first.virginPos(line.first.length() - 1));
8513 hr->setEndLine(line.second.m_lineNumber);
8514 parent->appendItem(hr);
8517template<
class Trait>
8520 std::shared_ptr<Block<Trait>> parent,
8522 typename Trait::StringList &linksToParse,
8523 const typename Trait::String &workingPath,
8524 const typename Trait::String &fileName,
8525 bool collectRefLinks,
8526 bool ignoreLineBreak,
8527 RawHtmlBlock<Trait> &html,
8531 if (fr.m_data.empty()) {
8535 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
8536 p->setStartColumn(fr.m_data.at(0).first.virginPos(0));
8537 p->setStartLine(fr.m_data.at(0).second.m_lineNumber);
8539 auto delims = collectDelimiters(fr.m_data);
8541 TextParsingOpts<Trait> po = {fr, p,
nullptr, doc, linksToParse, workingPath, fileName,
8542 collectRefLinks, ignoreLineBreak, html, m_textPlugins};
8543 typename Delims::iterator styleStackBottom = delims.end();
8545 if (html.m_html.get() && html.m_continueHtml) {
8546 finishRawHtmlTag(delims.begin(), delims.end(), po,
false);
8547 }
else if (!delims.empty()) {
8548 for (
auto it = delims.begin(), last = delims.end(); it != last; ++it) {
8549 if (po.m_line > po.m_lastTextLine) {
8553 if (po.shouldStopParsing() && po.m_lastTextLine < it->m_line) {
8556 makeText(po.m_lastTextLine < it->m_line ? po.m_lastTextLine : it->m_line,
8557 po.m_lastTextLine < it->m_line ? po.m_lastTextPos : it->m_pos, po);
8560 switch (it->m_type) {
8561 case Delimiter::SquareBracketsOpen: {
8562 it = checkForLink(it, last, po);
8563 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8564 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8567 case Delimiter::ImageOpen: {
8568 it = checkForImage(it, last, po);
8569 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8570 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8573 case Delimiter::Less: {
8574 it = checkForAutolinkHtml(it, last, po,
true);
8576 if (!html.m_html.get()) {
8577 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8578 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8582 case Delimiter::Strikethrough:
8583 case Delimiter::Emphasis1:
8584 case Delimiter::Emphasis2: {
8585 if (!collectRefLinks) {
8586 it = checkForStyle(delims.begin(), it, last, styleStackBottom, po);
8587 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8588 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8592 case Delimiter::Math: {
8593 it = checkForMath(it, last, po);
8594 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8595 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8598 case Delimiter::InlineCode: {
8599 if (!it->m_backslashed) {
8600 it = checkForInlineCode(it, last, po);
8601 p->setEndColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos + it->m_len - 1));
8602 p->setEndLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8606 case Delimiter::HorizontalLine: {
8607 po.m_wasRefLink =
false;
8608 po.m_firstInParagraph =
false;
8610 const auto pos = skipSpaces<Trait>(0, po.m_fr.m_data[it->m_line].first.asString());
8611 const auto withoutSpaces = po.m_fr.m_data[it->m_line].first.asString().sliced(pos);
8613 auto h2 = isH2<Trait>(withoutSpaces);
8615 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8617 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8619 if (it->m_line - 1 >= 0) {
8620 p->setEndColumn(fr.m_data.at(it->m_line - 1).first.virginPos(
8621 fr.m_data.at(it->m_line - 1).first.length() - 1));
8622 p->setEndLine(fr.m_data.at(it->m_line - 1).second.m_lineNumber);
8627 if (!h2 || !po.m_headingAllowed) {
8628 if (!collectRefLinks && !p->isEmpty()) {
8629 parent->appendItem(p);
8636 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8637 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8638 fr.m_data[it->m_line].second.m_lineNumber,
8643 {po.m_fr.m_data[it->m_line].first.virginPos(pos),
8644 fr.m_data[it->m_line].second.m_lineNumber,
8645 po.m_fr.m_data[it->m_line].first.virginPos(
8646 lastNonSpacePos(po.m_fr.m_data[it->m_line].first.asString())),
8647 fr.m_data[it->m_line].second.m_lineNumber},
8650 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 (po.m_headingAllowed) {
8691 optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()),
8692 fr.m_data[it->m_line].first.virginPos(it->m_pos + it->m_len - 1),
8693 fr.m_data[it->m_line].second.m_lineNumber,
8694 it->m_type == Delimiter::H1 ? 1 : 2,
8698 {po.m_fr.m_data[it->m_line].first.virginPos(skipSpaces<Trait>(
8699 0, po.m_fr.m_data[it->m_line].first.asString())),
8700 fr.m_data[it->m_line].second.m_lineNumber,
8701 po.m_fr.m_data[it->m_line].first.virginPos(lastNonSpacePos(
8702 po.m_fr.m_data[it->m_line].first.asString())),
8703 fr.m_data[it->m_line].second.m_lineNumber},
8706 po.m_checkLineOnNewType =
true;
8708 p.reset(
new Paragraph<Trait>);
8709 po.m_rawTextData.clear();
8711 if (it->m_line + 1 <
static_cast<long long int>(fr.m_data.size())) {
8712 p->setStartColumn(fr.m_data.at(it->m_line + 1).first.virginPos(0));
8713 p->setStartLine(fr.m_data.at(it->m_line + 1).second.m_lineNumber);
8716 po.m_line = it->m_line;
8717 po.m_pos = it->m_pos + it->m_len;
8718 }
else if (p->startColumn() == -1) {
8719 p->setStartColumn(fr.m_data.at(it->m_line).first.virginPos(it->m_pos));
8720 p->setStartLine(fr.m_data.at(it->m_line).second.m_lineNumber);
8727 if (!po.shouldStopParsing()) {
8728 po.m_wasRefLink =
false;
8729 po.m_firstInParagraph =
false;
8731 makeText(it->m_line, it->m_pos + it->m_len, po);
8736 if (po.shouldStopParsing()) {
8740 if (po.m_checkLineOnNewType) {
8741 if (po.m_line + 1 <
static_cast<long long int>(po.m_fr.m_data.size())) {
8744 bool doBreak =
false;
8748 po.m_detected = TextParsingOpts<Trait>::Detected::Code;
8754 po.m_detected = TextParsingOpts<Trait>::Detected::List;
8759 po.m_detected = TextParsingOpts<Trait>::Detected::Blockquote;
8772 po.m_checkLineOnNewType =
false;
8777 if (po.m_lastTextLine == -1) {
8781 switch(po.m_detected) {
8782 case TextParsingOpts<Trait>::Detected::Table:
8783 makeText(po.m_lastTextLine, po.m_lastTextPos, po);
8786 case TextParsingOpts<Trait>::Detected::Nothing:
8788 if(po.m_line <=
static_cast<long long int>(po.m_fr.m_data.size() - 1)) {
8789 makeText(po.m_fr.m_data.size() - 1, po.m_fr.m_data.back().first.length(), po);
8798 if (!p->isEmpty()) {
8799 optimizeParagraph<Trait>(p, po, OptimizeParagraphType::Semi);
8801 checkForTextPlugins<Trait>(p, po, m_textPlugins, inLink);
8805 if (!p->isEmpty() && !collectRefLinks) {
8806 parent->appendItem(optimizeParagraph<Trait>(p, po, defaultParagraphOptimization()));
8809 po.m_rawTextData.clear();
8812 normalizePos(po.m_pos, po.m_line, po.m_line <
static_cast<long long int>(po.m_fr.m_data.size()) ?
8813 po.m_fr.m_data[po.m_line].first.length() : 0, po.m_fr.m_data.size());
8815 if (po.m_detected != TextParsingOpts<Trait>::Detected::Nothing) {
8816 if (po.m_line <
static_cast<long long int>(po.m_fr.m_data.size())) {
8817 return po.m_fr.m_data.at(po.m_line).second.m_lineNumber;
8824template<
class Trait>
8827 std::shared_ptr<Block<Trait>>,
8829 typename Trait::StringList &linksToParse,
8830 const typename Trait::String &workingPath,
8831 const typename Trait::String &fileName,
8832 bool collectRefLinks)
8835 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
8836 return !s.first.isEmpty();
8839 if (it != fr.m_data.end()) {
8840 fr.m_data.erase(it, fr.m_data.end());
8844 if (!fr.m_data.empty()) {
8845 std::shared_ptr<Footnote<Trait>> f(
new Footnote<Trait>);
8846 f->setStartColumn(fr.m_data.front().first.virginPos(0));
8847 f->setStartLine(fr.m_data.front().second.m_lineNumber);
8848 f->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
8849 f->setEndLine(fr.m_data.back().second.m_lineNumber);
8851 auto delims = collectDelimiters(fr.m_data);
8853 RawHtmlBlock<Trait> html;
8855 TextParsingOpts<Trait> po = {fr, f,
nullptr, doc, linksToParse, workingPath, fileName,
8856 collectRefLinks,
false, html, m_textPlugins};
8857 po.m_lastTextLine = fr.m_data.size();
8858 po.m_lastTextPos = fr.m_data.back().first.length();
8860 if (!delims.empty() && delims.cbegin()->m_type == Delimiter::SquareBracketsOpen &&
8861 !delims.cbegin()->m_isWordBefore) {
8862 typename MdBlock<Trait>::Data id;
8863 typename Delims::iterator it = delims.end();
8865 po.m_line = delims.cbegin()->m_line;
8866 po.m_pos = delims.cbegin()->m_pos;
8868 std::tie(
id, it) = checkForLinkText(delims.begin(), delims.end(), po);
8870 if (!toSingleLine(
id).isEmpty() &&
8871 id.front().first.asString().startsWith(Trait::latin1ToString(
"^")) &&
8872 it != delims.cend() &&
8873 fr.m_data.at(it->m_line).first.length() > it->m_pos + 2 &&
8874 fr.m_data.at(it->m_line).first[it->m_pos + 1] == Trait::latin1ToChar(
':') &&
8875 fr.m_data.at(it->m_line).first[it->m_pos + 2].isSpace()) {
8876 f->setIdPos({fr.m_data[delims.cbegin()->m_line].first.virginPos(delims.cbegin()->m_pos),
8877 fr.m_data[delims.cbegin()->m_line].second.m_lineNumber,
8878 fr.m_data.at(it->m_line).first.virginPos(it->m_pos + 1),
8879 fr.m_data.at(it->m_line).second.m_lineNumber});
8882 typename MdBlock<Trait>::Data tmp;
8883 std::copy(fr.m_data.cbegin() + it->m_line, fr.m_data.cend(),
8884 std::back_inserter(tmp));
8888 fr.m_data.front().first = fr.m_data.front().first.sliced(it->m_pos + 3);
8890 for (
auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it) {
8891 if (it->first.asString().startsWith(Trait::latin1ToString(
" "))) {
8892 it->first = it->first.sliced(4);
8896 StringListStream<Trait> stream(fr.m_data);
8898 parse(stream, f, doc, linksToParse, workingPath, fileName, collectRefLinks);
8900 if (!f->isEmpty()) {
8901 doc->insertFootnote(Trait::latin1ToString(
"#") + toSingleLine(
id) +
8902 Trait::latin1ToString(
"/") + (!workingPath.isEmpty() ?
8903 workingPath + Trait::latin1ToString(
"/") :
typename Trait::String()) + fileName,
8911template<
class Trait>
8914 std::shared_ptr<Block<Trait>> parent,
8916 typename Trait::StringList &linksToParse,
8917 const typename Trait::String &workingPath,
8918 const typename Trait::String &fileName,
8919 bool collectRefLinks,
8920 RawHtmlBlock<Trait> &)
8922 const long long int pos = fr.m_data.front().first.asString().indexOf(Trait::latin1ToChar(
'>'));
8923 long long int extra = 0;
8925 long long int line = -1;
8928 typename Blockquote<Trait>::Delims delims;
8930 long long int i = 0, j = 0;
8932 BlockType bt = BlockType::EmptyLine;
8934 for (
auto it = fr.m_data.begin(), last = fr.m_data.end(); it != last; ++it, ++i) {
8935 const auto ns = skipSpaces<Trait>(0, it->first.asString());
8936 const auto gt = (ns < it->first.length() ? (it->first[ns] == Trait::latin1ToChar(
'>') ? ns : -1) : -1);
8939 const auto dp = it->first.virginPos(gt);
8940 delims.push_back({dp, it->second.m_lineNumber, dp, it->second.m_lineNumber});
8942 if (it == fr.m_data.begin()) {
8943 extra = gt + (it->first.length() > gt + 1 ?
8944 (it->first[gt + 1] == Trait::latin1ToChar(
' ') ? 1 : 0) : 0) + 1;
8947 it->first = it->first.sliced(gt + (it->first.length() > gt + 1 ?
8948 (it->first[gt + 1] == Trait::latin1ToChar(
' ') ? 1 : 0) : 0) + 1);
8950 bt = whatIsTheLine(it->first);
8954 if (ns < 4 && isHorizontalLine<Trait>(it->first.asString().sliced(ns))) {
8955 line = it->second.m_lineNumber;
8959 const auto tmpBt = whatIsTheLine(it->first);
8961 if (isListType(tmpBt)) {
8962 line = it->second.m_lineNumber;
8966 if (bt == BlockType::Text) {
8967 if (isH1<Trait>(it->first.asString())) {
8968 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
8970 it->first.insert(p, Trait::latin1ToChar(
'\\'));
8973 }
else if (isH2<Trait>(it->first.asString())) {
8974 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'-'));
8976 it->first.insert(p, Trait::latin1ToChar(
'\\'));
8980 }
else if ((bt == BlockType::Code || bt == BlockType::CodeIndentedBySpaces) &&
8981 it->second.m_mayBreakList) {
8982 line = it->second.m_lineNumber;
8986 if ((bt == BlockType::Text || bt == BlockType::Blockquote || bt == BlockType::List)
8987 && (tmpBt == BlockType::Text || tmpBt == BlockType::CodeIndentedBySpaces)) {
8990 line = it->second.m_lineNumber;
8996 typename MdBlock<Trait>::Data tmp;
8998 for (; j < i; ++j) {
8999 tmp.push_back(fr.m_data.at(j));
9002 StringListStream<Trait> stream(tmp);
9004 std::shared_ptr<Blockquote<Trait>> bq(
new Blockquote<Trait>);
9005 bq->setStartColumn(fr.m_data.at(0).first.virginPos(0) - extra);
9006 bq->setStartLine(fr.m_data.at(0).second.m_lineNumber);
9007 bq->setEndColumn(fr.m_data.at(j - 1).first.virginPos(fr.m_data.at(j - 1).first.length() - 1));
9008 bq->setEndLine(fr.m_data.at(j - 1).second.m_lineNumber);
9009 bq->delims() = delims;
9011 parse(stream, bq, doc, linksToParse, workingPath, fileName, collectRefLinks);
9013 if (!collectRefLinks) {
9014 parent->appendItem(bq);
9022template<
class Trait>
9025 long long int indent)
9029 if (p >= indent || p == s.size()) {
9035 if (p + 1 >= s.size()) {
9038 space = s[p + 1].isSpace();
9042 if (s[p] == Trait::latin1ToChar(
'*') && space) {
9044 }
else if (s[p] == Trait::latin1ToChar(
'-') && space) {
9046 }
else if (s[p] == Trait::latin1ToChar(
'+') && space) {
9056template<
class Trait>
9057inline std::pair<long long int, long long int>
9065template<
class Trait>
9066inline std::tuple<bool, long long int, typename Trait::Char, bool>
9072 if (p == s.size()) {
9073 return {
false, 0,
typename Trait::Char(),
false};
9078 if (p + 1 >= s.size()) {
9081 space = s[p + 1].isSpace();
9085 if (s[p] == Trait::latin1ToChar(
'*') && space) {
9086 return {
true, p + 2, Trait::latin1ToChar(
'*'),
9087 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9088 }
else if (s[p] == Trait::latin1ToChar(
'-')) {
9090 return {
false, p + 2, Trait::latin1ToChar(
'-'),
false};
9092 return {
true, p + 2, Trait::latin1ToChar(
'-'),
9093 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9095 }
else if (s[p] == Trait::latin1ToChar(
'+') && space) {
9096 return {
true, p + 2, Trait::latin1ToChar(
'+'),
9097 p + 2 < s.size() ? !s.sliced(p + 2).isEmpty() :
false};
9100 typename Trait::Char c;
9103 return {
true, p + l + 2, c,
9104 p + l + 2 < s.size() ? !s.sliced(p + l + 2).isEmpty() :
false};
9106 return {
false, 0,
typename Trait::Char(),
false};
9111 return {
false, 0,
typename Trait::Char(),
false};
9115template<
class Trait>
9121 item->setEndColumn(pos);
9122 item->setEndLine(line);
9126template<
class Trait>
9134 for (
auto &i : it->second) {
9135 i.first->setEndColumn(html.
m_html->endColumn());
9136 i.first->setEndLine(html.
m_html->endLine());
9142template<
class Trait>
9145 std::shared_ptr<Block<Trait>> parent,
9147 typename Trait::StringList &linksToParse,
9148 const typename Trait::String &workingPath,
9149 const typename Trait::String &fileName,
9150 bool collectRefLinks,
9151 RawHtmlBlock<Trait> &html)
9153 bool resetTopParent =
false;
9154 long long int line = -1;
9156 if (!html.m_topParent) {
9157 html.m_topParent = parent;
9158 resetTopParent =
true;
9161 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9163 if (p != fr.m_data.front().first.length()) {
9164 std::shared_ptr<List<Trait>>
list(
new List<Trait>);
9166 typename MdBlock<Trait>::Data listItem;
9167 auto it = fr.m_data.begin();
9168 listItem.push_back(*it);
9169 list->setStartColumn(it->first.virginPos(p));
9170 list->setStartLine(it->second.m_lineNumber);
9173 long long int indent = 0;
9174 typename Trait::Char marker;
9176 std::tie(std::ignore, indent, marker, std::ignore) =
9177 listItemData<Trait>(listItem.front().first.asString(),
false);
9179 html.m_blocks.push_back({
list,
list->startColumn() + indent});
9181 if (!collectRefLinks) {
9182 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
9185 bool updateIndent =
false;
9187 auto addListMakeNew = [&]() {
9189 parent->appendItem(list);
9192 html.m_blocks.pop_back();
9194 list.reset(
new List<Trait>);
9198 if (!collectRefLinks) {
9199 html.m_toAdjustLastPos.insert({
list, html.m_blocks});
9203 auto processLastHtml = [&](std::shared_ptr<ListItem<Trait>> resItem) {
9204 if (html.m_html && resItem) {
9205 html.m_parent = (resItem->startLine() == html.m_html->startLine() ||
9206 html.m_html->startColumn() >= resItem->startColumn() + indent ?
9207 resItem : html.findParent(html.m_html->startColumn()));
9209 if (!html.m_parent) {
9210 html.m_parent = html.m_topParent;
9213 if (html.m_parent != resItem) {
9217 const auto continueHtml = html.m_onLine && html.m_continueHtml && html.m_parent == html.m_topParent;
9219 if (!collectRefLinks) {
9220 if (!continueHtml) {
9221 html.m_parent->appendItem(html.m_html);
9224 updateLastPosInList<Trait>(html);
9227 if (!continueHtml) {
9228 resetHtmlTag<Trait>(html);
9233 auto processListItem = [&]() {
9234 MdBlock<Trait> block = {listItem, 0};
9236 std::shared_ptr<ListItem<Trait>> resItem;
9238 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9239 collectRefLinks, html, &resItem);
9243 processLastHtml(resItem);
9244 }
else if (line >= 0) {
9249 for (
auto last = fr.m_data.end(); it != last; ++it) {
9251 std::tie(std::ignore, indent, marker, std::ignore) =
9252 listItemData<Trait>(it->first.asString(),
false);
9254 if (!collectRefLinks) {
9255 html.m_blocks.back().second = indent;
9258 updateIndent =
false;
9261 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9263 if (isH1<Trait>(it->first.asString().sliced(ns)) && ns < indent && !listItem.empty()) {
9264 const auto p = it->first.asString().indexOf(Trait::latin1ToChar(
'='));
9266 it->first.insert(p, Trait::latin1ToChar(
'\\'));
9267 }
else if (isHorizontalLine<Trait>(it->first.asString().sliced(ns)) &&
9268 ns < indent && !listItem.empty()) {
9269 updateIndent =
true;
9277 if (!collectRefLinks) {
9278 makeHorLine<Trait>(*it, parent);
9282 }
else if (isListItemAndNotNested<Trait>(it->first.asString(), indent) &&
9283 !listItem.empty() && !it->second.m_mayBreakList) {
9284 typename Trait::Char tmpMarker;
9285 std::tie(std::ignore, indent, tmpMarker, std::ignore) =
9286 listItemData<Trait>(it->first.asString(),
false);
9290 if (tmpMarker != marker) {
9303 listItem.push_back(*it);
9305 if (
list->startColumn() == -1) {
9306 list->setStartColumn(
9307 it->first.virginPos(std::min(it->first.length() ?
9308 it->first.length() - 1 : 0, skipSpaces<Trait>(0, it->first.asString()))));
9309 list->setStartLine(it->second.m_lineNumber);
9311 if (!collectRefLinks) {
9312 html.m_blocks.back().second +=
list->startColumn();
9317 if (!listItem.empty()) {
9318 MdBlock<Trait> block = {listItem, 0};
9319 line = parseListItem(block, list, doc, linksToParse, workingPath, fileName,
9320 collectRefLinks, html);
9324 parent->appendItem(list);
9327 html.m_blocks.pop_back();
9330 if (resetTopParent) {
9331 html.m_topParent.reset();
9337template<
class Trait>
9340 std::shared_ptr<Block<Trait>> parent,
9342 typename Trait::StringList &linksToParse,
9343 const typename Trait::String &workingPath,
9344 const typename Trait::String &fileName,
9345 bool collectRefLinks,
9346 RawHtmlBlock<Trait> &html,
9347 std::shared_ptr<ListItem<Trait>> *resItem)
9350 const auto it = (std::find_if(fr.m_data.rbegin(), fr.m_data.rend(), [](
const auto &s) {
9351 return !s.first.isEmpty();
9354 if (it != fr.m_data.end()) {
9355 fr.m_data.erase(it, fr.m_data.end());
9359 const auto p = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9361 std::shared_ptr<ListItem<Trait>> item(
new ListItem<Trait>);
9363 item->setStartColumn(fr.m_data.front().first.virginPos(p));
9364 item->setStartLine(fr.m_data.front().second.m_lineNumber);
9368 if (isOrderedList<Trait>(fr.m_data.front().first.asString(), &i, &len)) {
9369 item->setListType(ListItem<Trait>::Ordered);
9370 item->setStartNumber(i);
9371 item->setDelim({item->startColumn(), item->startLine(), item->startColumn() + len, item->startLine()});
9373 item->setListType(ListItem<Trait>::Unordered);
9374 item->setDelim({item->startColumn(), item->startLine(), item->startColumn(), item->startLine()});
9377 if (item->listType() == ListItem<Trait>::Ordered) {
9378 item->setOrderedListPreState(i == 1 ? ListItem<Trait>::Start : ListItem<Trait>::Continue);
9381 typename MdBlock<Trait>::Data data;
9383 auto it = fr.m_data.begin();
9388 long long int indent = 0;
9389 bool wasText =
false;
9391 std::tie(std::ignore, indent, std::ignore, wasText) =
9392 listItemData<Trait>(fr.m_data.front().first.asString(), wasText);
9394 html.m_blocks.push_back({item, item->startColumn() + indent});
9396 if (!collectRefLinks) {
9397 html.m_toAdjustLastPos.insert({item, html.m_blocks});
9400 const auto firstNonSpacePos = calculateIndent<Trait>(
9401 fr.m_data.front().first.asString(), indent).second;
9403 if (firstNonSpacePos - indent < 4) {
9404 indent = firstNonSpacePos;
9407 if (indent < fr.m_data.front().first.length()) {
9408 data.push_back({fr.m_data.front().first.right(fr.m_data.front().first.length() - indent),
9409 fr.m_data.front().second});
9412 bool taskList =
false;
9413 bool checked =
false;
9415 if (!data.empty()) {
9416 auto p = skipSpaces<Trait>(0, data.front().first.asString());
9418 if (p < data.front().first.length()) {
9419 if (data.front().first[p] == Trait::latin1ToChar(
'[')) {
9420 const auto startTaskDelimPos = data.front().first.virginPos(p);
9424 if (p < data.front().first.length()) {
9425 if (data.front().first[p] == Trait::latin1ToChar(
' ') ||
9426 data.front().first[p].toLower() == Trait::latin1ToChar(
'x')) {
9427 if (data.front().first[p].toLower() == Trait::latin1ToChar(
'x')) {
9433 if (p < data.front().first.length()) {
9434 if (data.front().first[p] == Trait::latin1ToChar(
']')) {
9435 item->setTaskDelim({startTaskDelimPos, item->startLine(), data.front().first.virginPos(p), item->startLine()});
9439 data[0].first = data[0].first.sliced(p + 1);
9449 item->setTaskList();
9450 item->setChecked(checked);
9453 bool fensedCode =
false;
9454 typename Trait::String startOfCode;
9455 bool wasEmptyLine =
false;
9457 std::vector<std::pair<RawHtmlBlock<Trait>,
long long int>> htmlToAdd;
9458 long long int line = -1;
9460 auto parseStream = [&](StringListStream<Trait> &stream) ->
long long int
9462 const auto tmpHtml = html;
9463 long long int line = -1;
9464 std::tie(html, line) =
parse(stream, item, doc, linksToParse, workingPath, fileName,
9465 collectRefLinks,
false,
true,
true);
9466 html.m_topParent = tmpHtml.m_topParent;
9467 html.m_blocks = tmpHtml.m_blocks;
9468 html.m_toAdjustLastPos = tmpHtml.m_toAdjustLastPos;
9473 auto processHtml = [&](
auto it) ->
long long int
9475 auto finishHtml = [&]()
9478 htmlToAdd.push_back({html, html.m_parent->items().size()});
9479 updateLastPosInList<Trait>(html);
9480 resetHtmlTag<Trait>(html);
9484 if (html.m_html.get()) {
9485 html.m_parent = html.findParent(html.m_html->startColumn());
9487 if (!html.m_parent) {
9488 html.m_parent = html.m_topParent;
9493 if (html.m_continueHtml) {
9495 tmp.m_emptyLineAfter = fr.m_emptyLineAfter;
9496 tmp.m_emptyLinesBefore = emptyLinesBeforeCount<Trait>(fr.m_data.begin(), it);
9497 std::copy(it, fr.m_data.end(), std::back_inserter(tmp.m_data));
9499 const auto line = parseText(tmp, html.m_parent, doc, linksToParse, workingPath, fileName,
9500 collectRefLinks, html);
9502 if (!html.m_continueHtml) {
9515 if (processHtml(std::prev(it)) == -2) {
9516 for (
auto last = fr.m_data.end(); it != last; ++it, ++pos) {
9518 fensedCode = isCodeFences<Trait>(it->first.asString().startsWith(
9519 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9520 it->first.asString().sliced(indent) : it->first.asString());
9523 startOfCode = startSequence<Trait>(it->first.asString());
9525 }
else if (fensedCode &&
9526 isCodeFences<Trait>(it->first.asString().startsWith(
9527 typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9528 it->first.asString().sliced(indent) : it->first.asString(),
9529 true) && startSequence<Trait>(it->first.asString()).contains(startOfCode)) {
9534 long long int newIndent = 0;
9537 std::tie(ok, newIndent, std::ignore, wasText) = listItemData<Trait>(
9538 it->first.asString().startsWith(
typename Trait::String(indent, Trait::latin1ToChar(
' '))) ?
9539 it->first.asString().sliced(indent) : it->first.asString(),
9542 if (ok && !it->second.m_mayBreakList) {
9543 StringListStream<Trait> stream(data);
9545 line = parseStream(stream);
9549 const auto lineAfterHtml = processHtml(it);
9551 if (lineAfterHtml != -2) {
9552 if (lineAfterHtml == -1) {
9555 if (html.m_parent == html.m_topParent) {
9556 line = lineAfterHtml;
9558 it += (lineAfterHtml - it->second.m_lineNumber);
9567 if (!htmlToAdd.empty() && htmlToAdd.back().first.m_parent == html.m_topParent) {
9568 line = it->second.m_lineNumber;
9572 typename MdBlock<Trait>::Data nestedList;
9573 nestedList.push_back(*it);
9574 const auto emptyLinesBefore = emptyLinesBeforeCount<Trait>(fr.m_data.begin(), it);
9577 wasEmptyLine =
false;
9579 for (; it != last; ++it) {
9580 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9581 std::tie(ok, std::ignore, std::ignore, wasText) =
9582 listItemData<Trait>((ns >= indent ? it->first.asString().sliced(indent) :
9583 it->first.asString()), wasText);
9586 wasEmptyLine =
false;
9589 if (ok || ns >= indent + newIndent || ns == it->first.length() || !wasEmptyLine) {
9590 nestedList.push_back(*it);
9595 wasEmptyLine = (ns == it->first.length());
9597 wasText = (wasEmptyLine ? false : wasText);
9600 for (
auto it = nestedList.begin(), last = nestedList.end(); it != last; ++it) {
9601 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9603 if (ns < indent && ns != it->first.length()) {
9604 it->second.m_mayBreakList =
true;
9606 it->first = it->first.sliced(std::min(ns, indent));
9610 while (!nestedList.empty() &&
9611 nestedList.back().first.asString().isEmpty()) {
9612 nestedList.pop_back();
9615 MdBlock<Trait> block = {nestedList, emptyLinesBefore, wasEmptyLine};
9617 line = parseList(block, item, doc, linksToParse, workingPath, fileName,
9618 collectRefLinks, html);
9624 for (; it != last; ++it) {
9625 if (it->first.asString().startsWith(
typename Trait::String(
9626 indent, Trait::latin1ToChar(
' ')))) {
9627 it->first = it->first.sliced(indent);
9630 data.push_back(*it);
9636 if (!it->second.m_mayBreakList &&
9637 it->first.asString().startsWith(
typename Trait::String(
9638 indent, Trait::latin1ToChar(
' ')))) {
9639 it->first = it->first.sliced(indent);
9642 data.push_back(*it);
9644 wasEmptyLine = (skipSpaces<Trait>(0, it->first.asString()) == it->first.length());
9646 wasText = !wasEmptyLine;
9649 if (!it->second.m_mayBreakList &&
9650 it->first.asString().startsWith(
typename Trait::String(
9651 indent, Trait::latin1ToChar(
' ')))) {
9652 it->first = it->first.sliced(indent);
9655 data.push_back(*it);
9659 if (!data.empty()) {
9660 StringListStream<Trait> stream(data);
9662 line = parseStream(stream);
9665 html.m_parent = html.findParent(html.m_html->startColumn());
9667 if (!html.m_parent) {
9668 html.m_parent = html.m_topParent;
9676 if (!collectRefLinks) {
9678 parent->appendItem(item);
9681 long long int i = 0;
9683 for (
auto &h : htmlToAdd) {
9684 if (h.first.m_parent != h.first.m_topParent) {
9685 h.first.m_parent->insertItem(h.second + i, h.first.m_html);
9698 long long int htmlStartColumn = -1;
9699 long long int htmlStartLine = -1;
9702 std::tie(htmlStartColumn, htmlStartLine) =
9703 localPosFromVirgin<Trait>(fr, html.m_html->startColumn(), html.m_html->startLine());
9706 long long int localLine = (html.m_html ? htmlStartLine : fr.m_data.size() - 1);
9709 if (skipSpaces<Trait>(0, fr.m_data[localLine].first.asString()) >= htmlStartColumn) {
9714 const auto lastLine = fr.m_data[localLine].second.m_lineNumber;
9716 const auto lastColumn = fr.m_data[localLine].first.virginPos(
9717 fr.m_data[localLine].first.length() ? fr.m_data[localLine].first.length() - 1 : 0);
9719 item->setEndColumn(lastColumn);
9720 item->setEndLine(lastLine);
9721 parent->setEndColumn(lastColumn);
9722 parent->setEndLine(lastLine);
9730 html.m_blocks.pop_back();
9735template<
class Trait>
9738 std::shared_ptr<Block<Trait>> parent,
9739 bool collectRefLinks)
9741 const auto indent = skipSpaces<Trait>(0, fr.m_data.front().first.asString());
9743 if (indent != fr.m_data.front().first.length()) {
9744 WithPosition startDelim, endDelim, syntaxPos;
9745 typename Trait::String syntax;
9746 isStartOfCode<Trait>(fr.m_data.front().first.asString(), &syntax, &startDelim, &syntaxPos);
9747 syntax = replaceEntity<Trait>(syntax);
9748 startDelim.setStartLine(fr.m_data.front().second.m_lineNumber);
9749 startDelim.setEndLine(startDelim.startLine());
9750 startDelim.setStartColumn(fr.m_data.front().first.virginPos(startDelim.startColumn()));
9751 startDelim.setEndColumn(fr.m_data.front().first.virginPos(startDelim.endColumn()));
9753 if (syntaxPos.startColumn() != -1) {
9754 syntaxPos.setStartLine(startDelim.startLine());
9755 syntaxPos.setEndLine(startDelim.startLine());
9756 syntaxPos.setStartColumn(fr.m_data.front().first.virginPos(syntaxPos.startColumn()));
9757 syntaxPos.setEndColumn(fr.m_data.front().first.virginPos(syntaxPos.endColumn()));
9760 const long long int startPos = fr.m_data.front().first.virginPos(indent);
9761 const long long int emptyColumn = fr.m_data.front().first.virginPos(fr.m_data.front().first.length());
9762 const long long int startLine = fr.m_data.front().second.m_lineNumber;
9763 const long long int endPos = fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1);
9764 const long long int endLine = fr.m_data.back().second.m_lineNumber;
9766 fr.m_data.erase(fr.m_data.cbegin());
9769 const auto it = std::prev(fr.m_data.cend());
9771 if (it->second.m_lineNumber > -1) {
9772 endDelim.setStartColumn(it->first.virginPos(skipSpaces<Trait>(0, it->first.asString())));
9773 endDelim.setStartLine(it->second.m_lineNumber);
9774 endDelim.setEndLine(endDelim.startLine());
9775 endDelim.setEndColumn(it->first.virginPos(it->first.length() - 1));
9778 fr.m_data.erase(it);
9781 if (syntax.toLower() == Trait::latin1ToString(
"math")) {
9782 typename Trait::String math;
9785 for (
const auto &l : std::as_const(fr.m_data)) {
9787 math.push_back(Trait::latin1ToChar(
'\n'));
9790 math.push_back(l.first.virginSubString());
9795 if (!collectRefLinks) {
9796 std::shared_ptr<Paragraph<Trait>> p(
new Paragraph<Trait>);
9797 p->setStartColumn(startPos);
9798 p->setStartLine(startLine);
9799 p->setEndColumn(endPos);
9800 p->setEndLine(endLine);
9802 std::shared_ptr<Math<Trait>> m(
new Math<Trait>);
9804 if (!fr.m_data.empty()) {
9805 m->setStartColumn(fr.m_data.front().first.virginPos(0));
9806 m->setStartLine(fr.m_data.front().second.m_lineNumber);
9807 m->setEndColumn(fr.m_data.back().first.virginPos(fr.m_data.back().first.length() - 1));
9808 m->setEndLine(fr.m_data.back().second.m_lineNumber);
9810 m->setStartColumn(emptyColumn);
9811 m->setStartLine(startLine);
9812 m->setEndColumn(emptyColumn);
9813 m->setEndLine(startLine);
9816 m->setInline(
false);
9818 m->setStartDelim(startDelim);
9819 m->setEndDelim(endDelim);
9820 m->setSyntaxPos(syntaxPos);
9821 m->setFensedCode(
true);
9824 parent->appendItem(p);
9827 return parseCodeIndentedBySpaces(fr, parent, collectRefLinks, indent, syntax, emptyColumn,
9828 startLine,
true, startDelim, endDelim, syntaxPos);
9835template<
class Trait>
9838 std::shared_ptr<Block<Trait>> parent,
9839 bool collectRefLinks,
9841 const typename Trait::String &syntax,
9842 long long int emptyColumn,
9843 long long int startLine,
9845 const WithPosition &startDelim,
9846 const WithPosition &endDelim,
9847 const WithPosition &syntaxPos)
9849 typename Trait::String code;
9850 long long int startPos = 0;
9853 auto it = fr.m_data.begin(), lastIt = fr.m_data.end();
9855 for (; it != lastIt; ++it) {
9856 if (it->second.m_mayBreakList) {
9861 if (!collectRefLinks) {
9862 const auto ns = skipSpaces<Trait>(0, it->first.asString());
9868 code.push_back((indent > 0 ? it->first.virginSubString(ns < indent ? ns : indent) +
9869 typename Trait::String(Trait::latin1ToChar(
'\n')) :
9870 typename Trait::String(it->first.virginSubString()) +
9871 typename Trait::String(Trait::latin1ToChar(
'\n'))));
9875 if (!collectRefLinks) {
9876 if (!code.isEmpty()) {
9877 code.remove(code.length() - 1, 1);
9880 std::shared_ptr<Code<Trait>> codeItem(
new Code<Trait>(code, fensedCode,
false));
9881 codeItem->setSyntax(syntax);
9882 codeItem->setStartDelim(startDelim);
9883 codeItem->setEndDelim(endDelim);
9884 codeItem->setSyntaxPos(syntaxPos);
9886 if (lastIt != fr.m_data.end() || (it == fr.m_data.end() && !fr.m_data.empty())) {
9887 codeItem->setStartColumn(fr.m_data.front().first.virginPos(startPos));
9888 codeItem->setStartLine(fr.m_data.front().second.m_lineNumber);
9889 auto tmp = std::prev(lastIt);
9890 codeItem->setEndColumn(tmp->first.virginPos(tmp->first.length() - 1));
9891 codeItem->setEndLine(tmp->second.m_lineNumber);
9893 codeItem->setStartColumn(emptyColumn);
9894 codeItem->setStartLine(startLine);
9895 codeItem->setEndColumn(emptyColumn);
9896 codeItem->setEndLine(startLine);
9900 parent->appendItem(codeItem);
9901 }
else if (!parent->items().empty() && parent->items().back()->type() == ItemType::Code) {
9902 auto c = std::static_pointer_cast<Code<Trait>>(parent->items().
back());
9904 if (!c->isFensedCode()) {
9905 auto line = c->endLine();
9906 auto text = c->text();
9908 for (; line < codeItem->startLine(); ++line) {
9909 text.push_back(Trait::latin1ToString(
"\n"));
9912 text.push_back(codeItem->text());
9914 c->setEndColumn(codeItem->endColumn());
9915 c->setEndLine(codeItem->endLine());
9917 parent->appendItem(codeItem);
9920 parent->appendItem(codeItem);
9924 if (lastIt != fr.m_data.end()) {
9925 return lastIt->second.m_lineNumber;
Abstract block (storage of child items).
const Items & items() const
typename Trait::template Vector< WithPosition > Delims
Type of list of service chanracters.
Base class for items that can have style options.
void setOpts(int o)
Set style options.
const Styles & closeStyles() const
const Styles & openStyles() const
typename Trait::template Vector< StyleDelim > Styles
Type of list of emphasis.
Base class for item in Markdown document.
virtual ItemType type() const =0
void removeTextPlugin(int id)
Remove text plugin.
friend struct PrivateAccess
Used in tests.
void addTextPlugin(int id, TextPluginFunc< Trait > plugin, bool processInLinks, const typename Trait::StringList &userData)
Add text plugin.
std::shared_ptr< Document< Trait > > parse(const typename Trait::String &fileName, bool recursive=true, const typename Trait::StringList &ext={Trait::latin1ToString("md"), Trait::latin1ToString("markdown")}, bool fullyOptimizeParagraphs=true)
Wrapper for typename Trait::StringList to be behaved like a stream.
Trait::InternalString lineAt(long long int pos)
std::pair< typename Trait::InternalString, bool > readLine()
void setLineNumber(long long int lineNumber)
long long int size() const
StringListStream(typename MdBlock< Trait >::Data &stream)
long long int currentStreamPos() const
long long int currentLineNumber() const
Emphasis in the Markdown document.
TextStream(QTextStream &stream)
TextStream(std::istream &stream)
Wrapper for UChar32 to be used with MD::Parser.
Wrapper for icu::UnicodeString to be used with MD::Parser.
void push_back(const UnicodeChar &ch)
std::vector< UnicodeString > split(const UnicodeChar &ch) const
UnicodeString scheme() const
UnicodeString host() const
Base for any thing with start and end position.
void setEndColumn(long long int c)
Set end column.
long long int startColumn() const
void setStartColumn(long long int c)
Set start column.
long long int startLine() const
long long int endColumn() const
long long int endLine() const
Q_SCRIPTABLE QString start(QString train="")
Q_SCRIPTABLE Q_NOREPLY void start()
Type type(const QSqlDatabase &db)
QAction * quit(const QObject *recvr, const char *slot, QObject *parent)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT CopyJob * link(const QList< QUrl > &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
QList< QVariant > parse(const QString &message, const QDateTime &externalIssueDateTime=QDateTime())
QString path(const QString &relativePath)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem back(BidiMode useBidi=IgnoreRTL)
QString label(StandardShortcut id)
@ StrikethroughText
Strikethrough.
@ TextWithoutFormat
No format.
bool isOrderedList(const typename Trait::String &s, int *num=nullptr, int *len=nullptr, typename Trait::Char *delim=nullptr, bool *isFirstLineEmpty=nullptr)
Trait::String paragraphToLabel(Paragraph< Trait > *p)
Convert Paragraph to label.
std::pair< long long int, long long int > prevPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
bool isValidUrl< UnicodeStringTrait >(const UnicodeString &url)
bool isMult3(long long int i1, long long int i2)
std::pair< long long int, long long int > nextPosition(const MdBlock< Trait > &fr, long long int pos, long long int line)
bool checkForEndHtmlComments(const typename Trait::String &line, long long int pos)
bool isH1(const typename Trait::String &s)
bool isEmail(const typename Trait::String &url)
bool isLineBreak(const typename Trait::String &s)
TextOption styleToTextOption(Style s)
std::shared_ptr< Text< Trait > > concatenateText(typename Block< Trait >::Items::const_iterator it, typename Block< Trait >::Items::const_iterator last)
Concatenate texts in block.
bool isH(const typename Trait::String &s, const typename Trait::Char &c)
std::tuple< bool, long long int, typename Trait::Char, bool > listItemData(const typename Trait::String &s, bool wasText)
bool isGitHubAutolink< QStringTrait >(const QString &url)
bool isSemiOptimization(OptimizeParagraphType t)
long long int skipSpaces(long long int i, const typename Trait::String &line)
Skip spaces in line from position i.
Trait::InternalString prepareTableData(typename Trait::InternalString s)
Prepare data in table cell for parsing.
void makeTextObject(const typename Trait::String &text, TextParsingOpts< Trait > &po, long long int startPos, long long int startLine, long long int endPos, long long int endLine, bool doRemoveSpacesAtEnd=false)
Make text item.
static const Trait::String s_canBeEscaped
Characters that can be escaped.
int isTableHeader(const typename Trait::String &s)
void initLastItemWithOpts(TextParsingOpts< Trait > &po, std::shared_ptr< ItemWithOpts< Trait > > item)
Initialize item with style information and set it as last item.
std::tuple< long long int, long long int, bool, typename Trait::String, long long int > readLinkDestination(long long int line, long long int pos, const TextParsingOpts< Trait > &po, WithPosition *urlPos=nullptr)
Read link's destination.
Trait::StringList splitString(const typename Trait::String &str, const typename Trait::Char &ch)
Split string.
void removeSpacesAtEnd(String &s)
Remove spaces at the end of string s.
bool isListItemAndNotNested(const typename Trait::String &s, long long int indent)
std::pair< bool, bool > readUnquotedHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
static const char * s_startComment
Starting HTML comment string.
long long int processGitHubAutolinkExtension(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, long long int idx)
Process GitHub autolinks for the text with index idx.
void setLastPos(std::shared_ptr< Item< Trait > > item, long long int pos, long long int line)
Set last position of the item.
long long int lastNonSpacePos(const String &line)
std::shared_ptr< Paragraph< Trait > > optimizeParagraph(std::shared_ptr< Paragraph< Trait > > &p, TextParsingOpts< Trait > &po, OptimizeParagraphType type=OptimizeParagraphType::Full)
Optimize Paragraph.
WithPosition findAndRemoveClosingSequence(typename Trait::InternalString &s)
Find and remove closing sequence of "#" in heading.
std::shared_ptr< Paragraph< Trait > > splitParagraphsAndFreeHtml(std::shared_ptr< Block< Trait > > parent, std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, bool collectRefLinks, bool fullyOptimizeParagraphs=true)
Split Paragraph and free HTML.
bool isFootnote(const typename Trait::String &s)
void githubAutolinkPlugin(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const typename Trait::StringList &)
GitHub autolinks plugin.
void replaceTabs(typename Trait::InternalString &s)
Replace tabs with spaces (just for internal simpler use).
bool isCodeFences(const typename Trait::String &s, bool closing=false)
long long int lineBreakLength(const typename Trait::String &s)
bool indentInList(const std::vector< long long int > *indents, long long int indent, bool codeIndentedBySpaces)
std::pair< long long int, long long int > localPosFromVirgin(const MdBlock< Trait > &fr, long long int virginColumn, long long int virginLine)
OptimizeParagraphType
Type of the paragraph's optimization.
@ Semi
Semi optimization, optimization won't concatenate text items if style delimiters will be in the middl...
@ SemiWithoutRawData
Semi optimization, but raw text data won't be concatenated (will be untouched).
@ FullWithoutRawData
Full optimization, but raw text data won't be concatenated (will be untouched).
std::shared_ptr< Paragraph< Trait > > makeParagraph(typename Block< Trait >::Items::const_iterator first, typename Block< Trait >::Items::const_iterator last)
Make Paragraph.
bool isH2(const typename Trait::String &s)
Trait::String readEscapedSequence(long long int i, const typename Trait::String &str, long long int *endPos=nullptr)
Skip escaped sequence of characters till first space.
std::function< void(std::shared_ptr< Paragraph< Trait > >, TextParsingOpts< Trait > &, const typename Trait::StringList &)> TextPluginFunc
Functor type for text plugin.
bool isGitHubAutolink< UnicodeStringTrait >(const UnicodeString &url)
void normalizePos(long long int &pos, long long int &line, long long int length, long long int linesCount)
Normalize position.
std::pair< bool, bool > readHtmlAttrValue(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Read HTML attribute value.
std::pair< long long int, long long int > calculateIndent(const typename Trait::String &s, long long int p)
void makeHorLine(const typename MdBlock< Trait >::Line &line, std::shared_ptr< Block< Trait > > parent)
Make horizontal line.
long long int emptyLinesBeforeCount(typename MdBlock< Trait >::Data::iterator begin, typename MdBlock< Trait >::Data::iterator it)
void makeText(long long int lastLine, long long int lastPos, TextParsingOpts< Trait > &po)
Make text item.
std::tuple< bool, long long int, long long int, bool, typename Trait::String > isHtmlTag(long long int line, long long int pos, TextParsingOpts< Trait > &po, int rule)
void resetHtmlTag(RawHtmlBlock< Trait > &html, TextParsingOpts< Trait > *po=nullptr)
Reset pre-stored HTML.
bool isStartOfCode(const typename Trait::String &str, typename Trait::String *syntax=nullptr, WithPosition *delim=nullptr, WithPosition *syntaxPos=nullptr)
Trait::String stringToLabel(const typename Trait::String &s)
Convert string to label.
UnicodeStringTrait::StringList splitString< UnicodeStringTrait >(const UnicodeString &str, const UnicodeChar &ch)
int isTableAlignment(const typename Trait::String &s)
bool isColumnAlignment(const typename Trait::String &s)
void makeHeading(std::shared_ptr< Block< Trait > > parent, std::shared_ptr< Document< Trait > > doc, std::shared_ptr< Paragraph< Trait > > p, long long int lastColumn, long long int lastLine, int level, const typename Trait::String &workingPath, const typename Trait::String &fileName, bool collectRefLinks, const WithPosition &delim, TextParsingOpts< Trait > &po)
Make heading.
void checkForTableInParagraph(TextParsingOpts< Trait > &po, long long int lastLine)
Check for table in paragraph.
std::tuple< long long int, long long int, bool, typename Trait::String, long long int > readLinkTitle(long long int line, long long int pos, const TextParsingOpts< Trait > &po)
Read link's title.
bool isOnlyHtmlTagsAfterOrClosedRule1(long long int line, long long int pos, TextParsingOpts< Trait > &po, int rule)
void checkForTextPlugins(std::shared_ptr< Paragraph< Trait > > p, TextParsingOpts< Trait > &po, const TextPluginsMap< Trait > &textPlugins, bool inLink)
Process text plugins.
Trait::String virginSubstr(const MdBlock< Trait > &fr, const WithPosition &virginPos)
std::map< int, std::tuple< TextPluginFunc< Trait >, bool, typename Trait::StringList > > TextPluginsMap
Type of the map of text plugins.
void eatRawHtml(long long int line, long long int pos, long long int toLine, long long int toPos, TextParsingOpts< Trait > &po, bool finish, int htmlRule, bool onLine, bool continueEating=false)
Read HTML data.
void skipSpacesInHtml(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr)
Skip spaces.
@ FootnoteRef
Footnote ref.
void appendCloseStyle(TextParsingOpts< Trait > &po, const StyleDelim &s)
Append close style.
void makeTextObjectWithLineBreak(const typename Trait::String &text, TextParsingOpts< Trait > &po, long long int startPos, long long int startLine, long long int endPos, long long int endLine)
Make text item with line break.
std::pair< typename Trait::InternalStringList, std::vector< long long int > > splitTableRow(const typename Trait::InternalString &s)
Split table's row on cells.
bool isSetextHeadingBetween(const TextParsingOpts< Trait > &po, long long int startLine, long long int endLine)
long long int textAtIdx(std::shared_ptr< Paragraph< Trait > > p, size_t idx)
long long int listLevel(const std::vector< long long int > &indents, long long int pos)
long long int posOfListItem(const typename Trait::String &s, bool ordered)
bool isGitHubAutolink(const typename Trait::String &url)
bool isHorizontalLine(const typename Trait::String &s)
bool isValidUrl(const typename Trait::String &url)
Trait::String startSequence(const typename Trait::String &line)
bool isValidUrl< QStringTrait >(const QString &url)
TextPlugin
ID of text plugin.
@ UnknownPluginID
Unknown plugin.
@ UserDefinedPluginID
First user defined plugin ID.
@ GitHubAutoLinkPluginID
GitHub's autolinks plugin.
void applyStyles(int &opts, std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles)
Apply styles.
long long int lastVirginPositionInParagraph(Item< Trait > *item)
void skipSpacesUpTo1Line(long long int &line, long long int &pos, const typename MdBlock< Trait >::Data &fr)
Skip space in the block up to 1 new line.
bool isHtmlComment(const typename Trait::String &s)
void resolveLinks(typename Trait::StringList &linksToParse, std::shared_ptr< Document< Trait > > doc)
Resolve links in the document.
QStringTrait::StringList splitString< QStringTrait >(const QString &str, const QChar &ch)
bool isWithoutRawDataOptimization(OptimizeParagraphType t)
Trait::String replaceEntity(const typename Trait::String &s)
Replace entities in the string with corresponding character.
static const std::map< typename Trait::String, const char16_t * > s_entityMap
String removeBackslashes(const String &s)
Remove backslashes from the string.
Trait::String removeLineBreak(const typename Trait::String &s)
Remove line break from the end of string.
void updateLastPosInList(const RawHtmlBlock< Trait > &html)
Update last position of all parent.
void closeStyle(std::vector< typename TextParsingOpts< Trait >::StyleInfo > &styles, Style s)
Close style.
std::pair< typename Trait::String, WithPosition > findAndRemoveHeaderLabel(typename Trait::InternalString &s)
Find and remove heading label.
std::pair< bool, bool > readHtmlAttr(long long int &l, long long int &p, const typename MdBlock< Trait >::Data &fr, bool checkForSpace)
Read HTML attribute.
void checkForHtmlComments(const typename Trait::InternalString &line, StringListStream< Trait > &stream, MdLineData::CommentDataMap &res)
Collect information about HTML comments.
bool isEmpty() const const
void push_back(parameter_type value)
QString first(qsizetype n) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString sliced(qsizetype pos) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toCaseFolded() const const
QString toLower() const const
QString toUpper() const const
const QChar * unicode() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype size() const const
QString host(ComponentFormattingOptions options) const const
bool isRelative() const const
bool isValid() const const
QString scheme() const const
Internal structure for block of text in Markdown.
typename Trait::template Vector< Line > Data
std::pair< typename Trait::InternalString, MdLineData > Line
long long int m_emptyLinesBefore
Internal structure for auxiliary information about a line in Markdown.
long long int m_lineNumber
std::pair< char, bool > CommentData
std::map< long long int, CommentData > CommentDataMap
CommentDataMap m_htmlCommentData
Trait to use this library with QString.
Internal structure for pre-storing HTML.
std::unordered_map< std::shared_ptr< Block< Trait > >, SequenceOfBlock > m_toAdjustLastPos
std::vector< std::pair< std::shared_ptr< Block< Trait > >, long long int > > SequenceOfBlock
std::shared_ptr< RawHtml< Trait > > m_html
std::shared_ptr< Block< Trait > > findParent(long long int indent) const
std::shared_ptr< Block< Trait > > m_topParent
std::shared_ptr< Block< Trait > > m_parent
Internal structure for auxiliary options for parser.
bool shouldStopParsing() const
RawHtmlBlock< Trait > & m_html
std::shared_ptr< Document< Trait > > m_doc
bool m_checkLineOnNewType
ItemWithOpts< Trait >::Styles m_openStyles
void concatenateAuxText(long long int start, long long int end)
Trait::StringList & m_linksToParse
long long int m_lastTextPos
long long int m_startTableLine
std::shared_ptr< ItemWithOpts< Trait > > m_lastItemWithStyle
std::shared_ptr< Block< Trait > > m_parent
std::shared_ptr< RawHtml< Trait > > m_tmpHtml
std::shared_ptr< Text< Trait > > m_lastText
const TextPluginsMap< Trait > & m_textPlugins
Trait::String m_workingPath
std::vector< StyleInfo > m_styles
std::vector< TextData > m_rawTextData
long long int m_lastTextLine
Trait to use this library with std::string.
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.