parse_hyperlinks/parser/
html_img.rs

1//! This module implements parsers for HTML image elements.
2#![allow(dead_code)]
3
4use crate::parser::html::attribute_list;
5use crate::parser::html::tag_a_opening as href_tag_a_opening;
6use crate::parser::Link;
7use html_escape::decode_html_entities;
8use nom::branch::alt;
9use nom::bytes::complete::is_not;
10use nom::bytes::complete::tag;
11use nom::error::Error;
12use nom::error::ErrorKind;
13use nom::sequence::tuple;
14use std::borrow::Cow;
15
16/// Wrapper around `html_img()` that packs the result in
17/// `Link::Image`.
18pub fn html_img_link(i: &str) -> nom::IResult<&str, Link> {
19    let (i, (alt, src)) = html_img(i)?;
20    Ok((i, Link::Image(alt, src)))
21}
22
23/// Parse an HTML _image_.
24///
25/// It returns either `Ok((i, (img_alt, img_src)))` or some error.
26///
27/// The parser expects to start at the link start (`<`) to succeed.
28/// ```
29/// use parse_hyperlinks;
30/// use parse_hyperlinks::parser::html_img::html_img;
31/// use std::borrow::Cow;
32///
33/// assert_eq!(
34///   html_img(r#"<img src="/images/my&amp;dog.png" alt="my Dog" width="500">abc"#),
35///   Ok(("abc", (Cow::from("my Dog"), Cow::from("/images/my&dog.png"))))
36/// );
37/// ```
38pub fn html_img(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>)> {
39    tag_img(i)
40}
41
42/// Parses a `<img ...>` tag and returns
43/// either `Ok((i, (img_alt, img_src)))` or some error.
44#[inline]
45fn tag_img(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>)> {
46    nom::sequence::delimited(
47        // HTML is case insensitive. XHTML, that is being XML is case sensitive.
48        // Here we deal with HTML.
49        alt((tag("<img "), tag("<IMG "))),
50        nom::combinator::map_parser(is_not(">"), parse_attributes),
51        tag(">"),
52    )(i)
53}
54
55/// Wrapper around `html_img()` that packs the result in
56/// `Link::Image`.
57pub fn html_img2dest_link(i: &str) -> nom::IResult<&str, Link> {
58    let (i, (text1, img_alt, img_src, text2, dest, title)) = html_img2dest(i)?;
59    Ok((
60        i,
61        Link::Image2Dest(text1, img_alt, img_src, text2, dest, title),
62    ))
63}
64
65/// Parse an HTML inline hyperlink with embedded image.
66///
67/// It returns either
68// `Ok((i, (text1, img_alt, img_src, text2, dest, title)))` or some error.
69///
70///
71/// The parser expects to start at the link start (`<`) to succeed.
72/// ```
73/// use parse_hyperlinks::parser::Link;
74/// use parse_hyperlinks::parser::html_img::html_img2dest;
75/// use std::borrow::Cow;
76///
77/// assert_eq!(
78///   html_img2dest("<a href=\"my doc.html\" title=\"title\">\
79///                    before<img src=\"dog.png\" alt=\"alt dog\"/>after\
80///                    </a>abc"),
81///   Ok(("abc",
82///    (Cow::from("before"), Cow::from("alt dog"), Cow::from("dog.png"),
83///     Cow::from("after"), Cow::from("my doc.html"), Cow::from("title"),
84/// ))));
85/// ```
86pub fn html_img2dest(
87    i: &str,
88) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>, Cow<str>, Cow<str>, Cow<str>)> {
89    let (i, ((dest, title), text)) = nom::sequence::terminated(
90        nom::sequence::pair(
91            href_tag_a_opening,
92            alt((
93                nom::bytes::complete::take_until("</a>"),
94                nom::bytes::complete::take_until("</A>"),
95            )),
96        ),
97        // HTML is case insensitive. XHTML, that is being XML is case sensitive.
98        // Here we deal with HTML.
99        alt((tag("</a>"), tag("</A>"))),
100    )(i)?;
101
102    let (_, (text1, (img_alt, img_src), text2)) = tuple((
103        nom::bytes::complete::take_until("<img"),
104        html_img,
105        nom::combinator::rest,
106    ))(text)?;
107
108    let text1 = decode_html_entities(text1);
109    let text2 = decode_html_entities(text2);
110
111    Ok((i, (text1, img_alt, img_src, text2, dest, title)))
112}
113
114/// Extracts the `src` and `alt` attributes and returns
115/// `Ok((img_alt, img_src))`. `img_alt` can be empty,
116/// `img_src` not.
117fn parse_attributes(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>)> {
118    let (i, attributes) = attribute_list(i)?;
119    let mut src = Cow::Borrowed("");
120    let mut alt = Cow::Borrowed("");
121
122    for (name, value) in attributes {
123        if name == "src" {
124            // Make sure `src` is empty, it can appear only
125            // once.
126            if !(&*src).is_empty() {
127                return Err(nom::Err::Error(Error::new(name, ErrorKind::ManyMN)));
128            }
129            src = value;
130        } else if name == "alt" {
131            // Make sure `title` is empty, it can appear only
132            // once.
133            if !(&*alt).is_empty() {
134                return Err(nom::Err::Error(Error::new(name, ErrorKind::ManyMN)));
135            }
136            alt = value;
137        }
138    }
139
140    // Assure that `href` is not empty.
141    if (&*src).is_empty() {
142        return Err(nom::Err::Error(Error::new(i, ErrorKind::Eof)));
143    };
144
145    Ok((i, (alt, src)))
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::parser::html::attribute_list;
152
153    #[test]
154    fn test_tag_img() {
155        let expected = (
156            "abc",
157            (
158                Cow::from("My dog"),
159                Cow::from("http://getreu.net/my&dog.png"),
160            ),
161        );
162        assert_eq!(
163            tag_img(r#"<img src="http://getreu.net/my&amp;dog.png" alt="My dog">abc"#).unwrap(),
164            expected
165        );
166        assert_eq!(
167            tag_img(r#"<IMG src="http://getreu.net/my&amp;dog.png" alt="My dog">abc"#).unwrap(),
168            expected
169        );
170        assert_eq!(
171            tag_img(r#"<IMG src="http://getreu.net/my&amp;dog.png" alt="My dog"/>abc"#).unwrap(),
172            expected
173        );
174        assert_eq!(
175            tag_img(r#"<IMG src="http://getreu.net/my&amp;dog.png" alt="My dog" />abc"#).unwrap(),
176            expected
177        );
178
179        let expected = (
180            "abc",
181            (Cow::from("Some picture"), Cow::from("t%20m%20p.jpg")),
182        );
183        assert_eq!(
184            tag_img(r#"<img src="t%20m%20p.jpg" alt="Some picture" />abc"#).unwrap(),
185            expected
186        );
187    }
188
189    #[test]
190    fn test_parse_attributes() {
191        let expected = (
192            "",
193            (
194                Cow::from("My dog"),
195                Cow::from("http://getreu.net/my&dog.png"),
196            ),
197        );
198        assert_eq!(
199            parse_attributes(r#"abc src="http://getreu.net/my&amp;dog.png" abc alt="My dog" abc"#)
200                .unwrap(),
201            expected
202        );
203
204        let expected =
205            nom::Err::Error(nom::error::Error::new("src", nom::error::ErrorKind::ManyMN));
206        assert_eq!(
207            parse_attributes(r#" src="http://getreu.net" src="http://blog.getreu.net" "#)
208                .unwrap_err(),
209            expected
210        );
211
212        let expected =
213            nom::Err::Error(nom::error::Error::new("alt", nom::error::ErrorKind::ManyMN));
214        assert_eq!(
215            parse_attributes(r#" src="http://getreu.net" alt="a" alt="b" "#).unwrap_err(),
216            expected
217        );
218
219        let expected = nom::Err::Error(nom::error::Error::new("", nom::error::ErrorKind::Eof));
220        assert_eq!(
221            parse_attributes(r#" title="title" "#).unwrap_err(),
222            expected
223        );
224    }
225
226    #[test]
227    fn test_attribute_list() {
228        let expected = (
229            "",
230            vec![
231                ("", Cow::from("")),
232                ("src", Cow::from("http://getreu.net/my&dog.png")),
233                ("", Cow::from("")),
234                ("alt", Cow::from("My dog")),
235                ("", Cow::from("")),
236            ],
237        );
238        assert_eq!(
239            attribute_list(r#"abc src="http://getreu.net/my&amp;dog.png" abc alt="My dog" abc"#)
240                .unwrap(),
241            expected
242        );
243    }
244}