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        match self {
342            Self::Area
343            | Self::Base
344            | Self::Br
345            | Self::Col
346            | Self::Embed
347            | Self::Hr
348            | Self::Img
349            | Self::Input
350            | Self::Link
351            | Self::Meta
352            | Self::Param
353            | Self::Source
354            | Self::Track
355            | Self::Wbr => true,
356            _ => false,
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 an iterator over the names of the attributes of an element.
525    pub fn attributes(&self) -> impl Iterator<Item = &str> {
526        self.attrs.names()
527    }
528
529    /// Return the value of an attribute, if the attribute is set.
530    /// Otherwise, return `None`.
531    pub fn attribute(&self, name: &str) -> Option<&AttributeValue> {
532        self.attrs.get(name)
533    }
534
535    /// Return the value of an attribute as text, if the attribute is
536    /// set. Otherwise, return `None`.
537    pub fn attribute_value(&self, name: &str) -> Option<&str> {
538        self.attrs.get(name).map(|v| v.as_str())
539    }
540
541    /// Set a key/value attribute. If the attribute was already set,
542    /// change the value it has.
543    pub fn set_attribute(&mut self, name: &str, value: &str) {
544        self.attrs.set(name, value);
545    }
546
547    /// Set a boolean attribute.
548    pub fn set_boolean_attribute(&mut self, name: &str) {
549        self.attrs.set_boolean(name);
550    }
551
552    /// Remove an attribute, which can be key/value or boolean.
553    pub fn unset_attribute(&mut self, name: &str) {
554        self.attrs.unset(name);
555    }
556
557    /// Return current classes set directly for this element.
558    pub fn classes(&self) -> impl Iterator<Item = &str> {
559        let v = if let Some(v) = self.attribute_value("class") {
560            v
561        } else {
562            ""
563        };
564        v.split_ascii_whitespace()
565    }
566
567    /// Does the element have a class set directly?
568    pub fn has_class(&self, wanted: &str) -> bool {
569        self.classes().any(|v| v == wanted)
570    }
571
572    /// Add a class to the element. This does not replace existing
573    /// classes.
574    pub fn add_class(&mut self, class: &str) {
575        if let Some(old) = self.attribute_value("class") {
576            if !old.split_ascii_whitespace().any(|s| s == class) {
577                self.set_attribute("class", &format!("{old} {class}"));
578            }
579        } else {
580            self.set_attribute("class", class);
581        }
582    }
583
584    /// Append text to element. It will be escaped, if needed, when
585    /// the element is serialized.
586    pub fn push_text(&mut self, text: &str) {
587        self.children.push(Content::text(text));
588    }
589
590    /// Append a child element to this element.
591    pub fn push_child(&mut self, child: Element) {
592        self.children.push(Content::element(&child));
593    }
594
595    /// Remove all children.
596    pub fn clear_children(&mut self) {
597        self.children.clear();
598    }
599
600    /// Append HTML to element. It will NOT be escaped, when the
601    /// element is serialized. This is an easy to inject arbitrary
602    /// junk into the HTML. No validation is done. You should avoid
603    /// this if you can.
604    pub fn push_html(&mut self, html: &str) {
605        self.children.push(Content::html(html));
606    }
607
608    /// Serialize an element into HTML.
609    pub fn serialize(&self) -> String {
610        format!("{}", self)
611    }
612
613    /// Return all the textual content in an element and its children.
614    /// This does not include attributes.
615    pub fn plain_text(&self) -> String {
616        let mut text = TextVisitor::default();
617        text.visit(self);
618        text.text
619    }
620}
621
622impl Display for Element {
623    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
624        if self.tag().can_self_close() && self.children.is_empty() {
625            write!(f, "<{}{}/>", self.tag, self.attrs)?;
626        } else {
627            write!(f, "<{}{}>", self.tag, self.attrs)?;
628            for child in &self.children {
629                write!(f, "{}", child)?;
630            }
631            write!(f, "</{}>", self.tag)?;
632        }
633        Ok(())
634    }
635}
636
637#[cfg(test)]
638mod test_element {
639    use super::{Element, Tag};
640
641    #[test]
642    fn empty_p() {
643        let e = Element::new(Tag::P);
644        assert_eq!(e.to_string(), "<P></P>");
645    }
646
647    #[test]
648    fn empty_br() {
649        let e = Element::new(Tag::Br);
650        assert_eq!(e.to_string(), "<BR/>");
651    }
652}
653
654/// Represent content in HTML.
655#[derive(Clone, Debug, Eq, PartialEq)]
656pub enum Content {
657    /// Non-HTML text.
658    Text(String),
659    /// An HTML element.
660    Element(Element),
661    /// HTML text.
662    Html(String),
663}
664
665impl Content {
666    /// Create a new [`Content::Text`].
667    pub fn text(s: &str) -> Self {
668        Self::Text(s.into())
669    }
670
671    /// Create a new [`Content::Element`].
672    pub fn element(e: &Element) -> Self {
673        Self::Element(e.clone())
674    }
675
676    /// Create a new [`Content::Html`].
677    pub fn html(s: &str) -> Self {
678        Self::Html(s.into())
679    }
680}
681
682impl Display for Content {
683    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
684        match self {
685            Self::Text(s) => write!(f, "{}", encode_safe(s))?,
686            Self::Element(e) => write!(f, "{}", e)?,
687            Self::Html(s) => write!(f, "{}", s)?,
688        }
689        Ok(())
690    }
691}
692
693/// A read-only visitor for an HTML element.
694///
695/// Implementing this trait allows "visiting" element and all of its
696/// children. The provided [`Visitor::visit`] method visits the
697/// element first, and then each of its children in order, and
698/// recursively visits the children of each child.
699///
700/// ~~~
701/// # use html_page::{Element, Tag, Visitor};
702/// #[derive(Default)]
703/// struct Collector {
704///     tags: Vec<Tag>,
705///     text: String,
706/// }
707///
708/// impl Visitor for Collector {
709///     fn visit_element(&mut self, e: &Element) {
710///         self.tags.push(e.tag());
711///     }
712///
713///     fn visit_text(&mut self, s: &str) {
714///         self.text.push_str(s);
715///     }
716/// }
717/// #
718/// # let mut e = Element::new(Tag::P);
719/// # e.push_text("hello ");
720/// # let mut world = Element::new(Tag::B);
721/// # world.push_text("world");
722/// # e.push_child(world);
723/// #
724/// # let mut collector = Collector::default();
725/// # collector.visit(&e);
726/// # assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
727/// # assert_eq!(collector.text, "hello world");
728/// ~~~
729pub trait Visitor {
730    /// Visit an element.
731    fn visit_element(&mut self, _: &Element) {}
732    /// Visit non-HTML text content.
733    fn visit_text(&mut self, _: &str) {}
734    /// Visit literal HTML content.
735    fn visit_html(&mut self, _: &str) {}
736
737    /// Visit recursively an element and each of its children.
738    fn visit(&mut self, root: &Element) {
739        self.visit_element(root);
740        for child in &root.children {
741            match child {
742                Content::Text(s) => self.visit_text(s),
743                Content::Element(e) => self.visit(e),
744                Content::Html(s) => self.visit_html(s),
745            }
746        }
747    }
748}
749
750/// A visitor to extract the text of an element and its children.
751///
752/// This does not include attributes or their values.
753///
754/// Note that you can call [`Element::plain_text`] for simplicity.
755///
756/// ~~~
757/// use html_page::{Element, Tag, TextVisitor, Visitor};
758/// let e = Element::new(Tag::P).with_text("hello, there");
759/// let mut tv = TextVisitor::default();
760/// tv.visit(&e);
761/// assert_eq!(tv.text, "hello, there");
762/// ~~~
763#[derive(Debug, Default)]
764pub struct TextVisitor {
765    /// The text collected by the visitor.
766    pub text: String,
767}
768
769impl Visitor for TextVisitor {
770    fn visit_text(&mut self, s: &str) {
771        self.text.push_str(s);
772    }
773}
774
775#[cfg(test)]
776mod test {
777    use super::{AttributeValue, Content, Element, Tag, Visitor};
778
779    #[test]
780    fn element_has_correct_tag() {
781        let e = Element::new(Tag::P);
782        assert_eq!(e.tag(), Tag::P);
783    }
784
785    #[test]
786    fn element_has_no_attributes_initially() {
787        let e = Element::new(Tag::P);
788        assert_eq!(e.attributes().count(), 0);
789    }
790
791    #[test]
792    fn element_returns_no_value_for_missing_attribute() {
793        let e = Element::new(Tag::P);
794        assert_eq!(e.attribute("foo"), None);
795    }
796
797    #[test]
798    fn can_add_attribute_to_element() {
799        let mut e = Element::new(Tag::P);
800        e.set_attribute("foo", "bar");
801        assert_eq!(
802            e.attribute("foo"),
803            Some(&AttributeValue::String("bar".into()))
804        );
805        assert_eq!(e.attribute("foo").map(|x| x.as_str()), Some("bar"));
806        assert_eq!(e.attribute_value("foo"), Some("bar"));
807    }
808
809    #[test]
810    fn can_create_element_with_attribute() {
811        let e = Element::new(Tag::P).with_attribute("foo", "bar");
812        assert_eq!(
813            e.attribute("foo"),
814            Some(&AttributeValue::String("bar".into()))
815        );
816    }
817
818    #[test]
819    fn can_add_class_to_element() {
820        let mut e = Element::new(Tag::P);
821        e.add_class("foo");
822        let classes: Vec<&str> = e.classes().collect();
823        assert_eq!(classes, ["foo"]);
824        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
825    }
826
827    #[test]
828    fn can_two_classes_to_element() {
829        let mut e = Element::new(Tag::P);
830        e.add_class("foo");
831        e.add_class("bar");
832        let classes: Vec<&str> = e.classes().collect();
833        assert_eq!(classes, ["foo", "bar"]);
834        assert_eq!(e.to_string(), r#"<P class="foo bar"></P>"#);
835    }
836
837    #[test]
838    fn can_add_same_class_twice_to_element() {
839        let mut e = Element::new(Tag::P);
840        e.add_class("foo");
841        e.add_class("foo");
842        let classes: Vec<&str> = e.classes().collect();
843        assert_eq!(classes, ["foo"]);
844        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
845    }
846
847    #[test]
848    fn can_add_boolean_attribute_to_element() {
849        let mut e = Element::new(Tag::P);
850        e.set_boolean_attribute("foo");
851        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
852    }
853
854    #[test]
855    fn can_create_element_with_boolan_attribute() {
856        let e = Element::new(Tag::P).with_boolean_attribute("foo");
857        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
858    }
859
860    #[test]
861    fn unset_attribute_is_unset() {
862        let e = Element::new(Tag::P);
863        assert_eq!(e.attribute("foo"), None);
864    }
865
866    #[test]
867    fn can_unset_attribute_in_element() {
868        let mut e = Element::new(Tag::P);
869        e.set_attribute("foo", "bar");
870        e.unset_attribute("foo");
871        assert_eq!(e.attribute("foo"), None);
872    }
873
874    #[test]
875    fn element_has_no_children_initially() {
876        let e = Element::new(Tag::P);
877        assert!(e.children.is_empty());
878    }
879
880    #[test]
881    fn add_child_to_element() {
882        let mut e = Element::new(Tag::P);
883        let child = Content::text("foo");
884        e.push_text("foo");
885        assert_eq!(e.children, &[child]);
886    }
887
888    #[test]
889    fn element_has_no_location_initially() {
890        let e = Element::new(Tag::P);
891        assert!(e.location().is_none());
892    }
893
894    #[test]
895    fn element_with_location() {
896        let e = Element::new(Tag::P).with_location(1, 2);
897        assert_eq!(e.location(), Some((1, 2)));
898    }
899
900    #[test]
901    fn attribute_can_be_serialized() {
902        let mut e = Element::new(Tag::P);
903        e.set_attribute("foo", "bar");
904        assert_eq!(e.serialize(), "<P foo=\"bar\"></P>");
905    }
906
907    #[test]
908    fn dangerous_attribute_value_is_esacped() {
909        let mut e = Element::new(Tag::P);
910        e.set_attribute("foo", "<");
911        assert_eq!(e.serialize(), "<P foo=\"&lt;\"></P>");
912    }
913
914    #[test]
915    fn boolean_attribute_can_be_serialized() {
916        let mut e = Element::new(Tag::P);
917        e.set_boolean_attribute("foo");
918        assert_eq!(e.serialize(), "<P foo></P>");
919    }
920
921    #[test]
922    fn element_can_be_serialized() {
923        let mut e = Element::new(Tag::P);
924        e.push_text("hello ");
925        let mut world = Element::new(Tag::B);
926        world.push_text("world");
927        e.push_child(world);
928        assert_eq!(e.serialize(), "<P>hello <B>world</B></P>");
929    }
930
931    #[test]
932    fn dangerous_text_is_escaped() {
933        let mut e = Element::new(Tag::P);
934        e.push_text("hello <world>");
935        assert_eq!(e.serialize(), "<P>hello &lt;world&gt;</P>");
936    }
937
938    #[test]
939    fn element_has_no_class_initially() {
940        let e = Element::new(Tag::P);
941        assert_eq!(e.attribute_value("class"), None);
942        assert_eq!(e.classes().next(), None);
943        assert!(!e.has_class("foo"));
944    }
945
946    #[test]
947    fn element_adds_first_class() {
948        let mut e = Element::new(Tag::P);
949        e.add_class("foo");
950        assert_eq!(e.attribute_value("class"), Some("foo"));
951        assert!(e.has_class("foo"));
952    }
953
954    #[test]
955    fn element_adds_second_class() {
956        let mut e = Element::new(Tag::P);
957        e.add_class("foo");
958        e.add_class("bar");
959        assert_eq!(e.attribute_value("class"), Some("foo bar"));
960        assert!(e.has_class("foo"));
961        assert!(e.has_class("bar"));
962    }
963
964    #[test]
965    fn creates_classy_element() {
966        let e = Element::new(Tag::P).with_class("foo").with_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    #[derive(Default)]
973    struct Collector {
974        tags: Vec<Tag>,
975        text: String,
976    }
977
978    impl Visitor for Collector {
979        fn visit_element(&mut self, e: &Element) {
980            self.tags.push(e.tag());
981        }
982
983        fn visit_text(&mut self, s: &str) {
984            self.text.push_str(s);
985        }
986    }
987
988    #[test]
989    fn visits_all_children() {
990        let e = Element::new(Tag::P)
991            .with_text("hello ")
992            .with_child(Element::new(Tag::B).with_text("world"));
993
994        let mut collector = Collector::default();
995        collector.visit(&e);
996        assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
997        assert_eq!(collector.text, "hello world");
998    }
999}