spreadsheet_ods/
condition.rs

1//! Defines conditional expressions that are used for cell-validation and
2//! conditional styles via style-maps.
3use get_size2::GetSize;
4use std::fmt::{Display, Formatter};
5
6use crate::CellRange;
7
8/// A value that is used in a comparison.
9#[derive(Clone, Debug)]
10pub struct Value {
11    val: String,
12}
13
14fn quote(val: &str) -> String {
15    let mut buf = String::new();
16    buf.push('"');
17    for c in val.chars() {
18        if c == '"' {
19            buf.push('"');
20            buf.push('"');
21        } else {
22            buf.push(c);
23        }
24    }
25    buf.push('"');
26    buf
27}
28
29impl Display for Value {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        write!(f, "{}", self.val)
32    }
33}
34
35impl From<&str> for Value {
36    fn from(s: &str) -> Self {
37        Value { val: quote(s) }
38    }
39}
40
41impl From<&&str> for Value {
42    fn from(s: &&str) -> Self {
43        Value { val: quote(s) }
44    }
45}
46
47impl From<String> for Value {
48    fn from(s: String) -> Self {
49        Value {
50            val: quote(s.as_str()),
51        }
52    }
53}
54
55impl From<&String> for Value {
56    fn from(s: &String) -> Self {
57        Value {
58            val: quote(s.as_str()),
59        }
60    }
61}
62
63macro_rules! from_x_conditionvalue {
64    ($int:ty) => {
65        impl From<$int> for Value {
66            fn from(v: $int) -> Self {
67                Value { val: v.to_string() }
68            }
69        }
70
71        impl From<&$int> for Value {
72            fn from(v: &$int) -> Self {
73                Value { val: v.to_string() }
74            }
75        }
76    };
77}
78
79from_x_conditionvalue!(i8);
80from_x_conditionvalue!(i16);
81from_x_conditionvalue!(i32);
82from_x_conditionvalue!(i64);
83from_x_conditionvalue!(u8);
84from_x_conditionvalue!(u16);
85from_x_conditionvalue!(u32);
86from_x_conditionvalue!(u64);
87from_x_conditionvalue!(f32);
88from_x_conditionvalue!(f64);
89from_x_conditionvalue!(bool);
90
91/// Defines a condition that compares the cell-content with a value.
92#[derive(Default, Clone, Debug, GetSize)]
93pub struct ValueCondition {
94    cond: String,
95}
96
97impl Display for ValueCondition {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{}", self.cond)
100    }
101}
102
103impl ValueCondition {
104    /// Creates a value condition from a string that was read.
105    pub(crate) fn new<S: Into<String>>(str: S) -> Self {
106        Self { cond: str.into() }
107    }
108
109    /// Compares the cell-content with a value.
110    pub fn value_eq<V: Into<Value>>(value: V) -> ValueCondition {
111        let mut buf = String::new();
112        buf.push_str("value()=");
113        buf.push_str(value.into().to_string().as_str());
114        ValueCondition { cond: buf }
115    }
116
117    /// Compares the cell-content with a value.
118    pub fn value_ne<V: Into<Value>>(value: V) -> ValueCondition {
119        let mut buf = String::new();
120        buf.push_str("value()!=");
121        buf.push_str(value.into().to_string().as_str());
122        ValueCondition { cond: buf }
123    }
124
125    /// Compares the cell-content with a value.
126    pub fn value_lt<V: Into<Value>>(value: V) -> ValueCondition {
127        let mut buf = String::new();
128        buf.push_str("value()<");
129        buf.push_str(value.into().to_string().as_str());
130        ValueCondition { cond: buf }
131    }
132
133    /// Compares the cell-content with a value.
134    pub fn value_gt<V: Into<Value>>(value: V) -> ValueCondition {
135        let mut buf = String::new();
136        buf.push_str("value()>");
137        buf.push_str(value.into().to_string().as_str());
138        ValueCondition { cond: buf }
139    }
140
141    /// Compares the cell-content with a value.
142    pub fn value_le<V: Into<Value>>(value: V) -> ValueCondition {
143        let mut buf = String::new();
144        buf.push_str("value()<=");
145        buf.push_str(value.into().to_string().as_str());
146        ValueCondition { cond: buf }
147    }
148
149    /// Compares the cell-content with a value.
150    pub fn value_ge<V: Into<Value>>(value: V) -> ValueCondition {
151        let mut buf = String::new();
152        buf.push_str("value()>=");
153        buf.push_str(value.into().to_string().as_str());
154        ValueCondition { cond: buf }
155    }
156}
157
158/// Defines a condition for a cell-validation.
159#[derive(Default, Clone, Debug, GetSize)]
160pub struct Condition {
161    cond: String,
162}
163
164impl Display for Condition {
165    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166        write!(f, "{}", self.cond)
167    }
168}
169
170impl Condition {
171    /// Creates a condition from a read string.
172    pub(crate) fn new<S: Into<String>>(str: S) -> Self {
173        Self { cond: str.into() }
174    }
175
176    /// Compares the cell-content with a value.
177    pub fn content_eq<V: Into<Value>>(value: V) -> Condition {
178        let mut buf = String::new();
179        buf.push_str("cell-content()=");
180        buf.push_str(value.into().to_string().as_str());
181        Condition { cond: buf }
182    }
183
184    /// Compares the cell-content with a value.
185    pub fn content_ne<V: Into<Value>>(value: V) -> Condition {
186        let mut buf = String::new();
187        buf.push_str("cell-content()!=");
188        buf.push_str(value.into().to_string().as_str());
189        Condition { cond: buf }
190    }
191
192    /// Compares the cell-content with a value.
193    pub fn content_lt<V: Into<Value>>(value: V) -> Condition {
194        let mut buf = String::new();
195        buf.push_str("cell-content()<");
196        buf.push_str(value.into().to_string().as_str());
197        Condition { cond: buf }
198    }
199
200    /// Compares the cell-content with a value.
201    pub fn content_gt<V: Into<Value>>(value: V) -> Condition {
202        let mut buf = String::new();
203        buf.push_str("cell-content()>");
204        buf.push_str(value.into().to_string().as_str());
205        Condition { cond: buf }
206    }
207
208    /// Compares the cell-content with a value.
209    pub fn content_le<V: Into<Value>>(value: V) -> Condition {
210        let mut buf = String::new();
211        buf.push_str("cell-content()<=");
212        buf.push_str(value.into().to_string().as_str());
213        Condition { cond: buf }
214    }
215
216    /// Compares the cell-content with a value.
217    pub fn content_ge<V: Into<Value>>(value: V) -> Condition {
218        let mut buf = String::new();
219        buf.push_str("cell-content()>=");
220        buf.push_str(value.into().to_string().as_str());
221        Condition { cond: buf }
222    }
223
224    /// Compares the content length to a value.
225    pub fn content_text_length_eq(len: u32) -> Condition {
226        let mut buf = String::new();
227        buf.push_str("cell-content-text-length()=");
228        buf.push_str(len.to_string().as_str());
229        Condition { cond: buf }
230    }
231
232    /// Compares the content length to a value.
233    pub fn content_text_length_ne(len: u32) -> Condition {
234        let mut buf = String::new();
235        buf.push_str("cell-content-text-length()!=");
236        buf.push_str(len.to_string().as_str());
237        Condition { cond: buf }
238    }
239
240    /// Compares the content length to a value.
241    pub fn content_text_length_lt(len: u32) -> Condition {
242        let mut buf = String::new();
243        buf.push_str("cell-content-text-length()<");
244        buf.push_str(len.to_string().as_str());
245        Condition { cond: buf }
246    }
247
248    /// Compares the content length to a value.
249    pub fn content_text_length_gt(len: u32) -> Condition {
250        let mut buf = String::new();
251        buf.push_str("cell-content-text-length()>");
252        buf.push_str(len.to_string().as_str());
253        Condition { cond: buf }
254    }
255
256    /// Compares the content length to a value.
257    pub fn content_text_length_le(len: u32) -> Condition {
258        let mut buf = String::new();
259        buf.push_str("cell-content-text-length()<=");
260        buf.push_str(len.to_string().as_str());
261        Condition { cond: buf }
262    }
263
264    /// Compares the content length to a value.
265    pub fn content_text_length_ge(len: u32) -> Condition {
266        let mut buf = String::new();
267        buf.push_str("cell-content-text-length()>=");
268        buf.push_str(len.to_string().as_str());
269        Condition { cond: buf }
270    }
271
272    /// Compares the content length to a range of values.
273    pub fn content_text_length_is_between(from: u32, to: u32) -> Condition {
274        let mut buf = String::new();
275        buf.push_str("cell-content-text-length-is-between(");
276        buf.push_str(from.to_string().as_str());
277        buf.push_str(", ");
278        buf.push_str(to.to_string().as_str());
279        buf.push(')');
280        Condition { cond: buf }
281    }
282
283    /// Range check.
284    pub fn content_text_length_is_not_between(from: u32, to: u32) -> Condition {
285        let mut buf = String::new();
286        buf.push_str("cell-content-text-length-is-not-between(");
287        buf.push_str(from.to_string().as_str());
288        buf.push_str(", ");
289        buf.push_str(to.to_string().as_str());
290        buf.push(')');
291        Condition { cond: buf }
292    }
293
294    /// The value is in this list.
295    pub fn content_is_in_list<'a, V>(list: &'a [V]) -> Condition
296    where
297        Value: From<&'a V>,
298    {
299        let mut buf = String::new();
300        buf.push_str("cell-content-is-in-list(");
301
302        let mut sep = false;
303        for v in list {
304            if sep {
305                buf.push(';');
306            }
307            let vv: Value = v.into();
308            let vstr = vv.to_string();
309            if !vstr.starts_with('"') {
310                buf.push('"');
311            }
312            buf.push_str(vstr.as_str());
313            if !vstr.starts_with('"') {
314                buf.push('"');
315            }
316            sep = true;
317        }
318
319        buf.push(')');
320        Condition { cond: buf }
321    }
322
323    /// The choices are made up from the values in the cellrange.
324    ///
325    /// Warning
326    /// For the cellrange the distance to the base-cell is calculated,
327    /// and this result is added to the cell this condition is applied to.
328    /// You may want to use an absolute cell-reference to avoid this..
329    ///
330    pub fn content_is_in_cellrange(range: CellRange) -> Condition {
331        let mut buf = String::new();
332        buf.push_str("cell-content-is-in-list(");
333        buf.push_str(range.to_formula().as_str());
334        buf.push(')');
335        Condition { cond: buf }
336    }
337
338    /// Content is a date and matches a comparison.
339    /// The date is an integer value that amounts to the days since
340    /// 30.12.1899.
341    pub fn content_is_date_and(vcond: Condition) -> Condition {
342        let mut buf = String::new();
343        buf.push_str("cell-content-is-date()");
344        buf.push_str(" and ");
345        buf.push_str(vcond.to_string().as_str());
346        Condition { cond: buf }
347    }
348
349    /// Content is a time and matches a comparison.
350    /// The time is given as a fraction of a day.
351    pub fn content_is_time_and(vcond: Condition) -> Condition {
352        let mut buf = String::new();
353        buf.push_str("cell-content-is-time()");
354        buf.push_str(" and ");
355        buf.push_str(vcond.to_string().as_str());
356        Condition { cond: buf }
357    }
358
359    /// Content is a number and matches the comparison.
360    pub fn content_is_decimal_number_and(vcond: Condition) -> Condition {
361        let mut buf = String::new();
362        buf.push_str("cell-content-is-decimal-number()");
363        buf.push_str(" and ");
364        buf.push_str(vcond.to_string().as_str());
365        Condition { cond: buf }
366    }
367
368    /// Content is a whole number and matches the comparison.
369    pub fn content_is_whole_number_and(vcond: Condition) -> Condition {
370        let mut buf = String::new();
371        buf.push_str("cell-content-is-whole-number()");
372        buf.push_str(" and ");
373        buf.push_str(vcond.to_string().as_str());
374        Condition { cond: buf }
375    }
376
377    /// Evaluates a formula.
378    pub fn is_true_formula<S: AsRef<str>>(formula: S) -> Condition {
379        let mut buf = String::new();
380        buf.push_str("is-true-formula(");
381        buf.push_str(formula.as_ref());
382        buf.push(')');
383        Condition { cond: buf }
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use crate::condition::{Condition, ValueCondition};
390    use crate::CellRange;
391
392    #[test]
393    fn test_valuecondition() {
394        let c = ValueCondition::value_eq(5);
395        assert_eq!(c.to_string(), "value()=5");
396        let c = ValueCondition::value_ne(5);
397        assert_eq!(c.to_string(), "value()!=5");
398        let c = ValueCondition::value_lt(5);
399        assert_eq!(c.to_string(), "value()<5");
400        let c = ValueCondition::value_gt(5);
401        assert_eq!(c.to_string(), "value()>5");
402        let c = ValueCondition::value_le(5);
403        assert_eq!(c.to_string(), "value()<=5");
404        let c = ValueCondition::value_ge(5);
405        assert_eq!(c.to_string(), "value()>=5");
406    }
407
408    #[test]
409    fn test_condition() {
410        let c = Condition::content_text_length_eq(7);
411        assert_eq!(c.to_string(), "cell-content-text-length()=7");
412        let c = Condition::content_text_length_is_between(5, 7);
413        assert_eq!(c.to_string(), "cell-content-text-length-is-between(5, 7)");
414        let c = Condition::content_text_length_is_not_between(5, 7);
415        assert_eq!(
416            c.to_string(),
417            "cell-content-text-length-is-not-between(5, 7)"
418        );
419        let c = Condition::content_is_in_list(&[1, 2, 3, 4, 5]);
420        assert_eq!(
421            c.to_string(),
422            r#"cell-content-is-in-list("1";"2";"3";"4";"5")"#
423        );
424        let c = Condition::content_is_in_list(&["a", "b", "c"]);
425        assert_eq!(c.to_string(), r#"cell-content-is-in-list("a";"b";"c")"#);
426        let c = Condition::content_is_in_cellrange(CellRange::remote("other", 0, 0, 10, 0));
427        assert_eq!(c.to_string(), "cell-content-is-in-list([other.A1:.A11])");
428
429        let c = Condition::content_is_date_and(Condition::content_eq(0));
430        assert_eq!(c.to_string(), "cell-content-is-date() and cell-content()=0");
431        let c = Condition::content_is_time_and(Condition::content_eq(0));
432        assert_eq!(c.to_string(), "cell-content-is-time() and cell-content()=0");
433        let c = Condition::content_is_decimal_number_and(Condition::content_eq(0));
434        assert_eq!(
435            c.to_string(),
436            "cell-content-is-decimal-number() and cell-content()=0"
437        );
438        let c = Condition::content_is_whole_number_and(Condition::content_eq(0));
439        assert_eq!(
440            c.to_string(),
441            "cell-content-is-whole-number() and cell-content()=0"
442        );
443
444        let c = Condition::is_true_formula("formula");
445        assert_eq!(c.to_string(), "is-true-formula(formula)");
446    }
447}