1use serde::Serialize;
4
5#[cfg(feature = "pre-spec-subs")]
6use super::substitution::SubstitutionSpec;
7use super::{
8 DocumentAttribute,
9 anchor::Anchor,
10 attributes::{AttributeValue, ElementAttributes},
11 attribution::{Attribution, CiteTitle},
12 caption::Caption,
13 location::Location,
14 substitution::Substitution,
15};
16
17pub type Role<'a> = &'a str;
18
19#[derive(Clone, Debug, Default, PartialEq)]
20pub(crate) struct PositionalAttribute<'a> {
21 pub(crate) value: &'a str,
22 pub(crate) substitutions: bool,
23 pub(crate) location: Option<Location>,
24}
25
26#[derive(Clone, Debug, Default, PartialEq)]
27pub(crate) struct DocumentAttributeEvents<'a>(Vec<DocumentAttribute<'a>>);
28
29#[derive(Clone, Debug, Default, Serialize)]
34#[non_exhaustive]
35pub struct BlockMetadata<'a> {
36 #[serde(skip)]
38 pub(crate) document_attributes: Option<Box<DocumentAttributeEvents<'a>>>,
39 #[serde(skip)]
43 pub caption: Option<Caption<'a>>,
44 #[serde(default, skip_serializing_if = "ElementAttributes::is_empty")]
45 pub attributes: ElementAttributes<'a>,
46 #[serde(default, skip_serializing)]
48 pub(crate) positional_attributes: Vec<PositionalAttribute<'a>>,
49 #[serde(skip)]
51 pub(crate) retained_positional_attributes: Option<Vec<PositionalAttribute<'a>>>,
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub roles: Vec<Role<'a>>,
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
55 pub options: Vec<&'a str>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub style: Option<&'a str>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub id: Option<Anchor<'a>>,
60 #[serde(default, skip_serializing_if = "Vec::is_empty")]
61 pub anchors: Vec<Anchor<'a>>,
62 #[cfg(feature = "pre-spec-subs")]
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub substitutions: Option<SubstitutionSpec>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub attribution: Option<Attribution<'a>>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub citetitle: Option<CiteTitle<'a>>,
79 #[serde(default, skip_serializing)]
80 pub(crate) attribution_substitutions: bool,
81 #[serde(default, skip_serializing)]
82 pub(crate) citetitle_substitutions: bool,
83 #[serde(skip)]
84 pub location: Option<Location>,
85}
86
87impl PartialEq for BlockMetadata<'_> {
88 fn eq(&self, other: &Self) -> bool {
89 let Self {
90 document_attributes,
91 caption,
92 attributes,
93 positional_attributes: _,
94 retained_positional_attributes: _,
95 roles,
96 options,
97 style,
98 id,
99 anchors,
100 #[cfg(feature = "pre-spec-subs")]
101 substitutions,
102 attribution,
103 citetitle,
104 attribution_substitutions,
105 citetitle_substitutions,
106 location,
107 } = self;
108 #[cfg(feature = "pre-spec-subs")]
109 if substitutions != &other.substitutions {
110 return false;
111 }
112 document_attributes == &other.document_attributes
113 && caption == &other.caption
114 && attributes == &other.attributes
115 && roles == &other.roles
116 && options == &other.options
117 && style == &other.style
118 && id == &other.id
119 && anchors == &other.anchors
120 && attribution == &other.attribution
121 && citetitle == &other.citetitle
122 && attribution_substitutions == &other.attribution_substitutions
123 && citetitle_substitutions == &other.citetitle_substitutions
124 && location == &other.location
125 && self
126 .positional_attributes()
127 .eq(other.positional_attributes())
128 }
129}
130
131impl<'a> BlockMetadata<'a> {
132 #[must_use]
134 pub fn new() -> Self {
135 Self::default()
136 }
137
138 #[must_use]
143 pub fn uses_substitution(
144 &self,
145 substitution: &Substitution,
146 defaults: &[Substitution],
147 ) -> bool {
148 #[cfg(feature = "pre-spec-subs")]
149 if let Some(spec) = &self.substitutions {
150 return spec.contains(substitution, defaults);
151 }
152 defaults.contains(substitution)
153 }
154
155 pub(crate) fn id_anchor(&self) -> Option<&Anchor<'a>> {
158 self.id.as_ref().or_else(|| self.anchors.first())
159 }
160
161 #[must_use]
163 pub fn with_attributes(mut self, attributes: ElementAttributes<'a>) -> Self {
164 self.attributes = attributes;
165 self
166 }
167
168 #[must_use]
170 pub fn with_options(mut self, options: Vec<&'a str>) -> Self {
171 self.options = options;
172 self
173 }
174
175 #[must_use]
177 pub fn with_roles(mut self, roles: Vec<Role<'a>>) -> Self {
178 self.roles = roles;
179 self
180 }
181
182 #[must_use]
184 pub fn with_style(mut self, style: Option<&'a str>) -> Self {
185 self.style = style;
186 self
187 }
188
189 #[must_use]
191 pub fn with_id(mut self, id: Option<Anchor<'a>>) -> Self {
192 self.id = id;
193 self
194 }
195
196 #[must_use]
201 pub fn positional_attribute(&self, index: usize) -> Option<&'a str> {
202 self.raw_positional_attributes()
203 .get(index)
204 .map(|attribute| attribute.value)
205 .filter(|value| !value.is_empty())
206 }
207
208 pub fn positional_attributes(&self) -> impl ExactSizeIterator<Item = Option<&'a str>> + '_ {
230 self.raw_positional_attributes()
231 .iter()
232 .map(|attribute| (!attribute.value.is_empty()).then_some(attribute.value))
233 }
234
235 pub(crate) fn raw_positional_attributes(&self) -> &[PositionalAttribute<'a>] {
236 self.retained_positional_attributes
237 .as_deref()
238 .unwrap_or(&self.positional_attributes)
239 }
240
241 fn retain_positional_attributes(&mut self) {
242 self.retained_positional_attributes
243 .get_or_insert_with(|| self.positional_attributes.clone());
244 }
245
246 pub(crate) fn take_positional_attributes<const N: usize>(
247 &mut self,
248 ) -> [Option<PositionalAttribute<'a>>; N] {
249 self.retain_positional_attributes();
250 let count = N.min(self.positional_attributes.len());
251 let mut slots = self.positional_attributes.drain(..count);
252 std::array::from_fn(|_| slots.next())
253 }
254
255 pub(crate) fn prepend_synthetic_positional_attribute(
256 &mut self,
257 attribute: PositionalAttribute<'a>,
258 ) {
259 self.retain_positional_attributes();
260 self.positional_attributes.insert(0, attribute);
261 }
262
263 pub(crate) fn move_positional_attributes_to_attributes(&mut self) {
264 let positional_attributes = std::mem::take(&mut self.positional_attributes);
265 if self.style != Some("source") {
266 for attribute in &positional_attributes {
267 if !attribute.value.is_empty() {
268 self.attributes.insert(
269 std::borrow::Cow::Borrowed(attribute.value),
270 AttributeValue::None,
271 );
272 }
273 }
274 }
275 if self.retained_positional_attributes.is_none() {
277 self.retained_positional_attributes = Some(positional_attributes);
278 }
279 }
280
281 pub(crate) fn has_document_attributes(&self) -> bool {
282 self.document_attributes.is_some()
283 }
284
285 pub(crate) fn push_document_attribute(&mut self, attribute: DocumentAttribute<'a>) {
286 self.document_attributes
287 .get_or_insert_with(Default::default)
288 .0
289 .push(attribute);
290 }
291
292 pub(crate) fn take_document_attributes(&mut self) -> Vec<DocumentAttribute<'a>> {
293 self.document_attributes
294 .take()
295 .map_or_else(Vec::new, |attributes| attributes.0)
296 }
297
298 pub(crate) fn append_document_attributes(
299 &mut self,
300 mut attributes: Vec<DocumentAttribute<'a>>,
301 ) {
302 if attributes.is_empty() {
303 return;
304 }
305 self.document_attributes
306 .get_or_insert_with(Default::default)
307 .0
308 .append(&mut attributes);
309 }
310
311 pub(crate) fn overlay_positional_attributes(&mut self, other: &[PositionalAttribute<'a>]) {
312 overlay_positional_slots(&mut self.positional_attributes, other);
313 }
314
315 #[must_use]
321 pub fn is_default(&self) -> bool {
322 #[cfg(feature = "pre-spec-subs")]
323 let subs_default = self.substitutions.is_none();
324 #[cfg(not(feature = "pre-spec-subs"))]
325 let subs_default = true;
326 self.roles.is_empty()
327 && self.options.is_empty()
328 && self.style.is_none()
329 && self.id.is_none()
330 && self.anchors.is_empty()
331 && self.attributes.is_empty()
332 && self.positional_attributes.is_empty()
333 && subs_default
334 && self.attribution.is_none()
335 && self.citetitle.is_none()
336 }
337
338 #[tracing::instrument(level = "debug", skip_all)]
339 pub(crate) fn merge(&mut self, other: &BlockMetadata<'a>) {
340 if let Some(attributes) = &other.document_attributes {
341 self.document_attributes
342 .get_or_insert_with(Default::default)
343 .0
344 .extend(attributes.0.iter().cloned());
345 }
346 for (name, value) in other.attributes.iter() {
347 self.attributes.set(name.clone(), value.clone());
348 }
349 if self.retained_positional_attributes.is_some()
350 || other.retained_positional_attributes.is_some()
351 {
352 self.retain_positional_attributes();
353 if let Some(retained) = &mut self.retained_positional_attributes {
354 overlay_positional_slots(retained, other.raw_positional_attributes());
355 }
356 }
357 self.overlay_positional_attributes(&other.positional_attributes);
358 if !other.roles.is_empty() {
359 self.roles.clone_from(&other.roles);
360 }
361 if !other.options.is_empty() {
362 self.options.clone_from(&other.options);
363 }
364 if other.style.is_some() {
365 self.style.clone_from(&other.style);
366 }
367 if other.id.is_some() {
368 self.id.clone_from(&other.id);
369 }
370 self.anchors.extend(other.anchors.clone());
371 #[cfg(feature = "pre-spec-subs")]
372 if other.substitutions.is_some() {
373 self.substitutions.clone_from(&other.substitutions);
374 }
375 if other.attribution.is_some() {
376 self.attribution.clone_from(&other.attribution);
377 self.attribution_substitutions = other.attribution_substitutions;
378 }
379 if other.citetitle.is_some() {
380 self.citetitle.clone_from(&other.citetitle);
381 self.citetitle_substitutions = other.citetitle_substitutions;
382 }
383 }
384}
385
386fn overlay_positional_slots<'a>(
387 destination: &mut Vec<PositionalAttribute<'a>>,
388 source: &[PositionalAttribute<'a>],
389) {
390 if destination.len() < source.len() {
391 destination.resize(source.len(), PositionalAttribute::default());
392 }
393 for (destination, attribute) in destination.iter_mut().zip(source) {
394 destination.clone_from(attribute);
395 }
396}