Friday, April 21. 2017

Legato is great at supplementing features that GoFiler already has and hooking into existing functions and modifying how they work slightly. Probably its most powerful ability, however, is to add entirely new functionality to GoFiler. One feature GoFiler doesn’t have natively is the ability to have multiple currency types for a single fact in XBRL. Well, we can add support for that using Legato! The new functionality will be achieved by combining two different XFR files.

To get started let’s assume that we have a completed XFR file that needs multiple currencies for one or more facts. Make sure all the facts are the same currency and follow the steps below:

1) Save a new copy of the file with an alternate name, preferably using the currency in the name. So if the file is “MyFancy.xfr”, the user could save a new file with the name “MyFancyCAD.xfr”.

2) Edit the facts into the alternate currencies in the newly saved file to use the new currency instead of the original’s currency and change the values as appropriate.

3) Run the script, which will export both files, then combines the instance files into a single document.

4) Proof and/or file the merged XBRL file set.

This will be the most complicated script we have done in this blog to this point, so it is going to be broken up through multiple weeks. This first post will cover the relatively minor user interface (UI) and validation of user input.

Our script so far consists of two files: the resource file that represents the dialog and the merger script. The merger script looks like this:

We haven’t really talked much about resource files, but they are a standard way of writing dialog controls in Windows. For more complete documentation on exactly how they work, check out Microsoft’s SDK page here. The resource file, when actually called, produces a dialog that should look like this:

Our script will handle what happens when buttons are pressed on the dialog. First, our merger script makes its declarations and global variables. The only import is going to be “XBRLMerger.rc”. This is the resource file that controls our user input dialog. For functions, our script will use the standard setup, run, and main functions we’ve talked about before. In addition to them, though, we have the validate_file function, which will take a file path and display name as parameters and make sure the file exists, that we can access it, and check if it’s already open. In addition to this, we will have several functions with the prefix “merge_”, which are called automatically by our dialog at specific points. They do not need to be declared at the top with the other functions. We’ll talk about this again in a bit. Our global variables will be edit_windows, a two-dimensional string array that will store information about currently open edit windows, and FileOneWindow and FileTwoWindow, which store handles to the files we are going to open and combine.

Our setup function this time is pretty much identical to most of what we’ve seen before. The main difference is that in the array item, we are defining an index for “Class” as well. By specifying this is an XBRLExtension class, the menu option will appear under the tools area of the XBRL toolbar instead of the tools area of the File toolbar in GoFiler.

Our run function is actually very small here because most of the execution occurs in the specific handler functions for dialog actions. All the run function has to do is determine if we are running as preprocess, and if so, then enumerate the edit windows to fill our array edit_windows. Once we have that, we can use the DialogBox function to open our dialog UI. This function takes two parameters: the name of the dialog in the resource file and the prefix we want to use for our automatically called UI functions. In this case, we will use the prefix “merge_” as mentioned previously. This means we will have to define other functions below like the “merge_load” function to handle how the UI loads or the “merge_ok” function to handle what happens when the user presses the OK button. Now our specific dialog event handlers are hooked into our dialog.

Once our dialog is called, this function blocks execution of the rest of the run function until the dialog is ended, so afterwards all we need to do is close our handles to our open files.

The main function is very basic, but this time we’re going to check if the current script is running in our development IDE. If so, we’re going to run the setup function and then run our run function. This will let us do debugging in our development environment instead of having to run our script from the menu hook every time, where getting debug information is more difficult.

So now that we have our basic functions defined, we can actually start writing handlers for UI actions. The first one, merge_load, will handle loading information that the dialog will need to display properly to the user. For this script, the GetSetting function is used to get the last used file_one and file_two variables. Then the EditSetText function is used to set their values into the file selection boxes on our UI Dialog.

Next, the merge_action function is defined. This function is called whenever a user interacts with the UI. So within it, we can define specific handlers for every control by checking the c_id parameter. If this parameter matches up with a specific control, we can tell the user has interacted with that control or in our case, pressed a specific button. First, we test for the XBRL_RESET button. This button, when pressed, triggers the EditSetText and function for both of our inputs to set them back to blank strings and runs the PutSetting function to reset our stored values for file_one and file_two back to blank strings as well. These settings are stored in the first place because when making changes to a file, there may be a reason to merge the same files repeatedly, but if you want to start a new job, a reset button is handy.

