• Solutions
    • FERC XBRL Reporting
    • FDTA Financial Reporting
    • SEC Compliance
    • Windows Clipboard Management
    • Legato Scripting
  • Products
    • GoFiler Suite
    • XBRLworks
    • SEC Exhibit Explorer
    • SEC Extractor
    • Clipboard Scout
    • Legato
  • Education
    • Training
    • SEC and EDGAR Compliance
    • Legato Developers
  • Blog
  • Support
  • Skip to blog entries
  • Skip to archive page
  • Skip to right sidebar

Friday, March 09. 2018

LDC #75: Automatic Creation of Previous Versions

One common task when working on an HTML document in GoFiler is to save different versions of the file. Every time a copy is sent out for review, users can do a save as operation, and save a new copy of the file with a different version number. GoFiler by default allows you to create backup copies of the file (.bak files) whenever you press the save button, but that’s often too many backups, and they don’t represent true versions. The example script in this blog post then attempts to automate the process of creating different versions of files. This script will trigger when the user presses the “To Browser” button on an HTML file, and ask if the user wants to create a previous version folder of this file. If the user presses “Yes”, the script makes a new folder called “Revisions”, puts a folder stamped with the version number, date, and time into it, and copies all the HTML and images out of your folder into this previous version folder.


Our full sample script is below:



/* AutomateProofVersions.ms
 *
 * Author: Steven Horowitz
 *
 * Rev 01       SCH             3/06/18
 *
 */

#define         REV_FOLDER_NM                   "Revisions"
#define         REV_INSTANCE_FOLDER_NM          "v%03d (%s)"
#define         NEW_REV_QUERY                   "Would you like to create a previous version folder for this file?"
#define         CANNOT_CREATE_FOLDER_MSG        "Cannot create target folder."
#define         FOLDER_CREATE_MSG               "New version folder created at %s."
#define         TIME_TEMPLATE                   "m-d-y Gi"

void            setup();
void            run(int f_id, string mode, handle window);
int             get_version_num(string foldername);

void setup(){

    string              fn;

    fn = GetScriptFilename();
    MenuSetHook("FILE_LAUNCH", fn, "run");
    }

void main(){

    int                 ix;
    int                 size;
    string              windows[][];
    handle              window;

    if (GetScriptParent() == "LegatoIDE"){
      windows = EnumerateEditWindows();
      size = ArrayGetAxisDepth(windows);
      for (ix = 0 ; ix < size; ix++){
        if (windows[ix]["FileTypeToken"] == "FT_HTML"){
          run (0,"preprocess", MakeHandle(windows[ix]["ClientHandle"]));
          }
        }
      }
    setup();
    }

void run(int f_id, string mode, handle window){

    dword               wType;
    int                 rc;
    string              folder;
    string              datestamp;
    string              file_content;
    string              filename;
    string              rev_folder;
    string              other_files[];
    string              new_rev_folder;
    string              new_foldername;
    string              rev_folders[];
    string              ext;
    int                 num_files;
    int                 num_folders;
    int                 next_version;
    int                 version;
    int                 ix;

    if (mode != "preprocess"){
      return;
      }
    if (IsWindowHandleValid(window)==false){
      window = GetActiveEditWindow();
      wType = GetEditWindowType(window) & EDX_TYPE_ID_MASK;
      if (wType != EDX_TYPE_PSG_PAGE_VIEW){
        return;
        }
      }
    rc = YesNoBox('q',NEW_REV_QUERY);
    if (rc != IDYES){
      return;
      }

    filename = GetEditWindowFilename(window);
    folder = GetFilePath(filename);
    rev_folder = AddPaths(folder,REV_FOLDER_NM);
    if (IsFolder(rev_folder)==false){
      rc = CreateFolder(rev_folder);
      if (IsError(rc)){
        MessageBox('x',CANNOT_CREATE_FOLDER_MSG);
        return;
        }
      }
    rev_folders = EnumerateFolders(AddPaths(rev_folder,"*.*"));
    num_folders = ArrayGetAxisDepth(rev_folders);
    next_version = 0;
    for(ix=0; ix<num_folders; ix++){
      version = get_version_num(rev_folders[ix]);
      if (version >= next_version && version>0){
        next_version = version;
        }
      }
    next_version++;
    datestamp = FormatDate(GetLocalTime(),TIME_TEMPLATE);
    new_foldername = FormatString(REV_INSTANCE_FOLDER_NM,next_version,datestamp);
    new_foldername = AddPaths(rev_folder,new_foldername);
    rc = CreateFolder(new_foldername);
    MessageBox('i',FOLDER_CREATE_MSG,new_foldername);
    if (IsError(rc)){
      MessageBox('x',CANNOT_CREATE_FOLDER_MSG);
      return;
      }

    other_files = EnumerateFiles(AddPaths(folder,"*.html;*.htm;*.jpg;*.gif"));
    num_files = ArrayGetAxisDepth(other_files);
    for(ix=0; ix<num_files; ix++){
      filename = GetFilename(other_files[ix]);
      CopyFile(AddPaths(folder,filename),AddPaths(new_foldername,filename));
      }
    }


