1pub fn slice_island(html: &str, island_name: &str) -> Option<String> {
11 let needle = format!(r#"spark:island="{island_name}""#);
12 let attr_pos = html.find(&needle)?;
13 let tag_open = html[..attr_pos].rfind('<')?;
15 let tag_name_start = tag_open + 1;
17 let after_name = html[tag_name_start..]
18 .find(|c: char| c.is_whitespace() || c == '>' || c == '/')
19 .unwrap_or(0);
20 let tag_name = &html[tag_name_start..tag_name_start + after_name];
21 let open_close = tag_open + html[tag_open..].find('>')?;
23 let _ = open_close;
24 let close = find_balanced_close(html, tag_open, tag_name)?;
26 Some(html[tag_open..close].to_string())
27}
28
29fn find_balanced_close(html: &str, start: usize, tag: &str) -> Option<usize> {
30 let mut depth: i32 = 0;
31 let open_marker = format!("<{tag}");
32 let close_marker = format!("</{tag}>");
33 let mut i = start;
34 while i < html.len() {
35 if html[i..].starts_with(&close_marker) {
36 depth -= 1;
37 if depth == 0 {
38 return Some(i + close_marker.len());
39 }
40 i += close_marker.len();
41 continue;
42 }
43 if html[i..].starts_with(&open_marker) {
44 if let Some(end_rel) = html[i..].find('>') {
46 let self_close = html.as_bytes().get(i + end_rel - 1) == Some(&b'/');
47 if !self_close {
48 depth += 1;
49 }
50 i += end_rel + 1;
51 continue;
52 }
53 return None;
54 }
55 let next = html[i..].chars().next().map(|c| c.len_utf8()).unwrap_or(1);
57 i += next;
58 }
59 None
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn slices_named_island() {
68 let html = r#"<div spark:id="x"><h1>head</h1><div spark:island="messages"><ul><li>hi</li></ul></div><p>footer</p></div>"#;
69 let sliced = slice_island(html, "messages").unwrap();
70 assert!(sliced.contains("<ul><li>hi</li></ul>"));
71 assert!(sliced.starts_with("<div spark:island=\"messages\""));
72 assert!(sliced.ends_with("</div>"));
73 }
74
75 #[test]
76 fn returns_none_when_missing() {
77 let html = "<div spark:id=\"x\"></div>";
78 assert!(slice_island(html, "messages").is_none());
79 }
80
81 #[test]
82 fn handles_nested_same_tag() {
83 let html = r#"<div spark:island="a"><div>inner</div><div>also</div></div>"#;
84 let sliced = slice_island(html, "a").unwrap();
85 assert_eq!(
86 sliced,
87 r#"<div spark:island="a"><div>inner</div><div>also</div></div>"#
88 );
89 }
90}