asciidoc_parser/blocks/
media.rs1use crate::{
2 HasSpan, Parser, Span,
3 attributes::{Attrlist, AttrlistContext},
4 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
5 span::MatchedItem,
6 strings::CowStr,
7 warnings::{MatchAndWarnings, Warning, WarningType},
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct MediaBlock<'src> {
13 type_: MediaType,
14 target: Span<'src>,
15 macro_attrlist: Attrlist<'src>,
16 source: Span<'src>,
17 title_source: Option<Span<'src>>,
18 title: Option<String>,
19 anchor: Option<Span<'src>>,
20 attrlist: Option<Attrlist<'src>>,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum MediaType {
26 Image,
28
29 Video,
31
32 Audio,
34}
35
36impl<'src> MediaBlock<'src> {
37 pub(crate) fn parse(
38 metadata: &BlockMetadata<'src>,
39 parser: &mut Parser,
40 ) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
41 let line = metadata.block_start.take_normalized_line();
42
43 if !line.item.ends_with(']') {
45 return MatchAndWarnings {
46 item: None,
47 warnings: vec![],
48 };
49 }
50
51 let Some(name) = line.item.take_ident() else {
52 return MatchAndWarnings {
53 item: None,
54 warnings: vec![],
55 };
56 };
57
58 let type_ = match name.item.data() {
59 "image" => MediaType::Image,
60 "video" => MediaType::Video,
61 "audio" => MediaType::Audio,
62 _ => {
63 return MatchAndWarnings {
64 item: None,
65 warnings: vec![],
66 };
67 }
68 };
69
70 let Some(colons) = name.after.take_prefix("::") else {
71 return MatchAndWarnings {
72 item: None,
73 warnings: vec![Warning {
74 source: name.after,
75 warning: WarningType::MacroMissingDoubleColon,
76 }],
77 };
78 };
79
80 let target = colons.after.take_while(|c| c != '[');
82
83 if target.item.is_empty() {
84 return MatchAndWarnings {
85 item: None,
86 warnings: vec![Warning {
87 source: target.after,
88 warning: WarningType::MediaMacroMissingTarget,
89 }],
90 };
91 }
92
93 let Some(open_brace) = target.after.take_prefix("[") else {
94 return MatchAndWarnings {
95 item: None,
96 warnings: vec![Warning {
97 source: target.after,
98 warning: WarningType::MacroMissingAttributeList,
99 }],
100 };
101 };
102
103 let attrlist = open_brace.after.slice(0..open_brace.after.len() - 1);
104 let macro_attrlist = Attrlist::parse(attrlist, parser, AttrlistContext::Inline);
107
108 let source: Span = metadata.source.trim_remainder(line.after);
109 let source = source.slice(0..source.trim().len());
110
111 MatchAndWarnings {
112 item: Some(MatchedItem {
113 item: Self {
114 type_,
115 target: target.item,
116 macro_attrlist: macro_attrlist.item.item,
117 source,
118 title_source: metadata.title_source,
119 title: metadata.title.clone(),
120 anchor: metadata.anchor,
121 attrlist: metadata.attrlist.clone(),
122 },
123
124 after: line.after.discard_empty_lines(),
125 }),
126 warnings: macro_attrlist.warnings,
127 }
128 }
129
130 pub fn type_(&self) -> MediaType {
132 self.type_
133 }
134
135 pub fn target(&'src self) -> Option<&'src Span<'src>> {
137 Some(&self.target)
138 }
139
140 pub fn macro_attrlist(&'src self) -> &'src Attrlist<'src> {
150 &self.macro_attrlist
151 }
152}
153
154impl<'src> IsBlock<'src> for MediaBlock<'src> {
155 fn content_model(&self) -> ContentModel {
156 ContentModel::Empty
157 }
158
159 fn raw_context(&self) -> CowStr<'src> {
160 match self.type_ {
161 MediaType::Audio => "audio",
162 MediaType::Image => "image",
163 MediaType::Video => "video",
164 }
165 .into()
166 }
167
168 fn title_source(&'src self) -> Option<Span<'src>> {
169 self.title_source
170 }
171
172 fn title(&self) -> Option<&str> {
173 self.title.as_deref()
174 }
175
176 fn anchor(&'src self) -> Option<Span<'src>> {
177 self.anchor
178 }
179
180 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
181 self.attrlist.as_ref()
182 }
183}
184
185impl<'src> HasSpan<'src> for MediaBlock<'src> {
186 fn span(&self) -> Span<'src> {
187 self.source
188 }
189}