use serde::Serialize;
#[cfg(feature = "pre-spec-subs")]
use super::substitution::SubstitutionSpec;
use super::{
DocumentAttribute,
anchor::Anchor,
attributes::{AttributeValue, ElementAttributes},
attribution::{Attribution, CiteTitle},
caption::Caption,
location::Location,
substitution::Substitution,
};
pub type Role<'a> = &'a str;
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct PositionalAttribute<'a> {
pub(crate) value: &'a str,
pub(crate) substitutions: bool,
pub(crate) location: Option<Location>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct DocumentAttributeEvents<'a>(Vec<DocumentAttribute<'a>>);
#[derive(Clone, Debug, Default, Serialize)]
#[non_exhaustive]
pub struct BlockMetadata<'a> {
#[serde(skip)]
pub(crate) document_attributes: Option<Box<DocumentAttributeEvents<'a>>>,
#[serde(skip)]
pub caption: Option<Caption<'a>>,
#[serde(default, skip_serializing_if = "ElementAttributes::is_empty")]
pub attributes: ElementAttributes<'a>,
#[serde(default, skip_serializing)]
pub(crate) positional_attributes: Vec<PositionalAttribute<'a>>,
#[serde(skip)]
pub(crate) retained_positional_attributes: Option<Vec<PositionalAttribute<'a>>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub roles: Vec<Role<'a>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub style: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Anchor<'a>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub anchors: Vec<Anchor<'a>>,
#[cfg(feature = "pre-spec-subs")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub substitutions: Option<SubstitutionSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribution: Option<Attribution<'a>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub citetitle: Option<CiteTitle<'a>>,
#[serde(default, skip_serializing)]
pub(crate) attribution_substitutions: bool,
#[serde(default, skip_serializing)]
pub(crate) citetitle_substitutions: bool,
#[serde(skip)]
pub location: Option<Location>,
}
impl PartialEq for BlockMetadata<'_> {
fn eq(&self, other: &Self) -> bool {
let Self {
document_attributes,
caption,
attributes,
positional_attributes: _,
retained_positional_attributes: _,
roles,
options,
style,
id,
anchors,
#[cfg(feature = "pre-spec-subs")]
substitutions,
attribution,
citetitle,
attribution_substitutions,
citetitle_substitutions,
location,
} = self;
#[cfg(feature = "pre-spec-subs")]
if substitutions != &other.substitutions {
return false;
}
document_attributes == &other.document_attributes
&& caption == &other.caption
&& attributes == &other.attributes
&& roles == &other.roles
&& options == &other.options
&& style == &other.style
&& id == &other.id
&& anchors == &other.anchors
&& attribution == &other.attribution
&& citetitle == &other.citetitle
&& attribution_substitutions == &other.attribution_substitutions
&& citetitle_substitutions == &other.citetitle_substitutions
&& location == &other.location
&& self
.positional_attributes()
.eq(other.positional_attributes())
}
}
impl<'a> BlockMetadata<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn uses_substitution(
&self,
substitution: &Substitution,
defaults: &[Substitution],
) -> bool {
#[cfg(feature = "pre-spec-subs")]
if let Some(spec) = &self.substitutions {
return spec.contains(substitution, defaults);
}
defaults.contains(substitution)
}
pub(crate) fn id_anchor(&self) -> Option<&Anchor<'a>> {
self.id.as_ref().or_else(|| self.anchors.first())
}
#[must_use]
pub fn with_attributes(mut self, attributes: ElementAttributes<'a>) -> Self {
self.attributes = attributes;
self
}
#[must_use]
pub fn with_options(mut self, options: Vec<&'a str>) -> Self {
self.options = options;
self
}
#[must_use]
pub fn with_roles(mut self, roles: Vec<Role<'a>>) -> Self {
self.roles = roles;
self
}
#[must_use]
pub fn with_style(mut self, style: Option<&'a str>) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_id(mut self, id: Option<Anchor<'a>>) -> Self {
self.id = id;
self
}
#[must_use]
pub fn positional_attribute(&self, index: usize) -> Option<&'a str> {
self.raw_positional_attributes()
.get(index)
.map(|attribute| attribute.value)
.filter(|value| !value.is_empty())
}
pub fn positional_attributes(&self) -> impl ExactSizeIterator<Item = Option<&'a str>> + '_ {
self.raw_positional_attributes()
.iter()
.map(|attribute| (!attribute.value.is_empty()).then_some(attribute.value))
}
pub(crate) fn raw_positional_attributes(&self) -> &[PositionalAttribute<'a>] {
self.retained_positional_attributes
.as_deref()
.unwrap_or(&self.positional_attributes)
}
fn retain_positional_attributes(&mut self) {
self.retained_positional_attributes
.get_or_insert_with(|| self.positional_attributes.clone());
}
pub(crate) fn take_positional_attributes<const N: usize>(
&mut self,
) -> [Option<PositionalAttribute<'a>>; N] {
self.retain_positional_attributes();
let count = N.min(self.positional_attributes.len());
let mut slots = self.positional_attributes.drain(..count);
std::array::from_fn(|_| slots.next())
}
pub(crate) fn prepend_synthetic_positional_attribute(
&mut self,
attribute: PositionalAttribute<'a>,
) {
self.retain_positional_attributes();
self.positional_attributes.insert(0, attribute);
}
pub(crate) fn move_positional_attributes_to_attributes(&mut self) {
let positional_attributes = std::mem::take(&mut self.positional_attributes);
if self.style != Some("source") {
for attribute in &positional_attributes {
if !attribute.value.is_empty() {
self.attributes.insert(
std::borrow::Cow::Borrowed(attribute.value),
AttributeValue::None,
);
}
}
}
if self.retained_positional_attributes.is_none() {
self.retained_positional_attributes = Some(positional_attributes);
}
}
pub(crate) fn has_document_attributes(&self) -> bool {
self.document_attributes.is_some()
}
pub(crate) fn push_document_attribute(&mut self, attribute: DocumentAttribute<'a>) {
self.document_attributes
.get_or_insert_with(Default::default)
.0
.push(attribute);
}
pub(crate) fn take_document_attributes(&mut self) -> Vec<DocumentAttribute<'a>> {
self.document_attributes
.take()
.map_or_else(Vec::new, |attributes| attributes.0)
}
pub(crate) fn append_document_attributes(
&mut self,
mut attributes: Vec<DocumentAttribute<'a>>,
) {
if attributes.is_empty() {
return;
}
self.document_attributes
.get_or_insert_with(Default::default)
.0
.append(&mut attributes);
}
pub(crate) fn overlay_positional_attributes(&mut self, other: &[PositionalAttribute<'a>]) {
overlay_positional_slots(&mut self.positional_attributes, other);
}
#[must_use]
pub fn is_default(&self) -> bool {
#[cfg(feature = "pre-spec-subs")]
let subs_default = self.substitutions.is_none();
#[cfg(not(feature = "pre-spec-subs"))]
let subs_default = true;
self.roles.is_empty()
&& self.options.is_empty()
&& self.style.is_none()
&& self.id.is_none()
&& self.anchors.is_empty()
&& self.attributes.is_empty()
&& self.positional_attributes.is_empty()
&& subs_default
&& self.attribution.is_none()
&& self.citetitle.is_none()
}
#[tracing::instrument(level = "debug", skip_all)]
pub(crate) fn merge(&mut self, other: &BlockMetadata<'a>) {
if let Some(attributes) = &other.document_attributes {
self.document_attributes
.get_or_insert_with(Default::default)
.0
.extend(attributes.0.iter().cloned());
}
for (name, value) in other.attributes.iter() {
self.attributes.set(name.clone(), value.clone());
}
if self.retained_positional_attributes.is_some()
|| other.retained_positional_attributes.is_some()
{
self.retain_positional_attributes();
if let Some(retained) = &mut self.retained_positional_attributes {
overlay_positional_slots(retained, other.raw_positional_attributes());
}
}
self.overlay_positional_attributes(&other.positional_attributes);
if !other.roles.is_empty() {
self.roles.clone_from(&other.roles);
}
if !other.options.is_empty() {
self.options.clone_from(&other.options);
}
if other.style.is_some() {
self.style.clone_from(&other.style);
}
if other.id.is_some() {
self.id.clone_from(&other.id);
}
self.anchors.extend(other.anchors.clone());
#[cfg(feature = "pre-spec-subs")]
if other.substitutions.is_some() {
self.substitutions.clone_from(&other.substitutions);
}
if other.attribution.is_some() {
self.attribution.clone_from(&other.attribution);
self.attribution_substitutions = other.attribution_substitutions;
}
if other.citetitle.is_some() {
self.citetitle.clone_from(&other.citetitle);
self.citetitle_substitutions = other.citetitle_substitutions;
}
}
}
fn overlay_positional_slots<'a>(
destination: &mut Vec<PositionalAttribute<'a>>,
source: &[PositionalAttribute<'a>],
) {
if destination.len() < source.len() {
destination.resize(source.len(), PositionalAttribute::default());
}
for (destination, attribute) in destination.iter_mut().zip(source) {
destination.clone_from(attribute);
}
}