use pulldown_cmark::{ Alignment, BlockQuoteKind, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, TextMergeStream, }; use std::collections::HashMap; use std::fmt::Write; // ========== Класс состояния таблицы ========== #[derive(Debug, Clone, PartialEq)] pub enum AlignmentState { Left, Center, Right, None, } impl From for AlignmentState { fn from(alignment: Alignment) -> Self { match alignment { Alignment::Left => AlignmentState::Left, Alignment::Center => AlignmentState::Center, Alignment::Right => AlignmentState::Right, Alignment::None => AlignmentState::None, } } } impl AlignmentState { pub fn to_css_class(&self) -> &'static str { match self { AlignmentState::Left => "align-left", AlignmentState::Center => "align-center", AlignmentState::Right => "align-right", AlignmentState::None => "", } } } #[derive(Debug, Clone, Default)] pub struct TableState { pub alignments: Vec, pub column_count: usize, pub is_inside_table: bool, pub is_inside_head: bool, pub is_inside_body: bool, pub current_row: usize, pub current_column: usize, pub cell_data: HashMap<(usize, usize), String>, pub has_header: bool, } impl TableState { pub fn new() -> Self { Self::default() } pub fn start_table(&mut self, alignments: Vec) { self.alignments = alignments.into_iter().map(AlignmentState::from).collect(); self.column_count = self.alignments.len(); self.is_inside_table = true; self.is_inside_head = false; self.is_inside_body = false; self.current_row = 0; self.current_column = 0; self.cell_data.clear(); self.has_header = false; } pub fn end_table(&mut self) { self.is_inside_table = false; self.is_inside_head = false; self.is_inside_body = false; } pub fn start_head(&mut self) { if self.is_inside_table { self.is_inside_head = true; self.is_inside_body = false; self.current_row = 0; self.current_column = 0; self.has_header = true; } } pub fn end_head(&mut self) { self.is_inside_head = false; } pub fn start_row(&mut self) { if self.is_inside_table { if self.is_inside_body { self.current_row += 1; } self.current_column = 0; } } pub fn end_row(&mut self) { if self.current_column != self.column_count && self.is_inside_table && self.column_count > 0 { // Исправляем неполные строки, добавляя недостающие ячейки while self.current_column < self.column_count { let row = if self.is_inside_head { 0 } else { self.current_row }; let col = self.current_column; self.cell_data.insert((row, col), String::new()); self.current_column += 1; } } } pub fn start_cell(&mut self) { if self.is_inside_table { self.current_column += 1; } } pub fn end_cell(&mut self, data: Option) { if let Some(text) = data { let row = if self.is_inside_head { 0 } else { self.current_row }; let col = self.current_column - 1; self.cell_data.insert((row, col), text); } } pub fn get_current_alignment(&self) -> &AlignmentState { if self.current_column == 0 || self.current_column > self.alignments.len() { return &AlignmentState::None; } let col_index = self.current_column - 1; &self.alignments[col_index] } } // ========== Основной рендерер ========== pub struct MarkdownRenderer { table_state: TableState, in_code_block: bool, code_content: String, } impl MarkdownRenderer { pub fn new() -> Self { Self { table_state: TableState::new(), in_code_block: false, code_content: String::new(), } } pub fn render_start_tag(&mut self, tag: Tag<'_>) -> String { let mut output = String::new(); match tag { Tag::Heading { level, .. } => { output.push_str(format!("<{level}>").as_str()); } Tag::Strikethrough => { output.push_str(""); } Tag::List(Some(_)) => output.push_str("
    "), Tag::List(None) => output.push_str("