• Solutions
    • FERC XBRL Reporting
    • FDTA Financial Reporting
    • SEC Compliance
    • Windows Clipboard Management
    • Legato Scripting
  • Products
    • GoFiler Suite
    • XBRLworks
    • SEC Exhibit Explorer
    • SEC Extractor
    • Clipboard Scout
    • Legato
  • Education
    • Training
    • SEC and EDGAR Compliance
    • Legato Developers
  • Blog
  • Support
  • Skip to blog entries
  • Skip to archive page
  • Skip to right sidebar

Friday, December 14. 2018

LDC #114: Bulk Filings Part 1

For this week’s blog post we are going to start creating a script that will allow users to create a bulk filing for submission to EDGAR. In order to accomplish this we are going to split the blog into three parts: interface, project file processing, and finally the submission creation. Since this is the first blog in the series we are going to start with the interface.


Our interface needs a few things. The user will need to be able to specify a list of files to add to the bulk submission. They will also need to specify the name of the resulting XML file. We could just run the BrowseOpenFiles function and then the BrowseSaveFile function in succession but that isn’t very cohesive and the user would need to know everything that is required of him or her before running the script. Additionally, it would mean that all the files need to be selected from the same location. A dialog is a much better solution for this operation to allow the user flexibility in the workflow and process.


The first step of designing a dialog involves identifying what controls and what types of controls we need. For the list of files that will be added to the bulk submission, we can use a list box control. To add files to the list, we can have an “add” button that runs the BrowseOpenFiles function. If the user accidentally adds the wrong file, a button to remove a file would be nice. To save the bulk submission, we need a text edit control to store the name of the file and a browse button so the user doesn’t need to type in the full path to the location that submission will be created.


The code for the completed dialog could look something like this:



#define DC_BULK         201
#define DC_LIST         301
#define DC_ADD          101
#define DC_REMOVE       102
#define DC_BROWSE       103

BulkDialog DIALOGEX 0, 0, 344, 190
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Bulk Filing"
FONT 8, "MS Shell Dlg", 400, 0
{
 CONTROL "Files", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 4, 20, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 26, 9, 314, 1, 0
 CONTROL "", DC_LIST, "listbox", LBS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 12, 16, 276, 120, 0
 CONTROL "&Add...", DC_ADD, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 16, 45, 12, 0
 CONTROL "&Remove", DC_REMOVE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 31, 45, 12, 0
 CONTROL "Submission", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 137, 42, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 48, 142, 292, 1, 0
 CONTROL "&Bulk Submission File:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 150, 72, 8, 0
 CONTROL "", DC_BULK, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 88, 148, 200, 12
 CONTROL "Browse...", DC_BROWSE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 148, 45, 12, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 166, 335, 1, 0
 CONTROL "&Create", IDOK, "BUTTON", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 232, 172, 50, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 287, 172, 50, 14
}


This code will create a dialog that follows Novaworks’ dialog design specification, so it’s going to look like the other dialogs in GoFiler:


Picture of example dialog


You can design your own dialog to your own specification but make sure to include the controls discussed above because we will be adding the supporting code for those controls next.


Since this script is going to be built upon in another blog post, we need to make sure the files list is available as a global variable. So we can start off with globals and function definitions. As a reminder this script on its own will not do anything other than display a functional dialog. All the processing after the dialog is closed will be added in a later post.



//
// Function Definitions and Globals
// --------------------------------

int     bd_load                 ();
void    bd_set_state            ();
int     bd_action               (int id, int action);
int     bd_validate             ();

string  files[];
string  bulk;


For our functions we need to define several dialog handlers. The load function will handle loading the initial dialog state. The set state function will handle setting the state of the dialog controls after user actions. The action function handles all the user interaction with the dialog. Finally, the validate function will check the user supplied data, and, if the data is valid, save the data to our global variables.


We also have our two global variables. The files variable represents the list of files to be added to the bulk submission. The bulk variable is the name of the resulting bulk submission XML file. Pretty straight forward.


We also need a few defines for the dialog controls as well as a couple string constants for later. The dialog control defines were shown in the above dialog code to show a complete dialog but I moved them here when coding the script.



//
// Dialog and Defines
// --------------------------------

#define DC_BULK         201
#define DC_LIST         301
#define DC_ADD          101
#define DC_REMOVE       102
#define DC_BROWSE       103

#define ADD_FILTER      "All Projects|*.gfp;*.eis;*.xml;&GoFiler Projects|*.gfp&EDGAR Internet Submissions|*.eis&XML Submission Files|*.xml&All Files *.*|*.*"
#define SAVE_FILTER     "XML Submission Files|*.xml&All Files *.*|*.*"


