Skip to main content

basic/
basic.rs

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}