Introduction
To add links, every day or two I walk through the news on various search sites, look at some related clubs in the US and UK and also look at stuff on YouTube. Once I find a story, I add the link:
1. Copy the story title to the home page.
2. Add a citation like “(ARRL 01/24/2020)” in small type next to the link.
3. Copy the link URL and then use the Insert Hyperlink (which in GoFiler does not like non-SEC links, so a warning message is presented each time).
4. Change the target to “_blank” (again, EDGAR does not allow target so it has to be added manually).
5. Add a class to change the look of the link.
As can be seen, this is a lot of steps. So, what about a script to augment the functionality of GoFiler?
Here’s a dialog I can create:
Which adds the following code to the document at the current caret position:
The K7RA Solar Update (ARRL 01/24/2020)
or in HTML:
<a class="page" target="_blank" href="http://www.arrl.org/news/the-k7ra-solar-update-613">
The K7RA Solar Update</a> <font style="font-size: 8pt">(ARRL 01/24/2020)</font>
Note the class, which is hard coded.
The Script
Ideally the script can be executed as part of the application start up by adding it to the scripts folder or the user scripts extension folder (it must be renamed with the .ms extension).
Let’s look at the script:
/************************************************/
string a_class;
string a_text;
string a_url;
string a_cite;
/************************************************/
int setup() {
string fnScript;
string item[10];
int rc;
item["Code"] = "EXTENSION_QUICK_WEB_LINK";
item["MenuText"] = "&Quick Web Link";
item["Description"] = "<B>Quick Web Link</B>\r\rAdds a custom external web hypertext link.";
item["Class"] = "DocumentExtension";
rc = MenuFindFunctionID(item["Code"]);
if (IsNotError(rc)) { return ERROR_NONE; }
rc = MenuAddFunction(item);
if (IsError(rc)) { return ERROR_NONE; }
fnScript = GetScriptFilename();
MenuSetHook(item["Code"], fnScript, "run");
ArrayClear(item);
item["KeyCode"] = "L_KEY_CONTROL";
item["ChordAKey"] = "Q";
item["FunctionCode"] = "EXTENSION_QUICK_WEB_LINK";
item["Description"] = "Insert quick keb link";
QuickKeyRegister("Page View", item);
return ERROR_NONE;
}
/************************************************/
int main() {
string s1;
int rc;
s1 = GetScriptParent();
if (s1 == "LegatoIDE") {
rc = MenuFindFunctionID("EXTENSION_QUICK_WEB_LINK");
if (IsError(rc)) {
setup();
}
else {
MenuDeleteHook("EXTENSION_QUICK_WEB_LINK");
s1 = GetScriptFilename();
MenuSetHook("EXTENSION_QUICK_WEB_LINK", s1, "run");
}
MessageBox('i', "Hook running on IDE");
}
return ERROR_NONE;
}
/************************************************/
int run(int f_id, string mode) {
handle hEO;
string code;
string s1;
int c_x, c_y,
rc;
if (mode != "preprocess") { return ERROR_NONE; }
hEO = GetActiveEditObject();
if (hEO == NULL_HANDLE) { return ERROR_CONTEXT; }
rc = GetSelectMode(hEO);
if (rc != EDO_NOT_SELECTED) {
MessageBox('x', "Deselect to use this function");
return ERROR_CANCEL;
}
c_y = GetCaretYPosition(hEO); c_x = GetCaretXPosition(hEO);
rc = DialogBox("LinkQuery01Dlg", "lq_");
if (IsError(rc)) { return rc; }
a_class = "page";
code = "<a " +
"class=\"" + a_class + "\" " +
"target=\"_blank\" " +
"href=\"" + a_url + "\"" +
">";
code += ANSITextToXML(a_text);
code += "</a>";
code += " <font style=\"font-size: 8pt\">";
code += ANSITextToXML(a_cite);
code += "</font>";
WriteSegment(hEO, code, c_x, c_y);
return ERROR_NONE;
}
/************************************************/
#beginresource
#define ASL_URL 201
#define ASL_URL_LOOKUP 202
#define ASL_TEXT 203
#define ASL_CITE 204
LinkQuery01Dlg DIALOGEX 0, 0, 280, 118
STYLE DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "Add Quick Web Link"
FONT 8, "MS Shell Dlg"
{
CONTROL "Link To:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 4, 30, 8, 0
CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 36, 9, 236, 1, 0
CONTROL "&URL:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 12, 18, 30, 8
CONTROL "", ASL_URL, "edit", ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 16, 170, 12
//CONTROL "Look Up", ASL_URL_LOOKUP, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 225, 16, 40, 12, 0
CONTROL "Parameters:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 6, 37, 40, 8, 0
CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 46, 42, 226, 1, 0
CONTROL "&Text:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 12, 53, 30, 8, 0
CONTROL "", ASL_TEXT, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 51, 220, 12, 0
CONTROL "&Cite:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 12, 69, 30, 8, 0
CONTROL "", ASL_CITE, "edit", ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 45, 67, 220, 12, 0
CONTROL "", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 6, 90, 268, 1, 0
CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 168, 96, 50, 14
CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 223, 96, 50, 14
}
#endresource
int lq_validate() {
string parts[];
string s1;
int rc;
a_url = EditGetText(ASL_URL, "URL", EGT_FLAG_REQUIRED);
rc = GetLastError();
if (IsError(rc)) { return rc; }
parts = GetURIComponents(a_url);
s1 = MakeLowerCase(parts["scheme"]);
if ((s1 != "http:") && (s1 != "https:")) {
MessageBox('x', "Need an HTTP or HTTPS scheme on link.");
return ERROR_SOFT | ASL_URL;
}
a_text = EditGetText(ASL_TEXT, "Text", EGT_FLAG_REQUIRED);
rc = GetLastError();
if (IsError(rc)) { return rc; }
a_cite = EditGetText(ASL_CITE, "Cite", EGT_FLAG_REQUIRED);
rc = GetLastError();
if (IsError(rc)) { return rc; }
return ERROR_NONE;
}
Let’s break this down:
The Globals
Four variables are global, all used for passing information out of the dialog: a_class, a_text, a_url and a_cite. Remember, the class variable is hard coded.
The Setup
When run as a menu script, or from the IDE, the setup function creates a custom menu function and adds a quick key. The quick key is added to the key chord combination for linking as Ctrl+L Q.
Just a reminder, when the application is initializing, it looks for in specific folders for menu scripts (*.ms) and runs the setup() function. It does not run the main() function.
The first function called is MenuAddFunction, only after checking that it has not already been added. This test is also performed during the main() function. The particulars are added to an array and then to the application’s function list. The next thing to do is add the hook using MenuSetHook. The hook causes our script (this script) to be added and then the run() function is called on execution of the specified menu item. This means the script cannot be run from the IDE as an untitled window (save the file before experimenting with this).
The extension is added to the Document ribbon:
Finally, a quick key is added. I like to quickly hit a key to start a function rather than hunting through the ribbons and menus. Note that it is only added to Page View.
The Main
This function is here only to support the debugging process to allow the script to be worked on in the IDE. If the function is already defined, it will delete the hook and re-hook to the IDE copy. This allows the script to be edited and then run repeatedly.
A couple of important notes: First, it only needs to be re-hooked once. So, testing the script simply involves saving the script (the hook reloads the file if the date, time or size changes). Second, the hook does not directly display errors. The hook just will not work! So preprocess the script for errors, use the trace function and other tricks like message boxes to see what is going on. Also, when working on tricky code, you can remove it from the script and make a jig so you can step through with the debugger.
Finally, note that main() does not run the linking function. It must be run from the application menu.
Run
This is the heart of the program. As one can see, not much is going on here. First, we only want to run on menu preprocess, so others are ignored. The next thing is to get the Edit Object for our window. Ideally, we should make sure we are running on an HTML file, but, the hook is on the Document | Tools menu, and that ribbon only shows up in Page View and Text View with HTML, so we can be pretty certain that users won’t be accessing this script from a non-HTML window.
The edit select mode is tested using GetSelectMode and the caret position retrieved using GetCaretYPosition and GetCaretXPosition functions. If the mode is not compatible, the script leaves after notifying the user.
Next the dialog is run. If “ok”, we fall into creating our code. This could be done with the SGML Object, but I decided to just hard code the string data.
Finally, the resulting code is inserted to the active edit window.
Dialog
I have discussed dialogs in a number of other previous blogs so I am not going to delve to deep here. In this case I coded only one function, validate the controls. All the dialog controls are required and the URL must be an “http” or “https” scheme.
If you look carefully, you’ll see a control commented called “Look Up”. This will be uncommented in a follow-up blog to fill in all the information by retrieving the web page and capturing menu data.
Conclusion
In many ways, this is a perfect example of extending the functionality of the application. This has been on the to do list for quite a while and I plan to start using this right away. I plan to later improve it with the addition of the lookup and a cite table that will fill the text and cite fields for certain common sites.
Here is the result:
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato