asciidoc_parser/document/
attribute.rs1use crate::{
2 HasSpan, Parser, Span,
3 content::{Content, SubstitutionGroup},
4 span::MatchedItem,
5 strings::CowStr,
6};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct Attribute<'src> {
14 name: Span<'src>,
15 value_source: Option<Span<'src>>,
16 value: InterpretedValue,
17 source: Span<'src>,
18}
19
20impl<'src> Attribute<'src> {
21 pub(crate) fn parse(
22 source: Span<'src>,
23 parser: &Parser<'_>,
24 ) -> Option<MatchedItem<'src, Self>> {
25 let attr_line = source.take_line_with_continuation()?;
26 let colon = attr_line.item.take_prefix(":")?;
27
28 let mut unset = false;
29 let line = if colon.after.starts_with('!') {
30 unset = true;
31 colon.after.slice_from(1..)
32 } else {
33 colon.after
34 };
35
36 let name = line.take_user_attr_name()?;
37
38 let line = if name.after.starts_with('!') && !unset {
39 unset = true;
40 name.after.slice_from(1..)
41 } else {
42 name.after
43 };
44
45 let line = line.take_prefix(":")?;
46
47 let (value, value_source) = if unset {
48 (InterpretedValue::Unset, None)
50 } else if line.after.is_empty() {
51 (InterpretedValue::Set, None)
52 } else {
53 let raw_value = line.after.take_whitespace();
54 (
55 InterpretedValue::from_raw_value(&raw_value.after, parser),
56 Some(raw_value.after),
57 )
58 };
59
60 let source = source.trim_remainder(attr_line.after);
61 Some(MatchedItem {
62 item: Self {
63 name: name.item,
64 value_source,
65 value,
66 source: source.trim_trailing_whitespace(),
67 },
68 after: attr_line.after,
69 })
70 }
71
72 pub fn name(&'src self) -> &'src Span<'src> {
74 &self.name
75 }
76
77 pub fn raw_value(&'src self) -> Option<Span<'src>> {
79 self.value_source
80 }
81
82 pub fn value(&'src self) -> &'src InterpretedValue {
84 &self.value
85 }
86}
87
88impl<'src> HasSpan<'src> for Attribute<'src> {
89 fn span(&self) -> Span<'src> {
90 self.source
91 }
92}
93
94#[derive(Clone, Debug, Eq, PartialEq)]
100pub enum InterpretedValue {
101 Value(String),
103
104 Set,
107
108 Unset,
110}
111
112impl InterpretedValue {
113 fn from_raw_value(raw_value: &Span<'_>, parser: &Parser) -> Self {
114 let data = raw_value.data();
115 let mut content = Content::from(*raw_value);
116
117 if data.contains('\n') {
118 let lines: Vec<&str> = data.lines().collect();
119 let last_count = lines.len() - 1;
120
121 let value: Vec<String> = lines
122 .iter()
123 .enumerate()
124 .map(|(count, line)| {
125 let line = if count > 0 {
126 line.trim_start_matches(' ')
127 } else {
128 line
129 };
130
131 let line = line
132 .trim_start_matches('\r')
133 .trim_end_matches(' ')
134 .trim_end_matches('\\')
135 .trim_end_matches(' ');
136
137 if line.ends_with('+') {
138 format!("{}\n", line.trim_end_matches('+').trim_end_matches(' '))
139 } else if count < last_count {
140 format!("{line} ")
141 } else {
142 line.to_string()
143 }
144 })
145 .collect();
146
147 content.rendered = CowStr::Boxed(value.join("").into_boxed_str());
148 }
149
150 SubstitutionGroup::Header.apply(&mut content, parser, None);
151
152 InterpretedValue::Value(content.rendered.into_string())
153 }
154
155 pub(crate) fn as_maybe_str(&self) -> Option<&str> {
156 match self {
157 InterpretedValue::Value(value) => Some(value.as_ref()),
158 _ => None,
159 }
160 }
161}