asciidoc_parser/blocks/
section.rs1use std::slice::Iter;
2
3use crate::{
4 HasSpan, Parser, Span,
5 attributes::Attrlist,
6 blocks::{
7 Block, ContentModel, IsBlock, metadata::BlockMetadata, parse_utils::parse_blocks_until,
8 },
9 content::{Content, SubstitutionGroup},
10 span::MatchedItem,
11 strings::CowStr,
12 warnings::MatchAndWarnings,
13};
14
15#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct SectionBlock<'src> {
24 level: usize,
25 section_title: Content<'src>,
26 blocks: Vec<Block<'src>>,
27 source: Span<'src>,
28 title_source: Option<Span<'src>>,
29 title: Option<String>,
30 anchor: Option<Span<'src>>,
31 attrlist: Option<Attrlist<'src>>,
32}
33
34impl<'src> SectionBlock<'src> {
35 pub(crate) fn parse(
36 metadata: &BlockMetadata<'src>,
37 parser: &mut Parser,
38 ) -> Option<MatchAndWarnings<'src, MatchedItem<'src, Self>>> {
39 let source = metadata.block_start.discard_empty_lines();
40 let level = parse_title_line(source)?;
41
42 let maw_blocks = parse_blocks_until(
43 level.after,
44 |i| peer_or_ancestor_section(*i, level.item.0),
45 parser,
46 );
47
48 let blocks = maw_blocks.item;
49 let source = metadata.source.trim_remainder(blocks.after);
50
51 let mut section_title = Content::from(level.item.1);
52 SubstitutionGroup::Title.apply(&mut section_title, parser, metadata.attrlist.as_ref());
53
54 Some(MatchAndWarnings {
55 item: MatchedItem {
56 item: Self {
57 level: level.item.0,
58 section_title,
59 blocks: blocks.item,
60 source: source.trim_trailing_whitespace(),
61 title_source: metadata.title_source,
62 title: metadata.title.clone(),
63 anchor: metadata.anchor,
64 attrlist: metadata.attrlist.clone(),
65 },
66 after: blocks.after,
67 },
68 warnings: maw_blocks.warnings,
69 })
70 }
71
72 pub fn level(&self) -> usize {
82 self.level
83 }
84
85 pub fn section_title_source(&self) -> Span<'src> {
87 self.section_title.original()
88 }
89
90 pub fn section_title(&'src self) -> &'src str {
93 self.section_title.rendered()
94 }
95}
96
97impl<'src> IsBlock<'src> for SectionBlock<'src> {
98 fn content_model(&self) -> ContentModel {
99 ContentModel::Compound
100 }
101
102 fn raw_context(&self) -> CowStr<'src> {
103 "section".into()
104 }
105
106 fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
107 self.blocks.iter()
108 }
109
110 fn title_source(&'src self) -> Option<Span<'src>> {
111 self.title_source
112 }
113
114 fn title(&self) -> Option<&str> {
115 self.title.as_deref()
116 }
117
118 fn anchor(&'src self) -> Option<Span<'src>> {
119 self.anchor
120 }
121
122 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
123 self.attrlist.as_ref()
124 }
125}
126
127impl<'src> HasSpan<'src> for SectionBlock<'src> {
128 fn span(&self) -> Span<'src> {
129 self.source
130 }
131}
132
133fn parse_title_line(source: Span<'_>) -> Option<MatchedItem<'_, (usize, Span<'_>)>> {
134 let mi = source.take_non_empty_line()?;
135 let mut line = mi.item;
136
137 let mut count = 0;
142
143 while let Some(mi) = line.take_prefix("=") {
144 count += 1;
145 line = mi.after;
146 }
147
148 let title = line.take_required_whitespace()?;
149
150 Some(MatchedItem {
151 item: (count - 1, title.after),
152 after: mi.after,
153 })
154}
155
156fn peer_or_ancestor_section(source: Span<'_>, level: usize) -> bool {
157 if let Some(mi) = parse_title_line(source) {
158 mi.item.0 <= level
159 } else {
160 false
161 }
162}