Cleaning a dirty file path in Servoy.

Cleaning a dirty file path in Servoy.

Sometimes you need to import or export files into Servoy. And if the user gets to determine the filename and path, then it’s probably best to validate it a bit first.

Also, Servoy can work with various path types, URL, Windows, UNIX/Mac etc. So it’d also be nice to make sure we don’t just dump a Windows C:\whatever.txt type path to a Mac!

So I made a little class to ‘clean’ dirty paths for me.

I thought cleaning a path would be fairly straightforward, but it turned out to be a fairly complex problem to solve. So I split the path down into it’s constituent parts, then validated it.

So, you don’t need to just use an object created with this class to clean a path, you can also use it to split the path down and manipulate the parts any way you wish.

It should support URLs, Windows/UNIX file paths, and server file paths.

This is very old code, and predates my ‘standardisation’ of structuring ‘classes’ in Servoy. It also suffers from what I call ‘JavaScript .this disease’

It was never used for our project in the end, so hasn’t had much testing… But may be of use to someone.

application.getOSName()

As an aside, and so hopefully you don’t feel you’ve wasted your time reading all this crap.

The Servoy function application.getOSName() returns the OS name of the ‘Client’ system! Not the ‘Server’ system!

We had to test this, as (of course this being Servoy) it is not specified in the Servoy documentation…

Demo code.

Here is some code for an onAction event to test cleaning some paths.

/**
 * @param {JSEvent} event the event that triggered the action
 */
