mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
Almost every Craft app carries its own small XML helper: a bounded element tree on a pull parser and an escaping string writer. This is the shared one, so each app can drop its copy. - An owned tree (El, Node) read from bytes with limits on input size, depth, node count and attributes per element. A limit exceeded is an error. No DTD is processed and no entity is expanded beyond the five predefined ones and numeric character references. UTF-8 and UTF-16. - Names as written, by local name, or rewritten to the caller's canonical prefixes by namespace URI, with hooks for a prefix that depends on the parent and for attribute values that hold prefixes. - Lenient and strict reading; streaming of repeated elements to a callback; first_start; a pull reader with byte ranges. - Accessors, typed attribute getters, mutation and a builder. Where the apps disagreed (whole or local names, own or deep text, exact or lossy numbers) there are two spellings. - esc, esc_attr, compact and pretty serialising, and a Writer. A name that is not an XML name is never written. Built on quick-xml 0.41's tokenizer (MIT), the version the apps use. The apps' own tests for their helpers are ported in tests/apps.rs. No app is switched here.
80 lines
2.8 KiB
Rust
80 lines
2.8 KiB
Rust
//! Attribute and text values as numbers and booleans. Every function trims surrounding
|
|
//! whitespace and returns `None` for anything it does not read.
|
|
|
|
/// A decimal `u32`. Signs other than `+`, fractions and out-of-range values are `None`.
|
|
pub fn to_u32(s: &str) -> Option<u32> {
|
|
s.trim().parse().ok()
|
|
}
|
|
|
|
/// A decimal `i32`.
|
|
pub fn to_i32(s: &str) -> Option<i32> {
|
|
s.trim().parse().ok()
|
|
}
|
|
|
|
/// A decimal `i64`.
|
|
pub fn to_i64(s: &str) -> Option<i64> {
|
|
s.trim().parse().ok()
|
|
}
|
|
|
|
/// A finite `f64`. `NaN` and the infinities are `None`.
|
|
pub fn to_f64(s: &str) -> Option<f64> {
|
|
s.trim().parse::<f64>().ok().filter(|v| v.is_finite())
|
|
}
|
|
|
|
/// A boolean in any spelling the office formats use: `1`, `true`, `on`, `t` and `0`, `false`,
|
|
/// `off`, `f`. Case matters, as in `xsd:boolean`.
|
|
pub fn to_bool(s: &str) -> Option<bool> {
|
|
match s.trim() {
|
|
"1" | "true" | "on" | "t" => Some(true),
|
|
"0" | "false" | "off" | "f" => Some(false),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// An integer that may be written as a decimal fraction or lie out of range: a finite fraction
|
|
/// is truncated toward zero and anything beyond ±9.0e18 is clamped.
|
|
pub fn to_i64_lossy(s: &str) -> Option<i64> {
|
|
to_i64(s).or_else(|| to_f64(s).map(|f| f.clamp(-9.0e18, 9.0e18) as i64))
|
|
}
|
|
|
|
/// [`to_i64_lossy`], clamped to `i32`.
|
|
pub fn to_i32_lossy(s: &str) -> Option<i32> {
|
|
to_i64_lossy(s).map(|v| v.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32)
|
|
}
|
|
|
|
/// [`to_i64_lossy`], clamped to `u32` (a negative value reads as 0).
|
|
pub fn to_u32_lossy(s: &str) -> Option<u32> {
|
|
to_i64_lossy(s).map(|v| v.clamp(0, i64::from(u32::MAX)) as u32)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn strict_and_lossy_numbers() {
|
|
assert_eq!(to_u32(" 42 "), Some(42));
|
|
assert_eq!((to_u32("-1"), to_u32("1.5"), to_u32("4294967296"), to_u32("")), (None, None, None, None));
|
|
assert_eq!((to_i32("-7"), to_i64("-9000000000")), (Some(-7), Some(-9_000_000_000)));
|
|
assert_eq!((to_f64("2.5"), to_f64("NaN"), to_f64("inf"), to_f64("-inf"), to_f64("x")), (Some(2.5), None, None, None, None));
|
|
assert_eq!(
|
|
(to_i64_lossy("12.9"), to_i64_lossy("-12.9"), to_i64_lossy("1e30"), to_i64_lossy("NaN")),
|
|
(Some(12), Some(-12), Some(9_000_000_000_000_000_000), None)
|
|
);
|
|
assert_eq!((to_i32_lossy("99999999999"), to_u32_lossy("-5"), to_u32_lossy("99999999999")), (Some(i32::MAX), Some(0), Some(u32::MAX)));
|
|
}
|
|
|
|
#[test]
|
|
fn boolean_spellings() {
|
|
for t in ["1", "true", "on", "t", " true "] {
|
|
assert_eq!(to_bool(t), Some(true), "{t}");
|
|
}
|
|
for f in ["0", "false", "off", "f"] {
|
|
assert_eq!(to_bool(f), Some(false), "{f}");
|
|
}
|
|
for other in ["", "TRUE", "yes", "2"] {
|
|
assert_eq!(to_bool(other), None, "{other}");
|
|
}
|
|
}
|
|
}
|