mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:22:45 +00:00 
			
		
		
		
	 2227a80f34
			
		
	
	
		2227a80f34
		
	
	
	
	
		
			
			This patch adds contexts to line iterator for nesting list items and blockquotes. It also incidentally makes the api for LineIterator simpler, and will make it easier to add other containers in the future.
		
			
				
	
	
		
			139 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			139 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
 | |
|  * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/StringBuilder.h>
 | |
| #include <LibMarkdown/List.h>
 | |
| #include <LibMarkdown/Paragraph.h>
 | |
| 
 | |
| namespace Markdown {
 | |
| 
 | |
| String List::render_to_html(bool) const
 | |
| {
 | |
|     StringBuilder builder;
 | |
| 
 | |
|     const char* tag = m_is_ordered ? "ol" : "ul";
 | |
|     builder.appendff("<{}", tag);
 | |
| 
 | |
|     if (m_start_number != 1)
 | |
|         builder.appendff(" start=\"{}\"", m_start_number);
 | |
| 
 | |
|     builder.append(">\n");
 | |
| 
 | |
|     for (auto& item : m_items) {
 | |
|         builder.append("<li>");
 | |
|         if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast<Paragraph const*>(&(item->blocks()[0]))))
 | |
|             builder.append("\n");
 | |
|         builder.append(item->render_to_html(m_is_tight));
 | |
|         builder.append("</li>\n");
 | |
|     }
 | |
| 
 | |
|     builder.appendff("</{}>\n", tag);
 | |
| 
 | |
|     return builder.build();
 | |
| }
 | |
| 
 | |
| String List::render_for_terminal(size_t) const
 | |
| {
 | |
|     StringBuilder builder;
 | |
| 
 | |
|     int i = 0;
 | |
|     for (auto& item : m_items) {
 | |
|         builder.append("  ");
 | |
|         if (m_is_ordered)
 | |
|             builder.appendff("{}. ", ++i);
 | |
|         else
 | |
|             builder.append("* ");
 | |
|         builder.append(item->render_for_terminal());
 | |
|         builder.append("\n");
 | |
|     }
 | |
|     builder.append("\n");
 | |
| 
 | |
|     return builder.build();
 | |
| }
 | |
| 
 | |
| OwnPtr<List> List::parse(LineIterator& lines)
 | |
| {
 | |
|     Vector<OwnPtr<ContainerBlock>> items;
 | |
| 
 | |
|     bool first = true;
 | |
|     bool is_ordered = false;
 | |
| 
 | |
|     bool is_tight = true;
 | |
|     bool has_trailing_blank_lines = false;
 | |
|     size_t start_number = 1;
 | |
| 
 | |
|     while (!lines.is_end()) {
 | |
| 
 | |
|         size_t offset = 0;
 | |
| 
 | |
|         const StringView& line = *lines;
 | |
| 
 | |
|         bool appears_unordered = false;
 | |
| 
 | |
|         while (offset < line.length() && line[offset] == ' ')
 | |
|             ++offset;
 | |
| 
 | |
|         if (offset + 2 <= line.length()) {
 | |
|             if (line[offset + 1] == ' ' && (line[offset] == '*' || line[offset] == '-' || line[offset] == '+')) {
 | |
|                 appears_unordered = true;
 | |
|                 offset++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool appears_ordered = false;
 | |
|         for (size_t i = offset; i < 10 && i < line.length(); i++) {
 | |
|             char ch = line[i];
 | |
|             if ('0' <= ch && ch <= '9')
 | |
|                 continue;
 | |
|             if (ch == '.' || ch == ')')
 | |
|                 if (i + 1 < line.length() && line[i + 1] == ' ') {
 | |
|                     auto maybe_start_number = line.substring_view(offset, i - offset).to_uint<size_t>();
 | |
|                     if (!maybe_start_number.has_value())
 | |
|                         break;
 | |
|                     if (first)
 | |
|                         start_number = maybe_start_number.value();
 | |
|                     appears_ordered = true;
 | |
|                     offset = i + 1;
 | |
|                 }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         VERIFY(!(appears_unordered && appears_ordered));
 | |
|         if (!appears_unordered && !appears_ordered) {
 | |
|             if (first)
 | |
|                 return {};
 | |
| 
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         while (offset < line.length() && line[offset] == ' ')
 | |
|             offset++;
 | |
| 
 | |
|         if (first) {
 | |
|             is_ordered = appears_ordered;
 | |
|         } else if (appears_ordered != is_ordered) {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         is_tight = is_tight && !has_trailing_blank_lines;
 | |
| 
 | |
|         lines.push_context(LineIterator::Context::list_item(offset));
 | |
| 
 | |
|         auto list_item = ContainerBlock::parse(lines);
 | |
|         is_tight = is_tight && !list_item->has_blank_lines();
 | |
|         has_trailing_blank_lines = has_trailing_blank_lines || list_item->has_trailing_blank_lines();
 | |
|         items.append(move(list_item));
 | |
| 
 | |
|         lines.pop_context();
 | |
| 
 | |
|         first = false;
 | |
|     }
 | |
| 
 | |
|     return make<List>(move(items), is_ordered, is_tight, start_number);
 | |
| }
 | |
| 
 | |
| }
 |