use crate::structs::Attributes;
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag},
character::complete::{char, none_of},
combinator::{fail, opt, value},
sequence::{delimited, preceded, tuple},
IResult,
};
pub fn attributes(mut input: &str) -> IResult<&str, Attributes> {
let mut attributes = Attributes {
class: None,
id: None,
style: None,
language: None,
};
loop {
if let Ok((rest, (class, id))) = class_and_id(input) {
if attributes.class.is_none() {
attributes.class = class;
} else if class.is_some() {
return Ok((input, attributes));
}
if attributes.id.is_none() {
attributes.id = id;
} else if id.is_some() {
return Ok((input, attributes));
}
input = rest;
} else if let Ok((rest, style)) = style(input) {
if attributes.style.is_none() {
attributes.style = Some(style);
} else {
return Ok((input, attributes));
}
input = rest;
} else if let Ok((rest, language)) = language(input) {
if attributes.language.is_none() {
attributes.language = Some(language);
} else {
return Ok((input, attributes));
}
input = rest;
} else {
break;
}
}
if attributes.class.is_some()
|| attributes.id.is_some()
|| attributes.style.is_some()
|| attributes.language.is_some()
{
Ok((input, attributes))
} else {
fail(input)
}
}
fn class_and_id(
input: &str,
) -> IResult<&str, (Option<String>, Option<String>)> {
let (rest, (opt_class, opt_id)) = delimited(
char('('),
tuple((opt(class_name), opt(preceded(char('#'), class_name)))),
char(')'),
)(input)?;
if opt_class.is_some() || opt_id.is_some() {
Ok((rest, (opt_class, opt_id)))
} else {
fail(rest)
}
}
fn class_name(input: &str) -> IResult<&str, String> {
escaped_transform(
none_of("\\#()"),
'\\',
alt((
value("\\", tag("\\")),
value("(", tag("(")),
value(")", tag(")")),
value("#", tag("#")),
)),
)(input)
}
fn style(input: &str) -> IResult<&str, String> {
delimited(
char('{'),
escaped_transform(
none_of("\\{}"),
'\\',
alt((
value("\\", tag("\\")),
value("{", tag("{")),
value("}", tag("}")),
)),
),
char('}'),
)(input)
}
fn language(input: &str) -> IResult<&str, String> {
delimited(
char('['),
escaped_transform(
none_of("\\[]"),
'\\',
alt((
value("\\", tag("\\")),
value("[", tag("[")),
value("]", tag("]")),
)),
),
char(']'),
)(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attributes_csl() {
let input = "(class#id){style}[language]";
let result = attributes(input);
assert_eq!(
result,
Ok((
"",
Attributes {
class: Some(String::from("class")),
id: Some(String::from("id")),
style: Some(String::from("style")),
language: Some(String::from("language")),
}
))
);
}
#[test]
fn attributes_scl() {
let input = "{style}(class#id)[language]";
let result = attributes(input);
assert_eq!(
result,
Ok((
"",
Attributes {
class: Some(String::from("class")),
id: Some(String::from("id")),
style: Some(String::from("style")),
language: Some(String::from("language")),
}
))
);
}
#[test]
fn attributes_lcs() {
let input = "[language]{style}(class#id)";
let result = attributes(input);
assert_eq!(
result,
Ok((
"",
Attributes {
class: Some(String::from("class")),
id: Some(String::from("id")),
style: Some(String::from("style")),
language: Some(String::from("language")),
}
))
);
}
#[test]
fn attributes_class() {
let input = "(class)";
let result = attributes(input);
assert_eq!(
result,
Ok((
"",
Attributes {
class: Some(String::from("class")),
id: None,
style: None,
language: None,
}
))
);
}
}