use std::collections::HashMap;
use std::ops::Range;
use winnow::Result as PResult;
use winnow::ascii::{multispace0, multispace1};
use winnow::combinator::{alt, delimited, opt, preceded, repeat};
use winnow::prelude::*;
use winnow::token::{literal, take_till, take_while};
pub(crate) trait BlockTagParser {
fn next(&mut self) -> anyhow::Result<Option<BlockTag>>;
}
#[derive(Debug)]
pub(crate) enum BlockTag {
Start {
tag_range: Range<usize>,
attributes: HashMap<String, String>,
},
End {
start_position: usize,
},
}
pub(crate) struct WinnowBlockTagParser<'source> {
source: &'source str,
cursor: usize,
}
impl<'source> WinnowBlockTagParser<'source> {
pub(crate) fn new(source: &'source str, cursor: usize) -> Self {
Self { source, cursor }
}
pub(crate) fn cursor(&self) -> usize {
self.cursor
}
}
impl<'source> BlockTagParser for WinnowBlockTagParser<'source> {
fn next(&mut self) -> anyhow::Result<Option<BlockTag>> {
if self.cursor >= self.source.len() {
return Ok(None);
}
let input = &self.source[self.cursor..];
let mut current_input = input;
let mut offset = 0;
loop {
if let Some(pos) = current_input.find("<") {
offset += pos;
let potential_tag_start = ¤t_input[pos..];
if let Ok((remaining, attributes)) = parse_start_tag.parse_peek(potential_tag_start)
{
let start_position = self.cursor + offset;
let match_len = potential_tag_start.len() - remaining.len();
let end_position = start_position + match_len;
self.cursor = end_position;
return Ok(Some(BlockTag::Start {
tag_range: start_position..end_position,
attributes,
}));
}
if let Ok((remaining, _)) = parse_end_tag.parse_peek(potential_tag_start) {
let start_position = self.cursor + offset;
let match_len = potential_tag_start.len() - remaining.len();
let end_position = start_position + match_len;
self.cursor = end_position;
return Ok(Some(BlockTag::End { start_position }));
}
current_input = &potential_tag_start[1..];
offset += 1;
} else {
self.cursor = self.source.len();
return Ok(None);
}
}
}
}
fn parse_start_tag(input: &mut &str) -> PResult<HashMap<String, String>> {
delimited(
literal("<block"),
parse_attributes,
(multispace0, literal(">")),
)
.parse_next(input)
}
fn parse_end_tag(input: &mut &str) -> PResult<()> {
(
literal("<"),
opt(multispace0),
literal("/"),
opt(multispace0),
literal("block"),
opt(multispace0),
literal(">"),
)
.void()
.parse_next(input)
}
fn parse_attributes(input: &mut &str) -> PResult<HashMap<String, String>> {
repeat(
0..,
preceded(
multispace1, (
parse_attribute_name,
opt(preceded(
(multispace0, literal("="), multispace0),
parse_attribute_value,
)),
),
),
)
.fold(
HashMap::new,
|mut map: HashMap<String, String>, (key, value): (String, Option<String>)| {
map.insert(key, value.unwrap_or_default());
map
},
)
.parse_next(input)
}
fn parse_attribute_name(input: &mut &str) -> PResult<String> {
take_while(1.., |c: char| c.is_alphanumeric() || c == '-' || c == '_')
.map(|s: &str| s.to_string())
.parse_next(input)
}
fn parse_attribute_value(input: &mut &str) -> PResult<String> {
alt((
delimited(literal("\""), take_till(0.., '"'), literal("\"")),
delimited(literal("'"), take_till(0.., '\''), literal("'")),
take_while(1.., |c: char| c.is_alphanumeric() || c == '-' || c == '_'),
))
.map(|s: &str| s.to_string())
.parse_next(input)
}