int get_version_num(string foldername){

    string              version;
    int                 rc;
    int                 val;
    int                 vpos;

    vpos = FindInString(foldername, "v");
    version = GetStringSegment(foldername, vpos+1, 3);
    val = DecimalToInteger(version);
    rc = GetLastError();
    if (IsError(rc)){
      return (-1);
      }
    return val;
    }


First, let’s take a look at the defined values. I tried to make this as customizable as possible, so using defines for things like folder name templates makes it easy to change or modify them in the future. REV_FOLDER_NM is the name of the master revisions folder everything goes into. REV_INSTANCE_FOLDER_NM is the template used for the name of a specific revision. The “v%03d” is important to keep together so you have a 3 digit revision number like “v004”, and the second %s is very important to keep there because that’s where the date time stamp goes. Other than that, things can be added or removed from this template. NEW_REV_QUERY is just the question that pops up when the user is prompted for making a new revision folder. The CANNOT_CREATE_FOLDER_MSG and FOLDER_CREATE_MSG defines are pretty self explanatory, they are the messages that show up when a new folder cannot be created, or was created respectively. Finally, TIME_TEMPLATE is the template used when making the individual revision folders. It’s the date format string inserted into the string parameter of the REV_INSTANCE_FOLDER_NM define.



#define         REV_FOLDER_NM                   "Revisions"
#define         REV_INSTANCE_FOLDER_NM          "v%03d (%s)"
#define         NEW_REV_QUERY                   "Would you like to create a previous version folder for this file?"
#define         CANNOT_CREATE_FOLDER_MSG        "Cannot create target folder."
#define         FOLDER_CREATE_MSG               "New version folder created at %s."
#define         TIME_TEMPLATE                   "m-d-y Gi"


The main function is very simple, we just want something to cycle through all open windows, and if it finds an HTML window, run our script on it. This is purely for testing purposes, and exactly how this works has been covered in previous blog posts.



void main(){

    int                 ix;
    int                 size;
    string              windows[][];
    handle              window;

    if (GetScriptParent() == "LegatoIDE"){
      windows = EnumerateEditWindows();
      size = ArrayGetAxisDepth(windows);
      for (ix = 0 ; ix < size; ix++){
        if (windows[ix]["FileTypeToken"] == "FT_HTML"){
          run (0,"preprocess", MakeHandle(windows[ix]["ClientHandle"]));
          }
        }
      }
    setup();
    }


I’m skipping the setup function, because all that does is hook our script to the proof to browser function and is pretty obvious in how it works. The run function is where all the fun stuff happens. First, we need to check to ensure we’re running in preprocess mode. Then, we can check to see if we were passed a valid handle. This would only happen if running from the IDE for debugging. If we’re not debugging, we need to grab the active edit window, and check if it’s a page view. If so, we can go ahead and use it. Otherwise, we can return here. After we have our window, we can ask the user with a YesNoBox function if they want to create a new version. If they click anything but yes, we can exit here. Otherwise, we need to get the filename and folder of the HTML file we’re in, and build the path to our revisions folder. If that folder doesn’t exist, we need to try to create it using the CreateFolder function. Whenever we create a folder, we always need to use IsError on the resulting return code to ensure that it actually created correctly. There are many reasons why a folder might not be able to be created, so it’s best to test to make sure it actually worked.



