1use serde::{
4 Serialize,
5 ser::{SerializeMap, Serializer},
6};
7
8use super::{
9 caption::Caption,
10 inlines::InlineNode,
11 location::Location,
12 section::{SectionKind, SectionNumber},
13 title::Title,
14};
15
16pub const UNNUMBERED_SECTION_STYLES: &[&str] = &[
22 "preface",
23 "abstract",
24 "dedication",
25 "colophon",
26 "bibliography",
27 "glossary",
28 "index",
29 "appendix",
30];
31
32#[derive(Clone, Debug, Default, PartialEq, Serialize)]
36#[non_exhaustive]
37pub struct Anchor<'a> {
38 pub id: &'a str,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub xreflabel: Option<&'a str>,
41 pub location: Location,
42 #[serde(skip)]
43 pub(crate) bibliography: bool,
44 #[serde(skip)]
45 pub(crate) bibliography_label: Option<Box<BibliographyLabel<'a>>>,
46}
47
48#[derive(Clone, Debug, PartialEq)]
49pub(crate) struct BibliographyLabel<'a> {
50 pub(crate) source: Option<&'a str>,
51 pub(crate) content: Vec<InlineNode<'a>>,
52}
53
54impl<'a> Anchor<'a> {
55 #[must_use]
57 pub fn new(id: &'a str, location: Location) -> Self {
58 Self {
59 id,
60 xreflabel: None,
61 location,
62 bibliography: false,
63 bibliography_label: None,
64 }
65 }
66
67 #[must_use]
69 pub fn with_xreflabel(mut self, xreflabel: Option<&'a str>) -> Self {
70 self.xreflabel = xreflabel;
71 self
72 }
73
74 #[must_use]
76 pub fn is_bibliography(&self) -> bool {
77 self.bibliography
78 }
79
80 #[must_use]
85 pub fn bibliography_label(&self) -> Option<&[InlineNode<'a>]> {
86 self.bibliography
87 .then_some(self.bibliography_label.as_deref())
88 .flatten()
89 .map(|label| label.content.as_slice())
90 }
91}
92
93#[derive(Clone, Debug, PartialEq)]
97#[non_exhaustive]
98pub struct TocEntry<'a> {
99 pub id: &'a str,
101 pub title: Title<'a>,
103 pub level: u8,
105 pub xreflabel: Option<&'a str>,
107 pub kind: SectionKind,
110 number: Option<SectionNumber>,
111 pub location: Location,
113}
114
115impl<'a> TocEntry<'a> {
116 pub(crate) fn for_section(
117 id: &'a str,
118 title: Title<'a>,
119 level: u8,
120 xreflabel: Option<&'a str>,
121 kind: SectionKind,
122 location: Location,
123 ) -> Self {
124 Self {
125 id,
126 title,
127 level,
128 xreflabel,
129 kind,
130 number: None,
131 location,
132 }
133 }
134
135 pub(super) fn set_number(&mut self, number: Option<SectionNumber>) {
136 self.number = number;
137 }
138
139 #[must_use]
143 pub fn number(&self) -> Option<&str> {
144 self.number.as_ref().map(SectionNumber::as_str)
145 }
146}
147
148impl Serialize for TocEntry<'_> {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: Serializer,
152 {
153 let mut state = serializer.serialize_map(None)?;
154 state.serialize_entry("id", &self.id)?;
155 state.serialize_entry("title", &self.title)?;
156 state.serialize_entry("level", &self.level)?;
157 if self.xreflabel.is_some() {
158 state.serialize_entry("xreflabel", &self.xreflabel)?;
159 }
160 if let Some(style) = self.kind.as_style() {
161 state.serialize_entry("style", style)?;
162 }
163 state.end()
164 }
165}
166
167#[derive(Clone, Debug, PartialEq, Eq)]
169pub(crate) struct SectionReference {
170 pub(crate) name: &'static str,
171 pub(super) number: Option<SectionNumber>,
172}
173
174#[derive(Clone, Debug, PartialEq)]
180#[non_exhaustive]
181pub struct Reference<'a> {
182 pub xreflabel: Option<Vec<InlineNode<'a>>>,
187 pub title: Option<Title<'a>>,
192 pub location: Location,
194 pub caption: Option<Caption<'a>>,
196 pub(crate) section: Option<SectionReference>,
197 pub(crate) bibliography: bool,
198 pub(crate) automatic_citation: bool,
199}
200
201impl Reference<'_> {
202 #[must_use]
207 pub fn section_name(&self) -> Option<&str> {
208 self.section.as_ref().map(|section| section.name)
209 }
210
211 #[must_use]
226 pub fn section_number(&self) -> Option<&str> {
227 self.section
228 .as_ref()?
229 .number
230 .as_ref()
231 .map(SectionNumber::as_str)
232 }
233
234 #[must_use]
236 pub fn is_bibliography(&self) -> bool {
237 self.bibliography
238 }
239
240 #[must_use]
242 pub fn has_automatic_citation(&self) -> bool {
243 self.automatic_citation
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn section_reference_queries_borrow_shared_numbers() -> Result<(), Box<dyn std::error::Error>> {
253 let parsed = crate::parse(
254 include_str!("../../fixtures/tests/section_reference_metadata.adoc"),
255 &crate::Options::default(),
256 )?;
257 let document = parsed.document();
258 for id in ["part", "chapter", "section", "appendix"] {
259 let reference = document.references.get(id).ok_or("missing reference")?;
260 let cloned = reference.clone();
261 let number = reference.section_number().ok_or("missing number")?;
262 let cloned_number = cloned.section_number().ok_or("missing cloned number")?;
263 let entry = document
264 .toc_entries
265 .iter()
266 .find(|entry| entry.id == id)
267 .ok_or("missing TOC entry")?;
268
269 assert!(std::ptr::eq(number, cloned_number), "{id}");
271 if let Some(heading_number) = entry.number() {
272 assert!(std::ptr::eq(number, heading_number), "{id}");
273 }
274 }
275 let plain = document.references.get("plain").ok_or("missing section")?;
276 assert_eq!(plain.section_name(), Some("section"));
277 assert_eq!(plain.section_number(), None);
278 let block = document.references.get("block").ok_or("missing block")?;
279 assert_eq!(block.section_name(), None);
280 assert_eq!(block.section_number(), None);
281 Ok(())
282 }
283
284 fn toc_entry(kind: SectionKind) -> TocEntry<'static> {
285 TocEntry::for_section(
286 "_intro",
287 Title::default(),
288 1,
289 None,
290 kind,
291 Location::default(),
292 )
293 }
294
295 #[test]
296 fn toc_entry_serializes_special_style() -> Result<(), serde_json::Error> {
297 let json = serde_json::to_value(toc_entry(SectionKind::Preface))?;
298 assert_eq!(json.get("style").and_then(|v| v.as_str()), Some("preface"));
299 Ok(())
300 }
301
302 #[test]
303 fn toc_entry_omits_style_for_normal_section() -> Result<(), serde_json::Error> {
304 let json = serde_json::to_value(toc_entry(SectionKind::Normal))?;
305 assert!(json.get("style").is_none());
306 Ok(())
307 }
308}