As stated above the first set of defines are the identifiers for the dialog controls. The second set are string defines we can use as filters for the browse functions. These defines give the BrowseOpenFiles and BrowseSaveFile functions a little more polish as the user is automatically filtered to the appropriate types of files. Making the defines means if we wanted to add support for other project formats at a later date we only need to change the define.


Now that all the easy stuff is out of the way let’s start working on the dialog functions. A logical place to start is the bd_load function.



int bd_load() {
    int         ix, mx;

    // Load List
    mx = ArrayGetAxisDepth(files);
    for (ix = 0; ix < mx; ix++) {
      ListBoxAddItem(DC_LIST, files[ix]);
      }

    EditSetText(DC_BULK, bulk);
    bd_set_state();
    return ERROR_NONE;
    }


This function does a little more than it needs to for our script. It loads the dialog’s list of files from our global variable as well as the bulk file name. Since all our script does is open this dialog all those global variables are going to be empty. However, by adding these few lines of code we allow for expanded functionality. If we wanted to add logic to fill the list or name from settings files or based on the active window in the application now the dialog would start with the list already entered and allow the user to edit it instead.


In order to accomplish this we use ArrayGetAxisDepth and a simple for loop to add each string from the files list to the list box. The ListBoxAddItem function simply adds the file to the end of the list. Then we use the EditSetText function to make sure the bulk variable is displayed in the DC_BULK text control. Finally we call the bd_set_state function. This last step is important since regardless of the data we did or didn’t load to the dialog we need to make sure the dialog buttons reflect what actions the user is able to perform.


Since we just wrote code that calls the bd_set_state function let’s take a look at that function now.



void bd_set_state() {
    int         fx;

    fx = ListBoxGetSelectIndex(DC_LIST);
    if (fx >= 0) {
      ControlEnable(DC_REMOVE);
      }
    else {
      ControlDisable(DC_REMOVE);
      }
    }


Since all of the dialog controls are available at all times except the remove button this function checks if the remove button can be used. This button isn’t available unless there is a file selected in the file list. So we use the ListBoxGetSelectIndex function to check for a selection. If there is no selection we disable the button; otherwise we enable the button. With our logic for enabling controls out of the way we can move to the bd_action function.



int bd_action(int id, int action) {

    string      tba[];
    string      path;
    int         rc, ix, mx;

    switch (id) {
      case DC_ADD:
        tba = BrowseOpenFiles("Select Projects", ADD_FILTER);
        if (GetLastError() == ERROR_CANCEL) {
          break;
          }
        mx = ArrayGetAxisDepth(tba);
        for (ix = 0; ix < mx; ix++) {
          rc = ListBoxFindItem(DC_LIST, tba[ix]); 
          if (IsError(rc)) {
            ListBoxAddItem(DC_LIST, tba[ix]);
            }
          }
        bd_set_state();
        break;
      case DC_REMOVE:
        rc = ListBoxGetSelectIndex(DC_LIST);
        if (rc >= 0) {
          ListBoxDeleteItem(DC_LIST, rc);
          while (rc >= ListBoxGetItemCount(DC_LIST)) {
            rc--;
            }
          if (rc >= 0) {
            ListBoxSetSelectIndex(DC_LIST, rc);
            }
          }
        bd_set_state();
        break;
      case DC_BROWSE:
        path = EditGetText(DC_BULK);
        path = BrowseSaveFile("Save Bulk Submission", SAVE_FILTER, path);
        if (GetLastError() == ERROR_CANCEL) {
          break;
          }
        EditSetText(DC_BULK, path);
        break;
      case DC_LIST:
        if (action == LBN_SELCHANGE) {
          bd_set_state();
          }
        break;
      }
    return ERROR_NONE;
    }


This function controls the user interactions with the dialog. We need to add actions for several of the controls. The most obvious actions are the button controls so we can start there.


The DC_ADD button allows the user to add files to the file list. So we run the BrowseOpenFiles function to get a list of files. If the user canceled the dialog we immediately stop processing as there is nothing else to do. Otherwise, we iterate over the files in this new list and then check if they are already our current list using the ListBoxFindItem function. We don’t want to allow the user to accidentally add the same file to the submission twice as it would result in a duplicate filing. If the file was not in the list we add it to the list using ListBoxAddItem. Lastly, we run our bd_set_state function. This is not required given that adding items to the list does not change whether the remove button is available. However, it is a good practice to add this sort of logic in case more controls are added at a later date that do require state changes based on the number of items in the list. An example would be a label that contains the number of files in the submission.