void run(int f_id, string mode, handle window){

    ....omitted declarations...

    if (mode != "preprocess"){
      return;
      }
    if (IsWindowHandleValid(window)==false){
      window = GetActiveEditWindow();
      wType = GetEditWindowType(window) & EDX_TYPE_ID_MASK;
      if (wType != EDX_TYPE_PSG_PAGE_VIEW){
        return;
        }
      }
    rc = YesNoBox('q',NEW_REV_QUERY);
    if (rc != IDYES){
      return;
      }

    filename = GetEditWindowFilename(window);
    folder = GetFilePath(filename);
    rev_folder = AddPaths(folder,REV_FOLDER_NM);
    if (IsFolder(rev_folder)==false){
      rc = CreateFolder(rev_folder);
      if (IsError(rc)){
        MessageBox('x',CANNOT_CREATE_FOLDER_MSG);
        return;
        }
      }


Now that we have our folder, we can use EnumerateFolders on our revisions folder to get all the different version folder names. We can use ArrayGetAxisDepth to get the number of those folders, then iterate over each of them. We’re just looking for the next version number, so for each folder we need to run our get_version_num function to get the version, and if it’s greater than or equal to the next_version variable, we can reset next_version to it’s value and keep going on. This should ensure that next_version will equal the highest version in the folder. We can then add one because we’re making a new version, get a datestamp by using the FormatDate and GetLocalTime functions, and build our new folder name. Once we have our new foldername, we can use AddPath to make it a complete path, and run the CreateFolder function again, being sure to let the user know we created a new folder, and also checking to make sure we actually created the folder.



    rev_folders = EnumerateFolders(AddPaths(rev_folder,"*.*"));
    num_folders = ArrayGetAxisDepth(rev_folders);
    next_version = 0;
    for(ix=0; ix<num_folders; ix++){
      version = get_version_num(rev_folders[ix]);
      if (version >= next_version && version>0){
        next_version = version;
        }
      }
    next_version++;
    datestamp = FormatDate(GetLocalTime(),TIME_TEMPLATE);
    new_foldername = FormatString(REV_INSTANCE_FOLDER_NM,next_version,datestamp);
    new_foldername = AddPaths(rev_folder,new_foldername);
    rc = CreateFolder(new_foldername);
    MessageBox('i',FOLDER_CREATE_MSG,new_foldername);
    if (IsError(rc)){
      MessageBox('x',CANNOT_CREATE_FOLDER_MSG);
      return;
      }


All that’s left to do now is to copy the contents of all files from our current folder into our revisions folder. We need to use EnumerateFiles to get all html, htm, jpg, and gif files first. Then we can iterate over each, and use CopyFile to copy it into it’s new location.


    
    other_files = EnumerateFiles(AddPaths(folder,"*.html;*.htm;*.jpg;*.gif"));
    num_files = ArrayGetAxisDepth(other_files);
    for(ix=0; ix<num_files; ix++){
      filename = GetFilename(other_files[ix]);
      CopyFile(AddPaths(folder,filename),AddPaths(new_foldername,filename));
      }
    }


The get_version_num function’s job is to take a folder name, and return the version number from it. It’s pretty simple, it needs to get the position of the “v” character in the folder name, then get the three numbers after it, and convert them to an integer. If the last error was an error, then it means the folder didn’t have a valid version number or we couldn’t get the version number, so we can return -1, which our run function will handle accordingly.



int get_version_num(string foldername){

    string              version;
    int                 rc;
    int                 val;
    int                 vpos;

    vpos = FindInString(foldername, "v");
    version = GetStringSegment(foldername, vpos+1, 3);
    val = DecimalToInteger(version);
    rc = GetLastError();
    if (IsError(rc)){
      return (-1);
      }
    return val;
    }


Automation tasks like this are where Legato truly shines. If you have some very basic task that you want to insure is always done, taking a few hours to write a script for it will save a lot of time (and headache, if someone forgets to do it) over manually creating revision folders like this. Really any simple, repetitive task is a great application for Legato.


 


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 



Posted by
Steven Horowitz
in Development at 16:55
Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)
No comments
Add Comment
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
   
 

Quicksearch

Categories

  • XML Accounting
  • XML AICPA News
  • XML FASB News
  • XML GASB News
  • XML IASB News
  • XML Development
  • XML Events
  • XML FERC
  • XML eForms News
  • XML FERC Filing Help
  • XML Filing Technology
  • XML Information Technology
  • XML Investor Education
  • XML MSRB
  • XML EMMA News
  • XML FDTA
  • XML MSRB Filing Help
  • XML Novaworks News
  • XML GoFiler Online Updates
  • XML GoFiler Updates
  • XML XBRLworks Updates
  • XML SEC
  • XML Corporation Finance
  • XML DERA
  • XML EDGAR News
  • XML Investment Management
  • XML SEC Filing Help
  • XML XBRL
  • XML Data Quality Committee
  • XML GRIP Taxonomy
  • XML IFRS Taxonomy
  • XML US GAAP Taxonomy

Calendar

Back May '25 Forward
Mo Tu We Th Fr Sa Su
Sunday, May 18. 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Feeds

  • XML
Sign Up Now
Get SEC news articles and blog posts delivered monthly to your inbox!
Based on the s9y Bulletproof template framework

Compliance

  • FERC
  • EDGAR
  • EMMA

Software

  • GoFiler Suite
  • SEC Exhibit Explorer
  • SEC Extractor
  • XBRLworks
  • Legato Scripting

Company

  • About Novaworks
  • News
  • Site Map
  • Support

Follow Us:

  • LinkedIn
  • YouTube
  • RSS
  • Newsletter
  • © 2024 Novaworks, LLC
  • Privacy
  • Terms of Use
  • Trademarks and Patents
  • Contact Us