Skip to main content

oxihuman_export/
html_report_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! HTML body measurement report export.
6
7#[derive(Debug, Clone)]
8pub struct HtmlBodyReport {
9    pub title: String,
10    pub measurements: Vec<(String, String, String)>, /* name, value, unit */
11    pub notes: Vec<String>,
12}
13
14pub fn new_html_body_report(title: &str) -> HtmlBodyReport {
15    HtmlBodyReport {
16        title: title.to_string(),
17        measurements: Vec::new(),
18        notes: Vec::new(),
19    }
20}
21
22pub fn add_measurement(report: &mut HtmlBodyReport, name: &str, value: f32, unit: &str) {
23    report
24        .measurements
25        .push((name.to_string(), format!("{:.2}", value), unit.to_string()));
26}
27
28pub fn add_note(report: &mut HtmlBodyReport, note: &str) {
29    report.notes.push(note.to_string());
30}
31
32fn html_escape_str(s: &str) -> String {
33    s.replace('&', "&amp;")
34        .replace('<', "&lt;")
35        .replace('>', "&gt;")
36        .replace('"', "&quot;")
37}
38
39pub fn render_html_report(report: &HtmlBodyReport) -> String {
40    let mut html = format!(
41        "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>{}</title></head><body>\n",
42        html_escape_str(&report.title)
43    );
44    html.push_str(&format!("<h1>{}</h1>\n", html_escape_str(&report.title)));
45    html.push_str("<table border=\"1\"><tr><th>Measurement</th><th>Value</th><th>Unit</th></tr>\n");
46    for (name, value, unit) in &report.measurements {
47        html.push_str(&format!(
48            "<tr><td>{}</td><td>{}</td><td>{}</td></tr>\n",
49            html_escape_str(name),
50            html_escape_str(value),
51            html_escape_str(unit)
52        ));
53    }
54    html.push_str("</table>\n");
55    if !report.notes.is_empty() {
56        html.push_str("<ul>\n");
57        for note in &report.notes {
58            html.push_str(&format!("<li>{}</li>\n", html_escape_str(note)));
59        }
60        html.push_str("</ul>\n");
61    }
62    html.push_str("</body></html>\n");
63    html
64}
65
66pub fn export_html_body_report(report: &HtmlBodyReport) -> Vec<u8> {
67    render_html_report(report).into_bytes()
68}
69
70pub fn measurement_count(report: &HtmlBodyReport) -> usize {
71    report.measurements.len()
72}
73pub fn note_count(report: &HtmlBodyReport) -> usize {
74    report.notes.len()
75}
76pub fn validate_html_report(report: &HtmlBodyReport) -> bool {
77    !report.title.is_empty()
78}
79pub fn html_report_size_bytes(report: &HtmlBodyReport) -> usize {
80    render_html_report(report).len()
81}
82
83pub fn default_html_body_report(height_cm: f32, weight_kg: f32) -> HtmlBodyReport {
84    let mut r = new_html_body_report("Body Report");
85    add_measurement(&mut r, "Height", height_cm, "cm");
86    add_measurement(&mut r, "Weight", weight_kg, "kg");
87    r
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_new_html_body_report() {
96        let r = new_html_body_report("Test");
97        assert_eq!(r.title, "Test");
98    }
99
100    #[test]
101    fn test_add_measurement() {
102        let mut r = new_html_body_report("T");
103        add_measurement(&mut r, "Height", 170.0, "cm");
104        assert_eq!(measurement_count(&r), 1);
105    }
106
107    #[test]
108    fn test_render_contains_doctype() {
109        let r = new_html_body_report("X");
110        let html = render_html_report(&r);
111        assert!(html.contains("<!DOCTYPE html>"));
112    }
113
114    #[test]
115    fn test_render_contains_title() {
116        let r = new_html_body_report("MyTitle");
117        let html = render_html_report(&r);
118        assert!(html.contains("MyTitle"));
119    }
120
121    #[test]
122    fn test_export_bytes_nonempty() {
123        let r = new_html_body_report("T");
124        assert!(!export_html_body_report(&r).is_empty());
125    }
126
127    #[test]
128    fn test_validate_html_report() {
129        let r = new_html_body_report("V");
130        assert!(validate_html_report(&r));
131    }
132
133    #[test]
134    fn test_note_count() {
135        let mut r = new_html_body_report("T");
136        add_note(&mut r, "Note 1");
137        assert_eq!(note_count(&r), 1);
138    }
139
140    #[test]
141    fn test_default_html_body_report() {
142        let r = default_html_body_report(165.0, 55.0);
143        assert_eq!(measurement_count(&r), 2);
144    }
145}