This week, we’re taking another look at using Legato to enhance GoFiler’s validation. Last month, someone asked me if there was a way GoFiler could check a 10-Q project to ensure a completed XBRL file set was attached to it. Well, this isn’t something GoFiler normally checks for (since there are times when you might want to omit these files), but we can definitely use Legato to enhance the validation of the project file, so we can warn the user if they might have forgotten something. It also gives us another opportunity to demonstrate some of the project file functions, which are always useful if you’re going to be working with GFP files.
Friday, January 10. 2020
LDC #164: Validating Project Files Have XBRL
First, let’s take a look at some defined values we need to make this work.
#define XBRL_FORMS "10-K,10-K/A,10-Q,10-Q/A,S-4,S-4/A" #define NO_XBRL_MSG "This submission does not have a complete XBRL file set. Double check to make sure all required files are attached." #define PRE "EX-101.PRE" #define LAB "EX-101.LAB" #define XSD "EX-101.SCH"
These defines allow us to quickly define what forms should have XBRL attached to them, our XBRL message, and the document types of the XBRL files we’re looking for. For the XBRL_FORMS string, we’re going to use just a simple CSV-style string, since this doesn’t really need to be any more complicated than that for this to work well. We’re also only going to specify the forms that we care about for this specific task. Obviously, XBRL is allowed for forms beyond what we had defined here, but for our purposes in this script, I’m choosing to specify just a few form types.
It’s worth noting that XBRL can actually have up to six files. Of those, two are potentially not required (CAL and DEF files are not always needed for all XBRL projects) and inline XBRL doesn’t have an instance file, since the primary HTML document in the filing is both instance file and primary document. As a result, we have here defines for the three files that will appear in all XBRL documents no matter what type of XBRL it is: our PRE, LAB, and XSD files.
Our script has the same basic setup / main / run function configuration that many of our examples do. We can skip over the setup and main functions, since they’ve been thoroughly explained in other blog posts; all they do is set up our run function to trigger when the user presses the Validate button on the Submission Ribbon. Let’s start then by taking a look at our run function.
/****************************************/ int run(int f_id, string mode){ /* run function */ /****************************************/ handle edit_window; /* handle to the edit window */ dword window_type; /* the window type */ boolean xsd,pre,lab, validate; /* true if the corresponding files exist*/ int filecount; /* number of files in project */ int ix; /* counter */ string data[]; /* files attached to project */ string type; /* type of the file */ /* */ if(mode != "preprocess"){ /* if we're not in preprocess */ return ERROR_NONE; /* return no error */ } /* */ edit_window = GetEditWindowHandle(); /* get handle to edit window */ window_type = GetEditWindowType(edit_window); /* get type of edit window */ window_type&= EDX_TYPE_ID_MASK; /* mask type */ if (window_type != EDX_TYPE_EDGAR_VIEW ){ /* check if in EDGAR */ return ERROR_NONE; /* return with error */ } /* */
The first thing our script needs to do is check to make sure we’re running in preprocess mode, to ensure it doesn’t run twice (once in preprocess, once in postprocess). We can then use GetEditWindowHandle to get our edit window and use GetEditWindowType to get the type of window. Once we have that, we can make sure our window type is an EDGAR project view. If we don’t do this, our script will run regardless of the type of active window, which is not what we want. Because the script validates the currently open project, it would always run and possibly present a message box when validating other non-project files.
validate = false; /* initialize variable */ xsd = false; /* initialize variable */ pre = false; /* initialize variable */ lab = false; /* initialize variable */ filecount = ProjectGetEntryCount(); /* get number of files */ if(filecount == -1){ /* if not a project */ return ERROR_NONE; /* return no error */ } /* */
Once we’ve ensured that our project file is the current active window, we can reset our variables. Validate is a boolean variable we’re going to use to test to see if we need to actually validate the file. For now it’s false, but if it’s a project we want to validate, we’ll flip it to true later. The variables xsd, pre, and lab are also boolean flags that are all false, but get set to true if those files are present in the project. Those set, we can use ProjectGetEntryCount to get the number of documents in our project file. If it returns -1, that means there was an error, so we can just return because there’s nothing to validate.
for(ix=0;ix<filecount;ix++){ /* for each file in project */ data = ProjectGetEntryData(ix); /* get data for file */ if(IsInString(XBRL_FORMS,data["Type"])){ /* if this is a file type to validate */ validate = true; /* set validate to true */ } /* */ switch(data["Type"]){ /* switch on data type */ case PRE: /* if it's a pre file */ pre=true; /* set pre to true */ break; /* break switch */ case XSD: /* if it's an XSD file */ xsd=true; /* set xsd to true */ break; /* break switch */ case LAB: /* if it's a lab file */ lab=true; /* set lab to true */ break; /* break switch */ } /* */ } /* */
Now it’s time to iterate over all of our project entries. For each entry, we need to get the data from the entry with ProjectGetEntryData, and test to see if the type attribute is in our CSV string of allowable types. If so, that means this is a project we want to validate, so we’re going to flip the validate flag to true. Then, we’re going to want to switch on the Type as well, because we need to test if the entry is an XML file. If it’s one of our expected XML files for XBRL, we can set the corresponding flag to true.
if(validate == false){ /* if we dont need to validate */ return ERROR_NONE; /* return without error */ } /* */ if(lab == true && pre == true && xsd == true){ /* if all 3 files are there */ return ERROR_NONE; /* return without error */ } /* */ MessageBox('i',NO_XBRL_MSG); /* display warning about XBRL missing */ return ERROR_NONE; /* return without error */ } /* */ /* */
If the validate flag was never set to true, then it’s still false, and we can just return here because there’s nothing to validate. If it is set to true though, we need to test to make sure lab, pre, and xsd are all true as well. If they are, that means our file has all the XBRL files we’re testing for, so we can return without a problem. If we haven’t returned yet, then it means that validate must have been true, and one of our three XBRL files must be missing, so we can display a warning message to the user, and then just return.
This is a very quick little script that extends GoFiler’s ability to prevent user error pretty handily, making it a great example of the kind of things you can do with Legato. It gives you the flexibility you need to customize GoFiler to do what you want, and to change or add whatever you want within the core functionality of the program.
Below is the completed script without commentary:
#define XBRL_FORMS "10-K,10-K/A,10-Q,10-Q/A,S-4,S-4/A" #define NO_XBRL_MSG "This submission does not have a complete XBRL file set. Double check to make sure all required files are attached." #define PRE "EX-101.PRE" #define LAB "EX-101.LAB" #define XSD "EX-101.SCH" void setup(); /* setup */ void main(); /* main */ int run(int f_id, string mode); /* run */ /****************************************/ void setup(){ /* setup function */ /****************************************/ string fname; /* script function name */ /* */ fname = GetScriptFilename(); /* get script name */ MenuSetHook("EDGAR_VALIDATE",fname,"run"); /* set hook on validate */ } /* */ /****************************************/ void main(){ /* main function */ /****************************************/ setup(); /* setup */ } /* */ /****************************************/ int run(int f_id, string mode){ /* run function */ /****************************************/ handle edit_window; /* handle to the edit window */ dword window_type; /* the window type */ boolean xsd,pre,lab, validate; /* true if the corresponding files exist*/ int filecount; /* number of files in project */ int ix; /* counter */ string data[]; /* files attached to project */ string type; /* type of the file */ /* */ if(mode != "preprocess"){ /* if we're not in preprocess */ return ERROR_NONE; /* return no error */ } /* */ edit_window = GetEditWindowHandle(); /* get handle to edit window */ window_type = GetEditWindowType(edit_window); /* get type of edit window */ window_type&= EDX_TYPE_ID_MASK; /* mask type */ if (window_type != EDX_TYPE_EDGAR_VIEW ){ /* check if in EDGAR */ return ERROR_NONE; /* return with error */ } /* */ validate = false; /* initialize variable */ xsd = false; /* initialize variable */ pre = false; /* initialize variable */ lab = false; /* initialize variable */ filecount = ProjectGetEntryCount(); /* get number of files */ if(filecount == -1){ /* if not a project */ return ERROR_NONE; /* return no error */ } /* */ for(ix=0;ix<filecount;ix++){ /* for each file in project */ data = ProjectGetEntryData(ix); /* get data for file */ if(IsInString(XBRL_FORMS,data["Type"])){ /* if this is a file type to validate */ validate = true; /* set validate to true */ } /* */ switch(data["Type"]){ /* switch on data type */ case PRE: /* if it's a pre file */ pre=true; /* set pre to true */ break; /* break switch */ case XSD: /* if it's an XSD file */ xsd=true; /* set xsd to true */ break; /* break switch */ case LAB: /* if it's a lab file */ lab=true; /* set lab to true */ break; /* break switch */ } /* */ } /* */ if(validate == false){ /* if we dont need to validate */ return ERROR_NONE; /* return without error */ } /* */ if(lab == true && pre == true && xsd == true){ /* if all 3 files are there */ return ERROR_NONE; /* return without error */ } /* */ MessageBox('i',NO_XBRL_MSG); /* display warning about XBRL missing */ return ERROR_NONE; /* return without error */ } /* */ /* */
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