document_tree/
attribute_types.rs

1use std::str::FromStr;
2
3use anyhow::{Error, bail, format_err};
4use regex::Regex;
5use serde_derive::Serialize;
6
7use crate::url::Url;
8
9#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
10pub enum EnumeratedListType {
11    Arabic,
12    LowerAlpha,
13    UpperAlpha,
14    LowerRoman,
15    UpperRoman,
16}
17
18#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
19pub enum AutoFootnoteType {
20    Number,
21    Symbol,
22}
23
24#[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
25pub enum FixedSpace {
26    Default,
27    // yes, default really is not “Default”
28    #[default]
29    Preserve,
30}
31
32#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
33pub enum AlignH {
34    Left,
35    Center,
36    Right,
37}
38#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
39pub enum AlignHV {
40    Top,
41    Middle,
42    Bottom,
43    Left,
44    Center,
45    Right,
46}
47#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
48pub enum AlignV {
49    Top,
50    Middle,
51    Bottom,
52}
53
54#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
55pub enum TableAlignH {
56    Left,
57    Right,
58    Center,
59    Justify,
60    Char,
61}
62#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone, Copy)]
63pub enum TableBorder {
64    Top,
65    Bottom,
66    TopBottom,
67    All,
68    Sides,
69    None,
70}
71
72#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)]
73pub struct ID(pub String);
74#[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)]
75pub struct NameToken(pub String);
76
77// The table DTD has the cols attribute of tgroup as required, but having
78// TableGroupCols not implement Default would leave no possible implementation
79// for TableGroup::with_children.
80#[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)]
81pub struct TableGroupCols(pub usize);
82
83// no eq for f64
84#[derive(Debug, PartialEq, Serialize, Clone)]
85pub enum Measure {
86    // https://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#length-units
87    Em(f64),
88    Ex(f64),
89    Mm(f64),
90    Cm(f64),
91    In(f64),
92    Px(f64),
93    Pt(f64),
94    Pc(f64),
95}
96
97impl FromStr for AlignHV {
98    type Err = Error;
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        use self::AlignHV as A;
101        Ok(match s {
102            "top" => A::Top,
103            "middle" => A::Middle,
104            "bottom" => A::Bottom,
105            "left" => A::Left,
106            "center" => A::Center,
107            "right" => A::Right,
108            s => bail!("Invalid Alignment {}", s),
109        })
110    }
111}
112
113impl From<&str> for ID {
114    fn from(s: &str) -> Self {
115        ID(s.to_owned().replace(' ', "-"))
116    }
117}
118
119impl From<&str> for NameToken {
120    fn from(s: &str) -> Self {
121        NameToken(s.to_owned())
122    }
123}
124
125impl FromStr for Measure {
126    type Err = Error;
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        use self::Measure as M;
129        let re =
130            Regex::new(r"(?P<float>\d+\.\d*|\.?\d+)\s*(?P<unit>em|ex|mm|cm|in|px|pt|pc)").unwrap();
131        let caps: regex::Captures = re
132            .captures(s)
133            .ok_or_else(|| format_err!("Invalid measure"))?;
134        let value: f64 = caps["float"].parse()?;
135        Ok(match &caps["unit"] {
136            "em" => M::Em(value),
137            "ex" => M::Ex(value),
138            "mm" => M::Mm(value),
139            "cm" => M::Cm(value),
140            "in" => M::In(value),
141            "px" => M::Px(value),
142            "pt" => M::Pt(value),
143            "pc" => M::Pc(value),
144            _ => unreachable!(),
145        })
146    }
147}
148
149#[cfg(test)]
150mod parse_tests {
151    use super::*;
152
153    #[test]
154    fn measure() {
155        let _a: Measure = "1.5em".parse().unwrap();
156        let _b: Measure = "20 mm".parse().unwrap();
157        let _c: Measure = ".5in".parse().unwrap();
158        let _d: Measure = "1.pc".parse().unwrap();
159    }
160}
161
162pub(crate) trait CanBeEmpty {
163    fn is_empty(&self) -> bool;
164}
165
166/* Specialization necessary
167impl<T> CanBeEmpty for T {
168    fn is_empty(&self) -> bool { false }
169}
170*/
171macro_rules! impl_cannot_be_empty {
172    ($t:ty) => {
173        impl CanBeEmpty for $t {
174            fn is_empty(&self) -> bool { false }
175        }
176    };
177    ($t:ty, $($ts:ty),*) => {
178        impl_cannot_be_empty!($t);
179        impl_cannot_be_empty!($($ts),*);
180    };
181}
182impl_cannot_be_empty!(Url);
183impl_cannot_be_empty!(TableGroupCols);
184
185impl<T> CanBeEmpty for Option<T> {
186    fn is_empty(&self) -> bool {
187        self.is_none()
188    }
189}
190
191impl<T> CanBeEmpty for Vec<T> {
192    fn is_empty(&self) -> bool {
193        self.is_empty()
194    }
195}
196
197impl CanBeEmpty for bool {
198    fn is_empty(&self) -> bool {
199        !self
200    }
201}
202
203impl CanBeEmpty for FixedSpace {
204    fn is_empty(&self) -> bool {
205        self == &FixedSpace::default()
206    }
207}