nwn_lib_rs/
gui.rs

1use encoding_rs::WINDOWS_1252;
2use nom::sequence::tuple;
3use std::borrow::Cow;
4use xml::name::OwnedName;
5
6use nom::bytes::complete as nbc;
7use nom::character::complete as ncc;
8use nom::error::VerboseError;
9use nom::{multi as nm, Parser};
10use nom::{Finish, IResult};
11use xml::common::XmlVersion;
12
13use crate::parsing::{get_nom_input_linecol, get_nom_input_offset, nom_context_error};
14
15pub use xml::reader::XmlEvent;
16
17fn to_xml_pos(pos: (usize, usize)) -> xml::common::TextPosition {
18    xml::common::TextPosition {
19        row: pos.0 as u64,
20        column: pos.1 as u64,
21    }
22}
23
24fn is_whitespace(c: u8) -> bool {
25    match c {
26        0x20 | 0x9 | 0xD | 0xA => true,
27        _ => false,
28    }
29}
30
31// fn xml_char(input: &str) -> IResult<&str, char, VerboseError<&str>> {
32//     fn escape_code(input: &str) -> IResult<&str, char, VerboseError<&str>> {
33//         let (after_input, (_, code, _)) =
34//             nom::sequence::tuple((nbc::tag("&"), nbc::take_until(";"), nbc::tag(";")))(input)?;
35//         let res = match code {
36//             "amp" => '&',
37//             "apos" => '\'',
38//             "gt" => '>',
39//             "lt" => '<',
40//             "quot" => '"',
41//             _ => return Err(nom_context_error("unknown XML escape sequence", input)),
42//         };
43//         Ok((after_input, res))
44//     }
45
46//     nom::branch::alt((escape_code, ncc::anychar))(input)
47// }
48fn unescape_xml_str(input: &str) -> Cow<str> {
49    let mut res = Cow::from(input);
50
51    // let mut result_string = String::with_capacity(xml_str.len() * 6 / 5);
52    let mut input = input;
53    while let Some((before, after)) = input.split_once("&") {
54        if let Cow::Borrowed(_) = res {
55            res = String::with_capacity(input.len() * 6 / 5).into();
56        };
57
58        if let Cow::Owned(result_string) = &mut res {
59            result_string.push_str(before);
60
61            input = if after.starts_with("amp;") {
62                result_string.push('&');
63                &after[4..]
64            } else if after.starts_with("apos;") {
65                result_string.push('\'');
66                &after[5..]
67            } else if after.starts_with("gt;") {
68                result_string.push('>');
69                &after[3..]
70            } else if after.starts_with("lt;") {
71                result_string.push('<');
72                &after[3..]
73            } else if after.starts_with("quot;") {
74                result_string.push('"');
75                &after[5..]
76            } else {
77                result_string.push('&');
78                after
79            };
80        }
81    }
82    if let Cow::Owned(result_string) = &mut res {
83        result_string.push_str(input);
84    }
85
86    res
87}
88
89pub fn decode_xml_str(input: &[u8], lossy: bool) -> IResult<&[u8], Cow<str>, VerboseError<&[u8]>> {
90    let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
91    let (input, _) = nbc::tag_no_case("<?xml")(input)?;
92
93    fn parse_str(input: &[u8]) -> IResult<&[u8], String, VerboseError<&[u8]>> {
94        fn parse_doublequoted(input: &[u8]) -> IResult<&[u8], String, VerboseError<&[u8]>> {
95            let (input, _) = nbc::tag("\"")(input)?;
96            let (input, str_input) = nbc::take_until("\"")(input)?;
97            let (input, _) = nbc::tag("\"")(input)?;
98            Ok((input, String::from_utf8_lossy(str_input).into_owned()))
99        }
100        fn parse_singlequoted(input: &[u8]) -> IResult<&[u8], String, VerboseError<&[u8]>> {
101            let (input, _) = nbc::tag("'")(input)?;
102            let (input, str_input) = nbc::take_until("'")(input)?;
103            let (input, _) = nbc::tag("'")(input)?;
104            Ok((input, String::from_utf8_lossy(str_input).into_owned()))
105        }
106        fn parse_unquoted(input: &[u8]) -> IResult<&[u8], String, VerboseError<&[u8]>> {
107            let (input, str_input) = nbc::take_till(|c| !is_whitespace(c))(input)?;
108            Ok((input, String::from_utf8_lossy(str_input).into_owned()))
109        }
110
111        nom::branch::alt((parse_doublequoted, parse_singlequoted, parse_unquoted))(input)
112    }
113
114    fn parse_attribute(input: &[u8]) -> IResult<&[u8], (String, String), VerboseError<&[u8]>> {
115        // eprintln!("parse_attribute @ {:?}", input.get(..10));
116        let (input, name) = nbc::take_till1(|c| is_whitespace(c) || c == '=' as u8)(input)?;
117        let name = String::from_utf8_lossy(name).into_owned();
118        // eprintln!("   name={} @ {:?}", name, input.get(..10));
119
120        let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
121        let (input, _) = nbc::tag("=")(input)?;
122        let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
123        // eprintln!("   =");
124
125        let (input, value) = parse_str(input)?;
126        // eprintln!("   value={} @ {:?}", value, input.get(..10));
127
128        Ok((input, (name, value)))
129    }
130    let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
131    // eprintln!("   Starting attrribnutes @ {:?}", input.get(..10));
132    let (input, attributes) =
133        nm::separated_list0(nbc::take_till1(|c| !is_whitespace(c)), parse_attribute)(input)?;
134    // eprintln!("   attribs parsed @ {:?}", input.get(..10));
135    let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
136    let (input, _) = nom::branch::alt((nbc::tag(">"), nbc::tag("?>")))(input)?;
137    // eprintln!("   header parsed @ {:?}", input.get(..10));
138
139    // let mut version = "1.0";
140    let mut encoding = "UTF-8".to_owned();
141    for (name, value) in attributes.iter() {
142        match name.to_lowercase().as_str() {
143            // "version" => version = value,
144            "encoding" => encoding = value.to_uppercase(),
145            _ => {}
146        }
147    }
148
149    let xml_str: Cow<_> = match encoding.as_str() {
150        "UTF-8" | "NWN2UI" => {
151            if lossy {
152                String::from_utf8_lossy(input).into()
153            } else {
154                std::str::from_utf8(input)
155                    .map_err(|e| nom_context_error("bad UTF8 sequence", &input[..e.valid_up_to()]))?
156                    .into()
157            }
158        }
159        "WINDOWS-1252" => {
160            let (s, _encoding, _converted) = WINDOWS_1252.decode(input).to_owned();
161            s
162        }
163        _ => return Err(nom_context_error("unhandled XML encoding", input)),
164    };
165
166    Ok((input, xml_str))
167}
168fn parse_once<'a>(
169    input: &'a str,
170    stack: &[OwnedName],
171) -> IResult<&'a str, (XmlEvent, bool), VerboseError<&'a str>> {
172    #[derive(Debug)]
173    enum TagType {
174        Open,
175        Close,
176        SelfClose,
177    }
178    #[derive(Debug)]
179    struct Tag<'a> {
180        name: &'a str,
181        tagtype: TagType,
182        attributes: Vec<(&'a str, Cow<'a, str>)>,
183    }
184
185    fn is_whitespace(c: char) -> bool {
186        match c {
187            ' ' | '\t' | '\r' | '\n' => true,
188            _ => false,
189        }
190    }
191
192    fn parse_str(input: &str) -> IResult<&str, (Cow<str>, bool), VerboseError<&str>> {
193        // eprintln!("parse_str @ {:?}", input.get(..10));
194        use nom::bytes::complete::is_not;
195        use nom::character::complete::char;
196        use nom::sequence::delimited;
197
198        let is_quoted = match input.as_bytes().first() {
199            Some(b'"' | b'\'') => true,
200            _ => false,
201        };
202        let (input, s) = nom::branch::alt((
203            delimited(char('"'), is_not("\""), char('"')),
204            delimited(char('\''), is_not("'"), char('\'')),
205            nbc::take_till(|c| is_whitespace(c) || c == '>' || c == '/'),
206        ))(input)?;
207
208        let s = unescape_xml_str(s);
209
210        Ok((input, (s, is_quoted)))
211    }
212
213    fn parse_tag(input: &str) -> IResult<&str, Tag, VerboseError<&str>> {
214        // eprintln!("try parse_tag @ {:?}", input.get(..input.len().min(10)));
215        let (input, _) = nbc::tag("<")(input)?;
216        let (input, closing_tag) =
217            if let Ok((input, _)) = nbc::tag::<_, _, VerboseError<_>>("/")(input) {
218                (input, true)
219            } else {
220                (input, false)
221            };
222        let (input, _) = ncc::multispace0(input)?;
223        let (input, name) = ncc::alphanumeric1(input)?;
224        // eprintln!("   <{}>", name);
225        let (input, _) = ncc::multispace0(input)?;
226
227        fn parse_attribute(
228            input: &str,
229        ) -> IResult<&str, (&str, Cow<str>, bool), VerboseError<&str>> {
230            // eprintln!("  parse_attribute @ {:?}", input.get(..10));
231            let (input, (name, _, _, _, (value, is_quoted))) = tuple((
232                nbc::take_till1(|c| match c {
233                    ' ' | '\t' | '\r' | '\n' | '=' => true,
234                    _ => false,
235                }),
236                ncc::multispace0,
237                nbc::tag("="),
238                ncc::multispace0,
239                parse_str,
240            ))(input)?;
241            // eprintln!("   {}={}", name, value);
242
243            Ok((input, (name, value, is_quoted)))
244        }
245
246        let mut attributes = vec![];
247        let mut input = input;
248        loop {
249            let name;
250            let value;
251            let is_quoted;
252            (input, (name, value, is_quoted)) = match parse_attribute(input) {
253                Ok(res) => res,
254                _ => break,
255            };
256            attributes.push((name, value));
257
258            let sep = if is_quoted {
259                ncc::multispace0::<_, VerboseError<_>>(input)
260            } else {
261                ncc::multispace1(input)
262            };
263            (input, _) = match sep {
264                Ok(res) => res,
265                _ => break,
266            }
267        }
268
269        // eprintln!("  Attributes: {:?}", attributes);
270
271        let (input, tagtype) = if closing_tag {
272            let (input, _) = nbc::tag(">")(input)?;
273            (input, TagType::Close)
274        } else {
275            let (input, txt) = nom::branch::alt((nbc::tag(">"), nbc::tag("/>")))(input)?;
276            match txt {
277                ">" => (input, TagType::Open),
278                "/>" => (input, TagType::SelfClose),
279                _ => panic!(),
280            }
281        };
282
283        Ok((
284            input,
285            Tag {
286                name,
287                tagtype,
288                attributes,
289            },
290        ))
291    }
292    fn parse_comment(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
293        // eprintln!("try parse_comment @ {:?}", input.get(..input.len().min(10)));
294        let (input, (_, comment, _)) = nom::sequence::tuple((
295            nbc::tag("<!--").or(nbc::tag("<--")),
296            nbc::take_until("-->"),
297            nbc::tag("-->"),
298        ))(input)?;
299        Ok((input, comment))
300    }
301    fn parse_text(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
302        // eprintln!("try parse_text @ {:?}", input.get(..input.len().min(10)));
303        nbc::take_until1("<")(input)
304    }
305
306    // Build XML tree
307    //
308    let mut input = input;
309    loop {
310        let parse_res: Option<Result<(XmlEvent, bool), _>>;
311        (input, parse_res) = if let Ok((input, comment)) = parse_comment(input) {
312            // eprintln!("SUCCESS parse_comment");
313            (
314                input,
315                Some(Ok((XmlEvent::Comment(comment.to_string()), false))),
316            )
317        } else if let Ok((input, tag)) = parse_tag(input) {
318            // eprintln!("SUCCESS parse_tag: {:?}", tag);
319            match tag.tagtype {
320                TagType::Open => {
321                    let elmt = XmlEvent::StartElement {
322                        name: xml::name::OwnedName::local(tag.name),
323                        attributes: tag
324                            .attributes
325                            .into_iter()
326                            .map(|(name, value)| {
327                                xml::attribute::OwnedAttribute::new(
328                                    xml::name::OwnedName::local(name),
329                                    value.to_string(),
330                                )
331                            })
332                            .collect(),
333                        namespace: xml::namespace::Namespace::empty(),
334                    };
335
336                    (input, Some(Ok((elmt, false))))
337                }
338                TagType::Close => {
339                    if let Some(expected_name) = stack.last() {
340                        if tag.name == expected_name.local_name {
341                            (
342                                input,
343                                Some(Ok((
344                                    XmlEvent::EndElement {
345                                        name: OwnedName::local(tag.name),
346                                    },
347                                    false,
348                                ))),
349                            )
350                        } else {
351                            // NWN2 special case: unmatched closing tags are ignored
352                            (input, None)
353                        }
354                    } else {
355                        // NWN2 special case: additional closing elements are ignored
356                        (input, None)
357                    }
358                }
359                TagType::SelfClose => {
360                    let elmt = XmlEvent::StartElement {
361                        name: xml::name::OwnedName::local(tag.name),
362                        attributes: tag
363                            .attributes
364                            .into_iter()
365                            .map(|(name, value)| {
366                                xml::attribute::OwnedAttribute::new(
367                                    xml::name::OwnedName::local(name),
368                                    value,
369                                )
370                            })
371                            .collect(),
372                        namespace: xml::namespace::Namespace::empty(),
373                    };
374                    (input, Some(Ok((elmt.into(), true))))
375                }
376            }
377        } else if let Ok((input, text)) = parse_text(input) {
378            // eprintln!("SUCCESS Text {:?}", text);
379            if text.chars().all(|c| c.is_ascii_whitespace()) {
380                // Skip this part and continue parsing
381                // eprintln!("Text is whitespace: {:?} @ {:?}", text, input);
382                (input, None)
383            } else {
384                // eprintln!("SUCCESS text: {:?}", text);
385                (
386                    input,
387                    Some(Ok((XmlEvent::Characters(text.trim().into()), false))),
388                )
389            }
390        } else {
391            // eprintln!("SUCCESS Other");
392            let (consumed_input, _) = ncc::multispace0(input)?;
393            if consumed_input.len() > 0 {
394                return Err(nom::Err::Failure(VerboseError {
395                    errors: vec![(
396                        input,
397                        nom::error::VerboseErrorKind::Nom(nom::error::ErrorKind::Fail),
398                    )],
399                }));
400            } else {
401                (consumed_input, Some(Ok((XmlEvent::EndDocument {}, false))))
402            }
403        };
404        // Ok((&[], ()))
405        if let Some(parse_res) = parse_res {
406            return Ok((input, parse_res?));
407        } else {
408            continue;
409        }
410    }
411}
412
413pub struct GuiXmlEventReader<R: std::io::Read> {
414    source: R,
415    data: Option<String>,
416    data_ptr: usize,
417    pending_selfclose: bool,
418    stack: Vec<OwnedName>,
419    lossy: bool,
420}
421impl<R: std::io::Read> GuiXmlEventReader<R> {
422    pub fn new(source: R, lossy: bool) -> Self {
423        Self {
424            source,
425            data: None,
426            data_ptr: 0,
427            pending_selfclose: false,
428            stack: vec![],
429            lossy,
430        }
431    }
432    pub fn next(&mut self) -> xml::reader::Result<XmlEvent> {
433        match &self.data {
434            None => {
435                // Parse XML header
436                let mut data: Vec<u8> = vec![];
437                self.source.read_to_end(&mut data)?;
438
439                let (_, datastr) = decode_xml_str(data.as_slice(), self.lossy)
440                    .finish()
441                    .map_err(|e| -> xml::reader::Error {
442                        // eprintln!("Errors {:?}", e.errors);
443                        // eprintln!("data {:?}", data);
444                        xml::reader::Error::from((
445                            &to_xml_pos((0, get_nom_input_offset(e.errors[0].0, data.as_slice()))),
446                            std::borrow::Cow::from(format!("while parsing XML header: {:?}", e)),
447                        ))
448                    })?;
449
450                self.data = Some(datastr.into_owned());
451
452                Ok(XmlEvent::StartDocument {
453                    version: XmlVersion::Version10,
454                    encoding: "UTF-8".to_string(),
455                    standalone: None,
456                })
457            }
458            Some(data) => {
459                // Parse XML
460                if self.pending_selfclose {
461                    self.pending_selfclose = false;
462                    return Ok(XmlEvent::EndElement {
463                        name: self.stack.pop().expect("bad pending selfclose"),
464                    });
465                }
466
467                let step_input = &data[self.data_ptr..];
468                let (input, (ev, self_closing)) = parse_once(step_input, &self.stack)
469                    .finish()
470                    .map_err(|e| -> xml::reader::Error {
471                        use nom::error::convert_error;
472                        // eprintln!("ERRORS: {:?}", e.errors[0]);
473                        xml::reader::Error::from((
474                            &to_xml_pos(get_nom_input_linecol(e.errors[0].0, data)),
475                            std::borrow::Cow::from(format!(
476                                "while parsing XML data: {}",
477                                convert_error(data.as_str(), e)
478                            )),
479                        ))
480                    })?;
481
482                match &ev {
483                    XmlEvent::StartElement {
484                        name,
485                        attributes: _,
486                        namespace: _,
487                    } => {
488                        self.stack.push(name.clone());
489                    }
490                    XmlEvent::EndElement { name } => {
491                        let popped = self.stack.pop().ok_or(xml::reader::Error::from((
492                            &to_xml_pos(get_nom_input_linecol(step_input, data.as_str())),
493                            std::borrow::Cow::from(format!(
494                                "unexpected closing element: {:?}",
495                                name
496                            )),
497                        )))?;
498                        debug_assert!(popped == *name);
499                    }
500                    XmlEvent::EndDocument => {
501                        if let Some(elmt) = self.stack.last() {
502                            return Err(xml::reader::Error::from((
503                                &to_xml_pos(get_nom_input_linecol(input, data.as_str())),
504                                std::borrow::Cow::from(format!(
505                                    "reached the end of the document while waiting for a closing \
506                                     </{}>",
507                                    elmt
508                                )),
509                            )));
510                        }
511                    }
512                    _ => {}
513                }
514
515                self.data_ptr = data.len() - input.len();
516                self.pending_selfclose = self_closing;
517                Ok(ev)
518            }
519        }
520    }
521    pub fn skip(&mut self) -> xml::reader::Result<()> {
522        self.next()?;
523        Ok(())
524    }
525    pub fn source(&self) -> &R {
526        &self.source
527    }
528    pub fn source_mut(&mut self) -> &mut R {
529        &mut self.source
530    }
531    pub fn into_inner(self) -> R {
532        self.source
533    }
534    pub fn doctype(&self) -> Option<&str> {
535        None
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use core::str;
542
543    use xml::attribute::OwnedAttribute;
544
545    use super::*;
546
547    fn read_all(input: &str) -> Result<Vec<XmlEvent>, Box<dyn std::error::Error>> {
548        let mut reader = GuiXmlEventReader::new(std::io::Cursor::new(input), false);
549
550        let mut res = vec![];
551        loop {
552            match reader.next()? {
553                XmlEvent::EndDocument => break,
554                ev => {
555                    eprintln!("read event: {:?}", ev);
556                    res.push(ev);
557                }
558            }
559        }
560        Ok(res)
561    }
562
563    #[test]
564    fn test_headers() {
565        read_all(r##"<?xml version="1.0" encoding="utf-8"?><UIScene></UIScene>"##).unwrap();
566        read_all(r##"<?xml version="1.0" encoding="utf-8" ?><UIScene></UIScene>"##).unwrap();
567        read_all(r##"<?xml version="1.0" encoding="utf-8"><UIScene></UIScene>"##).unwrap();
568        read_all(r##"<?xml version="1.0" encoding="utf-8" ><UIScene></UIScene>"##).unwrap();
569        read_all(r##"<?xml?><UIScene></UIScene>"##).unwrap();
570        read_all(r##"<?xml ?><UIScene></UIScene>"##).unwrap();
571        read_all(r##"<?xml><UIScene></UIScene>"##).unwrap();
572        read_all(r##"<?xml ><UIScene></UIScene>"##).unwrap();
573
574        assert!(GuiXmlEventReader::new(
575            std::io::Cursor::new(r##"<xml><UIScene></UIScene>"##),
576            false
577        )
578        .next()
579        .is_err());
580        assert!(
581            GuiXmlEventReader::new(std::io::Cursor::new(r##"<UIScene></UIScene>"##), false)
582                .next()
583                .is_err()
584        );
585    }
586
587    #[test]
588    fn test_errors() {
589        // XML errors
590        read_all(r##"<?xml?><UIScene value/>"##).unwrap_err();
591        read_all(r##"<?xml?><UIScene value=something with spaces/>"##).unwrap_err();
592        read_all(r##"<?xml?><<UIScene/>"##).unwrap_err();
593        read_all(r##"<?xml?><UIScene/><UIButton>"##).unwrap_err();
594        read_all(r##"<?xml?><UIScene/><UIButton><UIFrame/>"##).unwrap_err();
595        read_all(r##"<?xml?><UIScene/><UIButton></UIFrame>"##).unwrap_err();
596
597        // Extra closing tags are allowed
598        read_all(r##"<?xml?><UIScene/></UIButton>"##).unwrap();
599        read_all(r##"<?xml?><UIScene/><UIButton></UIButton></UIButton>"##).unwrap();
600        read_all(r##"<?xml?><UIScene/><UIButton></UIFrame></UIButton>"##).unwrap();
601
602        // Encoding
603        let res = read_all(r##"<?xml?><UIScene/><UIText text="Hello world &quot; &apos; &lt; &gt; &amp;  &err;"></UIText>"##).unwrap();
604        let (name, attributes, _) = if let XmlEvent::StartElement {
605            name,
606            attributes,
607            namespace,
608        } = &res[3]
609        {
610            (name, attributes, namespace)
611        } else {
612            panic!("{:?} is not XmlEvent::StartElement", &res[2])
613        };
614        assert_eq!(name.local_name, "UIText");
615        assert_eq!(
616            attributes
617                .iter()
618                .find(|v| v.name.local_name == "text")
619                .unwrap()
620                .value,
621            r##"Hello world " ' < > &  &err;"##
622        );
623    }
624
625    #[test]
626    fn test_misc() {
627        let events = read_all(
628            r##"<?xml>
629            <UIPortrait name="PORTRAIT"	texture="p_m_bg_dark.tga" x=14 y=85 width=128 height=128
630			update=true OnUpdate=UIPortrait_OnUpdate_UpdateCharacterPortrait()
631			OnRender=UIPortrait_OnRender_RenderCharacterPortrait()
632			ambground_intens=".4" ambgroundcolor_r=".7" ambgroundcolor_g=".55" ambgroundcolor_b=".4"
633			ambsky_intens=".8" ambskycolor_r=".3" ambskycolor_g=".4" ambskycolor_b=".78"
634			diffusecolor_r=.9 diffusecolor_g=.8 diffusecolor_b=.6
635			light_intens=.0 ></UIPortrait>
636
637            <UIListBox name="INFOPANE_LISTBOX" x="21" y="49" width="465" height="218" xPadding="5" yPadding="5" showpartialchild="true"
638                unequalcontrols="true" scrollsegmentsize="30" hidescrollbarwhennotneeded="true" >
639                <UIText name="HelpField" strref="183397" width="PARENT_WIDTH" height="DYNAMIC" fontfamily="Default" multiline="true" />
640                <UIScrollBar name="SB" style="STYLE_SB_THIN" />
641            </UIListBox>
642
643            <UIText name="Character"fontfamily="NWN2_Dialog" color=888888 update=true OnUpdate="UIText_OnUpdate_DisplaySelectedCharacter()"  />
644            "##,
645        ).unwrap();
646        if let XmlEvent::StartElement {
647            name,
648            attributes,
649            namespace: _,
650        } = &events[1]
651        {
652            assert_eq!(name.local_name.as_str(), "UIPortrait");
653            assert!(attributes.contains(&OwnedAttribute::new(OwnedName::local("name"), "PORTRAIT")));
654            assert!(attributes.contains(&OwnedAttribute::new(
655                OwnedName::local("OnUpdate"),
656                "UIPortrait_OnUpdate_UpdateCharacterPortrait()"
657            )));
658        } else {
659            panic!();
660        }
661    }
662
663    #[test]
664    fn test_parsing_campaign() {
665        let input = include_bytes!("../unittest/campaign.xml");
666
667        let mut reader = GuiXmlEventReader::new(std::io::Cursor::new(input), false);
668
669        let mut i = 0;
670        loop {
671            let ev = reader.next().unwrap();
672            match ev {
673                XmlEvent::EndDocument => break,
674                XmlEvent::Comment(_) => continue,
675                _ => {}
676            }
677
678            match i {
679                0 => {
680                    if let XmlEvent::StartDocument {
681                        version,
682                        encoding,
683                        standalone: _,
684                    } = ev
685                    {
686                        assert_eq!(version, XmlVersion::Version10);
687                        assert_eq!(encoding, "UTF-8");
688                    } else {
689                        panic!("{:?}", ev);
690                    }
691                }
692                1 => {
693                    if let XmlEvent::StartElement {
694                        name,
695                        attributes,
696                        namespace,
697                    } = ev
698                    {
699                        assert_eq!(name.local_name.as_str(), "UIScene");
700                        assert!(attributes.contains(&OwnedAttribute::new(
701                            OwnedName::local("name"),
702                            "SCREEN_CAMPAIGNLIST"
703                        )));
704                        assert!(attributes.contains(&OwnedAttribute::new(
705                            OwnedName::local("OnAdd"),
706                            "UIScene_OnAdd_CreateCampaignList(\"CampaignListBox\")"
707                        )));
708                        assert_eq!(namespace, xml::namespace::Namespace::empty());
709                    } else {
710                        panic!("{:?}", ev);
711                    }
712                }
713                2 => assert_eq!(
714                    ev,
715                    XmlEvent::EndElement {
716                        name: OwnedName::local("UIScene")
717                    }
718                ),
719                3 => {
720                    if let XmlEvent::StartElement {
721                        name,
722                        attributes,
723                        namespace: _,
724                    } = ev
725                    {
726                        assert_eq!(name.local_name.as_str(), "UIPane");
727                        assert!(attributes
728                            .contains(&OwnedAttribute::new(OwnedName::local("name"), "TitlePane")));
729                        assert!(attributes
730                            .contains(&OwnedAttribute::new(OwnedName::local("width"), "984")));
731                    } else {
732                        panic!("{:?}", ev);
733                    }
734                }
735                4 => {
736                    if let XmlEvent::StartElement {
737                        name,
738                        attributes,
739                        namespace: _,
740                    } = ev
741                    {
742                        assert_eq!(name.local_name.as_str(), "UIText");
743                        assert!(attributes.contains(&OwnedAttribute::new(
744                            OwnedName::local("name"),
745                            "TITLE_TEXT"
746                        )));
747                        assert!(attributes
748                            .contains(&OwnedAttribute::new(OwnedName::local("style"), "4")));
749                    } else {
750                        panic!("{:?}", ev);
751                    }
752                }
753                5 => assert_eq!(
754                    ev,
755                    XmlEvent::EndElement {
756                        name: OwnedName::local("UIText")
757                    }
758                ),
759                6 => {
760                    if let XmlEvent::StartElement {
761                        name,
762                        attributes,
763                        namespace: _,
764                    } = ev
765                    {
766                        assert_eq!(name.local_name.as_str(), "UIIcon");
767                        assert!(attributes.contains(&OwnedAttribute::new(
768                            OwnedName::local("img"),
769                            "main_sub_titles.tga"
770                        )));
771                        assert!(attributes.contains(&OwnedAttribute::new(
772                            OwnedName::local("height"),
773                            "PARENT_HEIGHT"
774                        )));
775                    } else {
776                        panic!("{:?}", ev);
777                    }
778                }
779                7 => assert_eq!(
780                    ev,
781                    XmlEvent::EndElement {
782                        name: OwnedName::local("UIIcon")
783                    }
784                ),
785                8 => assert_eq!(
786                    ev,
787                    XmlEvent::EndElement {
788                        name: OwnedName::local("UIPane")
789                    }
790                ),
791                22 => {
792                    if let XmlEvent::StartElement {
793                        name,
794                        attributes,
795                        namespace: _,
796                    } = ev
797                    {
798                        assert_eq!(name.local_name.as_str(), "UIListBox");
799                        assert!(attributes.contains(&OwnedAttribute::new(
800                            OwnedName::local("name"),
801                            "CAMP_DESC_LISTBOX"
802                        )));
803                        assert!(attributes
804                            .contains(&OwnedAttribute::new(OwnedName::local("xPadding"), "5")));
805                    } else {
806                        panic!("{:?}", ev);
807                    }
808                }
809                40 => {
810                    if let XmlEvent::StartElement {
811                        name,
812                        attributes,
813                        namespace: _,
814                    } = ev
815                    {
816                        assert_eq!(name.local_name.as_str(), "UIButton");
817                        assert!(attributes.contains(&OwnedAttribute::new(
818                            OwnedName::local("name"),
819                            "START_CAMPAIGN"
820                        )));
821                        assert!(attributes.contains(&OwnedAttribute::new(
822                            OwnedName::local("OnLeftClick"),
823                            "UIButton_Input_StartModule(\"SCREEN_CHARCHOICE\",Local:0)"
824                        )));
825                    } else {
826                        panic!("{:?}", ev);
827                    }
828                }
829                41 => assert_eq!(
830                    ev,
831                    XmlEvent::EndElement {
832                        name: OwnedName::local("UIButton")
833                    }
834                ),
835                42 => assert_eq!(
836                    ev,
837                    XmlEvent::EndElement {
838                        name: OwnedName::local("UIPane")
839                    }
840                ),
841                43 => {
842                    if let XmlEvent::StartElement {
843                        name,
844                        attributes,
845                        namespace: _,
846                    } = ev
847                    {
848                        assert_eq!(name.local_name.as_str(), "UIIcon");
849                        assert!(attributes.contains(&OwnedAttribute::new(
850                            OwnedName::local("img"),
851                            "main_sub_bg.tga"
852                        )));
853                        assert!(attributes.contains(&OwnedAttribute::new(
854                            OwnedName::local("scalewithscene"),
855                            "true"
856                        )));
857                    } else {
858                        panic!("{:?}", ev);
859                    }
860                }
861                44 => assert_eq!(
862                    ev,
863                    XmlEvent::EndElement {
864                        name: OwnedName::local("UIIcon")
865                    }
866                ),
867                _ => {}
868            }
869            i += 1;
870        }
871        assert_eq!(i, 45);
872    }
873
874    #[test]
875    fn test_addbuddy() {
876        let input = include_bytes!("../unittest/addbuddy.xml");
877
878        let mut reader = GuiXmlEventReader::new(std::io::Cursor::new(input), true);
879
880        let output = std::io::Cursor::new(vec![]);
881        let mut writer = xml::writer::EmitterConfig::new()
882            .perform_indent(true)
883            .create_writer(output);
884
885        loop {
886            let ev = reader.next().unwrap();
887            if let XmlEvent::EndDocument = ev {
888                break;
889            }
890            if let Some(ev) = ev.as_writer_event() {
891                writer.write(ev).unwrap();
892            }
893        }
894    }
895}