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