Skip to content

Commit

Permalink
V1 Complete
Browse files Browse the repository at this point in the history
- script editor with support for:
  - ranges ("N1..N2")
  - hyphens ("prefix-expansion-suffix")
  - commas ("first,second,third")
- console output with brief diagnostic information
- text editor that lists labels individually
  - allows for manual verification
  - enables any manual modifications
- document generator that puts 68 labels onto each page
  • Loading branch information
Henry Loh committed Nov 14, 2024
0 parents commit 5aeae96
Show file tree
Hide file tree
Showing 30 changed files with 54,618 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
18,005 changes: 18,005 additions & 0 deletions document-template.xml

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* Simple styling to get the layout you described */
body {
font-family: Arial, sans-serif;
}

.container {
padding: 20px;
white-space: nowrap; /* Prevent wrapping */
}

.left-side {
display: inline-block;
width: 55%;
padding: 10px;
vertical-align: top; /* Align vertically */
white-space: wrap; /* Allow wrapping */
}

.right-side {
display: inline-block;
width: 40%;
padding: 10px;
vertical-align: top; /* Align vertically */
margin-left: 20px;
}

.small-text-input {
width: 50%;
height: 30px;
margin-bottom: 10px;
padding: 10px;
font-size: 16px;
}

.medium-textarea {
width: 100%;
height: 200px;
margin-bottom: 20px;
margin-right: 20px;
padding: 10px;
font-size: 16px;
}

.large-textbox {
width: 100%;
height: 80vh;
padding: 10px;
font-size: 16px;
}

button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<div class="left-side">
<span>
<label for="cfg_prefix">Output File Prefix:</label>
<input type="text" id="cfg_prefix" name="cfg_prefix" value="labels"><br><br>
<!--
<label for="cfg_initials">Date:</label>
<input type="text" id="cfg_initials" name="cfg_initials" value="Doe"><br><br>
<label for="lname">Date:</label>
<input type="text" id="lname" name="lname" value="Doe"><br><br>
-->
</span>
<label for="script_box">
Columns:<br>
<ul>
<li>Initials ("HL" etc.)</li>
<li>Date ("11-20-24" etc.)</li>
<li>Test Group (UM, DM, etc.)</li>
<li>Subject ID</li>
<li>Sample ID</li>
<li>Sample Type</li>
</ul>
Expansion Rules:<br>
<ul>
<li>ranges "num1..num2" -- expands into the range [num1, num1+1, ..., num2] (inclusive)</li>
<li>hyphens "-" -- merges together its sub-groups</li>
<li>commas "," -- concatenates independent sub-groups together</li>
<li>ranges are higher precedence than hyphens which are higher precedence than commas:
<ul>
<li>so "A-1..5,B-6..9" is treated like "(A-(1..5)),(B-(6..9))"</li>
<li>and its output would be "A-1 A-2 A-3 A-4 A-5 B-6 B-7 B-8 B-9"</li>
<li>note: you cannot actually use parantheses to override the precedence rules</li>
</ul>
</ul>
Script Box:<br>
</label><br>
<textarea id="script_box" class="medium-textarea"></textarea><br>
<button id="generateButton" onclick="parseScript()">Generate Combinations</button>
<button id="downloadButton" onclick="generateAllDocuments()">Download Documents</button>
<br>
<label for="console">Console:</label><br>
<textarea id="console" class="medium-textarea" readonly></textarea><br>
<label for="example">Example Script:</label><br>
<textarea id="example" class="medium-textarea" readonly>
# Cornell
NH 11-20-24 UM 207..218 01..06 Tibia
NH 11-20-24 UM 207..218 01..10 Serum
NH 11-20-24 UM 207..218 01..01 Cecum,LFemur,RFemur,Feces
NH 11-20-24 CM 219..221 01..06 Tibia
NH 11-20-24 CM 219..230 01..10 Serum
NH 11-20-24 CM 219..230 01..01 Cecum,LFemur,RFemur,Feces

