asciidoc_parser/document/
attribute.rs1use crate::{
2 HasSpan, Parser, Span,
3 attributes::Attrlist,
4 blocks::{ContentModel, IsBlock},
5 content::{Content, SubstitutionGroup},
6 span::MatchedItem,
7 strings::CowStr,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct Attribute<'src> {
33 name: Span<'src>,
34 value_source: Option<Span<'src>>,
35 value: InterpretedValue,
36 source: Span<'src>,
37}
38
39impl<'src> Attribute<'src> {
40 pub(crate) fn parse(source: Span<'src>, parser: &Parser) -> Option<MatchedItem<'src, Self>> {
41 let attr_line = source.take_line_with_continuation()?;
42 let colon = attr_line.item.take_prefix(":")?;
43
44 let mut unset = false;
45 let line = if colon.after.starts_with('!') {
46 unset = true;
47 colon.after.slice_from(1..)
48 } else {
49 colon.after
50 };
51
52 let name = line.take_user_attr_name()?;
53
54 let line = if name.after.starts_with('!') && !unset {
55 unset = true;
56 name.after.slice_from(1..)
57 } else {
58 name.after
59 };
60
61 let line = line.take_prefix(":")?;
62
63 let (value, value_source) = if unset {
64 (InterpretedValue::Unset, None)
66 } else if line.after.is_empty() {
67 (InterpretedValue::Set, None)
68 } else {
69 let raw_value = line.after.take_whitespace();
70 (
71 InterpretedValue::from_raw_value(&raw_value.after, parser),
72 Some(raw_value.after),
73 )
74 };
75
76 let source = source.trim_remainder(attr_line.after);
77 Some(MatchedItem {
78 item: Self {
79 name: name.item,
80 value_source,
81 value,
82 source: source.trim_trailing_whitespace(),
83 },
84 after: attr_line.after,
85 })
86 }
87
88 pub fn name(&'src self) -> &'src Span<'src> {
90 &self.name
91 }
92
93 pub fn raw_value(&'src self) -> Option<Span<'src>> {
95 self.value_source
96 }
97
98 pub fn value(&'src self) -> &'src InterpretedValue {
100 &self.value
101 }
102}
103
104impl<'src> HasSpan<'src> for Attribute<'src> {
105 fn span(&self) -> Span<'src> {
106 self.source
107 }
108}
109
110impl<'src> IsBlock<'src> for Attribute<'src> {
111 fn content_model(&self) -> ContentModel {
112 ContentModel::Empty
113 }
114
115 fn raw_context(&self) -> CowStr<'src> {
116 "attribute".into()
117 }
118
119 fn title_source(&'src self) -> Option<Span<'src>> {
120 None
121 }
122
123 fn title(&self) -> Option<&str> {
124 None
125 }
126
127 fn anchor(&'src self) -> Option<Span<'src>> {
128 None
129 }
130
131 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
132 None
133 }
134}
135
136#[derive(Clone, Eq, PartialEq)]
142pub enum InterpretedValue {
143 Value(String),
145
146 Set,
149
150 Unset,
152}
153
154impl std::fmt::Debug for InterpretedValue {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 InterpretedValue::Value(value) => f
158 .debug_tuple("InterpretedValue::Value")
159 .field(value)
160 .finish(),
161
162 InterpretedValue::Set => write!(f, "InterpretedValue::Set"),
163 InterpretedValue::Unset => write!(f, "InterpretedValue::Unset"),
164 }
165 }
166}
167
168impl InterpretedValue {
169 fn from_raw_value(raw_value: &Span<'_>, parser: &Parser) -> Self {
170 let data = raw_value.data();
171 let mut content = Content::from(*raw_value);
172
173 if data.contains('\n') {
174 let lines: Vec<&str> = data.lines().collect();
175 let last_count = lines.len() - 1;
176
177 let value: Vec<String> = lines
178 .iter()
179 .enumerate()
180 .map(|(count, line)| {
181 let line = if count > 0 {
182 line.trim_start_matches(' ')
183 } else {
184 line
185 };
186
187 let line = line
188 .trim_start_matches('\r')
189 .trim_end_matches(' ')
190 .trim_end_matches('\\')
191 .trim_end_matches(' ');
192
193 if line.ends_with('+') {
194 format!("{}\n", line.trim_end_matches('+').trim_end_matches(' '))
195 } else if count < last_count {
196 format!("{line} ")
197 } else {
198 line.to_string()
199 }
200 })
201 .collect();
202
203 content.rendered = CowStr::Boxed(value.join("").into_boxed_str());
204 }
205
206 SubstitutionGroup::Header.apply(&mut content, parser, None);
207
208 InterpretedValue::Value(content.rendered.into_string())
209 }
210
211 pub(crate) fn as_maybe_str(&self) -> Option<&str> {
212 match self {
213 InterpretedValue::Value(value) => Some(value.as_ref()),
214 _ => None,
215 }
216 }
217}