spreadsheet_ods/
condition.rs

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