The SEC recently updated the requirements for what information should be included on a number of the cover pages for their forms. GoFiler already has templates for a lot of common forms like the 8-K, 10-Q, and 10-K, but I had an idea that would eventually make creating these cover pages even easier than they already are by putting the power of Legato to work. The current templates simply create a blank cover page with empty areas to enter data in the Page View edit window. My script will prompt the user to provide the information in a simple form when they create the document and automatically place that data on the cover page.
Friday, May 31. 2019
LDC #138: Using Legato to Ease the Creation of 8-K Cover Pages
If you have not seen the new cover page requirements, you can find them in our breakdown sheet. This sheet has a focus on the inline XBRL requirements for cover page tagging but also includes information about the changes that were made to the cover pages. Since this is a custom script to add data to a cover page template, you can eventually expand on this script to add Inline XBRL or XDX tags as part of the data inserted into the cover page.
For now, this script will be discussed in two parts. This week, we will create the basic script to create a cover page for an 8-K. Next week, we will expand the script to be fully extensible and add in some more advanced options.
To start, I made a list of requirements for the script. The script must: 1) get data from a user; 2) fill that data into fields on an HTML cover page; and 3) hook into GoFiler’s New function. The last requirement comes with a couple of design limitations but gives us a good way to make our initial script extensible.
GoFiler allows us to customize the “HTML New File with Template” list of templates. This is done by modifying a file “html.csv” in GoFiler’s ‘Templates’ folder. For this script to run, you must add a new line to the bottom of this file, as below:
Form 8-K Legato,Current Report,The current report that announces major events that shareholders should know about.,8kwiz.ls,script
where:
- the first column contains the form name of the template (displayed in the Form Name list on the New File with Template dialog),
- the second column contains the name of the template (displayed in the Name field on the New File with Template dialog),
- the third column contains the description of the template (displayed on the New File with Template dialog),
- the fourth column contains the name and optional path of the script, and
- the fifth column contains the word “script”, indicating that this is a script template.
If you have never modified this .csv file before, you can copy the version from the installation directory and put it into your AppData folder (%appdata%/Novaworks/Templates - you may have to create this folder if it does not exist). This will preserve the original installed templates file, but use your custom file when you run GoFiler from your computer. It also protects your changes from being overwritten when GoFiler updates.
Now that we have the set up for the hook, we need to decide how to get data from the user. The script will need a dialog interface that opens after the new file with template option is selected. Unfortunately, creating custom dialogs can take a lot of time, especially if the information we want to gather is variable depending on form type. Since I eventually want to make this script extensible for form types other than 8-K (Forms 10-Q and 10-K especially), I wanted to make the dialog as extensible as possible as well. For this, I decided to use an editable data control.
The next decision I had to make was to decide how best to get information into an HTML file. I ended up deciding to use ReplaceInString() to replace unique identifiers placed into an HTML template. I’ve created a simple cover page template, which you can download here. Any place information will be required from the user, the template has a keyword based on the name of the XBRL element that will be used to report that information. For example, where the cover page asks for the Date of Report, I put ##&DocumentPeriodEndDate;## in the HTML file. Most of these keywords end up being inside paragraph tags, but there are a couple of exceptions. EntityAddressAddressLine2 and 3 are optional elements. For these I just placed the keyword in the HTML document without a containing HTML block, so we can add the entire HTML tag, or replace our keyword with nothing if the user enters nothing into the dialog.
To keep the script simple, it does not perform any validation of the data the user enters. Validation can be added a later date so users are warned if they enter invalid data into the dialog.
Here’s the script:
/***************************************************************************************************************** Cover Page From Template ------------------------ Revision: 05-31-19 JCK Initial creation Notes: - Part 1 Proof of Concept (c) 2019 Novaworks, LLC. All Rights Reserved. *****************************************************************************************************************/ string DocumentPeriodEndDate; string EntityFileNumber; string EntityRegistrantName; string EntityIncorporationStateCountryCode; string EntityTaxIdentifcationNumber; string EntityAddressAddressLine1; string EntityAddressAddressLine2; string EntityAddressAddressLine3; string EntityAddressCityOrTown; string EntityAddressStateOrProvince; string EntityAddressCountry; string EntityAddressPostalZipCode; string LocalPhoneNumber; string EntityInformationFormerLegalOrRegisteredName; string PreCommencementIssuerTenderOffer; string PreCommencementTenderOffer; string SolicitingMaterial; string WrittenCommunications; string EntityEmergingGrowthCompany; string EntityExTransitionPeriod; string content; string create_content (string name, string options); string run8k (boolean fromHook); int main (); int XDX_load (); int XDX_ok (); int main() { run8k(false); return ERROR_NONE; } string create_content ( string name, string options ) { return run8k(true); } string run8k(boolean fromHook) { DocumentPeriodEndDate = ""; EntityFileNumber = ""; EntityRegistrantName = ""; EntityIncorporationStateCountryCode = ""; EntityTaxIdentifcationNumber = ""; EntityAddressAddressLine1 = ""; EntityAddressAddressLine2 = ""; EntityAddressAddressLine3 = ""; EntityAddressCityOrTown = ""; EntityAddressStateOrProvince = ""; EntityAddressCountry = ""; EntityAddressPostalZipCode = ""; LocalPhoneNumber = ""; EntityInformationFormerLegalOrRegisteredName = ""; PreCommencementIssuerTenderOffer = ""; PreCommencementTenderOffer = ""; SolicitingMaterial = ""; WrittenCommunications = ""; EntityEmergingGrowthCompany = ""; EntityExTransitionPeriod = ""; content = ""; string filename; if (fromHook == false) { filename = BrowseSaveFile("Select 8-K Save Location", "HTML Files (*.htm, *.html)|*.htm;*.html"); if (filename == "") { return ""; } if (GetExtension(filename) != ".htm" && GetExtension(filename) != ".html" && filename != "") { filename += ".htm"; } } int rc; //Start Dialog rc = DialogBox("XDXTemplateWizDlg", "XDX_"); //if (rc == ERROR_CANCEL) { return ERROR_CANCEL; } if (fromHook == false) { AddMessage("DocumentPeriodEndDate : %s", DocumentPeriodEndDate ); AddMessage("EntityFileNumber : %s", EntityFileNumber ); AddMessage("EntityRegistrantName : %s", EntityRegistrantName ); AddMessage("EntityIncorporationStateCountryCode : %s", EntityIncorporationStateCountryCode ); AddMessage("EntityTaxIdentifcationNumber : %s", EntityTaxIdentifcationNumber ); AddMessage("EntityAddressAddressLine1 : %s", EntityAddressAddressLine1 ); AddMessage("EntityAddressAddressLine2 : %s", EntityAddressAddressLine2 ); AddMessage("EntityAddressAddressLine3 : %s", EntityAddressAddressLine3 ); AddMessage("EntityAddressCityOrTown : %s", EntityAddressCityOrTown ); AddMessage("EntityAddressStateOrProvince : %s", EntityAddressStateOrProvince ); AddMessage("EntityAddressCountry : %s", EntityAddressCountry ); AddMessage("EntityAddressPostalZipCode : %s", EntityAddressPostalZipCode ); AddMessage("LocalPhoneNumber : %s", LocalPhoneNumber ); AddMessage("EntityInformationFormerLegalOrRegisteredName : %s", EntityInformationFormerLegalOrRegisteredName ); AddMessage("PreCommencementIssuerTenderOffer : %s", PreCommencementIssuerTenderOffer ); AddMessage("PreCommencementTenderOffer : %s", PreCommencementTenderOffer ); AddMessage("SolicitingMaterial : %s", SolicitingMaterial ); AddMessage("WrittenCommunications : %s", WrittenCommunications ); AddMessage("EntityEmergingGrowthCompany : %s", EntityEmergingGrowthCompany ); AddMessage("EntityExTransitionPeriod : %s", EntityExTransitionPeriod ); } content = FileToString(GetScriptFolder() + "8ktemplate.htm"); content = ReplaceInString(content, "##&DocumentPeriodEndDate;##", DocumentPeriodEndDate); content = ReplaceInString(content, "##&EntityFileNumber;##", EntityFileNumber); if (EntityRegistrantName != "") { content = ReplaceInString(content, "##&EntityRegistrantName;##", EntityRegistrantName); } else { content = ReplaceInString(content, "##&EntityRegistrantName;##", " "); } if (EntityIncorporationStateCountryCode != "") { content = ReplaceInString(content, "##&EntityIncorporationStateCountryCode;##", EntityIncorporationStateCountryCode); } else { content = ReplaceInString(content, "##&EntityIncorporationStateCountryCode;##", " "); } if (EntityTaxIdentifcationNumber != "") { content = ReplaceInString(content, "##&EntityTaxIdentifcationNumber;##", EntityTaxIdentifcationNumber); } else { content = ReplaceInString(content, "##&EntityTaxIdentifcationNumber;##", " "); } content = ReplaceInString(content, "##&EntityAddressAddressLine1;##", EntityAddressAddressLine1); if (EntityAddressAddressLine2 != ""){ content = ReplaceInString(content, "##&EntityAddressAddressLine2;##", "<P STYLE=\"font-size: 10pt; text-align: center; margin-top: 12pt; margin-bottom: 0pt\"><B>" + EntityAddressAddressLine2 + "</B></P>"); } else { content = ReplaceInString(content, "##&EntityAddressAddressLine2;##", ""); } if (EntityAddressAddressLine3 != "") { content = ReplaceInString(content, "##&EntityAddressAddressLine3;##", "<P STYLE=\"font-size: 10pt; text-align: center; margin-top: 12pt; margin-bottom: 0pt\"><B>" + EntityAddressAddressLine3 + "</B></P>"); } else { content = ReplaceInString(content, "##&EntityAddressAddressLine3;##", ""); } if (EntityAddressCityOrTown == "" && EntityAddressStateOrProvince == "" && EntityAddressCountry == "") { content = ReplaceInString(content, "##&EntityAddressCountry;##", " "); } content = ReplaceInString(content, "##&EntityAddressCityOrTown;##", EntityAddressCityOrTown); content = ReplaceInString(content, "##&EntityAddressStateOrProvince;##", EntityAddressStateOrProvince); content = ReplaceInString(content, "##&EntityAddressCountry;##", EntityAddressCountry); content = ReplaceInString(content, "##&EntityAddressPostalZipCode;##", EntityAddressPostalZipCode); if (LocalPhoneNumber != "") { content = ReplaceInString(content, "##&LocalPhoneNumber;##", LocalPhoneNumber); } else { content = ReplaceInString(content, "##&LocalPhoneNumber;##", " "); } if (EntityInformationFormerLegalOrRegisteredName != "") { content = ReplaceInString(content, "##&EntityInformationFormerLegalOrRegisteredName;##", EntityInformationFormerLegalOrRegisteredName); } else { content = ReplaceInString(content, "##&EntityInformationFormerLegalOrRegisteredName;##", " "); } if (IsTrue(PreCommencementIssuerTenderOffer)) { content = ReplaceInString(content, "##&PreCommencementIssuerTenderOffer;##", "☒"); } else { content = ReplaceInString(content, "##&PreCommencementIssuerTenderOffer;##", "☐"); } if (IsTrue(PreCommencementTenderOffer)) { content = ReplaceInString(content, "##&PreCommencementTenderOffer;##", "☒"); } else { content = ReplaceInString(content, "##&PreCommencementTenderOffer;##", "☐"); } if (IsTrue(SolicitingMaterial)) { content = ReplaceInString(content, "##&SolicitingMaterial;##", "☒"); } else { content = ReplaceInString(content, "##&SolicitingMaterial;##", "☐"); } if (IsTrue(WrittenCommunications)) { content = ReplaceInString(content, "##&WrittenCommunications;##", "☒"); } else { content = ReplaceInString(content, "##&WrittenCommunications;##", "☐"); } if (IsTrue(EntityEmergingGrowthCompany)) { content = ReplaceInString(content, "##&EntityEmergingGrowthCompany;##", "☒"); } else { content = ReplaceInString(content, "##&EntityEmergingGrowthCompany;##", "☐"); } if (IsTrue(EntityExTransitionPeriod)) { content = ReplaceInString(content, "##&EntityExTransitionPeriod;##", "☒"); } else { content = ReplaceInString(content, "##&EntityExTransitionPeriod;##", "☐"); } if (fromHook == false) { StringToFile(content, filename); } return content; } #define PVX_OF_LIST 101 #define XDX_TemplateWizDlg 100 #beginresource XDXTemplateWizDlg DIALOGEX 0, 0, 400, 300 STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Wizard" FONT 8, "MS Shell Dlg" { CONTROL "Properties", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 4, 4, 40, 8, 0 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 40, 9, 350, 1, 0 CONTROL "", PVX_OF_LIST, "data_control", LBS_NOTIFY | WS_VSCROLL | WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 18, 378, 250, 0 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 335, 276, 50, 14 } #endresource int XDX_load() { DataControlSetColumnPositions(PVX_OF_LIST, 150, 200); DataControlSetColumnHeadings(PVX_OF_LIST, "Name", "Value"); DataControlSetGridMode(PVX_OF_LIST, DCC_GRID_BOTH); DataControlSetLocalEditMode(PVX_OF_LIST, DCC_EDIT_MODE_AUTO); DataControlSetSelectMode(PVX_OF_LIST, DCC_SELECT_MODE_NONE); DataControlSetDefaultRowHeight(PVX_OF_LIST, 18); DataControlAddString(PVX_OF_LIST, "Date Of Report"); DataControlAddString(PVX_OF_LIST, "Commission File Number"); DataControlAddString(PVX_OF_LIST, "Exact Name of Registrant"); DataControlAddString(PVX_OF_LIST, "State or Other Jurisdiction of Organization"); DataControlAddString(PVX_OF_LIST, "I.R.S. Employer Identification No."); DataControlAddString(PVX_OF_LIST, "Address Line 1"); DataControlAddString(PVX_OF_LIST, "Address Line 2"); DataControlAddString(PVX_OF_LIST, "Address Line 3"); DataControlAddString(PVX_OF_LIST, "City or Town"); DataControlAddString(PVX_OF_LIST, "State or Province"); DataControlAddString(PVX_OF_LIST, "Country of Executive Offices"); DataControlAddString(PVX_OF_LIST, "Zip Code"); DataControlAddString(PVX_OF_LIST, "Telephone Number"); DataControlAddString(PVX_OF_LIST, "\"Former Name, if Changed\""); DataControlAddString(PVX_OF_LIST, "Written communications pursuant to Rule 425 under the Securities Act"); DataControlSetCellType(PVX_OF_LIST, 14, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Soliciting material pursuant to Rule 14a-12 under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 15, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Pre-commencement communications pursuant to Rule 14d-2(b) under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 16, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Pre-commencement communications pursuant to Rule 13e-4(c) under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 17, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Emerging growth company"); DataControlSetCellType(PVX_OF_LIST, 18, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Elected Not to Use Extended Transition Period"); DataControlSetCellType(PVX_OF_LIST, 19, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); int count; count = 0; while (count < 20) { DataControlSetCellState(PVX_OF_LIST, count, 0, FALSE); count++; } return ERROR_NONE; } int XDX_ok() { DocumentPeriodEndDate = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 0, 1)); EntityFileNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 1, 1)); EntityRegistrantName = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 2, 1)); EntityIncorporationStateCountryCode = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 3, 1)); EntityTaxIdentifcationNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 4, 1)); EntityAddressAddressLine1 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 5, 1)); EntityAddressAddressLine2 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 6, 1)); EntityAddressAddressLine3 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 7, 1)); EntityAddressCityOrTown = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 8, 1)); EntityAddressStateOrProvince = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 9, 1)); EntityAddressCountry = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 10, 1)); EntityAddressPostalZipCode = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 11, 1)); LocalPhoneNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 12, 1)); EntityInformationFormerLegalOrRegisteredName = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 13, 1)); PreCommencementIssuerTenderOffer = DataControlGetCellText(PVX_OF_LIST, 14, 1); PreCommencementTenderOffer = DataControlGetCellText(PVX_OF_LIST, 15, 1); SolicitingMaterial = DataControlGetCellText(PVX_OF_LIST, 16, 1); WrittenCommunications = DataControlGetCellText(PVX_OF_LIST, 17, 1); EntityEmergingGrowthCompany = DataControlGetCellText(PVX_OF_LIST, 18, 1); EntityExTransitionPeriod = DataControlGetCellText(PVX_OF_LIST, 19, 1); return ERROR_NONE; }
The first thing you’ll notice is a large number of defined strings at the top of the file. This is where we are going to store all of our strings. Once we extend this script to take care of more than one form type, the plan will be to move these into some sort of structure that we can read from in order to construct the dialog and hold responses. For now everything is individual variables specific to Form 8-K.
int main() { run8k(false); return ERROR_NONE; } string create_content ( string name, string options ) { return run8k(true); }
Our main function is here so that we can test our script through the IDE. We use it to call our run8k() function with a false boolean. This tells the run8k() function that we are running through the IDE rather than through the HTML from Template function. We use this to modify the way that the script handles output. I wanted to make sure that a developer could run the script from the IDE safely because it is much easier to test small changes through the IDE.
The function create_content() is called by the “New HTML from Template” function when our template is selected. It expects a return value of a string (which should contain the full HTML template we created) that will be placed in a blank HTML Page View edit window. The name variable is the name that we put in the .csv file, which will later allow us to differentiate between form types. The options variable will be the string that we put in the options column of the .csv file. For us it’s just going to be “script” because we are not using any options.
Since all we have right now is the 8-K cover page, we are always calling the run8k() function without checking the name and telling the function that it is being run from the “New HTML from Template” function.
string run8k(boolean fromHook) { DocumentPeriodEndDate = ""; EntityFileNumber = ""; EntityRegistrantName = ""; EntityIncorporationStateCountryCode = ""; EntityTaxIdentifcationNumber = ""; EntityAddressAddressLine1 = ""; EntityAddressAddressLine2 = ""; EntityAddressAddressLine3 = ""; EntityAddressCityOrTown = ""; EntityAddressStateOrProvince = ""; EntityAddressCountry = ""; EntityAddressPostalZipCode = ""; LocalPhoneNumber = ""; EntityInformationFormerLegalOrRegisteredName = ""; PreCommencementIssuerTenderOffer = ""; PreCommencementTenderOffer = ""; SolicitingMaterial = ""; WrittenCommunications = ""; EntityEmergingGrowthCompany = ""; EntityExTransitionPeriod = ""; content = ""; string filename; if (fromHook == false) { filename = BrowseSaveFile("Select 8-K Save Location", "HTML Files (*.htm, *.html)|*.htm;*.html"); if (filename == "") { return ""; } if (GetExtension(filename) != ".htm" && GetExtension(filename) != ".html" && filename != "") { filename += ".htm"; } } int rc; //Start Dialog rc = DialogBox("XDXTemplateWizDlg", "XDX_");
The run8k() function starts by setting all of our global content variables to blank strings. We then check to see if we were called from the IDE or not. If we were called from the IDE we use BrowseSaveFile() to ask the user to choose a location where they want to save the file. If the user does not choose a location we are going to bail out as we do not have a place to put our data at that point. Otherwise we check the extension of the filename chosen using the GetExtension() function and, provided that the user entered something, we make sure that the extension is an HTML extension.
We then open our dialog using the DialogBox() function. So let’s head down into our dialog:
#define PVX_OF_LIST 101 #define XDX_TemplateWizDlg 100 #beginresource XDXTemplateWizDlg DIALOGEX 0, 0, 400, 300 STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Wizard" FONT 8, "MS Shell Dlg" { CONTROL "Properties", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 4, 4, 40, 8, 0 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 40, 9, 350, 1, 0 CONTROL "", PVX_OF_LIST, "data_control", LBS_NOTIFY | WS_VSCROLL | WS_BORDER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 18, 378, 250, 0 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 335, 276, 50, 14 } #endresource
Our dialog is very simple: only four controls and half of those are static. We have the label, the line above our data control, the data control, and an OK button. Our setup is going to be a bit more complex:
int XDX_load() { DataControlSetColumnPositions(PVX_OF_LIST, 150, 200); DataControlSetColumnHeadings(PVX_OF_LIST, "Name", "Value"); DataControlSetGridMode(PVX_OF_LIST, DCC_GRID_BOTH); DataControlSetLocalEditMode(PVX_OF_LIST, DCC_EDIT_MODE_AUTO); DataControlSetSelectMode(PVX_OF_LIST, DCC_SELECT_MODE_NONE); DataControlSetDefaultRowHeight(PVX_OF_LIST, 18); DataControlAddString(PVX_OF_LIST, "Date Of Report"); DataControlAddString(PVX_OF_LIST, "Commission File Number"); DataControlAddString(PVX_OF_LIST, "Exact Name of Registrant"); DataControlAddString(PVX_OF_LIST, "State or Other Jurisdiction of Organization"); DataControlAddString(PVX_OF_LIST, "I.R.S. Employer Identification No."); DataControlAddString(PVX_OF_LIST, "Address Line 1"); DataControlAddString(PVX_OF_LIST, "Address Line 2"); DataControlAddString(PVX_OF_LIST, "Address Line 3"); DataControlAddString(PVX_OF_LIST, "City or Town"); DataControlAddString(PVX_OF_LIST, "State or Province"); DataControlAddString(PVX_OF_LIST, "Country of Executive Offices"); DataControlAddString(PVX_OF_LIST, "Zip Code"); DataControlAddString(PVX_OF_LIST, "Telephone Number"); DataControlAddString(PVX_OF_LIST, "\"Former Name, if Changed\""); DataControlAddString(PVX_OF_LIST, "Written communications pursuant to Rule 425 under the Securities Act"); DataControlSetCellType(PVX_OF_LIST, 14, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Soliciting material pursuant to Rule 14a-12 under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 15, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Pre-commencement communications pursuant to Rule 14d-2(b) under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 16, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Pre-commencement communications pursuant to Rule 13e-4(c) under the Exchange Act"); DataControlSetCellType(PVX_OF_LIST, 17, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Emerging growth company"); DataControlSetCellType(PVX_OF_LIST, 18, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); DataControlAddString(PVX_OF_LIST, "Elected Not to Use Extended Transition Period"); DataControlSetCellType(PVX_OF_LIST, 19, 1, DS_CF_DISPLAY_CHECKBOX_AUTO); int count; count = 0; while (count < 20) { DataControlSetCellState(PVX_OF_LIST, count, 0, FALSE); count++; } return ERROR_NONE; }
There are a lot of options selected here because we are changing a lot of the default data control options. First off we set our column positions using DataControlSetColumnPositions(). This tells our dialog we want two columns. Next we set the headings of our columns using DataControlSetColumnHeadings(). We need a Name and a Value column, one where we specify what data we are looking for and the other for the user to fill in the data. Next we set the grid mode with DataControlSetGridMode() as we want to have both horizontal and vertical gridlines visible so it looks more like a spreadsheet. The hope here is that it is more intuitive to what the user is supposed to do if we make it appear more like a spreadsheet.
Next we set the edit mode using DataControlSetLocalEditMode(). We set the mode so that the user can edit the fields in the control. By default, everything is read only but that does not work for our needs. Next we disable the normal selection mode by using DataControlSetSelectMode(). The user is not selecting a row but instead is filling out data. Having rows being selected upon interaction would only serve to confuse the user. Finally, we change the height of the rows to be a little bit taller than the default using the DataControlSetDefaultRowHeight() function. The extra space makes it a lot easier to read when you’re filling out a grid of values.
We then go through and add a label to the first column of each row. This is all of the information that we are asking from the user. We do this by using the DataControlAddString() function. This is kind of a cheesy trick here as we are only passing a string and not CSV to the function. This effectively fills the first column but nothing else. What this demonstrates is that you do not need to pass a blank value for the second column, you can just ignore it completely and it will be left blank. The other thing of note here is that we are using DataControlSetCellType() on the last six rows to turn the second column into a checkbox that can be modified. Since on the cover page all of these values are checkboxes it makes sense to limit the user here to only being able to check and uncheck a box. Finally we loop through all of our rows and set the state of the first column to be disabled using the DataControlSetCellState() function. There is no effective way to set individual cells as read only, so we get around this by disabling the cells. This is not necessarily ideal as the disabled cells appear gray, but it does very clearly make the cells unable to be edited.
int XDX_ok() { DocumentPeriodEndDate = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 0, 1)); EntityFileNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 1, 1)); EntityRegistrantName = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 2, 1)); EntityIncorporationStateCountryCode = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 3, 1)); EntityTaxIdentifcationNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 4, 1)); EntityAddressAddressLine1 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 5, 1)); EntityAddressAddressLine2 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 6, 1)); EntityAddressAddressLine3 = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 7, 1)); EntityAddressCityOrTown = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 8, 1)); EntityAddressStateOrProvince = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 9, 1)); EntityAddressCountry = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 10, 1)); EntityAddressPostalZipCode = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 11, 1)); LocalPhoneNumber = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 12, 1)); EntityInformationFormerLegalOrRegisteredName = UTFToEntities(DataControlGetCellText(PVX_OF_LIST, 13, 1)); PreCommencementIssuerTenderOffer = DataControlGetCellText(PVX_OF_LIST, 14, 1); PreCommencementTenderOffer = DataControlGetCellText(PVX_OF_LIST, 15, 1); SolicitingMaterial = DataControlGetCellText(PVX_OF_LIST, 16, 1); WrittenCommunications = DataControlGetCellText(PVX_OF_LIST, 17, 1); EntityEmergingGrowthCompany = DataControlGetCellText(PVX_OF_LIST, 18, 1); EntityExTransitionPeriod = DataControlGetCellText(PVX_OF_LIST, 19, 1); return ERROR_NONE; }
When the OK button is clicked we go through and get the text that is in all of the second column cells and store them in their proper variable place using the DataControlGetCellText() function. The difference here is that we need to store the string values in a way that allows us to put these strings into an HTML document. This means that we have to take the raw values from the data control and put in character entities if necessary. We can use the UTFToEntities() function to do the work for us. I decided to store the entities in the local variables rather than converting them as we put the variables into the HTML document because it was easier to do it all at once and does not have any negative consequences. That is all the dialog code we have. Next we move back to our run8k() function and pick up after the dialog.
if (fromHook == false) { AddMessage("DocumentPeriodEndDate : %s", DocumentPeriodEndDate ); AddMessage("EntityFileNumber : %s", EntityFileNumber ); AddMessage("EntityRegistrantName : %s", EntityRegistrantName ); AddMessage("EntityIncorporationStateCountryCode : %s", EntityIncorporationStateCountryCode ); AddMessage("EntityTaxIdentifcationNumber : %s", EntityTaxIdentifcationNumber ); AddMessage("EntityAddressAddressLine1 : %s", EntityAddressAddressLine1 ); AddMessage("EntityAddressAddressLine2 : %s", EntityAddressAddressLine2 ); AddMessage("EntityAddressAddressLine3 : %s", EntityAddressAddressLine3 ); AddMessage("EntityAddressCityOrTown : %s", EntityAddressCityOrTown ); AddMessage("EntityAddressStateOrProvince : %s", EntityAddressStateOrProvince ); AddMessage("EntityAddressCountry : %s", EntityAddressCountry ); AddMessage("EntityAddressPostalZipCode : %s", EntityAddressPostalZipCode ); AddMessage("LocalPhoneNumber : %s", LocalPhoneNumber ); AddMessage("EntityInformationFormerLegalOrRegisteredName : %s", EntityInformationFormerLegalOrRegisteredName ); AddMessage("PreCommencementIssuerTenderOffer : %s", PreCommencementIssuerTenderOffer ); AddMessage("PreCommencementTenderOffer : %s", PreCommencementTenderOffer ); AddMessage("SolicitingMaterial : %s", SolicitingMaterial ); AddMessage("WrittenCommunications : %s", WrittenCommunications ); AddMessage("EntityEmergingGrowthCompany : %s", EntityEmergingGrowthCompany ); AddMessage("EntityExTransitionPeriod : %s", EntityExTransitionPeriod ); } content = FileToString(GetScriptFolder() + "8ktemplate.htm"); content = ReplaceInString(content, "##&DocumentPeriodEndDate;##", DocumentPeriodEndDate); content = ReplaceInString(content, "##&EntityFileNumber;##", EntityFileNumber);
If we are running from the IDE, our version of debug mode, we are going to print out the values that were returned by the dialog by using AddMessage(). This allows us to see the values and make sure they are coming back from the dialog correctly. We then use FileToString() to pick up our HTML template. We assume that it is saved in the same place as the script. If this is not the case, you can hard code the template location. Then we enter the main portion of modifying the template. We take our content string and use ReplaceInString() to find our element name surrounded by scratch marks (or hashtag symbols, if you prefer) and replace that with the value from our local variable.
In this script we are using ReplaceInString() because it is easy and efficient from a programmer’s prospective. Keep in mind, however, that ReplaceInString() is a fairly expensive call. Using it only 20-30 times like we are is okay as our file is fairly small, but if you have a massive file the script will take longer. If we wanted to make this more programmatically efficient we would traverse the file ourselves while searching for our replacement marks. However, since the practical difference for our current use case is imperceptibly small we are fine with using ReplaceInString().
if (EntityRegistrantName != "") { content = ReplaceInString(content, "##&EntityRegistrantName;##", EntityRegistrantName); } else { content = ReplaceInString(content, "##&EntityRegistrantName;##", " "); } if (EntityIncorporationStateCountryCode != "") { content = ReplaceInString(content, "##&EntityIncorporationStateCountryCode;##", EntityIncorporationStateCountryCode); } else { content = ReplaceInString(content, "##&EntityIncorporationStateCountryCode;##", " "); } if (EntityTaxIdentifcationNumber != "") { content = ReplaceInString(content, "##&EntityTaxIdentifcationNumber;##", EntityTaxIdentifcationNumber); } else { content = ReplaceInString(content, "##&EntityTaxIdentifcationNumber;##", " "); } content = ReplaceInString(content, "##&EntityAddressAddressLine1;##", EntityAddressAddressLine1); if (EntityAddressAddressLine2 != ""){ content = ReplaceInString(content, "##&EntityAddressAddressLine2;##", "<P STYLE=\"font-size: 10pt; text-align: center; margin-top: 12pt; margin-bottom: 0pt\"><B>" + EntityAddressAddressLine2 + "</B></P>"); } else { content = ReplaceInString(content, "##&EntityAddressAddressLine2;##", ""); } if (EntityAddressAddressLine3 != "") { content = ReplaceInString(content, "##&EntityAddressAddressLine3;##", "<P STYLE=\"font-size: 10pt; text-align: center; margin-top: 12pt; margin-bottom: 0pt\"><B>" + EntityAddressAddressLine3 + "</B></P>"); } else { content = ReplaceInString(content, "##&EntityAddressAddressLine3;##", ""); } if (EntityAddressCityOrTown == "" && EntityAddressStateOrProvince == "" && EntityAddressCountry == "") { content = ReplaceInString(content, "##&EntityAddressCountry;##", " "); } content = ReplaceInString(content, "##&EntityAddressCityOrTown;##", EntityAddressCityOrTown); content = ReplaceInString(content, "##&EntityAddressStateOrProvince;##", EntityAddressStateOrProvince); content = ReplaceInString(content, "##&EntityAddressCountry;##", EntityAddressCountry); content = ReplaceInString(content, "##&EntityAddressPostalZipCode;##", EntityAddressPostalZipCode); if (LocalPhoneNumber != "") { content = ReplaceInString(content, "##&LocalPhoneNumber;##", LocalPhoneNumber); } else { content = ReplaceInString(content, "##&LocalPhoneNumber;##", " "); } if (EntityInformationFormerLegalOrRegisteredName != "") { content = ReplaceInString(content, "##&EntityInformationFormerLegalOrRegisteredName;##", EntityInformationFormerLegalOrRegisteredName); } else { content = ReplaceInString(content, "##&EntityInformationFormerLegalOrRegisteredName;##", " "); }
We continue onward through our ReplaceInString() function calls. There are a couple things to highlight in here. Most of the scratchmark elements are the only content with a paragraph tag in our HTML template. This means if the user does not put anything in the dialog we need to add a non-breaking space to the document to hold the paragraph. The exception to this are the two EntityAddressAddressLine elements, 2 and 3. These, if they exist, need to have the entire paragraph tag entered into the document, and if they do not exist they are replaced with nothing. This is so that we don’t have extra blank paragraphs that we would have to deal with in the template. It ends up making the optional elements look nicer when they do not exist.
if (IsTrue(PreCommencementIssuerTenderOffer)) { content = ReplaceInString(content, "##&PreCommencementIssuerTenderOffer;##", "☒"); } else { content = ReplaceInString(content, "##&PreCommencementIssuerTenderOffer;##", "☐"); } if (IsTrue(PreCommencementTenderOffer)) { content = ReplaceInString(content, "##&PreCommencementTenderOffer;##", "☒"); } else { content = ReplaceInString(content, "##&PreCommencementTenderOffer;##", "☐"); } if (IsTrue(SolicitingMaterial)) { content = ReplaceInString(content, "##&SolicitingMaterial;##", "☒"); } else { content = ReplaceInString(content, "##&SolicitingMaterial;##", "☐"); } if (IsTrue(WrittenCommunications)) { content = ReplaceInString(content, "##&WrittenCommunications;##", "☒"); } else { content = ReplaceInString(content, "##&WrittenCommunications;##", "☐"); } if (IsTrue(EntityEmergingGrowthCompany)) { content = ReplaceInString(content, "##&EntityEmergingGrowthCompany;##", "☒"); } else { content = ReplaceInString(content, "##&EntityEmergingGrowthCompany;##", "☐"); } if (IsTrue(EntityExTransitionPeriod)) { content = ReplaceInString(content, "##&EntityExTransitionPeriod;##", "☒"); } else { content = ReplaceInString(content, "##&EntityExTransitionPeriod;##", "☐"); } if (fromHook == false) { StringToFile(content, filename); } return content; }
The final section of our content are the six checkboxes. The values that we can get from the dialog are “1”, “0”, or “”. In this case we only care if the value is 1, but since this is a string we use the IsTrue() function to check and see if the value is “1”. If it is, we are replacing our keywords with “☒”, which is the Unicode character entity for a checked ballot box. Technically it is actually a ☒ rather than a ☑ (“☑”), but either is acceptable to the SEC if you feel the need to change it for your own use. If the value reported from the dialog is not true (the box is not checked) we are replacing our mark with “☐”, or the ☐ character.
We then check to see if we are running from the IDE. If we are, we save our contents out to the filename that the user chose at the beginning of this process by using the StringToFile() function.
Finally we return our content back to the create_content() function, which will send our content back to GoFiler and put that content into the opened Page View.
So, looking forward, what do we have left to do? First and foremost, we need to come up with a way to fill out the last of the information from the 8-K cover page, namely the securities list. This is more complex as it is not a one to one relationship, and so it will require another dialog or some more coding on our current dialog. Secondly we want to refactor some of our code to make it extensible so we can add more form types. While each form type will require a decent amount of specific code writing, we should be able to make it so that we can use this process and dialog for each form type, which makes the end result a lot less messy. Next week we will be making those changes and a few others in an attempt to make this a bit more user friendly. See you then!
Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013. |
Additional Resources