This week’s script is another real-world example that was requested by a client. When inserting an image into an EDGAR HTML file, the image has to be in the same folder as the HTML. This script adds a menu command that allows an image to be inserted and copied to the same folder in one step. To do this, we use a lot of SDK functions we’ve discussed in earlier blog posts, however, in this post we also discuss inserting text at the current cursor position.
Friday, March 03. 2017
LDC #24: Inserting Text Into an HTML Edit Window
Like our script last week, this one only has two functions, run and main. Most simple scripts that perform a single operation will have these two functions. There may be more functions if you decide to refactor code into common subroutines. Our example script:
// // GoFiler Complete - Insert non-local image // ------------------------------------ // // Insert Non Local Image // // Revised 03/03/2017 SCH Used to insert an image from a location other than src folder // // (c) 2016 Novaworks, LLC. All rights reserved. // /********************************************************/ /* Global Items */ /* ------------ */ /********************************************************/ int run(int id, string mode); /****************************************/ int setup() { /* Called from Application Startup */ /****************************************/ string fnScript; /* Us */ string item[10]; /* Menu Item */ int rc; /* Return Code */ /* */ /* ** Add Menu Item */ /* * Define Function */ item["MenuText"] = "&Insert Non-Local Image"; /* Menu Text */ item["Description"] = "<B>Insert Image</B>\r\r"; /* Description (long) */ item["Description"]+= "Insert an image from a different folder"; /* Description */ item["Class"] = "Extension"; /* * Check for Existing */ item["Code"] = "HTML_INSERT_IMAGE"; /* Function Code */ rc = MenuFindFunctionID(item["Code"]); /* Look for existing */ if (IsNotError(rc)) { /* Was already be added */ return ERROR_NONE; /* Exit */ } /* end error */ /* * Registration */ rc = MenuAddFunction(item); /* Add the item */ if (IsError(rc)) { /* Was already be added */ return ERROR_NONE; /* Exit */ } /* end error */ fnScript = GetScriptFilename(); /* Get the script filename */ MenuSetHook(item["Code"], fnScript, "run"); /* Set the Test Hook */ return ERROR_NONE; /* */ } /* end setup */ /****************************************/ int main() { /* Default Entry */ /****************************************/ setup(); /* setup script */ return ERROR_NONE; /* */ } /* end setup */ /****************************************/ int run(int f_id, string mode) { /* Call from Hook Processor */ /****************************************/ handle hView, hEdit; /* Window and Edit Object */ dword w_type; /* Window Type */ string s1; /* General */ string htm_folder; /* folder for current file */ string new_img_path; /* path to new image */ string img_path; /* path to image */ string img_name; /* file name of image */ string img_string; /* image string */ int s_x, s_y, e_x, e_y; /* Position */ int ox; /* Outline Index */ int ix, size; /* General */ int rc; /* Return Code */ /* */ /* ** Post Process */ /* * Only on Frontside Hook */ if (mode != "preprocess") { /* Filter out all but preprocess */ return ERROR_NONE; /* Just leave */ } /* end not preprocess */ /* * Get Active Window */ hView = GetActiveEditWindow(); /* Get the active window */ if (hView == NULL_HANDLE) { /* No handle */ MessageBox('x', "Active window required."); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */ w_type = GetEditWindowType(hView); /* Get the window type */ if (IsError(GetLastError())) { /* Error, window may be a child */ hView = GetParentWindow(hView); /* Get the parent */ w_type = GetEditWindowType(hView); /* Try this type */ } /* end parent window */ w_type &= EDX_TYPE_ID_MASK; /* Rip off type */ if ((w_type != EDX_TYPE_PSG_PAGE_VIEW) && /* Must be Page View */ (w_type != EDX_TYPE_PSG_TEXT_VIEW)) { /* or Code View */ MessageBox('x', "Wrong window type. Must be Page View or Code View."); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */ /* o Get the Edit Object */ hEdit = GetEditObject(hView); /* Get the edit object */ if (hEdit == NULL_HANDLE) { /* Problem with handle */ MessageBox('x', "Could not find source window"); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */ /* o Check the File Type */ /* * Perform the Outline */ htm_folder = GetEditObjectFilename(hEdit); /* get htm file */ if (htm_folder == ""){ /* if we cannot get the folder */ MessageBox('x', "Cannot determine destination folder."); /* display error */ return ERROR_EXIT; /* exit */ } /* */ htm_folder = GetFilePath(htm_folder); /* strip filename from image */ img_path = BrowseOpenFile("Select Image File", "Images (jpg,gif)|*.jpg;*.gif");/* get image source */ if (IsError(GetLastError())){ /* if no image picked */ return ERROR_CANCEL; /* cancelled */ } /* */ img_name = GetFilename(img_path); /* get name of image */ new_img_path = AddPaths(htm_folder, img_name); /* new path to image */ if (IsFile(new_img_path)){ /* test for image in folder */ MessageBox('x', "Image already exists in folder."); /* display error */ return ERROR_EXIT; /* return */ } /* */ rc = CopyFile(img_path,new_img_path); /* copy the image to the new location */ if (IsError(rc)){ /* if error */ MessageBox('x', "Error Copying File: %0x", rc); /* display error */ return ERROR_EXIT; /* return error */ } /* get cursor position */ s_x = GetCaretXPosition(hEdit); /* */ s_y = GetCaretYPosition(hEdit); /* */ e_x = s_x; /* set end to current cursor */ e_y = s_y; /* */ img_string = "<IMG SRC=\"%s\" ALT=\"\">"; /* image string base */ img_string = FormatString(img_string, img_name); /* add image to image tag */ WriteSegment(hEdit, img_string, s_x, s_y, e_x, e_y); /* write tag */ CloseHandle(hEdit); /* cleanup */ CloseHandle(hView); /* cleanup */ return ERROR_NONE; /* */ }
As always, whenever we’re doing a hooked script we need to prototype the run function before adding our setup function. The setup function defines the menu text as “Insert Non-Local Image”, gives a description of the function as it will appear on the menu, and hooks our Legato function to that menu item.
int run(int id, string mode); /****************************************/ int setup() { /* Called from Application Startup */ /****************************************/ string fnScript; /* Us */ string item[10]; /* Menu Item */ int rc; /* Return Code */ /* */ /* ** Add Menu Item */ /* * Define Function */ item["MenuText"] = "&Insert Non-Local Image"; /* Menu Text */ item["Description"] = "<B>Insert Image</B>\r\r"; /* Description (long) */ item["Description"]+= "Insert an image from a different folder"; /* Description */ item["Class"] = "Extension"; /* * Check for Existing */ item["Code"] = "HTML_INSERT_IMAGE"; /* Function Code */ rc = MenuFindFunctionID(item["Code"]); /* Look for existing */ if (IsNotError(rc)) { /* Was already be added */ return ERROR_NONE; /* Exit */ } /* end error */ /* * Registration */ rc = MenuAddFunction(item); /* Add the item */ if (IsError(rc)) { /* Was already be added */ return ERROR_NONE; /* Exit */ } /* end error */ fnScript = GetScriptFilename(); /* Get the script filename */ MenuSetHook(item["Code"], fnScript, "run"); /* Set the Test Hook */ return ERROR_NONE; /* */ /* */ } /* end setup */
Now whenever the user clicks on that menu item we’ve added, it will execute our run function. The first part of the run function checks the current execution mode. Any time a menu option is run, the hook function is called both before and after the menu command. Therefore this function is actually called twice. We only want to run it once, though. Since we’re not doing the preprocess part of the function we can simply leave.
if (mode != "preprocess") { /* Filter out all but preprocess */ return ERROR_NONE; /* Just leave */ } /* end not preprocess */ /* * Get Active Window */
Now that we’re sure we are running the function only once, we need to actually get a handle to the HTML edit object with which the user is working. First, we grab the active window by using the GetActiveEditWindow function. After any function like this, we need to check the return value. If the return value from GetActiveEditWindow is NULL_HANDLE, it means there was no edit window open, so our script can just leave right here. If we were able to get the handle to a window, we need to check what type of window it is. The GetEditWindowType function returns the edit window type as a DWORD. We also need to check if this function call resulted in an error using the GetLastError function. If we had an error, it probably means that we’re in a sub window, like code view. In that case, we can use the GetParentWindow function to get the handle to the window’s parent and then try to retrieve the type again.
Once we have the type as a return value, we can get the type of window by using bitwise AND with the EDX_TYPE_ID_MASK and our type variable. This leaves just the type of the window. We can compare that with EDX_TYPE_PSG_PAGE_VIEW and EDX_TYPE_PSGV_TEXT_VIEW to ensure that we are in code view or page view. This is important as we only have code for dealing with those view types. If the type isn’t either of these, we can display an error message and return. After finally confirming that we have a valid window and it has a valid type, we can get our edit object and test to make sure it’s valid. Again, always make sure to test if the functions returned proper handles. It’s better to get errors than to sit around wondering why the script doesn’t work.
hView = GetActiveEditWindow(); /* Get the active window */ if (hView == NULL_HANDLE) { /* No handle */ MessageBox('x', "Active window required."); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */ w_type = GetEditWindowType(hView); /* Get the window type */ if (IsError(GetLastError())) { /* Error, window may be a child */ hView = GetParentWindow(hView); /* Get the parent */ w_type = GetEditWindowType(hView); /* Try this type */ } /* end parent window */ w_type &= EDX_TYPE_ID_MASK; /* Rip off type */ if ((w_type != EDX_TYPE_PSG_PAGE_VIEW) && /* Must be Page View */ (w_type != EDX_TYPE_PSG_TEXT_VIEW)) { /* or Code View */ MessageBox('x', "Wrong window type. Must be Page View or Code View."); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */ /* o Get the Edit Object */ hEdit = GetEditObject(hView); /* Get the edit object */ if (hEdit == NULL_HANDLE) { /* Problem with handle */ MessageBox('x', "Could not find source window"); /* Display error */ return ERROR_CANCEL_AUTO; /* Exit w/error */ } /* end error */
After ensuring that we have a valid edit object, we need to actually find out where this HTML file is stored on the computer or network. The GetEditObjectFilename function will return the full path to the HTML file. If the function returns a blank path, that usually means that the open edit tab is untitled or that something bad has happened. In any case, we won’t be able to move an image into a folder when we don’t know where the folder is located. Given that scenario, we can display an error and return if the name is empty. If the name is valid, we can get the path of the containing folder by using the GetFilePath function.
htm_folder = GetEditObjectFilename(hEdit); /* get htm file */ if (htm_folder == ""){ /* if we cannot get the folder */ MessageBox('x', "Cannot determine destination folder."); /* display error */ return ERROR_EXIT; /* exit */ } /* */ htm_folder = GetFilePath(htm_folder); /* strip filename from image */
Now we need the image file to be included in the HTML. The BrowseOpenFile function opens a browse file window to allow the user to choose an image file. It uses file masks to only look for .jpg and .gif images. These are the only types acceptable in EDGAR. We need to test if there was an error when picking the image, and if so, we can assume the user cancelled. The most common type of error code from this function would be ERROR_CANCEL if the user hits cancel on the browse window, but in any case, if there’s an error we can’t continue, so we could treat them as the same result programmatically.
After getting the path to our image, we can use the GetFilename and AddPaths functions to create the destination filename of where the image file needs to be located. We need to test if a file with the same name already exists in the target folder as well. I decided it would be best to display an error if the image already exists and end the script, but the script could be modified to overwrite the existing file.
img_path = BrowseOpenFile("Select Image File", "Images (jpg,gif)|*.jpg;*.gif");/* get image source */ if (IsError(GetLastError())){ /* if no image picked */ return ERROR_CANCEL; /* cancelled */ } /* */ img_name = GetFilename(img_path); /* get name of image */ new_img_path = AddPaths(htm_folder, img_name); /* new path to image */ if (IsFile(new_img_path)){ /* test for image in folder */ MessageBox('x', "Image already exists in folder."); /* display error */ return ERROR_EXIT; /* return */ } /* */
So now we have all three pieces: the edit object to the HTML file, a source image, and a destination image location. Next, we need to copy the image to the destination. Whenever copying or moving files around, it’s very important to check the return code of the operation. Errors like insufficient permissions to copy files are very common in operations like this, so we need to explicitly get the return code of the CopyFile function, test if it was an error, and if so display the error to the user for troubleshooting.
rc = CopyFile(img_path, new_img_path); /* copy the image to the new location */ if (IsError(rc)){ /* if error */ MessageBox('x', "Error Copying File: %0x",rc); /* display error */ return ERROR_EXIT; /* return error */ } /* get cursor position */
The very last thing we have to do is actually insert our the reference to the image into the HTML file. When writing out a segment of data to a file, the simplest way to do this is to use the WriteSegment function. This requires the start and end positions of the segment in the document we are overwriting. Our start positions are going to be s_x and s_y variables, and the end positions we’ll call e_x and e_y. We can use the GetCaretXPosition and GetCaretYPosition functions to get the s_x and s_y values, and because we’re not overwriting anything, e_x and e_y will be the same. We also need the data to actually insert, in this case a link to our newly copied image file. There are a whole suite of functions in Legato for generating HTML code but in this case just creating code using FormatString is sufficient. Once we have the start and end positions, as well as the <img> tag referencing our copied file, we can put it all together and call the WriteSegment function to actually insert the text at our cursor position. Then all we need to do is close the handles we’ve opened, and we’re done!
s_x = GetCaretXPosition(hEdit); /* */ s_y = GetCaretYPosition(hEdit); /* */ e_x = s_x; /* set end to current cursor */ e_y = s_y; /* */ img_string = "<IMG SRC=\"%s\" ALT=\"\">"; /* image string base */ img_string = FormatString(img_string,img_name); /* add image to image tag */ WriteSegment(hEdit,img_string,s_x,s_y,e_x,e_y); /* write tag */ CloseHandle(hEdit); /* cleanup */ CloseHandle(hView); /* cleanup */ return ERROR_NONE; /* */
It is important to note that the script does not check if an <img> tag can be inserted in this location (e.g. if the caret is inside another tag). We could further refine the script to do so but it is outside the scope of this blog.
This script inserts an image tag into a file, but it could really be used to insert any text or HTML code at your cursor position. If there are common templates or snippets of code that you regularly use, they can all be inserted this same way. Combined with previous blog posts you could connect to an SQL database, retrieve information, format it into a table, insert the table into the HTML file, and then run the Table Polish function on it. This script is intended to be a very basic example, but the ability to create and insert sections of HTML via scripting is very flexible and powerful.
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