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