+ use std::collections::HashMap;
+
+ // this implementation merges the header and the sections
+ pub fn parse<'a>(s: &'a str, trtab: &HashMap<&'a str, &'a str>) -> HashMap<&'a str, Vec<&'a str>> {
+ let mut ret = HashMap::new();
+ let mut it = s.lines().peekable();
+
+ // parse header
+ // KEY: VALUE
+ while let Some(x) = it.peek() {
+ if !x.trim().is_empty() {
+ if x.starts_with('%') {
+ break;
+ }
+ if let Some(p) = x.find(": ") {
+ if p != 0 {
+ ret.insert(&x[..p], vec![x[p+2..].trim_start()]);
+ }
+ }
+ // else: regard it as comment
+ }
+ it.next();
+ }
+
+ // parse sections
+ // %KEY\nVALUE...
+ let mut section = None;
+ for i in it {
+ if i.starts_with('%') {
+ if let Some((key, value)) = section.take() {
+ ret.insert(key, value);
+ }
+ section = Some((&i[1..].trim_end(), Vec::new()));
+ } else if let Some((_, value)) = &mut section {
+ value.push(i);
+ } else {
+ unreachable!();
+ }
+ }
+ if let Some((key, value)) = section {
+ ret.insert(key, value);
+ }
+
+ // perform translation substitution, e.g. cleanup keys
+ for (from, to) in trtab {
+ if ret.contains_key(to) {
+ // don't do conflict handling for now
+ continue;
+ }
+ if let Some(x) = ret.remove(from) {
+ // move element
+ ret.insert(to, x);
+ }
+ }
+
+ // cleanup values
+ for val in ret.values_mut() {
+ while val.last().map(|i| i.trim().is_empty()) == Some(true) {
+ val.pop();
+ }
+ val.shrink_to_fit();
+ }
+
+ return ret;
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ #[test]
+ fn ex0() {
+ let mut trtab = HashMap::new();
+ trtab.insert("Kategorie", "Category");
+ trtab.insert("Zutaten", "Ingredients");
+ trtab.insert("Zubereitung", "Making");
+ trtab.insert("Bemerkungen", "Remarks");
+ trtab.insert("Quellen", "Sources");
+
+ let mut res = HashMap::new();
+ res.insert("Name", vec!["Fränkische Kohlsuppe"]);
+ res.insert("Kategorie", vec!["Nullprodukt"]);
+ res.insert("Category", vec!["nully..."]);
+ res.insert("Ingredients", vec!["Salz"]);
+ res.insert("Making", vec!["Mit Wasser abschmecken", " - oder so... "]);
+ res.insert("Remarks", vec!["was soll das?"]);
+ res.insert("Sources", vec!["keine"]);
+
+ assert_eq!(parse(r#"
+ Name: Fränkische Kohlsuppe
+ Kategorie: Nullprodukt
+ Das ist nur ein Kommentar, kein Doppelpunkt...
+ : Das auch: ...
+ Category: nully...
+
+ %Zutaten
+ Salz
+
+ %Zubereitung
+ Mit Wasser abschmecken
+ - oder so...
+
+ %Bemerkungen
+ was soll das?
+
+ %Quellen
+ keine
+
+
+ "#, &trtab), res);
+ }
+ }