ftml/parsing/rule/impls/
blockquote.rs1use super::prelude::*;
22use crate::parsing::paragraph::ParagraphStack;
23use crate::parsing::{process_depths, DepthItem, DepthList};
24use crate::tree::{AttributeMap, Container, ContainerType};
25
26const MAX_BLOCKQUOTE_DEPTH: usize = 30;
27
28pub const RULE_BLOCKQUOTE: Rule = Rule {
29 name: "blockquote",
30 position: LineRequirement::StartOfLine,
31 try_consume_fn,
32};
33
34fn try_consume_fn<'r, 't>(
35 parser: &mut Parser<'r, 't>,
36) -> ParseResult<'r, 't, Elements<'t>> {
37 debug!("Parsing nested native blockquotes");
38
39 let mut depths = Vec::new();
41 let mut errors = Vec::new();
42
43 loop {
45 let current = parser.current();
46 let depth = match current.token {
47 Token::Quote => current.slice.len(),
49
50 _ => {
52 warn!("Didn't find blockquote token, ending list iteration");
53 break;
54 }
55 };
56 parser.step()?;
57 parser.get_optional_space()?; if depth > MAX_BLOCKQUOTE_DEPTH {
61 debug!("Native blockquote has a depth ({depth}) greater than the maximum ({MAX_BLOCKQUOTE_DEPTH})! Failing");
62 return Err(parser.make_err(ParseErrorKind::BlockquoteDepthExceeded));
63 }
64
65 let mut paragraph_safe = true;
67 let mut elements = collect_consume(
68 parser,
69 RULE_BLOCKQUOTE,
70 &[
71 ParseCondition::current(Token::LineBreak),
72 ParseCondition::current(Token::ParagraphBreak),
73 ParseCondition::current(Token::InputEnd),
74 ],
75 &[],
76 None,
77 )?
78 .chain(&mut errors, &mut paragraph_safe);
79
80 elements.push(Element::LineBreak);
82
83 depths.push((depth - 1, (), (elements, paragraph_safe)))
90 }
91
92 if depths.is_empty() {
94 return Err(parser.make_err(ParseErrorKind::RuleFailed));
95 }
96
97 let depth_lists = process_depths((), depths);
98 let elements: Vec<Element> = depth_lists
99 .into_iter()
100 .map(|(_, depth_list)| build_blockquote_element(depth_list))
101 .collect();
102
103 ok!(false; elements, errors)
104}
105
106fn build_blockquote_element(list: DepthList<(), (Vec<Element>, bool)>) -> Element {
107 let mut stack = ParagraphStack::new();
108
109 for item in list {
111 match item {
112 DepthItem::Item((elements, paragraph_safe)) => {
113 for element in elements {
114 stack.push_element(element, paragraph_safe);
115 }
116 }
117 DepthItem::List(_, list) => {
118 let blockquote = build_blockquote_element(list);
119 stack.pop_line_break();
120 stack.push_element(blockquote, false);
121 }
122 }
123 }
124
125 stack.pop_line_break();
126
127 Element::Container(Container::new(
128 ContainerType::Blockquote,
129 stack.into_elements(),
130 AttributeMap::new(),
131 ))
132}