document_tree/
attribute_types.rs

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