elm_parser/
parser.rs

1use crate::counter::counter_commands::CounterCommand;
2
3use super::datacell::{
4    BlockCell::BlockCell, BlockChildType::*, CellTrait::Cell, Datacell::*, ElementCell::*,
5};
6use super::element_text::ElementText;
7use super::helpers::*;
8use super::parser_helpers::{tag_stack_pop, TagInfo};
9
10#[derive(Debug)]
11pub struct Parser {
12    result: DataCell,
13    pub id: usize,
14    //reset_line_tracking_on: String // used for tracking error line on merged input files . when input files are merged we reset the line on every element related to file
15}
16
17#[derive(Debug)]
18pub enum ParserError {
19    ExtraSpacesError(usize),
20    None4xSpacesError(usize),
21}
22
23impl ParserError {
24    pub fn to_string(&self) -> String {
25        match self {
26            ParserError::ExtraSpacesError(line) => format!("Extra spaces found on line {}", line),
27            ParserError::None4xSpacesError(line) => {
28                format!("None 4x spaces found on line {}", line)
29            }
30        }
31    }
32
33    pub fn to_string_without_line(&self) -> String {
34        match self {
35            ParserError::ExtraSpacesError(_) => format!("Extra spaces found"),
36            ParserError::None4xSpacesError(_) => {
37                format!("None 4x spaces found")
38            }
39        }
40    }
41}
42
43impl Parser {
44    pub fn new() -> Parser {
45        Self {
46            result: DataCell {
47                parent_id: 0,
48                id: 0,
49                cell_type: CellType::Root(Root { children: vec![] }),
50            },
51            id: 1,
52        }
53    }
54
55    pub fn import_json(json: &String) -> String {
56        json.clone()
57    }
58
59    fn handle_tag_line(
60        &mut self,
61        trimmed_line: &str,
62        mut curr_el_id: Option<usize>,
63        mut is_nested: &bool,
64        tag_stack: &mut Vec<TagInfo>,
65        indent: usize,
66    ) {
67        let tag_name = trimmed_line[3..].trim();
68        tag_stack_pop(tag_stack, &indent);
69
70        curr_el_id = if let Some(last) = tag_stack.last() {
71            Some(last.id)
72        } else if *is_nested {
73            is_nested = &false;
74            curr_el_id
75        } else {
76            Some(0)
77        };
78
79        ElementCell::add_cell(&mut self.result, curr_el_id.unwrap(), self.id, tag_name);
80
81        tag_stack.push(TagInfo {
82            id: self.id,
83            name: tag_name.to_string(),
84            indent,
85            is_self_closing: false,
86            in_props: true,
87        });
88        self.id += 1;
89    }
90
91    fn props_end(&mut self, trimmed_line: &str, tag_stack: &Vec<TagInfo>) -> bool {
92        trimmed_line.is_empty() // end of props
93        && tag_stack
94            .last()
95            .map_or(false, |tag| tag.in_props && !tag.is_self_closing)
96    }
97
98    pub fn export_json(
99        &mut self,
100        elm: &str,
101        mut curr_el_id: Option<usize>,
102        mut is_nested: bool,
103    ) -> Result<String, ParserError> {
104        let mut tag_stack: Vec<TagInfo> = Vec::new();
105        let lines = elm.lines();
106        let mut lines_to_skip: u32 = 0;
107        let mut text_node = String::new();
108
109        for (index, line) in lines.clone().enumerate() {
110            if lines_to_skip > 0 {
111                lines_to_skip = lines_to_skip - 1;
112                continue;
113            }
114
115            let trimmed_line = line.trim_start();
116            let indent = get_line_indent(line);
117
118            if trimmed_line.starts_with("!!") {
119                continue;
120            }
121            check_indent_size(indent as isize, index)?;
122            if let Some(last) = tag_stack.last() {
123                check_extra_spaces(indent, last.indent, index)?;
124            }
125
126            if trimmed_line.starts_with("|> ") {
127                self.handle_tag_line(trimmed_line, curr_el_id, &is_nested, &mut tag_stack, indent);
128                continue;
129            }
130            if self.props_end(trimmed_line, &tag_stack) {
131                if let Some(last) = tag_stack.last_mut() {
132                    last.in_props = false;
133                }
134                continue;
135            }
136
137            if !trimmed_line.is_empty() {
138                tag_stack_pop(&mut tag_stack, &indent);
139
140                curr_el_id = if let Some(last) = tag_stack.last() {
141                    Some(last.id)
142                } else if is_nested {
143                    is_nested = false;
144                    curr_el_id
145                } else {
146                    Some(0)
147                };
148
149                let last = tag_stack
150                    .last()
151                    .expect(&format!("There is no parent tag . at line \n {:?}", index));
152                if last.in_props {
153                    // tag props
154                    ElementCell::add_attribute(&mut self.result, last.id, trimmed_line);
155                    continue;
156                }
157
158                let next_line = lines.clone().nth(index + 1);
159                let next_line_empty = next_line.is_none()
160                    || (next_line.is_some() && next_line.unwrap().trim().is_empty());
161                let next_line_is_element =
162                    next_line.is_some() && next_line.unwrap().trim_start().starts_with("|> ");
163                let next_line_indent = if let Some(next_line) = next_line {
164                    Some(get_line_indent(next_line))
165                } else {
166                    None
167                };
168
169                let end_of_attached_element =
170                    next_line_indent.is_some_and(|ne| ne > 0 && ne < indent);
171
172                // break if next line is empty
173                if next_line_empty
174                    || next_line_is_element
175                    || indent < tag_stack.last().unwrap().indent
176                    || end_of_attached_element
177                {
178                    text_node = format!(
179                        "{}{}{}",
180                        text_node,
181                        if text_node == "" { "" } else { " " },
182                        // if there's one space we keep it
183                        if trimmed_line.trim_end().len() + 1 == trimmed_line.len() {
184                            trimmed_line
185                        } else {
186                            trimmed_line.trim_end()
187                        },
188                    );
189
190                    let mut block = BlockCell::new();
191
192                    let e = ElementText::new(&text_node);
193                    let block_children = e.split_text();
194
195                    // mark block with counter syntax to avoid seaching all blocks later
196                    // if CounterCommand::has_counter_syntax(text_node.as_str()) {
197                    //     block.has_counter_commands = true;
198                    // }
199                    block.has_counter_commands =
200                        CounterCommand::has_counter_syntax(text_node.as_str());
201                    block.has_handle_insert =
202                        CounterCommand::has_handle_insert_syntax(text_node.as_str());
203
204                    block_children.iter().for_each(|child| {
205                        if let BlockChildType::Text(text) = &child {
206                            if text.content != "" {
207                                BlockChildType::push_cell(&mut block, child.to_owned());
208                            }
209                        } else {
210                            BlockChildType::push_cell(&mut block, child.to_owned());
211                        }
212                    });
213
214                    text_node = "".to_string();
215
216                    BlockCell::add_cell(&mut self.result, curr_el_id.unwrap(), self.id, &block);
217                    self.id += 1;
218
219                    if end_of_attached_element && tag_stack.len() > 1 {
220                        let parent_id = self.get_parent_id(&tag_stack);
221                        ElementCell::add_cell(
222                            &mut self.result,
223                            parent_id.unwrap(),
224                            self.id,
225                            "Space",
226                        );
227                        self.id += 1;
228                    }
229
230                    continue;
231                }
232
233                text_node = format!(
234                    "{}{}{}",
235                    text_node,
236                    if text_node == "" { "" } else { " " },
237                    if trimmed_line.trim_end().len() + 1 == trimmed_line.len() {
238                        trimmed_line
239                    } else {
240                        trimmed_line.trim_end()
241                    },
242                );
243                if !text_node.is_empty() {
244                    text_node.push_str("\n")
245                }
246            }
247        }
248        let res = serde_json::to_string_pretty(&self.result);
249        Ok(res.unwrap_or("Something Wrong".to_string()))
250    }
251
252    fn get_parent_id(&self, tag_stack: &Vec<TagInfo>) -> Option<usize> {
253        let before_last_index = tag_stack.len().checked_sub(2);
254        if before_last_index.is_none() {
255            return None;
256        }
257        let before_last_index = before_last_index.unwrap();
258        if let Some(before_last) = tag_stack.get(before_last_index) {
259            Some(before_last.id)
260        } else {
261            None
262        }
263    }
264}