Skip to content

Commit af29efa

Browse files
committed
Refactor ESI Parser to Use ParseResult Struct
- Updated the parser to utilize the new ParseResult struct for handling parsed elements. - Modified the `parse` and `parse_with_callback` functions to return ParseResult instead of Vec<Element>. - Adjusted various parsing functions to convert between Vec<Element> and ParseResult. - Enhanced error handling and improved code clarity by leveraging the new structure. - Updated tests to reflect changes in the parser's return types and ensure correctness.
1 parent f132ab5 commit af29efa

5 files changed

Lines changed: 696 additions & 109 deletions

File tree

esi/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ impl Processor {
261261
let mut execution_queue: Vec<ExecutionElement> = Vec::new();
262262

263263
// Parse with callback - as we parse, we dispatch includes and build the execution queue
264-
let parse_result = parser::parse_with_callback(&doc_content, |elements| {
265-
for element in elements {
264+
let parse_result = parser::parse_with_callback(&doc_content, |result| {
265+
for element in result.into_vec() {
266266
match self.process_element(element, dispatch_fragment_request) {
267267
Ok(mut exec_elements) => {
268268
execution_queue.append(&mut exec_elements);
@@ -691,7 +691,7 @@ pub fn evaluate_interpolated_string(input: &str, ctx: &mut EvalContext) -> Resul
691691

692692
// Parse the input string with interpolated expressions using nom parser
693693
let elements = match crate::parser::parse_interpolated_string(input) {
694-
Ok((_, elements)) => elements,
694+
Ok((_, parse_result)) => parse_result.into_vec(),
695695
Err(_) => {
696696
// If parsing fails, treat the whole input as text
697697
return Ok(input.to_string());

esi/src/parser.rs

Lines changed: 52 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ use nom::bytes::complete::{
55
use nom::character::complete::{alpha1, char, multispace0, multispace1};
66
use nom::combinator::{map, map_res, opt, recognize, verify};
77
use nom::error::Error;
8-
use nom::multi::{fold_many0, many0, separated_list0};
8+
use nom::multi::{many0, separated_list0};
99
use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
1010
use nom::{AsChar, IResult};
1111

1212
use crate::parser_types::*;
1313

14-
pub fn parse(input: &str) -> IResult<&str, Vec<Element<'_>>, Error<&str>> {
15-
fold_many0(element, Vec::new, |mut acc: Vec<Element>, item| {
16-
item.push_to(&mut acc);
17-
acc
18-
})(input)
14+
pub fn parse(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
15+
map(many0(element), ParseResult::from_results)(input)
1916
}
2017

2118
/// Callback-based parser that invokes a callback for each element as it's parsed
@@ -25,15 +22,15 @@ pub fn parse_with_callback<'a, F>(
2522
mut callback: F,
2623
) -> IResult<&'a str, (), Error<&'a str>>
2724
where
28-
F: FnMut(Vec<Element<'a>>) -> Result<(), String>,
25+
F: FnMut(ParseResult<'a>) -> Result<(), String>,
2926
{
3027
let mut remaining = input;
3128

3229
while !remaining.is_empty() {
3330
match element(remaining) {
3431
Ok((rest, result)) => {
35-
// Call the callback with parsed elements
36-
if let Err(_e) = callback(result.into_vec()) {
32+
// Call the callback with parsed result
33+
if let Err(_e) = callback(result) {
3734
return Err(nom::Err::Failure(Error::new(
3835
rest,
3936
nom::error::ErrorKind::Fail,
@@ -57,31 +54,20 @@ pub fn parse_expression(input: &str) -> IResult<&str, Expr<'_>, Error<&str>> {
5754
expr(input)
5855
}
5956

60-
// Parses a string that may contain interpolated expressions like $(VAR)
61-
pub fn parse_interpolated_string(input: &str) -> IResult<&str, Vec<Element<'_>>, Error<&str>> {
62-
fold_many0(
63-
alt((interpolated_expression, interpolated_text)),
64-
Vec::new,
65-
|mut acc: Vec<Element>, item| {
66-
item.push_to(&mut acc);
67-
acc
68-
},
57+
/// Parses a string that may contain interpolated expressions like $(VAR)
58+
pub fn parse_interpolated_string(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
59+
map(
60+
many0(alt((interpolated_expression, interpolated_text))),
61+
ParseResult::from_results,
6962
)(input)
7063
}
7164

7265
fn element(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
7366
alt((text, esi_tag, html))(input)
7467
}
7568

76-
fn parse_interpolated(input: &str) -> IResult<&str, Vec<Element<'_>>, Error<&str>> {
77-
fold_many0(
78-
interpolated_element,
79-
Vec::new,
80-
|mut acc: Vec<Element>, item| {
81-
item.push_to(&mut acc);
82-
acc
83-
},
84-
)(input)
69+
fn parse_interpolated(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
70+
map(many0(interpolated_element), ParseResult::from_results)(input)
8571
}
8672

8773
fn interpolated_element(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
@@ -113,9 +99,9 @@ fn parse_assign_attributes_short<'a>(attrs: Vec<(&'a str, &'a str)>) -> ParseRes
11399
let mut name = "";
114100
let mut value_str = "";
115101
for (key, val) in attrs {
116-
match key {
117-
"name" => name = val,
118-
"value" => value_str = val,
102+
match key.as_bytes() {
103+
b"name" => name = val,
104+
b"value" => value_str = val,
119105
_ => {}
120106
}
121107
}
@@ -197,7 +183,7 @@ fn esi_assign_long(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
197183
parse_interpolated,
198184
tag("</esi:assign>"),
199185
)),
200-
|(attrs, content, _)| parse_assign_long(attrs, content),
186+
|(attrs, content, _)| parse_assign_long(attrs, content.into_vec()),
201187
)(input)
202188
}
203189
fn esi_except(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
@@ -207,7 +193,7 @@ fn esi_except(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
207193
parse_interpolated,
208194
tag("</esi:except>"),
209195
),
210-
|v| ParseResult::Single(Element::Esi(Tag::Except(v))),
196+
|v| ParseResult::Single(Element::Esi(Tag::Except(v.into_vec()))),
211197
)(input)
212198
}
213199
fn esi_attempt(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
@@ -217,14 +203,14 @@ fn esi_attempt(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
217203
parse_interpolated,
218204
tag("</esi:attempt>"),
219205
),
220-
|v| ParseResult::Single(Element::Esi(Tag::Attempt(v))),
206+
|v| ParseResult::Single(Element::Esi(Tag::Attempt(v.into_vec()))),
221207
)(input)
222208
}
223209
fn esi_try(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
224210
map(delimited(tag("<esi:try>"), parse, tag("</esi:try>")), |v| {
225211
let mut attempts = vec![];
226212
let mut except = None;
227-
for element in v {
213+
for element in v.into_vec() {
228214
match element {
229215
Element::Esi(Tag::Attempt(cs)) => attempts.push(cs),
230216
Element::Esi(Tag::Except(cs)) => {
@@ -250,7 +236,7 @@ fn esi_otherwise(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
250236
|(_, content, _)| {
251237
// Return the Otherwise tag followed by its content elements (same as esi_when)
252238
let mut result = vec![Element::Esi(Tag::Otherwise)];
253-
result.extend(content);
239+
result.extend(content.into_vec());
254240
ParseResult::Multiple(result)
255241
},
256242
)(input)
@@ -270,18 +256,18 @@ fn esi_when(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
270256
|(attrs, content, _)| {
271257
let test = attrs
272258
.iter()
273-
.find(|(key, _)| *key == "test")
259+
.find(|(key, _)| key.as_bytes() == b"test")
274260
.map(|(_, val)| *val)
275261
.unwrap_or("");
276262

277263
let match_name = attrs
278264
.iter()
279-
.find(|(key, _)| *key == "matchname")
265+
.find(|(key, _)| key.as_bytes() == b"matchname")
280266
.map(|(_, val)| val.to_string());
281267

282268
// Return the When tag followed by its content elements as a marker
283269
let mut result = vec![Element::Esi(Tag::When { test, match_name })];
284-
result.extend(content);
270+
result.extend(content.into_vec());
285271
ParseResult::Multiple(result)
286272
},
287273
)(input)
@@ -296,7 +282,7 @@ fn esi_choose(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
296282
let mut current_when: Option<WhenBranch> = None;
297283
let mut in_otherwise = false;
298284

299-
for element in v {
285+
for element in v.into_vec() {
300286
match element {
301287
Element::Esi(Tag::When { test, match_name }) => {
302288
// Save any previous when
@@ -366,15 +352,8 @@ fn parse_vars_attributes<'a>(
366352
attrs: Vec<(&'a str, &'a str)>,
367353
) -> Result<ParseResult<'a>, &'static str> {
368354
if let Some((_k, v)) = attrs.iter().find(|(k, _v)| *k == "name") {
369-
if let Ok((_, expr)) = expression(v) {
370-
// expression returns Vec<Element>, convert to ParseResult
371-
if expr.is_empty() {
372-
Ok(ParseResult::Empty)
373-
} else if expr.len() == 1 {
374-
Ok(ParseResult::Single(expr.into_iter().next().unwrap()))
375-
} else {
376-
Ok(ParseResult::Multiple(expr))
377-
}
355+
if let Ok((_, result)) = expression(v) {
356+
Ok(result)
378357
} else {
379358
Err("failed to parse expression")
380359
}
@@ -414,36 +393,21 @@ fn esi_tag_non_vars(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>>
414393

415394
// Parser for content inside esi:vars - handles text, expressions, and most ESI tags (except nested vars)
416395
// Supports nested variable expressions like $(VAR{$(other)}) as of the nom migration
417-
fn parse_vars_content(input: &str) -> IResult<&str, Vec<Element<'_>>, Error<&str>> {
418-
fold_many0(
419-
alt((
396+
fn parse_vars_content(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
397+
map(
398+
many0(alt((
420399
interpolated_text,
421400
interpolated_expression,
422401
esi_tag_non_vars,
423402
html,
424-
)),
425-
Vec::new,
426-
|mut acc: Vec<Element>, item| {
427-
item.push_to(&mut acc);
428-
acc
429-
},
403+
))),
404+
ParseResult::from_results,
430405
)(input)
431406
}
432407

433408
fn esi_vars_long(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
434409
// Use parse_vars_content instead of parse_interpolated to avoid infinite recursion
435-
map(
436-
delimited(tag("<esi:vars>"), parse_vars_content, tag("</esi:vars>")),
437-
|content| {
438-
if content.is_empty() {
439-
ParseResult::Empty
440-
} else if content.len() == 1 {
441-
ParseResult::Single(content.into_iter().next().unwrap())
442-
} else {
443-
ParseResult::Multiple(content)
444-
}
445-
},
446-
)(input)
410+
delimited(tag("<esi:vars>"), parse_vars_content, tag("</esi:vars>"))(input)
447411
}
448412

449413
fn esi_comment(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
@@ -485,10 +449,10 @@ fn esi_include(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
485449
let mut alt = None;
486450
let mut continue_on_error = false;
487451
for (key, val) in attrs {
488-
match key {
489-
"src" => src = val,
490-
"alt" => alt = Some(val),
491-
"onerror" => continue_on_error = val == "continue",
452+
match key.as_bytes() {
453+
b"src" => src = val,
454+
b"alt" => alt = Some(val),
455+
b"onerror" => continue_on_error = val.as_bytes() == b"continue",
492456
_ => {}
493457
}
494458
}
@@ -779,8 +743,8 @@ fn expr(input: &str) -> IResult<&str, Expr<'_>, Error<&str>> {
779743
Ok((rest, exp))
780744
}
781745
}
782-
fn expression(input: &str) -> IResult<&str, Vec<Element<'_>>, Error<&str>> {
783-
map(expr, |x| vec![Element::Expr(x)])(input)
746+
fn expression(input: &str) -> IResult<&str, ParseResult<'_>, Error<&str>> {
747+
map(expr, |x| ParseResult::Single(Element::Expr(x)))(input)
784748
}
785749

786750
#[cfg(test)]
@@ -857,7 +821,7 @@ exception!
857821
fn test_new_parse_script_with_src() {
858822
let (rest, x) = parse("<sCripT src=\"whatever\">").unwrap();
859823
assert_eq!(rest.len(), 0);
860-
assert_eq!(x, [Element::Html("<sCripT src=\"whatever\">")]);
824+
assert_eq!(x.into_vec(), [Element::Html("<sCripT src=\"whatever\">")]);
861825
}
862826
#[test]
863827
fn test_new_parse_esi_vars_short() {
@@ -874,7 +838,10 @@ exception!
874838
// The inner <esi:vars> tags should be treated as plain text/HTML
875839
let (rest, x) = parse(r#"<esi:vars>hello<br></esi:vars>"#).unwrap();
876840
assert_eq!(rest.len(), 0);
877-
assert_eq!(x, [Element::Text("hello"), Element::Html("<br>"),]);
841+
assert_eq!(
842+
x.into_vec(),
843+
[Element::Text("hello"), Element::Html("<br>"),]
844+
);
878845
}
879846

880847
#[test]
@@ -894,9 +861,10 @@ exception!
894861
if rest.is_empty() {
895862
// Inner vars should be treated as text/HTML
896863
eprintln!("Parsed elements: {:?}", elements);
864+
let elements_vec = elements.into_vec();
897865
// We expect the text "outer<esi:vars>inner" to be captured somehow
898866
assert!(
899-
elements
867+
elements_vec
900868
.iter()
901869
.any(|c| matches!(c, Element::Text(t) if t.contains("inner"))),
902870
"Inner <esi:vars> content should be present as text"
@@ -922,7 +890,7 @@ exception!
922890
parse(r#"<esi:vars name="$call('hello') matches $(var{'key'})"/>"#).unwrap();
923891
assert_eq!(rest.len(), 0);
924892
assert_eq!(
925-
x,
893+
x.into_vec(),
926894
[Element::Expr(Expr::Comparison {
927895
left: Box::new(Expr::Call("call", vec![Expr::String(Some("hello"))])),
928896
operator: Operator::Matches,
@@ -1012,14 +980,14 @@ exception!
1012980
fn test_new_parse_plain_text() {
1013981
let (rest, x) = parse("hello\nthere").unwrap();
1014982
assert_eq!(rest.len(), 0);
1015-
assert_eq!(x, [Element::Text("hello\nthere")]);
983+
assert_eq!(x.into_vec(), [Element::Text("hello\nthere")]);
1016984
}
1017985
#[test]
1018986
fn test_new_parse_interpolated() {
1019987
let (rest, x) = parse("hello $(foo)<esi:vars>goodbye $(foo)</esi:vars>").unwrap();
1020988
assert_eq!(rest.len(), 0);
1021989
assert_eq!(
1022-
x,
990+
x.into_vec(),
1023991
[
1024992
Element::Text("hello $(foo)"),
1025993
Element::Text("goodbye "),
@@ -1159,12 +1127,13 @@ exception!
11591127
#[test]
11601128
fn test_html_comment_in_document() {
11611129
let input = "<!-- comment --><div>test</div>";
1162-
let (rest, elements) = parse(input).unwrap();
1130+
let (rest, result) = parse(input).unwrap();
11631131
assert_eq!(rest, "");
1132+
let elements = result.into_vec();
11641133
// Comment should be parsed as one element
11651134
assert!(elements.len() >= 1, "Expected at least 1 element");
11661135
match &elements[0] {
1167-
Element::Html(s) => assert_eq!(s, &"<!-- comment -->"),
1136+
Element::Html(s) => assert_eq!(*s, "<!-- comment -->"),
11681137
_ => panic!("Expected Html element for comment, got {:?}", &elements[0]),
11691138
}
11701139
}

esi/src/parser_types.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ impl<'a> ParseResult<'a> {
2222
ParseResult::Empty => {}
2323
}
2424
}
25+
26+
/// Convert a Vec<Element> into the appropriate ParseResult variant
27+
pub fn from_vec(vec: Vec<Element<'a>>) -> Self {
28+
match vec.len() {
29+
0 => ParseResult::Empty,
30+
1 => ParseResult::Single(vec.into_iter().next().unwrap()),
31+
_ => ParseResult::Multiple(vec),
32+
}
33+
}
34+
35+
/// Flatten a Vec of ParseResults into a single ParseResult
36+
pub fn from_results(results: Vec<ParseResult<'a>>) -> ParseResult<'a> {
37+
let mut elements = Vec::new();
38+
for result in results {
39+
result.push_to(&mut elements);
40+
}
41+
Self::from_vec(elements)
42+
}
2543
}
2644

2745
/// Represents a single when branch in a choose block

0 commit comments

Comments
 (0)