asciidoc_parser/document/
attribute.rs1use crate::{span::MatchedItem, strings::CowStr, HasSpan, Span};
2
3#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct Attribute<'src> {
9 name: Span<'src>,
10 value: RawAttributeValue<'src>,
11 source: Span<'src>,
12}
13
14impl<'src> Attribute<'src> {
15 pub(crate) fn parse(source: Span<'src>) -> Option<MatchedItem<'src, Self>> {
16 let attr_line = source.take_line_with_continuation()?;
17 let colon = attr_line.item.take_prefix(":")?;
18
19 let mut unset = false;
20 let line = if colon.after.starts_with('!') {
21 unset = true;
22 colon.after.slice_from(1..)
23 } else {
24 colon.after
25 };
26
27 let name = line.take_user_attr_name()?;
28
29 let line = if name.after.starts_with('!') && !unset {
30 unset = true;
31 name.after.slice_from(1..)
32 } else {
33 name.after
34 };
35
36 let line = line.take_prefix(":")?;
37
38 let value = if unset {
39 RawAttributeValue::Unset
41 } else if line.after.is_empty() {
42 RawAttributeValue::Set
43 } else {
44 let value = line.after.take_whitespace();
45 RawAttributeValue::Value(value.after)
46 };
47
48 let source = source.trim_remainder(attr_line.after);
49 Some(MatchedItem {
50 item: Self {
51 name: name.item,
52 value,
53 source: source.trim_trailing_whitespace(),
54 },
55 after: attr_line.after,
56 })
57 }
58
59 pub fn name(&'src self) -> &'src Span<'src> {
61 &self.name
62 }
63
64 pub fn raw_value(&'src self) -> &'src RawAttributeValue<'src> {
66 &self.value
67 }
68
69 pub fn value(&'src self) -> InterpretedValue<'src> {
71 self.value.as_interpreted_value()
72 }
73}
74
75impl<'src> HasSpan<'src> for Attribute<'src> {
76 fn span(&'src self) -> &'src Span<'src> {
77 &self.source
78 }
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum RawAttributeValue<'src> {
87 Value(Span<'src>),
89
90 Set,
93
94 Unset,
96}
97
98impl<'src> RawAttributeValue<'src> {
99 pub fn as_interpreted_value(&self) -> InterpretedValue<'src> {
102 match self {
103 Self::Value(span) => {
104 let data = span.data();
105 if data.contains('\n') {
106 let lines: Vec<&str> = data.lines().collect();
107 let last_count = lines.len() - 1;
108
109 let value: Vec<String> = lines
110 .iter()
111 .enumerate()
112 .map(|(count, line)| {
113 let line = if count > 0 {
114 line.trim_start_matches(' ')
115 } else {
116 line
117 };
118
119 let line = line
120 .trim_start_matches('\r')
121 .trim_end_matches(' ')
122 .trim_end_matches('\\')
123 .trim_end_matches(' ');
124
125 if line.ends_with('+') {
126 format!("{}\n", line.trim_end_matches('+').trim_end_matches(' '))
127 } else if count < last_count {
128 format!("{line} ")
129 } else {
130 line.to_string()
131 }
132 })
133 .collect();
134
135 let value = value.join("");
136 InterpretedValue::Value(CowStr::from(value))
137 } else {
138 InterpretedValue::Value(CowStr::Borrowed(data))
139 }
140 }
141
142 Self::Set => InterpretedValue::Set,
143 Self::Unset => InterpretedValue::Unset,
144 }
145 }
146}
147
148#[derive(Clone, Debug, Eq, PartialEq)]
154pub enum InterpretedValue<'src> {
155 Value(CowStr<'src>),
157
158 Set,
161
162 Unset,
164}