Last week, our support desk here at Novaworks received an interesting email regarding the spacing between ampersand characters (‘&’) and the rest of the line. This space was lost when the document was printed. Through some testing, we’ve discovered that when printing a document from Internet Explorer to PDF or a normal printer, if the first character on a line of code begins a character entity or is an ampersand then the return on the line above it will not be treated as whitespace for printing purposes but instead ignored. Take the following code for example:
Friday, July 14. 2017
LDC #43: Character Entity Spacing Issue When Printing
<P> The Cat & The Hat</P>
When viewed in a web browser, or in GoFiler, the code would render as a normal paragraph, and it would look like this:
The Cat & The Hat
However, when printed from Internet Explorer to our physical printer here, it renders like this:
The Cat& The Hat
This is obviously not right and should be corrected. While it has absolutely no bearing on the EDGAR compliance of a document or the validity of an HTML file, people still may print the document in question, and it’s a trivial enough problem to fix. The resolution is to simply put an extra whitespace character before the ampersand character on the second line of code. It’s a pretty simple thing to do, but to expect a user to do it for every file is a bit much.
So it’s a great opportunity to write a new Legato script! The one we’re covering this week has hooks into the Save and Save As functions of GoFiler instead of as a new menu item so that it runs whenever a user saves a file. If the file is open in Page View or Code View, it will scan it to see if it has the problem with a line starting with a character entity, and if so, prompt the user for action. If the user wants to fix it, our script simply places a new space on the required lines and then saves the file.
Our script for this week:
// // // GoFiler Legato Script - Character Entity Spaces // ------------------------------------------ // // Rev 07/13/2017 // // (c) 2017 Novaworks, LLC -- All rights reserved. // // Compensates for issue printing HTML with Character Entities as first character on line in IE #define ISSUES_WRN "Found %d potential spacing issues that may appear when printing from IE. Add spaces to compensate?" #define FIXED_MSG "Corrected %d potential spacing issues in this file." int run (int f_id, string mode, boolean correct);/* Call from Hook Processor */ /****************************************/ int setup() { /* Called from Application Startup */ /****************************************/ string fnScript; /* Us */ int rc; /* Return Code */ /* */ fnScript = GetScriptFilename(); /* Get the script filename */ MenuSetHook("FILE_SAVE", fnScript, "run"); /* Set the Test Hook */ MenuSetHook("FILE_SAVE_AS", fnScript, "run"); /* Set the Test Hook */ return ERROR_NONE; /* Return value (does not matter) */ } /* end setup */ /****************************************/ int main() { /* Initialize from Hook Processor */ /****************************************/ setup(); /* Add to the menu */ return ERROR_NONE; /* Return value (does not matter) */ } /* end setup */ /****************************************/ int run(int f_id, string mode, boolean correct) { /* Call from Hook Processor */ /****************************************/ handle edit_window; /* active edit window */ handle text; /* mapped text object */ string line; /* line of text */ int issues; /* number of potential issues in file */ int fixed; /* number of fixed issues */ int response; /* user response */ int ix; /* counter */ int rc; /* return code */ dword type; /* type of window */ /* */ issues = 0; /* reset issues counter */ fixed = 0; /* reset fixed counter */ ix = 0; /* reset counter */ if (mode!="preprocess"){ /* if not preprocess */ return ERROR_NONE; /* return without error */ } /* */ edit_window = GetActiveEditWindow(); /* get handle to edit window */ if(IsError(edit_window)){ /* get active edit window */ return ERROR_NONE; /* return */ } /* */ type = GetEditWindowType(edit_window) & EDX_TYPE_ID_MASK; /* get the type of the window */ if (type!=EDX_TYPE_PSG_PAGE_VIEW && type!=EDX_TYPE_PSG_TEXT_VIEW){ /* and make sure type is HTML or Code */ return ERROR_NONE; /* return error */ } /* */ text = GetEditObject(edit_window); /* get text of window */ line = ReadLine(text,ix); /* read the first line of the file */ rc = ERROR_NONE; /* set default error code */ while (IsNotError(rc)){ /* while we have a next line */ if (GetStringSegment(line,0,1)=="&"){ /* if first char of line is ampersand */ line = " "+line; /* append space to start of line */ if (correct == true){ /* if we're correcting it */ WriteSegment(text,line,0,ix,GetStringLength(line)-1,ix); /* write out new line segment */ fixed ++; /* increment fixed counter */ } /* */ else{ /* */ issues ++; /* increment number of issues in file */ } /* */ } /* */ ix++; /* increment line counter */ line = ReadLine(text,ix); /* read the next line */ rc = GetLastError(); /* get result of read */ } /* */ if (issues>0){ /* if any potential issues in file */ response = YesNoBox('q',ISSUES_WRN,issues); /* ask user for input */ if (response == IDYES){ /* if fixing */ run (f_id, mode, true); /* re-run, but fix this time */ } /* */ } /* */ if (fixed>0){ /* if we fixed some issues */ MessageBox('x',FIXED_MSG,fixed); /* display message */ return ERROR_NONE; /* return no error */ } /* */ return ERROR_NONE; /* return no error */ } /* end run */
As we usually do, let’s start with a couple of defines that contain our messages to the user, one that alerts him/her to the extent of the problem and another to indicate the script has corrected it. We also have our three functions: main, setup, and run. Our main function simply calls our setup function, and our setup function creates our menu function hooks.
/****************************************/ int setup() { /* Called from Application Startup */ /****************************************/ string fnScript; /* Us */ int rc; /* Return Code */ /* */ fnScript = GetScriptFilename(); /* Get the script filename */ MenuSetHook("FILE_SAVE", fnScript, "run"); /* Set the Test Hook */ MenuSetHook("FILE_SAVE_AS", fnScript, "run"); /* Set the Test Hook */ return ERROR_NONE; /* Return value (does not matter) */ } /* end setup */
This time we are hooking our run function into menu functions (“Save” and “Save As”) that already exist, so we can just use the GetScriptFilename and MenuSetHook functions to set up the connection and return without an error.
Now let’s take a look at the run function itself. We first define the variables we’ll need, including handles to the edit window and edit object, as well as counting variables. Our counting variables we’ll set to zero before checking to make sure our mode, handed to the run function by the call from the menu hook, is preprocess. After that, we’ll gather some handles we need to what’s being saved.
edit_window = GetActiveEditWindow(); /* get handle to edit window */ if(IsError(edit_window)){ /* get active edit window */ return ERROR_NONE; /* return */ } /* */ type = GetEditWindowType(edit_window) & EDX_TYPE_ID_MASK; /* get the type of the window */ if (type!=EDX_TYPE_PSG_PAGE_VIEW && type!=EDX_TYPE_PSG_TEXT_VIEW){ /* and make sure type is HTML or Code */ return ERROR_NONE; /* return error */ } /* */
We use the GetActiveEditWindow function to obtain a handle to the the active edit window. If the handle is invalid, we return with an error. Otherwise, we acquire the type of edit window with the GetEditWindowType function and bitwise AND it with EDX_TYPE_ID_MASK. That allows us to determine if the edit window contains HTML or text, and if it doesn’t, we can return with no error.
text = GetEditObject(edit_window); /* get text of window */ line = ReadLine(text,ix); /* read the first line of the file */ rc = ERROR_NONE; /* set default error code */ while (IsNotError(rc)){ /* while we have a next line */ if (GetStringSegment(line,0,1)=="&"){ /* if first char of line is ampersand */ line = " "+line; /* append space to start of line */ if (correct == true){ /* if we're correcting it */ WriteSegment(text,line,0,ix,GetStringLength(line)-1,ix); /* write out new line segment */ fixed ++; /* increment fixed counter */ } /* */ else{ /* */ issues ++; /* increment number of issues in file */ } /* */ } /* */ ix++; /* increment line counter */ line = ReadLine(text,ix); /* read the next line */ rc = GetLastError(); /* get result of read */ } /* */
Using our handle to the edit window, we can retrieve a handle to the edit object associated with it. using the GetEditObject function. Now let’s read the first line with the ReadLine function and store it in our line string variable. We set our rc variable to ERROR_NONE before we begin parsing. Our while loop checks rc with each iteration and continues so long as it’s not an error. If the first character in the line is an ampersand (which we check with the GetStringSegment function), we continue. We add space to the front of the line. Now we check our boolean correct flag that is passed to the run function. If this is true, we correct the problem with the WriteSegment function, writing our adjusted line back out to the edit object with the space added in the front. The fixed counter is incremented. Otherwise, we’re on our first call to the run function, and we’re assessing the extent of the problem, so we count this instance with issues counter. We finish our loop code by reading the next line with the ReadLine function, and should that return an error, we set rc and allow the while loop to exit.
if (issues>0){ /* if any potential issues in file */ response = YesNoBox('q',ISSUES_WRN,issues); /* ask user for input */ if (response == IDYES){ /* if fixing */ run (f_id, mode, true); /* re-run, but fix this time */ } /* */ } /* */ if (fixed>0){ /* if we fixed some issues */ MessageBox('x',FIXED_MSG,fixed); /* display message */ return ERROR_NONE; /* return no error */ } /* */ return ERROR_NONE;
Our run function completes by alerting the user. If issues is larger than zero, it’s our first run through where we are counting the number of bad lines. We prompt the user with the YesNoBox function, indicating the number of problems and asking him/her to continue with fixing them. If the Yes button is pressed, we call the run function again, only with the correct flag set to true.
If the fixed counter is larger than zero, then we have already corrected the problems. We can use the MessageBox function to alert the user that the script was successful and indicate how many lines were fixed.
So this simple function performs what would have been a tedious and time-consuming task to do by hand. Every line that begins with an ampersand is adjusted to have an extra white space placed in front of it to prevent it from printing incorrectly. Legato is particularly well adapted to these sorts of the menial, repetitive operations, and it can make changes like these easy and quick to do.
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