html_page/
lib.rs

1//! Construct and manipulate an HTML element represented using Rust types.
2//!
3//! Conceptually, an element has a tag, optional attributes, and
4//! optional children. A child can consist of non-HTML text, or be an
5//! element of its own.
6//!
7//! This crate aims to follow the WhatWG specification at
8//! <https://html.spec.whatwg.org/>.
9//!
10//! # Example
11//!
12//! ~~~
13//! use html_page::{Element, Tag};
14//!
15//! let e = Element::new(Tag::P)
16//!     .with_text("hello ")
17//!     .with_child(Element::new(Tag::Strong).with_text("world"));
18//! assert_eq!(e.serialize(), "<P>hello <STRONG>world</STRONG></P>");
19//! assert_eq!(e.plain_text(), "hello world");
20//! ~~~
21
22#![deny(missing_docs)]
23
24use html_escape::{encode_double_quoted_attribute, encode_safe};
25use std::collections::HashMap;
26use std::fmt::{Display, Formatter};
27
28/// An HTML document ("page'),consisting of a head and a body element.
29///
30/// ~~~
31/// # use html_page::{HtmlPage, Element, Tag};
32/// let title = Element::new(Tag::Title).with_text("my page");
33/// let doc = HtmlPage::default().with_head_element(title);
34/// assert_eq!(format!("{}", doc), "<!DOCTYPE html>\n<HTML>\n\
35/// <HEAD><TITLE>my page</TITLE></HEAD>\n<BODY></BODY>\n</HTML>\n");
36/// ~~~
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct HtmlPage {
39    head: Element,
40    body: Element,
41}
42
43impl Default for HtmlPage {
44    fn default() -> Self {
45        Self {
46            head: Element::new(Tag::Head),
47            body: Element::new(Tag::Body),
48        }
49    }
50}
51
52impl HtmlPage {
53    /// Append an element to the head.
54    pub fn push_to_head(&mut self, e: Element) {
55        self.head.push_child(e);
56    }
57
58    /// Append an element to the body.
59    pub fn push_to_body(&mut self, e: Element) {
60        self.body.push_child(e);
61    }
62
63    /// Append an element to the head, when constructing.
64    pub fn with_head_element(mut self, e: Element) -> Self {
65        self.head.push_child(e);
66        self
67    }
68
69    /// Append an element to the body, when constructing.
70    pub fn with_body_element(mut self, e: Element) -> Self {
71        self.body.push_child(e);
72        self
73    }
74
75    /// Append text to the body, when constructing.
76    pub fn with_body_text(mut self, text: &str) -> Self {
77        self.body.push_text(text);
78        self
79    }
80
81    /// Append all children of `e` as to body of page.
82    pub fn push_children(&mut self, e: &Element) {
83        for child in &e.children {
84            self.body.children.push(child.clone());
85        }
86    }
87}
88
89impl Display for HtmlPage {
90    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
91        writeln!(f, "<!DOCTYPE html>")?;
92        writeln!(f, "<{}>", Tag::Html)?;
93        writeln!(f, "{}", &self.head)?;
94        writeln!(f, "{}", &self.body)?;
95        writeln!(f, "</{}>", Tag::Html)?;
96        Ok(())
97    }
98}
99
100/// The tag of an HTML5 element.
101///
102/// Note that we only support HTML5 elements, as listed on
103/// <https://html.spec.whatwg.org//>.
104#[derive(Copy, Clone, Debug, Eq, PartialEq)]
105#[allow(missing_docs)] // the variants are just element names, no need
106                       // to document each separately
107pub enum Tag {
108    A,
109    Abbr,
110    Address,
111    Area,
112    Article,
113    Aside,
114    Audio,
115    B,
116    Base,
117    Bdi,
118    Bdo,
119    Blockquote,
120    Body,
121    Br,
122    Button,
123    Canvas,
124    Caption,
125    Cite,
126    Code,
127    Col,
128    ColGroup,
129    Data,
130    DataList,
131    Dd,
132    Del,
133    Details,
134    Dfn,
135    Dialog,
136    Div,
137    Dl,
138    Dt,
139    Em,
140    Embed,
141    FieldSet,
142    FigCaption,
143    Figure,
144    Footer,
145    Form,
146    H1,
147    H2,
148    H3,
149    H4,
150    H5,
151    H6,
152    Head,
153    Header,
154    Hr,
155    Html,
156    I,
157    Iframe,
158    Img,
159    Input,
160    Ins,
161    Kbd,
162    Label,
163    Legend,
164    Li,
165    Link,
166    Main,
167    Map,
168    Mark,
169    Meta,
170    Meter,
171    Nav,
172    NoScript,
173    Object,
174    Ol,
175    OptGroup,
176    Option,
177    Output,
178    P,
179    Param,
180    Picture,
181    Pre,
182    Progress,
183    Q,
184    Rp,
185    Rt,
186    Ruby,
187    S,
188    Samp,
189    Script,
190    Section,
191    Select,
192    Small,
193    Source,
194    Span,
195    Strong,
196    Style,
197    Sub,
198    Summary,
199    Sup,
200    Svg,
201    Table,
202    Tbody,
203    Td,
204    Template,
205    TextArea,
206    Tfoot,
207    Th,
208    Time,
209    Title,
210    Tr,
211    Track,
212    U,
213    Ul,
214    Var,
215    Video,
216    Wbr,
217}
218
219impl Display for Tag {
220    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
221        write!(f, "{}", self.as_str())
222    }
223}
224
225impl Tag {
226    fn as_str(&self) -> &str {
227        match self {
228            Self::A => "A",
229            Self::Abbr => "ABBR",
230            Self::Address => "ADDRESS",
231            Self::Area => "AREA",
232            Self::Article => "ARTICLE",
233            Self::Aside => "ASIDE",
234            Self::Audio => "AUDIO",
235            Self::B => "B",
236            Self::Base => "BASE",
237            Self::Bdi => "BDI",
238            Self::Bdo => "BDO",
239            Self::Blockquote => "BLOCKQUOTE",
240            Self::Body => "BODY",
241            Self::Br => "BR",
242            Self::Button => "BUTTON",
243            Self::Canvas => "CANVAS",
244            Self::Caption => "CAPTION",
245            Self::Cite => "CITE",
246            Self::Code => "CODE",
247            Self::Col => "COL",
248            Self::ColGroup => "COLGROUP",
249            Self::Data => "DATA",
250            Self::DataList => "DATALIST",
251            Self::Dd => "DD",
252            Self::Del => "DEL",
253            Self::Details => "DETAILS",
254            Self::Dfn => "DFN",
255            Self::Dialog => "DIALOG",
256            Self::Div => "DIV",
257            Self::Dl => "DL",
258            Self::Dt => "DT",
259            Self::Em => "EM",
260            Self::Embed => "EMBED",
261            Self::FieldSet => "FIELDSET",
262            Self::FigCaption => "FIGCAPTIO",
263            Self::Figure => "FIGURE",
264            Self::Footer => "FOOTER",
265            Self::Form => "FORM",
266            Self::H1 => "H1",
267            Self::H2 => "H2",
268            Self::H3 => "H3",
269            Self::H4 => "H4",
270            Self::H5 => "H5",
271            Self::H6 => "H6",
272            Self::Head => "HEAD",
273            Self::Header => "HEADER",
274            Self::Hr => "HR",
275            Self::Html => "HTML",
276            Self::I => "I",
277            Self::Iframe => "IFRAME",
278            Self::Img => "IMG",
279            Self::Input => "INPUT",
280            Self::Ins => "INS",
281            Self::Kbd => "KBD",
282            Self::Label => "LABEL",
283            Self::Legend => "LEGEND",
284            Self::Li => "LI",
285            Self::Link => "LINK",
286            Self::Main => "MAIN",
287            Self::Map => "MAP",
288            Self::Mark => "MARK",
289            Self::Meta => "META",
290            Self::Meter => "METER",
291            Self::Nav => "NAV",
292            Self::NoScript => "NOSCRIPT",
293            Self::Object => "OBJECT",
294            Self::Ol => "OL",
295            Self::OptGroup => "OPTGROUP",
296            Self::Option => "OPTION",
297            Self::Output => "OUTPUT",
298            Self::P => "P",
299            Self::Param => "PARAM",
300            Self::Picture => "PICTURE",
301            Self::Pre => "PRE",
302            Self::Progress => "PROGRESS",
303            Self::Q => "Q",
304            Self::Rp => "RP",
305            Self::Rt => "RT",
306            Self::Ruby => "RUBY",
307            Self::S => "S",
308            Self::Samp => "SAMP",
309            Self::Script => "SCRIPT",
310            Self::Section => "SECTION",
311            Self::Select => "SELECT",
312            Self::Small => "SMALL",
313            Self::Source => "SOURCE",
314            Self::Span => "SPAN",
315            Self::Strong => "STRONG",
316            Self::Style => "STYLE",
317            Self::Sub => "SUB",
318            Self::Summary => "SUMMARY",
319            Self::Sup => "SUP",
320            Self::Svg => "SVG",
321            Self::Table => "TABLE",
322            Self::Tbody => "TBODY",
323            Self::Td => "TD",
324            Self::Template => "TEMPLATE",
325            Self::TextArea => "TEXTAREA",
326            Self::Tfoot => "TFOOT",
327            Self::Th => "TH",
328            Self::Time => "TIME",
329            Self::Title => "TITLE",
330            Self::Tr => "TR",
331            Self::Track => "TRACK",
332            Self::U => "U",
333            Self::Ul => "UL",
334            Self::Var => "VAR",
335            Self::Video => "VIDEO",
336            Self::Wbr => "WBR",
337        }
338    }
339
340    fn can_self_close(&self) -> bool {
341        matches!(
342            self,
343            Self::Area
344                | Self::Base
345                | Self::Br
346                | Self::Col
347                | Self::Embed
348                | Self::Hr
349                | Self::Img
350                | Self::Input
351                | Self::Link
352                | Self::Meta
353                | Self::Param
354                | Self::Source
355                | Self::Track
356                | Self::Wbr
357        )
358    }
359}
360
361#[cfg(test)]
362mod test_tag {
363    use super::Tag;
364
365    #[test]
366    fn can_self_close() {
367        assert!(Tag::Area.can_self_close());
368        assert!(Tag::Base.can_self_close());
369        assert!(Tag::Br.can_self_close());
370        assert!(Tag::Col.can_self_close());
371        assert!(Tag::Embed.can_self_close());
372        assert!(Tag::Hr.can_self_close());
373        assert!(Tag::Img.can_self_close());
374        assert!(Tag::Input.can_self_close());
375        assert!(Tag::Link.can_self_close());
376        assert!(Tag::Meta.can_self_close());
377        assert!(Tag::Param.can_self_close());
378        assert!(Tag::Source.can_self_close());
379        assert!(Tag::Track.can_self_close());
380        assert!(Tag::Wbr.can_self_close());
381    }
382}
383
384#[derive(Clone, Debug, Default, Eq, PartialEq)]
385struct Attributes {
386    attrs: HashMap<String, AttributeValue>,
387}
388
389impl Attributes {
390    fn set(&mut self, name: &str, value: &str) {
391        self.attrs
392            .insert(name.into(), AttributeValue::String(value.into()));
393    }
394
395    fn set_boolean(&mut self, name: &str) {
396        self.attrs.insert(name.into(), AttributeValue::Boolean);
397    }
398
399    fn unset(&mut self, name: &str) {
400        self.attrs.remove(name);
401    }
402
403    fn get(&self, name: &str) -> Option<&AttributeValue> {
404        self.attrs.get(name)
405    }
406
407    fn names(&self) -> impl Iterator<Item = &str> {
408        self.attrs.keys().map(|s| s.as_ref())
409    }
410}
411
412impl Display for Attributes {
413    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
414        for (name, value) in self.attrs.iter() {
415            match value {
416                AttributeValue::Boolean => write!(f, " {name}")?,
417                AttributeValue::String(s) => {
418                    write!(f, " {}=\"{}\"", name, encode_double_quoted_attribute(s))?
419                }
420            }
421        }
422        Ok(())
423    }
424}
425
426/// The value of an element attribute.
427///
428/// Attributes may be "boolean" (just the name of an attribute), or a
429/// key/value pair, where the value is a string. Technically, in HTML,
430/// a boolean attribute with a true value can be expressed as a
431/// key/value pair with a value that is an empty string or the name of
432/// the attribute, but in this representation we make it more
433/// explicit.
434#[derive(Clone, Debug, Eq, PartialEq)]
435pub enum AttributeValue {
436    /// The value of a key/value attribute.
437    String(String),
438    /// A boolean attribute. It the attribute is present, the value is
439    /// true.
440    Boolean,
441}
442
443impl AttributeValue {
444    /// Return value of an attribute as a string. For a boolean
445    /// attribute, this is the empty string.
446    pub fn as_str(&self) -> &str {
447        match self {
448            Self::String(s) => s,
449            Self::Boolean => "",
450        }
451    }
452}
453
454/// An HTML element.
455///
456/// The element has a [`Tag`], possibly some attributes, and possibly
457/// some children. It may also have a location: this is used when the
458/// element is constructed by parsing some input value.
459#[derive(Clone, Debug, Eq, PartialEq)]
460pub struct Element {
461    loc: Option<(usize, usize)>,
462    tag: Tag,
463    attrs: Attributes,
464    children: Vec<Content>,
465}
466
467impl Element {
468    /// Create a new element, with a given tag.
469    pub fn new(tag: Tag) -> Self {
470        Self {
471            tag,
472            attrs: Attributes::default(),
473            children: vec![],
474            loc: None,
475        }
476    }
477
478    /// Set the location of an element in a source file.
479    pub fn with_location(mut self, line: usize, col: usize) -> Self {
480        self.loc = Some((line, col));
481        self
482    }
483
484    /// Append a child element, when constructing.
485    pub fn with_child(mut self, child: Element) -> Self {
486        self.children.push(Content::Element(child));
487        self
488    }
489
490    /// Append a text child, when constructing.
491    pub fn with_text(mut self, child: &str) -> Self {
492        self.children.push(Content::Text(child.into()));
493        self
494    }
495
496    /// Set an attribute when creating an element.
497    pub fn with_attribute(mut self, name: &str, value: &str) -> Self {
498        self.attrs.set(name, value);
499        self
500    }
501
502    /// Set a boolean attribute when creating an element.
503    pub fn with_boolean_attribute(mut self, name: &str) -> Self {
504        self.attrs.set_boolean(name);
505        self
506    }
507
508    /// Add a class when creating an element.
509    pub fn with_class(mut self, class: &str) -> Self {
510        self.add_class(class);
511        self
512    }
513
514    /// Return the [`Tag`] of the element.
515    pub fn tag(&self) -> Tag {
516        self.tag
517    }
518
519    /// Return the location of the element.
520    pub fn location(&self) -> Option<(usize, usize)> {
521        self.loc
522    }
523
524    /// Return list children.
525    pub fn children(&self) -> &[Content] {
526        &self.children
527    }
528
529    /// Return an iterator over the names of the attributes of an element.
530    pub fn attributes(&self) -> impl Iterator<Item = &str> {
531        self.attrs.names()
532    }
533
534    /// Return the value of an attribute, if the attribute is set.
535    /// Otherwise, return `None`.
536    pub fn attribute(&self, name: &str) -> Option<&AttributeValue> {
537        self.attrs.get(name)
538    }
539
540    /// Return the value of an attribute as text, if the attribute is
541    /// set. Otherwise, return `None`.
542    pub fn attribute_value(&self, name: &str) -> Option<&str> {
543        self.attrs.get(name).map(|v| v.as_str())
544    }
545
546    /// Set a key/value attribute. If the attribute was already set,
547    /// change the value it has.
548    pub fn set_attribute(&mut self, name: &str, value: &str) {
549        self.attrs.set(name, value);
550    }
551
552    /// Set a boolean attribute.
553    pub fn set_boolean_attribute(&mut self, name: &str) {
554        self.attrs.set_boolean(name);
555    }
556
557    /// Remove an attribute, which can be key/value or boolean.
558    pub fn unset_attribute(&mut self, name: &str) {
559        self.attrs.unset(name);
560    }
561
562    /// Return current classes set directly for this element.
563    pub fn classes(&self) -> impl Iterator<Item = &str> {
564        let v = self.attribute_value("class").unwrap_or_default();
565        v.split_ascii_whitespace()
566    }
567
568    /// Does the element have a class set directly?
569    pub fn has_class(&self, wanted: &str) -> bool {
570        self.classes().any(|v| v == wanted)
571    }
572
573    /// Add a class to the element. This does not replace existing
574    /// classes.
575    pub fn add_class(&mut self, class: &str) {
576        if let Some(old) = self.attribute_value("class") {
577            if !old.split_ascii_whitespace().any(|s| s == class) {
578                self.set_attribute("class", &format!("{old} {class}"));
579            }
580        } else {
581            self.set_attribute("class", class);
582        }
583    }
584
585    /// Append text to element. It will be escaped, if needed, when
586    /// the element is serialized.
587    pub fn push_text(&mut self, text: &str) {
588        self.children.push(Content::text(text));
589    }
590
591    /// Append a child element to this element.
592    pub fn push_child(&mut self, child: Element) {
593        self.children.push(Content::element(&child));
594    }
595
596    /// Append several child elements to this element.
597    pub fn push_children(&mut self, children: &[Element]) {
598        for child in children {
599            self.children.push(Content::element(child));
600        }
601    }
602
603    /// Remove all children.
604    pub fn clear_children(&mut self) {
605        self.children.clear();
606    }
607
608    /// Append HTML to element. It will NOT be escaped, when the
609    /// element is serialized. This is an easy to inject arbitrary
610    /// junk into the HTML. No validation is done. You should avoid
611    /// this if you can.
612    pub fn push_html(&mut self, html: &str) {
613        self.children.push(Content::html(html));
614    }
615
616    /// Serialize an element into HTML.
617    pub fn serialize(&self) -> String {
618        format!("{self}")
619    }
620
621    /// Return all the textual content in an element and its children.
622    /// This does not include attributes.
623    pub fn plain_text(&self) -> String {
624        let mut text = TextVisitor::default();
625        text.visit(self);
626        text.text
627    }
628}
629
630impl Display for Element {
631    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
632        if self.tag().can_self_close() && self.children.is_empty() {
633            write!(f, "<{}{}/>", self.tag, self.attrs)?;
634        } else {
635            write!(f, "<{}{}>", self.tag, self.attrs)?;
636            for child in &self.children {
637                write!(f, "{child}")?;
638            }
639            write!(f, "</{}>", self.tag)?;
640        }
641        Ok(())
642    }
643}
644
645#[cfg(test)]
646mod test_element {
647    use super::{Element, Tag};
648
649    #[test]
650    fn empty_p() {
651        let e = Element::new(Tag::P);
652        assert_eq!(e.to_string(), "<P></P>");
653    }
654
655    #[test]
656    fn empty_br() {
657        let e = Element::new(Tag::Br);
658        assert_eq!(e.to_string(), "<BR/>");
659    }
660}
661
662/// Represent content in HTML.
663#[derive(Clone, Debug, Eq, PartialEq)]
664pub enum Content {
665    /// Non-HTML text.
666    Text(String),
667    /// An HTML element.
668    Element(Element),
669    /// HTML text.
670    Html(String),
671}
672
673impl Content {
674    /// Create a new [`Content::Text`].
675    pub fn text(s: &str) -> Self {
676        Self::Text(s.into())
677    }
678
679    /// Create a new [`Content::Element`].
680    pub fn element(e: &Element) -> Self {
681        Self::Element(e.clone())
682    }
683
684    /// Create a new [`Content::Html`].
685    pub fn html(s: &str) -> Self {
686        Self::Html(s.into())
687    }
688}
689
690impl Display for Content {
691    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
692        match self {
693            Self::Text(s) => write!(f, "{}", encode_safe(s))?,
694            Self::Element(e) => write!(f, "{e}")?,
695            Self::Html(s) => write!(f, "{s}")?,
696        }
697        Ok(())
698    }
699}
700
701/// A read-only visitor for an HTML element.
702///
703/// Implementing this trait allows "visiting" element and all of its
704/// children. The provided [`Visitor::visit`] method visits the
705/// element first, and then each of its children in order, and
706/// recursively visits the children of each child.
707///
708/// ~~~
709/// # use html_page::{Element, Tag, Visitor};
710/// #[derive(Default)]
711/// struct Collector {
712///     tags: Vec<Tag>,
713///     text: String,
714/// }
715///
716/// impl Visitor for Collector {
717///     fn visit_element(&mut self, e: &Element) {
718///         self.tags.push(e.tag());
719///     }
720///
721///     fn visit_text(&mut self, s: &str) {
722///         self.text.push_str(s);
723///     }
724/// }
725/// #
726/// # let mut e = Element::new(Tag::P);
727/// # e.push_text("hello ");
728/// # let mut world = Element::new(Tag::B);
729/// # world.push_text("world");
730/// # e.push_child(world);
731/// #
732/// # let mut collector = Collector::default();
733/// # collector.visit(&e);
734/// # assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
735/// # assert_eq!(collector.text, "hello world");
736/// ~~~
737pub trait Visitor {
738    /// Visit an element.
739    fn visit_element(&mut self, _: &Element) {}
740    /// Visit non-HTML text content.
741    fn visit_text(&mut self, _: &str) {}
742    /// Visit literal HTML content.
743    fn visit_html(&mut self, _: &str) {}
744
745    /// Visit recursively an element and each of its children.
746    fn visit(&mut self, root: &Element) {
747        self.visit_element(root);
748        for child in &root.children {
749            match child {
750                Content::Text(s) => self.visit_text(s),
751                Content::Element(e) => self.visit(e),
752                Content::Html(s) => self.visit_html(s),
753            }
754        }
755    }
756}
757
758/// A visitor to extract the text of an element and its children.
759///
760/// This does not include attributes or their values.
761///
762/// Note that you can call [`Element::plain_text`] for simplicity.
763///
764/// ~~~
765/// use html_page::{Element, Tag, TextVisitor, Visitor};
766/// let e = Element::new(Tag::P).with_text("hello, there");
767/// let mut tv = TextVisitor::default();
768/// tv.visit(&e);
769/// assert_eq!(tv.text, "hello, there");
770/// ~~~
771#[derive(Debug, Default)]
772pub struct TextVisitor {
773    /// The text collected by the visitor.
774    pub text: String,
775}
776
777impl Visitor for TextVisitor {
778    fn visit_text(&mut self, s: &str) {
779        self.text.push_str(s);
780    }
781}
782
783#[cfg(test)]
784mod test {
785    use super::{AttributeValue, Content, Element, Tag, Visitor};
786
787    #[test]
788    fn element_has_correct_tag() {
789        let e = Element::new(Tag::P);
790        assert_eq!(e.tag(), Tag::P);
791    }
792
793    #[test]
794    fn element_has_no_attributes_initially() {
795        let e = Element::new(Tag::P);
796        assert_eq!(e.attributes().count(), 0);
797    }
798
799    #[test]
800    fn element_returns_no_value_for_missing_attribute() {
801        let e = Element::new(Tag::P);
802        assert_eq!(e.attribute("foo"), None);
803    }
804
805    #[test]
806    fn can_add_attribute_to_element() {
807        let mut e = Element::new(Tag::P);
808        e.set_attribute("foo", "bar");
809        assert_eq!(
810            e.attribute("foo"),
811            Some(&AttributeValue::String("bar".into()))
812        );
813        assert_eq!(e.attribute("foo").map(|x| x.as_str()), Some("bar"));
814        assert_eq!(e.attribute_value("foo"), Some("bar"));
815    }
816
817    #[test]
818    fn can_create_element_with_attribute() {
819        let e = Element::new(Tag::P).with_attribute("foo", "bar");
820        assert_eq!(
821            e.attribute("foo"),
822            Some(&AttributeValue::String("bar".into()))
823        );
824    }
825
826    #[test]
827    fn can_add_class_to_element() {
828        let mut e = Element::new(Tag::P);
829        e.add_class("foo");
830        let classes: Vec<&str> = e.classes().collect();
831        assert_eq!(classes, ["foo"]);
832        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
833    }
834
835    #[test]
836    fn can_two_classes_to_element() {
837        let mut e = Element::new(Tag::P);
838        e.add_class("foo");
839        e.add_class("bar");
840        let classes: Vec<&str> = e.classes().collect();
841        assert_eq!(classes, ["foo", "bar"]);
842        assert_eq!(e.to_string(), r#"<P class="foo bar"></P>"#);
843    }
844
845    #[test]
846    fn can_add_same_class_twice_to_element() {
847        let mut e = Element::new(Tag::P);
848        e.add_class("foo");
849        e.add_class("foo");
850        let classes: Vec<&str> = e.classes().collect();
851        assert_eq!(classes, ["foo"]);
852        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
853    }
854
855    #[test]
856    fn can_add_boolean_attribute_to_element() {
857        let mut e = Element::new(Tag::P);
858        e.set_boolean_attribute("foo");
859        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
860    }
861
862    #[test]
863    fn can_create_element_with_boolan_attribute() {
864        let e = Element::new(Tag::P).with_boolean_attribute("foo");
865        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
866    }
867
868    #[test]
869    fn unset_attribute_is_unset() {
870        let e = Element::new(Tag::P);
871        assert_eq!(e.attribute("foo"), None);
872    }
873
874    #[test]
875    fn can_unset_attribute_in_element() {
876        let mut e = Element::new(Tag::P);
877        e.set_attribute("foo", "bar");
878        e.unset_attribute("foo");
879        assert_eq!(e.attribute("foo"), None);
880    }
881
882    #[test]
883    fn element_has_no_children_initially() {
884        let e = Element::new(Tag::P);
885        assert!(e.children.is_empty());
886    }
887
888    #[test]
889    fn add_child_to_element() {
890        let mut e = Element::new(Tag::P);
891        let child = Content::text("foo");
892        e.push_text("foo");
893        assert_eq!(e.children(), &[child]);
894    }
895
896    #[test]
897    fn element_has_no_location_initially() {
898        let e = Element::new(Tag::P);
899        assert!(e.location().is_none());
900    }
901
902    #[test]
903    fn element_with_location() {
904        let e = Element::new(Tag::P).with_location(1, 2);
905        assert_eq!(e.location(), Some((1, 2)));
906    }
907
908    #[test]
909    fn attribute_can_be_serialized() {
910        let mut e = Element::new(Tag::P);
911        e.set_attribute("foo", "bar");
912        assert_eq!(e.serialize(), "<P foo=\"bar\"></P>");
913    }
914
915    #[test]
916    fn dangerous_attribute_value_is_esacped() {
917        let mut e = Element::new(Tag::P);
918        e.set_attribute("foo", "<");
919        assert_eq!(e.serialize(), "<P foo=\"&lt;\"></P>");
920    }
921
922    #[test]
923    fn boolean_attribute_can_be_serialized() {
924        let mut e = Element::new(Tag::P);
925        e.set_boolean_attribute("foo");
926        assert_eq!(e.serialize(), "<P foo></P>");
927    }
928
929    #[test]
930    fn element_can_be_serialized() {
931        let mut e = Element::new(Tag::P);
932        e.push_text("hello ");
933        let mut world = Element::new(Tag::B);
934        world.push_text("world");
935        e.push_child(world);
936        assert_eq!(e.serialize(), "<P>hello <B>world</B></P>");
937    }
938
939    #[test]
940    fn dangerous_text_is_escaped() {
941        let mut e = Element::new(Tag::P);
942        e.push_text("hello <world>");
943        assert_eq!(e.serialize(), "<P>hello &lt;world&gt;</P>");
944    }
945
946    #[test]
947    fn element_has_no_class_initially() {
948        let e = Element::new(Tag::P);
949        assert_eq!(e.attribute_value("class"), None);
950        assert_eq!(e.classes().next(), None);
951        assert!(!e.has_class("foo"));
952    }
953
954    #[test]
955    fn element_adds_first_class() {
956        let mut e = Element::new(Tag::P);
957        e.add_class("foo");
958        assert_eq!(e.attribute_value("class"), Some("foo"));
959        assert!(e.has_class("foo"));
960    }
961
962    #[test]
963    fn element_adds_second_class() {
964        let mut e = Element::new(Tag::P);
965        e.add_class("foo");
966        e.add_class("bar");
967        assert_eq!(e.attribute_value("class"), Some("foo bar"));
968        assert!(e.has_class("foo"));
969        assert!(e.has_class("bar"));
970    }
971
972    #[test]
973    fn creates_classy_element() {
974        let e = Element::new(Tag::P).with_class("foo").with_class("bar");
975        assert_eq!(e.attribute_value("class"), Some("foo bar"));
976        assert!(e.has_class("foo"));
977        assert!(e.has_class("bar"));
978    }
979
980    #[derive(Default)]
981    struct Collector {
982        tags: Vec<Tag>,
983        text: String,
984    }
985
986    impl Visitor for Collector {
987        fn visit_element(&mut self, e: &Element) {
988            self.tags.push(e.tag());
989        }
990
991        fn visit_text(&mut self, s: &str) {
992            self.text.push_str(s);
993        }
994    }
995
996    #[test]
997    fn visits_all_children() {
998        let e = Element::new(Tag::P)
999            .with_text("hello ")
1000            .with_child(Element::new(Tag::B).with_text("world"));
1001
1002        let mut collector = Collector::default();
1003        collector.visit(&e);
1004        assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
1005        assert_eq!(collector.text, "hello world");
1006    }
1007}