MDText.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #include <AK/StringBuilder.h>
  2. #include <LibMarkdown/MDText.h>
  3. static String unescape(const StringView& text)
  4. {
  5. StringBuilder builder;
  6. for (int i = 0; i < text.length(); ++i) {
  7. if (text[i] == '\\' && i != text.length() - 1) {
  8. builder.append(text[i + 1]);
  9. i++;
  10. continue;
  11. }
  12. builder.append(text[i]);
  13. }
  14. return builder.build();
  15. }
  16. String MDText::render_to_html() const
  17. {
  18. StringBuilder builder;
  19. Vector<String> open_tags;
  20. Style current_style;
  21. for (auto& span : m_spans) {
  22. struct TagAndFlag {
  23. String tag;
  24. bool Style::*flag;
  25. };
  26. TagAndFlag tags_and_flags[] = {
  27. { "i", &Style::emph },
  28. { "b", &Style::strong },
  29. { "code", &Style::code }
  30. };
  31. auto it = open_tags.find([&](const String& open_tag) {
  32. for (auto& tag_and_flag : tags_and_flags) {
  33. if (open_tag == tag_and_flag.tag && !(span.style.*tag_and_flag.flag))
  34. return true;
  35. }
  36. return false;
  37. });
  38. if (!it.is_end()) {
  39. // We found an open tag that should
  40. // not be open for the new span. Close
  41. // it and all the open tags that follow
  42. // it.
  43. for (auto it2 = --open_tags.end(); it2 >= it; --it2) {
  44. const String& tag = *it2;
  45. builder.appendf("</%s>", tag.characters());
  46. for (auto& tag_and_flag : tags_and_flags)
  47. if (tag == tag_and_flag.tag)
  48. current_style.*tag_and_flag.flag = false;
  49. }
  50. open_tags.shrink(it.index());
  51. }
  52. for (auto& tag_and_flag : tags_and_flags) {
  53. if (current_style.*tag_and_flag.flag != span.style.*tag_and_flag.flag) {
  54. open_tags.append(tag_and_flag.tag);
  55. builder.appendf("<%s>", tag_and_flag.tag.characters());
  56. }
  57. }
  58. current_style = span.style;
  59. builder.append(span.text);
  60. }
  61. for (auto it = --open_tags.end(); it >= open_tags.begin(); --it) {
  62. const String& tag = *it;
  63. builder.appendf("</%s>", tag.characters());
  64. }
  65. return builder.build();
  66. }
  67. String MDText::render_for_terminal() const
  68. {
  69. StringBuilder builder;
  70. for (auto& span : m_spans) {
  71. bool needs_styling = span.style.strong || span.style.emph || span.style.code;
  72. if (needs_styling) {
  73. builder.append("\033[");
  74. bool first = true;
  75. if (span.style.strong || span.style.code) {
  76. builder.append('1');
  77. first = false;
  78. }
  79. if (span.style.emph) {
  80. if (!first)
  81. builder.append(';');
  82. builder.append('4');
  83. }
  84. builder.append('m');
  85. }
  86. builder.append(span.text.characters());
  87. if (needs_styling)
  88. builder.append("\033[0m");
  89. }
  90. return builder.build();
  91. }
  92. bool MDText::parse(const StringView& str)
  93. {
  94. Style current_style;
  95. int current_span_start = 0;
  96. for (int offset = 0; offset < str.length(); offset++) {
  97. char ch = str[offset];
  98. bool is_escape = ch == '\\';
  99. if (is_escape && offset != str.length() - 1) {
  100. offset++;
  101. continue;
  102. }
  103. bool is_special_character = false;
  104. is_special_character |= ch == '`';
  105. if (!current_style.code)
  106. is_special_character |= ch == '*' || ch == '_';
  107. if (!is_special_character)
  108. continue;
  109. if (current_span_start != offset) {
  110. Span span {
  111. str.substring_view(current_span_start, offset - current_span_start),
  112. current_style
  113. };
  114. m_spans.append(move(span));
  115. }
  116. if (ch == '`') {
  117. current_style.code = !current_style.code;
  118. } else {
  119. if (offset + 1 < str.length() && str[offset + 1] == ch) {
  120. offset++;
  121. current_style.strong = !current_style.strong;
  122. } else {
  123. current_style.emph = !current_style.emph;
  124. }
  125. }
  126. current_span_start = offset + 1;
  127. }
  128. if (current_span_start < str.length()) {
  129. Span span {
  130. unescape(str.substring_view(current_span_start, str.length() - current_span_start)),
  131. current_style
  132. };
  133. m_spans.append(move(span));
  134. }
  135. return true;
  136. }