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
31fn unescape_xml_str(input: &str) -> Cow<str> {
49 let mut res = Cow::from(input);
50
51 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 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 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 let (input, value) = parse_str(input)?;
126 Ok((input, (name, value)))
129 }
130 let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
131 let (input, attributes) =
133 nm::separated_list0(nbc::take_till1(|c| !is_whitespace(c)), parse_attribute)(input)?;
134 let (input, _) = nbc::take_till(|c| !is_whitespace(c))(input)?;
136 let (input, _) = nom::branch::alt((nbc::tag(">"), nbc::tag("?>")))(input)?;
137 let mut encoding = "UTF-8".to_owned();
141 for (name, value) in attributes.iter() {
142 match name.to_lowercase().as_str() {
143 "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 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 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 let (input, _) = ncc::multispace0(input)?;
226
227 fn parse_attribute(
228 input: &str,
229 ) -> IResult<&str, (&str, Cow<str>, bool), VerboseError<&str>> {
230 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 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 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 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 nbc::take_until1("<")(input)
304 }
305
306 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 (
314 input,
315 Some(Ok((XmlEvent::Comment(comment.to_string()), false))),
316 )
317 } else if let Ok((input, tag)) = parse_tag(input) {
318 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 (input, None)
353 }
354 } else {
355 (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 if text.chars().all(|c| c.is_ascii_whitespace()) {
380 (input, None)
383 } else {
384 (
386 input,
387 Some(Ok((XmlEvent::Characters(text.trim().into()), false))),
388 )
389 }
390 } else {
391 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 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 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 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 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 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 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 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 let res = read_all(r##"<?xml?><UIScene/><UIText text="Hello world " ' < > & &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}