Last week, we created a way to create template files for test filing XBRL in GoFiler. We also designed a method to open and edit them easily. Now it’s time to add the next level of functionality to this project: a script that allows us to test file XFR files with a single button press. This script will create a pair of new folders in the same directory as the XFR file: “Automated Test File” and “Automated Test Archive”. The first folder will contain the most recent test filing, while the latter will contain a copy of every test filing submitted to the SEC just in case it’s needed for future reference.
Friday, June 02. 2017
LDC #37: Automated Test Filing XBRL, Part 2
This script uses seven functions: setup, is_xfr_open, clear_folder, export_file, get_xbrl_filetype, main, and run. The main and setup functions are identical to every other setup function covered in previous posts, so they will not be discussed in depth here. The is_xfr_open function is almost an exact copy of is_project_open from last week’s post. clear_folder and export_file functions are very similar to functions of the same name that were in the XBRL Merger script from two weeks ago. The clear_folder function is actually identical to the XBRL Merger script’s version. The only new function in this script is the get_xbrl_filetype function. There is also additional logic for the run function to combine these parts in a new way. Once you write enough scripts, making a new one is less like building something entirely from scratch and more like picking and choosing pieces of existing scripts to recombine them in new ways.
The script for the week is:
// // GoFiler - TestFileXBRL.ms // ------------------------------------ // // Test files an XFR file. // // Revised 05-30-2017 SCH Page Created // // (c) 2017 Novaworks, LLC. All rights reserved. // #include "TestFileXBRLDefinitions.ls" #define TEST_FOLDER_NAME "Automated Test File" #define TEST_FILE_ARCHIVE "Automated Test Archive" #define TEST_DOCUMENT_CONTENTS "Test" #define TEST_DOCUMENT_NAME "test.txt"; int setup (); boolean is_xfr_open (); int clear_folder (string path); string export_file (string file, handle window); string get_xbrl_filetype (string filename); int run (int f_id, string mode); /***** Globals **************************/ string xfr_path; /* the path to the XFR file */ string xbrl_files[]; /* exported xbrl files */ /****************************************/ int setup(){ /* primary setup function */ /****************************************/ string item[]; /* params of menu item */ string script; /* script running */ int rc; /* return code */ /* */ item["Code"]="XBRL_TEST_FILE"; /* function name */ item["MenuText"]="Test File XFR File"; /* menu item */ item["Description"]="Test file an XFR file to EDGAR."; /* discription */ item["Class"] = "XBRLExtension"; /* Add to XBRL toolbar */ MenuAddFunction(item); /* add item to the menu */ script = GetScriptFilename(); /* get name of this script */ MenuSetHook(item["Code"],script,"run"); /* set hook to run function */ return ERROR_NONE; /* return no error */ } /* */ /****************************************/ int run(int f_id, string mode){ /* run save function */ /****************************************/ string output; /* final location of output file */ string amendment; /* amendment flag from xbrl */ string project_type; /* get the type of the project */ int rc; /* return code */ int primary_context; /* index to primary context in XBRL */ int num_files; /* number of xbrl files */ int ix; /* file counter */ string context_properties[]; /* properties of a context */ string entry_path; /* name of file entry */ string out_folder; /* output folder */ string template_fn; /* name of the template folder */ string accession; /* accession of filing */ string archive_folder; /* path to archive folder */ string template_folder; /* folder for templates */ string template_file; /* template file location */ string new_template; /* new template location */ handle hXBRL; /* xbrl handle */ handle hWindow; /* edit window handle */ /* */ if (mode!="preprocess"){ /* if not preprocess */ return ERROR_NONE; /* exit */ } /* */ if (is_xfr_open()==false){ /* if we do not have a project open */ MessageBox('x',GetLastErrorMessage()); /* display error message */ return ERROR_EXIT; /* exit with error */ } /* */ template_folder = AddPaths(TEMPLATE_FOLDER_LOCATION, /* build path to templates folder */ TEMPLATE_FOLDER_NAME); /* build path to templates folder */ hWindow = GetActiveEditWindow(); /* get the active edit window */ hXBRL = XBRLGetObject(hWindow); /* get handle to XBRL view */ primary_context = XBRLGetPrimaryContext(hXBRL); /* get primary context of report */ context_properties = XBRLGetContext(hXBRL,primary_context); /* get props of primary context */ project_type = XBRLGetFact(hXBRL,"dei:DocumentType", /* get the document type fact */ context_properties["ID"]); /* get document type fact */ if (project_type==""){ /* if we didn't get a project type */ MessageBox('x',"Cannot read Document Type from XFR file."); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ amendment = XBRLGetFact(hXBRL,"dei:AmendmentFlag", /* get amendment flag value */ context_properties["ID"]); /* get amendment flag value */ if (amendment == "true" && FindInString(project_type,"/A")<0){ /* if amendment and project type isn't */ project_type = project_type + "/A"; /* append /A to project type */ } /* */ template_fn = project_type+".gfp"; /* get name of template file */ template_file = AddPaths(template_folder,template_fn); /* get path to template file */ if (IsFile(template_file)==false){ /* if no template exists */ MessageBox('x',"No template for Document Type %s exists. Create "+/* display error */ "a template file and try again.",project_type); /* display error */ return ERROR_EXIT; /* return error */ } /* */ out_folder = export_file(xfr_path,GetActiveEditWindow()); /* export the open XFR file */ if (out_folder == ""){ /* if we don't have a new template */ MessageBox('x',"Cannot export fileset"); /* display error message */ return ERROR_EXIT; /* return an error */ } /* */ archive_folder = AddPaths(GetFilePath(xfr_path),TEST_FILE_ARCHIVE); /* get path to archive folder */ if (IsFolder(archive_folder)==false){ /* if no archive folder exists */ rc = CreateFolder(archive_folder); /* create it */ if (IsError(rc)){ /* if we can't */ MessageBox('x',"Cannot create folder %s, error %0x", /* display error */ archive_folder,rc); /* */ return ERROR_EXIT; /* exit with error */ } /* */ } /* */ new_template = AddPaths(out_folder,template_fn); /* get path to new template location */ rc = CopyFile(template_file,new_template); /* copy file to new template folder */ if (IsError(rc)){ /* if it failed to copy */ MessageBox('x',"Cannot copy template to %s",new_template); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ RunMenuFunction("FILE_OPEN","Filename:"+new_template); /* open the new template file */ entry_path = AddPaths(out_folder,TEST_DOCUMENT_NAME); /* path to an entry file */ StringToFile(TEST_DOCUMENT_CONTENTS,entry_path); /* write out entry */ rc = ProjectInsertEntry(-1,entry_path,project_type); /* insert file into project */ if (IsError(rc)){ /* if we failed to add file */ MessageBox('x',"Cannot add %s to project.",entry_path); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ num_files = ArrayGetAxisDepth(xbrl_files); /* get number of XBRL files */ for(ix=0;ix<num_files;ix++){ /* iterate over XBRL files */ project_type = get_xbrl_filetype(xbrl_files[ix]); /* get the type of XBRL file */ rc = ProjectInsertEntry(-1,xbrl_files[ix],project_type); /* insert the xbrl file into project */ if (IsError(rc)){ /* if we failed to add a file */ MessageBox('x',"Cannot add file %s",xbrl_files[ix]); /* display an error message */ return ERROR_EXIT; /* return an error */ } /* */ } /* */ RunMenuFunction("FILE_SAVE_AS","Filename:"+new_template); /* save file */ RunMenuFunction("EDGAR_VALIDATE"); /* validate file */ output = GetMenuFunctionResponse(); /* get validation response */ if (GetParameter(output,"FatalMessages")!=""){ /* if there are any fatal messages */ MessageBox('x',"Fatal errors in template. Fix the template"+ /* */ " before attempting to test XFR file."); /* display error */ } /* */ else{ /* if no fatal errors, test file. */ rc = RunMenuFunction("EDGAR_SUBMIT_TEST"); /* test file */ output = GetMenuFunctionResponse(); /* get response from SEC */ accession = GetParameter(output,"AccessionNumber"); /* get accession number */ if (rc!=ERROR_CANCEL){ /* if the user didn't cancel the filing */ if (accession == ""){ /* if we don't have an accession */ MessageBox('x',"Unable to get accession number, no archive"+ /* display an error message */ "file created."); /* display an error message */ } /* */ else{ /* if we got an accession */ accession = accession + " [TEST].xml"; /* build filename of archive file */ rc = CopyFile(AddPaths(out_folder,accession), /* copy from temp working directory */ AddPaths(archive_folder,accession)); /* to archive directory */ } /* */ } } /* */ RunMenuFunction("FILE_SAVE_AS","Filename:"+new_template); /* save file */ RunMenuFunction("FILE_CLOSE"); /* close file */ return ERROR_NONE; /* return no error */ } /* */ /****************************************/ boolean is_xfr_open(){ /* tests if an XFR is open */ /****************************************/ handle hWindow; /* window handle */ dword wType; /* type of window */ /* */ hWindow = GetActiveEditWindow(); /* get the active edit window */ if (IsError(hWindow)){ /* if we cannot get the active window */ SetLastError(ERROR_EXIT,"No File is open."); /* set error message */ return false; /* return false */ } /* */ wType = GetEditWindowType(hWindow); /* get the type of the window */ if (IsError()){ /* if we cannot get the type of window */ hWindow = GetParentWindow(hWindow); /* get the parent of the window */ wType = GetEditWindowType(hWindow); /* get the type of the parent */ } /* */ wType &= EDX_TYPE_ID_MASK; /* mask form type */ if (wType != EDX_TYPE_XBRL_VIEW){ /* if not in XBRL Tree View */ SetLastError(ERROR_EXIT,"Cannot test file this file type."); /* display error */ return false; /* exit with error */ } /* */ xfr_path = GetEditWindowFilename(hWindow); /* store filename for later */ return true; /* return no error */ } /****************************************/ string get_xbrl_filetype(string filename){ /* get the type of XBRL file */ /****************************************/ if (GetExtension(filename)==".xsd"){ /* if a schema file */ return "EX-101.SCH"; /* return type */ } /* */ if (FindInString(filename,"_cal")>0){ /* if it's a cal file */ return "EX-101.CAL"; /* mark as a cal file */ } /* */ if (FindInString(filename,"_pre")>0){ /* if it's a pre file */ return "EX-101.PRE"; /* mark as a pre file */ } /* */ if (FindInString(filename,"_def")>0){ /* if it's a def file */ return "EX-101.DEF"; /* mark as a def file */ } /* */ if (FindInString(filename,"_lab")>0){ /* if it's a lab file */ return "EX-101.LAB"; /* mark as a lab file */ } /* */ return "EX-101.INS"; /* mark it as INS file */ } /* */ /****************************************/ string export_file(string file, handle window){ /* export the XFR file */ /****************************************/ int rc; /* response code */ string response; /* response from function */ string path; /* path to output file */ string newfolder; /* new output folder */ string folder; /* response from export */ string cmd; /* command to export */ /* */ folder = GetFilePath(file); /* get folder */ newfolder = TEST_FOLDER_NAME; /* build name of new folder */ newfolder = AddPaths(folder,newfolder); /* build full path to newfolder */ if (IsFolder(newfolder)==false){ /* if folder doesn't exist */ rc = CreateFolder(newfolder); /* try to create the folder */ if (IsError(rc)){ /* did it create OK? */ return ""; /* return a blank string */ } /* */ } /* */ rc = clear_folder(newfolder); /* clear the export folder */ if (IsError(rc)){ /* if we couldn't clear the export */ SetLastError(rc,"Cannot delete files in folder "+newfolder); /* set error */ return ""; /* return */ } /* */ if (newfolder == ""){ /* if the export folder is blank */ return ""; /* return an error */ } /* */ path = AddPathDelimiter(newfolder); /* ensure path ends in slash */ cmd = "NoQuery: TRUE; Path: " + path; /* generate command string */ rc = RunMenuFunction("XBRL_EXPORT",cmd); /* export the file */ if (IsError(rc)){ /* if we couldn't export */ return ""; /* return */ } /* otherwise */ xbrl_files = EnumerateFiles(AddPaths(newfolder,"*.*")); /* enumerate all files exported */ return newfolder; /* return the newly created folder */ } /****************************************/ int clear_folder(string path){ /* delete all files from given folder */ /****************************************/ string filenames[]; /* filenames to delete */ int num_files; /* number of files */ int rc; /* result */ int ix; /* counter */ /* */ path = AddPathDelimiter(path); /* ensure path ends in slash */ filenames = EnumerateFiles(path+"*.*"); /* get all files in folder */ num_files = ArrayGetAxisDepth(filenames); /* get the number of files in folder */ for (ix=0;ix<num_files;ix++){ /* for each file in folder */ rc = DeleteFile(AddPaths(path,filenames[ix])); /* delete it */ if (IsError(rc)){ /* if we couldn't delete it */ return rc; /* return the error code */ } /* */ } /* */ return ERROR_NONE; /* return no error */ } /****************************************/ int main(){ /* main */ /****************************************/ setup(); /* run setup */ return ERROR_NONE; /* return no error */ }
The first new function this week is get_xbrl_filetype. This one is really simple. It takes a filename as a parameter and then tests to see which of the six XBRL file types it is. We can test the extension with the GetExtension SDK function, and if it is .xsd, it’s a schema file, so it returns the appropriate document type that EDGAR is expecting. If the position of “_cal”, “_pre”, “_def”, or “_lab” in the filename is greater than zero, then it’s a CAL, PRE, DEF, or LAB file, respectively, and the function returns the appropriate document type for each case. If it gets to the end of the function without returning, it must be an instance file, so the INS document type is returned. This assumes that the files will be named a certain way, but since they are being exported from GoFiler literally seconds before this function is to be called, it’s a pretty safe assumption.
/****************************************/ string get_xbrl_filetype(string filename){ /* get the type of XBRL file */ /****************************************/ if (GetExtension(filename)==".xsd"){ /* if a schema file */ return "EX-101.SCH"; /* return type */ } /* */ if (FindInString(filename,"_cal")>0){ /* if it's a cal file */ return "EX-101.CAL"; /* mark as a cal file */ } /* */ if (FindInString(filename,"_pre")>0){ /* if it's a pre file */ return "EX-101.PRE"; /* mark as a pre file */ } /* */ if (FindInString(filename,"_def")>0){ /* if it's a def file */ return "EX-101.DEF"; /* mark as a def file */ } /* */ if (FindInString(filename,"_lab")>0){ /* if it's a lab file */ return "EX-101.LAB"; /* mark as a lab file */ } /* */ return "EX-101.INS"; /* mark it as INS file */ } /* */
The export_file function is just a modified version of the one we covered in the Merge XBRL script from a few weeks ago. This function takes two parameters: the path of the XFR file to export and the handle to the currently open edit window. The window is assumed to be an XBRL edit window, so we need to test for that in the run function before we call this. First, we use the GetFilePath function to get the path to the folder the XFR file is in. Then we build our output folder location with the AddPaths function. We can test if the folder exists with the IsFolder function, and, if not, try to create it. If the function fails to create the folder, it returns an empty string. Otherwise, execution continues on, and we run our clear_folder function. If the function returns an error, we can set the error message, and return an empty string.
Next, we can build our command for the menu function before executing it with the RunMenuFunction function. If it resulted in an error, we return an empty string. One big difference between this version of this function and the previous one is the use of the EnumerateFiles SDK function at the bottom. This stores the list of exported files in the xbrl_files array for future use. It will be needed to add these files to the project file. The last thing this function does is return the name of the export folder location.
/****************************************/ string export_file(string file, handle window){ /* export the XFR file */ /****************************************/ int rc; /* response code */ string response; /* response from function */ string path; /* path to output file */ string newfolder; /* new output folder */ string folder; /* response from export */ string cmd; /* command to export */ /* */ folder = GetFilePath(file); /* get folder */ newfolder = TEST_FOLDER_NAME; /* build name of new folder */ newfolder = AddPaths(folder,newfolder); /* build full path to newfolder */ if (IsFolder(newfolder)==false){ /* if folder doesn't exist */ rc = CreateFolder(newfolder); /* try to create the folder */ if (IsError(rc)){ /* did it create OK? */ return ""; /* return a blank string */ } /* */ } /* */ rc = clear_folder(newfolder); /* clear the export folder */ if (IsError(rc)){ /* if we couldn't clear the export */ SetLastError(rc,"Cannot delete files in folder "+newfolder); /* set error */ return ""; /* return */ } /* */ if (newfolder == ""){ /* if the export folder is blank */ return ""; /* return an error */ } /* */ path = AddPathDelimiter(newfolder); /* ensure path ends in slash */ cmd = "NoQuery: TRUE; Path: " + path; /* generate command string */ rc = RunMenuFunction("XBRL_EXPORT",cmd); /* export the file */ if (IsError(rc)){ /* if we couldn't export */ return ""; /* return */ } /* otherwise */ xbrl_files = EnumerateFiles(AddPaths(newfolder,"*.*")); /* enumerate all files exported */ return newfolder; /* return the newly created folder */ }
The function is_xfr_open unsurprisingly tests to see if an XFR file is open. If so, it returns true. If not, it returns false. This is basically the same thing as is_project_open from last week with a couple of tweaks. The error messages have been changed to reflect that we’re testing for an XFR file. The constant to which we’re comparing wType is also changed. It’s now EDX_TYPE_XBRL_VIEW because we want to check to make sure it’s in XBRL view. The xfr_path variable is also new. It’s obtained through the GetEditWindowFilename function, which returns the path to the open XFR file. We can pass that to the export_file function.
/****************************************/ boolean is_xfr_open(){ /* tests if an XFR is open */ /****************************************/ handle hWindow; /* window handle */ dword wType; /* type of window */ /* */ hWindow = GetActiveEditWindow(); /* get the active edit window */ if (IsError(hWindow)){ /* if we cannot get the active window */ SetLastError(ERROR_EXIT,"No File is open."); /* set error message */ return false; /* return false */ } /* */ wType = GetEditWindowType(hWindow); /* get the type of the window */ if (IsError()){ /* if we cannot get the type of window */ hWindow = GetParentWindow(hWindow); /* get the parent of the window */ wType = GetEditWindowType(hWindow); /* get the type of the parent */ } /* */ wType &= EDX_TYPE_ID_MASK; /* mask form type */ if (wType != EDX_TYPE_XBRL_VIEW){ /* if not in XBRL */ SetLastError(ERROR_EXIT,"Cannot test file this file type."); /* display error */ return false; /* exit with error */ } /* */ xfr_path = GetEditWindowFilename(hWindow); /* store filename for later */ return true; /* return no error */ }
A big challenge with a script like this is dealing with error conditions gracefully. For example, what happens if you go to access a folder, but it’s not there? Well, the script needs to create the folder. But what happens if the create folder operation fails due to insufficient permissions to create a folder or some other reason? We need to check for these issues so we can exit the script gracefully instead of continuing on and getting into “undefined behavior” conditions where things are going wrong. The run function becomes much more complicated because of all of this error checking, but it’s an important part of the program for usability and safety.
The first thing we need to do is ensure we’re running in preprocess and return if not. If the mode is preprocess, we can check if an XFR file is open by using the is_xfr_open function. This also fills out the path to the open XFR file in the global variable xfr_path. Next we build the path to our template folder by using the AddPaths function with our defined values for the template location and name. Now we need to read some information out of the XFR file. In order to attach the files to the correct project, we need to know the type of project being done. We can use the GetActiveEditWindow and XBRLGetObject functions to create an XBRL object, which can access this information. First we need to get the index of the primary context by using the XBRLGetPrimaryContext function. We can retrieve the properties of this context by using the XBRLGetContext function next. Using the “ID” attribute from the returned array along with the element name of “dei:DocumentType” as parameters, the XBRLGetFact function returns the project type. If the project type is blank, we need to exit at this point by displaying a message and returning an error. After we have the project type, we need to get the amendment flag. In XBRL, a 10-Q/A filing can be marked as a 10-Q with the amendment flag set to true. Should the value of the amendment flag be set to true, we need to check if the project type contains a “/A”. If not, we need to add it to the end of the project type.
/****************************************/ int run(int f_id, string mode){ /* run save function */ /****************************************/ // variable declarations omitted, see full copy of code up top for // declarations if (mode!="preprocess"){ /* if not preprocess */ return ERROR_NONE; /* exit */ } /* */ if (is_xfr_open()==false){ /* if we do not have a project open */ MessageBox('x',GetLastErrorMessage()); /* display error message */ return ERROR_EXIT; /* exit with error */ } /* */ template_folder = AddPaths(TEMPLATE_FOLDER_LOCATION, /* build path to templates folder */ TEMPLATE_FOLDER_NAME); /* build path to templates folder */ hWindow = GetActiveEditWindow(); /* get the active edit window */ hXBRL = XBRLGetObject(hWindow); /* get handle to XBRL view */ primary_context = XBRLGetPrimaryContext(hXBRL); /* get primary context of report */ context_properties = XBRLGetContext(hXBRL,primary_context); /* get props of primary context */ project_type = XBRLGetFact(hXBRL,"dei:DocumentType", /* get the document type fact */ context_properties["ID"]); /* get document type fact */ if (project_type==""){ /* if we didn't get a project type */ MessageBox('x',"Cannot read Document Type from XFR file."); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ amendment = XBRLGetFact(hXBRL,"dei:AmendmentFlag", /* get amendment flag value */ context_properties["ID"]); /* get amendment flag value */ if (amendment == "true" && FindInString(project_type,"/A")<0){ /* if amendment and project type isn't */ project_type = project_type + "/A"; /* append /A to project type */ } /* */
Using the project type, we can add “.gfp” the the end of it to make a file name and then use the AddPaths function to get the location to where the template should be. The IsFile function can check if that template actually exists or not. If not, an error should be returned causing the script to exit so the user can go use the script we wrote last week to make a template. Otherwise, we can use our export_file function to actually export our XFR file and return a path to the output folder. If the export_file function returned an empty string, that means it failed, so we can just return an error message and leave the script. Continuing on, we can build a path to the archive folder by using the AddPaths function again. After that, we test if the folder is already there or not and, if not, try to create it.
template_fn = project_type+".gfp"; /* get name of template file */ template_file = AddPaths(template_folder,template_fn); /* get path to template file */ if (IsFile(template_file)==false){ /* if no template exists */ MessageBox('x',"No template for Document Type %s exists. Create "+/* display error */ "a template file and try again.",project_type); /* display error */ return ERROR_EXIT; /* return error */ } /* */ out_folder = export_file(xfr_path,GetActiveEditWindow()); /* export the open XFR file */ if (out_folder == ""){ /* if we don't have a new template */ MessageBox('x',"Cannot export fileset"); /* display error message */ return ERROR_EXIT; /* return an error */ } /* */ archive_folder = AddPaths(GetFilePath(xfr_path),TEST_FILE_ARCHIVE); /* get path to archive folder */ if (IsFolder(archive_folder)==false){ /* if no archive folder exists */ rc = CreateFolder(archive_folder); /* create it */ if (IsError(rc)){ /* if we can't */ MessageBox('x',"Cannot create folder %s, error %0x", /* display error */ archive_folder,rc); /* */ return ERROR_EXIT; /* exit with error */ } /* */ } /* */
Next, we can build the destination path for our template and use the CopyFile function to move the template into our output directory. We test it for an error and then move on to run the “FILE_OPEN” menu function with the RunMenuFunction function. This will open up our copied project file template. We then need to start building our fake primary document to attach to this. The primary document would normally be the HTML 10-Q or 10-K or whatever is being filed, but we don’t have one, so we can just make a dummy text file and use it instead as a placeholder. Using the StringToFile function, we can write out our defined test contents to our defined test document name in our output folder. We can then run the ProjectInsertEntry function to attach this file to our project and test to see if it succeeded or not. If so, we can get the number of XBRL files we need to attach and iterate over them. For each XBRL file exported, we need to run the get_xbrl_filetype function to get the type of the file. Then we should execute the ProjectInsertEntry function to attach it to our open project, checking for errors each time.
new_template = AddPaths(out_folder,template_fn); /* get path to new template location */ rc = CopyFile(template_file,new_template); /* copy file to new template folder */ if (IsError(rc)){ /* if it failed to copy */ MessageBox('x',"Cannot copy template to %s",new_template); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ RunMenuFunction("FILE_OPEN","Filename:"+new_template); /* open the new template file */ entry_path = AddPaths(out_folder,TEST_DOCUMENT_NAME); /* path to an entry file */ StringToFile(TEST_DOCUMENT_CONTENTS,entry_path); /* write out entry */ rc = ProjectInsertEntry(-1,entry_path,project_type); /* insert file into project */ if (IsError(rc)){ /* if we failed to add file */ MessageBox('x',"Cannot add %s to project.",entry_path); /* display error */ return ERROR_EXIT; /* return with error */ } /* */ num_files = ArrayGetAxisDepth(xbrl_files); /* get number of XBRL files */ for(ix=0;ix<num_files;ix++){ /* iterate over XBRL files */ project_type = get_xbrl_filetype(xbrl_files[ix]); /* get the type of XBRL file */ rc = ProjectInsertEntry(-1,xbrl_files[ix],project_type); /* insert the xbrl file into project */ if (IsError(rc)){ /* if we failed to add a file */ MessageBox('x',"Cannot add file %s",xbrl_files[ix]); /* display an error message */ return ERROR_EXIT; /* return an error */ } /* */ } /* */
After the XBRL files are attached, we can use RunMenuFunction function again to save our project and then to validate it with the FILE_SAVE_AS and EDGAR_VALIDATE parameters, respectively. We want to make sure our project file has no fatal errors. We can use the GetMenuFunctionResponse to get the validation results. If the response has the parameter FatalMessages set to anything, the project file template has problems. In this case, we can return an error message directing the user to go fix the template and then try again. If the validation had no fatal errors, we can proceed.
Finally we’re ready to test file our project with the EDGAR System. We can do this with the RunMenuFunction function, passing it EDGAR_SUBMIT_TEST. This will prompt the user before it actually files so he/she has one last chance to cancel the operation. After the submission function completes, we can get the response back and get the accession number from the response. If the user didn’t cancel the filing, there should be a file in that user’s folder created by GoFiler, with the accession number as a prefix and ending in “[TEST].xml”. We want to save that file in our archive directory, so we have a record of all past test filings. The working test folder is cleared before each filing, so it would be lost otherwise. If we don’t have an accession number (which can happen if there was a filing error or something happened to interrupt transmission) we need to give the user some feedback. Otherwise, we can use the CopyFile function to copy the submission file to our archive directory. Lastly, we want to use the RunMenuFunction function again to save and then close our project file.
RunMenuFunction("FILE_SAVE_AS","Filename:"+new_template); /* save file */ RunMenuFunction("EDGAR_VALIDATE"); /* validate file */ output = GetMenuFunctionResponse(); /* get validation response */ if (GetParameter(output,"FatalMessages")!=""){ /* if there are any fatal messages */ MessageBox('x',"Fatal errors in template. Fix the template"+ /* */ " before attempting to test XFR file."); /* display error */ } /* */ else{ /* if no fatal errors, test file. */ rc = RunMenuFunction("EDGAR_SUBMIT_TEST"); /* test file */ output = GetMenuFunctionResponse(); /* get response from SEC */ accession = GetParameter(output,"AccessionNumber"); /* get accession number */ if (rc!=ERROR_CANCEL){ /* if the user didn't cancel the filing */ if (accession == ""){ /* if we don't have an accession */ MessageBox('x',"Unable to get accession number, no archive"+ /* display an error message */ "file created."); /* display an error message */ } /* */ else{ /* if we got an accession */ accession = accession + " [TEST].xml"; /* build filename of archive file */ rc = CopyFile(AddPaths(out_folder,accession), /* copy from temp working directory */ AddPaths(archive_folder,accession)); /* to archive directory */ } /* */ } } /* */ RunMenuFunction("FILE_SAVE_AS","Filename:"+new_template); /* save file */ RunMenuFunction("FILE_CLOSE"); /* close file */ return ERROR_NONE; /* return no error */ } /* */
This is definitely an example of a more automation heavy script than ones we’ve done in the past. It really has no fancy algorithms or parsing logic. It’s simply a list of commands (and error handling) to automate a process that a user would otherwise have to do every time a project needs to be test filed. Legato excels at automating simple, repetitive tasks like this. If it’s a basic task that can have a complete script written for it that requires little time to develop, then it’s a great candidate for Legato automation.
Steven Horowitz has been working for Novaworks for over five years as a technical expert with a focus on EDGAR HTML and XBRL. Since the creation of the Legato language in 2015, Steven has been developing scripts to improve the GoFiler user experience. He is currently working toward a Bachelor of Sciences in Software Engineering at RIT and MCC. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato