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}