rbook/ebook/element/write.rs
1use crate::ebook::element::{Attribute, Attributes, Properties};
2use crate::util::str::StringExt;
3use std::iter::FusedIterator;
4
5impl Properties {
6 /// Returns [`Some`] if populated, otherwise [`None`].
7 pub(crate) fn as_option_str(&self) -> Option<&str> {
8 (!self.0.is_empty()).then_some(self.0.as_str())
9 }
10
11 /// Inserts one or more properties, returning `true` if at least one new property was added.
12 /// `false` is returned if all given properties are already present.
13 ///
14 /// The given input is split by whitespace and each property is inserted individually.
15 ///
16 /// # Examples
17 /// - Inserting multiple properties:
18 /// ```
19 /// # use rbook::ebook::element::Attribute;
20 /// # let mut attribute = Attribute::new("properties", "");
21 /// # let mut properties = attribute.as_properties_mut();
22 ///
23 /// // Inserting multiple properties
24 /// # // `assert_eq` is used over `assert` as it is more readable
25 /// assert_eq!(true, properties.insert("nav scripted"));
26 /// assert_eq!("nav scripted", properties.as_str());
27 ///
28 /// // Inserting an existing and new property
29 /// // Returns `true` as `cover` is new (`nav is skipped)
30 /// assert_eq!(true, properties.insert("nav cover"));
31 /// assert_eq!("nav scripted cover", properties.as_str());
32 ///
33 /// // Inserting an already existing property
34 /// // Returns `false` as `scripted` is already present
35 /// assert_eq!(false, properties.insert("scripted"));
36 /// assert_eq!("nav scripted cover", properties.as_str());
37 /// ```
38 pub fn insert(&mut self, properties: &str) -> bool {
39 let mut any_inserted = false;
40
41 for property in properties.split_whitespace() {
42 if !self.has_property(property) {
43 any_inserted = true;
44
45 if !self.0.is_empty() {
46 self.0.push(' ');
47 }
48 self.0.push_str(property);
49 }
50 }
51 any_inserted
52 }
53
54 /// Removes one or more properties, returning `true` if at least one property was removed.
55 /// `false` is returned if all given properties are not present.
56 ///
57 /// The given input is split by whitespace and each property is removed individually.
58 ///
59 /// # Examples
60 /// - Removing multiple properties:
61 /// ```
62 /// # use rbook::ebook::element::Attribute;
63 /// # let mut attribute = Attribute::new("properties", "nav scripted cover");
64 /// # let mut properties = attribute.as_properties_mut();
65 ///
66 /// // Initial state
67 /// assert_eq!("nav scripted cover", properties.as_str());
68 ///
69 /// assert_eq!(false, properties.remove("script"));
70 /// assert_eq!("nav scripted cover", properties.as_str());
71 ///
72 /// // Removing an existing property
73 /// // Returns `true` as the property was present and now removed
74 /// assert_eq!(true, properties.remove("scripted"));
75 /// assert_eq!("nav cover", properties.as_str());
76 ///
77 /// // Removing multiple properties
78 /// assert_eq!(true, properties.remove("nav scripted cover"));
79 /// assert!(properties.is_empty());
80 /// ```
81 pub fn remove(&mut self, properties: &str) -> bool {
82 if self.is_empty() {
83 return false;
84 }
85 let mut any_removed = false;
86
87 for property in properties.split_whitespace() {
88 // Check if the property to remove exists
89 // - In most, if not nearly all cases, `self` is empty or contains one property
90 let match_pos = self.0.match_indices(property).find(|&(start, _)| {
91 let end = start + property.len();
92 // Check spacing to ensure a proper space-separated match
93 let leading_ok = start == 0 || self.0.as_bytes()[start - 1] == b' ';
94 let trailing_ok = end == self.0.len() || self.0.as_bytes()[end] == b' ';
95 leading_ok && trailing_ok
96 });
97
98 if let Some((start, _)) = match_pos {
99 let end = start + property.len();
100 any_removed = true;
101
102 if end < self.0.len() {
103 // Has a trailing space: "xyz "
104 self.0.drain(start..=end);
105 } else if start > 0 {
106 // Has a leading space: " xyz"
107 self.0.drain(start - 1..end);
108 } else {
109 // The only property
110 self.0.clear();
111 }
112 }
113 }
114 any_removed
115 }
116
117 /// Removes all properties.
118 pub fn clear(&mut self) {
119 self.0.clear();
120 }
121}
122
123impl Attribute {
124 /// Creates a new attribute with the given [`name`](Self::name) and [`value`](Self::value).
125 ///
126 /// The given input has their leading and trailing whitespace trimmed in-place.
127 ///
128 /// The value is stored as plain text (e.g. `"1 < 2 & 3"`)
129 /// and is XML-escaped automatically during [writing](crate::Epub::write).
130 ///
131 /// # Examples
132 /// - Creating an attribute:
133 /// ```
134 /// # use rbook::ebook::element::Attribute;
135 /// let attribute = Attribute::new("rbook:val", " 123 ");
136 /// let name = attribute.name();
137 /// assert_eq!("rbook:val", name);
138 /// assert_eq!(Some("rbook"), name.prefix());
139 /// assert_eq!("val", name.local());
140 /// assert_eq!("123", attribute.value());
141 ///
142 /// let into_attribute: Attribute = (" val ", "456").into();
143 /// assert_eq!("val", into_attribute.name());
144 /// assert_eq!("456", into_attribute.value());
145 /// ```
146 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
147 Self::create(name, value)
148 }
149
150 /// Sets the attribute value and returns the previous value.
151 ///
152 /// The given `value` has its leading and trailing whitespace trimmed in-place.
153 ///
154 /// The value is stored as plain text (e.g. `"1 < 2 & 3"`)
155 /// and is XML-escaped automatically during [writing](crate::Epub::write).
156 ///
157 /// # Examples
158 /// - Setting an attribute value:
159 /// ```
160 /// # use rbook::ebook::element::Attribute;
161 /// # fn main() {
162 /// let mut attribute = Attribute::new("val", "123");
163 /// assert_eq!("123", attribute.value());
164 ///
165 /// attribute.set_value("\t 456 \n");
166 /// assert_eq!("456", attribute.value());
167 /// # }
168 /// ```
169 pub fn set_value(&mut self, value: impl Into<String>) -> String {
170 let mut value = value.into();
171 value.trim_in_place();
172 std::mem::replace(&mut self.value.0, value)
173 }
174
175 /// The attribute [`value`](Self::value) in the form of mutable [`Properties`].
176 ///
177 /// # Examples
178 /// - Modifying an attribute value as a list of properties:
179 /// ```
180 /// # use rbook::ebook::element::Attribute;
181 /// let mut attribute = Attribute::new("class", "title main section-1");
182 ///
183 /// let properties = attribute.as_properties_mut();
184 /// properties.insert("new title");
185 /// properties.insert("section-1");
186 /// properties.remove("main");
187 ///
188 /// assert_eq!("title section-1 new", attribute.value());
189 /// ```
190 pub fn as_properties_mut(&mut self) -> &mut Properties {
191 &mut self.value
192 }
193}
194
195impl<N: Into<String>, V: Into<String>> From<(N, V)> for Attribute {
196 fn from((name, value): (N, V)) -> Self {
197 Self::new(name.into(), value.into())
198 }
199}
200
201impl Attributes {
202 pub(crate) fn iter_key_value(&self) -> impl Iterator<Item = (&str, &str)> {
203 self.iter().map(|attr| (attr.name().as_str(), attr.value()))
204 }
205
206 /// Inserts the given attribute and returns the previous attribute with the same name, if any.
207 ///
208 /// # Examples
209 /// - Inserting a custom attribute into a detached spine entry:
210 /// ```
211 /// # use rbook::epub::spine::DetachedEpubSpineEntry;
212 /// let mut detached = DetachedEpubSpineEntry::new("c1");
213 /// let mut entry_mut = detached.as_mut();
214 /// let attributes = entry_mut.attributes_mut();
215 ///
216 /// // Insert a custom vendor attribute
217 /// attributes.insert(("this:appearance", "omit"));
218 ///
219 /// assert_eq!(Some("omit"), attributes.get_value("this:appearance"));
220 /// ```
221 pub fn insert(&mut self, attribute: impl Into<Attribute>) -> Option<Attribute> {
222 self.0.insert(attribute.into())
223 }
224
225 /// Returns the mutable [`Attribute`] with the given `name` if present, otherwise [`None`].
226 pub fn by_name_mut(&mut self, name: &str) -> Option<&mut Attribute> {
227 self.0.by_key_mut(name)
228 }
229
230 /// Returns an iterator over **all** mutable [`Attribute`] entries.
231 pub fn iter_mut(&mut self) -> AttributesIterMut<'_> {
232 AttributesIterMut(self.0.0.iter_mut())
233 }
234
235 /// Removes and returns the attribute with the given name, if present.
236 pub fn remove(&mut self, name: &str) -> Option<Attribute> {
237 self.0.remove(name)
238 }
239
240 /// Retains only the attributes specified by the predicate.
241 ///
242 /// If the closure returns `false`, the attribute is retained.
243 /// Otherwise, the attribute is removed.
244 ///
245 /// This method operates in place and visits every attribute exactly once.
246 ///
247 /// # See Also
248 /// - [`Self::extract_if`] to get an iterator of the removed attributes.
249 pub fn retain(&mut self, f: impl FnMut(&Attribute) -> bool) {
250 self.0.retain(f);
251 }
252
253 /// Removes and returns only the attributes specified by the predicate.
254 ///
255 /// If the closure returns `true`, the attribute is removed and yielded.
256 /// Otherwise, the attribute is retained.
257 ///
258 /// # Drop
259 /// If the returned iterator is not exhausted,
260 /// (e.g. dropped without iterating or iteration short-circuits),
261 /// then the remaining attributes are retained.
262 ///
263 /// Prefer [`Self::retain`] with a negated predicate if the returned iterator is not needed.
264 pub fn extract_if(
265 &mut self,
266 f: impl FnMut(&Attribute) -> bool,
267 ) -> impl Iterator<Item = Attribute> {
268 self.0.extract_if(f)
269 }
270
271 /// Removes and returns all attributes.
272 pub fn drain(&mut self) -> impl Iterator<Item = Attribute> {
273 self.0.drain()
274 }
275
276 /// Removes all attributes.
277 ///
278 /// # See Also
279 /// - [`Self::drain`] to get an iterator of the removed attributes.
280 pub fn clear(&mut self) {
281 self.0.clear();
282 }
283}
284
285impl Extend<Attribute> for Attributes {
286 fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
287 for attr in iter {
288 self.insert(attr);
289 }
290 }
291}
292
293impl<'a> IntoIterator for &'a mut Attributes {
294 type Item = &'a mut Attribute;
295 type IntoIter = AttributesIterMut<'a>;
296
297 fn into_iter(self) -> Self::IntoIter {
298 self.iter_mut()
299 }
300}
301
302/// An iterator over all mutable [`Attribute`] entries within [`Attributes`].
303///
304/// # See Also
305/// - [`Attributes::iter_mut`] to create an instance of this struct.
306pub struct AttributesIterMut<'a>(std::slice::IterMut<'a, Attribute>);
307
308impl<'a> Iterator for AttributesIterMut<'a> {
309 // AttributeData is not returned directly here
310 // to allow greater flexibility in the future.
311 type Item = &'a mut Attribute;
312
313 fn next(&mut self) -> Option<Self::Item> {
314 self.0.next()
315 }
316
317 fn size_hint(&self) -> (usize, Option<usize>) {
318 self.0.size_hint()
319 }
320}
321
322impl DoubleEndedIterator for AttributesIterMut<'_> {
323 fn next_back(&mut self) -> Option<Self::Item> {
324 self.0.next_back()
325 }
326}
327
328impl ExactSizeIterator for AttributesIterMut<'_> {
329 fn len(&self) -> usize {
330 self.0.len()
331 }
332}
333
334impl FusedIterator for AttributesIterMut<'_> {}