function onAction_tmp(event) {
    var i, paths = [
    "\\a/very\\dirty/path/to\\/a\\file//folder///file.doc",
    "C:///a/very\\dirty/path/to\\/a\\windows/file//folder///\\test.txt",
    "//server//path/to\\a/file//folder\\//\\//test.html",
    "https://a/very\\dirty/path/to\\/a\\windows/file//folder///\\test.html"
    ];
		
    for(i in paths) {
        application.output('----------------------------------------------------------------------\n'
         + 'Dirty path: ' + paths[i] + '\n'
         + 'Cleaned path: ' + scopes.daveTools.cleanPath(paths[i]));
    }
	
    for(i in paths) {
        var obj = new scopes.daveTools.Path(paths[i]);
		
        if (obj.hasError()) {
            application.output('----------------------------------------------------------------------');
            application.output("Error in path: " + obj.getErrorDescription());
        } else {
            application.output('----------------------------------------------------------------------');

            // Path type: Either WINDOWS, UNIX, SERVER or URL:
            application.output('fileType:\t\t' + obj.fileType);
			
            // The raw source path (prior to cleaning):
            application.output('raw:\t\t\t' + obj.raw);
		
            // Client filesystem type: Either WINDOWS or UNIX:
            application.output('client:\t\t\t' + obj.client);
			
            // Properties that breakdown the path:
            application.output('prefix:\t\t\t' + obj.prefix);
            application.output('path:\t\t\t' + obj.path);
            application.output('name:\t\t\t' + obj.name);
            application.output('extension:\t\t' + obj.extension);
			
            // get the full filename with file extension:
            application.output('getFilename():\t\t' + obj.getFilename());
			
            // Get the complete formated path.
            application.output('getFormattedPath():\t' + obj.getFormattedPath());
        }
	
    }

The class itself.

And here is the class itself. I originally stored this lot in a scope called ‘daveTools’, but it should be fairly easy to move elsewhere etc.

Notice the stand alone function cleanPath at the top. It will create an instance of the class below, clean a path and return it. Hopefully a useful function for globals.js.

/**
 * Will take a dirty path string, format it, and return a cleaned path string.
 * 
 * @example application.output(globals.cleanPath("C:/not/correct/for/windows/document.txt"));
 * 
 * @param {String} _p
 * @public
 */
function cleanPath(_p) {
    return String( new Path(_p).getFormattedPath() );
}


/**
 * Will take a dirty path string, and return a Path object.
 * 
 * @param {String} [source]
 * @public 
 *
 * @example var path = new Path("https://myurl.com/test.html"); application.output(path);
 */
function Path(source) {
	
    // PUBLIC ATTRIBUTES:
	
    /** @type {String} */
    this.fileType = ''; 
    /** @type {String} */
    this.client = ''; 
	
    /** @type {String} */
    this.prefix = ''; 
	
    /** @type {String} */
    this.path = ''; 
	
    /** @type {String} */
    this.name='';
    /** @type {String} */
    this.extension='';
	
    /** @type {String} */
    this.raw='';
	
    /** @type {String} */
    this.error='';

	
    // PUBLIC ENUMERATIONS:
	
    /**
     * @type {Object}
     */
    this.TYPES = {
WINDOWS: "WINDOWS",
UNIX: "UNIX",
URL: "URL",
SERVER: "SERVER"
    };
	
	
    // PUBLIC METHODS:

    /**
     * Returns the complete filename.
     * 
     * @return {String}
     * @public
     */
    this.getFilename = function() {
        return this.name + (this.extension != '' ? '.' + this.extension : '');
    }
	
    /**
     * Will remake an existing Path object from a supplied path string.
     * 
     * @param {String} newSource
     * @public 
     */
    this.remake = function(newSource) {
        if (newSource === undefined) newSource = new String();
	
        // Determine Servoy client OS file format (direction of slash)
        this.client = application.getOSName().match(/Windows/) ? this.TYPES.WINDOWS : this.TYPES.UNIX;
        this.raw = String(newSource);
	
        // Make all slashes a forward slash '/'
        var _str = this.raw.replace(/\\/g, '/');	
		
        // Get file type & prefix
        if (_str.match(/^(https|http|ftp|media)\:(\/){2}/)) {
            this.fileType = this.TYPES.URL;
            this.prefix = _str.replace(/^([a-zA-Z]*:(\/){2}).*$/, '$1');
        } else if (_str.match(/^(\/){2}/)) {
            this.fileType = this.TYPES.SERVER;
            this.prefix = '//';
        } else if (_str.match(/^[A-Za-z]{1}\:/)) {
            this.fileType = this.TYPES.WINDOWS;
            this.prefix = _str.replace(/^([a-zA-Z]:).*$/, '$1');
        } else {
            this.fileType = this.TYPES.UNIX;
            this.prefix = '';
        }
		
        // Get filename parts
        var _filename = '';
        if (_str.match(/\./)) {
            if (_str.match(/\/{1}/)) 
                _filename = _str.substr(_str.lastIndexOf('\/')+1);
            else 
                _filename = _str;
        }
        this.name = _filename.replace(/^(.*)\.(.*)$/, '$1');
        this.extension = _filename.replace(/^(.*)\.(.*)$/, '$2');
		
        // Get path part
        _str = _str.substr(this.prefix.length, _str.length-this.prefix.length);
        _str = _str.substr(0, _str.length-_filename.length);
        this.path = _str;
		
        // Remove duplicate slashes in path
        this.path = this.path.replace(/\/{2,}/g, '/');

        // If file type is Windows, convert forward slashes to back slashes
        if (this.fileType == this.TYPES.WINDOWS) {
            this.path = this.path.replace(/\//g,'\\');
			
            // If this is a Windows path, but client isn't Windows, warn
            if (this.client != this.TYPES.WINDOWS) this.error = "Warning: Windows path on non Windows system";
        }
		
        // If client is Windows, and prefix is blank, assume is a Windows path
        if (this.client == this.TYPES.WINDOWS && this.prefix == '') {
            this.path = this.path.replace(/\//g,'\\');
            this.fileType = this.TYPES.WINDOWS;
        }
		
        // If fileType is server, and path starts with a slash, then remove the slash
        if (this.fileType == this.TYPES.SERVER || this.fileType == this.TYPES.URL)
            this.path = this.path.replace(/^(\/)(.*)$/, '$2');

        // If client is Windows and fileType is server, use back slashes
        if (this.client == this.TYPES.WINDOWS && this.fileType == this.TYPES.SERVER) {
            this.path = this.path.replace(/\//g,'\\');
            this.prefix = '\\\\';
        }
    }
	
    /**
     * Dump out a string containing all Path properties.
     * 
     * @public
     */
    this.dumpOut = function() {
        var r = '{';
        for (var p in this) {
            if (typeof this[p] != "function") r += p + ':' + this[p]+',';
        }
        r = r.substr(0, r.length-1) + '}';
		
        return r;
    }
	
    /**
     * Return complete (cleaned) path as a string.
     * 
     * @return {String}
     * @public
     */
    this.getFormattedPath = function() {
        return this.prefix + this.path + this.name + (this.extension != '' ? '.' + this.extension : '');
    }
	
    /**
     * If true, supplied path string contains an error.
     * 
     * @return {Boolean}
     * @public 
     */
    this.hasError = function() {
        return this.error != '' ? true : false;
    }
	
    /**
     * If supplied path string contained an error, returns it's description
     *
     * @return {String}
     * @public 
     */
    this.getErrorDescription = function() {
        return this.error;
    }
	
	
    // CONSTRUCTOR:
	
    this.remake(source);
}
Path.prototype.constructor = Path;

Leave a Reply

Your email address will not be published. Required fields are marked *