1#![warn(
13 missing_docs,
14 rust_2018_idioms,
15 missing_debug_implementations,
16 rustdoc::broken_intra_doc_links
17)]
18use core::fmt;
19use regex::Regex;
20use std::collections::HashMap;
21use std::fmt::Display;
22
23static RE_OPEN_TAG: &str = r#"^\[(?P<tag>[^/\]]+?\S*?)((?:[ \t]+\S+?)?="?(?P<val>[^\]\n]*?))?"?\]"#;
24static RE_CLOSE_TAG: &str = r#"^\[/(?P<tag>[^/\]]+?\S*?)\]"#;
25static RE_NEWLINE: &str = r#"^\r?\n"#;
26
27#[derive(Debug, PartialEq, Eq, Clone)]
32pub enum BBTag {
33 None,
35 Bold,
36 Italic,
37 Underline,
38 Strikethrough,
39 FontSize,
40 FontColor,
41 Center,
42 Left,
43 Right,
44 Superscript,
45 Subscript,
46 Blur,
47 Quote,
48 Spoiler,
49 Link,
50 Email,
51 Image,
52 ListOrdered,
53 ListUnordered,
54 ListItem,
55 Code,
56 Preformatted,
57 Table,
58 TableHeading,
59 TableRow,
60 TableCell,
61 YouTube,
62 Unknown,
64}
65impl From<&str> for BBTag {
66 fn from(value: &str) -> BBTag {
67 let binding = value.trim().to_lowercase();
68 let trim_tag = binding.as_str();
69 match trim_tag {
70 "b" => BBTag::Bold,
71 "i" => BBTag::Italic,
72 "u" => BBTag::Underline,
73 "s" => BBTag::Strikethrough,
74 "size" => BBTag::FontSize,
75 "color" => BBTag::FontColor,
76 "center" => BBTag::Center,
77 "left" => BBTag::Left,
78 "right" => BBTag::Right,
79 "sup" => BBTag::Superscript,
80 "sub" => BBTag::Subscript,
81 "blur" => BBTag::Blur,
82 "email" => BBTag::Email,
83 "quote" => BBTag::Quote,
84 "spoiler" => BBTag::Spoiler,
85 "url" => BBTag::Link,
86 "img" => BBTag::Image,
87 "ul" | "list" => BBTag::ListUnordered,
88 "ol" => BBTag::ListOrdered,
89 "li" | "*" => BBTag::ListItem,
90 "code" | "highlight" => BBTag::Code,
91 "pre" => BBTag::Preformatted,
92 "table" => BBTag::Table,
93 "tr" => BBTag::TableRow,
94 "th" => BBTag::TableHeading,
95 "td" => BBTag::TableCell,
96 "youtube" => BBTag::YouTube,
97 "" => BBTag::None,
98 &_ => BBTag::Unknown,
99 }
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct BBNode {
106 pub text: String,
108 pub tag: BBTag,
110 pub value: Option<String>,
112 pub parent: Option<i32>,
114 pub children: Vec<i32>,
116}
117impl Default for BBNode {
118 fn default() -> Self {
119 Self {
120 text: "".to_string(),
121 tag: BBTag::None,
122 value: None,
123 parent: None,
124 children: vec![],
125 }
126 }
127}
128
129impl BBNode {
130 pub fn new(text: &str, tag: BBTag) -> BBNode {
132 BBNode {
133 text: String::from(text),
134 tag,
135 value: None,
136 parent: None,
137 children: vec![],
138 }
139 }
140}
141
142#[derive(Clone, PartialEq, Eq)]
144pub struct BBTree {
145 pub nodes: HashMap<i32, BBNode>,
147 id: i32,
148}
149impl Default for BBTree {
150 fn default() -> Self {
151 Self {
152 nodes: HashMap::new(),
153 id: -1,
154 }
155 }
156}
157impl Display for BBTree {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 writeln!(f, "Nodes: {}", self.id)?;
160 self.fmt_node(f, 0)
161 }
162}
163impl fmt::Debug for BBTree {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 writeln!(f, "Nodes: {}", self.id)?;
166 self.fmt_node(f, 0)
167 }
168}
169
170impl BBTree {
171 pub fn get_node(&self, i: i32) -> &BBNode {
173 self.nodes.get(&i).unwrap()
174 }
175 pub fn get_node_mut(&mut self, i: i32) -> &mut BBNode {
177 self.nodes.get_mut(&i).unwrap()
178 }
179 pub fn add_node(&mut self, node: BBNode) -> i32 {
181 self.id += 1;
182 self.nodes.insert(self.id, node);
183 self.id
184 }
185 pub fn get_depth(&self, i: i32) -> usize {
187 if self.get_node(i).parent.is_none() {
188 return 0;
189 }
190 return 1 + self.get_depth(self.get_node(i).parent.unwrap());
191 }
192 fn fmt_node(&self, f: &mut std::fmt::Formatter<'_>, i: i32) -> std::fmt::Result {
193 let indent = self.get_depth(i) * 2;
194 let node = self.get_node(i);
195 writeln!(f, "{:indent$}ID : {}", "", i, indent = indent)?;
196 writeln!(f, "{:indent$}Text : {}", "", node.text, indent = indent)?;
197 writeln!(f, "{:indent$}Tag : {:?}", "", node.tag, indent = indent)?;
198 writeln!(f, "{:indent$}Value : {:?}", "", node.value, indent = indent)?;
199 writeln!(
200 f,
201 "{:indent$}Parent: {:?}",
202 "",
203 node.parent,
204 indent = indent
205 )?;
206 writeln!(f)?;
207 for child in node.children.iter() {
208 self.fmt_node(f, *child)?;
209 }
210 Ok(())
211 }
212}
213
214#[derive(Debug)]
216pub struct BBCode {
217 open_matcher: Regex,
218 close_matcher: Regex,
219 newline_matcher: Regex,
220}
221impl Default for BBCode {
222 fn default() -> Self {
223 Self {
224 open_matcher: Regex::new(RE_OPEN_TAG).unwrap(),
225 close_matcher: Regex::new(RE_CLOSE_TAG).unwrap(),
226 newline_matcher: Regex::new(RE_NEWLINE).unwrap(),
227 }
228 }
229}
230
231impl BBCode {
232 pub fn parse(&self, input: &str) -> BBTree {
234 let mut slice = &input[0..];
236
237 let mut tree = BBTree::default();
240 let mut curr_node = tree.add_node(BBNode::default());
241 let mut closed_tag = false;
242
243 while !slice.is_empty() {
244 if let Some(captures) = self.newline_matcher.captures(slice) {
247 if tree.get_node(curr_node).tag == BBTag::ListItem {
248 curr_node = tree.get_node(curr_node).parent.unwrap();
250
251 slice = &slice[captures.get(0).unwrap().as_str().len()..];
253 closed_tag = true;
254 continue;
255 }
256 if tree.get_node(curr_node).parent.is_some()
257 && tree.get_node(tree.get_node(curr_node).parent.unwrap()).tag
258 == BBTag::ListItem
259 {
260 curr_node = tree
263 .get_node(tree.get_node(curr_node).parent.unwrap())
264 .parent
265 .unwrap();
266 slice = &slice[captures.get(0).unwrap().as_str().len()..];
268 closed_tag = true;
269 continue;
270 }
271 }
272 if let Some(captures) = self.open_matcher.captures(slice) {
274 let tag = captures.name("tag").unwrap().as_str();
278 let curr_node_obj = tree.get_node(curr_node);
279 if curr_node_obj.tag == BBTag::None && curr_node != 0 {
281 curr_node = curr_node_obj.parent.unwrap();
282 }
283 let mut node = BBNode {
284 tag: BBTag::from(tag),
285 parent: Some(curr_node),
286 ..Default::default()
287 };
288 if let Some(val) = captures.name("val") {
289 node.value = Some(val.as_str().to_string());
290 }
291 let new_id = tree.add_node(node);
292 tree.get_node_mut(curr_node).children.push(new_id);
293 curr_node = new_id;
294
295 slice = &slice[captures.get(0).unwrap().as_str().len()..];
297 closed_tag = false;
298 continue;
299 } else if let Some(captures) = self.close_matcher.captures(slice) {
300 let tag = captures.name("tag").unwrap().as_str();
302 let bbtag = BBTag::from(tag);
303 let curr_node_obj = tree.get_node(curr_node);
304 if curr_node_obj.tag == BBTag::None && !curr_node_obj.text.is_empty() {
305 let parent = tree.get_node(curr_node_obj.parent.unwrap());
308 if parent.tag == bbtag {
309 curr_node = parent.parent.unwrap();
310 slice = &slice[captures.get(0).unwrap().as_str().len()..];
311 closed_tag = true;
312 continue;
313 }
314 }
315 if bbtag == tree.get_node(curr_node).tag {
316 curr_node = tree.get_node(curr_node).parent.unwrap();
319 slice = &slice[captures.get(0).unwrap().as_str().len()..];
321 closed_tag = true;
322 continue;
323 } else {
324 slice = &slice[captures.get(0).unwrap().as_str().len()..];
326 closed_tag = false;
327 continue;
328 }
329 }
330
331 if let Some(ch) = slice.chars().next() {
333 if closed_tag {
334 let node = BBNode {
336 parent: Some(curr_node),
337 ..Default::default()
338 };
339 let new_id = tree.add_node(node);
340 tree.get_node_mut(curr_node).children.push(new_id);
341 curr_node = new_id;
342 }
343
344 tree.get_node_mut(curr_node).text.push(ch);
345 slice = &slice[ch.len_utf8()..];
346 closed_tag = false;
347 } else {
348 break;
350 }
351 }
352
353 tree
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 macro_rules! bbtest_regex {
362 ($($name:ident: $value:expr;)*) => {
363 $(
364 #[test]
365 fn $name() {
366 let open_re = Regex::new(RE_OPEN_TAG).unwrap();
367 let (input, expected_tag, expected_val) = $value;
368
369 let captures = open_re.captures(input);
371 if expected_tag.is_empty() && expected_val.is_empty() {
372 assert!(captures.is_none());
373 } else {
374 let captures = captures.unwrap();
375 let tag = captures.name("tag").unwrap().as_str();
376 assert_eq!(expected_tag, tag);
377
378 if expected_val.is_empty() {
379 let val = captures.name("val");
380 assert!(val.is_none());
381 } else {
382 let val = captures.name("val").unwrap().as_str();
383 assert_eq!(expected_val, val);
384 }
385 }
386 }
393 )*
394 }
395 }
396
397 macro_rules! test_lines {
398 ($($name:ident: $value:expr,)*) => {
399 $(
400 #[test]
401 fn $name() {
402 let (input, expected_tree) = $value;
403 let parser = BBCode::default();
404 let tree = parser.parse(input);
405 assert_eq!(expected_tree, tree);
406 }
407 )*
408 }
409 }
410
411 test_lines! {
412 test_one_tag: (
413 "[b]bold text[/b]",
414 BBTree {
415 nodes: HashMap::from([
416 (0, BBNode {
417 text: "".to_string(),
418 tag: BBTag::None,
419 children: vec![1],
420 ..Default::default()
421 }),
422 (1, BBNode {
423 text: "bold text".to_string(),
424 tag: BBTag::Bold,
425 parent: Some(0),
426 ..Default::default()
427 })
428 ]),
429 id: 1,
430 }),
431 test_one_tag_value: (
432 r#"[size="3"]big text[/size]"#,
433 BBTree {
434 nodes: HashMap::from([
435 (0, BBNode {
436 text: "".to_string(),
437 tag: BBTag::None,
438 children: vec![1],
439 ..Default::default()
440 }),
441 (1, BBNode {
442 text: "big text".to_string(),
443 tag: BBTag::FontSize,
444 value: Some("3".to_string()),
445 parent: Some(0),
446 ..Default::default()
447 })
448 ]),
449 id: 1,
450 }),
451 test_braces_not_tags: (
452 "text [] is [i]a[/i] thing",
453 BBTree {
454 nodes: HashMap::from([
455 (0, BBNode {
456 text: "text [] is ".to_string(),
457 children: vec![1, 2],
458 ..Default::default()
459 }),
460 (1, BBNode {
461 text: "a".to_string(),
462 tag: BBTag::Italic,
463 parent: Some(0),
464 ..Default::default()
465 }),
466 (2, BBNode {
467 text: " thing".to_string(),
468 parent: Some(0),
469 ..Default::default()
470 })
471 ]),
472 id: 2
473 }),
474 test_post_text: (
475 "[i]thing[/i] after",
476 BBTree {
477 nodes: HashMap::from([
478 (0, BBNode {
479 children: vec![1, 2],
480 ..Default::default()
481 }),
482 (1, BBNode {
483 text: "thing".to_string(),
484 tag: BBTag::Italic,
485 parent: Some(0),
486 ..Default::default()
487 }),
488 (2, BBNode {
489 text: " after".to_string(),
490 parent: Some(0),
491 ..Default::default()
492 })
493 ]),
494 id: 2
495 }
496 ),
497 test_ul_list_items: (
498 r#"[ul]
499[*]item one
500[*]item two
501[/ul]"#,
502 BBTree {
503 nodes: HashMap::from([
504 (0, BBNode {
505 children: vec![1],
506 ..Default::default()
507 }),
508 (1, BBNode {
509 text: "\n".to_string(),
510 tag: BBTag::ListUnordered,
511 parent: Some(0),
512 children: vec![2, 3],
513 ..Default::default()
514 }),
515 (2, BBNode {
516 text: "item one".to_string(),
517 tag: BBTag::ListItem,
518 parent: Some(1),
519 ..Default::default()
520 }),
521 (3, BBNode {
522 text: "item two".to_string(),
523 tag: BBTag::ListItem,
524 parent: Some(1),
525 ..Default::default()
526 })
527 ]),
528 id: 3
529 }
530 ),
531 test_ul_list_item_subtag: (
532 r#"[ul]
533[*][SIZE=4]wow[/SIZE]
534[/ul]"#,
535 BBTree {
536 nodes: HashMap::from([
537 (0, BBNode {
538 children: vec![1],
539 ..Default::default()
540 }),
541 (1, BBNode {
542 text: "\n".to_string(),
543 tag: BBTag::ListUnordered,
544 parent: Some(0),
545 children: vec![2],
546 ..Default::default()
547 }),
548 (2, BBNode {
549 tag: BBTag::ListItem,
550 parent: Some(1),
551 children: vec![3],
552 ..Default::default()
553 }),
554 (3, BBNode {
555 tag: BBTag::FontSize,
556 value: Some("4".to_string()),
557 text: "wow".to_string(),
558 parent: Some(2),
559 ..Default::default()
560 })
561 ]),
562 id: 3
563 }
564 ),
565 test_serveral: (
566 r#"[COLOR=#E5E5E5][CENTER][SIZE=5][COLOR=#00ffff]This Is A Title[/COLOR][/SIZE][/CENTER]
567
568[/COLOR][CENTER][SIZE=4][COLOR=#ff8c00]Now with new stuff![/COLOR][/SIZE][/CENTER]"#,
569 BBTree {
570 nodes: HashMap::from([
571 (0, BBNode {
572 children: vec![1, 6],
573 ..Default::default()
574 }),
575 (1, BBNode {
576 tag: BBTag::FontColor,
577 value: Some("#E5E5E5".to_string()),
578 parent: Some(0),
579 children: vec![2, 5],
580 ..Default::default()
581 }),
582 (2, BBNode {
583 tag: BBTag::Center,
584 parent: Some(1),
585 children: vec![3],
586 ..Default::default()
587 }),
588 (3, BBNode {
589 tag: BBTag::FontSize,
590 value: Some("5".to_string()),
591 parent: Some(2),
592 children: vec![4],
593 ..Default::default()
594 }),
595 (4, BBNode {
596 text: "This Is A Title".to_string(),
597 tag: BBTag::FontColor,
598 value: Some("#00ffff".to_string()),
599 parent: Some(3),
600 ..Default::default()
601 }),
602 (5, BBNode {
603 text: "\n\n".to_string(),
604 parent: Some(1),
605 ..Default::default()
606 }),
607 (6, BBNode {
608 tag: BBTag::Center,
609 parent: Some(0),
610 children: vec![7],
611 ..Default::default()
612 }),
613 (7, BBNode {
614 tag: BBTag::FontSize,
615 value: Some("4".to_string()),
616 parent: Some(6),
617 children: vec![8],
618 ..Default::default()
619 }),
620 (8, BBNode {
621 text: "Now with new stuff!".to_string(),
622 tag: BBTag::FontColor,
623 value: Some("#ff8c00".to_string()),
624 parent: Some(7),
625 ..Default::default()
626 })
627 ]),
628 id: 8
629 }
630 ),
631 test_complex_large: (
632 r#"[I][B][COLOR="Orange"]AddOn Name[/COLOR][/B][/I] is an addon
633
634[COLOR="DeepSkyBlue"]Color text:[/COLOR]
635[LIST]
636[*][COLOR="Orange"]Colored list item1[/COLOR]
637[LIST]
638[*]sublist1 item
639[*]item2:
640[LIST]
641[*]Sublist2 item [I]wow[/I] and [I]wooh[/I], is a thing
642[/LIST]
643
644Non listitem text in list.
645[/LIST]
646
647
648[*][COLOR="Orange"]List item2[/COLOR]
649[/LIST]
650
651[LIST]
652[*][COLOR="Orange"]color list item:[/COLOR][LIST][*] inline sub list item
653[/LIST][/LIST]
654"#,
655 BBTree {
656 nodes: HashMap::from([
657 (0, BBNode {
658 children: vec![1, 4, 5, 6, 7, 23, 24],
659 ..Default::default()
660 }),
661 (1, BBNode {
662 tag: BBTag::Italic,
663 parent: Some(0),
664 children: vec![2],
665 ..Default::default()
666 }),
667 (2, BBNode {
668 tag: BBTag::Bold,
669 parent: Some(1),
670 children: vec![3],
671 ..Default::default()
672 }),
673 (3, BBNode {
674 text: "AddOn Name".to_string(),
675 tag: BBTag::FontColor,
676 value: Some("Orange".to_string()),
677 parent: Some(2),
678 ..Default::default()
679 }),
680 (4, BBNode {
681 text: " is an addon\n\n".to_string(),
682 parent: Some(0),
683 ..Default::default()
684 }),
685 (5, BBNode {
686 text: "Color text:".to_string(),
687 tag: BBTag::FontColor,
688 value: Some("DeepSkyBlue".to_string()),
689 parent: Some(0),
690 ..Default::default()
691 }),
692 (6, BBNode {
693 text: "\n".to_string(),
694 parent: Some(0),
695 ..Default::default()
696 }),
697 (7, BBNode {
698 text: "\n".to_string(),
699 tag: BBTag::ListUnordered,
700 parent: Some(0),
701 children: vec![8, 10, 20, 21],
702 ..Default::default()
703 }),
704 (8, BBNode {
705 tag: BBTag::ListItem,
706 parent: Some(7),
707 children: vec![9],
708 ..Default::default()
709 }),
710 (9, BBNode {
711 text: "Colored list item1".to_string(),
712 tag: BBTag::FontColor,
713 value: Some("Orange".to_string()),
714 parent: Some(8),
715 ..Default::default()
716 }),
717 (10, BBNode {
718 text: "\n".to_string(),
719 tag: BBTag::ListUnordered,
720 parent: Some(7),
721 children: vec![11, 12, 13, 19],
722 ..Default::default()
723 }),
724 (11, BBNode {
725 text: "sublist1 item".to_string(),
726 tag: BBTag::ListItem,
727 parent: Some(10),
728 ..Default::default()
729 }),
730 (12, BBNode {
731 text: "item2:".to_string(),
732 tag: BBTag::ListItem,
733 parent: Some(10),
734 ..Default::default()
735 }),
736 (13, BBNode {
737 text: "\n".to_string(),
738 tag: BBTag::ListUnordered,
739 parent: Some(10),
740 children: vec![14],
741 ..Default::default()
742 }),
743 (14, BBNode {
744 text: "Sublist2 item ".to_string(),
745 tag: BBTag::ListItem,
746 parent: Some(13),
747 children: vec![15, 16, 17, 18],
748 ..Default::default()
749 }),
750 (15, BBNode {
751 text: "wow".to_string(),
752 tag: BBTag::Italic,
753 parent: Some(14),
754 ..Default::default()
755 }),
756 (16, BBNode {
757 text: " and ".to_string(),
758 parent: Some(14),
759 ..Default::default()
760 }),
761 (17, BBNode {
762 text: "wooh".to_string(),
763 tag: BBTag::Italic,
764 parent: Some(14),
765 ..Default::default()
766 }),
767 (18, BBNode {
768 text: ", is a thing".to_string(),
769 parent: Some(14),
770 ..Default::default()
771 }),
772 (19, BBNode {
773 text: "\n\nNon listitem text in list.\n".to_string(),
774 parent: Some(10),
775 ..Default::default()
776 }),
777 (20, BBNode {
778 text: "\n\n\n".to_string(),
779 parent: Some(7),
780 ..Default::default()
781 }),
782 (21, BBNode {
783 tag: BBTag::ListItem,
784 parent: Some(7),
785 children: vec![22],
786 ..Default::default()
787 }),
788 (22, BBNode {
789 text: "List item2".to_string(),
790 tag: BBTag::FontColor,
791 value: Some("Orange".to_string()),
792 parent: Some(21),
793 ..Default::default()
794 }),
795 (23, BBNode {
796 text: "\n\n".to_string(),
797 parent: Some(0),
798 ..Default::default()
799 }),
800 (24, BBNode {
801 text: "\n".to_string(),
802 tag: BBTag::ListUnordered,
803 parent: Some(0),
804 children: vec![25],
805 ..Default::default()
806 }),
807 (25, BBNode {
808 tag: BBTag::ListItem,
809 parent: Some(24),
810 children: vec![26, 27],
811 ..Default::default()
812 }),
813 (26, BBNode {
814 text: "color list item:".to_string(),
815 tag: BBTag::FontColor,
816 value: Some("Orange".to_string()),
817 parent: Some(25),
818 ..Default::default()
819 }),
820 (27, BBNode {
821 tag: BBTag::ListUnordered,
822 parent: Some(25),
823 children: vec![28],
824 ..Default::default()
825 }),
826 (28, BBNode {
827 text: " inline sub list item".to_string(),
828 tag: BBTag::ListItem,
829 parent: Some(27),
830 ..Default::default()
831 })
832 ]),
833 id: 28
834 }
835 ),
836 }
837
838 #[test]
839 fn build_re() {
840 let _open_re = Regex::new(RE_OPEN_TAG).unwrap();
842 let _close_re = Regex::new(RE_CLOSE_TAG).unwrap();
843 }
844
845 #[test]
846 fn bbcode_default() {
847 let _bbcode = BBCode::default();
849 }
850
851 #[test]
852 fn parse() {
853 let parser = BBCode::default();
854 let tree = parser.parse(r#"[SIZE="3"]Features:[/SIZE]
856
857[LIST]
858[*][B][URL=https://github.com/sirinsidiator/ESO-LibAddonMenu/wiki/Controls]Controls[/URL][/B] - LAM offers different control types to build elaborate settings menus
859[*][B]Reset to Default[/B] - LAM can restore the settings to their default state with one key press
860[*][B]Additional AddOn Info[/B] - Add a version label and URLs for website, donations, translations or feedback
861[*][B]AddOn Search[/B] - Can't find the settings for your AddOn between the other hundred entries? No problem! Simply use the text search to quickly find what you are looking for
862[*][B]Slash Commands[/B] - Provides a shortcut to open your settings menu from chat
863[*][B]Tooltips[/B] - In case you need more space to explain what a control does, simply use a tooltip
864[*][B]Warnings[/B] - If your setting causes some unexpected behaviour, you can simply slap a warning on them
865[*][B]Dangerous Buttons[/B] - when flagged as such, a button will have red text and ask for confirmation before it runs any action
866[*][B]Required UI Reload[/B] - For cases where settings have to reload the UI or should be stored to disk right away, LAM offers a user friendly way to ask for a UI reload.
867[*]Support for all 5 official languages and 6 custom localisation projects
868[/LIST]"#);
869 println!("{}", tree);
870
871 }
879
880 bbtest_regex! {
890 empty: ("hello", "", "");
891 bold: ("[b]hello[/b]", "b", "");
892 no_tag: ("[]hello[/]", "", "");
893 tag_and_val: ("[size=3]large[/size]", "size", "3");
894 tag_and_val_quote: (r#"[size="3"]large[/size]"#, "size", "3");
895 url: ("[url=https://www.com]some url[/url] ", "url", "https://www.com");
896 multi_tag: (r#"[SIZE="2"][COLOR=#e5e5e5][B]Team:[/COLOR][/B] "#, "SIZE", "2");
897 }
898
899 #[test]
900 fn test_node_eq() {
901 let node1 = BBNode {
902 text: "text1".to_string(),
903 ..Default::default()
904 };
905 let node2 = BBNode {
906 text: "text1".to_string(),
907 ..Default::default()
908 };
909 assert_eq!(node1, node2);
910 }
911
912 #[test]
913 fn test_node_ne_text() {
914 let node1 = BBNode {
915 text: "text1".to_string(),
916 ..Default::default()
917 };
918 let node2 = BBNode {
919 text: "text2".to_string(),
920 ..Default::default()
921 };
922 assert_ne!(node1, node2);
923 }
924
925 #[test]
926 fn test_node_ne_child() {
927 let node1 = BBNode {
928 text: "text1".to_string(),
929 children: vec![1],
930 ..Default::default()
931 };
932 let node2 = BBNode {
933 text: "text1".to_string(),
934 children: vec![2],
935 ..Default::default()
936 };
937 assert_ne!(node1, node2);
938 }
939}