<DOCUMENT>
<TYPE>10-K
<SEQUENCE>1
<FILENAME>filename.htm
<TEXT>
At the bottom, it injects:
</TEXT>
</DOCUMENT>
These are generally harmless because they don’t really have any HTML meaning and are supposed to be ignored by the web browsers. In tested versions of Chrome (we tested versions 56, 57, and 59), however, it’s not actually ignoring these tags, and it’s throwing off how the document renders. If the filing is viewed after removing these SEC injected tags, it looks completely normal, making it very hard to actually detect if your filing will be affected or not because unless you manually add the SEC tags yourself, proofing it really doesn’t reveal if there is a problem. This appears to only affect Chrome. Internet Explorer, Firefox, and Edge browsers all work as expected.
In researching the issue, we identified a significant number of filings that exhibit this problem. We also found several that use TABLE and DIV tags that should cause problems but don’t. In comparing a properly rendered file to a poorly rendered file, we determined that the key difference was DIV tags were wrapped around the potential trouble tags. Plain <DIV> tags should be neutral to a browser and so shouldn’t change the look of the document at all by themselves, but they do seem to prevent Chrome from failing to render things correctly. We have uploaded two example files, see the below two links. You can just search the file (ctrl+f) for “+++” and it should take you right to it. See the side by side images below.
Bugged File: chromeissue.htm
Modified File: chromeissue_fixed.htm
As far as we can tell, there is nothing wrong with these HTML files or the ones on the SEC’s website that have this rendering issue. It’s simply an issue with Chrome that’s being exposed by the SEC injecting non-HTML tags into an HTML document, which can cause unpredictable rendering issues. I’ve alerted Google and the SEC to this, and we’ll see if either party does anything to correct it. Until then, we’re left with a couple of options to prevent this from happening in the future.
1) Stop using Google Chrome to view SEC filings. This is probably the simplest solution, but because the bug may eventually be fixed in Chrome, changing your entire process to accommodate a temporary bug in the viewing software is a pretty big task.
2) Stop using float left or right with DIV tags and stop using align left or right with TABLE tags. You can achieve similar effects with tables using extra columns for spacers. This is another fairly big change to accommodate a bug in a single browser.
3) Wrap all of your TABLE and DIV tags that use align or float properties with simple DIV tags. This is a relatively simple fix, and is easy enough to implement. It’s a change to the process to compensate for a bug in other software, but it can be automated using Legato.
This blog will focus on automating adding div tags around potentially problematic objects in the HTML. Let’s take a closer look at how Legato can help with this problem. Here is our complete script.
int run (int f_id, string mode, boolean fix);
int setup() {
string fnScript;
fnScript = GetScriptFilename();
MenuSetHook("EDGAR_VALIDATE", fnScript, "run");
MenuSetHook("REVIEW_DISPLAY_ERRORS", fnScript, "run");
return ERROR_NONE;
}
int main() {
setup();
return ERROR_NONE;
}
int run(int f_id, string mode, boolean fix) {
int counter;
int fix_counter;
int rc;
int ex,ey,sx,sy;
dword type;
boolean is_div;
boolean is_table;
boolean has_div;
string div;
string div_close;
string prompt_msg;
string param;
string content;
string element;
handle sgml;
handle edit_object;
handle edit_window;
string text;
if (mode!="postprocess"){
return ERROR_NONE;
}
edit_window = GetActiveEditWindow();
if(IsError(edit_window)){
return ERROR_NONE;
}
type = GetEditWindowType(edit_window) & EDX_TYPE_ID_MASK;
if (type!=EDX_TYPE_PSG_PAGE_VIEW && type!=EDX_TYPE_PSG_TEXT_VIEW){
return ERROR_NONE;
}
edit_object = GetEditObject(edit_window);
sgml = SGMLCreate(edit_object);
SGMLSetElement(sgml,HT_DIV);
div = SGMLToString(sgml);
SGMLSetElement(sgml,HT__DIV);
div_close = SGMLToString(sgml);
element = SGMLNextElement(sgml);
prompt_msg = "Found %d potential alignment issues that may cause";
prompt_msg+= " display issues in Google Chrome.";
prompt_msg+= " Add DIV tags to compensate?";
while(element != ""){
if (IsError(element)){
return ERROR_EXIT;
}
if (FindInString(element,"<div>",0,false)>(-1)){
has_div = true;
}
else{
param = "";
if (FindInString(element,"<div",0,false)>(-1)){
param = SGMLGetParameter(sgml,"float");
}
else{
if (FindInString(element,"<table",0,false)>(-1)){
param = SGMLGetParameter(sgml,"align");
}
}
if (has_div == false){
param = MakeLowerCase(param);
if (param=="right" || param=="left"){
if (fix==true){
sx = SGMLGetItemPosSX(sgml);
sy = SGMLGetItemPosSY(sgml);
ex = SGMLGetItemPosEX(sgml);
ey = SGMLGetItemPosEY(sgml);
content = SGMLFindClosingElement(sgml);
rc = GetLastError();
if (IsError(rc)==false){
ex = SGMLGetItemPosEX(sgml);
ey = SGMLGetItemPosEY(sgml);
WriteSegment(edit_object,div,sx,sy);
if (sy == ey){
ex = ex+5;
}
WriteSegment(edit_object,div_close,ex,ey);
ex = GetLastXPosition(edit_object);
ey = GetLastYPosition(edit_object);
SGMLSetPosition(sgml,ex,ey);
fix_counter++;
}
else{
SGMLSetPosition(sgml,ex,ey);
}
}
counter++;
}
}
else{
has_div = false;
}
}
element = SGMLNextElement(sgml);
}
CloseHandle(edit_object);
CloseHandle(sgml);
CloseHandle(edit_window);
if (fix == true){
MessageBox('i',"Corrected %d objects.",fix_counter);
}
else{
if (counter>0){
rc = YesNoBox('Q',prompt_msg,counter);
if (rc==IDYES){
run(f_id,mode,true);
}
}
}
return ERROR_NONE;
}
Our script has our three functions: setup, main, and run. The setup and main functions are similar to previous blog posts concerning hooking custom functions into GoFiler menu items and won’t be covered in great depth here. They serve to hook our function into a couple menu functions: EDGAR_VALIDATE and REVIEW_DISPLAY_ERRORS. Let’s examine the run function in greater detail, as it’s the one that does our work this week.
It’s important to note that our run function operates in two modes: checking and fixing. The fixing mode is triggered only after the user is alerted as to the number of tags to fix and agrees to fixing them. This is controlled by the fix boolean flag, which is passed into the run function. In Legato, a user function, such as the one we are describing here, may be called with partial parameters. In this case, a warning is generated and default values (false in the case of our fix flag) are used. As long as all the supplied parameters are the correct type, execution will continue. This can be useful for creating overloads and functions that can be called from the API (such as the menu hook here) and from within your own code with varying options.
Also note that Legato booleans are defined as false by default, which is true as well for the is_div, is_table, and has_div variables. After defining our local variables, our function first examines the mode of the call from the menu hook by checking the mode variable passed into it. In this case, we want to be in a postprocess mode, so if we are not, we exit without processing further. Using the GetActiveEditWindow and GetEditWindowType SDK functions, we can get a handle to the current edit window and exit if the edit window is not active. Again, these topics have been covered in previous blog posts.
if (mode!="postprocess"){
return ERROR_NONE;
}
edit_window = GetActiveEditWindow();
if(IsError(edit_window)){
return ERROR_NONE;
}
type = GetEditWindowType(edit_window) & EDX_TYPE_ID_MASK;
if (type!=EDX_TYPE_PSG_PAGE_VIEW && type!=EDX_TYPE_PSG_TEXT_VIEW){
return ERROR_NONE;
}
The first thing we need to do in our script is check to ensure we are running in postproces. This script hooks into our validate functions, so we want to make sure the validator completes running before our script fires. Then, we can use the GetActiveEditWindow to get the active edit window. This function hooks into every single validate, function. So if we can’t get the active edit window, it just means we might be working on a file that doesn’t have one, so we can safely exit without error. Then we can use the GetEditWindowType function to get the type of the edit window, and compare it against the constants EDX_TYPE_PSG_PAGE_VIEW and EDX_TYPE_PSG_TEXT_VIEW. If it’s not in text view or page view, we’re working on a different type of file besides HTML, and we don’t want to run this script, so we exit without error.
edit_object = GetEditObject(edit_window);
sgml = SGMLCreate(edit_object);
SGMLSetElement(sgml,HT_DIV);
div = SGMLToString(sgml);
SGMLSetElement(sgml,HT__DIV);
div_close = SGMLToString(sgml);
element = SGMLNextElement(sgml);
Next, we retrieve a handle to the edit object using the GetEditObject function before creating an SGML object using the SGMLCreate function. This SGML object is going to allow us to parse the contents of our edit window. Using the SGMLSetElement function, we can tell our SGML object what its internal element class should be. HT_DIV and HT__DIV specify a DIV open and close tag, respectively. The SGMLToString function returns a string with the element’s namespace, the element itself, and any attributes. We create DTD-appropriate strings of our open DIV and close DIV tags for use later. Using the SGML object to do this is safer than hard-coding our strings because it ensures they match the document’s DTD, and creates content-aware tags.
We then use the SGMLNextElement function to move to the first element.
We also begin our notification message to the user. Note that this is only really necessary if fix is false, but we’ll mention it like this for simplicity’s sake.
prompt_msg = "Found %d potential alignment issues that may cause";
prompt_msg+= " display issues in Google Chrome.";
prompt_msg+= " Add DIV tags to compensate?";
And now we start parsing.
while(element != ""){
if (IsError(element)){
return ERROR_EXIT;
}
if (FindInString(element,"<div>",0,false)>(-1)){
has_div = true;
}
else{
param = "";
if (FindInString(element,"<div",0,false)>(-1)){
param = SGMLGetParameter(sgml,"float");
}
else{
if (FindInString(element,"<table",0,false)>(-1)){
param = SGMLGetParameter(sgml,"align");
}
}
While there are elements to examine, the script moves through the SGML object. The string for the current tag needs to be examined. We do this in three steps with the FindInString function: 1) we first check if the string contains a plain DIV tag, 2) we then check if the string contains an open DIV tag that also has some attributes, and 3) we finally check if the string contains an open TABLE tag that also has some properties. If the DIV has no properties, we can simply flag that we have a DIV tag. If the DIV tag does have attributes, we use the SGMLGetParameter SDK function to retrieve the parameter values that interest us as strings. For the DIV tags, we want to look at the float parameter. For the TABLE tags, we want to examine the align parameter.
if (has_div == false){
param = MakeLowerCase(param);
if (param=="right" || param=="left"){
if (fix==true){
sx = SGMLGetItemPosSX(sgml);
sy = SGMLGetItemPosSY(sgml);
ex = SGMLGetItemPosEX(sgml);
ey = SGMLGetItemPosEY(sgml);
content = SGMLFindClosingElement(sgml);
rc = GetLastError();
if (IsError(rc)==false){
ex = SGMLGetItemPosEX(sgml);
ey = SGMLGetItemPosEY(sgml);
WriteSegment(edit_object,div,sx,sy);
if (sy == ey){
ex = ex+5;
}
WriteSegment(edit_object,div_close,ex,ey);
ex = GetLastXPosition(edit_object);
ey = GetLastYPosition(edit_object);
SGMLSetPosition(sgml,ex,ey);
fix_counter++;
}
else{
SGMLSetPosition(sgml,ex,ey);
}
}
counter++;
}
}
else{
has_div = false;
}
}
element = SGMLNextElement(sgml);
}
Because we only care about DIV and TABLE tags that are not nested in a plain DIV tag, we check to see if our has_div flag is true. If it is, nothing needs to be done to this tag. If not, we may need to make some adjustments. First we make our parameter string lowercase with the MakeLowerCase function. Now we check to see if the value is “left” or “right”. If it is, we determine whether or not we are fixing or checking.
If we are fixing, we need to get the position of our SGML tag. The SGMLGetItemPosSX, SGMLGetItemPosSY, SGMLGetItemPosEX, and SGMLGetItemPosEY functions retrieve the location of our DIV or TABLE tag. The SGMLFindClosingElement function returns a string of the content up to the closing tag of our element. We don’t really care about the content of the string, but using this function will also move the parser position up to the end of the closing tag, where we’ll need to insert our closing DIV tag. Using the GetLastError function tells us if we could locate the closing tag. If it’s not an error, the parser is at the ending position of the closing DIV or TABLE tag. If it is an error (possibly caused by no matching closing tag), we need to reset our parse position to the end of the opening DIV or TABLE tag. We use the WriteSegment function to write our new DIV tag at the start position of the current tag by handing it our content-aware DIV tag string (div) and the start position (sx, sy). If the closing tag and the starting tag are on the same line, we add five characters to the end position to account for the length of our opening DIV tag, “<DIV>”.
Then we write the closing DIV tag, again with the WriteSegment function only this time we hand it our closing tag (div_close) and the end position of the tag we are trying to nest (ex, ey). The GetLastXPosition and GetLastYPosition functions alert us to the last position that was modified in the Edit Object. We can then hand those locations to the SGMLSetPosition function to move our internal pointer to the end of the tag we’ve just edited. We then increment our fixed_counter.
If we’re not fixing, we increment our general counter to mark that we found a problematic TABLE or DIV tag. If there was no DIV to begin with, we change has_div to false, and move to the next element in the SGML Object.
CloseHandle(edit_object);
CloseHandle(sgml);
CloseHandle(edit_window);
After this, regardless of whether or not the function was simply checking for the problem or fixing it, we no longer need our SGML and Edit Objects. We close all the necessary handles.
Now we take some action depending on if the function is checking or fixing. If the fix flag is true, the MessageBox function alerts the user of how many tags were successfully corrected by nesting them in a new DIV. Otherwise, if we are checking and our counter of problematic tags is larger than zero, we prompt the user with the number of affected elements and ask for confirmation as to whether or not to repair the document. If the user selects YES, we call our run function again, only this time with the fix flag set to true.
if (fix == true){
MessageBox('i',"Corrected %d objects.",fix_counter);
}
else{
if (counter>0){
rc = YesNoBox('Q',prompt_msg,counter);
if (rc==IDYES){
run(f_id,mode,true);
}
}
}
This takes our document that’s not rendering properly in Chrome and gets it where it should be. Until either Google or the SEC corrects this issue, we highly recommend you use a method similar to this one to keep your documents rendering properly in Chrome.
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