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 "What Changed"
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\">What Changed</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: 2px solid #5566AA; background-color: #CCDDFF; color: #5566AA;"),
277            1 => String::from("border: 2px solid #5566AA; background-color: #CCBBFF; color: #5566AA;"),
278            0 => String::from("border: 2px solid #5566AA; background-color: #AABBEE; color: #5566AA;"),
279            _ => String::from("border: 2px solid #5566AA; background-color: #AA99DD; color: #5566AA;"),
280        }
281    }
282
283    // Table of Contents
284    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
285    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
286    f.write(b"\t\t\t\t\t<div id=\"toc\">\n").expect("write failed");
287    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";
288    f.write(temp_str.as_bytes()).expect("write failed");
289    // This TOC is a bulleted list with links to document anchors
290    f.write(b"\t\t\t\t\t\t<ul style=\"toc-section\">\n").expect("write failed");
291    for (ii,sec) in s.iter().enumerate() { 
292        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";
293        f.write(temp_str.as_bytes()).expect("write failed");
294        f.write(b"\t\t\t\t\t\t\t\t<ul style=\"toc-step\">\n").expect("write failed");
295        for (jj,stp) in sec.get_all_steps().iter().enumerate() {
296            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";
297            f.write(temp_str.as_bytes()).expect("write failed");
298        }
299        f.write(b"\t\t\t\t\t\t\t\t</ul>\n").expect("write failed");
300    }
301    f.write(b"\t\t\t\t\t\t</ul>\n\n").expect("write failed");
302    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
303
304    // Roles - see Roles.css
305    f.write(b"\t\t\t\t\t<div id=\"roles\">\n").expect("write failed");
306    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");
307    f.write(b"\t\t\t\t\t\t<table id=\"roles-table\">\n").expect("write failed");
308    f.write(b"\t\t\t\t\t\t\t<thead id=\"roles-header\">\n").expect("write failed");
309    f.write(b"\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
310    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");
311    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-name\">Name of Person</td>\n").expect("write failed");
312    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-initials\">Initials</td>\n").expect("write failed");
313    f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-contact\">Contact Information</td>\n").expect("write failed");
314    f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
315    f.write(b"\t\t\t\t\t\t\t</thead>\n").expect("write failed");
316    f.write(b"\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
317    let num_roles:usize = 10;
318    for ii in 1..(num_roles+1) {
319        let temp_str = String::from("\t\t\t\t\t\t\t\t<tr id=\"role-".to_string() + &format!("{:0>2}",ii) + "\">\n");
320        f.write(temp_str.as_bytes()).expect("write failed");
321        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-title\"></td>\n").expect("write failed");
322        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-name\"></td>\n").expect("write failed");
323        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-initials\"></td>\n").expect("write failed");
324        f.write(b"\t\t\t\t\t\t\t\t\t<td class=\"role-contact\"></td>\n").expect("write failed");
325        f.write(b"\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
326    }
327    f.write(b"\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
328    f.write(b"\t\t\t\t\t\t</table>\n").expect("write failed");
329    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
330
331    // Various warnings
332    // TODO: robustify this by generalizing templates.
333    f.write(b"\t\t\t\t\t<div class=\"power-on\"><img/><span class=\"power-on-text\"></span></div>\n").expect("write failed");
334    f.write(b"\t\t\t\t\t<div class=\"rf-emission\"><img/><span class=\"rf-emission-text\"></span></div>\n").expect("write failed");
335    f.write(b"\t\t\t\t\t<div class=\"hazardous-ops\"><img/><span class=\"hazardous-ops-text\"></span></div>\n").expect("write failed");
336    f.write(b"\t\t\t\t\t<div class=\"esd\"><img/><span class=\"esd-text\"></span></div>\n").expect("write failed");
337    f.write(b"\t\t\t\t\t<div class=\"fod\"><img/><span class=\"fod-text\"></span></div>\n").expect("write failed");
338    f.write(b"\t\t\t\t\t<div class=\"welding\"><img/><span class=\"welding-text\"></span></div>\n").expect("write failed");
339    f.write(b"\t\t\t\t\t<div class=\"comsec\"><img/><span class=\"comsec-text\"></span></div>\n").expect("write failed");
340    f.write(b"\t\t\t\t\t<div class=\"cleanroom\"><img/><span class=\"cleanroom-text\"></span></div>\n").expect("write failed");
341    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
342    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
343
344    // New page for Subject/Product and Resources
345    f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
346    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
347    
348    let all_objectives = p.get_all_objectives();
349    if all_objectives.len() > 0 {
350        // List of Objectives
351        f.write(b"\t\t\t\t\t<div id=\"all-objectives\">\n").expect("write failed");
352        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";
353        f.write(temp_str.as_bytes()).expect("write failed");
354        f.write(b"\t\t\t\t\t\t<ol class=\"objective-list\">\n").expect("write failed");
355        for (_jj,(obj,snum)) in all_objectives.iter().enumerate() {
356            let temp_str = String::from("\t\t\t\t\t\t\t<li>") + &obj + " (" + &snum + ")</li>\n";
357            f.write(temp_str.as_bytes()).expect("write failed");
358        }
359        f.write(b"\t\t\t\t\t\t</ol>\n").expect("write failed");
360        f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
361    };
362
363    let all_out_of_scopes = p.get_all_out_of_scopes();
364    if all_out_of_scopes.len() > 0 {
365        // List of Objectives
366        f.write(b"\t\t\t\t\t<div id=\"all-out-of-scopes\">\n").expect("write failed");
367        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";
368        f.write(temp_str.as_bytes()).expect("write failed");
369        f.write(b"\t\t\t\t\t\t<ol class=\"out-of-scope-list\">\n").expect("write failed");
370        for (_jj,(oos,snum)) in all_out_of_scopes.iter().enumerate() {
371            let temp_str = String::from("\t\t\t\t\t\t\t<li>") + &oos + " (" + &snum + ")</li>\n";
372            f.write(temp_str.as_bytes()).expect("write failed");
373        }
374        f.write(b"\t\t\t\t\t\t</ol>\n").expect("write failed");
375        f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
376    };
377
378    // Subject/Product logic and writing
379    f.write(b"\t\t\t\t\t<div id=\"subject-product\">\n").expect("write failed");
380    
381    match p.get_product().as_str() {
382        ""|"N/A" => {
383            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";
384            f.write(temp_str.as_bytes()).expect("write failed");
385            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";
386            f.write(temp_str.as_bytes()).expect("write failed");
387        },
388        _ => {
389            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";
390            f.write(temp_str.as_bytes()).expect("write failed");
391            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";
392            f.write(temp_str.as_bytes()).expect("write failed");
393        },
394    };
395    
396    f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
397    // Resource blob
398    if p.get_all_resources().len() > 0 {
399        f.write(b"\t\t\t\t\t\t<div class=\"resources-container\">\n").expect("write failed");
400        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";
401        f.write(temp_str.as_bytes()).expect("write failed");
402
403        // How we're using the color picker: increment the "index" and let the function apply the remainder function
404        let mut step_num = "";
405        let mut index:usize = 0;
406        for (resource,resource_step) in p.get_all_resources() {
407            if !(step_num==resource_step) { index += 1; };
408            step_num = resource_step;
409            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";
410            f.write(temp_str.as_bytes()).expect("write failed");
411        }
412        f.write(b"\t\t\t\t\t\t</div><div style=\"clear:both;\"></div>\n").expect("write failed");
413    }
414    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
415    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
416    // end resource blob
417
418    // NCRs - Non-Conformances
419    f.write(b"\t\t\t<tr id=\"non-conformances\"><td>\n\n").expect("write failed");
420    f.write(b"\t\t\t\t<section class=\"\">\n").expect("write failed");
421    f.write(b"\t\t\t\t\t<span class=\"toc-title\">Log of Non-Conformances during this execution (print and insert more copies of this sheet as needed):</span>\n").expect("write failed");
422    f.write(b"\t\t\t\t\t<table>\n").expect("write failed");
423    f.write(b"\t\t\t\t\t\t<thead>\n").expect("write failed");
424    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
425    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Time</td>\n").expect("write failed");
426    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Step</td>\n").expect("write failed");
427    f.write(b"\t\t\t\t\t\t\t\t<td class=\"text-rotated\">Record</td>\n").expect("write failed");
428    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");
429    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
430    f.write(b"\t\t\t\t\t\t</thead>\n").expect("write failed");
431    f.write(b"\t\t\t\t\t\t<tbody>\n").expect("write failed");
432    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
433    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
434    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
435    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
436    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
437    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
438    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
439    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
440    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
441    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
442    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
443    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
444    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
445    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
446    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
447    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
448    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
449    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
450    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
451    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
452    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
453    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
454    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
455    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
456    f.write(b"\t\t\t\t\t\t\t<tr>\n").expect("write failed");
457    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
458    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
459    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
460    f.write(b"\t\t\t\t\t\t\t\t<td></td>\n").expect("write failed");
461    f.write(b"\t\t\t\t\t\t\t</tr>\n").expect("write failed");
462    f.write(b"\t\t\t\t\t\t</tbody>\n").expect("write failed");
463    f.write(b"\t\t\t\t\t</table>\n").expect("write failed");
464    f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
465    f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
466
467    
468}
469
470/// Write every Section in the Process, sequentially. Include Steps within Sections and SubSteps within Steps.
471fn compose_html_body_section(f:&mut File, p:&Process, all_sections:&Vec<Section>) {
472    
473    // Count the Sections as we go, beceuse sequential processes deserve sequential indenting and numbering
474    let mut counter = 0;
475    for section in all_sections {
476        // Each Section is cleverly its own *section* element... brilliant!
477        f.write(b"\t\t\t<tr><td>\n\n").expect("write failed");
478        f.write(b"\t\t\t\t<section>\n").expect("write failed");
479        
480        // Count the sections as we go
481        // TODO: modernize this by enumerating an iterator for all_sections, if Rust lets us use iterators for our own Vec<Section>
482        counter += 1;
483
484        // Print out the title of the section
485        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";
486        f.write(temp_str.as_bytes()).expect("write failed");
487
488        // Iterate over every Step in the Section
489        for (ii,step) in section.get_all_steps().iter().enumerate() {
490
491            // Use the text stored at the Step level at the top of the Step
492            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";
493            f.write(temp_str.as_bytes()).expect("write failed");
494
495            // Resource blob goes at the top of each Step, so that the operator can be full-kit before starting the Step
496            if step.get_resources().len() > 0 {
497                f.write(b"\t\t\t\t\t\t\t<div class=\"resources-container\">\n").expect("write failed");
498                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";
499                f.write(temp_str.as_bytes()).expect("write failed");
500                for resource in step.get_resources() {
501                    let temp_str = "\t\t\t\t\t\t\t\t<div class=\"resource-bubble\">".to_string() + &resource.get_name() + "</div>\n";
502                    f.write(temp_str.as_bytes()).expect("write failed");
503                }
504                f.write(b"\t\t\t\t\t\t\t</div><div style=\"clear:both;\"></div>\n").expect("write failed");
505            }
506            // end resource blob
507
508            // Iterate over all of the SubSteps in the Step, and use other functions in this crate to write out accordingly
509            for (_jj,sub_step) in step.get_all_sub_steps().iter().enumerate() {
510                match sub_step {
511                    SubStep::ActionSequence(action_sequence) => compose_html_body_section_step_action_sequence(f,&action_sequence),
512                    SubStep::Command(command_text) => compose_html_body_section_step_command(f,&command_text),
513                    SubStep::Image(image_file,image_text) => compose_html_body_section_step_image(f,&image_file,&image_text),
514                    SubStep::Warning(warning_text) => compose_html_body_section_step_warning(f,&warning_text),
515                    SubStep::Verification(requirement) => compose_html_body_section_step_verification(f,&p.get_author(),&requirement),
516                    // Do nothing for Resource type SubSteps, because we've printed them at the top of the Step
517                    // TODO: add a feature later to also do something in-place where the Resource was identified within the Step...
518                    SubStep::Resource(_resource) => (),
519                    SubStep::Context(text) => compose_html_body_section_step_context(f,&text),
520                    SubStep::Objective(objective_text) => compose_html_body_section_step_objective(f,&objective_text),
521                    SubStep::OutOfScope(out_of_scope_text) => compose_html_body_section_step_out_of_scope(f,&out_of_scope_text),
522                    SubStep::Table(table) => compose_html_body_section_step_table(f,&table),
523                }
524            }
525            // close the Step div
526            f.write(b"\t\t\t\t\t</div>\n").expect("write failed");
527        }
528        // close the section
529        f.write(b"\t\t\t\t</section>\n\n").expect("write failed");
530        f.write(b"\t\t\t</td></tr>\n\n").expect("write failed");
531    }
532}
533
534/// Print ActionSequence type SubStep
535fn compose_html_body_section_step_action_sequence(f:&mut File, act_seq:&Vec<Action>) {
536    // ActionSequence prints as a table (tables!)
537    f.write(b"\t\t\t\t\t\t<div class=\"action-sequence\">\n").expect("write failed");
538    f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
539    f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
540    f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
541    // Three columns: Action to perform, Expected result, and Two-Party Verification
542    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");
543    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-expected\">Expected</td>\n").expect("write failed");
544    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-result\">Result</td>\n").expect("write failed");
545    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">Done</td>\n").expect("write failed");
546    f.write(b"\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-tpv\">TPV</td>\n").expect("write failed");
547    f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
548    f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
549    f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
550
551    // one of these for each action
552    for act in act_seq {
553        f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
554        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";
555        f.write(temp_str.as_bytes()).expect("write failed");
556        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";
557        f.write(temp_str.as_bytes()).expect("write failed");
558        let temp_str = "\t\t\t\t\t\t\t\t\t\t<td class=\"action-sequence-result\">".to_string() + "</td>\n";
559        f.write(temp_str.as_bytes()).expect("write failed");
560        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";
561        f.write(temp_str.as_bytes()).expect("write failed");
562        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";
563        f.write(temp_str.as_bytes()).expect("write failed");
564        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
565    }
566
567    // close the table
568    f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
569    f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
570    f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
571}
572
573/// Print Command type SubStep
574fn compose_html_body_section_step_command(f:&mut File, c:&String) {
575    // Simple div and span combination for formatting in stanhope.css
576    let temp_str = "\t\t\t\t\t\t<div class=\"command\"><span class=\"command-text\">".to_string() + &c.to_string() + "</span></div>\n";
577    f.write(temp_str.as_bytes()).expect("write failed");
578}
579
580/// Print Image type SubStep
581fn compose_html_body_section_step_image(f:&mut File, img:&String, txt:&String) {
582    // There's a container Div, and inside that there's the image, and below the image there's the caption
583    // The container Div can be stylized in stanhope.css to be centered (for instance), with a border, etc.
584    // The container can also be styled to have maximum width or height, etc.
585    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";
586    f.write(temp_str.as_bytes()).expect("write failed");
587}
588
589/// Print Warning type SubStep
590fn compose_html_body_section_step_warning(f:&mut File, w:&String) {
591    // Simple div and span combination for formatting in stanhope.css
592    let temp_str = "\t\t\t\t\t\t<div class=\"warning\"><span class=\"warning-text\">".to_string() + &w.to_string() + "</span></div>\n";
593    f.write(temp_str.as_bytes()).expect("write failed");
594}
595
596/// Print Verification type SubStep
597fn compose_html_body_section_step_verification(f:&mut File, author:&String, r:&Requirement) {
598    // Simple div and span combination for formatting in stanhope.css, with additional Requirement Verificaiton boilerplate text
599    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";
600    f.write(temp_str.as_bytes()).expect("write failed");
601}
602
603/// Print Context type SubStep
604fn compose_html_body_section_step_context(f:&mut File, text:&String) {
605    // Simple div and span combination for formatting in stanhope.css
606    let temp_str = "\t\t\t\t\t\t<div class=\"context\"><span class=\"context-text\">".to_string() + &text.to_string() + "</span></div>\n";
607    f.write(temp_str.as_bytes()).expect("write failed");
608}
609
610/// Print Objective type SubStep
611fn compose_html_body_section_step_objective(f:&mut File, objective_text:&String) {
612    // Simple div and span combination for formatting in stanhope.css, with additional Objective text
613    let temp_str = "\t\t\t\t\t\t<div class=\"objective\">".to_string() + objective_text + "</div>\n";
614    f.write(temp_str.as_bytes()).expect("write failed");
615}
616
617/// Print Out of Scope type SubStep
618fn compose_html_body_section_step_out_of_scope(f:&mut File, out_of_scope_text:&String) {
619    // Simple div and span combination for formatting in stanhope.css, with additional Out Of Scope text
620    let temp_str = "\t\t\t\t\t\t<div class=\"out-of-scope\">".to_string() + out_of_scope_text + "</div>\n";
621    f.write(temp_str.as_bytes()).expect("write failed");
622}
623
624/// Print an arbitrary table in its own div element, based on the contents of a "Table" struct (including a caption)
625fn compose_html_body_section_step_table(f:&mut File, table:&Table) {
626
627    let (rows,cols) = table.get_size();
628
629    // Only write a table if there is a table...
630    if rows > 0 {
631
632        f.write(b"\t\t\t\t\t\t<div class=\"csv-table-container\">\n").expect("write failed");
633        f.write(b"\t\t\t\t\t\t\t<table>\n").expect("write failed");
634
635        let temp_str = "\t\t\t\t\t\t\t\t<caption>".to_string() + table.get_caption() + "</caption>\n";
636        f.write(temp_str.as_bytes()).expect("write failed");
637
638        f.write(b"\t\t\t\t\t\t\t\t<thead>\n").expect("write failed");
639        f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
640        let first_row = table.get_row(0);
641        for jj in 0..cols {
642
643            let temp_str = "\t\t\t\t\t\t\t\t\t\t<th>".to_string() + &first_row[jj] + "</th>\n";
644            f.write(temp_str.as_bytes()).expect("write failed");
645        }
646        f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
647        f.write(b"\t\t\t\t\t\t\t\t</thead>\n").expect("write failed");
648        f.write(b"\t\t\t\t\t\t\t\t<tbody>\n").expect("write failed");
649        
650        for ii in 1..rows {
651            let this_row = table.get_row(ii);
652            f.write(b"\t\t\t\t\t\t\t\t\t<tr>\n").expect("write failed");
653            for jj in 0..cols {
654                let temp_str = "\t\t\t\t\t\t\t\t\t\t<td>".to_string() + &this_row[jj] + "</td>\n";
655                f.write(temp_str.as_bytes()).expect("write failed");
656            }
657            f.write(b"\t\t\t\t\t\t\t\t\t</tr>\n").expect("write failed");
658        }
659        
660        f.write(b"\t\t\t\t\t\t\t\t</tbody>\n").expect("write failed");
661        f.write(b"\t\t\t\t\t\t\t</table>\n").expect("write failed");
662        f.write(b"\t\t\t\t\t\t</div>\n").expect("write failed");
663    }
664}
665
666/// Close out the sections, and include the process document's header and footer banners
667fn compose_html_body_end_of_sections_and_banners(f:&mut File, p:&Process) {
668    f.write(b"\t\t</tbody>\n\n").expect("write failed");
669    f.write(b"\t\t\t<tfoot><tr><td>\n").expect("write failed");
670    f.write(b"\t\t\t\t<div class=\"footer-space\">&nbsp;</div>\n").expect("write failed");
671    f.write(b"\t\t\t</td></tr></tfoot>\n").expect("write failed");
672    f.write(b"\t\t</table>\n\n").expect("write failed");
673
674    // The header div is hard-programmed here to be full-width. Stanhope.css configures the height
675    f.write(b"\t\t<div class=\"header\">\n").expect("write failed");
676    f.write(b"\t\t\t<table style=\"margin:0; padding:0; width:100%;\">\n").expect("write failed");
677    f.write(b"\t\t\t\t<tbody style=\"margin:0; padding:0;\">\n").expect("write failed");
678    f.write(b"\t\t\t\t\t<tr style=\"vertical-align: top; margin:0;\">\n").expect("write failed");
679
680    // Split the width of the header with... ANOTHER TABLE!
681    // 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.
682    // The logo/header image should be in ../assets/ for reusability
683    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";
684    f.write(temp_str.as_bytes()).expect("write failed");
685    // The right side of the header
686    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";
687    f.write(temp_str.as_bytes()).expect("write failed");
688    // Close out header
689    f.write(b"\t\t\t\t\t</tr>\n").expect("write failed");
690    f.write(b"\t\t\t\t</tbody>\n").expect("write failed");
691    f.write(b"\t\t\t</table>\n").expect("write failed");
692    f.write(b"\t\t</div>\n").expect("write failed");
693
694    // Footer is also a... TABLE!!
695    f.write(b"\t\t<div class=\"footer\">\n").expect("write failed");
696    f.write(b"\t\t\t<table style=\"margin:0; padding:0; width:100%;\">\n").expect("write failed");
697    f.write(b"\t\t\t\t<tbody style=\"margin:0; padding:0; width:100%\">\n").expect("write failed");
698    f.write(b"\t\t\t\t\t<tr style=\"vertical-align: top; margin:0;\">\n").expect("write failed");
699    // Similar to header
700    let temp_str = "\t\t\t\t\t\t<td width=\"35%\" style=\"margin:0; padding:0;\"><p class=\"header-p\"><a href=\"https://strativusgroup.com\"><img/>Produced efficiently with Strativus Group</a><span>".to_string() + "</p></td>\n";
701    f.write(temp_str.as_bytes()).expect("write failed");
702    // Middle section
703    let temp_str = "\t\t\t\t\t\t<td><p class=\"header-p\"><span>".to_string() + "See this document's first page for restrictions and distribution statements.</span><br/></p></td>\n";
704    f.write(temp_str.as_bytes()).expect("write failed");
705    /*
706    // Right-hand section
707    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();
708    f.write(temp_str.as_bytes()).expect("write failed");
709    */
710    
711    // close out the banners
712    f.write(b"\t\t\t\t\t</tr>\n").expect("write failed");
713    f.write(b"\t\t\t\t</tbody>\n").expect("write failed");
714    f.write(b"\t\t\t</table>\n").expect("write failed");
715    f.write(b"\t\t</div>\n").expect("write failed");   
716}