1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::escape::*;
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, HashSet};
10use std::io::{Read, Write};
11use std::ops::Index;
12use std::sync::Arc;
13use stylua_lib::{Config as LuaFormatterConfig, OutputVerification, format_code};
14use unicode_segmentation::UnicodeSegmentation;
15
16#[derive(Debug)]
17pub struct ArtemisAsbBuilder {}
19
20impl ArtemisAsbBuilder {
21 pub fn new() -> Self {
23 ArtemisAsbBuilder {}
24 }
25}
26
27impl ScriptBuilder for ArtemisAsbBuilder {
28 fn default_encoding(&self) -> Encoding {
29 Encoding::Utf8
30 }
31
32 fn build_script(
33 &self,
34 buf: Vec<u8>,
35 filename: &str,
36 encoding: Encoding,
37 _archive_encoding: Encoding,
38 config: &ExtraConfig,
39 _archive: Option<&Box<dyn Script>>,
40 ) -> Result<Box<dyn Script + Send + Sync>> {
41 Ok(Box::new(Asb::new(buf, encoding, config, filename)?))
42 }
43
44 fn extensions(&self) -> &'static [&'static str] {
45 &["asb", "iet"]
46 }
47
48 fn script_type(&self) -> &'static ScriptType {
49 &ScriptType::ArtemisAsb
50 }
51
52 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
53 if buf_len >= 5 && buf.starts_with(b"ASB\0\0") {
54 return Some(20);
55 }
56 None
57 }
58
59 fn can_create_file(&self) -> bool {
60 true
61 }
62
63 fn create_file<'a>(
64 &'a self,
65 filename: &'a str,
66 writer: Box<dyn WriteSeek + 'a>,
67 encoding: Encoding,
68 file_encoding: Encoding,
69 config: &ExtraConfig,
70 ) -> Result<()> {
71 create_file(
72 filename,
73 writer,
74 encoding,
75 file_encoding,
76 config.custom_yaml,
77 )
78 }
79}
80
81fn escape_text(s: &str) -> String {
82 let mut escaped = String::with_capacity(s.len());
83 for c in s.chars() {
84 match c {
85 '&' => escaped.push_str("&"),
86 '<' => escaped.push_str("<"),
87 _ => escaped.push(c),
88 }
89 }
90 escaped
91}
92
93#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
94struct Command {
95 pub name: String,
96 pub line_number: u32,
97 pub attributes: BTreeMap<String, String>,
98}
99
100impl Command {
101 pub fn new(name: String, line_number: u32) -> Self {
102 Command {
103 name,
104 line_number,
105 attributes: BTreeMap::new(),
106 }
107 }
108
109 pub fn to_xml(&self) -> String {
110 let mut xml = format!("<{}", self.name);
111 for (key, value) in &self.attributes {
112 xml.push_str(&format!(" {}=\"{}\"", key, escape_xml_text_value(value)));
113 }
114 xml.push('>');
115 xml
116 }
117}
118
119impl<'a> Index<&'a str> for Command {
120 type Output = str;
121 fn index(&self, key: &'a str) -> &Self::Output {
122 self.attributes.get(key).map_or("", |s| s.as_str())
123 }
124}
125
126impl<'a> Index<&'a String> for Command {
127 type Output = str;
128 fn index(&self, key: &'a String) -> &Self::Output {
129 self.attributes.get(key).map_or("", |s| s.as_str())
130 }
131}
132
133impl Index<String> for Command {
134 type Output = str;
135 fn index(&self, key: String) -> &Self::Output {
136 self.attributes.get(&key).map_or("", |s| s.as_str())
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
141#[serde(untagged)]
142enum Item {
143 Command(Command),
144 Label(String),
145}
146
147impl Item {
148 pub fn is_command(&self) -> bool {
149 matches!(self, Item::Command(_))
150 }
151
152 pub fn command_name_in(&self, names: &HashSet<String>) -> Option<String> {
153 if let Item::Command(cmd) = self {
154 if names.contains(&cmd.name) {
155 return Some(cmd.name.clone());
156 }
157 }
158 None
159 }
160}
161
162trait CustomReadFn {
163 fn read_string(&mut self, encoding: Encoding) -> Result<String>;
164 fn read_item(&mut self, encoding: Encoding) -> Result<Item>;
165}
166
167impl<T: Read> CustomReadFn for T {
168 fn read_string(&mut self, encoding: Encoding) -> Result<String> {
169 let len = self.read_u32()?;
170 let data = self.read_exact_vec(len as usize)?;
171 if self.read_u8()? != 0 {
172 return Err(anyhow::anyhow!("String not null-terminated"));
173 }
174 let s = decode_to_string(encoding, &data, true)?;
175 Ok(s)
176 }
177
178 fn read_item(&mut self, encoding: Encoding) -> Result<Item> {
179 let typ = self.read_u32()?;
180 match typ {
181 0 => {
182 let name = self.read_string(encoding)?;
183 let line_number = self.read_u32()?;
184 let mut command = Command::new(name, line_number);
185 let attr_count = self.read_u32()?;
186 for _ in 0..attr_count {
187 let key = self.read_string(encoding)?;
188 let value = self.read_string(encoding)?;
189 command.attributes.insert(key, value);
190 }
191 Ok(Item::Command(command))
192 }
193 1 => {
194 let label = self.read_string(encoding)?;
195 Ok(Item::Label(label))
196 }
197 _ => {
198 return Err(anyhow::anyhow!("Unknown item type: {}", typ));
199 }
200 }
201 }
202}
203
204trait CustomWriteFn {
205 fn write_string(&mut self, s: &str, encoding: Encoding) -> Result<()>;
206 fn write_item(&mut self, item: &Item, encoding: Encoding) -> Result<()>;
207}
208
209impl<T: Write> CustomWriteFn for T {
210 fn write_string(&mut self, s: &str, encoding: Encoding) -> Result<()> {
211 let data = encode_string(encoding, s, false)?;
212 self.write_u32(data.len() as u32)?;
213 self.write_all(&data)?;
214 self.write_u8(0)?; Ok(())
216 }
217
218 fn write_item(&mut self, item: &Item, encoding: Encoding) -> Result<()> {
219 match item {
220 Item::Command(cmd) => {
221 self.write_u32(0)?; self.write_string(&cmd.name, encoding)?;
223 self.write_u32(cmd.line_number)?;
224 self.write_u32(cmd.attributes.len() as u32)?;
225 for (key, value) in &cmd.attributes {
226 self.write_string(key, encoding)?;
227 self.write_string(value, encoding)?;
228 }
229 }
230 Item::Label(label) => {
231 self.write_u32(1)?; self.write_string(label, encoding)?;
233 }
234 }
235 Ok(())
236 }
237}
238
239struct TextParser<'a> {
240 items: Vec<Item>,
241 text: Vec<&'a str>,
242 pos: usize,
243 len: usize,
244 hcls_index: usize,
245 end_tag: &'a str,
246}
247
248impl<'a> TextParser<'a> {
249 pub fn new(str: &'a str, hcls_index: usize, end_tag: &'a str) -> Self {
250 let text: Vec<&'a str> = UnicodeSegmentation::graphemes(str, true).collect();
251 let len = text.len();
252 TextParser {
253 items: Vec::new(),
254 text,
255 pos: 0,
256 len,
257 hcls_index,
258 end_tag,
259 }
260 }
261
262 pub fn parse(mut self) -> Result<Vec<Item>> {
263 while let Some(c) = self.peek() {
264 match c {
265 "<" => {
266 self.parse_tag()?;
267 }
268 _ => {
269 let mut text = String::new();
270 self.eat_char();
271 text.push_str(c);
272 while let Some(b) = self.peek() {
273 if b == "<" {
274 break;
275 }
276 text.push_str(b);
277 self.eat_char();
278 }
279 if !text.is_empty() {
280 self.items.push(Item::Command(Command {
281 name: "print".to_string(),
282 line_number: 0,
283 attributes: [("data".to_string(), unescape_xml(&text))].into(),
284 }))
285 }
286 }
287 }
288 }
289 let mut hcls = Command::new(self.end_tag.to_string(), 0);
290 if self.end_tag == "hcls" {
291 hcls.attributes
292 .insert("0".to_string(), self.hcls_index.to_string());
293 }
294 self.items.push(Item::Command(hcls));
295 Ok(self.items)
296 }
297
298 fn parse_tag(&mut self) -> Result<()> {
299 self.parse_indent("<")?;
300 let key = self.parse_key()?;
301 self.erase_whitespace();
302 let mut cmd = Command::new(key, 0);
303 loop {
304 let c = self.peek().ok_or(self.error2("Unexpected eof"))?;
305 match c {
306 ">" => {
307 self.eat_char();
308 break;
309 }
310 " " => {
311 self.eat_char();
312 continue;
313 }
314 _ => {
315 let key = self.parse_key()?;
316 self.parse_indent("=")?;
317 let value = self.parse_str()?;
318 cmd.attributes.insert(key, value);
319 }
320 }
321 }
322 if self.items.is_empty() {
323 self.items
324 .push(Item::Command(Command::new("msgon".to_string(), 0)));
325 }
326 self.items.push(Item::Command(cmd));
327 Ok(())
328 }
329
330 fn parse_key(&mut self) -> Result<String> {
331 self.erase_whitespace();
332 let mut key = String::new();
333 while let Some(c) = self.peek() {
334 if c == "=" || c == " " || c == ">" {
335 break;
336 }
337 key.push_str(c);
338 self.eat_char();
339 }
340 if key.is_empty() {
341 return self.error("Expected key, but found nothing");
342 }
343 Ok(key)
344 }
345
346 fn parse_str(&mut self) -> Result<String> {
347 self.erase_whitespace();
348 self.parse_indent("\"")?;
349 let mut text = String::new();
350 loop {
351 match self.next().ok_or(self.error2("Unexpected eof"))? {
352 "\"" => {
353 break;
354 }
355 t => {
356 text.push_str(t);
357 }
358 }
359 }
360 Ok(unescape_xml(&text))
361 }
362
363 fn erase_whitespace(&mut self) {
364 while let Some(c) = self.peek() {
365 if c == " " {
366 self.eat_char();
367 } else {
368 break;
369 }
370 }
371 }
372
373 fn parse_indent(&mut self, indent: &str) -> Result<()> {
374 for ident in indent.graphemes(true) {
375 match self.next() {
376 Some(c) => {
377 if c != ident {
378 return self.error("Unexpected indent");
379 }
380 }
381 None => return self.error("Unexpected eof"),
382 }
383 }
384 Ok(())
385 }
386
387 fn eat_char(&mut self) {
388 if self.pos < self.len {
389 self.pos += 1;
390 }
391 }
392
393 fn next(&mut self) -> Option<&'a str> {
394 if self.pos < self.len {
395 let item = self.text[self.pos];
396 self.pos += 1;
397 Some(item)
398 } else {
399 None
400 }
401 }
402
403 fn peek(&self) -> Option<&'a str> {
404 if self.pos < self.len {
405 Some(self.text[self.pos])
406 } else {
407 None
408 }
409 }
410
411 fn error2<T>(&self, msg: T) -> anyhow::Error
412 where
413 T: std::fmt::Display,
414 {
415 anyhow::anyhow!("Failed to parse at position {}: {}", self.pos, msg)
416 }
417
418 fn error<T, A>(&self, msg: T) -> Result<A>
419 where
420 T: std::fmt::Display,
421 {
422 Err(anyhow::anyhow!(
423 "Failed to parse at position {}: {}",
424 self.pos,
425 msg
426 ))
427 }
428}
429
430#[derive(Debug)]
431pub struct Asb {
433 items: Vec<Item>,
434 custom_yaml: bool,
435 is_iet: bool,
436 format_lua: bool,
437 end_tags: Arc<HashSet<String>>,
438}
439
440impl Asb {
441 pub fn new(
447 buf: Vec<u8>,
448 encoding: Encoding,
449 config: &ExtraConfig,
450 filename: &str,
451 ) -> Result<Self> {
452 let mut data = MemReader::new(buf);
453 let mut magic = [0; 5];
454 data.read_exact(&mut magic)?;
455 if &magic != b"ASB\0\0" {
456 return Err(anyhow::anyhow!("Invalid ASB magic number: {:?}", magic));
457 }
458 let nums = data.read_u32()?;
459 let mut items = Vec::with_capacity(nums as usize);
460 for _ in 0..nums {
461 items.push(data.read_item(encoding)?);
462 }
463 Ok(Asb {
464 items,
465 custom_yaml: config.custom_yaml,
466 is_iet: std::path::Path::new(filename)
467 .extension()
468 .map_or(false, |ext| ext.eq_ignore_ascii_case("iet")),
469 format_lua: config.artemis_asb_format_lua,
470 end_tags: config.artemis_asb_end_tags.clone(),
471 })
472 }
473
474 fn to_string(&self, items: &[Item]) -> Result<String> {
475 if self.custom_yaml {
476 Ok(serde_yaml_ng::to_string(items)?)
477 } else {
478 Ok(serde_json::to_string_pretty(items)?)
479 }
480 }
481
482 fn format_lua(&self, script: &str) -> Result<String> {
483 let mut config = LuaFormatterConfig::new();
484 config.indent_type = stylua_lib::IndentType::Spaces;
485 config.indent_width = 2;
486 config.column_width = 120;
487 config.line_endings = stylua_lib::LineEndings::Unix;
488 Ok(format_code(script, config, None, OutputVerification::None)?)
489 }
490}
491
492impl Script for Asb {
493 fn default_output_script_type(&self) -> OutputScriptType {
494 if self.is_iet {
495 OutputScriptType::Custom
496 } else {
497 OutputScriptType::Json
498 }
499 }
500
501 fn is_output_supported(&self, out: OutputScriptType) -> bool {
502 if self.is_iet {
503 matches!(out, OutputScriptType::Custom)
504 } else {
505 true
506 }
507 }
508
509 fn default_format_type(&self) -> FormatOptions {
510 FormatOptions::None
511 }
512
513 fn custom_output_extension<'a>(&'a self) -> &'a str {
514 if self.custom_yaml { "yaml" } else { "json" }
515 }
516
517 fn extract_messages(&self) -> Result<Vec<Message>> {
518 let mut messages = Vec::new();
519 let mut name = None;
520 let mut cur_mes = String::new();
521 let mut in_print = false;
522 for item in self.items.iter() {
523 if in_print {
524 if let Item::Command(cmd) = item {
525 match cmd.name.as_str() {
526 "print" => {
527 cur_mes.push_str(&escape_text(&cmd["data"]));
528 }
529 "rt" => {
530 cur_mes.push('\n');
531 }
532 _ => {
533 if self.end_tags.contains(&cmd.name) {
534 in_print = false;
535 messages.push(Message {
536 name: name.take(),
537 message: cur_mes,
538 });
539 cur_mes = String::new();
540 continue;
541 }
542 cur_mes.push_str(&cmd.to_xml());
543 }
544 }
545 continue;
546 }
547 }
548 if let Item::Command(cmd) = item {
549 match cmd.name.as_str() {
550 "print" => {
551 cur_mes.push_str(&escape_text(&cmd["data"]));
552 in_print = true;
553 }
554 "name" => {
555 let v = (cmd.attributes.len() - 1).to_string();
556 name = Some(cmd[v].to_owned());
557 }
558 "msgon" => {
559 in_print = true;
560 }
561 "sel_text" => {
562 let t = &cmd["text"];
563 if !t.is_empty() {
564 messages.push(Message {
565 name: None,
566 message: t.to_owned(),
567 });
568 }
569 }
570 "RegisterTextToHistory" => {
571 let t = &cmd["1"];
572 if !t.is_empty() {
573 messages.push(Message {
574 name: None,
575 message: t.to_owned(),
576 });
577 }
578 }
579 _ => {}
580 }
581 }
582 }
583 if !cur_mes.is_empty() {
584 messages.push(Message {
585 name: name.take(),
586 message: cur_mes,
587 });
588 }
589 Ok(messages)
590 }
591
592 fn import_messages<'a>(
593 &'a self,
594 messages: Vec<Message>,
595 mut file: Box<dyn WriteSeek + 'a>,
596 _filename: &str,
597 encoding: Encoding,
598 replacement: Option<&'a ReplacementTable>,
599 ) -> Result<()> {
600 file.write_all(b"ASB\0\0")?;
601 let mut items = self.items.clone();
602 let mut name_index = None;
603 let mut mes_index = 0;
604 let mut item_index = 0;
605 let mut print_index = None;
606 let mut hcls_index = 1;
607 while item_index < items.len() {
608 if let Some(print_ind) = print_index.clone() {
609 if let Some(end_tag) = items[item_index].command_name_in(&self.end_tags) {
610 let message = messages
611 .get(mes_index)
612 .ok_or(anyhow::anyhow!("Not enough messages."))?;
613 if let Some(name_index) = name_index.take() {
614 let mut name = match &message.name {
615 Some(name) => name.to_owned(),
616 None => return Err(anyhow::anyhow!("Message without name.")),
617 };
618 if let Some(replacement) = replacement {
619 for (k, v) in &replacement.map {
620 name = name.replace(k, v);
621 }
622 }
623 if let Item::Command(cmd) = &mut items[name_index] {
624 if cmd.attributes.len() > 1 {
625 cmd.attributes
626 .insert(format!("{}", cmd.attributes.len() - 1), name);
627 } else {
628 let oname = cmd
629 .attributes
630 .get("0")
631 .ok_or(anyhow::anyhow!("No name attribute found."))?;
632 if oname != &name {
633 cmd.attributes.insert("1".to_string(), name);
634 }
635 }
636 }
637 }
638 let mut m = message.message.clone();
639 if let Some(replacement) = replacement {
640 for (k, v) in &replacement.map {
641 m = m.replace(k, v);
642 }
643 }
644 let new_cmds =
645 TextParser::new(&m.replace("\n", "<rt>"), hcls_index, &end_tag).parse()?;
646 hcls_index += 1;
647 let new_cmds_len = new_cmds.len();
648 items.splice(print_ind..=item_index, new_cmds);
649 print_index = None;
650 item_index = print_ind + new_cmds_len;
651 mes_index += 1;
652 continue;
653 } else if items[item_index].is_command() {
654 item_index += 1;
655 continue;
656 }
657 }
658 if let Item::Command(cmd) = &mut items[item_index] {
659 match cmd.name.as_str() {
660 "print" => {
661 print_index = Some(item_index);
662 }
663 "msgon" => {
664 print_index = Some(item_index);
665 }
666 "name" => {
667 name_index = Some(item_index);
668 }
669 "sel_text" => {
670 let message = messages
671 .get(mes_index)
672 .ok_or(anyhow::anyhow!("Not enough messages."))?;
673 let mut m = message.message.clone();
674 if let Some(replacement) = replacement {
675 for (k, v) in &replacement.map {
676 m = m.replace(k, v);
677 }
678 }
679 cmd.attributes.insert("text".to_string(), m);
680 mes_index += 1;
681 }
682 "RegisterTextToHistory" => {
683 let message = messages
684 .get(mes_index)
685 .ok_or(anyhow::anyhow!("Not enough messages."))?;
686 let mut m = message.message.clone();
687 if let Some(replacement) = replacement {
688 for (k, v) in &replacement.map {
689 m = m.replace(k, v);
690 }
691 }
692 cmd.attributes.insert("1".to_string(), m);
693 mes_index += 1;
694 }
695 _ => {}
696 }
697 }
698 item_index += 1;
699 }
700 if mes_index != messages.len() {
701 return Err(anyhow::anyhow!(
702 "Not all messages were processed, expected {}, got {}",
703 messages.len(),
704 mes_index
705 ));
706 }
707 file.write_u32(items.len() as u32)?;
708 for item in items {
709 file.write_item(&item, encoding)?;
710 }
711 file.flush()?;
712 Ok(())
713 }
714
715 fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
716 let s = if self.format_lua {
717 let items: Vec<_> = self
718 .items
719 .iter()
720 .map(|s| {
721 if let Item::Command(cmd) = s {
722 if cmd.name == "lua" {
723 if let Some(script) = cmd.attributes.get("script") {
724 let mut cmd = cmd.clone();
725 cmd.attributes.insert(
726 "script".to_string(),
727 match self.format_lua(script) {
728 Ok(s) => s,
729 Err(_) => {
730 eprintln!("Warning: Failed to format Lua script.");
731 crate::COUNTER.inc_warning();
732 script.clone()
733 }
734 },
735 );
736 return Item::Command(cmd);
737 }
738 }
739 }
740 s.clone()
741 })
742 .collect();
743 self.to_string(&items)?
744 } else {
745 self.to_string(&self.items)?
746 };
747 let s = encode_string(encoding, &s, false)?;
748 let mut file = std::fs::File::create(filename)?;
749 file.write_all(&s)?;
750 Ok(())
751 }
752
753 fn custom_import<'a>(
754 &'a self,
755 custom_filename: &'a str,
756 file: Box<dyn WriteSeek + 'a>,
757 encoding: Encoding,
758 output_encoding: Encoding,
759 ) -> Result<()> {
760 create_file(
761 custom_filename,
762 file,
763 encoding,
764 output_encoding,
765 self.custom_yaml,
766 )
767 }
768}
769
770pub fn create_file<'a>(
778 custom_filename: &'a str,
779 mut writer: Box<dyn WriteSeek + 'a>,
780 encoding: Encoding,
781 output_encoding: Encoding,
782 yaml: bool,
783) -> Result<()> {
784 let f = crate::utils::files::read_file(custom_filename)?;
785 let s = decode_to_string(output_encoding, &f, true)?;
786 let items: Vec<Item> = if yaml {
787 serde_yaml_ng::from_str(&s)?
788 } else {
789 serde_json::from_str(&s)?
790 };
791 writer.write_all(b"ASB\0\0")?;
792 writer.write_u32(items.len() as u32)?;
793 for item in items {
794 writer.write_item(&item, encoding)?;
795 }
796 Ok(())
797}
798
799#[test]
800fn test_parse() {
801 let text = "Hello < & World!<tag><tags x=\"123\"><name 0=\"Ok\">Test";
802 let parser = TextParser::new(text, 1, "hcls");
803 let items = parser.parse().unwrap();
804 assert_eq!(
805 items,
806 vec![
807 Item::Command(Command {
808 name: "print".to_string(),
809 line_number: 0,
810 attributes: [("data".to_string(), "Hello < & World!".to_string())].into(),
811 }),
812 Item::Command(Command {
813 name: "tag".to_string(),
814 line_number: 0,
815 attributes: BTreeMap::new(),
816 }),
817 Item::Command(Command {
818 name: "tags".to_string(),
819 line_number: 0,
820 attributes: [("x".to_string(), "123".to_string())].into(),
821 }),
822 Item::Command(Command {
823 name: "name".to_string(),
824 line_number: 0,
825 attributes: [("0".to_string(), "Ok".to_string())].into(),
826 }),
827 Item::Command(Command {
828 name: "print".to_string(),
829 line_number: 0,
830 attributes: [("data".to_string(), "Test".to_string())].into(),
831 }),
832 Item::Command(Command {
833 name: "hcls".to_string(),
834 line_number: 0,
835 attributes: BTreeMap::from([("0".to_string(), "1".to_string())]),
836 }),
837 ]
838 )
839}
840
841#[test]
842fn test_parse2() {
843 let text = "<ruby text=\"test\">OK</ruby>Hello < & World!<tag><tags x=\"123\"><name 0=\"Ok\">Test";
844 let parser = TextParser::new(text, 1, "click");
845 let items = parser.parse().unwrap();
846 assert_eq!(
847 items,
848 vec![
849 Item::Command(Command {
850 name: "msgon".to_string(),
851 line_number: 0,
852 attributes: BTreeMap::new(),
853 }),
854 Item::Command(Command {
855 name: "ruby".to_string(),
856 line_number: 0,
857 attributes: [("text".to_string(), "test".to_string())].into(),
858 }),
859 Item::Command(Command {
860 name: "print".to_string(),
861 line_number: 0,
862 attributes: [("data".to_string(), "OK".to_string())].into(),
863 }),
864 Item::Command(Command {
865 name: "/ruby".to_string(),
866 line_number: 0,
867 attributes: BTreeMap::new(),
868 }),
869 Item::Command(Command {
870 name: "print".to_string(),
871 line_number: 0,
872 attributes: [("data".to_string(), "Hello < & World!".to_string())].into(),
873 }),
874 Item::Command(Command {
875 name: "tag".to_string(),
876 line_number: 0,
877 attributes: BTreeMap::new(),
878 }),
879 Item::Command(Command {
880 name: "tags".to_string(),
881 line_number: 0,
882 attributes: [("x".to_string(), "123".to_string())].into(),
883 }),
884 Item::Command(Command {
885 name: "name".to_string(),
886 line_number: 0,
887 attributes: [("0".to_string(), "Ok".to_string())].into(),
888 }),
889 Item::Command(Command {
890 name: "print".to_string(),
891 line_number: 0,
892 attributes: [("data".to_string(), "Test".to_string())].into(),
893 }),
894 Item::Command(Command {
895 name: "click".to_string(),
896 line_number: 0,
897 attributes: BTreeMap::new(),
898 }),
899 ]
900 )
901}