This script is meant to be an example so I will post the entire script and then discuss parts of it rather than discussing the script from a design standpoint. Here is the entire script:
#define ROWSPERPAGE 250
void main() {
handle h;
string tbl[][];
string footer, fnDest;
int row, col,
rows, cols,
srow,
page, pages,
mrows,
rc;
tbl = CSVReadTable("https://raw.githubusercontent.com/chadwickbureau/baseballdatabank/master/core/People.csv");
rc = GetLastError();
rows = ArrayGetAxisDepth(tbl);
if (rows == 0) {
MessageBox('x', "Couldn't read CSV file. (0x%08X)", rc);
}
cols = ArrayGetAxisDepth(tbl, AXIS_COL);
fnDest = BrowseSaveFile("Save HTML...", "HTML Files|*.html;*.htm");
if (fnDest == "") {
return;
}
h = HTMLCreateWriterObject();
HTMLSetDTD(h);
HTMLAddHead(h, "Baseball Players", "Arial, Helvetica, Sans-serif", "9pt");
HTMLSetCellStyle(h, "padding-top: 5px; padding-bottom: 5px; border-top-width: 1pt; border-top-color: black; border-top-style: solid;");
ProgressOpen("Building HTML...");
row = 1;
page = 1;
pages = (rows / ROWSPERPAGE) + 1;
while (row < rows) {
ProgressSetStatus("Writing row %d of %d", row, rows - 1);
HTMLTableOpen(h);
HTMLSetRowStyle(h, "font-weight: bold; text-align: center; text-transform: uppercase;");
HTMLRowOpen(h);
for (col = 0; col < cols; col++) {
HTMLCellOpen(h);
HTMLAddText(h, tbl[0][col]);
HTMLCellClose(h);
}
HTMLRowClose(h);
HTMLSetRowStyle(h);
mrows = row + ROWSPERPAGE;
if (mrows > rows) {
mrows = rows;
}
srow = 0;
for (row; row < mrows; row++) {
ProgressUpdate(row, rows);
if ((srow % 2) == 0) {
HTMLSetRowStyle(h, "background-color: lightgrey; vertical-align: top;");
}
else {
HTMLSetRowStyle(h, "background-color: white; vertical-align: top;");
}
HTMLRowOpen(h);
for (col = 0; col < cols; col++) {
HTMLCellOpen(h);
HTMLAddText(h, tbl[row][col]);
HTMLCellClose(h);
}
HTMLRowClose(h);
srow++;
}
HTMLTableClose(h);
HTMLSetBlockStyle(h, "font-size: 80%");
HTMLAddPara(h, FormatString("Page %d of %d", page, pages), "right");
HTMLSetBlockStyle(h);
page++;
}
ProgressClose();
HTMLSetBlockStyle(h, "font-size: 80%");
footer = "Generated: " + FormatDate(GetLocalTime(), DS_MONTH_DAY_YEAR | DS_DATE_AT_TIME | DS_HHMMSS_12 | DS_INITIAL);
HTMLAddPara(h, footer);
HTMLAddFoot(h);
HTMLWriterToFile(h, fnDest);
RunProgram(fnDest);
}
We start off with a define for the number of rows to display on a “page.” When testing the blog script I discovered that most web browsers don’t perform well with extremely large tables so I decided to make the script split the table into smaller ones. This define is how many rows will be placed in each table. With the globals out of the way, we can discuss the main function.
handle h;
string tbl[][];
string footer, fnDest;
int row, col,
rows, cols,
srow,
page, pages,
mrows,
rc;
tbl = CSVReadTable("https://raw.githubusercontent.com/chadwickbureau/baseballdatabank/master/core/People.csv");
rc = GetLastError();
rows = ArrayGetAxisDepth(tbl);
if (rows == 0) {
MessageBox('x', "Couldn't read CSV file. (0x%08X)", rc);
}
cols = ArrayGetAxisDepth(tbl, AXIS_COL);
fnDest = BrowseSaveFile("Save HTML...", "HTML Files|*.html;*.htm");
if (fnDest == "") {
return;
}
First, we define our variables for the function. We load our CSV file using CSVReadTable. The example uses a CSV file from an open source major league baseball database. Next, the script checks to make sure the table loaded properly before getting the number of rows of data and the number of columns. This section could easily be replaced with an ODBC call or other database interaction. Next we use BrowseSaveFile to get a filename for later. We will use this name as the destination HTML file.
h = HTMLCreateWriterObject();
HTMLSetDTD(h);
HTMLAddHead(h, "Baseball Players", "Arial, Helvetica, Sans-serif", "9pt");
HTMLSetCellStyle(h, "padding-top: 5px; padding-bottom: 5px; border-top-width: 1pt; border-top-color: black; border-top-style: solid;");
ProgressOpen("Building HTML...");
These lines set up the HTML Writer object starting with the HTMLCreateWriterObject function. We then use the HTMLSetDTD to set the default DTD (this is HTML 5). We also use the HTMLAddHead function to add a title to the document as well as a default font. The next line sets the style for all table cells in the document. In our case, all our cells will have the same style so this call works perfectly. If you want specialty formatting for different cells in your table, you may need to set the style at different points in your code. The HTML Writer class also allows the use of CSS classes which is great if the HTML being written is going to be used on a website. For our sample, inline styles will suffice. Lastly, we open a progress window using the ProgressOpen function. With all of our preparations done we can move to the writing loop.
row = 1;
page = 1;
pages = (rows / ROWSPERPAGE) + 1;
while (row < rows) {
ProgressSetStatus("Writing row %d of %d", row, rows - 1);
HTMLTableOpen(h);
HTMLSetRowStyle(h, "font-weight: bold; text-align: center; text-transform: uppercase;");
HTMLRowOpen(h);
for (col = 0; col < cols; col++) {
HTMLCellOpen(h);
HTMLAddText(h, tbl[0][col]);
HTMLCellClose(h);
}
HTMLRowClose(h);
HTMLSetRowStyle(h);
mrows = row + ROWSPERPAGE;
if (mrows > rows) {
mrows = rows;
}
srow = 0;
for (row; row < mrows; row++) {
ProgressUpdate(row, rows);
if ((srow % 2) == 0) {
HTMLSetRowStyle(h, "background-color: lightgrey; vertical-align: top;");
}
else {
HTMLSetRowStyle(h, "background-color: white; vertical-align: top;");
}
HTMLRowOpen(h);
for (col = 0; col < cols; col++) {
HTMLCellOpen(h);
HTMLAddText(h, tbl[row][col]);
HTMLCellClose(h);
}
HTMLRowClose(h);
srow++;
}
HTMLTableClose(h);
HTMLSetBlockStyle(h, "font-size: 80%");
HTMLAddPara(h, FormatString("Page %d of %d", page, pages), "right");
HTMLSetBlockStyle(h);
page++;
}
We are going to loop over all the rows in the table. We start by using HTMLTableOpen to open an HTML table. We could optionally provide a width and other style information here. We set the style of all the rows using the HTMLSetRowStyle function. We want our table to have a heading row so the style here is different. We then use HTMLRowOpen and iterate over the columns to write out the first row as column headers. We use HTMLCellOpen, HTMLAddText, and HTMLCellClose to add content to the cells. We then close the row using HTMLRowClose and clear the style for the following rows by calling the HTMLSetRowStyle function again with no parameters.
Now we have written a heading row to the document we need to write the data rows. We calculate which row we should stop on using our define. Then we iterate over the data rows. The srow variable is used to make sure each table row is striped properly. The code for the data cells is very similar. We set the style for the row, open the cell, write the cell text, close the cell and close the row. When we are completely done we close the table using the HTMLTableClose function. Now we can add a footer after each table saying what “page” we are on. We use the HTMLSetBlockStyle function to reduce the font size and the HTMLAddPara to add the text. Then reset the block style and increase the page count before looping again.
Now that all the data has been added to the HTML file we can write it all out.
ProgressClose();
HTMLSetBlockStyle(h, "font-size: 80%");
footer = "Generated: " + FormatDate(GetLocalTime(), DS_MONTH_DAY_YEAR | DS_DATE_AT_TIME | DS_HHMMSS_12 | DS_INITIAL);
HTMLAddPara(h, footer);
HTMLAddFoot(h);
HTMLWriterToFile(h, fnDest);
RunProgram(fnDest);
}
We close the progress window and then add a footer to the entire document that says when the HTML was generated. We tell the HTML Writer to add the appropriate HTML closing tags and then write the whole thing to our file using the HTMLWriterToFile function. Lastly we use RunProgram to launch the document to the default web browser to see what we got.
As you can see the HTML Writer object allows use to quickly create HTML code without the use of templates or hardcoded HTML tagging. I didn’t use it for this blog but the HTML Writer class also supports writing tables with random access support. This means instead of writing rows and columns you can simply write cells using x and y positions. This can be used to write unsorted data in a sorted fashion. Whatever format your data is in, you can use Legato to help get it into another format for EDGAR, for a website, or even for catching up on stats for America’s favorite pastime.
David Theis has been developing software for Windows operating systems for over fifteen years. He has a Bachelor of Sciences in Computer Science from the Rochester Institute of Technology and co-founded Novaworks in 2006. He is the Vice President of Development and is one of the primary developers of GoFiler, a financial reporting software package designed to create and file EDGAR XML, HTML, and XBRL documents to the U.S. Securities and Exchange Commission. |
Additional Resources
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato