pub fn extract_svg(text: &str) -> Option<String> {
let lower = text.to_ascii_lowercase();
let start = lower.find("<svg")?;
let end_rel = lower[start..].rfind("</svg>")?;
let end = start + end_rel + "</svg>".len();
Some(text[start..end].trim().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pulls_svg_from_fenced_reply() {
let reply = "Here is your glyph:\n\n```svg\n<svg viewBox=\"0 0 10 10\">\
<path d=\"M0 0 L10 0 L10 10 Z\" fill=\"black\"/></svg>\n```\nEnjoy!";
let svg = extract_svg(reply).unwrap();
assert!(svg.starts_with("<svg"));
assert!(svg.ends_with("</svg>"));
assert!(!svg.contains("```"));
assert!(!svg.contains("Enjoy"));
}
#[test]
fn handles_uppercase_tags() {
let reply = "<SVG><PATH d=\"M0 0\"/></SVG>";
assert_eq!(extract_svg(reply).unwrap(), reply);
}
#[test]
fn takes_outermost_when_nested_close_text() {
let reply = "<svg><g></g></svg>";
assert_eq!(extract_svg(reply).unwrap(), reply);
}
#[test]
fn none_without_svg() {
assert!(extract_svg("sorry, I can't draw that").is_none());
assert!(extract_svg("<svg> unterminated").is_none());
}
#[test]
fn drafted_glyph_passes_preflight() {
use crate::conlang::writing::preflight;
let reply = "Sure! Here's a bold vertical bar:\n```svg\n\
<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1000 1000\">\
<path d=\"M400 100 L600 100 L600 900 L400 900 Z\" fill=\"black\"/></svg>\n```";
let svg = extract_svg(reply).unwrap();
assert!(preflight::lint_svg(&svg).is_usable());
}
#[test]
fn stroke_only_draft_is_rejected() {
use crate::conlang::writing::preflight;
let reply = "<svg viewBox=\"0 0 1000 1000\">\
<path d=\"M100 500 L900 500\" stroke=\"black\" fill=\"none\"/></svg>";
let svg = extract_svg(reply).unwrap();
assert!(!preflight::lint_svg(&svg).is_usable());
}
}