Let’s move to the remove button. The logic for this button is pretty simple: we get the current selection; and if there is one we remove the item from the list using the ListBoxDeleteItem. Usually when you delete the selected item in a list box, the selected item resets. This means the user would need to click on the list box again to delete another item. As that is kind of onerous, we can automatically select the next file in the list or the last item if we are at the end of the list. This means the user can just repeatedly hit remove to quickly empty the list.


The last button is the browse button. For this we can get the current name, if any, using the EditGetText function and then call the BrowseSaveFile function. Once again if the user hits cancel we want to stop processing. Otherwise we can use the EditSetText function to update the control on screen.


Since we dealing with a list we also need to add an action for when the user selects something. This action is going to run our bd_set_state function so the remove button becomes available if a selection is made. For the list box we make sure we check the action variable as list boxes tend to send many notifications and we don’t need our bd_set_state to be called for all of them.


Now we have a dialog with some functionality to it. But there is one major component missing, which is the validate function. Without this function, when the user presses the Create button, there is no error checking and the data is never saved to our global variables. So let’s take a look at the bd_validate function.



int bd_validate() {

    string      tba[];
    string      name;

    // Check List
    tba = ListBoxGetArray(DC_LIST);
    if (ArrayGetAxisDepth(tba) == 0) {
      MessageBox('x', "Submission must have at least one file.");
      return ERROR_SOFT | DC_LIST;
      }

    // Check Name
    name = EditGetText(DC_BULK);
    if (name == "") {
      MessageBox('x', "Bulk Submission File is a required field.");
      return ERROR_SOFT | DC_LIST;
      }
    
    // Save Values
    bulk = name;
    files = tba;
    return ERROR_NONE;
    }


It is a good practice to not edit the global variables until the entire dialog has been validated. For a script like this one, it is less important. But, take this example: we updated the list of files and the user forgets to fill in the bulk name. That global variable now has new data in it. Then the user hits the Cancel button and the script saves the file list for next time. Now, we have allowed the user to change the state of the script by hitting the Cancel button, yikes! Cancel should mean leave with no changes! So in general, it is better to validate everything and then store the results.


We start by using the ListBoxGetArray function which nicely gives us the entire list box as a string array. If that array is empty we put up a message box and exit the function with an error code. This stops the dialog from closing and sets the control focus to the offending control. Then we use the EditGetText function to get the name of the submission XML file. If the name is empty we complain as well. We could also add further checks here but for now this validation is acceptable. Lastly, we save our local data over the global data thus updating the script’s state with the user’s changes.


Here is the entire script with an added main function so we can test out the dialog:



//
// Function Definitions and Globals
// --------------------------------

int     bd_load                 ();
void    bd_set_state            ();
int     bd_action               (int id, int action);
int     bd_validate             ();

string  files[];
string  bulk;


int main() {
    DialogBox("BulkDialog", "bd_");
    return 0;
    }

//
// Dialog and Defines
// --------------------------------

#define DC_BULK         201
#define DC_LIST         301
#define DC_ADD          101
#define DC_REMOVE       102
#define DC_BROWSE       103

#define ADD_FILTER      "All Projects|*.gfp;*.eis;*.xml;&GoFiler Projects|*.gfp&EDGAR Internet Submissions|*.eis&XML Submission Files|*.xml&All Files *.*|*.*"
#define SAVE_FILTER     "XML Submission Files|*.xml&All Files *.*|*.*"


int bd_load() {
    int         ix, mx;

    // Load List
    mx = ArrayGetAxisDepth(files);
    for (ix = 0; ix < mx; ix++) {
      ListBoxAddItem(DC_LIST, files[ix]);
      }

    EditSetText(DC_BULK, bulk);
    bd_set_state();
    return ERROR_NONE;
    }

void bd_set_state() {
    int         fx;

    fx = ListBoxGetSelectIndex(DC_LIST);
    if (fx >= 0) {
      ControlEnable(DC_REMOVE);
      }
    else {
      ControlDisable(DC_REMOVE);
      }
    }


