Skip to main content

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: impl Into<Element>) {
55        self.head.push_child(e.into());
56    }
57
58    /// Append an element to the body.
59    pub fn push_to_body(&mut self, e: impl Into<Element>) {
60        self.body.push_child(e.into());
61    }
62
63    /// Append an element to the head, when constructing.
64    pub fn with_head_element(mut self, e: impl Into<Element>) -> Self {
65        self.head.push_child(e.into());
66        self
67    }
68
69    /// Append an element to the body, when constructing.
70    pub fn with_body_element(mut self, e: impl Into<Element>) -> Self {
71        self.body.push_child(e.into());
72        self
73    }
74
75    /// Append text to the body, when constructing.
76    pub fn with_body_text(mut self, text: impl Into<String>) -> 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: impl Into<Element>) {
83        for child in &e.into().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: impl Into<String>, value: impl Into<String>) {
391        self.attrs
392            .insert(name.into(), AttributeValue::String(value.into()));
393    }
394
395    fn set_boolean(&mut self, name: impl Into<String>) {
396        self.attrs.insert(name.into(), AttributeValue::Boolean);
397    }
398
399    fn unset(&mut self, name: impl AsRef<str>) {
400        self.attrs.remove(name.as_ref());
401    }
402
403    fn get(&self, name: impl AsRef<str>) -> Option<&AttributeValue> {
404        self.attrs.get(name.as_ref())
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    /// Create a P element.
479    pub fn p() -> Self {
480        Self::new(Tag::P)
481    }
482
483    /// Create a Span element.
484    pub fn span() -> Self {
485        Self::new(Tag::Span)
486    }
487
488    /// Create a Div element.
489    pub fn div() -> Self {
490        Self::new(Tag::Div)
491    }
492
493    /// Create a Code element.
494    pub fn code() -> Self {
495        Self::new(Tag::Code)
496    }
497
498    /// Create an H1 element.
499    pub fn h1() -> Self {
500        Self::new(Tag::H1)
501    }
502
503    /// Create an H2 element.
504    pub fn h2() -> Self {
505        Self::new(Tag::H2)
506    }
507
508    /// Create an H3 element.
509    pub fn h3() -> Self {
510        Self::new(Tag::H3)
511    }
512
513    /// Set the location of an element in a source file.
514    pub fn with_location(mut self, line: usize, col: usize) -> Self {
515        self.loc = Some((line, col));
516        self
517    }
518
519    /// Append a child element, when constructing.
520    pub fn with_child(mut self, child: Element) -> Self {
521        self.children.push(Content::Element(child));
522        self
523    }
524
525    /// Append a text child, when constructing.
526    pub fn with_text(mut self, child: impl Into<String>) -> Self {
527        self.children.push(Content::Text(child.into()));
528        self
529    }
530
531    /// Set an attribute when creating an element.
532    pub fn with_attribute(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
533        self.attrs.set(name.as_ref(), value.as_ref());
534        self
535    }
536
537    /// Set a boolean attribute when creating an element.
538    pub fn with_boolean_attribute(mut self, name: impl Into<String>) -> Self {
539        self.attrs.set_boolean(name);
540        self
541    }
542
543    /// Add a class when creating an element.
544    pub fn with_class(mut self, class: impl Into<String>) -> Self {
545        self.add_class(class);
546        self
547    }
548
549    /// Return the [`Tag`] of the element.
550    pub fn tag(&self) -> Tag {
551        self.tag
552    }
553
554    /// Return the location of the element.
555    pub fn location(&self) -> Option<(usize, usize)> {
556        self.loc
557    }
558
559    /// Return list children.
560    pub fn children(&self) -> &[Content] {
561        &self.children
562    }
563
564    /// Return an iterator over the names of the attributes of an element.
565    pub fn attributes(&self) -> impl Iterator<Item = &str> {
566        self.attrs.names()
567    }
568
569    /// Return the value of an attribute, if the attribute is set.
570    /// Otherwise, return `None`.
571    pub fn attribute(&self, name: impl AsRef<str>) -> Option<&AttributeValue> {
572        self.attrs.get(name.as_ref())
573    }
574
575    /// Return the value of an attribute as text, if the attribute is
576    /// set. Otherwise, return `None`.
577    pub fn attribute_value(&self, name: impl AsRef<str>) -> Option<&str> {
578        self.attrs.get(name.as_ref()).map(|v| v.as_str())
579    }
580
581    /// Set a key/value attribute. If the attribute was already set,
582    /// change the value it has.
583    pub fn set_attribute(&mut self, name: impl Into<String>, value: impl Into<String>) {
584        self.attrs.set(name, value);
585    }
586
587    /// Set a boolean attribute.
588    pub fn set_boolean_attribute(&mut self, name: impl Into<String>) {
589        self.attrs.set_boolean(name);
590    }
591
592    /// Remove an attribute, which can be key/value or boolean.
593    pub fn unset_attribute(&mut self, name: impl AsRef<str>) {
594        self.attrs.unset(name.as_ref());
595    }
596
597    /// Return current classes set directly for this element.
598    pub fn classes(&self) -> impl Iterator<Item = &str> {
599        let v = self.attribute_value("class").unwrap_or_default();
600        v.split_ascii_whitespace()
601    }
602
603    /// Does the element have a class set directly?
604    pub fn has_class(&self, wanted: impl AsRef<str>) -> bool {
605        let wanted = wanted.as_ref();
606        self.classes().any(|v| v == wanted)
607    }
608
609    /// Add a class to the element. This does not replace existing
610    /// classes.
611    pub fn add_class(&mut self, class: impl Into<String>) {
612        let class = class.into();
613        if let Some(old) = self.attribute_value("class") {
614            if !old.split_ascii_whitespace().any(|s| s == class) {
615                self.set_attribute("class", format!("{old} {class}"));
616            }
617        } else {
618            self.set_attribute("class", class);
619        }
620    }
621
622    /// Append text to element. It will be escaped, if needed, when
623    /// the element is serialized.
624    pub fn push_text(&mut self, text: impl Into<String>) {
625        self.children.push(Content::text(text));
626    }
627
628    /// Append a child element to this element.
629    pub fn push_child(&mut self, child: Element) {
630        self.children.push(Content::element(&child));
631    }
632
633    /// Append several child elements to this element.
634    pub fn push_children(&mut self, children: &[Element]) {
635        for child in children {
636            self.children.push(Content::element(child));
637        }
638    }
639
640    /// Remove all children.
641    pub fn clear_children(&mut self) {
642        self.children.clear();
643    }
644
645    /// Append HTML to element. It will NOT be escaped, when the
646    /// element is serialized. This is an easy to inject arbitrary
647    /// junk into the HTML. No validation is done. You should avoid
648    /// this if you can.
649    pub fn push_html(&mut self, html: impl Into<String>) {
650        self.children.push(Content::html(html));
651    }
652
653    /// Serialize an element into HTML.
654    pub fn serialize(&self) -> String {
655        format!("{self}")
656    }
657
658    /// Return all the textual content in an element and its children.
659    /// This does not include attributes.
660    pub fn plain_text(&self) -> String {
661        let mut text = TextVisitor::default();
662        text.visit(self);
663        text.text
664    }
665}
666
667impl Display for Element {
668    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
669        if self.tag().can_self_close() && self.children.is_empty() {
670            write!(f, "<{}{}/>", self.tag, self.attrs)?;
671        } else {
672            write!(f, "<{}{}>", self.tag, self.attrs)?;
673            for child in &self.children {
674                write!(f, "{child}")?;
675            }
676            write!(f, "</{}>", self.tag)?;
677        }
678        Ok(())
679    }
680}
681
682impl From<&Element> for Element {
683    fn from(value: &Element) -> Self {
684        value.clone()
685    }
686}
687
688#[cfg(test)]
689mod test_element {
690    use super::{Element, Tag};
691
692    #[test]
693    fn empty_p() {
694        let e = Element::new(Tag::P);
695        assert_eq!(e.to_string(), "<P></P>");
696    }
697
698    #[test]
699    fn empty_br() {
700        let e = Element::new(Tag::Br);
701        assert_eq!(e.to_string(), "<BR/>");
702    }
703}
704
705/// Represent content in HTML.
706#[derive(Clone, Debug, Eq, PartialEq)]
707pub enum Content {
708    /// Non-HTML text.
709    Text(String),
710    /// An HTML element.
711    Element(Element),
712    /// HTML text.
713    Html(String),
714}
715
716impl Content {
717    /// Create a new [`Content::Text`].
718    pub fn text(s: impl Into<String>) -> Self {
719        Self::Text(s.into())
720    }
721
722    /// Create a new [`Content::Element`].
723    pub fn element(e: &Element) -> Self {
724        Self::Element(e.clone())
725    }
726
727    /// Create a new [`Content::Html`].
728    pub fn html(s: impl Into<String>) -> Self {
729        Self::Html(s.into())
730    }
731}
732
733impl Display for Content {
734    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
735        match self {
736            Self::Text(s) => write!(f, "{}", encode_safe(s))?,
737            Self::Element(e) => write!(f, "{e}")?,
738            Self::Html(s) => write!(f, "{s}")?,
739        }
740        Ok(())
741    }
742}
743
744/// A read-only visitor for an HTML element.
745///
746/// Implementing this trait allows "visiting" element and all of its
747/// children. The provided [`Visitor::visit`] method visits the
748/// element first, and then each of its children in order, and
749/// recursively visits the children of each child.
750///
751/// ~~~
752/// # use html_page::{Element, Tag, Visitor};
753/// #[derive(Default)]
754/// struct Collector {
755///     tags: Vec<Tag>,
756///     text: String,
757/// }
758///
759/// impl Visitor for Collector {
760///     fn visit_element(&mut self, e: &Element) {
761///         self.tags.push(e.tag());
762///     }
763///
764///     fn visit_text(&mut self, s: &str) {
765///         self.text.push_str(s);
766///     }
767/// }
768/// #
769/// # let mut e = Element::new(Tag::P);
770/// # e.push_text("hello ");
771/// # let mut world = Element::new(Tag::B);
772/// # world.push_text("world");
773/// # e.push_child(world);
774/// #
775/// # let mut collector = Collector::default();
776/// # collector.visit(&e);
777/// # assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
778/// # assert_eq!(collector.text, "hello world");
779/// ~~~
780pub trait Visitor {
781    /// Visit an element.
782    fn visit_element(&mut self, _: &Element) {}
783    /// Visit non-HTML text content.
784    fn visit_text(&mut self, _: &str) {}
785    /// Visit literal HTML content.
786    fn visit_html(&mut self, _: &str) {}
787
788    /// Visit recursively an element and each of its children.
789    fn visit(&mut self, root: &Element) {
790        self.visit_element(root);
791        for child in &root.children {
792            match child {
793                Content::Text(s) => self.visit_text(s),
794                Content::Element(e) => self.visit(e),
795                Content::Html(s) => self.visit_html(s),
796            }
797        }
798    }
799}
800
801/// A visitor to extract the text of an element and its children.
802///
803/// This does not include attributes or their values.
804///
805/// Note that you can call [`Element::plain_text`] for simplicity.
806///
807/// ~~~
808/// use html_page::{Element, Tag, TextVisitor, Visitor};
809/// let e = Element::new(Tag::P).with_text("hello, there");
810/// let mut tv = TextVisitor::default();
811/// tv.visit(&e);
812/// assert_eq!(tv.text, "hello, there");
813/// ~~~
814#[derive(Debug, Default)]
815pub struct TextVisitor {
816    /// The text collected by the visitor.
817    pub text: String,
818}
819
820impl Visitor for TextVisitor {
821    fn visit_text(&mut self, s: &str) {
822        self.text.push_str(s);
823    }
824}
825
826#[cfg(test)]
827mod test {
828    use super::{AttributeValue, Content, Element, Tag, Visitor};
829
830    #[test]
831    fn element_has_correct_tag() {
832        let e = Element::new(Tag::P);
833        assert_eq!(e.tag(), Tag::P);
834    }
835
836    #[test]
837    fn element_has_no_attributes_initially() {
838        let e = Element::new(Tag::P);
839        assert_eq!(e.attributes().count(), 0);
840    }
841
842    #[test]
843    fn element_returns_no_value_for_missing_attribute() {
844        let e = Element::new(Tag::P);
845        assert_eq!(e.attribute("foo"), None);
846    }
847
848    #[test]
849    fn can_add_attribute_to_element() {
850        let mut e = Element::new(Tag::P);
851        e.set_attribute("foo", "bar");
852        assert_eq!(
853            e.attribute("foo"),
854            Some(&AttributeValue::String("bar".into()))
855        );
856        assert_eq!(e.attribute("foo").map(|x| x.as_str()), Some("bar"));
857        assert_eq!(e.attribute_value("foo"), Some("bar"));
858    }
859
860    #[test]
861    fn can_create_element_with_attribute() {
862        let e = Element::new(Tag::P).with_attribute("foo", "bar");
863        assert_eq!(
864            e.attribute("foo"),
865            Some(&AttributeValue::String("bar".into()))
866        );
867    }
868
869    #[test]
870    fn can_add_class_to_element() {
871        let mut e = Element::new(Tag::P);
872        e.add_class("foo");
873        let classes: Vec<&str> = e.classes().collect();
874        assert_eq!(classes, ["foo"]);
875        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
876    }
877
878    #[test]
879    fn can_two_classes_to_element() {
880        let mut e = Element::new(Tag::P);
881        e.add_class("foo");
882        e.add_class("bar");
883        let classes: Vec<&str> = e.classes().collect();
884        assert_eq!(classes, ["foo", "bar"]);
885        assert_eq!(e.to_string(), r#"<P class="foo bar"></P>"#);
886    }
887
888    #[test]
889    fn can_add_same_class_twice_to_element() {
890        let mut e = Element::new(Tag::P);
891        e.add_class("foo");
892        e.add_class("foo");
893        let classes: Vec<&str> = e.classes().collect();
894        assert_eq!(classes, ["foo"]);
895        assert_eq!(e.to_string(), r#"<P class="foo"></P>"#);
896    }
897
898    #[test]
899    fn can_add_boolean_attribute_to_element() {
900        let mut e = Element::new(Tag::P);
901        e.set_boolean_attribute("foo");
902        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
903    }
904
905    #[test]
906    fn can_create_element_with_boolan_attribute() {
907        let e = Element::new(Tag::P).with_boolean_attribute("foo");
908        assert_eq!(e.attribute("foo"), Some(&AttributeValue::Boolean));
909    }
910
911    #[test]
912    fn unset_attribute_is_unset() {
913        let e = Element::new(Tag::P);
914        assert_eq!(e.attribute("foo"), None);
915    }
916
917    #[test]
918    fn can_unset_attribute_in_element() {
919        let mut e = Element::new(Tag::P);
920        e.set_attribute("foo", "bar");
921        e.unset_attribute("foo");
922        assert_eq!(e.attribute("foo"), None);
923    }
924
925    #[test]
926    fn element_has_no_children_initially() {
927        let e = Element::new(Tag::P);
928        assert!(e.children.is_empty());
929    }
930
931    #[test]
932    fn add_child_to_element() {
933        let mut e = Element::new(Tag::P);
934        let child = Content::text("foo");
935        e.push_text("foo");
936        assert_eq!(e.children(), &[child]);
937    }
938
939    #[test]
940    fn element_has_no_location_initially() {
941        let e = Element::new(Tag::P);
942        assert!(e.location().is_none());
943    }
944
945    #[test]
946    fn element_with_location() {
947        let e = Element::new(Tag::P).with_location(1, 2);
948        assert_eq!(e.location(), Some((1, 2)));
949    }
950
951    #[test]
952    fn attribute_can_be_serialized() {
953        let mut e = Element::new(Tag::P);
954        e.set_attribute("foo", "bar");
955        assert_eq!(e.serialize(), "<P foo=\"bar\"></P>");
956    }
957
958    #[test]
959    fn dangerous_attribute_value_is_esacped() {
960        let mut e = Element::new(Tag::P);
961        e.set_attribute("foo", "<");
962        assert_eq!(e.serialize(), "<P foo=\"&lt;\"></P>");
963    }
964
965    #[test]
966    fn boolean_attribute_can_be_serialized() {
967        let mut e = Element::new(Tag::P);
968        e.set_boolean_attribute("foo");
969        assert_eq!(e.serialize(), "<P foo></P>");
970    }
971
972    #[test]
973    fn element_can_be_serialized() {
974        let mut e = Element::new(Tag::P);
975        e.push_text("hello ");
976        let mut world = Element::new(Tag::B);
977        world.push_text("world");
978        e.push_child(world);
979        assert_eq!(e.serialize(), "<P>hello <B>world</B></P>");
980    }
981
982    #[test]
983    fn dangerous_text_is_escaped() {
984        let mut e = Element::new(Tag::P);
985        e.push_text("hello <world>");
986        assert_eq!(e.serialize(), "<P>hello &lt;world&gt;</P>");
987    }
988
989    #[test]
990    fn element_has_no_class_initially() {
991        let e = Element::new(Tag::P);
992        assert_eq!(e.attribute_value("class"), None);
993        assert_eq!(e.classes().next(), None);
994        assert!(!e.has_class("foo"));
995    }
996
997    #[test]
998    fn element_adds_first_class() {
999        let mut e = Element::new(Tag::P);
1000        e.add_class("foo");
1001        assert_eq!(e.attribute_value("class"), Some("foo"));
1002        assert!(e.has_class("foo"));
1003    }
1004
1005    #[test]
1006    fn element_adds_second_class() {
1007        let mut e = Element::new(Tag::P);
1008        e.add_class("foo");
1009        e.add_class("bar");
1010        assert_eq!(e.attribute_value("class"), Some("foo bar"));
1011        assert!(e.has_class("foo"));
1012        assert!(e.has_class("bar"));
1013    }
1014
1015    #[test]
1016    fn creates_classy_element() {
1017        let e = Element::new(Tag::P).with_class("foo").with_class("bar");
1018        assert_eq!(e.attribute_value("class"), Some("foo bar"));
1019        assert!(e.has_class("foo"));
1020        assert!(e.has_class("bar"));
1021    }
1022
1023    #[derive(Default)]
1024    struct Collector {
1025        tags: Vec<Tag>,
1026        text: String,
1027    }
1028
1029    impl Visitor for Collector {
1030        fn visit_element(&mut self, e: &Element) {
1031            self.tags.push(e.tag());
1032        }
1033
1034        fn visit_text(&mut self, s: &str) {
1035            self.text.push_str(s);
1036        }
1037    }
1038
1039    #[test]
1040    fn visits_all_children() {
1041        let e = Element::new(Tag::P)
1042            .with_text("hello ")
1043            .with_child(Element::new(Tag::B).with_text("world"));
1044
1045        let mut collector = Collector::default();
1046        collector.visit(&e);
1047        assert_eq!(collector.tags, vec![Tag::P, Tag::B]);
1048        assert_eq!(collector.text, "hello world");
1049    }
1050}