LRC-LIB-KEYWORDS: Photo Supreme's vocabulary files read as keyword lists

Photo Supreme's Formatted Vocabulary File (Data ▸ Export to Formatted
Vocabulary File) turns out to be Lightroom Classic's keyword list
format: plain text with Windows line ends, a top-level category in
square brackets, keywords a tab further in at each level. A test with
a file of that shape pins it down; a real export of 118 keywords read
and wrote back line for line. (Photo Supreme reads Lightroom keyword
files too: Data ▸ Import from Lightroom Keywords file.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-09 16:27:16 -04:00
co-authored by Claude Opus 5.5
parent 89091ada49
commit 1d8d57ce9a
+17
View File
@@ -1196,6 +1196,23 @@ mod tests {
);
}
/// Photo Supreme's Formatted Vocabulary File is the same format: plain text with Windows line
/// ends and no byte-order mark, a top-level category in square brackets (an organizing keyword,
/// left out of export), keywords a tab further in at each level.
#[test]
fn a_photo_supreme_vocabulary_file_reads() {
let file = "[Places]\r\n\tPortugal\r\n\t\tLisbon\r\n\t\t\tAlfama\r\n\t\tPorto\r\n\tSpain\r\n\t\tSeville\r\n";
let read = parse_keyword_list(file).unwrap();
let paths: Vec<&str> = read.iter().map(|(p, _)| p.as_str()).collect();
assert_eq!(
paths,
["Places", "Portugal", "Portugal|Lisbon", "Portugal|Lisbon|Alfama", "Portugal|Porto", "Spain", "Spain|Seville"]
.map(|p| if p == "Places" { p.to_string() } else { format!("Places|{p}") })
);
assert!(!read[0].1.include_on_export, "the category organizes: it isn't exported");
assert!(read[1..].iter().all(|(_, i)| i.include_on_export));
}
/// A list that can't be read says which line and why, and nothing of it is taken.
#[test]
fn a_bad_keyword_list_says_which_line() {