//! Types for HTML attributes.
use std::collections::{btree_map, BTreeMap};
use std::iter::FromIterator;
use std::ops::{Index, Range};
use crate::offset::Offset;
/// A map of HTML attributes.
///
/// Does not preserve the order of attributes.
/// Iterating always yields attributes in order by name.
///
/// # Example
///
/// ```
/// # use html5tokenizer::attr::AttributeMap;
/// let attrs: AttributeMap<()> = vec![("href".into(), "http://example.com".into())]
/// .into_iter()
/// .collect();
/// assert_eq!(&attrs["href"], "http://example.com");
/// ```
#[derive(Debug, Default, PartialEq, Eq)]
pub struct AttributeMap {
pub(crate) inner: BTreeMap>,
}
/// The value type internally used by the [`AttributeMap`].
/// Not part of the public API.
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct AttrInternal {
pub value: String,
/// The start offset of the attribute name.
pub name_offset: O,
/// The start offset of the attribute value.
/// For the empty attribute syntax this is just `S::Offset::default()`.
/// We intentionally don't use `Option` here to spare us a byte per attribute.
pub value_offset: O,
pub value_syntax: Option,
}
/// The syntax of the attribute value.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AttrValueSyntax {
/// An unquoted attribute value, e.g. `id=foo`.
Unquoted,
/// A single-quoted attribute value, e.g. `id='foo'`.
SingleQuoted,
/// A double-quoted attribute value, e.g. `id="foo"`.
DoubleQuoted,
}
/// An HTML attribute borrowed from an [`AttributeMap`].
#[derive(Debug, Eq, PartialEq)]
pub struct Attribute<'a, O> {
name: &'a str,
map_val: &'a AttrInternal,
}
/// An owned HTML attribute.
#[derive(Debug, PartialEq, Eq)]
pub struct AttributeOwned {
/// The attribute name.
/// Uppercase ASCII characters (A-Z) have been converted to lowercase.
pub name: String,
/// The attribute value. Character references have been resolved.
pub value: String,
/// The start offset of the attribute name.
pub name_offset: O,
/// The start offset of the attribute value.
/// `None` in case of the empty attribute syntax (e.g. `disabled` in ``).
pub value_offset: Option,
/// The syntax of the attribute value.
/// `None` indicates the empty attribute syntax (e.g. `disabled` in ``).
pub value_syntax: Option,
}
impl AttributeMap {
/// Returns the attribute with the given name.
///
/// The name must not contain any uppercase ASCII character (A-Z)
/// or the method will always return `None`.
pub fn get(&self, name: &str) -> Option> {
self.inner
.get_key_value(name)
.map(|(name, map_val)| Attribute { name, map_val })
}
}
impl<'a, O: Offset> Attribute<'a, O> {
/// Returns the attribute name.
/// Uppercase ASCII characters (A-Z) have been converted to lowercase.
pub fn name(&self) -> &'a str {
self.name
}
/// Returns the attribute value. Character references have been resolved.
pub fn value(&self) -> &'a str {
&self.map_val.value
}
/// Calculates the span of the attribute name and returns it.
pub fn name_span(&self) -> Range {
self.map_val.name_offset..self.map_val.name_offset + self.name.len()
}
/// For explicitly defined values calculates the span of the attribute value and returns it.
///
/// Returns `None` for attributes using the empty attribute syntax (e.g. `disabled` in ``).
pub fn value_span(&self) -> Option> {
if self.map_val.value_syntax.is_none() {
return None;
}
Some(self.map_val.value_offset..self.map_val.value_offset + self.map_val.value.len())
}
/// Returns the attribute value syntax in case the value is explicitly defined.
///
/// Returns `None` for attributes using the empty attribute syntax (e.g. `disabled` in ``).
pub fn value_syntax(&self) -> Option {
self.map_val.value_syntax
}
}
// We cannot impl Index