es_htmlform/
types.rs

1//! Enums representing HTML elements, attributes and values.
2//!
3//! These should be complete according to the HTML specifications, all
4//! elements and all element-specific attributes should be represented,
5//! in such a way that your form structure and values will always be
6//! valid (assuming that you use the `HtmlForm` builder methods to set
7//! up the form, else form structure validity is not checked). Note that
8//! the values of the `Constraint` enum (mostly) represent HTML attributes
9//! that cause client-side validation, when used server-side validation
10//! is also performed. One exception is `Constraint::Func()`, which is
11//! used for per-element server-side validation and is not serialized
12//! as client-side attribute. Attributes in the `Attr` enum do not cause
13//! value validation.
14
15use std::fmt;
16use std::ops::Deref;
17
18use regex::Regex;
19
20use crate::error::ValidationError;
21use crate::value::Value;
22
23// XXX use lazy_static for the regs
24fn validate_date(date: &str) -> bool {
25    let reg_date = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
26    reg_date.is_match(date)
27}
28
29fn validate_datetime(datetime: &str) -> bool {
30    let reg_datetime = Regex::new(
31        r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$").unwrap();
32    reg_datetime.is_match(datetime)
33}
34
35fn validate_time(time: &str) -> bool {
36    let reg_time = Regex::new(r"^\d{2}:\d{2}$").unwrap();
37    reg_time.is_match(time)
38}
39
40fn validate_email(email: &str) -> bool {
41    // rather naive reg, but it should catch most common issues and should
42    // not lead to false negatives
43    let reg_email = Regex::new(r"^\S+@\w[-\.\w]+\.\w{2,}$").unwrap();
44    reg_email.is_match(email)
45}
46
47fn validate_url(url: &str) -> bool {
48    // rather naive reg, but it should catch most common issues and should
49    // not lead to false negatives - proper url checking is near impossible
50    // using regexps, so I don't want to go there...
51    let reg_url = Regex::new(r"^\w+\:\/\/\w[-\.\w]+(\/\S*)?$").unwrap();
52    reg_url.is_match(url)
53}
54
55/// Form methods, correspond to `method` attribute values (note that these
56/// do not correspond to HTTP methods per se, see specs).
57#[derive(Debug)]
58pub enum Method {
59    Get,
60    Post,
61    // The `dialog` method can be used for forms in a pop-up, closes
62    // the pop-up on submit (see specs).
63    Dialog,
64}
65
66impl Method {
67    pub fn attrvalue(&self) -> String {
68        match &self {
69            Method::Get => "get",
70            Method::Post => "post",
71            Method::Dialog => "dialog",
72        }.to_string()
73    }
74}
75
76// XXX Note that we miss some elements, we may want to add support for
77// fieldset w. legend (contains a list of fields, should be traversed on
78// validation), optgroup (how? don't like the thought of nested choices?)
79// and output (simple add with no validation, it seems?)
80/// Form element types, each of which represent a different HTML element.
81#[derive(Debug)]
82pub enum Element {
83    Input(InputType),
84    Textarea,
85    Select(SelectType),
86    Button(ButtonType),
87}
88
89impl Element {
90    /// Validate a value for an element type.
91    pub fn validate(&self, formvalue: &Value)
92            -> Result<(), ValidationError> {
93        match self {
94            Element::Input(InputType::Date) => {
95                if !validate_date(&formvalue.to_string()) {
96                    Err(ValidationError::new(
97                        &format!("Invalid date {}.", formvalue.as_string())))
98                } else {
99                    Ok(())
100                }
101            },
102            Element::Input(InputType::Time) => {
103                if !validate_time(&formvalue.to_string()) {
104                    Err(ValidationError::new(
105                        &format!("Invalid time {}.", formvalue.as_string())))
106                } else {
107                    Ok(())
108                }
109            },
110            Element::Input(InputType::DateTime) => {
111                if !validate_datetime(&formvalue.to_string()) {
112                    Err(ValidationError::new(
113                        &format!(
114                            "Invalid datetime {}.", formvalue.as_string())))
115                } else {
116                    Ok(())
117                }
118            },
119            Element::Input(InputType::Email) => {
120                if !validate_email(&formvalue.to_string()) {
121                    Err(ValidationError::new(
122                        &format!(
123                            "Invalid email address {}.",
124                            formvalue.as_string())))
125                } else {
126                    Ok(())
127                }
128            },
129            Element::Input(InputType::Url) => {
130                if !validate_url(&formvalue.to_string()) {
131                    Err(ValidationError::new(
132                        &format!("Invalid url {}", formvalue.as_string())))
133                } else {
134                    Ok(())
135                }
136            },
137            _ => Ok(())
138        }
139    }
140
141    /// Return the element's name (nodeName), used by `HtmlForm` to fill
142    /// its `element` attribute.
143    pub fn element_name(&self) -> &'static str {
144        match &self {
145            Element::Input(_) => "input",
146            Element::Textarea => "textarea",
147            Element::Select(_) => "select",
148            Element::Button(_) => "button",
149        }
150    }
151
152    /// Return the element's type (`type` attribute), used by `HtmlForm` to
153    /// fill its `type` attribute.
154    pub fn element_type(&self) -> &'static str {
155        match &self {
156            Element::Input(input_type) => match input_type {
157                InputType::Text => "text",
158                InputType::Password => "password",
159                InputType::Radio => "radio",
160                InputType::Checkbox => "checkbox",
161                InputType::Number => "number",
162                InputType::Range => "range",
163                InputType::Date => "date",
164                InputType::DateTime => "datetime-local",
165                InputType::Month => "month",
166                InputType::Week => "week",
167                InputType::Time => "time",
168                InputType::Url => "url",
169                InputType::Email => "email",
170                InputType::Tel => "tel",
171                InputType::Color => "color",
172                InputType::File => "file",
173                InputType::Search => "search",
174                InputType::Button => "button",
175                InputType::Reset => "reset",
176                InputType::Submit => "submit",
177                InputType::Image => "image",
178                InputType::Hidden => "hidden",
179            },
180            Element::Button(button_type) => match button_type {
181                ButtonType::Submit => "submit",
182                ButtonType::Reset => "reset",
183                ButtonType::Button => "button",
184            },
185            _ => "",
186        }
187    }
188
189    /// Return `true` for multi-selects and checkbox inputs, used by
190    /// `HtmlForm` to fill its `multi` attribute.
191    pub fn multi(&self) -> bool {
192        match self {
193            Element::Input(InputType::Checkbox) |
194            Element::Select(SelectType::Multi) => true,
195            _ => false,
196        }
197    }
198}
199
200/// Different input types, use with `Element::Input()`. Used as value of
201/// the `type` attribute on an HTML `input` element.
202#[derive(Debug)]
203pub enum InputType {
204    Text,
205    Password,
206    Radio,
207    Checkbox,
208    Number,
209    Range,
210    Date,
211    DateTime,
212    Month,
213    Week,
214    Time,
215    Url,
216    Email,
217    Tel,
218    Color,
219    File,
220    Search,
221    Button,
222    Submit,
223    Reset,
224    Image,
225    Hidden,
226}
227
228/// Value for `Element::Select()` to determine `select` element behaviour.
229#[derive(Debug)]
230pub enum SelectType {
231    Single,
232    Multi,
233}
234
235/// Value for `Element::Button()` to determine `button` element behaviour.
236#[derive(Debug)]
237pub enum ButtonType {
238    Submit,
239    Reset,
240    Button,
241}
242
243/// Value for `Attr::Spellcheck()` to determine `textarea` spell checking
244/// behaviour.
245#[derive(Debug)]
246pub enum Spellcheck {
247    True,
248    Default,
249    False,
250}
251
252/// Value for `Attr::Wrap()` to determine `textarea` wrapping behaviour.
253#[derive(Debug)]
254pub enum Wrap {
255    Hard,
256    Soft,
257    Off,
258}
259
260/// Value for `Attr::Autocomplete()`.
261#[derive(Debug)]
262pub enum Autocomplete {
263    On,
264    Off,
265}
266
267/// Constraints on `Field` values, perform validation.
268///
269/// All of the constraints cause server-side validation to be performed,
270/// all except `Constraint::Func` should - assuming they're serialized
271/// properly - result in client-side validation. HTML validity is checked
272/// when adding the constraints to the fields using `HtmlForm`'s builder
273/// methods.
274pub enum Constraint<'a> {
275    /// Constraint on most inputs.
276    MinLength(usize),
277    /// Constraint on most inputs.
278    MaxLength(usize),
279    /// Constraint on `number` and `range` inputs.
280    MinNumber(f64),
281    /// Constraint on `number` and `range` inputs.
282    MaxNumber(f64),
283    /// Constraint on `date` input.
284    MinDate(&'a str),
285    /// Constraint on `date` input.
286    MaxDate(&'a str),
287    /// Constraint on `datetime-local` input.
288    MinDateTime(&'a str),
289    /// Constraint on `datetime-local` input.
290    MaxDateTime(&'a str),
291    /// Constraint on `time` input.
292    MinTime(&'a str),
293    /// Constraint on `time` input.
294    MaxTime(&'a str),
295    /// Constraint on most `Element::Input` fields, causes regex pattern
296    /// validation (both on the server and the client, note that the pattern
297    /// therefore must execute correctly on both sides).
298    Pattern(&'a str),
299    /// Constraint on any field, is executed server-side only and not
300    /// serialized.
301    Func(Box<Fn(&Value) -> Result<(), ValidationError>>),
302}
303
304impl <'a> Constraint<'a> {
305    /// Validate a single, non-empty `Value`.
306    pub fn validate(&self, formvalue: &Value)
307            -> Result<(), ValidationError> {
308        match self {
309            Constraint::MinLength(min) => {
310                let value = formvalue.as_string();
311                if value.len() < *min {
312                    return Err(ValidationError::new(
313                        &format!(
314                            "Must be at least {} characters long.",
315                            min)));
316                }
317            },
318            Constraint::MaxLength(max) => {
319                let value = formvalue.as_string();
320                if value.len() > *max {
321                    return Err(ValidationError::new(
322                        &format!(
323                            "Can not be more than {} characters long.",
324                            max)));
325                }
326            },
327            Constraint::MinNumber(min) => {
328                let value: f64 = formvalue.parse()?;
329                if value < *min {
330                    return Err(ValidationError::new(
331                        &format!("Must be at least {}.", min)));
332                }
333            },
334            Constraint::MaxNumber(max) => {
335                let value: f64 = formvalue.parse()?;
336                if value > *max {
337                    return Err(ValidationError::new(
338                        &format!("Can not be more than {}.", max)));
339                }
340            },
341            Constraint::MinDate(min) => {
342                let value = formvalue.to_string();
343                if !validate_date(&value) {
344                    return Err(ValidationError::new(
345                        &format!("Invalid date {}.", value)));
346                }
347                // somewhat nasty, but taking into account the (fixed)
348                // format of the date, we can do char by char comparison
349                // to determine whether the provided value is less than min
350                for (i, chr) in value.as_bytes().iter().enumerate() {
351                    if *chr < min.as_bytes()[i] {
352                        return Err(ValidationError::new(
353                            &format!(
354                                "Date should be after {}.", min)));
355                    }
356                }
357            },
358            Constraint::MaxDate(max) => {
359                let value = formvalue.to_string();
360                if !validate_date(&value) {
361                    return Err(ValidationError::new(
362                        &format!("Invalid date {}.", value)));
363                }
364                for (i, chr) in value.as_bytes().iter().enumerate() {
365                    if *chr > max.as_bytes()[i] {
366                        return Err(ValidationError::new(
367                            &format!(
368                                "Date can not be after {}.", max)));
369                    }
370                }
371            },
372            Constraint::MinDateTime(min) => {
373                let value = formvalue.to_string();
374                if !validate_datetime(&value) {
375                    return Err(ValidationError::new(
376                        &format!("Invalid date and time {}.", value)));
377                }
378                for (i, chr) in value.as_bytes().iter().enumerate() {
379                    if *chr < min.as_bytes()[i] {
380                        return Err(ValidationError::new(
381                            &format!(
382                                "Date and time must be after {}",
383                                min)));
384                    }
385                }
386            },
387            Constraint::MaxDateTime(max) => {
388                let value = formvalue.to_string();
389                if !validate_datetime(&value) {
390                    return Err(ValidationError::new(
391                        &format!("Invalid date and time {}.", value)));
392                }
393                for (i, chr) in value.as_bytes().iter().enumerate() {
394                    if *chr > max.as_bytes()[i] {
395                        return Err(ValidationError::new(
396                            &format!(
397                                "Date and time can not be after {}.",
398                                max)));
399                    }
400                }
401            },
402            Constraint::MinTime(min) => {
403                let value = formvalue.to_string();
404                if !validate_time(&value) {
405                    return Err(ValidationError::new(
406                        &format!("Invalid time {}.", value)));
407                }
408                for (i, chr) in value.as_bytes().iter().enumerate() {
409                    if *chr < min.as_bytes()[i] {
410                        return Err(ValidationError::new(
411                            &format!(
412                                "Time must be after {}.", min)));
413                    }
414                }
415            },
416            Constraint::MaxTime(max) => {
417                let value = formvalue.to_string();
418                if !validate_time(&value) {
419                    return Err(ValidationError::new(
420                        &format!("Invalid time {}.", value)));
421                }
422                for (i, chr) in value.as_bytes().iter().enumerate() {
423                    if *chr > max.as_bytes()[i] {
424                        return Err(ValidationError::new(
425                            &format!(
426                                "Time can not be after {}.", max)));
427                    }
428                }
429            },
430            Constraint::Pattern(pattern) => {
431                let value = formvalue.as_string();
432                let reg = match Regex::new(pattern) {
433                    Ok(reg) => reg,
434                    Err(_) => return Err(
435                        ValidationError::new(
436                            &format!("Invalid pattern {:?}.", value))),
437                };
438                if !reg.is_match(&value) {
439                    return Err(
440                        ValidationError::new(
441                            &format!("Please match the format requested.")));
442                }
443            },
444            Constraint::Func(validator) => {
445                validator(&formvalue)?;
446            },
447        }
448        Ok(())
449    }
450
451    /// Returns the name and value of the HTML attribute of the Constraint.
452    ///
453    /// Returns None for `Constraint::Func`, as that is only functional on
454    /// the server side.
455    pub fn attrpair(&self) -> Option<(String, String)> {
456        match self {
457            Constraint::MinLength(min) =>
458                Some((String::from("minlength"), min.to_string())),
459            Constraint::MaxLength(max) =>
460                Some((String::from("maxlength"), max.to_string())),
461            Constraint::MinNumber(min) =>
462                Some((String::from("min"), min.to_string())),
463            Constraint::MaxNumber(max) =>
464                Some((String::from("max"), max.to_string())),
465            Constraint::MinDate(min) |
466            Constraint::MinTime(min) |
467            Constraint::MinDateTime(min) =>
468                Some((String::from("min"), min.to_string())),
469            Constraint::MaxDate(max) |
470            Constraint::MaxTime(max) |
471            Constraint::MaxDateTime(max) =>
472                Some((String::from("max"), max.to_string())),
473            Constraint::Pattern(pattern) =>
474                Some((String::from("pattern"), pattern.to_string())),
475            Constraint::Func(_) => None,
476        }
477    }
478
479    /// Return true if this `Constraint` is allowed on `element`,
480    /// false otherwise.
481    pub fn allowed_on(&self, element: &Element) -> bool {
482        match self {
483            Constraint::MinLength(_) |
484            Constraint::MaxLength(_) => match element {
485                Element::Textarea => true,
486                Element::Input(input_type) => match input_type {
487                    InputType::Radio |
488                    InputType::Checkbox |
489                    InputType::File |
490                    InputType::Button |
491                    InputType::Submit |
492                    InputType::Reset |
493                    InputType::Image |
494                    InputType::Hidden => false,
495                    _ => true,
496                },
497                _ => false,
498            },
499            Constraint::MinNumber(_) => match element {
500                Element::Input(InputType::Number) => true,
501                _ => false,
502            },
503            Constraint::MaxNumber(_) => match element {
504                Element::Input(InputType::Number) => true,
505                _ => false,
506            },
507            Constraint::MinDate(_) => match element {
508                Element::Input(InputType::Date) => true,
509                _ => false,
510            },
511            Constraint::MaxDate(_) => match element {
512                Element::Input(InputType::Date) => true,
513                _ => false,
514            },
515            Constraint::MinTime(_) => match element {
516                Element::Input(InputType::Time) => true,
517                _ => false,
518            },
519            Constraint::MaxTime(_) => match element {
520                Element::Input(InputType::Time) => true,
521                _ => false,
522            },
523            Constraint::MinDateTime(_) => match element {
524                Element::Input(InputType::DateTime) => true,
525                _ => false,
526            },
527            Constraint::MaxDateTime(_) => match element {
528                Element::Input(InputType::DateTime) => true,
529                _ => false,
530            },
531            Constraint::Pattern(_) => match element {
532                Element::Textarea => true,
533                Element::Input(input_type) => match input_type {
534                    InputType::Text |
535                        InputType::Password |
536                        InputType::Date |
537                        InputType::Url |
538                        InputType::Email |
539                        InputType::Tel |
540                        InputType::Search => true,
541                    _ => false,
542                },
543                _ => false,
544            },
545            Constraint::Func(_) => true,
546        }
547    }
548}
549
550impl <'a> fmt::Debug for Constraint<'a> {
551    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552        match self {
553            Constraint::MinLength(len) => {
554                write!(f, "Constraint::MinLength({})", len)
555            },
556            Constraint::MaxLength(len) => {
557                write!(f, "Constraint::MaxLength({})", len)
558            },
559            Constraint::MinNumber(number) => {
560                write!(f, "Constraint::MinNumber({})", number)
561            },
562            Constraint::MaxNumber(number) => {
563                write!(f, "Constraint::MaxNumber({})", number)
564            },
565            Constraint::MinDate(number) => {
566                write!(f, "Constraint::MinDate({})", number)
567            },
568            Constraint::MaxDate(number) => {
569                write!(f, "Constraint::MaxDate({})", number)
570            },
571            Constraint::MinTime(number) => {
572                write!(f, "Constraint::MinTime({})", number)
573            },
574            Constraint::MaxTime(number) => {
575                write!(f, "Constraint::MaxTime({})", number)
576            },
577            Constraint::MinDateTime(number) => {
578                write!(f, "Constraint::MinDateTime({})", number)
579            },
580            Constraint::MaxDateTime(number) => {
581                write!(f, "Constraint::MaxDateTime({})", number)
582            },
583            Constraint::Pattern(pattern) => {
584                write!(f, "Constraint::Pattern({})", pattern)
585            },
586            Constraint::Func(_) => {
587                write!(f, "Constraint::Func(Fn)")
588            },
589        }
590    }
591}
592
593// XXX missing 'form' attribute - should we add that?
594/// An HTML attribute without validation behaviour. The list should be
595/// complete and up-to-date with the latest HTML specifications. HTML
596/// validity is checked when adding the attributes to the fields using
597/// `HtmlForm`'s builder methods.
598#[derive(Debug)]
599pub enum Attr<'a> {
600    /// Use to add any attribute to any element without validation.
601    Any(&'a str, &'a str),
602
603    // general attrs for most elements/inputs
604    Id(&'a str),
605    Title(&'a str),
606    Placeholder(&'a str),
607    Autocomplete(Autocomplete),
608    Autofocus,
609    Disabled,
610    Readonly,
611    Tabindex(i64),
612
613    // 'step' for number, range, date, datetime, etc
614    StepFloat(f64),
615    StepInt(u64),
616
617    // rendering hints for most inputs
618    Size(u64),
619    Width(u64),
620    Height(u64),
621
622    // specific to textarea
623    Rows(u64),
624    Cols(u64),
625    Spellcheck(Spellcheck),
626    Wrap(Wrap),
627
628    // control form behaviour from an input, usually buttons (new to HTML 5)
629    FormAction(&'a str),
630    FormEnctype(&'a str),
631    FormNoValidate,
632    FormTarget(&'a str),
633}
634
635impl <'a> Attr<'a> {
636    pub fn attrpair(&self) -> (String, String) {
637        let (name, value) = match self {
638            Attr::Any(name, value) => (name.deref(), value.to_string()),
639            Attr::StepFloat(step) => ("step", step.to_string()),
640            Attr::StepInt(step) => ("step", step.to_string()),
641            Attr::Size(size) => ("size", size.to_string()),
642            Attr::Width(width) => ("width", width.to_string()),
643            Attr::Height(height) => ("height", height.to_string()),
644            Attr::Rows(rows) => ("rows", rows.to_string()),
645            Attr::Cols(cols) => ("cols", cols.to_string()),
646            Attr::Spellcheck(wrap) =>
647                ("wrap", match wrap {
648                    Spellcheck::True => String::from("true"),
649                    Spellcheck::Default => String::from("default"),
650                    Spellcheck::False => String::from("false"),
651                }),
652            Attr::Wrap(wrap) =>
653                ("wrap", match wrap {
654                    Wrap::Hard => String::from("hard"),
655                    Wrap::Soft => String::from("soft"),
656                    Wrap::Off => String::from("off"),
657                }),
658            Attr::FormAction(formaction) =>
659                ("formaction", formaction.to_string()),
660            Attr::FormEnctype(formenctype) =>
661                ("formenctype", formenctype.to_string()),
662            Attr::FormNoValidate => (
663                "formnovalidate", String::from("formnovalidate")),
664            Attr::FormTarget(formtarget) =>
665                ("formtarget", formtarget.to_string()),
666            Attr::Id(id) => ("id", id.to_string()),
667            Attr::Title(label) => ("title", label.to_string()),
668            Attr::Placeholder(label) =>
669                ("placeholder", label.to_string()),
670            Attr::Autocomplete(autocomplete) =>
671                ("autocomplete", match autocomplete {
672                    Autocomplete::On => String::from("on"),
673                    Autocomplete::Off => String::from("off"),
674                }),
675            Attr::Autofocus => ("autofocus", String::from("autofocus")),
676            Attr::Disabled => ("disabled", String::from("disabled")),
677            Attr::Readonly => ("readonly", String::from("readonly")),
678            Attr::Tabindex(tabindex) => ("tabindex", tabindex.to_string()),
679        };
680        (String::from(name), value)
681    }
682
683    pub fn allowed_on(&self, element: &Element) -> bool {
684        match element {
685            Element::Input(input_type) => match self {
686                Attr::Any(_, _) |
687                Attr::Id(_) |
688                // title makes no sense on some elements, but does seem
689                // to be allowed on all...
690                Attr::Title(_) => true,
691                Attr::StepFloat(_) => match input_type {
692                    InputType::Number => true,
693                    _ => false,
694                },
695                Attr::StepInt(_) => match input_type {
696                    InputType::Number |
697                    InputType::Date |
698                    InputType::Time |
699                    InputType::DateTime |
700                    InputType::Month |
701                    InputType::Week |
702                    InputType::Range => true,
703                    _ => false,
704                },
705                Attr::Width(_) |
706                Attr::Height(_) => match input_type {
707                    InputType::Image => true,
708                    _ => false,
709                },
710                Attr::Rows(_) |
711                Attr::Cols(_) |
712                Attr::Spellcheck(_) |
713                Attr::Wrap(_) => false,
714                Attr::FormAction(_) |
715                Attr::FormEnctype(_) |
716                Attr::FormTarget(_) => match input_type {
717                    InputType::Submit |
718                    InputType::Image => true,
719                    _ => false,
720                },
721                Attr::FormNoValidate => match input_type {
722                    InputType::Submit => true,
723                    _ => false,
724                },
725                Attr::Placeholder(_) => match input_type {
726                    InputType::Text |
727                    InputType::Password |
728                    InputType::Email |
729                    InputType::Url |
730                    InputType::Tel |
731                    InputType::Search => true,
732                    _ => false,
733                },
734                Attr::Autocomplete(_) => match input_type {
735                    InputType::Text |
736                    InputType::Email |
737                    InputType::Url |
738                    InputType::Tel |
739                    InputType::Search |
740                    InputType::Date |
741                    InputType::DateTime |
742                    InputType::Month | // XXX ?
743                    InputType::Week | // XXX ?
744                    InputType::Time | // XXX ?
745                    InputType::Range |
746                    InputType::Color => true,
747                    _ => false,
748                },
749                Attr::Autofocus => match input_type {
750                    InputType::Button |
751                    InputType::Submit |
752                    InputType::Reset |
753                    InputType::Image |
754                    InputType::Hidden => false,
755                    _ => true,
756                },
757                Attr::Size(_) |
758                Attr::Disabled |
759                Attr::Readonly |
760                Attr::Tabindex(_) => match input_type {
761                    InputType::Hidden => false,
762                    _ => true,
763                },
764            },
765            Element::Textarea => match self {
766                Attr::Any(_, _) |
767                Attr::Id(_) |
768                Attr::Title(_) |
769                Attr::Placeholder(_) |
770                Attr::Autocomplete(_) |
771                Attr::Autofocus |
772                Attr::Disabled |
773                Attr::Readonly |
774                Attr::Rows(_) |
775                Attr::Cols(_) |
776                Attr::Spellcheck(_) |
777                Attr::Wrap(_) => true,
778                _ => false,
779            },
780            Element::Select(_) => match self {
781                Attr::Any(_, _) |
782                Attr::Id(_) |
783                Attr::Title(_) |
784                Attr::Autocomplete(_) |
785                Attr::Autofocus |
786                Attr::Disabled |
787                Attr::Readonly |
788                Attr::Size(_) |
789                _ => false,
790            },
791            Element::Button(_) => match self {
792                Attr::Any(_, _) |
793                Attr::Id(_) |
794                Attr::Title(_) |
795                Attr::Autocomplete(_) |
796                Attr::Autofocus |
797                Attr::Disabled |
798                Attr::Readonly |
799                Attr::FormAction(_) |
800                Attr::FormEnctype(_) |
801                Attr::FormTarget(_) |
802                Attr::FormNoValidate |
803                Attr::Size(_) |
804                _ => false,
805            },
806        }
807    }
808}