mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Escape spaces before x<hex> and a separator in XML ids
Found while testing crates/doc/src/xml_id.rs:32 (from_object_name), decoded by escaped() at :72 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Haiku 5.5
parent
4821ec92c2
commit
4e9cd60540
@@ -32,9 +32,11 @@ pub fn to_object_name(id: &str) -> String {
|
||||
pub fn from_object_name(name: &str) -> String {
|
||||
let mut out = String::with_capacity(name.len().saturating_mul(2));
|
||||
let last = name.chars().count().saturating_sub(1);
|
||||
for (i, c) in name.chars().enumerate() {
|
||||
if c == ' ' && i == last {
|
||||
// A trailing bare `_` could read back as the end of a uniqueness suffix.
|
||||
let chars: Vec<char> = name.chars().collect();
|
||||
for (i, &c) in chars.iter().enumerate() {
|
||||
if c == ' ' && (i == last || starts_fake_escape(chars.get(i + 1..).unwrap_or_default())) {
|
||||
// A trailing bare `_` could read back as the end of a uniqueness suffix, and a bare
|
||||
// `_` before `x<hex>_` would read back as an `_xHH_` escape.
|
||||
out.push_str("_x20_");
|
||||
} else if c == ' ' {
|
||||
out.push('_');
|
||||
@@ -68,6 +70,19 @@ pub fn is_valid(id: &str) -> bool {
|
||||
chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '.' | '-' | ':'))
|
||||
}
|
||||
|
||||
/// Whether the encoded `rest` (after a space) starts with `x`, 1 to 6 hex digits and a character
|
||||
/// whose encoding begins with `_`, so that a bare `_` before it would form an escape.
|
||||
fn starts_fake_escape(rest: &[char]) -> bool {
|
||||
let Some(('x', digits)) = rest.split_first() else {
|
||||
return false;
|
||||
};
|
||||
let n = digits.iter().take_while(|c| c.is_ascii_hexdigit()).count();
|
||||
if n == 0 || n > 6 {
|
||||
return false;
|
||||
}
|
||||
digits.get(n).is_some_and(|&c| c == ' ' || c == '_' || !(c.is_alphanumeric() || matches!(c, '.' | '-' | ':')))
|
||||
}
|
||||
|
||||
/// `_xHHHH_` at the start of `s`: the character and the escape's length.
|
||||
fn escaped(s: &str) -> Option<(char, usize)> {
|
||||
let body = s.strip_prefix("_x")?;
|
||||
@@ -119,6 +134,15 @@ mod tests {
|
||||
assert_eq!(to_object_name("a_x-41_b"), "a x-41 b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn space_before_x_hex_and_underscore_is_not_an_escape() {
|
||||
for name in ["layer x1 copy", "a xa b", "a xe b", "a xb b", "a x1(b", "a x1_b", "a x123456 c"] {
|
||||
let id = from_object_name(name);
|
||||
assert_eq!(to_object_name(&id), name, "name {name:?} -> id {id:?}");
|
||||
}
|
||||
assert_eq!(from_object_name("layer x1 copy"), "layer_x20_x1_copy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_names_that_cannot_start_an_xml_name() {
|
||||
for name in ["-abc", ".5 mm", "²x", "Ⅷ", "", "1abc", "Layer 1", " lead", "_a", ":a", "#"] {
|
||||
|
||||
Reference in New Issue
Block a user