Signals read an image's alt and title text, not the title alone (#267)
xkcd's posts are an <img> whose alt and title are the comic's words. text_of dropped tags whole, attributes and all, so Jev was sent "Feed: xkcd.com / Title: Ground Effect", ground effect being an F1 aerodynamics term, and the comic went into the Formula 1 Signal at 0.60. With the alt text it scores 0.19. Alt and title are read once each, and once when they say the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -248,21 +248,49 @@ fn chosen(answer: &serde_json::Value, candidates: &[(String, String)]) -> Option
|
||||
candidates.iter().find(|(id, _)| id == c).map(|(id, _)| id.clone())
|
||||
}
|
||||
|
||||
/// Show notes are HTML: Jev is given their words.
|
||||
/// Show notes are HTML: Jev is given their words, and an image's alt and title text, which is all
|
||||
/// the words some posts have. xkcd's is a comic and its hover text; dropping the tag whole sent Jev
|
||||
/// "Ground Effect" alone, an F1 aerodynamics term, and the comic went into Formula 1 (#267).
|
||||
fn text_of(html: &str) -> String {
|
||||
let mut out = String::with_capacity(html.len());
|
||||
let mut in_tag = false;
|
||||
let mut tag: Option<String> = None;
|
||||
for c in html.chars() {
|
||||
match c {
|
||||
'<' => in_tag = true,
|
||||
'>' if in_tag => { in_tag = false; out.push(' '); }
|
||||
_ if !in_tag => out.push(c),
|
||||
_ => {}
|
||||
match (&mut tag, c) {
|
||||
(None, '<') => tag = Some(String::new()),
|
||||
(Some(t), '>') => {
|
||||
let mut said: Vec<&str> = Vec::new();
|
||||
for name in ["alt", "title"] {
|
||||
if let Some(v) = attr(t, name).filter(|v| !v.trim().is_empty() && !said.contains(v)) {
|
||||
said.push(v);
|
||||
}
|
||||
}
|
||||
for v in said {
|
||||
out.push(' ');
|
||||
out.push_str(v);
|
||||
}
|
||||
out.push(' ');
|
||||
tag = None;
|
||||
}
|
||||
(Some(t), c) => t.push(c),
|
||||
(None, c) => out.push(c),
|
||||
}
|
||||
}
|
||||
out.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
/// A quoted attribute's value in a tag's text, `alt="..."` or `alt='...'`.
|
||||
fn attr<'a>(tag: &'a str, name: &str) -> Option<&'a str> {
|
||||
let lower = tag.to_ascii_lowercase();
|
||||
for q in ['"', '\''] {
|
||||
let key = format!(" {name}={q}");
|
||||
if let Some(at) = lower.find(&key) {
|
||||
let rest = &tag[at + key.len()..];
|
||||
return rest.find(q).map(|end| &rest[..end]);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -291,6 +319,14 @@ mod tests {
|
||||
assert_eq!(chosen(&stray, &offered), None, "never one it was not offered");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_images_words_are_read_with_the_text() {
|
||||
let xkcd = r#"<img src="https://imgs.xkcd.com/comics/ground_effect.png" title="Runners wear sneakers." alt="Runners wear sneakers." referrerpolicy="no-referrer">"#;
|
||||
assert_eq!(text_of(xkcd), "Runners wear sneakers.", "alt and title once, when they say the same");
|
||||
assert_eq!(text_of("<p>Qualifying <b>report</b></p><img alt='A grid'>"), "Qualifying report A grid");
|
||||
assert_eq!(text_of(r#"<img src="x.png" alt="">"#), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_batch_is_full_by_its_rows_not_its_items() {
|
||||
let item = |n: usize| Unjudged {
|
||||
|
||||
Reference in New Issue
Block a user