About a week ago, a client brought up a good point in a discussion we were having about inline XBRL in GoFiler. While we could generate proof files for the XBRL and proof files for the HTML, the HTML proof only showed HTML data, and the XBRL proof only showed XBRL data. The HTML proof wasn’t highlighting which facts had been tagged with inline XBRL data, because unless you view it through a special viewer, it looks indistinguishable from regular HTML. It’s a feature we will be adding in a future release of GoFiler, but why wait? Through Legato features can be added on the fly!
In response to that question, I wrote a short, simple script to highlight iXBRL information embedded into HTML files. This script is going to trigger when the Proof button is pressed, as a post process function. After the proof is created, it will open the proof file, and surround any inline XBRL tags with a span tag, that has a border on the top and bottom, to highlight these values as tagged inline XBRL facts.
First, let’s take a look at our two defined values, BORDER_TAG and BORDER_TAG_CLOSE.
#define BORDER_TAG "<SPAN STYLE=\"border-top: Red 1pt solid; border-bottom: Red 1pt solid\">"
#define BORDER_TAG_CLOSE "</SPAN\">"
These tags are going to be wrapped around our inline tags, to cause the inline tags to stand out in the browser as XBRL facts. I chose to use SPAN tags here instead of FONT tags, because inline XBRL does not allow FONT tags. This is a proof file, and it doesn’t really matter since the web browser will render either correctly anyway, but I feel it’s best to remain consistent in the use of tags and use SPAN tags.
Our script this week has 3 primary functions, run, setup, and main. Setup simply hooks the script to the proof button, and main is effectively an alias to setup, so we’re going to focus on the run function, and how it works.
int run(int f_id, string mode){
handle window;
dword wType;
string s1;
string file;
string contents;
if (mode != "postprocess"){
return;
}
window = GetActiveEditWindow();
wType = GetEditWindowType(window) & EDX_TYPE_ID_MASK;
if (wType != EDX_TYPE_PSG_PAGE_VIEW){
return ERROR_NONE;
}
Our function starts out by ensuring that we’re running in post process mode only. This is a bit unusual, generally we want to trigger our script before the function runs, but in this case we need the function to run to create our proof file so we have something to modify. After we’ve checked the run mode, we can get the active edit window, and check to make sure it’s a page view window. If it’s not page view, it’s not an inline XBRL file, so there’s no sense running any further and we can just return without error.
// if inline XBRL
if (GetFileTypeCode(GetEditWindowFilename(window))==FT_IXBRL){
s1 = GetMenuFunctionResponse();
file = GetParameter(s1,"ResultFile");
contents = FileToString(file);
contents = ReplaceInString(contents,"<ix:nonNumeric",BORDER_TAG+"<ix:nonNumeric");
contents = ReplaceInString(contents,"</ix:nonNumeric>",BORDER_TAG_CLOSE);
contents = ReplaceInString(contents,"<ix:nonFraction",BORDER_TAG+"<ix:nonFraction");
contents = ReplaceInString(contents,"</ix:nonFraction>",BORDER_TAG_CLOSE);
contents = ReplaceInString(contents,"<ix:Fraction",BORDER_TAG+"<ix:Fraction");
contents = ReplaceInString(contents,"</ix:Fraction>",BORDER_TAG_CLOSE);
StringToFile(contents,file);
}
return ERROR_NONE;
}
Once we know we’re running in post process and we know the window is a page view, we can check to make sure the window is inline XBRL. If the GetFileTypeCode function doesn’t return the defined value FT_IXBRL, then it’s not inline XBRL, so we can just return instead of processing the values. If the value matches FT_IXBRL though, it means we’re dealing with an inline XBRL file, so we have to do some editing.
Most menu functions, when they finish running, post some sort of response. This response generally has results of the function, or status changed by the function, or something like that. In this case, we can call the GetMenuFunctionResponse function after our proof has been run, to get the resulting file that the proof created. It returns this as a parameter string, which we can break down by using the GetParameter function to get the name of the proof file. After we have the name of the proof file, we can go ahead and use FileToString to read the whole thing into memory as a string variable, so we can execute a series of find/replace operations on it.
There are three primary types of inline tags that we need to highlight, ix:nonNumeric, ix:nonFraction, and ix:Fraction. For each of these, we will need to use ReplaceInString to wrap the open and close tags for each of these facts in our new BORDER_TAG and BORDER_TAG_CLOSE values. Keep in mind that ReplaceInString has a limit of 1MB in string size, so this will only work with files below that. This script could be expanded to support that by using a more complex method of inserting the new tags, but this will work fine for basic, smaller files. After we’ve run these replace operations, we can execute a StringToFile command to write our proof file back out.
That’s all there is to it! One thing to be aware of here, is that since this is a post process operation, our proof has already been created, and the browser has already been told to open the file, and we’re just changing the file after it’s been opened in the browser. In my test cases, the operation has competed fast enough that the file was modified before the web browser got a chance to open the file, but keep in mind that since it’s timing dependent, you may have to refresh the proof file after it opens in the browser to get the proof to show up with the expected highlighting over inline XBRL facts.
Here’s our complete script without commentary:
#define BORDER_TAG "<SPAN STYLE=\"border-top: Red 1pt solid; border-bottom: Red 1pt solid\">"
#define BORDER_TAG_CLOSE "</SPAN\">"
void setup();
int run(int f_id, string mode);
void setup(){
string fn;
fn = GetScriptFilename();
MenuSetHook("FILE_LAUNCH", fn, "run");
}
void main(){
setup();
}
int run(int f_id, string mode){
handle window;
dword wType;
string s1;
string file;
string contents;
if (mode != "postprocess"){
return;
}
window = GetActiveEditWindow();
wType = GetEditWindowType(window) & EDX_TYPE_ID_MASK;
if (wType != EDX_TYPE_PSG_PAGE_VIEW){
return ERROR_NONE;
}
// if inline XBRL
if (GetFileTypeCode(GetEditWindowFilename(window))==FT_IXBRL){
s1 = GetMenuFunctionResponse();
file = GetParameter(s1,"ResultFile");
contents = FileToString(file);
contents = ReplaceInString(contents,"<ix:nonNumeric",BORDER_TAG+"<ix:nonNumeric");
contents = ReplaceInString(contents,"</ix:nonNumeric>",BORDER_TAG_CLOSE);
contents = ReplaceInString(contents,"<ix:nonFraction",BORDER_TAG+"<ix:nonFraction");
contents = ReplaceInString(contents,"</ix:nonFraction>",BORDER_TAG_CLOSE);
contents = ReplaceInString(contents,"<ix:Fraction",BORDER_TAG+"<ix:Fraction");
contents = ReplaceInString(contents,"</ix:Fraction>",BORDER_TAG_CLOSE);
StringToFile(contents,file);
}
return ERROR_NONE;
}
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
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato