Skip to main content

leyline/
html.rs

1mod assign;
2mod build;
3mod entity;
4mod fieldset;
5mod page;
6mod scan;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9#[non_exhaustive]
10pub enum FormMethod {
11    #[default]
12    Get,
13    Post,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17#[non_exhaustive]
18pub enum FormEnctype {
19    #[default]
20    UrlEncoded,
21    Multipart,
22    TextPlain,
23}
24
25impl FormEnctype {
26    const ALL: [FormEnctype; 3] = [Self::UrlEncoded, Self::Multipart, Self::TextPlain];
27
28    #[must_use]
29    pub fn as_str(self) -> &'static str {
30        match self {
31            Self::UrlEncoded => "application/x-www-form-urlencoded",
32            Self::Multipart => "multipart/form-data",
33            Self::TextPlain => "text/plain",
34        }
35    }
36
37    fn parse(value: &str) -> Self {
38        let value = value.trim();
39        Self::ALL
40            .into_iter()
41            .find(|enctype| enctype.as_str().eq_ignore_ascii_case(value))
42            .unwrap_or_default()
43    }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Default)]
47#[non_exhaustive]
48pub struct Form {
49    action: String,
50    method: FormMethod,
51    enctype: FormEnctype,
52    base: Option<String>,
53    id: Option<String>,
54    name: Option<String>,
55    fields: Vec<(String, String)>,
56    buttons: Vec<Button>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60struct Button {
61    name: String,
62    value: String,
63    image: bool,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67#[non_exhaustive]
68pub struct Anchor {
69    href: String,
70    text: String,
71    rel: Option<String>,
72}
73
74#[must_use]
75pub fn meta(document: &str, name: &str) -> Option<String> {
76    page::meta(document, name)
77}
78
79#[must_use]
80pub fn links(document: &str) -> Vec<Anchor> {
81    page::links(document)
82}
83
84impl Anchor {
85    #[must_use]
86    pub fn href(&self) -> &str {
87        &self.href
88    }
89
90    #[must_use]
91    pub fn text(&self) -> &str {
92        &self.text
93    }
94
95    #[must_use]
96    pub fn rel(&self) -> Option<&str> {
97        self.rel.as_deref()
98    }
99}
100
101#[must_use]
102pub fn forms(document: &str) -> Vec<Form> {
103    build::forms(document)
104}
105
106impl Form {
107    #[must_use]
108    pub fn find(document: &str, selector: &str) -> Option<Form> {
109        forms(document).into_iter().find(|form| {
110            form.id.as_deref() == Some(selector) || form.name.as_deref() == Some(selector)
111        })
112    }
113
114    #[must_use]
115    pub fn action(&self) -> &str {
116        &self.action
117    }
118
119    #[must_use]
120    pub fn method(&self) -> FormMethod {
121        self.method
122    }
123
124    #[must_use]
125    pub fn enctype(&self) -> FormEnctype {
126        self.enctype
127    }
128
129    #[must_use]
130    pub fn base(&self) -> Option<&str> {
131        self.base.as_deref()
132    }
133
134    #[must_use]
135    pub fn id(&self) -> Option<&str> {
136        self.id.as_deref()
137    }
138
139    #[must_use]
140    pub fn name(&self) -> Option<&str> {
141        self.name.as_deref()
142    }
143
144    #[must_use]
145    pub fn fields(&self) -> &[(String, String)] {
146        &self.fields
147    }
148
149    #[must_use]
150    pub fn field(&self, name: &str) -> Option<&str> {
151        self.fields
152            .iter()
153            .find(|(key, _)| key == name)
154            .map(|(_, value)| value.as_str())
155    }
156
157    pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
158        let name = name.into();
159        let value = value.into();
160        match self.fields.iter().position(|(key, _)| *key == name) {
161            Some(first) => {
162                self.fields[first].1 = value;
163                let mut index = 0;
164                self.fields.retain(|(key, _)| {
165                    let keep = index <= first || *key != name;
166                    index += 1;
167                    keep
168                });
169            }
170            None => self.fields.push((name, value)),
171        }
172        self
173    }
174
175    #[must_use]
176    pub fn buttons(&self) -> Vec<&str> {
177        self.buttons.iter().map(|b| b.name.as_str()).collect()
178    }
179
180    pub fn press(&mut self, button: &str) -> bool {
181        let Some(found) = self.buttons.iter().find(|b| b.name == button).cloned() else {
182            return false;
183        };
184        if found.image {
185            self.fields
186                .push((format!("{}.x", found.name), "0".to_owned()));
187            self.fields
188                .push((format!("{}.y", found.name), "0".to_owned()));
189        } else {
190            self.fields.push((found.name, found.value));
191        }
192        true
193    }
194}