Skip to main content

sidereon_core/astro/
xml.rs

1//! Minimal XML text helpers shared by the CCSDS message encoders (CDM, OMM).
2//!
3//! Decoding uses the `roxmltree` DOM reader; only encoding needs to escape the
4//! handful of characters that are significant in element text. This lives in one
5//! place so the message encoders do not each carry their own copy.
6
7/// Return the first character that cannot appear in XML 1.0 text.
8pub(crate) fn first_illegal_xml_1_0_char(value: &str) -> Option<char> {
9    value.chars().find(|&ch| !is_xml_1_0_char(ch))
10}
11
12fn is_xml_1_0_char(ch: char) -> bool {
13    matches!(
14        ch,
15        '\u{9}' | '\u{A}' | '\u{D}'
16            | '\u{20}'..='\u{D7FF}'
17            | '\u{E000}'..='\u{FFFD}'
18            | '\u{10000}'..='\u{10FFFF}'
19    )
20}
21
22/// Escape XML metacharacters in element text.
23pub(crate) fn escape(value: &str) -> String {
24    value
25        .replace('&', "&amp;")
26        .replace('<', "&lt;")
27        .replace('>', "&gt;")
28        .replace('"', "&quot;")
29        .replace('\'', "&apos;")
30        .replace('\r', "&#xD;")
31}
32
33/// Escape an optional string, treating `None` as empty.
34pub(crate) fn escape_opt(value: &Option<String>) -> String {
35    value.as_deref().map(escape).unwrap_or_default()
36}