pulldown_html_ext/html/
state.rs1use pulldown_cmark::{Alignment, LinkType};
2
3#[derive(Copy, Clone, Debug, PartialEq, Default)]
5pub enum TableContext {
6 #[default]
8 NotInTable,
9 InHeader,
11 InBody,
13}
14
15#[derive(Copy, Clone, Debug, PartialEq, Default)]
17pub enum ListContext {
18 Ordered(u32),
20 #[default]
22 Unordered,
23}
24
25pub struct HtmlState {
27 pub numbers: Vec<u32>,
29 pub table_state: TableContext,
31 pub table_cell_index: usize,
33 pub table_alignments: Vec<Alignment>,
35 pub list_stack: Vec<ListContext>,
37 pub link_stack: Vec<LinkType>,
39 pub heading_stack: Vec<String>,
41 pub currently_in_code_block: bool,
43 pub currently_in_footnote: bool,
45}
46
47impl HtmlState {
48 pub fn new() -> Self {
50 Self {
51 numbers: Vec::new(),
52 table_state: TableContext::default(),
53 table_cell_index: 0,
54 table_alignments: Vec::new(),
55 list_stack: Vec::new(),
56 link_stack: Vec::new(),
57 heading_stack: Vec::new(),
58 currently_in_code_block: false,
59 currently_in_footnote: false,
60 }
61 }
62
63 #[allow(dead_code)]
64 pub fn reset(&mut self) {
66 self.numbers.clear();
67 self.table_state = TableContext::default();
68 self.table_cell_index = 0;
69 self.table_alignments.clear();
70 self.list_stack.clear();
71 self.link_stack.clear();
72 self.heading_stack.clear();
73 self.currently_in_code_block = false;
74 }
75
76 #[allow(dead_code)]
77 pub fn in_table(&self) -> bool {
79 self.table_state != TableContext::NotInTable
80 }
81
82 #[allow(dead_code)]
83 pub fn in_table_header(&self) -> bool {
85 self.table_state == TableContext::InHeader
86 }
87
88 #[allow(dead_code)]
89 pub fn list_depth(&self) -> usize {
91 self.list_stack.len()
92 }
93
94 #[allow(dead_code)]
95 pub fn current_list_type(&self) -> Option<ListContext> {
97 self.list_stack.last().copied()
98 }
99}
100
101impl Default for HtmlState {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_renderer_state_new() {
113 let state = HtmlState::new();
114 assert_eq!(state.table_state, TableContext::NotInTable);
115 assert_eq!(state.table_cell_index, 0);
116 assert!(state.numbers.is_empty());
117 assert!(state.table_alignments.is_empty());
118 assert!(state.list_stack.is_empty());
119 assert!(state.link_stack.is_empty());
120 assert!(state.heading_stack.is_empty());
121 assert!(!state.currently_in_code_block);
122 }
123
124 #[test]
125 fn test_renderer_state_reset() {
126 let mut state = HtmlState::new();
127
128 state.numbers.push(1);
130 state.table_state = TableContext::InHeader;
131 state.table_cell_index = 2;
132 state.list_stack.push(ListContext::Ordered(1));
133 state.currently_in_code_block = true;
134
135 state.reset();
137
138 assert_eq!(state.table_state, TableContext::NotInTable);
140 assert_eq!(state.table_cell_index, 0);
141 assert!(state.numbers.is_empty());
142 assert!(state.list_stack.is_empty());
143 assert!(!state.currently_in_code_block);
144 }
145
146 #[test]
147 fn test_list_operations() {
148 let mut state = HtmlState::new();
149
150 assert_eq!(state.list_depth(), 0);
151 assert_eq!(state.current_list_type(), None);
152
153 state.list_stack.push(ListContext::Unordered);
154 assert_eq!(state.list_depth(), 1);
155 assert_eq!(state.current_list_type(), Some(ListContext::Unordered));
156
157 state.list_stack.push(ListContext::Ordered(1));
158 assert_eq!(state.list_depth(), 2);
159 assert_eq!(state.current_list_type(), Some(ListContext::Ordered(1)));
160 }
161
162 #[test]
163 fn test_table_state() {
164 let mut state = HtmlState::new();
165
166 assert!(!state.in_table());
167 assert!(!state.in_table_header());
168
169 state.table_state = TableContext::InHeader;
170 assert!(state.in_table());
171 assert!(state.in_table_header());
172
173 state.table_state = TableContext::InBody;
174 assert!(state.in_table());
175 assert!(!state.in_table_header());
176 }
177}