int bd_action(int id, int action) {

    string      tba[];
    string      path;
    int         rc, ix, mx;

    switch (id) {
      case DC_ADD:
        tba = BrowseOpenFiles("Select Projects", ADD_FILTER);
        if (GetLastError() == ERROR_CANCEL) {
          break;
          }
        mx = ArrayGetAxisDepth(tba);
        for (ix = 0; ix < mx; ix++) {
          rc = ListBoxFindItem(DC_LIST, tba[ix]);
          if (IsError(rc)) {
            ListBoxAddItem(DC_LIST, tba[ix]);
            }
          }
        bd_set_state();
        break;
      case DC_REMOVE:
        rc = ListBoxGetSelectIndex(DC_LIST);
        if (rc >= 0) {
          ListBoxDeleteItem(DC_LIST, rc);
          while (rc >= ListBoxGetItemCount(DC_LIST)) {
            rc--;
            }
          if (rc >= 0) {
            ListBoxSetSelectIndex(DC_LIST, rc);
            }
          }
        bd_set_state();
        break;
      case DC_BROWSE:
        path = EditGetText(DC_BULK);
        path = BrowseSaveFile("Save Bulk Submission", SAVE_FILTER, path);
        if (GetLastError() == ERROR_CANCEL) {
          break;
          }
        EditSetText(DC_BULK, path);
        break;
      case DC_LIST:
        if (action == LBN_SELCHANGE) {
          bd_set_state();
          }
        break;
      }
    return ERROR_NONE;
    }

int bd_validate() {

    string      tba[];
    string      name;

    // Check List
    tba = ListBoxGetArray(DC_LIST);
    if (ArrayGetAxisDepth(tba) == 0) {
      MessageBox('x', "Submission must have at least one file.");
      return ERROR_SOFT | DC_LIST;
      }

    // Check Name
    name = EditGetText(DC_BULK);
    if (name == "") {
      MessageBox('x', "Bulk Submission File is a required field.");
      return ERROR_SOFT | DC_LIST;
      }

    // Save Values
    bulk = name;
    files = tba;
    return ERROR_NONE;
    }


#beginresource

BulkDialog DIALOGEX 0, 0, 344, 190
EXSTYLE WS_EX_DLGMODALFRAME
STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Bulk Filing"
FONT 8, "MS Shell Dlg", 400, 0
{
 CONTROL "Files", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 4, 20, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 26, 9, 314, 1, 0
 CONTROL "", DC_LIST, "listbox", LBS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 12, 16, 276, 120, 0
 CONTROL "&Add...", DC_ADD, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 16, 45, 12, 0
 CONTROL "&Remove", DC_REMOVE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 31, 45, 12, 0
 CONTROL "Submission", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 137, 42, 8, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 48, 142, 292, 1, 0
 CONTROL "&Bulk Submission File:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 12, 150, 72, 8, 0
 CONTROL "", DC_BULK, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 88, 148, 200, 12
 CONTROL "Browse...", DC_BROWSE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 292, 148, 45, 12, 0
 CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 166, 335, 1, 0
 CONTROL "&Create", IDOK, "BUTTON", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 232, 172, 50, 14
 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 287, 172, 50, 14
}

#endresource


We now have the user interface for a script to create bulk filings. Next week Steve will add project file processing so we can assemble the bulk filing XML.


 


 


 


 


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 



Posted by
David Theis
in Development at 17:02
Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)
No comments
The author does not allow comments to this entry

Quicksearch

Categories

  • XML Accounting
  • XML AICPA News
  • XML FASB News
  • XML GASB News
  • XML IASB News
  • XML Development
  • XML Events
  • XML FERC
  • XML eForms News
  • XML FERC Filing Help
  • XML Filing Technology
  • XML Information Technology
  • XML Investor Education
  • XML MSRB
  • XML EMMA News
  • XML FDTA
  • XML MSRB Filing Help
  • XML Novaworks News
  • XML GoFiler Online Updates
  • XML GoFiler Updates
  • XML XBRLworks Updates
  • XML SEC
  • XML Corporation Finance
  • XML DERA
  • XML EDGAR News
  • XML Investment Management
  • XML SEC Filing Help
  • XML XBRL
  • XML Data Quality Committee
  • XML GRIP Taxonomy
  • XML IFRS Taxonomy
  • XML US GAAP Taxonomy

Calendar

Back May '25 Forward
Mo Tu We Th Fr Sa Su
Friday, May 16. 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Feeds

  • XML
Sign Up Now
Get SEC news articles and blog posts delivered monthly to your inbox!
Based on the s9y Bulletproof template framework

Compliance

  • FERC
  • EDGAR
  • EMMA

Software

  • GoFiler Suite
  • SEC Exhibit Explorer
  • SEC Extractor
  • XBRLworks
  • Legato Scripting

Company

  • About Novaworks
  • News
  • Site Map
  • Support

Follow Us:

  • LinkedIn
  • YouTube
  • RSS
  • Newsletter
  • © 2024 Novaworks, LLC
  • Privacy
  • Terms of Use
  • Trademarks and Patents
  • Contact Us