xmltv 2.1.0

XMLTV for electronic program guide (EPG) parser and generator using serde.
Documentation
use std::str::FromStr;

use quick_xml::de::from_str;
use quick_xml::se::to_string_with_root;
use xmltv::*;

#[test]
fn test_divmod() {
    // test secondes
    let time = Length {
        length: 100,
        units: Units::Seconds,
    };
    assert_eq!(time.to_hms(), (0, 1, 40));
    let time = Length {
        length: 7000,
        units: Units::Seconds,
    };
    assert_eq!(time.to_hms(), (1, 56, 40));
    //test minutes
    let time = Length {
        length: 112,
        units: Units::Minutes,
    };
    assert_eq!(time.to_hms(), (1, 52, 0));
    //test hours
    let time = Length {
        length: 2,
        units: Units::Hours,
    };
    assert_eq!(time.to_hms(), (2, 0, 0));
}

#[test]
fn test_empty_xmltv() {
    let item: Tv = from_str("<tv/>").unwrap();
    assert_eq!(
        item,
        Tv {
            source_info_url: None,
            source_info_name: None,
            source_data_url: None,
            generator_info_name: None,
            generator_info_url: None,
            channels: Vec::with_capacity(0),
            programmes: Vec::with_capacity(0),
        }
    );
    let empty: Tv = Tv {
        source_info_url: None,
        source_info_name: None,
        source_data_url: None,
        generator_info_name: None,
        generator_info_url: None,
        channels: Vec::with_capacity(0),
        programmes: Vec::with_capacity(0),
    };
    assert_eq!(to_string_with_root("tv", &empty).unwrap(), "<tv/>");
}

#[test]
fn test_generator_info_empty_xmltv() {
    let expected = "<tv generator-info-name=\"xmltv-rs\"/>";
    let item: Tv = from_str("<tv generator-info-name=\"xmltv-rs\" />").unwrap();
    assert_eq!(
        item,
        Tv {
            source_info_url: None,
            source_info_name: None,
            source_data_url: None,
            generator_info_name: Some(String::from_str("xmltv-rs").unwrap()),
            generator_info_url: None,
            channels: Vec::with_capacity(0),
            programmes: Vec::with_capacity(0),
        }
    );

    assert_eq!(to_string_with_root("tv", &item).unwrap(), expected);
}

#[test]
pub fn test_xmltv_one_channel() {
    let input = "<tv generator-info-name=\"xmltv-rs\">
\t<channel id=\"1\">
\t\t<display-name>TF1</display-name>
\t</channel>
</tv>\n";
    let item: Tv = from_str(input).unwrap();
    assert_eq!(
        item,
        Tv {
            source_info_url: None,
            source_info_name: None,
            source_data_url: None,
            generator_info_name: Some(String::from_str("xmltv-rs").unwrap()),
            generator_info_url: None,
            channels: vec![Channel {
                id: String::from_str("1").unwrap(),
                display_names: vec![NameAndLang {
                    name: String::from_str("TF1").unwrap(),
                    lang: None
                }],
                icons: Vec::with_capacity(0),
                urls: Vec::with_capacity(0)
            }],
            programmes: Vec::with_capacity(0),
        }
    );
    let expected = "<tv generator-info-name=\"xmltv-rs\"><channel id=\"1\"><display-name>TF1</display-name></channel></tv>";
    assert_eq!(to_string_with_root("tv", &item).unwrap(), expected);
}

#[test]
pub fn test_xmltv_programs() {
    let mut tv = xmltv::Tv::default();
    let programs = vec![
        (
            "1",
            "Les feux de l'amour é",
            None,
            "2021-10-09 12:00:00 +0200",
            "2021-10-09 13:00:00 +0200",
        ),
        (
            "2",
            "Le journal",
            Some("fr-FR"),
            "2021-10-09 12:20:00 +0200",
            "2021-10-09 12:35:00 +0200",
        ),
        (
            "3",
            "Le journal",
            None,
            "2021-10-09 13:00:00 +0200",
            "2021-10-09 13:40:00 +0200",
        ),
    ];

    for p in programs {
        let title = ValueAndLang {
            value: p.1.into(),
            lang: p.2.map(|v| v.into()),
        };
        let programme = Programme {
            channel: p.0.into(),
            start: p.3.into(),
            titles: vec![title],
            stop: Some(p.4.into()),
            ..Default::default()
        };

        tv.programmes.push(programme);
    }

    let expected = "<tv>\
<programme channel=\"1\" start=\"2021-10-09 12:00:00 +0200\" stop=\"2021-10-09 13:00:00 +0200\">\
<title>Les feux de l'amour é</title>\
</programme>\
<programme channel=\"2\" start=\"2021-10-09 12:20:00 +0200\" stop=\"2021-10-09 12:35:00 +0200\">\
<title lang=\"fr-FR\">Le journal</title>\
</programme>\
<programme channel=\"3\" start=\"2021-10-09 13:00:00 +0200\" stop=\"2021-10-09 13:40:00 +0200\">\
<title>Le journal</title>\
</programme>\
</tv>";
    assert_eq!(to_string_with_root("tv", &tv).unwrap(), expected);

    let item: Tv = from_str(expected).unwrap();
    assert_eq!(item, tv);

    // test with a `<new/>` tag
    tv.programmes[0].new = true;
    tv.programmes.pop();
    tv.programmes.pop();
    let expected = "<tv><programme channel=\"1\" start=\"2021-10-09 12:00:00 +0200\" stop=\"2021-10-09 13:00:00 +0200\">\
<title>Les feux de l'amour é</title><new/>\
</programme></tv>";
    assert_eq!(to_string_with_root("tv", &tv).unwrap(), expected);

    let item: Tv = from_str(expected).unwrap();
    assert_eq!(item, tv);
}

#[test]
fn test_simple_epg() {
    // from https://github.com/XMLTV/xmltv/blob/master/t/data/simple.xml
    let raw = r#"<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE tv SYSTEM "xmltv.dtd">
    <tv>
      <programme channel="bbc2.bbc.co.uk" start="20010829000500 BST">
        <title>The Phil Silvers Show</title>
        <desc>Bilko claims he's had a close encounter with an alien in order to be given some compassionate leave so he can visit an old flame in New York.</desc>
      </programme>
      <programme channel="channel4.com" start="20010829095500 BST">
        <title>King of the Hill</title>
        <sub-title>Meet the Propaniacs</sub-title>
        <desc>Bobby tours with a comedy troupe who specialize in propane-related mirth.</desc>
        <credits>
          <actor>Mike Judge</actor>
          <actor>Lane Smith</actor>
        </credits>
        <category>animation</category>
        <previously-shown />
      </programme>
    </tv>"#;
    let item: Tv = from_str(raw).unwrap();
    assert_eq!(
        item,
        Tv {
            programmes: vec![
                Programme {
                    channel: String::from("bbc2.bbc.co.uk"),
                    start: String::from("20010829000500 BST"),
                    titles: vec![ValueAndLang {
                        value: String::from("The Phil Silvers Show"),
                        lang: None
                    }],
                    descriptions: vec![ValueAndLang { value: String::from("Bilko claims he's had a close encounter with an alien in order to be given some compassionate leave so he can visit an old flame in New York."), lang: None}],
                    ..Default::default()
                },
                Programme {
                    channel: String::from("channel4.com"),
                    start: String::from("20010829095500 BST"),
                    titles: vec![ValueAndLang {
                        value: String::from("King of the Hill"),
                        lang: None
                    }],
                    sub_titles: vec![ValueAndLang { value: String::from("Meet the Propaniacs"), lang: None }],
                    descriptions: vec![ValueAndLang { value: String::from("Bobby tours with a comedy troupe who specialize in propane-related mirth."), lang: None}],
                    credits: Some(Credits {actors: vec![Actor { name: String::from("Mike Judge"), ..Default::default() }, Actor { name: String::from("Lane Smith"), ..Default::default() }], ..Default::default()}),
                    categories: vec![NameAndLang { name: String::from("animation"), lang: None }],
                    previously_shown: Some(xmltv::PreviouslyShown { start: None, channel: None }),
                    ..Default::default()
                }
            ],
            ..Default::default()
        }
    )
}

#[test]
fn test_tv_attributes() {
    let input = "<tv source-info-url=\"SIU\" source-info-name=\"SIN\" source-data-url=\"SDU\" generator-info-name=\"GIN\" generator-info-url=\"GIU\"></tv>";
    let expected = Tv {
        source_info_url: Some("SIU".to_owned()),
        source_info_name: Some("SIN".to_owned()),
        source_data_url: Some("SDU".to_owned()),
        generator_info_name: Some("GIN".to_owned()),
        generator_info_url: Some("GIU".to_owned()),
        ..Default::default()
    };
    let item: Tv = from_str(input).unwrap();
    assert_eq!(item, expected);
}

// available examples and tests here: https://github.com/XMLTV/xmltv/tree/master/t/data
// #[test]
// pub fn test_complex_xmltv() {
//     let mut xmltv = XMLTV::new();
//     xmltv.add_generator_info_name("xmltv-rs".into());

//     let channels = vec![
//         ("TF1", "1", Some("https://tf1.fr"), None),
//         ("France 2", "2", None, Some("http://icon-france-2.fr")),
//         (
//             "France 3",
//             "3",
//             Some("https://www.france.tv/france-3"),
//             None,
//         ),
//     ];

//     for elem in channels {
//         let display_name = XMLTVChannelDisplayName::new(elem.0.into(), None);

//         let mut channel = XMLTVChannel::new(elem.0.into(), elem.1.into(), display_name);
//         if let Some(url) = elem.2 {
//             channel.add_url(url.into());
//         }

//         if let Some(icon) = elem.3 {
//             channel.add_icon(icon.into());
//         }

//         xmltv.add_channel(channel).unwrap();
//     }

//     let mut writer: Vec<u8> = Vec::new();
//     xmltv.build(&mut writer).unwrap();

//     let expected = "<tv generator-info-name=\"xmltv-rs\">
// \t<channel channel=\"TF1\" id=\"1\">
// \t\t<display-name>TF1</display-name>
// \t\t<url>https://tf1.fr</url>
// \t</channel>
// \t<channel channel=\"France 2\" id=\"2\">
// \t\t<display-name>France 2</display-name>
// \t\t<icon src=\"http://icon-france-2.fr\" />
// \t</channel>
// \t<channel channel=\"France 3\" id=\"3\">
// \t\t<display-name>France 3</display-name>
// \t\t<url>https://www.france.tv/france-3</url>
// \t</channel>
// </tv>\n";
//     let res = std::str::from_utf8(&writer).unwrap();

//     assert_eq!(res, expected, "Both values does not match...")
// }