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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
use crate::ebook::element::{Attribute, Attributes, Properties};
use crate::util::str::StringExt;
impl Properties {
/// Returns [`Some`] if populated, otherwise [`None`].
pub(crate) fn as_option_str(&self) -> Option<&str> {
(!self.0.is_empty()).then_some(self.0.as_str())
}
/// Inserts one or more properties, returning `true` if at least one new property was added.
/// `false` is returned if all given properties are already present.
///
/// The given input is split by whitespace and each property is inserted individually.
///
/// # Examples
/// - Inserting multiple properties:
/// ```
/// # use rbook::ebook::element::Attribute;
/// # let mut attribute = Attribute::new("properties", "");
/// # let mut properties = attribute.as_properties_mut();
///
/// // Inserting multiple properties
/// # // `assert_eq` is used over `assert` as it is more readable
/// assert_eq!(true, properties.insert("nav scripted"));
/// assert_eq!("nav scripted", properties.as_str());
///
/// // Inserting an existing and new property
/// // Returns `true` as `cover` is new (`nav is skipped)
/// assert_eq!(true, properties.insert("nav cover"));
/// assert_eq!("nav scripted cover", properties.as_str());
///
/// // Inserting an already existing property
/// // Returns `false` as `scripted` is already present
/// assert_eq!(false, properties.insert("scripted"));
/// assert_eq!("nav scripted cover", properties.as_str());
/// ```
pub fn insert(&mut self, properties: &str) -> bool {
let mut any_inserted = false;
for property in properties.split_whitespace() {
if !self.has_property(property) {
any_inserted = true;
if !self.0.is_empty() {
self.0.push(' ');
}
self.0.push_str(property);
}
}
any_inserted
}
/// Removes one or more properties, returning `true` if at least one property was removed.
/// `false` is returned if all given properties are not present.
///
/// The given input is split by whitespace and each property is removed individually.
///
/// # Examples
/// - Removing multiple properties:
/// ```
/// # use rbook::ebook::element::Attribute;
/// # let mut attribute = Attribute::new("properties", "nav scripted cover");
/// # let mut properties = attribute.as_properties_mut();
///
/// // Initial state
/// assert_eq!("nav scripted cover", properties.as_str());
///
/// assert_eq!(false, properties.remove("script"));
/// assert_eq!("nav scripted cover", properties.as_str());
///
/// // Removing an existing property
/// // Returns `true` as the property was present and now removed
/// assert_eq!(true, properties.remove("scripted"));
/// assert_eq!("nav cover", properties.as_str());
///
/// // Removing multiple properties
/// assert_eq!(true, properties.remove("nav scripted cover"));
/// assert!(properties.is_empty());
/// ```
pub fn remove(&mut self, properties: &str) -> bool {
if self.is_empty() {
return false;
}
let mut any_removed = false;
for property in properties.split_whitespace() {
// Check if the property to remove exists
// - In most, if not nearly all cases, `self` is empty or contains one property
let match_pos = self.0.match_indices(property).find(|&(start, _)| {
let end = start + property.len();
// Check spacing to ensure a proper space-separated match
let leading_ok = start == 0 || self.0.as_bytes()[start - 1] == b' ';
let trailing_ok = end == self.0.len() || self.0.as_bytes()[end] == b' ';
leading_ok && trailing_ok
});
if let Some((start, _)) = match_pos {
let end = start + property.len();
any_removed = true;
if end < self.0.len() {
// Has a trailing space: "xyz "
self.0.drain(start..=end);
} else if start > 0 {
// Has a leading space: " xyz"
self.0.drain(start - 1..end);
} else {
// The only property
self.0.clear();
}
}
}
any_removed
}
/// Removes all properties.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl Attribute {
/// Creates a new attribute with the given [`name`](Self::name) and [`value`](Self::value).
///
/// The given input has their leading and trailing whitespace trimmed in-place.
///
/// The value is stored as plain text (e.g. `"1 < 2 & 3"`)
/// and is XML-escaped automatically during [writing](crate::Epub::write).
///
/// # Examples
/// - Creating an attribute:
/// ```
/// # use rbook::ebook::element::Attribute;
/// let attribute = Attribute::new("rbook:val", " 123 ");
/// let name = attribute.name();
/// assert_eq!("rbook:val", name);
/// assert_eq!(Some("rbook"), name.prefix());
/// assert_eq!("val", name.local());
/// assert_eq!("123", attribute.value());
///
/// let into_attribute: Attribute = (" val ", "456").into();
/// assert_eq!("val", into_attribute.name());
/// assert_eq!("456", into_attribute.value());
/// ```
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self::create(name, value)
}
/// Sets the attribute value and returns the previous value.
///
/// The given `value` has its leading and trailing whitespace trimmed in-place.
///
/// The value is stored as plain text (e.g. `"1 < 2 & 3"`)
/// and is XML-escaped automatically during [writing](crate::Epub::write).
///
/// # Examples
/// - Setting an attribute value:
/// ```
/// # use rbook::ebook::element::Attribute;
/// # fn main() {
/// let mut attribute = Attribute::new("val", "123");
/// assert_eq!("123", attribute.value());
///
/// attribute.set_value("\t 456 \n");
/// assert_eq!("456", attribute.value());
/// # }
/// ```
pub fn set_value(&mut self, value: impl Into<String>) -> String {
let mut value = value.into();
value.trim_in_place();
std::mem::replace(&mut self.value.0, value)
}
/// The attribute [`value`](Self::value) in the form of mutable [`Properties`].
///
/// # Examples
/// - Modifying an attribute value as a list of properties:
/// ```
/// # use rbook::ebook::element::Attribute;
/// let mut attribute = Attribute::new("class", "title main section-1");
///
/// let properties = attribute.as_properties_mut();
/// properties.insert("new title");
/// properties.insert("section-1");
/// properties.remove("main");
///
/// assert_eq!("title section-1 new", attribute.value());
/// ```
pub fn as_properties_mut(&mut self) -> &mut Properties {
&mut self.value
}
}
impl<N: Into<String>, V: Into<String>> From<(N, V)> for Attribute {
fn from((name, value): (N, V)) -> Self {
Self::new(name.into(), value.into())
}
}
impl Attributes {
pub(crate) fn iter_key_value(&self) -> impl Iterator<Item = (&str, &str)> {
self.iter().map(|attr| (attr.name().as_str(), attr.value()))
}
/// Inserts the given attribute and returns the previous attribute with the same name, if any.
///
/// # Examples
/// - Inserting a custom attribute into a detached spine entry:
/// ```
/// # use rbook::epub::spine::DetachedEpubSpineEntry;
/// let mut detached = DetachedEpubSpineEntry::new("c1");
/// let mut entry_mut = detached.as_mut();
/// let attributes = entry_mut.attributes_mut();
///
/// // Insert a custom vendor attribute
/// attributes.insert(("this:appearance", "omit"));
///
/// assert_eq!(Some("omit"), attributes.get_value("this:appearance"));
/// ```
pub fn insert(&mut self, attribute: impl Into<Attribute>) -> Option<Attribute> {
self.0.insert(attribute.into())
}
/// Returns the mutable [`Attribute`] with the given `name` if present, otherwise [`None`].
pub fn by_name_mut(&mut self, name: &str) -> Option<&mut Attribute> {
self.0.by_key_mut(name)
}
/// Returns an iterator over **all** mutable [`Attribute`] entries.
pub fn iter_mut(&mut self) -> AttributesIterMut<'_> {
AttributesIterMut(self.0.0.iter_mut())
}
/// Removes and returns the attribute with the given name, if present.
pub fn remove(&mut self, name: &str) -> Option<Attribute> {
self.0.remove(name)
}
/// Retains only the attributes specified by the predicate.
///
/// If the closure returns `false`, the attribute is retained.
/// Otherwise, the attribute is removed.
///
/// This method operates in place and visits every attribute exactly once.
///
/// # See Also
/// - [`Self::extract_if`] to retrieve an iterator of the removed attributes.
pub fn retain(&mut self, f: impl FnMut(&Attribute) -> bool) {
self.0.retain(f);
}
/// Removes and returns only the attributes specified by the predicate.
///
/// If the closure returns `true`, the attribute is removed and yielded.
/// Otherwise, the attribute is retained.
///
/// # Drop
/// If the returned iterator is not exhausted,
/// (e.g. dropped without iterating or iteration short-circuits),
/// then the remaining attributes are retained.
///
/// Prefer [`Self::retain`] with a negated predicate if the returned iterator is not needed.
pub fn extract_if(
&mut self,
f: impl FnMut(&Attribute) -> bool,
) -> impl Iterator<Item = Attribute> {
self.0.extract_if(f)
}
/// Removes and returns all attributes.
pub fn drain(&mut self) -> impl Iterator<Item = Attribute> {
self.0.drain()
}
/// Removes all attributes.
///
/// # See Also
/// - [`Self::drain`] to retrieve an iterator of the removed attributes.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl Extend<Attribute> for Attributes {
fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
for attr in iter {
self.insert(attr);
}
}
}
impl<'a> IntoIterator for &'a mut Attributes {
type Item = &'a mut Attribute;
type IntoIter = AttributesIterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
/// An iterator over all mutable [`Attribute`] entries within [`Attributes`].
///
/// # See Also
/// - [`Attributes::iter_mut`] to create an instance of this struct.
pub struct AttributesIterMut<'a>(std::slice::IterMut<'a, Attribute>);
impl<'a> Iterator for AttributesIterMut<'a> {
// AttributeData is not returned directly here
// to allow greater flexibility in the future.
type Item = &'a mut Attribute;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}