document_tree/
attribute_types.rs

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