NH 11-20-24 UM 231..235 01..01 EDL,TA,LFemur,RFemur
NH 11-20-24 DM 236..243 01..01 EDL,TA,LFemur,RFemur
NH 11-20-24 CF A-01..05 01..01 LFemur,RFemur
</textarea><br>
</div>
<div class="right-side">
<label for="raw_box">Output of Script Box (can be manually modified too):</label><br>
<textarea id="raw_box" class="large-textbox"></textarea>
</div>
</div>

<script src="zip.js"></script>
<script src="index.js"></script>
<script src="template.js/content_types.xml.js"></script>
<script src="template.js/docProps/app.xml.js"></script>
<script src="template.js/docProps/core.xml.js"></script>
<script src="template.js/docProps/custom.xml.js"></script>
<script src="template.js/_rels/dot_rels.js"></script>
<script src="template.js/word/document.xml.js"></script>
<script src="template.js/word/fontTable.xml.js"></script>
<script src="template.js/word/settings.xml.js"></script>
<script src="template.js/word/styles.xml.js"></script>
<script src="template.js/word/webSettings.xml.js"></script>
<script src="template.js/word/theme/theme1.xml.js"></script>
<script src="template.js/word/_rels/document.xml.rels.js"></script>
</body>
</html>
228 changes: 228 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@

const MAX_LABELS_PER_PAGE = 68;

/* Input: string
* Output: list of values
*/
function getValues (rawInput)
{
// Commas separate completely independent groups, then add them back together
let groups = rawInput.split(",");
if (groups.length > 1)
{
// Expand each individual group then concatenate together
return groups.map(getValues).flat();
}

// Hyphens enable prefixing / suffixing strings onto sub-expressions
// For example:
// A-01..05
// Is split into:
// ["A", "01..05"]
// Expanded into:
// [["A"], ["01", "02", "03", "04", "05"]]
// Then combined back into:
// ["A-01", "A-02", "A-03", "A-04", "A-05"]
let hyphens = rawInput.split("-"); /* e.g. ["A", "01..05"] */
if (hyphens.length > 1)
{
/* Example: [["A"], ["01", "02", "03", "04", "05"]] */
let expandedValues = hyphens.map(getValues);

/* Example: [["A", "01"], ["A", "02"], ["A", "03"], ["A", "04"], ["A", "05"]] */
let combinations = generateCombinations(expandedValues);

/* Example: ["A-01", "A-02", "A-03", "A-04", "A-05"] */
let merged = combinations.map((pair) => pair.join("-"));

return merged;
}

// The double-dot ".." enables expanding a numeric range
// For example:
// 01..05
// Is split into:
// ["01", "05"]
// Parsed as:
// [1, 5]
// Expanded to:
// [1, 2, 3, 4, 5]
// And stringified into:
// ["1", "2", "3", "4", "5"]
let bounds = rawInput.split("..");
if (bounds.length > 1)
{
if (bounds.length > 2)
{
alert("Bad input, invalid bounds: " + rawInput);
return [];
}
else
{
let numbers = bounds.map(parseFloat);
let start = numbers[0];
let stop = numbers[1];
let range = Array.from({length: stop - start + 1}, (x, i) => i + start);

return range.map((num) => ("" + num));
}
}

return [rawInput];
}

function generateCombinations (dimensions)
{
/* Base case, return empty list */
if (dimensions.length == 0)
return []
else if (dimensions.length == 1)
{
return dimensions[0];
}
else
{
let outputs = [];

/* Generate combos from this node with its children, e.g.
* values = [1, 2]
* children = [[a], [b], [c]]
* outputs = [
* [1, a], [1, b], [1, c],
* [2, a], [2, b], [2, c],
* ]
*/
let dim = dimensions[0];
let children = generateCombinations(dimensions.slice(1));
for (let d=0; d<dim.length; d++)
{
for (let c=0; c<children.length; c++)
{
outputs.push([dim[d]].concat(children[c]));
}
}

return outputs;
}
}

