Text.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. /*
  2. * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
  3. * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/ScopeGuard.h>
  8. #include <AK/StringBuilder.h>
  9. #include <LibMarkdown/Text.h>
  10. #include <LibMarkdown/Visitor.h>
  11. #include <ctype.h>
  12. #include <string.h>
  13. namespace Markdown {
  14. void Text::EmphasisNode::render_to_html(StringBuilder& builder) const
  15. {
  16. builder.append((strong) ? "<strong>"sv : "<em>"sv);
  17. child->render_to_html(builder);
  18. builder.append((strong) ? "</strong>"sv : "</em>"sv);
  19. }
  20. void Text::EmphasisNode::render_for_terminal(StringBuilder& builder) const
  21. {
  22. if (strong) {
  23. builder.append("\e[1m"sv);
  24. child->render_for_terminal(builder);
  25. builder.append("\e[22m"sv);
  26. } else {
  27. builder.append("\e[3m"sv);
  28. child->render_for_terminal(builder);
  29. builder.append("\e[23m"sv);
  30. }
  31. }
  32. void Text::EmphasisNode::render_for_raw_print(StringBuilder& builder) const
  33. {
  34. child->render_for_raw_print(builder);
  35. }
  36. size_t Text::EmphasisNode::terminal_length() const
  37. {
  38. return child->terminal_length();
  39. }
  40. RecursionDecision Text::EmphasisNode::walk(Visitor& visitor) const
  41. {
  42. RecursionDecision rd = visitor.visit(*this);
  43. if (rd != RecursionDecision::Recurse)
  44. return rd;
  45. return child->walk(visitor);
  46. }
  47. void Text::CodeNode::render_to_html(StringBuilder& builder) const
  48. {
  49. builder.append("<code>"sv);
  50. code->render_to_html(builder);
  51. builder.append("</code>"sv);
  52. }
  53. void Text::CodeNode::render_for_terminal(StringBuilder& builder) const
  54. {
  55. builder.append("\e[1m"sv);
  56. code->render_for_terminal(builder);
  57. builder.append("\e[22m"sv);
  58. }
  59. void Text::CodeNode::render_for_raw_print(StringBuilder& builder) const
  60. {
  61. code->render_for_raw_print(builder);
  62. }
  63. size_t Text::CodeNode::terminal_length() const
  64. {
  65. return code->terminal_length();
  66. }
  67. RecursionDecision Text::CodeNode::walk(Visitor& visitor) const
  68. {
  69. RecursionDecision rd = visitor.visit(*this);
  70. if (rd != RecursionDecision::Recurse)
  71. return rd;
  72. return code->walk(visitor);
  73. }
  74. void Text::BreakNode::render_to_html(StringBuilder& builder) const
  75. {
  76. builder.append("<br />"sv);
  77. }
  78. void Text::BreakNode::render_for_terminal(StringBuilder&) const
  79. {
  80. }
  81. void Text::BreakNode::render_for_raw_print(StringBuilder&) const
  82. {
  83. }
  84. size_t Text::BreakNode::terminal_length() const
  85. {
  86. return 0;
  87. }
  88. RecursionDecision Text::BreakNode::walk(Visitor& visitor) const
  89. {
  90. RecursionDecision rd = visitor.visit(*this);
  91. if (rd != RecursionDecision::Recurse)
  92. return rd;
  93. // Normalize return value
  94. return RecursionDecision::Continue;
  95. }
  96. void Text::TextNode::render_to_html(StringBuilder& builder) const
  97. {
  98. builder.append(escape_html_entities(text));
  99. }
  100. void Text::TextNode::render_for_raw_print(StringBuilder& builder) const
  101. {
  102. builder.append(text);
  103. }
  104. void Text::TextNode::render_for_terminal(StringBuilder& builder) const
  105. {
  106. if (collapsible && (text == "\n" || text.is_whitespace())) {
  107. builder.append(' ');
  108. } else {
  109. builder.append(text);
  110. }
  111. }
  112. size_t Text::TextNode::terminal_length() const
  113. {
  114. if (collapsible && text.is_whitespace()) {
  115. return 1;
  116. }
  117. return text.length();
  118. }
  119. RecursionDecision Text::TextNode::walk(Visitor& visitor) const
  120. {
  121. RecursionDecision rd = visitor.visit(*this);
  122. if (rd != RecursionDecision::Recurse)
  123. return rd;
  124. rd = visitor.visit(text);
  125. if (rd != RecursionDecision::Recurse)
  126. return rd;
  127. // Normalize return value
  128. return RecursionDecision::Continue;
  129. }
  130. void Text::LinkNode::render_to_html(StringBuilder& builder) const
  131. {
  132. if (is_image) {
  133. builder.append("<img src=\""sv);
  134. builder.append(escape_html_entities(href));
  135. if (has_image_dimensions()) {
  136. builder.append("\" style=\""sv);
  137. if (image_width.has_value())
  138. builder.appendff("width: {}px;", *image_width);
  139. if (image_height.has_value())
  140. builder.appendff("height: {}px;", *image_height);
  141. }
  142. builder.append("\" alt=\""sv);
  143. text->render_to_html(builder);
  144. builder.append("\" >"sv);
  145. } else {
  146. builder.append("<a href=\""sv);
  147. builder.append(escape_html_entities(href));
  148. builder.append("\">"sv);
  149. text->render_to_html(builder);
  150. builder.append("</a>"sv);
  151. }
  152. }
  153. void Text::LinkNode::render_for_raw_print(StringBuilder& builder) const
  154. {
  155. text->render_for_raw_print(builder);
  156. }
  157. void Text::LinkNode::render_for_terminal(StringBuilder& builder) const
  158. {
  159. bool is_linked = href.contains("://"sv);
  160. if (is_linked) {
  161. builder.append("\033[0;34m\e]8;;"sv);
  162. builder.append(href);
  163. builder.append("\e\\"sv);
  164. }
  165. text->render_for_terminal(builder);
  166. if (is_linked) {
  167. builder.appendff(" <{}>", href);
  168. builder.append("\033]8;;\033\\\033[0m"sv);
  169. }
  170. }
  171. size_t Text::LinkNode::terminal_length() const
  172. {
  173. return text->terminal_length();
  174. }
  175. RecursionDecision Text::LinkNode::walk(Visitor& visitor) const
  176. {
  177. RecursionDecision rd = visitor.visit(*this);
  178. if (rd != RecursionDecision::Recurse)
  179. return rd;
  180. // Don't recurse on href.
  181. return text->walk(visitor);
  182. }
  183. void Text::MultiNode::render_to_html(StringBuilder& builder) const
  184. {
  185. for (auto& child : children) {
  186. child->render_to_html(builder);
  187. }
  188. }
  189. void Text::MultiNode::render_for_raw_print(StringBuilder& builder) const
  190. {
  191. for (auto& child : children) {
  192. child->render_for_raw_print(builder);
  193. }
  194. }
  195. void Text::MultiNode::render_for_terminal(StringBuilder& builder) const
  196. {
  197. for (auto& child : children) {
  198. child->render_for_terminal(builder);
  199. }
  200. }
  201. size_t Text::MultiNode::terminal_length() const
  202. {
  203. size_t length = 0;
  204. for (auto& child : children) {
  205. length += child->terminal_length();
  206. }
  207. return length;
  208. }
  209. RecursionDecision Text::MultiNode::walk(Visitor& visitor) const
  210. {
  211. RecursionDecision rd = visitor.visit(*this);
  212. if (rd != RecursionDecision::Recurse)
  213. return rd;
  214. for (auto const& child : children) {
  215. rd = child->walk(visitor);
  216. if (rd == RecursionDecision::Break)
  217. return rd;
  218. }
  219. return RecursionDecision::Continue;
  220. }
  221. void Text::StrikeThroughNode::render_to_html(StringBuilder& builder) const
  222. {
  223. builder.append("<del>"sv);
  224. striked_text->render_to_html(builder);
  225. builder.append("</del>"sv);
  226. }
  227. void Text::StrikeThroughNode::render_for_raw_print(StringBuilder& builder) const
  228. {
  229. striked_text->render_for_raw_print(builder);
  230. }
  231. void Text::StrikeThroughNode::render_for_terminal(StringBuilder& builder) const
  232. {
  233. builder.append("\e[9m"sv);
  234. striked_text->render_for_terminal(builder);
  235. builder.append("\e[29m"sv);
  236. }
  237. size_t Text::StrikeThroughNode::terminal_length() const
  238. {
  239. return striked_text->terminal_length();
  240. }
  241. RecursionDecision Text::StrikeThroughNode::walk(Visitor& visitor) const
  242. {
  243. RecursionDecision rd = visitor.visit(*this);
  244. if (rd != RecursionDecision::Recurse)
  245. return rd;
  246. return striked_text->walk(visitor);
  247. }
  248. size_t Text::terminal_length() const
  249. {
  250. return m_node->terminal_length();
  251. }
  252. ByteString Text::render_to_html() const
  253. {
  254. StringBuilder builder;
  255. m_node->render_to_html(builder);
  256. return builder.to_byte_string().trim(" \n\t"sv);
  257. }
  258. ByteString Text::render_for_raw_print() const
  259. {
  260. StringBuilder builder;
  261. m_node->render_for_raw_print(builder);
  262. return builder.to_byte_string().trim(" \n\t"sv);
  263. }
  264. ByteString Text::render_for_terminal() const
  265. {
  266. StringBuilder builder;
  267. m_node->render_for_terminal(builder);
  268. return builder.to_byte_string().trim(" \n\t"sv);
  269. }
  270. RecursionDecision Text::walk(Visitor& visitor) const
  271. {
  272. RecursionDecision rd = visitor.visit(*this);
  273. if (rd != RecursionDecision::Recurse)
  274. return rd;
  275. return m_node->walk(visitor);
  276. }
  277. Text Text::parse(StringView str)
  278. {
  279. Text text;
  280. auto const tokens = tokenize(str);
  281. auto iterator = tokens.begin();
  282. text.m_node = parse_sequence(iterator, false);
  283. return text;
  284. }
  285. static bool flanking(StringView str, size_t start, size_t end, int dir)
  286. {
  287. ssize_t next = ((dir > 0) ? end : start) + dir;
  288. if (next < 0 || next >= (ssize_t)str.length())
  289. return false;
  290. if (isspace(str[next]))
  291. return false;
  292. if (!ispunct(str[next]))
  293. return true;
  294. ssize_t prev = ((dir > 0) ? start : end) - dir;
  295. if (prev < 0 || prev >= (ssize_t)str.length())
  296. return true;
  297. return isspace(str[prev]) || ispunct(str[prev]);
  298. }
  299. Vector<Text::Token> Text::tokenize(StringView str)
  300. {
  301. Vector<Token> tokens;
  302. StringBuilder current_token;
  303. auto flush_run = [&](bool left_flanking, bool right_flanking, bool punct_before, bool punct_after, bool is_run) {
  304. if (current_token.is_empty())
  305. return;
  306. tokens.append({
  307. current_token.to_byte_string(),
  308. left_flanking,
  309. right_flanking,
  310. punct_before,
  311. punct_after,
  312. is_run,
  313. });
  314. current_token.clear();
  315. };
  316. auto flush_token = [&]() {
  317. flush_run(false, false, false, false, false);
  318. };
  319. bool in_space = false;
  320. for (size_t offset = 0; offset < str.length(); ++offset) {
  321. auto has = [&](StringView seq) {
  322. if (offset + seq.length() > str.length())
  323. return false;
  324. return str.substring_view(offset, seq.length()) == seq;
  325. };
  326. auto expect = [&](StringView seq) {
  327. VERIFY(has(seq));
  328. flush_token();
  329. current_token.append(seq);
  330. flush_token();
  331. offset += seq.length() - 1;
  332. };
  333. char ch = str[offset];
  334. if (ch != ' ' && in_space) {
  335. flush_token();
  336. in_space = false;
  337. }
  338. if (ch == '\\' && offset + 1 < str.length() && ispunct(str[offset + 1])) {
  339. current_token.append(str[offset + 1]);
  340. ++offset;
  341. } else if (ch == '*' || ch == '_' || ch == '`' || ch == '~') {
  342. flush_token();
  343. char delim = ch;
  344. size_t run_offset;
  345. for (run_offset = offset; run_offset < str.length() && str[run_offset] == delim; ++run_offset) {
  346. current_token.append(str[run_offset]);
  347. }
  348. flush_run(flanking(str, offset, run_offset - 1, +1),
  349. flanking(str, offset, run_offset - 1, -1),
  350. offset > 0 && ispunct(str[offset - 1]),
  351. run_offset < str.length() && ispunct(str[run_offset]),
  352. true);
  353. offset = run_offset - 1;
  354. } else if (ch == ' ') {
  355. if (!in_space) {
  356. flush_token();
  357. in_space = true;
  358. }
  359. current_token.append(ch);
  360. } else if (has("\n"sv)) {
  361. expect("\n"sv);
  362. } else if (has("["sv)) {
  363. expect("["sv);
  364. } else if (has("!["sv)) {
  365. expect("!["sv);
  366. } else if (has("]("sv)) {
  367. expect("]("sv);
  368. } else if (has(")"sv)) {
  369. expect(")"sv);
  370. } else {
  371. current_token.append(ch);
  372. }
  373. }
  374. flush_token();
  375. return tokens;
  376. }
  377. NonnullOwnPtr<Text::MultiNode> Text::parse_sequence(Vector<Token>::ConstIterator& tokens, bool in_link)
  378. {
  379. auto node = make<MultiNode>();
  380. for (; !tokens.is_end(); ++tokens) {
  381. if (tokens->is_space()) {
  382. node->children.append(parse_break(tokens));
  383. } else if (*tokens == "\n"sv) {
  384. node->children.append(parse_newline(tokens));
  385. } else if (tokens->is_run) {
  386. switch (tokens->run_char()) {
  387. case '*':
  388. case '_':
  389. node->children.append(parse_emph(tokens, in_link));
  390. break;
  391. case '`':
  392. node->children.append(parse_code(tokens));
  393. break;
  394. case '~':
  395. node->children.append(parse_strike_through(tokens));
  396. break;
  397. }
  398. } else if (*tokens == "["sv || *tokens == "!["sv) {
  399. node->children.append(parse_link(tokens));
  400. } else if (in_link && *tokens == "]("sv) {
  401. return node;
  402. } else {
  403. node->children.append(make<TextNode>(tokens->data));
  404. }
  405. if (in_link && !tokens.is_end() && *tokens == "]("sv)
  406. return node;
  407. if (tokens.is_end())
  408. break;
  409. }
  410. return node;
  411. }
  412. NonnullOwnPtr<Text::Node> Text::parse_break(Vector<Token>::ConstIterator& tokens)
  413. {
  414. auto next_tok = tokens + 1;
  415. if (next_tok.is_end() || *next_tok != "\n"sv)
  416. return make<TextNode>(tokens->data);
  417. if (tokens->data.length() >= 2)
  418. return make<BreakNode>();
  419. return make<MultiNode>();
  420. }
  421. NonnullOwnPtr<Text::Node> Text::parse_newline(Vector<Token>::ConstIterator& tokens)
  422. {
  423. auto node = make<TextNode>(tokens->data);
  424. auto next_tok = tokens + 1;
  425. if (!next_tok.is_end() && next_tok->is_space())
  426. // Skip whitespace after newline.
  427. ++tokens;
  428. return node;
  429. }
  430. bool Text::can_open(Token const& opening)
  431. {
  432. return (opening.run_char() == '~' && opening.left_flanking) || (opening.run_char() == '*' && opening.left_flanking) || (opening.run_char() == '_' && opening.left_flanking && (!opening.right_flanking || opening.punct_before));
  433. }
  434. bool Text::can_close_for(Token const& opening, Text::Token const& closing)
  435. {
  436. if (opening.run_char() != closing.run_char())
  437. return false;
  438. if (opening.run_length() != closing.run_length())
  439. return false;
  440. return (opening.run_char() == '~' && closing.right_flanking) || (opening.run_char() == '*' && closing.right_flanking) || (opening.run_char() == '_' && closing.right_flanking && (!closing.left_flanking || closing.punct_after));
  441. }
  442. NonnullOwnPtr<Text::Node> Text::parse_emph(Vector<Token>::ConstIterator& tokens, bool in_link)
  443. {
  444. auto opening = *tokens;
  445. // Check that the opening delimiter run is properly flanking.
  446. if (!can_open(opening))
  447. return make<TextNode>(opening.data);
  448. auto child = make<MultiNode>();
  449. for (++tokens; !tokens.is_end(); ++tokens) {
  450. if (tokens->is_space()) {
  451. child->children.append(parse_break(tokens));
  452. } else if (*tokens == "\n"sv) {
  453. child->children.append(parse_newline(tokens));
  454. } else if (tokens->is_run) {
  455. if (can_close_for(opening, *tokens)) {
  456. return make<EmphasisNode>(opening.run_length() >= 2, move(child));
  457. }
  458. switch (tokens->run_char()) {
  459. case '*':
  460. case '_':
  461. child->children.append(parse_emph(tokens, in_link));
  462. break;
  463. case '`':
  464. child->children.append(parse_code(tokens));
  465. break;
  466. case '~':
  467. child->children.append(parse_strike_through(tokens));
  468. break;
  469. }
  470. } else if (*tokens == "["sv || *tokens == "!["sv) {
  471. child->children.append(parse_link(tokens));
  472. } else if (in_link && *tokens == "]("sv) {
  473. child->children.prepend(make<TextNode>(opening.data));
  474. return child;
  475. } else {
  476. child->children.append(make<TextNode>(tokens->data));
  477. }
  478. if (in_link && !tokens.is_end() && *tokens == "]("sv) {
  479. child->children.prepend(make<TextNode>(opening.data));
  480. return child;
  481. }
  482. if (tokens.is_end())
  483. break;
  484. }
  485. child->children.prepend(make<TextNode>(opening.data));
  486. return child;
  487. }
  488. NonnullOwnPtr<Text::Node> Text::parse_code(Vector<Token>::ConstIterator& tokens)
  489. {
  490. auto opening = *tokens;
  491. auto is_closing = [&](Token const& token) {
  492. return token.is_run && token.run_char() == '`' && token.run_length() == opening.run_length();
  493. };
  494. bool is_all_whitespace = true;
  495. auto code = make<MultiNode>();
  496. for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) {
  497. if (is_closing(*iterator)) {
  498. tokens = iterator;
  499. // Strip first and last space, when appropriate.
  500. if (!is_all_whitespace) {
  501. auto& first = dynamic_cast<TextNode&>(*code->children.first());
  502. auto& last = dynamic_cast<TextNode&>(*code->children.last());
  503. if (first.text.starts_with(' ') && last.text.ends_with(' ')) {
  504. first.text = first.text.substring(1);
  505. last.text = last.text.substring(0, last.text.length() - 1);
  506. }
  507. }
  508. return make<CodeNode>(move(code));
  509. }
  510. is_all_whitespace = is_all_whitespace && iterator->data.is_whitespace();
  511. code->children.append(make<TextNode>((*iterator == "\n"sv) ? " " : iterator->data, false));
  512. }
  513. return make<TextNode>(opening.data);
  514. }
  515. NonnullOwnPtr<Text::Node> Text::parse_link(Vector<Token>::ConstIterator& tokens)
  516. {
  517. auto opening = *tokens++;
  518. bool is_image = opening == "!["sv;
  519. auto link_text = parse_sequence(tokens, true);
  520. if (tokens.is_end() || *tokens != "]("sv) {
  521. link_text->children.prepend(make<TextNode>(opening.data));
  522. return link_text;
  523. }
  524. auto separator = *tokens;
  525. VERIFY(separator == "]("sv);
  526. Optional<int> image_width;
  527. Optional<int> image_height;
  528. auto parse_image_dimensions = [&](StringView dimensions) -> bool {
  529. if (!dimensions.starts_with('='))
  530. return false;
  531. ArmedScopeGuard clear_image_dimensions = [&] {
  532. image_width = {};
  533. image_height = {};
  534. };
  535. auto dimension_seperator = dimensions.find('x', 1);
  536. if (!dimension_seperator.has_value())
  537. return false;
  538. auto width_string = dimensions.substring_view(1, *dimension_seperator - 1);
  539. if (!width_string.is_empty()) {
  540. auto width = width_string.to_number<int>();
  541. if (!width.has_value())
  542. return false;
  543. image_width = width;
  544. }
  545. auto height_start = *dimension_seperator + 1;
  546. if (height_start < dimensions.length()) {
  547. auto height_string = dimensions.substring_view(height_start);
  548. auto height = height_string.to_number<int>();
  549. if (!height.has_value())
  550. return false;
  551. image_height = height;
  552. }
  553. clear_image_dimensions.disarm();
  554. return true;
  555. };
  556. StringBuilder address;
  557. for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) {
  558. // FIXME: What to do if there's multiple dimension tokens?
  559. if (is_image && !address.is_empty() && parse_image_dimensions(iterator->data))
  560. continue;
  561. if (*iterator == ")"sv) {
  562. tokens = iterator;
  563. ByteString href = address.to_byte_string().trim_whitespace();
  564. // Add file:// if the link is an absolute path otherwise it will be assumed relative.
  565. if (AK::StringUtils::starts_with(href, "/"sv, CaseSensitivity::CaseSensitive))
  566. href = ByteString::formatted("file://{}", href);
  567. return make<LinkNode>(is_image, move(link_text), move(href), image_width, image_height);
  568. }
  569. address.append(iterator->data);
  570. }
  571. link_text->children.prepend(make<TextNode>(opening.data));
  572. link_text->children.append(make<TextNode>(separator.data));
  573. return link_text;
  574. }
  575. NonnullOwnPtr<Text::Node> Text::parse_strike_through(Vector<Token>::ConstIterator& tokens)
  576. {
  577. auto opening = *tokens;
  578. auto is_closing = [&](Token const& token) {
  579. return token.is_run && token.run_char() == '~' && token.run_length() == opening.run_length();
  580. };
  581. bool is_all_whitespace = true;
  582. auto striked_text = make<MultiNode>();
  583. for (auto iterator = tokens + 1; !iterator.is_end(); ++iterator) {
  584. if (is_closing(*iterator)) {
  585. tokens = iterator;
  586. if (!is_all_whitespace) {
  587. auto& first = dynamic_cast<TextNode&>(*striked_text->children.first());
  588. auto& last = dynamic_cast<TextNode&>(*striked_text->children.last());
  589. if (first.text.starts_with(' ') && last.text.ends_with(' ')) {
  590. first.text = first.text.substring(1);
  591. last.text = last.text.substring(0, last.text.length() - 1);
  592. }
  593. }
  594. return make<StrikeThroughNode>(move(striked_text));
  595. }
  596. is_all_whitespace = is_all_whitespace && iterator->data.is_whitespace();
  597. striked_text->children.append(make<TextNode>((*iterator == "\n"sv) ? " " : iterator->data, false));
  598. }
  599. return make<TextNode>(opening.data);
  600. }
  601. }