1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Attributes and their values: the in-tag half of the builder.
use crate::markup::token::QuoteType;
use super::Builder;
use crate::event::{Event, EventKind};
use crate::markup::surface::{AttrValue, Attribute, Token};
impl<'a, const RAW: bool, const LINT: bool> Builder<'a, '_, RAW, LINT> {
/// `events[i]` is the first `AttrName` piece. Directive pieces all
/// record as `AttrName`, so the raw name token spans from the first
/// piece to the `AttrNameEnd` offset — the authored spelling, sigils
/// and modifiers included.
pub(super) fn attribute(&mut self) -> Attribute<'a> {
// The caller saw an `AttrName` event at `i`; without one there is no
// name, so an empty attribute at the cursor stands in.
let first = self.events.get(self.i).copied().unwrap_or(Event {
kind: EventKind::AttrName,
aux: 0,
start: self.cursor as u32,
end: self.cursor as u32,
});
let name_start = first.start as usize;
let mut name_end = first.end as usize;
self.i += 1;
loop {
match self.events.get(self.i) {
Some(next) if next.kind == EventKind::AttrName => {
name_end = next.end as usize;
self.i += 1;
}
Some(next) if next.kind == EventKind::AttrNameEnd => {
name_end = next.start as usize;
self.i += 1;
break;
}
_ => break,
}
}
let name = self.token_at(name_start, name_end);
// Value pieces (split at entities) until the AttrEnd event.
let mut data: Option<(usize, usize)> = None;
loop {
match self.events.get(self.i) {
Some(next) if next.kind == EventKind::AttrData => {
let (s, e) = (next.start as usize, next.end as usize);
data = Some(data.map_or((s, e), |(from, _)| (from, e)));
self.i += 1;
}
Some(next) if next.kind == EventKind::AttrEnd => {
let (quote, endq) = (next.quote(), next.start as usize);
self.i += 1;
let (eq, value) = self.attr_value(quote, endq, data);
return Attribute { name, eq, value };
}
_ => {
return Attribute {
name,
eq: None,
value: None,
};
}
}
}
}
/// The `=` between the cursor and `before`, if authored (absent in
/// the recovered `a"v"` form).
fn eq_token(&mut self, before: usize) -> Option<Token<'a>> {
let eq = self.find_byte(b'=', self.cursor, before)?;
Some(self.token_at(eq, eq + 1))
}
fn attr_value(
&mut self,
quote: QuoteType,
endq: usize,
data: Option<(usize, usize)>,
) -> (Option<Token<'a>>, Option<AttrValue<'a>>) {
match quote {
QuoteType::NoValue => (None, None),
QuoteType::Unquoted => match data {
Some((s, e)) => {
let eq = self.eq_token(s);
let content = self.token_at(s, e);
(eq, Some(AttrValue::unquoted(content)))
}
None => {
// `a= >`: a value was announced and never written —
// the content is a typed `Missing` hole.
let Some(eq) = self.eq_token(endq) else {
return (None, None);
};
let content = self.missing_to(self.cursor);
(Some(eq), Some(AttrValue::unquoted(content)))
}
},
QuoteType::Single | QuoteType::Double => {
let qc = if quote == QuoteType::Single {
b'\''
} else {
b'"'
};
let open_pos = match data {
Some((s, _)) => Some(s - 1),
None => self.find_byte(qc, self.cursor, self.src.len()),
};
let Some(oq) = open_pos else {
// Defensive: no quote byte found; treat as valueless.
return (None, None);
};
let eq = self.eq_token(oq);
let open_quote = self.token_at(oq, oq + 1);
let content = match data {
Some((s, e)) => self.token_at(s, e),
None => self.token_at(self.cursor, self.cursor),
};
let close_quote =
if endq >= self.cursor && self.src.as_bytes().get(endq) == Some(&qc) {
self.token_at(endq, endq + 1)
} else {
// Unterminated at EOF: a typed `Missing` hole.
self.missing_to(self.cursor)
};
(
eq,
Some(AttrValue {
open_quote: Some(open_quote),
content,
close_quote: Some(close_quote),
}),
)
}
}
}
}