function parseScript ()
{
const scripts = document.getElementById("script_box").value;
const lines = scripts.split("\n");

let debug = document.getElementById("console");
let output = document.getElementById("raw_box");

debug.value = "";
for (let i=0; i<lines.length; i++)
{
let line = lines[i];
let segments = line.split(" ");

debug.value += line + "\n";

if (line[0] == "#")
{
debug.value += "(ignored)\n";
}
else if (segments.length <= 1)
{
debug.value += "(empty)\n";
}
else if (segments.length != 6)
{
debug.value += "Err: row does not have 6 columns!\n";
}
else
{
/* Expand stuff like "01..05" into ["01", "02", "03", "04", "05"]; */
let values = segments.map(getValues);

/* Generate combos of individual dimensions */
let combos = generateCombinations(values);

/* Add to output box */
combos.forEach(
(combo) => {
output.value += combo.join(" ") + "\n";
}
);

debug.value += "Generated " + combos.length + " combinations.\n";
}
}
}

function generateOneDocument (filename, labels)
{
z = new Zip(filename);

var document_xml_modified = document_xml;

for (let i=0; i < labels.length; i++)
{
/* Example:
* NH 11-20-24 UM 209 3 Tibia
*/
let label = labels[i];
let parts = label.split(" ");
let match_number = String(i+1).padStart(2, "0");

document_xml_modified = (
document_xml_modified
.replace(`INITIALS_${match_number}`, parts[0])
.replace(`MM_DD_YY_${match_number}`, parts[1])
.replace(`UM_OR_OTHER_${match_number}`, parts[2])
.replace(`IDENTITY_1_${match_number}`, parts[3])
.replace(`IDENTITY_2_${match_number}`, parts[3])
.replace(`INDEX_1_${match_number}`, parts[4])
.replace(`INDEX_2_${match_number}`, parts[4])
.replace(`SAMPLE_TYPE_${match_number}`, parts[5])
);
}

document_xml_modified = (
document_xml_modified
.replaceAll("INITIALS_", "FL")
.replaceAll("MM_DD_YY_", "mm-dd-yy")
.replaceAll("UM_OR_OTHER_", "CC")
.replaceAll("IDENTITY_1_", "0")
.replaceAll("IDENTITY_2_", "0")
.replaceAll("INDEX_1_", "0")
.replaceAll("INDEX_2_", "0")
.replaceAll("SAMPLE_TYPE_", "X")
)

z.str2zip("[Content_Types].xml", content_types_xml, "");
z.str2zip("app.xml", app_xml, "docProps/");
z.str2zip("custom.xml", custom_xml, "docProps/");
z.str2zip("core.xml", core_xml, "docProps/");
z.str2zip(".rels", dot_rels, "_rels/");
z.str2zip("document.xml", document_xml_modified, "word/");
z.str2zip("fontTable.xml", fontTable_xml, "word/");
z.str2zip("settings.xml", settings_xml, "word/");
z.str2zip("styles.xml", styles_xml, "word/");
z.str2zip("webSettings.xml", webSettings_xml, "word/");
z.str2zip("theme1.xml", theme1_xml, "word/theme/");
z.str2zip("document.xml.rels", document_xml_rels, "word/_rels/");

z.makeZip();
}

function generateAllDocuments ()
{
const output_name = document.getElementById("cfg_prefix").value;
const raw_labels = document.getElementById("raw_box").value.trim().split("\n");

for (let page_ix = 0; page_ix * MAX_LABELS_PER_PAGE < raw_labels.length; page_ix++)
{
let left = page_ix * MAX_LABELS_PER_PAGE;
let right = left + MAX_LABELS_PER_PAGE;
let one_page_of_labels = raw_labels.slice(left, right);
right = left + one_page_of_labels.length;
let filename = `${output_name}_${left+1}_${right}.docx`

generateOneDocument(filename, one_page_of_labels);
}
}
Loading

0 comments on commit 5aeae96

Please sign in to comment.