1use kindaxml::{AttrValue, ParserConfig, RecoveryStrategy, UnknownMode, parse};
2use std::collections::HashSet;
3
4fn main() {
5 let config = build_config();
6 let samples = vec![
7 ("Inline span", "We shipped <cite id=\"1\">last week</cite>."),
8 (
9 "Retroactive cite",
10 "We shipped last week <cite id=1>. More info <note>soon",
11 ),
12 (
13 "Forward token",
14 "Risks: <risk level=high> load tests are late. <risk level=low>Docs slipping",
15 ),
16 (
17 "Self-closing markers",
18 "Todo list: <todo id=7/>finish rollout <todo/> update docs.",
19 ),
20 ];
21
22 for (label, input) in samples {
23 println!("=== {} ===", label);
24 println!("Original text:\n{}\n", input);
25 let parsed = parse(input, &config);
26 println!("Parsed text:\n{}\nSegments:", parsed.text);
27
28 for segment in &parsed.segments {
29 if segment.annotations.is_empty() {
30 println!("- '{}'", segment.text);
31 } else {
32 let anns: Vec<String> = segment
33 .annotations
34 .iter()
35 .map(|ann| {
36 let attrs = format_attrs(&ann.attrs);
37 if attrs.is_empty() {
38 ann.tag.clone()
39 } else {
40 format!("{} [{}]", ann.tag, attrs)
41 }
42 })
43 .collect();
44 println!("- '{}' ({})", segment.text, anns.join("; "));
45 }
46 }
47
48 if !parsed.markers.is_empty() {
49 println!("Markers:");
50 for marker in &parsed.markers {
51 let attrs = format_attrs(&marker.annotation.attrs);
52 let tag = if attrs.is_empty() {
53 marker.annotation.tag.clone()
54 } else {
55 format!("{} [{}]", marker.annotation.tag, attrs)
56 };
57 println!("- @{} {}", marker.pos, tag);
58 }
59 }
60
61 println!();
62 }
63}
64
65fn build_config() -> ParserConfig {
66 let recognized_tags = ["cite", "note", "risk", "todo"]
67 .into_iter()
68 .map(String::from)
69 .collect::<HashSet<_>>();
70 let mut cfg = ParserConfig {
71 recognized_tags,
72 ..ParserConfig::default()
73 };
74 cfg.per_tag_recovery
75 .insert("cite".into(), RecoveryStrategy::RetroLine);
76 cfg.per_tag_recovery
77 .insert("note".into(), RecoveryStrategy::ForwardUntilNewline);
78 cfg.per_tag_recovery
79 .insert("risk".into(), RecoveryStrategy::ForwardNextToken);
80 cfg.case_sensitive_tags = false;
81 cfg.unknown_mode = UnknownMode::Strip;
82 cfg
83}
84
85fn format_attrs(attrs: &std::collections::HashMap<String, AttrValue>) -> String {
86 let mut pairs: Vec<_> = attrs.iter().collect();
87 pairs.sort_by_key(|(k, _)| *k);
88 pairs
89 .into_iter()
90 .map(|(k, v)| match v {
91 AttrValue::Bool(b) => format!("{}={}", k, b),
92 AttrValue::Str(s) => format!("{}=\"{}\"", k, s),
93 })
94 .collect::<Vec<_>>()
95 .join(", ")
96}