After that, we can test for the XBRL_ONE_BROWSE button. If the user presses this button, we need to first use the EditGetText function to get the path of the first file that was selected in case a path already exists there. If the path exists, we pass it to our BrowseOpenFile function, which prompts the user to pick a file with the “.xfr” file extension. If the user actually chooses a path, we can then use the EditSetText function to change the text field to show the path and use the PutSetting function to store the location of the selected file for later.

Testing for the XBRL_TWO_BROWSE button is basically the exact same thing as above. However, if the user hasn’t entered a path for the second file, we can take the path from the first file to use as a base. This makes it so that the BrowseOpenFile function will open up to the same folder that the first file is in, which is good and more convenient because it’s likely that the first file and the second file are in the same folder. This finishes off the merge_action function.

The merge_validate function is called by the UI after the OK button is pressed to handle the event. If this function returns ERROR_EXIT, the UI does nothing. If it returns ERROR_NONE, the UI continues onto the function merge_ok. First, we need to get the values for our two selected files using the EditGetText functions. Then, we test if file_one or file_two are blank. If so, we display an error message and then return with an error. We also need to check if file_one is the same as file_two. If so, again we display an error and return with an error.

If we’re still going, we can run our validate_file function on file_one. The function will be explained more next, but it pretty much just makes sure we can actually work with this file, and if the file is already open, it sets the value of our global FileTwoWindow variable to the handle of the file. Because we’re concerned about file_one still, after we validate file_one, we can check the value of the handle FileTwoWindow using the IsWindowHandleValid function. If this function returns true, it means file_one is open already, and we have a handle to it. So then we need to store that handle in our global FileOneWindow, and reset the handle of FileTwoWindow by setting it to NULL_HANDLE. Then we can call the validate_file function on file_two to make sure it’s valid. If the responses from our validate_file function equal each other, and at least one of them is ERROR_NONE, we can assume both are ERROR_NONE, and then return ERROR_NONE. Otherwise, we need to return ERROR_EXIT, and let the user fix whatever errors occurred.

The validate_file function is responsible for checking to make sure that the file chosen exists, is a “.xfr” file, and if it’s not open in GoFiler, it ensures it actually can be opened. This is very important, because we don’t want to assume these things without checking first. Firstly, we can check the number of open edit windows and store that for later. Then, we check if the user input was actually a file or not with the function IsFile. If not, we need to display an error message. If it is a file, we can test if we can access it by using the CanAccessFile function. Read/Write access is required to the file, so we use the parameter FO_WRITE to check the correct access level. If this function returns true, then we can use the MakeLowerCase and GetExtension functions to make sure the selected file is an “.xfr” file. If not, we can display an error message. If all of that is true, we can return right now without error because this is a valid “.xfr” file that GoFiler has the ability to open.

If the function CanAccessFile returns false, it’s possible that the file is already open in GoFiler, so we need to check that. To do that, we have a for loop that iterates over the open edit windows. For each edit window, we can check the “Filename” column of the table, and if it matches the filename of the file we’re testing, it means that the file is an open tab in GoFiler. Thus we have access to the file. Then we can store the handle to that window by using the MakeHandle function on the “ClientHandle” index for that row of our edit windows table. Next, we need to get the last error to ensure our handle was actually created. If not, then we can display an error message. If it was created, we can return ERROR_NONE right here. If we exit the for loop without returning, then we can display an error message that we cannot open the file. This function is designed to return ERROR_NONE if it’s valid, so if we’re still running this function at the end and we haven’t returned yet, we can assume there was a problem, so we can return ERROR_EXIT.

The last function is merge_ok. This function is called after the merge_validate if the validation passed. For now, it gets the file paths from the UI using the EditGetText functions. Then it tests the FileOneWindow and FileTwoWindow variables to see if those files are already open. If so, it just prints out a message to the user for now. This function will be modified next week.

This is intended to be a base point for our script; from here we can add in the rest of our functionality. Next week, we will look at expanding this script to open the files, export them to XBRL filesets, and test to make sure we can actually merge the two files.

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.

E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.Enter the string from the spam-prevention image above: