cot_proto/
detail.rs

1use quick_xml::events::Event;
2
3use crate::{
4    base::{Cot, CotBase},
5    Error,
6};
7
8/// A CoT message struct with unparsed \<detail\> section, which is captured as a raw
9/// `Vec<String>`.
10pub type CotUnparsedDetail = Cot<Vec<String>>;
11
12impl From<CotBase> for CotUnparsedDetail {
13    fn from(cot: CotBase) -> Self {
14        CotUnparsedDetail {
15            version: cot.version,
16            uid: cot.uid,
17            cot_type: cot.cot_type,
18            time: cot.time,
19            start: cot.start,
20            stale: cot.stale,
21            how: cot.how,
22            detail: vec![],
23            point: cot.point,
24        }
25    }
26}
27
28/// Deserialize a UTF8 XML CoT message into a struct, but capture the `<detail>` section as an
29/// unparsed `Vec<String>`.
30///
31/// If you want to parse the `<detail>` section into a strongly-typed struct, instead do this:
32/// ```rust
33/// # use cot_proto::base::Cot;
34/// # use cot_proto::examples::COT_TRACK_EXAMPLE;
35/// # #[derive(serde::Deserialize)]
36/// # struct Foo {}
37/// # let input_str = COT_TRACK_EXAMPLE;
38/// let cot: Cot<Foo> = quick_xml::de::from_str(input_str).unwrap();
39/// ```
40/// where `Foo` is your known type struct for the detail section, which implements `Deserialize`.
41pub fn parse(input: &str) -> Result<CotUnparsedDetail, Error> {
42    let mut reader = quick_xml::Reader::from_str(input);
43    reader.config_mut().trim_text(true);
44    let detail = extract_detail(reader)?;
45    let cot_base: CotBase = quick_xml::de::from_str(input)?;
46    let mut cot: CotUnparsedDetail = cot_base.into();
47    cot.detail = detail;
48    Ok(cot)
49}
50
51/// Extract the `<detail>` section from a CoT message without trying to parse it into a concrete
52/// type.
53pub fn extract_detail(mut reader: quick_xml::reader::Reader<&[u8]>) -> Result<Vec<String>, Error> {
54    let mut detail: Vec<String> = vec![];
55    let mut is_detail = false;
56    loop {
57        match reader.read_event() {
58            Ok(Event::Start(ref e)) => {
59                if e.name().as_ref() == b"detail" {
60                    is_detail = true;
61                }
62            }
63            Ok(Event::Empty(ref e)) => {
64                if is_detail {
65                    // XXX there should be a better way to get raw lines here?
66                    detail.push(format!("<{}/>", String::from_utf8_lossy(e)));
67                }
68            }
69            Ok(Event::Text(_e)) => {}
70            Ok(Event::End(ref e)) => {
71                if e.name().as_ref() == b"detail" {
72                    is_detail = false;
73                }
74            }
75            Err(e) => return Err(Error::Xml(e)),
76            Ok(Event::Eof) => break,
77            _ => (),
78        }
79    }
80    Ok(detail)
81}
82
83#[cfg(test)]
84mod test {
85    use std::collections::HashSet;
86
87    use crate::examples::{
88        COT_STRIKE_DETAIL_LINES, COT_STRIKE_EXAMPLE, COT_TRACK_DETAIL_LINES, COT_TRACK_EXAMPLE,
89    };
90
91    #[test]
92    fn test_detail_parse_track2() {
93        test_expected_detail(COT_TRACK_EXAMPLE, &COT_TRACK_DETAIL_LINES)
94    }
95
96    #[test]
97    fn test_detail_parse_strike() {
98        test_expected_detail(COT_STRIKE_EXAMPLE, &COT_STRIKE_DETAIL_LINES)
99    }
100
101    fn test_expected_detail(input: &str, expected_lines: &[&str]) {
102        let cot = super::parse(input).unwrap();
103        let mut expected_lines: HashSet<&str> = HashSet::from_iter(expected_lines.iter().cloned());
104        for line in &cot.detail {
105            let removed = expected_lines.remove(&line.as_str());
106            if !removed {
107                panic!(
108                    "Unexpected line: {:?}\n  not in: {:?}",
109                    line, expected_lines
110                );
111            }
112        }
113        assert_eq!(expected_lines.len(), 0);
114    }
115}