use nom::bytes::complete::{tag, take_while1};
use crate::parser::{
HsmlProcessContext, HsmlResult, Span, advance, delimited_section_len,
error::{ErrorCode, HsmlError},
quoted_string_len, take_prefix,
};
fn is_valid_attribute_key_start(c: char) -> bool {
c.is_alphabetic() || c == ':' || c == '#' || c == '@' || c == '[' || c == '('
}
fn is_key_char(c: char) -> bool {
c.is_alphanumeric() || matches!(c, '-' | '_' | ':' | '#' | '@' | '.' | '{' | '}')
}
pub(super) fn process_attribute_key(input: Span<'_>) -> HsmlResult<'_, Span<'_>> {
let Some(first_char) = input.fragment().chars().next() else {
return Err(HsmlError::fail_code(input, ErrorCode::InvalidAttributeKey));
};
if first_char.is_numeric() {
return Err(HsmlError::fail_code(input, ErrorCode::InvalidAttributeKey));
}
if !is_valid_attribute_key_start(first_char) {
return Err(HsmlError::fail_code(input, ErrorCode::InvalidAttributeKey));
}
let mut remaining = input;
let mut key_len = 0;
loop {
if let Ok((rest, taken)) = take_while1::<_, Span, HsmlError>(is_key_char)(remaining) {
key_len += taken.fragment().len();
remaining = rest;
continue;
}
if remaining.fragment().starts_with('[') {
if let Some(len) = delimited_section_len(remaining.fragment(), '[', ']') {
key_len += len;
remaining = advance(input, key_len);
continue;
}
return Err(HsmlError::fail_code(remaining, ErrorCode::UnclosedBracket));
}
if remaining.fragment().starts_with('(') {
if let Some(len) = delimited_section_len(remaining.fragment(), '(', ')') {
key_len += len;
remaining = advance(input, key_len);
continue;
}
return Err(HsmlError::fail_code(
remaining,
ErrorCode::UnclosedParenthesis,
));
}
break;
}
let attribute_key = take_prefix(input, key_len);
Ok((remaining, attribute_key))
}
pub(super) fn process_attribute_value<'a>(
input: Span<'a>,
_context: &mut HsmlProcessContext,
) -> HsmlResult<'a, Span<'a>> {
let Some(first_char) = input.fragment().chars().next() else {
return Err(HsmlError::fail_code(
input,
ErrorCode::ExpectedAttributeValue,
));
};
if first_char != '"' && first_char != '\'' {
return Err(HsmlError::fail_code(
input,
ErrorCode::ExpectedAttributeValue,
));
}
if let Some(len) = quoted_string_len(input.fragment()) {
let attribute_value = take_prefix(advance(input, 1), len - 2);
let remaining = advance(input, len);
return Ok((remaining, attribute_value));
}
Err(HsmlError::fail_code(input, ErrorCode::UnclosedQuote))
}
pub(super) fn process_attribute<'a>(
input: Span<'a>,
context: &mut HsmlProcessContext,
) -> HsmlResult<'a, Span<'a>> {
let (remaining, attribute_key) = process_attribute_key(input)?;
if let Ok((remaining_after_equal_sign, _)) = tag::<&str, Span, HsmlError>("=")(remaining) {
let (remaining_after_attribute_value, _attribute_value) =
process_attribute_value(remaining_after_equal_sign, context)?;
let consumed = input.fragment().len() - remaining_after_attribute_value.fragment().len();
let attribute = take_prefix(input, consumed);
return Ok((remaining_after_attribute_value, attribute));
}
Ok((remaining, attribute_key))
}