+fn column_order(rows: &[RowInput]) -> Vec<String> {
+ column_counts(rows)
+ .into_iter()
+ .map(|(_, col)| col)
+ .collect()
+}
+
+fn render_instance(entry: &Entry) -> HTML {
+ match &entry.instance {
+ None => HTML::from("✓"),
+ Some(instance) => HTML::escape(instance.as_ref()),
+ }
+}
+
+fn render_cell(col: &str, row: &RowInput) -> HTML {
+ let row_label = HTML::escape(row.label.as_ref());
+ let col_label = HTML::escape(col);
+ let entries: Vec<&Entry> = row.entries.iter().filter(|e| e.col == col).collect();
+ let class = HTML::from(if entries.is_empty() { "" } else { "yes" });
+ let all_empty = entries.iter().all(|e| e.instance.is_none());
+ let contents = if entries.is_empty() || (all_empty && entries.len() == 1) {
+ HTML::from("")
+ } else if all_empty {
+ HTML(format!("{}", entries.len()))
+ } else {
+ HTML(
+ entries
+ .iter()
+ .map(|i| render_instance(i))
+ .map(|html| html.0) // Waiting for slice_concat_trait to stabilize
+ .collect::<Vec<_>>()
+ .join(" "),
+ )
+ };
+ HTML(format!("<td class=\"{class}\" onmouseover=\"h2('{row_label}','{col_label}')\" onmouseout=\"ch2('{row_label}','{col_label}')\">{contents}</td>"))
+}
+
+fn render_row(columns: &[String], row: &RowInput) -> HTML {
+ // This is O(n^2) & doesn't need to be
+ let row_label = HTML::escape(row.label.as_ref());
+ HTML(format!(
+ "<tr><th id=\"{row_label}\">{row_label}</th>{}</tr>\n",
+ &columns
+ .iter()
+ .map(|col| render_cell(col, row))
+ .collect::<HTML>()
+ ))
+}
+
+fn render_column_headers(columns: &[String]) -> HTML {
+ HTML(
+ String::from("<tr class=\"key\"><th></th>")
+ + &columns.iter().fold(String::new(), |mut acc, col| {
+ let col_header = HTML::escape(col.as_ref());
+ write!(
+ &mut acc,
+ "<th id=\"{col_header}\"><div><div>{col_header}</div></div></th>"
+ )
+ .unwrap();
+ acc
+ })
+ + "</tr>\n",
+ )
+}