use rustc_hash::FxHashMap;
use serde::{
Serialize,
ser::{SerializeMap, Serializer},
};
pub const MAX_TOC_LEVELS: u8 = 5;
pub const MAX_SECTION_LEVELS: u8 = 5;
#[must_use]
pub fn strip_quotes(s: &str) -> &str {
s.trim_start_matches(['"', '\''])
.trim_end_matches(['"', '\''])
}
#[derive(Debug, PartialEq, Clone)]
struct AttributeMap {
all: FxHashMap<AttributeName, AttributeValue>,
explicit: FxHashMap<AttributeName, AttributeValue>,
}
impl Default for AttributeMap {
fn default() -> Self {
AttributeMap {
all: crate::constants::default_attributes(),
explicit: FxHashMap::default(), }
}
}
impl AttributeMap {
fn empty() -> Self {
AttributeMap {
all: FxHashMap::default(),
explicit: FxHashMap::default(),
}
}
fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
self.all.iter()
}
fn is_empty(&self) -> bool {
self.explicit.is_empty()
}
fn insert(&mut self, name: AttributeName, value: AttributeValue) {
if !self.contains_key(&name) {
self.all.insert(name.clone(), value.clone());
self.explicit.insert(name, value); }
}
fn set(&mut self, name: AttributeName, value: AttributeValue) {
self.all.insert(name.clone(), value.clone());
self.explicit.insert(name, value); }
fn get(&self, name: &str) -> Option<&AttributeValue> {
self.all.get(name)
}
fn contains_key(&self, name: &str) -> bool {
self.all.contains_key(name)
}
fn remove(&mut self, name: &str) -> Option<AttributeValue> {
self.explicit.remove(name);
self.all.remove(name)
}
fn merge(&mut self, other: AttributeMap) {
for (key, value) in other.all {
self.insert(key, value);
}
}
}
impl Serialize for AttributeMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sorted_keys: Vec<_> = self.explicit.keys().collect();
sorted_keys.sort();
let mut state = serializer.serialize_map(Some(self.explicit.len()))?;
for key in sorted_keys {
if let Some(value) = &self.explicit.get(key) {
match value {
AttributeValue::Bool(true) => {
if key == "toc" {
state.serialize_entry(key, "")?;
} else {
state.serialize_entry(key, &true)?;
}
}
value @ (AttributeValue::Bool(false)
| AttributeValue::String(_)
| AttributeValue::None) => {
state.serialize_entry(key, value)?;
}
}
}
}
state.end()
}
}
fn validate_bounded_attribute(key: &str, value: &AttributeValue) {
let AttributeValue::String(s) = value else {
return;
};
match key {
"sectnumlevels" => {
if let Ok(level) = s.parse::<u8>()
&& level > MAX_SECTION_LEVELS
{
tracing::warn!(
attribute = "sectnumlevels",
value = level,
"sectnumlevels must be between 0 and {MAX_SECTION_LEVELS}, got {level}. \
Values above {MAX_SECTION_LEVELS} will be treated as {MAX_SECTION_LEVELS}."
);
}
}
"toclevels" => {
if let Ok(level) = s.parse::<u8>()
&& level > MAX_TOC_LEVELS
{
tracing::warn!(
attribute = "toclevels",
value = level,
"toclevels must be between 0 and {MAX_TOC_LEVELS}, got {level}. \
Values above {MAX_TOC_LEVELS} will be treated as {MAX_TOC_LEVELS}."
);
}
}
_ => {}
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct DocumentAttributes(AttributeMap);
impl DocumentAttributes {
pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
self.0.iter()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
validate_bounded_attribute(&name, &value);
self.0.insert(name, value);
}
pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
validate_bounded_attribute(&name, &value);
self.0.set(name, value);
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&AttributeValue> {
self.0.get(name)
}
#[must_use]
pub fn contains_key(&self, name: &str) -> bool {
self.0.contains_key(name)
}
pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
self.0.remove(name)
}
pub fn merge(&mut self, other: Self) {
self.0.merge(other.0);
}
#[must_use]
pub fn get_string(&self, name: &str) -> Option<String> {
self.get(name).and_then(|v| match v {
AttributeValue::String(s) => Some(strip_quotes(s).to_string()),
AttributeValue::None | AttributeValue::Bool(_) => None,
})
}
}
impl Serialize for DocumentAttributes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ElementAttributes(AttributeMap);
impl Default for ElementAttributes {
fn default() -> Self {
ElementAttributes(AttributeMap::empty())
}
}
impl ElementAttributes {
pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
self.0.iter()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
self.0.insert(name, value);
}
pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
self.0.set(name, value);
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&AttributeValue> {
self.0.get(name)
}
#[must_use]
pub fn contains_key(&self, name: &str) -> bool {
self.0.contains_key(name)
}
pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
self.0.remove(name)
}
pub fn merge(&mut self, other: Self) {
self.0.merge(other.0);
}
#[must_use]
pub fn get_string(&self, name: &str) -> Option<String> {
self.get(name).and_then(|v| match v {
AttributeValue::String(s) => Some(strip_quotes(s).to_string()),
AttributeValue::None | AttributeValue::Bool(_) => None,
})
}
}
impl Serialize for ElementAttributes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
pub type AttributeName = String;
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum AttributeValue {
String(String),
Bool(bool),
None,
}
impl std::fmt::Display for AttributeValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AttributeValue::String(value) => write!(f, "{value}"),
AttributeValue::Bool(value) => write!(f, "{value}"),
AttributeValue::None => write!(f, "null"),
}
}
}
impl From<&str> for AttributeValue {
fn from(value: &str) -> Self {
AttributeValue::String(value.to_string())
}
}
impl From<String> for AttributeValue {
fn from(value: String) -> Self {
AttributeValue::String(value)
}
}
impl From<bool> for AttributeValue {
fn from(value: bool) -> Self {
AttributeValue::Bool(value)
}
}
impl From<()> for AttributeValue {
fn from((): ()) -> Self {
AttributeValue::None
}
}