stanhope/
write_html.rs

1//! *Write a single HTML file from a single EBML file input*
2//! 
3//! Some assumptions are made:
4//! * Common HTML assets (CSS, template images) are in a folder ../assets/
5//! * Cross-crate knowledge of the Process struct is required
6//! 
7//! A single public function performs this HTML writing task, which calls private functions to write their portions of the HTML file.
8use crate::read_ebml::Process;
9use crate::read_ebml::Section;
10use crate::read_ebml::SubStep;
11use crate::read_ebml::Action;
12use crate::read_ebml::Requirement;
13use crate::read_ebml::Table;
14
15use std::fs::File;
16use std::fs::OpenOptions;
17use std::io::Write;
18
19/// Single public function that writes out a specially-formatted HTML file "filename" according to the data stored in the Process struct "p"
20/// 
21/// The HTML heavily depends on stanhope.css in the ../assets/ folder to look nice on screen and in print.
22/// Other template CSS files also go in ../assets/
23pub fn generate_complete_html(filename:&String, p:&Process) {
24
25    // Open the filename input to write HTML out to that file
26    let mut html_file = OpenOptions::new()
27        .read(true)
28        .write(true)
29        .create(true)
30        .open(filename)
31        .expect("cannot open file");
32
33    // Compose the first line of HTML file through just before the <head> element
34    compose_top_of_html_file(&mut html_file);
35
36    // Compose the <head> portion of the HTML
37    compose_html_head(&mut html_file, &p);
38
39    // Compose the <bod> portion of the HTML, which is the majority of the page
40    compose_html_body(&mut html_file,&p,p.get_all_sections());
41
42    // compose everything after the close of </body> (which is not much)
43    compose_bottom_of_html_file(&mut html_file);
44}
45
46/// Basic doctype declaration and *html* opening
47fn compose_top_of_html_file(f:&mut File) {
48    f.write(b"<!DOCTYPE html>\n").expect("write failed");
49    f.write(b"<html lang=\"en-US\">\n\n").expect("write failed");
50}
51
52/// Close *html*
53fn compose_bottom_of_html_file(f:&mut File) {
54    f.write(b"</html>").expect("write failed");
55}
56
57/// Head portion, which references external style files, stored in the Process struct at "Templates"
58fn compose_html_head(f:&mut File,p:&Process) {
59    f.write(b"\t<head>\n").expect("write failed");
60
61    let temp_str = String::from("\t\t<title>") + p.get_number() + " \"" + p.get_title() + "\"</title>\n";
62    f.write(temp_str.as_bytes()).expect("write failed");
63    f.write(b"\t\t<meta charset=\"utf-8\" />\n").expect("write failed");
64    let temp_str = String::from("\t\t<meta name = \"keywords\" content = \"") + p.get_subject() + ", " + p.get_product() + "\" />\n";
65    f.write(temp_str.as_bytes()).expect("write failed");
66    let temp_str = String::from("\t\t<meta name = \"description\" content = \"") + p.get_number() + " " + p.get_title() + "\" />\n";
67    f.write(temp_str.as_bytes()).expect("write failed");
68    let temp_str = String::from("\t\t<meta name = \"author\" content = \"") + p.get_author() + "\" />\n";
69    f.write(temp_str.as_bytes()).expect("write failed");
70    f.write(b"\t\t<meta http-equiv = \"Content-Type\" content = \"text/html; charset = UTF-8\" />\n").expect("write failed");
71    f.write(b"\t\t<link rel=\"shortcut icon\" sizes=\"16x16 32x32 48x48\" type=\"image/png\" href=\"../assets/favicon.png\" />\n").expect("write failed");
72    f.write(b"\t\t<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"../assets/favicon.png\" />\n").expect("write failed");
73    f.write(b"\t\t<link rel=\"mask-icon\" href=\"../assets/favicon.svg\" color=\"#000000\" />\n\n").expect("write failed");
74    
75    f.write(b"\t\t<!-- The stanhope.css applies to every process, regardless of program -->\n").expect("write failed");
76    f.write(b"\t\t<link rel=\"stylesheet\" href=\"../assets/stanhope.css\"/>\n").expect("write failed");
77    // Place to add reference to user-defined "Template" CSS files
78    // loop over vector
79    for template in p.get_all_templates() {
80        let temp_str = String::from("\t\t<link rel=\"stylesheet\" href=\"../assets/") + &template + "\"/>\n";
81        f.write(temp_str.as_bytes()).expect("write failed");
82    }
83    f.write(b"\t</head>\n\n").expect("write failed");
84}
85
86/// Body portion, which is the bulk of the HTML file. The composition is broken up into other sequential functions:
87/// * Title page (styled with pagebreak in stanhope.css)
88/// * Revision and Requirements page (also has pagebreak)
89/// * Table of Contents, which also includes Resources (also has pagebreak)
90/// * TODO: add placeholder sections for other user-defined templates, like boilerplate warnings for ESD if ESD is checked
91/// * All Process Sections
92/// * * For each sequential Section, write out each sequential Step
93/// * * For each sequential Step, write out each sequential SubStep
94/// * * Include special cases for Resource, etc.
95/// * Close out the sections and insert the banners at the top and bottom of the page
96fn compose_html_body(f:&mut File,p:&Process,s:&Vec<Section>) {
97    f.write(b"\t<body>\n\n").expect("write failed");
98    f.write(b"\t\t<div id=\"classification-header\" class=\"classification\"><p></p></div>\n").expect("write failed");
99
100    // I CAN'T KNOW HOW TO HEAR ANY MORE ABOUT TABLES
101    compose_html_body_title_page(f,p);
102    compose_html_body_rev_req_page(f,p);
103    compose_html_body_toc(f,p,s);
104
105    // Where most of the action is
106    compose_html_body_section(f,p,s);
107    compose_html_body_end_of_sections_and_banners(f,p);
108
109    f.write(b"\t\t<div id=\"classification-footer\" class=\"classification\"><p></p></div>\n\n").expect("write failed");
110    f.write(b"\t</body>\n\n").expect("write failed");
111}
112
113/// The Title Page
114/// 
115/// Assumption: the *section* element is styled in stanhope.css to include a pagebreak-before (we'll use *section* for Sections, too)
116fn compose_html_body_title_page(f:&mut File, p:&Process) {
117    // Crappy old-school HTML tables are still alive
118    f.write(b"\t\t<table>\n\n").expect("write failed");
119    f.write(b"\t\t\t<thead><tr><td>\n").expect("write failed");
120    f.write(b"\t\t\t\t<div class=\"header-space\">&nbsp;</div>\n").expect("write failed");
121    f.write(b"\t\t\t</td></tr></thead>\n\n").expect("write failed");
122    f.write(b"\t\t<tbody>\n\n").expect("write failed");
123    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
124    f.write(b"\t\t\t\t<section class=\"hide-on-screen\">\n").expect("write failed");
125    f.write(b"\t\t\t\t\t<div id=\"title-page\">\n").expect("write failed");
126
127    // Document Title in big text
128    let temp_str = String::from("\t\t\t\t\t\t<div id=\"title-page-title\">") + p.get_title() + "</div>\n";
129    f.write(temp_str.as_bytes()).expect("write failed");
130    
131    // Document Number in big text
132    let proxi = Process::new();
133    let mut process_type = "".to_string();
134    if !(p.get_process_type() == proxi.get_process_type()) { process_type = "<br/>".to_owned() + p.get_process_type(); }
135    let temp_str = String::from("\t\t\t\t\t\t<div id=\"title-page-docno\">") + p.get_number() + &process_type + "<br/>Revision " + p.get_revision() + "</div>\n";
136    f.write(temp_str.as_bytes()).expect("write failed");
137
138    // Author / Reviewer signature block
139    f.write(b"\t\t\t\t\t\t<div id=\"title-page-authors\">\n").expect("write failed");
140    f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
141    f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
142    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
143    let temp_str = String::from("\t\t\t\t\t\t\t\t\t\t<td>Written by:<br/>") + p.get_author() + "</td>\n";
144    f.write(temp_str.as_bytes()).expect("write failed");    
145    let temp_str = String::from("\t\t\t\t\t\t\t\t\t\t<td>Reviewed by:<br/>") + p.get_reviewer() + "</td>\n";
146    f.write(temp_str.as_bytes()).expect("write failed");
147    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
148    f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
149    f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
150    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
151    f.write(b"\t\t\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
152    f.write(b"\t\t\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
153    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
154    f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
155    f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
156    f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
157
158    // The content of the "fine print" is defined by the TEMPLATE (it's "content" in the CSS)
159    f.write(b"\t\t\t\t\t\t<div id=\"title-page-fine-print\"></div>\n").expect("write failed");
160
161    // The content of the "No AI" fine print is defined by the TEMPLATE (it's "content" in the CSS)
162    f.write(b"\t\t\t\t\t\t<div id=\"title-page-no-ai\"></div>\n").expect("write failed");
163    
164    // Close out
165    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
166    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
167    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
168}
169
170/// Revisions and Requirements pages
171fn compose_html_body_rev_req_page(f:&mut File, p:&Process) {
172    // The document is one giant table, with <section> as proxy for pagebreak
173    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
174    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
175    f.write(b"\t\t\t\t\t<div id=\"revision-page\">\n").expect("write failed");
176    let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">Revision Table for ").to_string() + p.get_number() + " \"" + p.get_title() + "\"</span>\n";
177    f.write(temp_str.as_bytes()).expect("write failed");
178
179    // Start of the Revision table, which has two columns "Rev" and "Description of Change"
180    f.write(b"\t\t\t\t\t\t<div class=\"revision-table-container\">\n").expect("write failed");
181    f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
182    f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
183    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
184    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"revision-table-rev\">Rev</td>\n").expect("write failed");
185    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"revision-table-change\">Description of Change</td>\n").expect("write failed");
186    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
187    f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
188    f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
189
190    // Write out each line from data
191    // We'll compare a counter to the total number of Revisions we have in the Vector
192    let mut rev_counter = 0;
193    let len_revs = p.get_all_revisions().len();
194    for rev in p.get_all_revisions().iter().rev() {
195        rev_counter += 1;
196        // The *last* row we write is the current revision, so we wrap the cell contents in another span for stanhope.css styling
197        if rev_counter==len_revs {
198            f.write(b"\t\t\t\t\t\t\t\t\t<tr class=\"current-rev\">\n").expect("write failed"); 
199            let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"revision-table-rev\"><span class=\"current-rev\">".to_string() + &rev.0 + "</span></td>\n";
200            f.write(temp_str.as_bytes()).expect("write failed");
201        } else {
202            f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
203            let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"revision-table-rev\">".to_string() + &rev.0 + "</td>\n";
204            f.write(temp_str.as_bytes()).expect("write failed");
205        }
206        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"revision-table-change\">".to_string() + &rev.1 + "</td>\n";
207        f.write(temp_str.as_bytes()).expect("write failed");
208        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
209    }
210    // Close out the revision table
211    f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
212    f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
213    f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
214    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
215    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
216    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
217
218    // If there are no Verifications in this Process, then this page is done
219    if p.get_all_verifications().len()==0 { return };
220
221    // If there are any Verifications, then we create a new page (<section> has pagebreak)
222    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
223    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
224    f.write(b"\t\t\t\t\t<div id=\"requirements-page\">\n").expect("write failed");
225    let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">Requirements Table for ").to_string() + p.get_number() + " \"" + p.get_title() + "\"</span>\n";
226    f.write(temp_str.as_bytes()).expect("write failed");
227    // Special blurb that mimics an in-process Verification in a Step
228    let temp_str = "\t\t\t\t\t\t<div class=\"requirement\">Successful execution of this procedure produces evidence of verification for the requirements listed in the table below. Therefore, this procedure's execution is particularly important for us to achieve our goal. When you see procedure call-outs in this style of box, that step is what produces this critical evidence. Take the time to understand how these requirements will be verified prior to starting this procedure, and consult this procedure's author (".to_string() + p.get_author() + ") if necessary.</div>\n";
229    f.write(temp_str.as_bytes()).expect("write failed");
230    // Now just generate a table of all requirement verifications in the process document
231    f.write(b"\t\t\t\t\t\t<div class=\"requirements-table-container\">\n").expect("write failed");
232    f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
233    f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
234    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
235    // Four columns: Requirement ID (RID), Text of the Requirement itself, Method of Verification, and the Step number in this process that identifies the Verification step
236    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-rid\">RID</td>\n").expect("write failed");
237    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-text\">Text of Requirement</td>\n").expect("write failed");
238    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-method\">Verif. Method</td>\n").expect("write failed");
239    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-step\">Step</td>\n").expect("write failed");
240    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
241    f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
242    f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
243    // Simply pull the data out and fill the table
244    for req in p.get_all_verifications().iter() {
245        f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
246        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-rid\">".to_string() + &req.0.get_id() + "</td>\n";
247        f.write(temp_str.as_bytes()).expect("write failed");
248        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-text\">".to_string() + &req.0.get_text() + "</td>\n";
249        f.write(temp_str.as_bytes()).expect("write failed");
250        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-method\">".to_string() + &req.0.get_method() + "</td>\n";
251        f.write(temp_str.as_bytes()).expect("write failed");
252        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"requirements-table-step\">".to_string() + &req.1 + "</td>\n";
253        f.write(temp_str.as_bytes()).expect("write failed");
254        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
255    }
256    // Close out the table and the section/page
257    f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
258    f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
259    f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
260    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
261    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
262    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
263}
264
265/// Table of Contents page, and the Subject/Product Resources page...
266/// 
267/// Currently, if there are any verifications in the document at all, they are gathered up to the TOC page as well.
268/// 
269/// We'll create some special elements that look somewhat like those that appear in-process (roundrect) but alternate colors
270fn compose_html_body_toc(f:&mut File,p:&Process,s:&Vec<Section>) {
271
272    /// Round robin color picker for alternating steps in the Resource blob
273    fn pick_color(index:usize) -> String {
274        // If we want three colors, etc.
275        match index % 3 {
276            2 => String::from("border: 0; background-color: #FFFFFF; color: #5566AA;"),
277            1 => String::from("border: 0; background-color: #FFFFFF; color: #5566AA;"),
278            0 => String::from("border: 0; background-color: #FFFFFF; color: #5566AA;"),
279            _ => String::from("border: 0; background-color: #AA99DD; color: #5566AA;"),
280        }
281        /*
282        match index % 3 {
283            2 => String::from("border: 2px solid #5566AA; background-color: #CCDDFF; color: #5566AA;"),
284            1 => String::from("border: 2px solid #5566AA; background-color: #CCBBFF; color: #5566AA;"),
285            0 => String::from("border: 2px solid #5566AA; background-color: #AABBEE; color: #5566AA;"),
286            _ => String::from("border: 2px solid #5566AA; background-color: #AA99DD; color: #5566AA;"),
287        }
288        */
289    }
290
291    // Table of Contents
292    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
293    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
294    f.write(b"\t\t\t\t\t<div id=\"toc\">\n").expect("write failed");
295    let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">").to_string() + p.get_number() + ", Rev " + p.get_revision() + " \"" + p.get_title() + "\"</span>\n";
296    f.write(temp_str.as_bytes()).expect("write failed");
297    // This TOC is a bulleted list with links to document anchors
298    f.write(b"\t\t\t\t\t\t<ul style=\"toc-section\">\n").expect("write failed");
299    for (ii,sec) in s.iter().enumerate() { 
300        let temp_str = String::from("\t\t\t\t\t\t\t<li><a class=\"pageref\" href=\"#Section") + &(ii+1).to_string() + "\">Section " + &(ii+1).to_string() + "</a>: " + sec.get_title() + "</a></li>\n";
301        f.write(temp_str.as_bytes()).expect("write failed");
302        f.write(b"\t\t\t\t\t\t\t\t<ul style=\"toc-step\">\n").expect("write failed");
303        for (jj,stp) in sec.get_all_steps().iter().enumerate() {
304            let temp_str = String::from("\t\t\t\t\t\t\t\t\t<li><a class=\"pageref\" href=\"#Step") + &(ii+1).to_string() + "." + &(jj+1).to_string() + "\">Step " + &(ii+1).to_string() + "." + &(jj+1).to_string() + "</a>: " + stp.get_text() + "</a></li>\n";
305            f.write(temp_str.as_bytes()).expect("write failed");
306        }
307        f.write(b"\t\t\t\t\t\t\t\t</ul>\n").expect("write failed");
308    }
309    f.write(b"\t\t\t\t\t\t</ul>\n\n").expect("write failed");
310    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
311
312    // Roles - see Roles.css
313    f.write(b"\t\t\t\t\t<div id=\"roles\">\n").expect("write failed");
314    f.write(b"\t\t\t\t\t\t<span class=\"toc-title\">Personnel Performing this Process, by Named Role:</span>\n").expect("write failed");
315    f.write(b"\t\t\t\t\t\t<table id=\"roles-table\">\n").expect("write failed");
316    f.write(b"\t\t\t\t\t\t\t<thead id=\"roles-header\">\n").expect("write failed");
317    f.write(b"\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
318    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-title\">Role in this Process</td>\n").expect("write failed");
319    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-name\">Name of Person</td>\n").expect("write failed");
320    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-initials\">Initials</td>\n").expect("write failed");
321    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-contact\">Contact Information</td>\n").expect("write failed");
322    f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
323    f.write(b"\t\t\t\t\t\t\t</thead>\n").expect("write failed");
324    f.write(b"\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
325    let num_roles:usize = 10;
326    for ii in 1..(num_roles+1) {
327        let temp_str = String::from("\t\t\t\t\t\t\t\t<tr id=\"role-".to_string() + &format!("{:0>2}",ii) + "\">\n");
328        f.write(temp_str.as_bytes()).expect("write failed");
329        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-title\"></td>\n").expect("write failed");
330        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-name\"></td>\n").expect("write failed");
331        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-initials\"></td>\n").expect("write failed");
332        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-contact\"></td>\n").expect("write failed");
333        f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
334    }
335    f.write(b"\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
336    f.write(b"\t\t\t\t\t\t</table>\n").expect("write failed");
337    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
338
339    // Various warnings
340    // TODO: robustify this by generalizing templates.
341    f.write(b"\t\t\t\t\t<div class=\"power-on\"><img/><span class=\"power-on-text\"></span></div>\n").expect("write failed");
342    f.write(b"\t\t\t\t\t<div class=\"rf-emission\"><img/><span class=\"rf-emission-text\"></span></div>\n").expect("write failed");
343    f.write(b"\t\t\t\t\t<div class=\"hazardous-ops\"><img/><span class=\"hazardous-ops-text\"></span></div>\n").expect("write failed");
344    f.write(b"\t\t\t\t\t<div class=\"esd\"><img/><span class=\"esd-text\"></span></div>\n").expect("write failed");
345    f.write(b"\t\t\t\t\t<div class=\"fod\"><img/><span class=\"fod-text\"></span></div>\n").expect("write failed");
346    f.write(b"\t\t\t\t\t<div class=\"welding\"><img/><span class=\"welding-text\"></span></div>\n").expect("write failed");
347    f.write(b"\t\t\t\t\t<div class=\"comsec\"><img/><span class=\"comsec-text\"></span></div>\n").expect("write failed");
348    f.write(b"\t\t\t\t\t<div class=\"cleanroom\"><img/><span class=\"cleanroom-text\"></span></div>\n").expect("write failed");
349    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
350    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
351
352    // New page for Subject/Product and Resources
353    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
354    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
355    
356    let all_objectives = p.get_all_objectives();
357    if all_objectives.len() > 0 {
358        // List of Objectives
359        f.write(b"\t\t\t\t\t<div id=\"all-objectives\">\n").expect("write failed");
360        let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">This process has ") + &all_objectives.len().to_string() + " stated objective" + match all_objectives.len() {1=>"",_=>"s"} + ":</span>\n";
361        f.write(temp_str.as_bytes()).expect("write failed");
362        f.write(b"\t\t\t\t\t\t<ol class=\"objective-list\">\n").expect("write failed");
363        for (_jj,(obj,snum)) in all_objectives.iter().enumerate() {
364            let temp_str = String::from("\t\t\t\t\t\t\t<li>") + &obj + " (" + &snum + ")</li>\n";
365            f.write(temp_str.as_bytes()).expect("write failed");
366        }
367        f.write(b"\t\t\t\t\t\t</ol>\n").expect("write failed");
368        f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
369    };
370
371    let all_out_of_scopes = p.get_all_out_of_scopes();
372    if all_out_of_scopes.len() > 0 {
373        // List of Objectives
374        f.write(b"\t\t\t\t\t<div id=\"all-out-of-scopes\">\n").expect("write failed");
375        let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">This process has ") + &all_out_of_scopes.len().to_string() + " <em>Out Of Scope</em> declaration" + match all_out_of_scopes.len() {1=>"",_=>"s"} + ":</span>\n";
376        f.write(temp_str.as_bytes()).expect("write failed");
377        f.write(b"\t\t\t\t\t\t<ol class=\"out-of-scope-list\">\n").expect("write failed");
378        for (_jj,(oos,snum)) in all_out_of_scopes.iter().enumerate() {
379            let temp_str = String::from("\t\t\t\t\t\t\t<li>") + &oos + " (" + &snum + ")</li>\n";
380            f.write(temp_str.as_bytes()).expect("write failed");
381        }
382        f.write(b"\t\t\t\t\t\t</ol>\n").expect("write failed");
383        f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
384    };
385
386    // Subject/Product logic and writing
387    f.write(b"\t\t\t\t\t<div id=\"subject-product\">\n").expect("write failed");
388    
389    match p.get_product().as_str() {
390        ""|"N/A" => {
391            let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">\"") + p.get_title() + "\" applies to <em>" + p.get_subject() + "</em></span><br/>\n";
392            f.write(temp_str.as_bytes()).expect("write failed");
393            let temp_str = String::from("\t\t\t\t\t\t<div class=\"subject-product-image-container\"><img src=\"") + p.get_subject_image() + "\"/></div>\n";
394            f.write(temp_str.as_bytes()).expect("write failed");
395        },
396        _ => {
397            let temp_str = String::from("\t\t\t\t\t\t<span class=\"toc-title\">\"") + p.get_title() + "\" applies to <em>" + p.get_subject() + "</em> to produce <em>" + p.get_product() +"</em></span><br/>\n";
398            f.write(temp_str.as_bytes()).expect("write failed");
399            let temp_str = String::from("\t\t\t\t\t\t<div class=\"subject-product-image-container\"><img src=\"") + p.get_subject_image() + "\"/><span class=\"arrow\">&rarr;</span><img src=\"" + p.get_product_image() + "\"/></div>\n";
400            f.write(temp_str.as_bytes()).expect("write failed");
401        },
402    };
403    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
404    
405    // Resource blob
406    if p.get_all_resources().len() > 0 {
407        f.write(b"\t\t\t\t\t\t<div class=\"resources-container\">\n").expect("write failed");
408        let temp_str = "\t\t\t\t\t\t<span class=\"resources-text\">".to_string() + &p.get_all_resources().len().to_string() + " named resources are identified to perform this process:</span><br/>\n";
409        f.write(temp_str.as_bytes()).expect("write failed");
410
411        // How we're using the color picker: increment the "index" and let the function apply the remainder function
412        let mut step_num = "";
413        let mut index:usize = 0;
414        for (resource,resource_step) in p.get_all_resources() {
415            if !(step_num==resource_step) { index += 1; };
416            step_num = resource_step;
417            let temp_str = "\t\t\t\t\t\t\t<div class=\"resource-bubble\" style=\"".to_string() + &pick_color(index) + ";\"><span class=\"resource-bubble-step\">" + &resource_step + "</span><br/>" + &resource.get_name() + "</div>\n";
418            f.write(temp_str.as_bytes()).expect("write failed");
419        }
420        f.write(b"\t\t\t\t\t\t</div><div style=\"clear:both;\"></div>\n").expect("write failed");
421
422        // Calibration table
423        if p.get_all_calibrated_resources().len() > 0 {
424            f.write(b"\t\t\t\t\t<div id=\"calibration\">\n").expect("write failed");
425            f.write(b"\t\t\t\t\t\t<span class=\"toc-title\">Equipment Calibration Status</span>\n").expect("write failed");
426            f.write(b"\t\t\t\t\t\t<table id=\"calibration-table\">\n").expect("write failed");
427            f.write(b"\t\t\t\t\t\t\t<thead id=\"calibration-header\">\n").expect("write failed");
428            //f.write(b"\t\t\t\t\t\t\t\t<tr><td class=\".nocell\"></td><td colspan=\"2\" class=\".whitecell\">Today's Date: </td></tr>\n").expect("write failed");
429            f.write(b"\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
430            f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-resource\">Equipment</td>\n").expect("write failed");
431            f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-num\">Model Number</td>\n").expect("write failed");
432            f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-serial\">Serial</td>\n").expect("write failed");
433            f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-date\">Last Cal Date</td>\n").expect("write failed");
434            f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-due\">Valid Until</td>\n").expect("write failed");
435            f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
436            f.write(b"\t\t\t\t\t\t\t</thead>\n").expect("write failed");
437            f.write(b"\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
438            for resource in p.get_all_calibrated_resources() {
439                let temp_str = String::from("\t\t\t\t\t\t\t\t<tr>\n");
440                f.write(temp_str.as_bytes()).expect("write failed");
441                //f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-resource\"></td>\n").expect("write failed");
442                let temp_str = "\t\t\t\t\t\t\t\t\t<td class=\"calibration-resource\">".to_string() + &resource.get_name() + "</td>\n";
443                f.write(temp_str.as_bytes()).expect("write failed");
444                f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-num\"></td>\n").expect("write failed");
445                f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-serial\"></td>\n").expect("write failed");
446                f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-date\"></td>\n").expect("write failed");
447                f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"calibration-due\"></td>\n").expect("write failed");
448                f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
449            }
450            f.write(b"\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
451            f.write(b"\t\t\t\t\t\t\t<tfoot>\n").expect("write failed");
452            f.write(b"\t\t\t\t\t\t\t\t<tr><td colspan=\"3\">If any equipment is within 30 days of needing recalibration, notify your QA rep. <strong>Today's Date:</strong></td></tr>\n").expect("write failed");
453            f.write(b"\t\t\t\t\t\t\t</tfoot>\n").expect("write failed");
454            f.write(b"\t\t\t\t\t\t</table>\n").expect("write failed");
455            f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
456        }
457
458
459    }
460    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
461    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
462    // end resource blob
463
464    // NCRs - Non-Conformances
465    f.write(b"\t\t\t<tr id=\"non-conformances\"><td>\n\n").expect("write failed");
466    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
467    f.write(b"\t\t\t\t\t<span class=\"toc-title\">Log of Non-Conformances during this execution:</span>\n").expect("write failed");
468    f.write(b"\t\t\t\t\t<table>\n").expect("write failed");
469    f.write(b"\t\t\t\t\t\t<thead>\n").expect("write failed");
470    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
471    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Time</td>\n").expect("write failed");
472    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Step</td>\n").expect("write failed");
473    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Record</td>\n").expect("write failed");
474    f.write(b"\t\t\t\t\t\t\t\t<td>Brief Summary of Observation(s) that Deviated from Documented Expectations</td>\n").expect("write failed");
475    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
476    f.write(b"\t\t\t\t\t\t</thead>\n").expect("write failed");
477    f.write(b"\t\t\t\t\t\t<tbody>\n").expect("write failed");
478    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
479    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
480    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
481    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
482    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
483    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
484    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
485    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
486    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
487    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
488    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
489    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
490    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
491    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
492    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
493    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
494    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
495    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
496    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
497    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
498    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
499    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
500    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
501    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
502    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
503    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
504    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
505    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
506    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
507    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
508    f.write(b"\t\t\t\t\t\t</tbody>\n").expect("write failed");
509    f.write(b"\t\t\t\t\t</table>\n").expect("write failed");
510    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
511    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
512
513    
514}
515
516/// Write every Section in the Process, sequentially. Include Steps within Sections and SubSteps within Steps.
517fn compose_html_body_section(f:&mut File, p:&Process, all_sections:&Vec<Section>) {
518    
519    // Count the Sections as we go, beceuse sequential processes deserve sequential indenting and numbering
520    let mut counter = 0;
521    for section in all_sections {
522        // Each Section is cleverly its own *section* element... brilliant!
523        f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
524        f.write(b"\t\t\t\t<section>\n").expect("write failed");
525        
526        // Count the sections as we go
527        // TODO: modernize this by enumerating an iterator for all_sections, if Rust lets us use iterators for our own Vec<Section>
528        counter += 1;
529
530        // Print out the title of the section
531        let temp_str = "\t\t\t\t\t<div class=\"section-title\"><a name=\"Section".to_string() + &counter.to_string() + "\"></a>Section " + &counter.to_string() + ": " + &section.get_title() + "</div>\n";
532        f.write(temp_str.as_bytes()).expect("write failed");
533
534        // Iterate over every Step in the Section
535        for (ii,step) in section.get_all_steps().iter().enumerate() {
536
537            // Use the text stored at the Step level at the top of the Step
538            let temp_str = "\t\t\t\t\t<div class=\"step\"><span class=\"step-text\"><a name=\"Step".to_string() + &counter.to_string() + "." + &{ii+1}.to_string() + "\"></a>Step " + &counter.to_string() + "." + &{ii+1}.to_string() + ": " + &step.get_text() + "</span><br/>\n";
539            f.write(temp_str.as_bytes()).expect("write failed");
540
541            // Resource blob goes at the top of each Step, so that the operator can be full-kit before starting the Step
542            if step.get_resources().len() > 0 {
543                f.write(b"\t\t\t\t\t\t\t<div class=\"resources-container\">\n").expect("write failed");
544                let temp_str = "\t\t\t\t\t\t\t\t<span class=\"resources-text\">".to_string() + &step.get_resources().len().to_string() + " named resources are identified to perform this step:</span><br/>\n";
545                f.write(temp_str.as_bytes()).expect("write failed");
546                for resource in step.get_resources() {
547                    let temp_str = "\t\t\t\t\t\t\t\t<div class=\"resource-bubble\">".to_string() + &resource.get_name() + "</div>\n";
548                    f.write(temp_str.as_bytes()).expect("write failed");
549                }
550                f.write(b"\t\t\t\t\t\t\t</div><div style=\"clear:both;\"></div>\n").expect("write failed");
551            }
552            // end resource blob
553
554            // Iterate over all of the SubSteps in the Step, and use other functions in this crate to write out accordingly
555            for (_jj,sub_step) in step.get_all_sub_steps().iter().enumerate() {
556                match sub_step {
557                    SubStep::ActionSequence(action_sequence) => compose_html_body_section_step_action_sequence(f,&action_sequence),
558                    SubStep::Command(command_text) => compose_html_body_section_step_command(f,&command_text),
559                    SubStep::Image(image_file,image_text) => compose_html_body_section_step_image(f,&image_file,&image_text),
560                    SubStep::Warning(warning_text) => compose_html_body_section_step_warning(f,&warning_text),
561                    SubStep::Verification(requirement) => compose_html_body_section_step_verification(f,&p.get_author(),&requirement),
562                    // Do nothing for Resource type SubSteps, because we've printed them at the top of the Step
563                    // TODO: add a feature later to also do something in-place where the Resource was identified within the Step...
564                    SubStep::Resource(_resource) => (),
565                    SubStep::Context(text) => compose_html_body_section_step_context(f,&text),
566                    SubStep::Objective(objective_text) => compose_html_body_section_step_objective(f,&objective_text),
567                    SubStep::OutOfScope(out_of_scope_text) => compose_html_body_section_step_out_of_scope(f,&out_of_scope_text),
568                    SubStep::Table(table) => compose_html_body_section_step_table(f,&table),
569                }
570            }
571            // close the Step div
572            f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
573        }
574        // close the section
575        f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
576        f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
577    }
578}
579
580/// Print ActionSequence type SubStep
581fn compose_html_body_section_step_action_sequence(f:&mut File, act_seq:&Vec<Action>) {
582    // ActionSequence prints as a table (tables!)
583    f.write(b"\t\t\t\t\t\t<div class=\"action-sequence\">\n").expect("write failed");
584    f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
585    f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
586    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
587    // Three columns: Action to perform, Expected result, and Two-Party Verification
588    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-action\">Action to perform</td>\n").expect("write failed");
589    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-expected\">Expected</td>\n").expect("write failed");
590    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-result\">Result</td>\n").expect("write failed");
591    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">Done</td>\n").expect("write failed");
592    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">TPV</td>\n").expect("write failed");
593    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
594    f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
595    f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
596
597    // one of these for each action
598    for act in act_seq {
599        f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
600        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-action\">".to_string() + act.get_perform() + "</td>\n";
601        f.write(temp_str.as_bytes()).expect("write failed");
602        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-expected\">".to_string() + act.get_expect() + "</td>\n";
603        f.write(temp_str.as_bytes()).expect("write failed");
604        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-result\">".to_string() + "</td>\n";
605        f.write(temp_str.as_bytes()).expect("write failed");
606        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">".to_string() + "<span class=\"action-checkbox\">&#x25A2;</span>" + "</td>\n";
607        f.write(temp_str.as_bytes()).expect("write failed");
608        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">".to_string() + { if !act.get_tpv() { "N/A" } else { "<span class=\"action-checkbox\">&#x25A2;</span>" }} + "</td>\n";
609        f.write(temp_str.as_bytes()).expect("write failed");
610        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
611    }
612
613    // close the table
614    f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
615    f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
616    f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
617}
618
619/// Print Command type SubStep
620fn compose_html_body_section_step_command(f:&mut File, c:&String) {
621    // Simple div and span combination for formatting in stanhope.css
622    let temp_str = "\t\t\t\t\t\t<div class=\"command\"><span class=\"command-text\">".to_string() + &c.to_string() + "</span></div>\n";
623    f.write(temp_str.as_bytes()).expect("write failed");
624}
625
626/// Print Image type SubStep
627fn compose_html_body_section_step_image(f:&mut File, img:&String, txt:&String) {
628    // There's a container Div, and inside that there's the image, and below the image there's the caption
629    // The container Div can be stylized in stanhope.css to be centered (for instance), with a border, etc.
630    // The container can also be styled to have maximum width or height, etc.
631    let temp_str = "\t\t\t\t\t\t<div class=\"image\"><div class=\"image-container\"><img src=\"".to_string() + &img.to_string() + "\" onerror=\"this.onerror=null; this.src='../assets/placeholderImage-small.png'\"/><br/><span class=\"image-text\">" + &txt.to_string() + "</span></div></div>\n";
632    f.write(temp_str.as_bytes()).expect("write failed");
633}
634
635/// Print Warning type SubStep
636fn compose_html_body_section_step_warning(f:&mut File, w:&String) {
637    // Simple div and span combination for formatting in stanhope.css
638    let temp_str = "\t\t\t\t\t\t<div class=\"warning\"><span class=\"warning-text\">".to_string() + &w.to_string() + "</span></div>\n";
639    f.write(temp_str.as_bytes()).expect("write failed");
640}
641
642/// Print Verification type SubStep
643fn compose_html_body_section_step_verification(f:&mut File, author:&String, r:&Requirement) {
644    // Simple div and span combination for formatting in stanhope.css, with additional Requirement Verificaiton boilerplate text
645    let temp_str = "\t\t\t\t\t\t<div class=\"requirement\">This step produces the evidence of verification for this requirement:<br/><span class=\"requirement-rid\">".to_string() + &r.get_id() + ": \"" + &r.get_text() + "\" (Verify by " + &r.get_method() + ")</span><br/>Please ensure the evidence produced passes the requirement before proceding. This might require consulting this procedure's author, " + author + ".</div>\n";
646    f.write(temp_str.as_bytes()).expect("write failed");
647}
648
649/// Print Context type SubStep
650fn compose_html_body_section_step_context(f:&mut File, text:&String) {
651    // Simple div and span combination for formatting in stanhope.css
652    let temp_str = "\t\t\t\t\t\t<div class=\"context\"><span class=\"context-text\">".to_string() + &text.to_string() + "</span></div>\n";
653    f.write(temp_str.as_bytes()).expect("write failed");
654}
655
656/// Print Objective type SubStep
657fn compose_html_body_section_step_objective(f:&mut File, objective_text:&String) {
658    // Simple div and span combination for formatting in stanhope.css, with additional Objective text
659    let temp_str = "\t\t\t\t\t\t<div class=\"objective\">".to_string() + objective_text + "</div>\n";
660    f.write(temp_str.as_bytes()).expect("write failed");
661}
662
663/// Print Out of Scope type SubStep
664fn compose_html_body_section_step_out_of_scope(f:&mut File, out_of_scope_text:&String) {
665    // Simple div and span combination for formatting in stanhope.css, with additional Out Of Scope text
666    let temp_str = "\t\t\t\t\t\t<div class=\"out-of-scope\">".to_string() + out_of_scope_text + "</div>\n";
667    f.write(temp_str.as_bytes()).expect("write failed");
668}
669
670/// Print an arbitrary table in its own div element, based on the contents of a "Table" struct (including a caption)
671fn compose_html_body_section_step_table(f:&mut File, table:&Table) {
672
673    let (rows,cols) = table.get_size();
674
675    // Only write a table if there is a table...
676    if rows > 0 {
677
678        f.write(b"\t\t\t\t\t\t<div class=\"csv-table-container\">\n").expect("write failed");
679        f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
680
681        let temp_str = "\t\t\t\t\t\t\t\t<caption>".to_string() + table.get_caption() + "</caption>\n";
682        f.write(temp_str.as_bytes()).expect("write failed");
683
684        f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
685        f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
686        let first_row = table.get_row(0);
687        for jj in 0..cols {
688
689            let temp_str = "\t\t\t\t\t\t\t\t\t\t<th>".to_string() + &first_row[jj] + "</th>\n";
690            f.write(temp_str.as_bytes()).expect("write failed");
691        }
692        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
693        f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
694        f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
695        
696        for ii in 1..rows {
697            let this_row = table.get_row(ii);
698            f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
699            for jj in 0..cols {
700                let temp_str = "\t\t\t\t\t\t\t\t\t\t<td>".to_string() + &this_row[jj] + "</td>\n";
701                f.write(temp_str.as_bytes()).expect("write failed");
702            }
703            f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
704        }
705        
706        f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
707        f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
708        f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
709    }
710}
711
712/// Close out the sections, and include the process document's header and footer banners
713fn compose_html_body_end_of_sections_and_banners(f:&mut File, p:&Process) {
714    f.write(b"\t\t</tbody>\n\n").expect("write failed");
715    f.write(b"\t\t\t<tfoot><tr><td>\n").expect("write failed");
716    f.write(b"\t\t\t\t<div class=\"footer-space\">&nbsp;</div>\n").expect("write failed");
717    f.write(b"\t\t\t</td></tr></tfoot>\n").expect("write failed");
718    f.write(b"\t\t</table>\n\n").expect("write failed");
719
720    // The header div is hard-programmed here to be full-width. Stanhope.css configures the height
721    f.write(b"\t\t<div class=\"header\">\n").expect("write failed");
722    f.write(b"\t\t\t<table style=\"margin:0; padding:0; width:100%;\">\n").expect("write failed");
723    f.write(b"\t\t\t\t<tbody style=\"margin:0; padding:0;\">\n").expect("write failed");
724    f.write(b"\t\t\t\t\t<tr style=\"vertical-align: top; margin:0;\">\n").expect("write failed");
725
726    // Split the width of the header with... ANOTHER TABLE!
727    // Logo image goes here in an empty *img* tag. Stanhope.css has a placeholder logo, but Template files can change the 'content' of this img to change what displays.
728    // The logo/header image should be in ../assets/ for reusability
729    let temp_str = "\t\t\t\t\t\t<td id=\"header-left\"><p class=\"header-p\"><img/><span style=\"font-size:1.3em; font-weight:bold;\">".to_string() + p.get_number() + ", Rev " + p.get_revision() + "</span><br/>Written by: " + p.get_author() + "<br/>Reviewed by: " + p.get_reviewer() + "</p></td>\n";
730    f.write(temp_str.as_bytes()).expect("write failed");
731    // The right side of the header
732    let temp_str = "\t\t\t\t\t\t<td id=\"header-right\"><p class=\"header-p\"><span style=\"font-size:1.3em; font-weight:bold;\">".to_string() + p.get_title() + "</span><br/>Applies to " + p.get_subject() + " to produce " + p.get_product() + "<br/>" + &p.get_all_resources().len().to_string() + " named resources are identified to perform this process</p></td>\n";
733    f.write(temp_str.as_bytes()).expect("write failed");
734    // Close out header
735    f.write(b"\t\t\t\t\t</tr>\n").expect("write failed");
736    f.write(b"\t\t\t\t</tbody>\n").expect("write failed");
737    f.write(b"\t\t\t</table>\n").expect("write failed");
738    f.write(b"\t\t</div>\n").expect("write failed");
739
740    // Footer is also a... TABLE!!
741    f.write(b"\t\t<div class=\"footer\">\n").expect("write failed");
742    f.write(b"\t\t\t<table style=\"margin:0; padding:0; width:100%;\">\n").expect("write failed");
743    f.write(b"\t\t\t\t<tbody style=\"margin:0; padding:0; width:100%\">\n").expect("write failed");
744    f.write(b"\t\t\t\t\t<tr style=\"vertical-align: top; margin:0;\">\n").expect("write failed");
745    // Similar to header
746    let temp_str = "\t\t\t\t\t\t<td width=\"100%\" style=\"margin:0; padding:0;\"><p class=\"header-p\"><!--<a href=\"https://strativusgroup.com\">--><img/>Produced efficiently with stanhope. See this document's first page for restrictions and distribution statements.<!--</a>--><span>".to_string() + "</p></td>\n";
747    f.write(temp_str.as_bytes()).expect("write failed");
748    // Middle section
749    let temp_str = "\t\t\t\t\t\t<td><p class=\"header-p\"><span>".to_string() + "</span><br/></p></td>\n";
750    f.write(temp_str.as_bytes()).expect("write failed");
751    /*
752    // Right-hand section
753    let temp_str = "\t\t\t\t\t\t<td width=\"10%\"><p class=\"header-p\"><span class=\"hide-on-screen\" id=\"pagenum\"></span><br/></p></td>\n".to_string();
754    f.write(temp_str.as_bytes()).expect("write failed");
755    */
756    
757    // close out the banners
758    f.write(b"\t\t\t\t\t</tr>\n").expect("write failed");
759    f.write(b"\t\t\t\t</tbody>\n").expect("write failed");
760    f.write(b"\t\t\t</table>\n").expect("write failed");
761    f.write(b"\t\t</div>\n").expect("write failed");   
762}