ValidatorPlugin

Dependencies:
- utils/form-validator
- utils/merge
- utils/events
- vendor/uuid

@param {object} rule
@param {object} [ data ] // from request
@param {string} [ formId ]
345714
function ValidatorPlugin(rules, data, formId) {

    this.plugin = 'validator';

    /**
     * validator event handler - isGFFCtx only
     * */
    var events      = ['ready', 'error', 'progress', 'submit', 'success', 'change', "destroy"];

    /** imports */
    var isGFFCtx        = ( ( typeof(module) !== 'undefined' ) && module.exports ) ? false : true;
    if (isGFFCtx) {
        require('utils/events');
        registerEvents(this.plugin, events);

        require('utils/dom');

    } else {
        var cacheless   = (process.env.IS_CACHELESS == 'false') ? false : true;
        if (cacheless) {
            delete require.cache[require.resolve('./form-validator')]
        }
    }

    var uuid            = (isGFFCtx) ? require('vendor/uuid') : require('uuid');
    var merge           = (isGFFCtx) ? require('utils/merge') : require('../../../../utils/lib/merge');
    var FormValidator   = (isGFFCtx) ? require('utils/form-validator') : require('./form-validator');

    /** definitions */
    var instance    = { // isGFFCtx only
        'id'                : 'validator-' + uuid.v1(),

        'plugin'            : this.plugin,
        'on'                : (isGFFCtx) ? on : null,
        'eventData'         : {},
        'target'            : (isGFFCtx) ? document : null, // by default

        'initialized'       : false,
        'isReady'           : false,
        'rules'             : {},
        '$forms'            : {},
        'getFormById'       : null,
        'validateFormById'  : null,
        'setOptions'        : null,
        'resetErrorsDisplay': null,
        'resetFields'       : null
    };

    // validator proto
    var $validator      = { // isGFFCtx only
        'id'                    : null, // form id

        'plugin'                : this.plugin,
        'on'                    : (isGFFCtx) ? on : null,
        'eventData'             : {},
        'target'                : (isGFFCtx) ? document : null, // by default

        'binded'                : false,
        'withUserBindings'      : false,
        'rules'                 : {},
        'setOptions'            : null,
        'send'                  : null,
        'submit'                : null,
        'destroy'               : null,
        'resetErrorsDisplay'    : null,
        'resetFields'           : null
    };


    /**
     * XML Request - isGFFCtx only
     * */
    var xhr         = null;
    var xhrOptions  = {
        'url'           : '',
        'method'        : 'GET',
        'isSynchrone'   : false,
        'withCredentials': false,
        'headers'       : {
            // to upload, use `multipart/form-data` for `enctype`
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            // cross domain is enabled by default, but you need to setup `Access-Control-Allow-Origin`
            'X-Requested-With': 'XMLHttpRequest' // in case of cross domain origin

        }
    };

    /**
     * backend definitions
     * */
    var setCustomRules = function (customRules) {
        // parsing rules
        if ( typeof(customRule) != 'undefined' ) {
            parseRules(customRule, '');
            checkForRulesImports(customRule);
        }
    }

    var backendProto = {
        'setCustomRules': setCustomRules
    };


    /**
     * Backend init
     *
     * @param {object} rules
     * @param {object} [customRule]
     * */
    var backendInit = function (rules, data, formId) {

        var $form = ( typeof(formId) != 'undefined' ) ? { 'id': formId } : null;
        var fields = {};

        for (var field in data) {
            fields[field] = data[field]
        }


        // parsing rules
        if ( typeof(rules) != 'undefined' && rules.count() > 0 ) {
            parseRules(rules, '');
            checkForRulesImports(rules);

            backendProto.rules = instance.rules;

            return validate($form, fields, null, instance.rules)

        } else {
            // without rules - by hand
            return new FormValidator(fields)
        }
    }


    /**
     * GFF definitions
     * */

    var setOptions = function (options) {
        var options = merge(options, xhrOptions);
        xhrOptions = options;
    }


    var getFormById = function(formId) {
        var $form = null, _id = formId;

        if ( !instance['$forms'] )
            throw new Error('`$forms` collection not found');

        if ( typeof(_id) == 'undefined') {
            throw new Error('[ FormValidator::getFormById(formId) ] `formId` is missing')
        }

        _id = _id.replace(/\#/, '');

        if ( typeof(instance['$forms'][_id]) != 'undefined' ) {
            instance['$forms'][_id].withUserBindings = true;

            $form = this.$forms[_id] = instance['$forms'][_id];

            return $form
        }

        return null
    }


    /**
     * validateFormById
     *
     * @param {string} formId
     * @param {object} [customRule]
     *
     * @return {object} $form
     * */
    var validateFormById = function(formId, customRule) {
        var $form = null, _id = formId;


        if ( !instance['$forms'] ) {
            throw new Error('`$forms` collection not found')
        }


        if ( typeof(_id) == 'undefined') {
            if ( typeof(this.id) != 'undefined' && this.id != '' && this.id != null ) {
                _id = this.id
            } else {
                throw new Error('[ FormValidator::validateFormById(formId, customRule) ] `formId` is missing')
            }
        }

        if ( typeof(_id) == 'string') {
            _id = _id.replace(/\#/, '')
        } else if ( typeof(_id) == 'object' && !Array.isArray(_id) ) { // weird exception

            var $target = _id.form;
            _id = $target.getAttribute('id') || 'form.'+uuid.v1();

            $target.setAttribute('id', _id);// just in case

        } else {
            throw new Error('[ FormValidator::validateFormById(formId[, customRule]) ] `formId` should be a `string`');
        }

        if ( typeof(instance['$forms'][_id]) != 'undefined' ) {
            $form   = this.$forms[_id] = instance['$forms'][_id];
        } else { // binding a form out of context (outside of the main instance)
            var $target             = document.getElementById(_id);
            $validator.id           = _id;
            $validator.target       = $target;

            $form = this.$forms[_id] = instance.$forms[_id] = merge({}, $validator);

            var rule    = null;
            if ( typeof(customRule) == 'undefined') {
                rule = _id.replace(/\-/g, '.');

                if ( typeof(instance.rules[rule]) != 'undefined' ) {
                    $form['rule'] = customRule = instance.rules[rule];
                } else if ( $form.target.getAttribute('data-gina-form-rule') ) {
                    rule = $form.target.getAttribute('data-gina-form-rule').replace(/\-/g, '.');

                    if ( typeof(instance.rules[rule]) != 'undefined' ) {
                        $form['rule'] = instance.rules[rule]
                    } else {
                        throw new Error('[ FormValidator::validateFormById(formId) ] using `data-gina-form-rule` on form `'+$form.target+'`: no matching rule found')
                    }
                } // no else to allow form without any rule
            } else {
                rule = customRule.replace(/\-/g, '.');

                if ( typeof(instance.rules[rule]) != 'undefined' ) {
                    $form['rule'] = instance.rules[rule]
                } else {
                    throw new Error('[ FormValidator::validateFormById(formId, customRule) ] `'+customRule+'` is not a valid rule')
                }
            }

            if ($target && !$form.binded)
                bindForm($target, rule);
        }

        if (!$form) throw new Error('[ FormValidator::validateFormById(formId, customRule) ] `'+_id+'` not found');

        return $form || null;

    }

    var handleErrorsDisplay = function($form, errors, data) {

        if ( gina.options.env == 'dev' )
            var formsErrors = null;

        var name = null, errAttr = null;
        var $err = null, $msg = null;
        var $el = null, $parent = null, $target = null;
        var id  = $form.getAttribute('id');

        for (var i = 0, len = $form.length; i<len; ++i) {
            $el     = $form[i];
            if ( /form\-item\-wrapper$/.test($el.parentNode.className) ) {
                $parent = $el.parentNode.parentNode;
                $target = $el.parentNode;
            } else {
                $parent = $el.parentNode;
                $target = $el;
            }

            name    = $el.getAttribute('name');
            errAttr = $el.getAttribute('data-gina-form-errors');

            if (!name) continue;

            if ( typeof(errors[name]) != 'undefined' && !/form\-item\-error/.test($parent.className) ) {

                $parent.className += ($parent.className == '' ) ? 'form-item-error' : ' form-item-error';

                $err = document.createElement('div');
                $err.setAttribute('class', 'form-item-error-message');

                // injecting error messages
                for (var e in errors[name]) {

                    if (e != 'stack') { // ignore stack for display
                        $msg = document.createElement('p');
                        $msg.appendChild( document.createTextNode(errors[name][e]) );
                        $err.appendChild($msg);
                    }

                    if ( gina.options.env == 'dev' ) {
                        if (!formsErrors) formsErrors = {};
                        if ( !formsErrors[ name ] )
                            formsErrors[ name ] = {};

                        formsErrors[ name ][e] = errors[name][e]
                    }
                }

                if ($target.type != 'hidden')
                    insertAfter($target, $err);

            } else if ( typeof(errors[name]) == 'undefined' && /form\-item\-error/.test($parent.className) ) {
                // reset when not in error
                // remove child elements
                var $children = $parent.getElementsByTagName('div');
                for (var c = 0, cLen = $children.length; c<cLen; ++c) {
                    if ( /form\-item\-error\-message/.test($children[c].className) ) {
                        //$parent.removeChild($children[c]);
                        $children[c].parentElement.removeChild($children[c]);
                        break
                    }
                }

                $parent.className = $parent.className.replace(/(\s+form\-item\-error|form\-item\-error)/, '');

            } else if ( typeof(errors[name]) != 'undefined' && errAttr) {
                // refreshing already displayed error on msg update
                var $divs = $parent.getElementsByTagName('div');
                for (var d = 0, dLen = $divs.length; d<dLen; ++d) {
                    if ($divs[d].className == 'form-item-error-message') {

                        $divs[d].parentElement.removeChild($divs[d]);
                        $err = document.createElement('div');
                        $err.setAttribute('class', 'form-item-error-message');

                        // injecting error messages
                        for (var e in errors[name]) {
                            $msg = document.createElement('p');
                            $msg.appendChild( document.createTextNode(errors[name][e]) );
                            $err.appendChild($msg);

                            if ( gina.options.env == 'dev' ) {
                                if (!formsErrors) formsErrors = {};
                                if ( !formsErrors[ name ] )
                                    formsErrors[ name ] = {};

                                formsErrors[ name ][e] = errors[name][e]
                            }
                        }

                        break;
                    }
                }

                if ($target.type != 'hidden')
                    insertAfter($target, $err);

            }
        }


        if ( formsErrors ) {

            triggerEvent(gina, $form, 'error.' + id, errors)

            if ( typeof(window.ginaToolbar) == 'object' ) {
                // update toolbar
                if (!gina.forms.errors)
                    gina.forms.errors = {};

                gina.forms.errors = formsErrors;

                window.ginaToolbar.update('forms', gina.forms);
            }

        }
    }


    /**
     * Reset errors display
     *
     * @param {object|string} [$form|formId]
     *
     * */
    var resetErrorsDisplay = function($form) {
        var $form = $form, _id = null;
        if ( typeof($form) == 'undefined' ) {
            if ( typeof(this.target) != 'undefined' ) {
                _id = this.target.getAttribute('id');
            } else {
                _id = this.getAttribute('id');
            }

            $form = instance.$forms[_id]
        } else if ( typeof($form) == 'string' ) {
            _id = $form;
            _id = _id.replace(/\#/, '');

            if ( typeof(instance.$forms[_id]) == 'undefined') {
                throw new Error('[ FormValidator::resetErrorsDisplay([formId]) ] `'+$form+'` not found')
            }

            $form = instance.$forms[_id]
        }
        //reseting error display
        handleErrorsDisplay($form['target'], []);

        return $form
    }

    /**
     * Reset fields
     *
     * @param {object|string} [$form|formId]
     *
     * */
    var resetFields = function($form) {
        var $form = $form, _id = null;
        if ( typeof($form) == 'undefined' ) {
            if ( typeof(this.target) != 'undefined' ) {
                _id = this.target.getAttribute('id');
            } else {
                _id = this.getAttribute('id');
            }

            $form = instance.$forms[_id]
        } else if ( typeof($form) == 'string' ) {
            _id = $form;
            _id = _id.replace(/\#/, '');

            if ( typeof(instance.$forms[_id]) == 'undefined') {
                throw new Error('[ FormValidator::resetErrorsDisplay([formId]) ] `'+$form+'` not found')
            }

            $form = instance.$forms[_id]
        }

        if ($form.defaultFields) {

            var elId        = null
                , $element  = null
                , type      = null;

            for (var f in $form.defaultFields) {

                $element    = document.getElementById(f)
                type        = $element.tagName.toLowerCase();

                if (type == 'input') {
                    $element.value = $form.defaultFields[f];
                } else if ( type == 'select' ) {
                    $element.options[ $form.defaultFields[f] ].selected = true;
                    $element.setAttribute('data-value',  $element.options[ $form.defaultFields[f] ].value);
                }
            }
        }

        return $form
    }

    // TODO - efreshErrorsDisplay
    // var refreshErrorsDisplay = function ($form) {
    //
    // }

    var submit = function () {

        var $form = null, _id = null, $target = null;

        if ( this.getAttribute ) {
            _id = this.getAttribute('id');
            $target = this;
        } else if ( typeof(this.target) && this.target.getAttribute ) {
            _id = this.target.getAttribute('id');
            $target = this.target
        }

        if ( typeof(instance.$forms[_id]) == 'undefined') {
            throw new Error('[ FormValidator::submit() ] not `$form` binded. Use `FormValidator::getFormById(id)` or `FormValidator::validateFormById(id)` first ')
        }

        triggerEvent(gina, $target, 'submit');

        return this;
    }


    /**
     * send
     * N.B.: no validation here; if you want to validate against rules, use `.submit()` before
     *
     *
     * @param {object} data
     * @param {object} [ options ]
     * */
    var send = function(data, options) {

        var $target = this.target , id = $target.getAttribute('id');
        var $form = instance.$forms[id] || this;

        // forward callback to HTML attribute
        listenToXhrEvents($form);

        if (options) {
            var options = merge(options, xhrOptions);
        } else {
            var options = xhrOptions;
        }

        var url         = $target.getAttribute('action') || options.url;
        var method      = $target.getAttribute('method') || options.method;
        method          = method.toUpperCase();
        options.method  = method;
        options.url     = url;

        // to upload, use `multipart/form-data` for `enctype`
        var enctype = $target.getAttribute('enctype');

        if ( options.withCredentials ) {

            if ('withCredentials' in xhr) {
                // XHR for Chrome/Firefox/Opera/Safari.
                if (options.isSynchrone) {
                    xhr.open(options.method, options.url, options.isSynchrone)
                } else {
                    xhr.open(options.method, options.url)
                }
            } else if ( typeof XDomainRequest != 'undefined' ) {
                // XDomainRequest for IE.
                xhr = new XDomainRequest();
                xhr.open(options.method, options.url);
            } else {
                // CORS not supported.
                xhr = null;
                var result = 'CORS not supported: the server is missing the header `"Access-Control-Allow-Credentials": true` ';
                triggerEvent(gina, $target, 'error.' + id, result);

                return
            }

            xhr.withCredentials = true;
        } else {
            if (options.isSynchrone) {
                xhr.open(options.method, options.url, options.isSynchrone)
            } else {
                xhr.open(options.method, options.url)
            }
        }

        // setting up headers
        for (var hearder in options.headers) {
            if ( hearder == 'Content-Type' && typeof (enctype) != 'undefined' && enctype != null && enctype != '') {
                options.headers[hearder] = enctype
            }

            xhr.setRequestHeader(hearder, options.headers[hearder]);
        }

        if (xhr) {
            // catching ready state cb
            xhr.onreadystatechange = function (event) {

                if (xhr.readyState == 4) {
                    // 200, 201, 201' etc ...
                    if( /^2/.test(xhr.status) ) {

                        try {
                            var result = xhr.responseText;
                            if ( /json$/.test( xhr.getResponseHeader("Content-Type") ) ) {
                                result = JSON.parse(xhr.responseText)
                            }

                            $form.eventData.success = result;

                            var XHRData = result;
                            if ( gina && typeof(window.ginaToolbar) == "object" && XHRData ) {
                                try {

                                    if ( typeof(XHRData) != 'undefined' ) {
                                        window.ginaToolbar.update("data-xhr", XHRData);
                                    }

                                } catch (err) {
                                    throw err
                                }
                            }

                            triggerEvent(gina, $target, 'success.' + id, result)

                        } catch (err) {
                            var result = {
                                status:  422,
                                error : err.message,
                                stack : err.stack

                            };

                            $form.eventData.error = result;

                            // // update toolbar
                            // var XHRData = result;
                            // if ( gina && typeof(window.ginaToolbar) == "object" && XHRData ) {
                            //     try {
                            //
                            //         // forward backend appplication errors to forms.errors when available
                            //         if ( XHRData.error && typeof(XHRData.error) == 'object' && $form.fields ) {
                            //             var formsErrors = {}, errCount = 0;
                            //             for (var e in XHRData.error) {
                            //                 if ( typeof($form.fields[e]) != 'undefined' ) {
                            //                     ++errCount;
                            //                     formsErrors[e] = XHRData.error[e];
                            //
                            //                     if ( typeof(XHRData.stack) != 'undefined' )
                            //                         formsErrors[e].stack = XHRData.stack;
                            //                 }
                            //             }
                            //
                            //             if (errCount > 0) {
                            //                 handleErrorsDisplay($form.target, formsErrors);
                            //             }
                            //         }
                            //         // update toolbar
                            //         ginaToolbar.update("data-xhr", XHRData );
                            //
                            //     } catch (err) {
                            //         throw err
                            //     }
                            // }

                            var XHRData = result;
                            if ( gina && typeof(window.ginaToolbar) == "object" && XHRData ) {
                                try {

                                    if ( typeof(XHRData) != 'undefined' ) {
                                        window.ginaToolbar.update("data-xhr", XHRData);
                                    }

                                } catch (err) {
                                    throw err
                                }
                            }

                            triggerEvent(gina, $target, 'error.' + id, result)
                        }

                    } else if ( xhr.status != 0) {

                        var result = { 'status': xhr.status };

                        if ( /^(\{|\[).test( xhr.responseText ) /) {

                            try {
                                result = JSON.parse(xhr.responseText);
                            } catch (err) {
                                result = merge(result, err)
                            }

                        } else if ( typeof(xhr.responseText) == 'object' ) {
                            result = xhr.responseText
                        } else {
                            result.message = xhr.responseText
                        }

                        $form.eventData.error = result;

                        // update toolbar
                        var XHRData = result;
                        if ( gina && typeof(window.ginaToolbar) == "object" && XHRData ) {
                            try {

                                // forward backend appplication errors to forms.errors when available
                                if ( XHRData.error && typeof(XHRData.error) == 'object' && $form.fields ) {
                                    var formsErrors = {}, errCount = 0;
                                    for (var e in XHRData.error) {
                                        if ( typeof($form.fields[e]) != 'undefined' ) {
                                            ++errCount;
                                            formsErrors[e] = XHRData.error[e];

                                            if ( typeof(XHRData.stack) != 'undefined' )
                                                formsErrors[e].stack = XHRData.stack;
                                        }
                                    }

                                    if (errCount > 0) {
                                        handleErrorsDisplay($form.target, formsErrors);
                                    }
                                }
                                // update toolbar
                                ginaToolbar.update('data-xhr', XHRData );

                            } catch (err) {
                                throw err
                            }
                        }

                        triggerEvent(gina, $target, 'error.' + id, result)
                    }
                }
            };

            // catching request progress
            xhr.onprogress = function(event) {
                // console.log(
                //    'progress position '+ event.position,
                //    '\nprogress total size '+ event.totalSize
                // );

                var percentComplete = (event.position / event.totalSize)*100;
                var result = {
                    'status': 100,
                    'progress': percentComplete
                };

                $form.eventData.onprogress = result;

                triggerEvent(gina, $target, 'progress.' + id, result)
            };

            // catching timeout
            xhr.ontimeout = function (event) {
                var result = {
                    'status': 408,
                    'error': 'Request Timeout'
                };

                $form.eventData.ontimeout = result;

                triggerEvent(gina, $target, 'error.' + id, result)
            };


            // sending
            if (!data)
                data = event.detail.data;

            if (data) {
                if ( typeof(data) == 'object' ) {
                    try {
                        data = JSON.stringify(data)
                    } catch (err) {
                        triggerEvent(gina, $target, 'error.' + id, err)
                    }
                }
                //console.log('sending -> ', data);
                //try {
                    xhr.send(data)
                // } catch (err) {
                //     var XHRData = result;
                //     if ( gina && typeof(window.ginaToolbar) == "object" && XHRData ) {
                //         try {
                //
                //             if ( typeof(XHRData) != 'undefined' ) {
                //                 window.ginaToolbar.update("data-xhr", XHRData);
                //             }
                //
                //         } catch (err) {
                //             throw err
                //         }
                //     }
                // }

            } else {
                xhr.send()
            }
            $form.sent = true;


        }
    }

    var listenToXhrEvents = function($form) {

        //$form['plugin'] = $validator.plugin;
        //$form['on']     = $validator.on;


        //data-gina-form-event-on-submit-success
        var htmlSuccesEventCallback =  $form.target.getAttribute('data-gina-form-event-on-submit-success') || null;
        if (htmlSuccesEventCallback != null) {

            if ( /\((.*)\)/.test(htmlSuccesEventCallback) ) {
                eval(htmlSuccesEventCallback)
            } else {
                $form.on('success',  window[htmlSuccesEventCallback])
            }
        }

        //data-gina-form-event-on-submit-error
        var htmlErrorEventCallback =  $form.target.getAttribute('data-gina-form-event-on-submit-error') || null;
        if (htmlErrorEventCallback != null) {
            if ( /\((.*)\)/.test(htmlErrorEventCallback) ) {
                eval(htmlErrorEventCallback)
            } else {
                $form.on('error', window[htmlErrorEventCallback])
            }
        }
    }

    var destroy = function(formId) {
        var $form = null, _id = formId;


        if ( !instance['$forms'] )
            throw new Error('`$forms` collection not found');


        if ( typeof(_id) == 'undefined') {
            if ( typeof(this.id) != 'undefined' && this.id != '' && this.id != null ) {
                _id  = this.id
            } else {
                throw new Error('[ FormValidator::destroy(formId) ] `formId` is missing')
            }
        }

        if ( typeof(_id) == 'string') {
            _id = _id.replace(/\#/, '')
        } else if ( typeof(_id) == 'object' && !Array.isArray(_id) ) { // weird exception
            var $target = _id.form;
            _id = $target.getAttribute('id') || 'form.'+uuid.v1();

            $target.setAttribute('id', _id);// just in case

        } else {
            throw new Error('[ FormValidator::destroy(formId) ] `formId` should be a `string`');
        }

        if ( typeof(instance['$forms'][_id]) != 'undefined' ) {
            $form = instance['$forms'][_id]
        } else if ( typeof(this.binded) != 'undefined' ) {
            $form = this;
        }

        if ($form) {
            // remove existing listeners

            // form events
            removeListener(gina, $form, 'success.' + _id);
            removeListener(gina, $form, 'validate.' + _id);
            removeListener(gina, $form, 'submit.' + _id);
            removeListener(gina, $form, 'error.' + _id);

            // binded elements
            var $el         = null
                , evt       = null
                , $els      = []
                , $elTMP    = [];

            // submit buttons
            $elTMP = $form.target.getElementsByTagName('button');
            if ( $elTMP.length > 0 ) {
                for(var i = 0, len = $elTMP.length; i < len; ++i) {
                    if ($elTMP[i].type == 'submit')
                        $els.push($elTMP[i])
                }
            }

            // submit links
            $elTMP = $form.target.getElementsByTagName('a');
            if ( $elTMP.length > 0 ) {
                for(var i = 0, len = $elTMP.length; i < len; ++i) {
                    if ( $elTMP[i].attributes.getNamedItem('data-gina-form-submit') || /^click\./.test( $elTMP[i].attributes.getNamedItem('id') ) )
                        $els.push($elTMP[i])
                }
            }

            // checkbox & radio
            $elTMP = $form.target.getElementsByTagName('input');
            if ( $elTMP.length > 0 ) {
                for(var i = 0, len = $elTMP.length; i < len; ++i) {
                    if ($elTMP[i].type == 'checkbox' || $elTMP[i].type == 'radio' )
                        $els.push( $elTMP[i] )
                }
            }

            for (var i = 0, len = $els.length; i < len; ++i) {

                $el = $els[i];
                if ($el.type == 'submit') {

                    evt = $el.getAttribute('id');
                    if ( typeof(gina.events[ evt ]) != 'undefined' )
                        removeListener(gina, $el, gina.events[ evt ]);

                } else {

                    evt ='click.' + $el.getAttribute('id');
                    if ( typeof(gina.events[ evt ]) != 'undefined' )
                        removeListener(gina, $el, evt);
                }
            }

            $form.binded = false;

            //addListener(gina, instance['$forms'][_id].target, 'destroy.' + _id, function(event) {
            addListener(gina, $form.target, 'destroy.' + _id, function(event) {

                cancelEvent(event);

                delete instance['$forms'][_id];
                removeListener(gina, event.currentTarget, event.type);
                removeListener(gina, event.currentTarget,'destroy');
            });

            //triggerEvent(gina, instance['$forms'][_id].target, 'destroy.' + _id);
            triggerEvent(gina, $form.target, 'destroy.' + _id);

        } else {
            throw new Error('[ FormValidator::destroy(formId) ] `'+_id+'` not found');
        }

    }

    var checkForRulesImports = function (rules) {
        // check if rules has imports & replace
        var rulesStr = JSON.stringify(rules, null, 4);
        var importedRules = rulesStr.match(/(\"@import\s+[a-z A-Z 0-9/.]+\")/g);
        if (importedRules && importedRules.length > 0) {
            var ruleArr = [], rule = {}, tmpRule = null;
            for (var r = 0, len = importedRules.length; r<len; ++r) {
                ruleArr = importedRules[r].replace(/(@import\s+|\"|\')/g, '').split(/\s/g);
                // [""@import client/form", ""@import project26/edit demo/edit"]
                //console.log('ruleArr -> ', ruleArr, importedRules[r]);
                for (var i = 0, iLen = ruleArr.length; i<iLen; ++i) {
                    tmpRule = ruleArr[i].replace(/\//g, '.');
                    if ( typeof(instance.rules[ tmpRule ]) != 'undefined' ) {
                        rule = merge(rule, instance.rules[ tmpRule ])
                    } else {
                        console.warn('[formValidator:rules] <@import error> on `'+importedRules[r]+'`: rule `'+ruleArr[i]+'` not found. Ignoring.')
                    }
                }
                //console.log('replacing ', importedRules[r]);
                rulesStr = rulesStr.replace(importedRules[r], JSON.stringify(rule));
                instance.rules = JSON.parse( JSON.stringify(instance.rules).replace( new RegExp(importedRules[r], 'g'), JSON.stringify(rule)) );
                //console.log('str ', rulesStr);
                rule = {}

            }

            if (!instance.rules) {
                instance.rules = {}
            }

            rules = JSON.parse(rulesStr);
            parseRules(rules, '');

            // if (!isGFFCtx) {
            //     backendProto.rules = instance.rules
            // }
        }
    }

    var init = function (rules) {

        if (gina.hasValidator) {
            instance = merge(instance, gina.validator);
            instance.on('init', function(event) {
                instance.isReady = true;
                triggerEvent(gina, instance.target, 'ready.' + instance.id, instance)
            })
        } else {
            setupInstanceProto();
            instance.on('init', function(event) {
                // parsing rules
                if ( typeof(rules) != 'undefined' && rules.count() ) {
                    gina.forms.rules = JSON.parse(JSON.stringify(rules));// making copy
                    parseRules(rules, '');
                    checkForRulesImports(rules);
                }

                $validator.setOptions           = setOptions;
                $validator.getFormById          = getFormById;
                $validator.validateFormById     = validateFormById;
                $validator.resetErrorsDisplay   = resetErrorsDisplay;
                $validator.resetFields          = resetFields;
                $validator.handleErrorsDisplay  = handleErrorsDisplay;
                $validator.submit               = submit;
                $validator.send                 = send;
                $validator.destroy              = destroy;

                var id          = null
                    , $target   = null
                    , i         = 0
                    , $forms    = []
                    , $allForms = document.getElementsByTagName('form');


                // has rule ?
                for (var f=0, len = $allForms.length; f<len; ++f) {
                    // preparing prototype (need at least an ID for this)

                    if ($allForms[f].getAttribute) {
                        id = $allForms[f].getAttribute('id') || 'form.' + uuid.v1();
                        if ( id !== $allForms[f].getAttribute('id') ) {
                            $allForms[f].setAttribute('id', id)
                        }
                    } else {
                        id = 'form.' + uuid.v1();
                        $allForms[f].setAttribute('id', id)
                    }

                    $allForms[f]['id'] = $validator.id = id;

                    if ( typeof($allForms[f].id) != 'undefined' && $allForms[f].id != 'null' && $allForms[f].id != '') {

                        $validator.target = $allForms[f];
                        instance.$forms[$allForms[f].id] = merge({}, $validator);

                        var customRule = $allForms[f].getAttribute('data-gina-form-rule');

                        if (customRule) {
                            customRule = customRule.replace(/\-/g, '.');
                            if ( typeof(instance.rules[customRule]) == 'undefined' ) {
                                throw new Error('['+$allForms[f].id+'] no rule found with key: `'+customRule+'`');
                                customRule = null
                            } else {
                                customRule = instance.rules[customRule]
                            }
                        }

                        // finding forms handled by rules
                        if ( typeof($allForms[f].id) == 'string' && typeof(instance.rules[$allForms[f].id.replace(/\-/g, '.')]) != 'undefined' ) {
                            $target = instance.$forms[$allForms[f].id].target;
                            if (customRule) {
                                bindForm($target, customRule)
                            } else {
                                bindForm($target)
                            }

                            ++i
                        } else {
                            // weird exception when having in the form an element with name="id"
                            if ( typeof($allForms[f].id) == 'object' ) {
                                delete instance.$forms[$allForms[f].id];

                                var _id = $allForms[f].attributes.getNamedItem('id').nodeValue || 'form.'+uuid.v1();

                                $allForms[f].setAttribute('id', _id);
                                $allForms[f]['id'] = _id;

                                $validator.target = $allForms[f];
                                instance.$forms[_id] = merge({}, $validator);

                                $target = instance.$forms[_id].target;
                                if (customRule) {
                                    bindForm($target, customRule)
                                } else {
                                    bindForm($target)
                                }
                            } else {

                                $target = instance.$forms[$allForms[f].id].target;
                                if (customRule) {
                                    bindForm($target, customRule)
                                } else {
                                    bindForm($target)
                                }
                            }
                        }
                    }

                }


                // setting up AJAX
                if (window.XMLHttpRequest) { // Mozilla, Safari, ...
                    xhr = new XMLHttpRequest();
                } else if (window.ActiveXObject) { // IE
                    try {
                        xhr = new ActiveXObject("Msxml2.XMLHTTP");
                    } catch (e) {
                        try {
                            xhr = new ActiveXObject("Microsoft.XMLHTTP");
                        }
                        catch (e) {}
                    }
                }

                instance.isReady = true;
                gina.hasValidator = true;
                gina.validator = instance;
                triggerEvent(gina, instance.target, 'ready.' + instance.id, instance);
            });

        }

        instance.initialized = true;
        return instance
    }

    /**
     * parseRules - Preparing rules paths
     *
     * @param {object} rules
     * @param {string} tmp - path
     * */
    var parseRules = function(rules, tmp) {
        var _r = null;
        for (var r in rules) {

            if ( typeof(rules[r]) == 'object' && typeof(instance.rules[tmp + r]) == 'undefined' ) {

                _r = r;
                if (/\[|\]/.test(r) ) { // must be a real path
                    _r = r.replace(/\[/g, '.').replace(/\]/g, '');
                }

                instance.rules[tmp + _r] = rules[r];
                //delete instance.rules[r];
                parseRules(rules[r], tmp + _r +'.');
            }
        }
    }

    /**
     * makeObject - Preparing form data
     *
     * @param {object} obj - data
     * @param {string\number\boolean} value
     * @param {array} string
     * @param {number} len
     * @param {number} i
     *
     * */
    var makeObject = function (obj, value, args, len, i) {

        if (i >= len) {
            return false
        }

        var key     = args[i].replace(/^\[|\]$/g, '');
        var nextKey = ( i < len-1 && typeof(args[i+1]) != 'undefined' ) ?  args[i+1].replace(/^\[|\]$/g, '') : null;

        if ( typeof(obj[key]) == 'undefined' ) {
            if (nextKey && /^\d+$/.test(nextKey)) {
                nextKey = parseInt(nextKey);
                obj[key] = []
            } else {
                obj[key] = {}
            }
        }

        for (var o in obj) {

            if ( typeof(obj[o]) == 'object' ) {

                if ( Array.isArray(obj[o]) ) {


                    if (o === key) {

                        var _args = JSON.parse(JSON.stringify(args));
                        _args.splice(0, 1);

                        for (var a = i, aLen = _args.length; a < aLen; ++a) {
                            key = parseInt(_args[a].replace(/^\[|\]$/g, ''))
                            obj[o][key] = {};

                            if (a == aLen-1) {
                                obj[o][key] = value;
                            }
                        }
                    }

                } else if ( o === key ) {

                    if (i == len-1) {
                        obj[o] = value;
                    } else {
                        makeObject(obj[o], value, args, len, i+1)
                    }
                }
            }
        }

    }

    var formatData = function (data) {

        var args        = null
            , obj       = {}
            , key       = null
            , fields    = {};

        for (name in data) {

            if ( /\[(.*)\]/.test(name) ) {
                // backup name key
                key = name;

                // properties
                args    = name.match(/(\[[-_\[a-z 0-9]*\]\]|\[[-_\[a-z 0-9]*\])/ig);

                // root
                name    = name.match(/^[-_a-z 0-9]+\[{0}/ig);

                // building object tree
                makeObject(obj, data[key], args, args.length, 0);

                fields[name] = merge( fields[name], obj );
                obj = {}

            } else {
                fields[name] = data[name];
            }
        }

        return fields
    }


    /**
     * bindForm
     *
     * @param {object} target - DOM element
     * @param {object} [customRule]
     * */
    var bindForm = function($target, customRule) {

        var $form = null, _id = null;

        try {
            if ( $target.getAttribute && $target.getAttribute('id') ) {
                _id = $target.getAttribute('id');
                if ( typeof(instance.$forms[_id]) != 'undefined')
                    $form = instance.$forms[_id];
                else
                    throw new Error('form instance `'+ _id +'` not found');

            } else {
                throw new Error('Validator::bindForm($target, customRule): `$target` must be a DOM element\n'+err.stack )
            }
        } catch(err) {
            throw new Error('Validator::bindForm($target, customRule) could not bind form `'+ $target +'`\n'+err.stack )
        }

        if ( typeof($form) != 'undefined' && $form.binded) {
            return false
        }

        var withRules = false, rule = null, evt = '', procced = null;

        if ( typeof(customRule) != 'undefined' || typeof(_id) == 'string' && typeof(instance.rules[_id.replace(/\-/g, '.')]) != 'undefined' ) {
            withRules = true;

            if ( customRule && typeof(customRule) == 'object' ) {
                rule = customRule
            } else if ( customRule && typeof(customRule) == 'string' && typeof(instance.rules[customRule.replace(/\-/g, '.')]) != 'undefined') {
                rule = instance.rules[customRule.replace(/\-/g, '.')]
            } else {
                rule = instance.rules[_id.replace(/\-/g, '.')]
            }

            $form.rules = rule
        }

        // form fields
        if (!$form.defaultFields)
            $form.defaultFields = {};

        // binding form elements
        var type        = null
            , id        = null
            // input: checkbox, radio
            , $inputs   = $target.getElementsByTagName('input')
            // select
            , $select   = $target.getElementsByTagName('select')
        ;

        var elId = null;
        for (var i = 0, len = $inputs.length; i < len; ++i) {
            elId = $inputs[i].getAttribute('id');
            if (!elId) {
                elId = 'input.' + uuid.v1();
                $inputs[i].setAttribute('id', elId)
            }

            if (!$form.defaultFields[ elId ])
                $form.defaultFields[ elId ] = $inputs[i].value;
        }

        var selectedIndex = null, selectedValue = null;
        for (var s = 0, sLen = $select.length; s < sLen; ++s) {
            elId = $select[s].getAttribute('id');
            if (!elId) {
                elId = 'select.' + uuid.v1();
                $select[s].setAttribute('id', elId)
            }

            if ($select[s].options && !$form.defaultFields[ elId ]) {
                selectedIndex = 0;
                selectedValue = $select[s].getAttribute('data-value');

                if ( $select[s].options[ $select[s].selectedIndex ].index ) {
                    selectedIndex = $select[s].options[ $select[s].selectedIndex ].index
                } else if ( typeof(selectedValue) != 'undefined' ) {
                    for (var o = 0, oLen = $select[s].options.length; o < oLen; ++o ) {
                        if ( $select[s].options[o].value == selectedValue) {
                            selectedIndex = o;
                            break
                        }
                    }
                }

                $form.defaultFields[ elId ] = selectedIndex;
                // update select
                $select[s].options[ selectedIndex ].selected = true;
                $select[s].setAttribute('data-value',  $select[s].options[ selectedIndex ].value);

            }
        }

        var updateCheckBox = function($el) {

            var checked     = $el.checked;

            if ( !checked || checked == 'null' || checked == 'false' || checked == '' ) {

                // prevents ticking behavior
                setTimeout(function () {
                    $el.checked = false;
                }, 0);

                $el.removeAttribute('checked');
                $el.value = false;
                $el.setAttribute('value', 'false');

            } else {

                // prevents ticking behavior
                setTimeout(function () {
                    $el.checked = true;
                }, 0);

                $el.setAttribute('checked', 'checked');
                //boolean exception handling
                $el.value = true;
                $el.setAttribute('value', 'true');

            }
        };

        var updateRadio = function($el, isInit) {
            var checked = $el.checked;
            var isBoolean = /^(true|false)$/i.test($el.value);

            // loop if radio group
            if (!isInit) {
                var radioGroup = document.getElementsByName($el.name);
                //console.log('found ', radioGroup.length, radioGroup)
                for (var r = 0, rLen = radioGroup.length; r < rLen; ++r) {
                    if (radioGroup[r].id !== $el.id) {
                        radioGroup[r].checked = false;
                        radioGroup[r].removeAttribute('checked');
                    }
                }
            }

            if ( !checked || checked == 'null' || checked == 'false' || checked == '' ) {

                // prevents ticking behavior
                setTimeout(function () {
                    $el.checked = false;
                }, 0)

                $el.removeAttribute('checked');

                if (isBoolean) {
                    $el.value = false;
                }

            } else {

                // prevents ticking behavior
                setTimeout(function () {
                    $el.checked = true;
                }, 0)

                $el.setAttribute('checked', 'checked');

                if (isBoolean) { // no multiple choice supported
                    $el.value = true;
                }
                var radioGroup = document.getElementsByName($el.name);
                    //console.log('found ', radioGroup.length, radioGroup)
                    for (var r = 0, rLen = radioGroup.length; r < rLen; ++r) {
                        if (radioGroup[r].id !== $el.id) {
                            radioGroup[r].checked = false;
                            radioGroup[r].removeAttribute('checked');
                            if (isBoolean) {
                                radioGroup[r].value = false;
                            }
                        }
                    }


            }
        }

        evt = 'click';

        procced = function () {
            // click proxy
            addListener(gina, $target, 'click', function(event) {

                if ( /(label)/i.test(event.target.tagName) )
                    return false;

                if ( typeof(event.target.id) == 'undefined' || !event.target.getAttribute('id') ) {
                    event.target.setAttribute('id', 'click.' + uuid.v1() );
                    event.target.id = event.target.getAttribute('id')
                } else {
                    event.target.id = event.target.getAttribute('id')
                }


                if (/^click\./.test(event.target.id) || withRules) {

                    var _evt = event.target.id;

                    if (!_evt) return false;

                    if ( ! /^click\./.test(_evt) ) {
                        _evt = event.target.id
                    }

                    // prevent event to be triggered twice
                    if ( typeof(event.defaultPrevented) != 'undefined' && event.defaultPrevented )
                        return false;

                    if (gina.events[_evt]) {
                        cancelEvent(event);

                        triggerEvent(gina, event.target, _evt, event.detail);
                    }

                }

            })
        }


        procced()

        for (var i=0, len = $inputs.length; i<len; ++i) {
            type    = $inputs[i].getAttribute('type');

            if ( typeof($inputs[i].id) == 'undefined' || $inputs[i].id == '' ) {
                $inputs[i]['id'] = type +'-'+ uuid.v1();
                $inputs[i].setAttribute('id', $inputs[i]['id'])
            }


            // recover default state only on value === true || false || on
            if ( typeof(type) != 'undefined' && type == 'checkbox' && /^(true|false|on)$/i.test($inputs[i].value) || typeof(type) != 'undefined' && type == 'checkbox' && !$inputs[i].getAttribute('value') ) {

                if ( !/^(true|false|on)$/i.test($inputs[i].value)  ) {

                    if ( !$inputs[i].checked || $inputs[i].checked == 'null' || $inputs[i].checked == 'false' || $inputs[i].checked == '' ) {
                        $inputs[i].value = false;
                        $inputs[i].setAttribute('value', false)
                    } else {
                        $inputs[i].value = true;
                        $inputs[i].setAttribute('value', true)
                    }
                }


                evt = $inputs[i].id;

                procced = function ($el, evt) {

                    // recover default state only on value === true || false
                    addListener(gina, $el, evt, function(event) {

                        if ( /^(true|false|on)$/i.test(event.target.value) ) {
                            cancelEvent(event);
                            updateCheckBox(event.target);
                        }
                    });

                    // default state recovery
                    var value = $el.value || $el.getAttribute('value') || $el.getAttribute('data-value');
                    if ( /^(true|false|on)$/i.test(value)  ) {

                        if ( typeof(value) != 'undefined' && /^(true|on|false)$/.test(value) ) {
                            $el.checked = /true|on/.test(value) ? true : false;
                        }

                        updateCheckBox($el);
                    }


                }

                if ( typeof(gina.events[evt]) != 'undefined' && gina.events[evt] == $inputs[i].id ) {
                    removeListener(gina, $inputs[i], evt);
                    procced($inputs[i], evt)

                } else {
                    procced($inputs[i], evt)
                }

            } else if ( typeof(type) != 'undefined' && type == 'radio' ) {

                evt = $inputs[i].id;

                procced = function ($el, evt) {
                    addListener(gina, $el, evt, function(event) {

                        cancelEvent(event);
                        updateRadio(event.target);
                    });


                    updateRadio($el, true);
                }

                if ( typeof(gina.events[evt]) != 'undefined' && gina.events[evt] == $inputs[i].id ) {
                    removeListener(gina, $inputs[i], evt);
                    procced(event.target, evt)

                } else {
                    procced($inputs[i], evt)
                }
            }
        }


        if (withRules) {

            evt = 'validate.' + _id;
            procced = function () {

                // attach form event
                addListener(gina, $target, evt, function(event) {
                    cancelEvent(event);


                    var result = event['detail'] || $form.eventData.validation;
                    //console.log('$form[ '+_id+' ] validation done !!!\n isValid ? ', result['isValid'](), '\nErrors -> ', result['errors'], '\nData -> ', result['data']);

                    handleErrorsDisplay(event['target'], result['errors'], result['data']);

                    var _id = event.target.getAttribute('id');

                    if ( result['isValid']() ) { // send if valid
                        // now sending to server
                        // TODO - remove comments, or replace 'submit' by 'submit.' + _id
                        //if ( $form.withUserBindings && /validate\./.test(event.type) && typeof(gina.events[event.type]) != 'undefined' ) {
                        //    triggerEvent(gina, event.target, 'submit', result)
                        //} else {

                            if (instance.$forms[_id]) {
                                instance.$forms[_id].send(result['data']);
                            } else if ($form) { // just in case the form is being destroyed
                                $form.send(result['data']);
                            }
                        //}
                    }

                    return false

                })
            }

            if ( typeof(gina.events[evt]) != 'undefined' && gina.events[evt] == 'validate.' + _id ) {
                removeListener(gina, $form, evt, procced)
            } else {
                procced()
            }



            var proccedToSubmit = function (evt, $submit) {
                // console.log('placing submit ', evt, $submit);
                // attach submit events
                addListener(gina, $submit, evt, function(event) {
                    // start validation
                    cancelEvent(event);

                    // getting fields & values
                    var $fields     = {}
                        , fields    = { '_length': 0 }
                        , id        = $target.getAttribute('id')
                        , rules     = ( typeof(gina.validator.$forms[id]) != 'undefined' ) ? gina.validator.$forms[id].rules : null
                        , name      = null
                        , value     = 0
                        , type      = null
                        , index     = { checkbox: 0, radio: 0 };


                    for (var i = 0, len = $target.length; i<len; ++i) {

                        name    = $target[i].getAttribute('name');

                        if (!name) continue;

                        // TODO - add switch cases against tagName (checkbox/radio)
                        if ( typeof($target[i].type) != 'undefined' && $target[i].type == 'radio' || typeof($target[i].type) != 'undefined' && $target[i].type == 'checkbox' ) {

                            if ( $target[i].checked ) {
                                fields[name] = $target[i].value
                            }  else if ( // force validator to pass `false` if boolean is required explicitly
                            rules
                            && typeof(rules[name]) != 'undefined'
                            && typeof(rules[name].isBoolean)
                            && typeof(rules[name].isRequired)
                            ) {
                                fields[name] = false;
                            }

                        } else {
                            fields[name]    = $target[i].value;
                        }


                        $fields[name]   = $target[i];
                        // reset filed error data attributes
                        $fields[name].setAttribute('data-gina-form-errors', '');

                        ++fields['_length']
                    }

                    //console.log('$fields =>\n' + $fields);

                    //instance.$forms[ $target.getAttribute('id') ].sent = false;

                    if ( fields['_length'] == 0 ) { // nothing to validate
                        delete fields['_length'];
                        var result = {
                            'errors'    : [],
                            'isValid'   : function() { return true },
                            'data'      : formatData(fields)
                        };

                        triggerEvent(gina, $target, 'validate.' + _id, result)

                    } else {
                        // update rule in case the current event is triggered outside the main sequence
                        // e.g.: form `id` attribute rewritten on the fly
                        _id = $target.getAttribute('id');
                        var customRule = $target.getAttribute('data-gina-form-rule');

                        if ( customRule ) { // 'data-gina-form-rule'
                            rule = gina.validator.rules[ customRule.replace(/\-/g, '.') ];
                        } else {
                            rule = gina.validator.$forms[ _id ].rules;
                        }

                        //console.log('testing rule [ '+_id.replace(/\-/g, '.') +' ]\n'+ JSON.stringify(rule, null, 4));
                        //console.log('validating ', $form, fields, rule);
                        validate($target, fields, $fields, rule, function onValidation(result){
                            //console.log('validation result ', 'validate.' + _id, JSON.stringify(result.data, null, 2));
                            //console.log('events ', 'validate.' + _id, self.events )
                            triggerEvent(gina, $target, 'validate.' + _id, result)
                        })
                    }

                });
            }


            // binding submit button
            var $submit = null, $buttons = [], $buttonsTMP = [], buttonId = null;
            $buttonsTMP = $target.getElementsByTagName('button');
            if ( $buttonsTMP.length > 0 ) {
                for(var b = 0, len = $buttonsTMP.length; b < len; ++b) {
                    if ($buttonsTMP[b].type == 'submit')
                        $buttons.push($buttonsTMP[b])
                }
            }

            $buttonsTMP = $target.getElementsByTagName('a');
            if ( $buttonsTMP.length > 0 ) {
                for(var b = 0, len = $buttonsTMP.length; b < len; ++b) {
                    if ( $buttonsTMP[b].attributes.getNamedItem('data-gina-form-submit'))
                        $buttons.push($buttonsTMP[b])
                }
            }


            var onclickAttribute = null, isSubmitType = false;
            for (var b=0, len=$buttons.length; b<len; ++b) {

                $submit = $buttons[b];

                if ($submit.tagName == 'A') { // without this test, XHR callback is ignored
                    //console.log('a#$buttons ', $buttonsTMP[b]);
                    onclickAttribute    = $submit.getAttribute('onclick');
                    isSubmitType        = $submit.getAttribute('data-gina-form-submit');

                    if ( !onclickAttribute && !isSubmitType) {
                        $submit.setAttribute('onclick', 'return false;')
                    } else if ( !/return false/ && !isSubmitType) {
                        if ( /\;$/.test(onclickAttribute) ) {
                            onclickAttribute += 'return false;'
                        } else {
                            onclickAttribute += '; return false;'
                        }
                    }
                }

                if (!$submit['id']) {

                    evt = 'click.'+ uuid.v1();
                    $submit['id'] = evt;
                    $submit.setAttribute( 'id', evt);

                } else {
                    evt = $submit['id'];
                }


                if ( typeof(gina.events[evt]) == 'undefined' || gina.events[evt] != $submit.id ) {
                    proccedToSubmit(evt, $submit)
                }

            }
        }



        evt = 'submit';

        // submit proxy
        addListener(gina, $target, evt, function(e) {

            var $target     = e.target
                , id        = $target.getAttribute('id')
                , isBinded  = instance.$forms[id].binded
            ;

            // prevent event to be triggered twice
            if ( typeof(e.defaultPrevented) != 'undefined' && e.defaultPrevented )
                return false;

            if (withRules || isBinded) {
                cancelEvent(e);
            }


            // just collect data for over forms
            // getting fields & values
            var $fields     = {}
                , fields    = { '_length': 0 }
                , id        = $target.getAttribute('id')
                , rules     = ( typeof(gina.validator.$forms[id]) != 'undefined' ) ? gina.validator.$forms[id].rules : null
                , name      = null
                , value     = 0
                , type      = null
                , index     = { checkbox: 0, radio: 0 };


            for (var i = 0, len = $target.length; i<len; ++i) {
                name = $target[i].getAttribute('name');

                if (!name) continue;

                // checkbox/radio
                if ( typeof($target[i].type) != 'undefined' && $target[i].type == 'radio' || typeof($target[i].type) != 'undefined' && $target[i].type == 'checkbox' ) {

                    if ( $target[i].checked ) {
                        fields[name] = $target[i].value
                    }  else if ( // force validator to pass `false` if boolean is required explicitly
                    rules
                    && typeof(rules[name]) != 'undefined'
                    && typeof(rules[name].isBoolean)
                    && typeof(rules[name].isRequired)
                    ) {
                        fields[name] = false;
                    }

                } else {
                    fields[name]    = $target[i].value;
                }



                $fields[name] = $target[i];
                // reset filed error data attributes
                $fields[name].setAttribute('data-gina-form-errors', '');

                ++fields['_length']
            }


            if ( fields['_length'] == 0 ) { // nothing to validate

                delete fields['_length'];
                var result = {
                    'errors'    : [],
                    'isValid'   : function() { return true },
                    'data'      : formatData(fields)
                };

                if ( typeof(gina.events['submit.' + id]) != 'undefined' ) { // if `on('submit', cb)` is binded
                    triggerEvent(gina, $target, 'submit.' + id, result);
                } else {
                    triggerEvent(gina, $target, 'validate.' + id, result);
                }

            } else {
                // update rule in case the current event is triggered outside the main sequence
                // e.g.: form `id` attribute rewritten on the fly
                //ruleId = id.replace(/-/g, '.');
                //if ( typeof(instance.$forms[id].rules) != 'undefined' )
                //    rule = instance.$forms[id].rules;

                var customRule = $target.getAttribute('data-gina-form-rule');

                if ( customRule ) { // 'data-gina-form-rule'
                    rule = gina.validator.rules[ customRule.replace(/\-/g, '.') ];
                } else {
                    rule = gina.validator.$forms[ id ].rules;
                }

                validate($target, fields, $fields, rule, function onValidation(result){
                    if ( typeof(gina.events['submit.' + id]) != 'undefined' ) { // if `on('submit', cb)` is binded
                        triggerEvent(gina, $target, 'submit.' + id, result);
                    } else {
                        triggerEvent(gina, $target, 'validate.' + id, result);
                    }
                })
            }
        });

        instance.$forms[_id]['binded']  = true;
    }

    var validate = function($form, fields, $fields, rules, cb) {

        delete fields['_length']; //cleaning

        var id = null, data = null;
        if (isGFFCtx) {
            id = $form.getAttribute('id') || $form.id;
            instance.$forms[id].fields = fields;
        }
        //console.log(fields, $fields);

        var d = new FormValidator(fields, $fields), args = null;
        var fieldErrorsAttributes = {};
        var re = null, flags = null;

        var forEachField = function($form, fields, $fields, rules, cb, i) {
            var hasCase = false, conditions = null;
            var caseValue = null, caseType = null;
            var localRules = null;

            //console.log('parsing ', fields, $fields, rules);

            for (var field in fields) {

                // $fields[field].tagName getAttribute('type')
                if ( $fields[field].tagName.toLowerCase() == 'input' && $fields[field].getAttribute('type') && !$fields[field].checked ) {
                    continue;
                }

                hasCase = ( typeof(rules['_case_' + field]) != 'undefined' ) ? true : false;
                if (hasCase) {

                    conditions = rules['_case_' + field]['conditions'];

                    if ( !conditions ) {
                        throw new Error('[ ginaFormValidator ] case `_case_'+field+'` found without `condition(s)` !');
                    }

                    for (var c = 0, cLen = conditions.length; c<cLen; ++c) {

                        caseValue = fields[field];

                        if (isGFFCtx) {
                            if (fields[field] == "true")
                                caseValue = true;
                            else if (fields[field] == "false")
                                caseValue = false;
                        }

                        //console.log(caseValue +' VS '+ conditions[c]['case'], "->", (caseValue == conditions[c]['case'] || Array.isArray(conditions[c]['case']) && conditions[c]['case'].indexOf(caseValue) > -1) );
                        if ( conditions[c]['case'] === caseValue || Array.isArray(conditions[c]['case']) && conditions[c]['case'].indexOf(caseValue) > -1 || /^\//.test(conditions[c]['case']) ) {

                            //console.log('[fields ] ' + JSON.stringify(fields, null, 4));
                            localRules = {};

                            for (var f in conditions[c]['rules']) {
                                //console.log('F: ', f, '\nrule: '+ JSON.stringify(conditions[c]['rules'][f], null, 2));
                                if ( /^\//.test(f) ) { // RegExp found

                                    re      = f.match(/\/(.*)\//).pop();
                                    flags   = f.replace('/'+ re +'/', '');
                                    re      = new RegExp(re, flags);

                                    for (var localField in $fields) {
                                        if ( re.test(localField) ) {
                                            if ( /^\//.test(conditions[c]['case']) ) {
                                                re      = conditions[c]['case'].match(/\/(.*)\//).pop();
                                                flags   = conditions[c]['case'].replace('/'+ re +'/', '');
                                                re      = new RegExp(re, flags);

                                                if ( re.test(caseValue) ) {
                                                    localRules[localField] = conditions[c]['rules'][f]
                                                }

                                            } else {
                                                localRules[localField] = conditions[c]['rules'][f]
                                            }
                                        }
                                    }

                                } else {
                                    if ( /^\//.test(conditions[c]['case']) ) {
                                        re      = conditions[c]['case'].match(/\/(.*)\//).pop();
                                        flags   = conditions[c]['case'].replace('/'+ re +'/', '');
                                        re      = new RegExp(re, flags);

                                        if ( re.test(caseValue) ) {
                                            localRules[f] = conditions[c]['rules'][f]
                                        }

                                    } else {
                                        localRules[f] = conditions[c]['rules'][f]
                                    }
                                }
                            }
                            //console.log('parsing ', localRules, fields);
                            if (isGFFCtx)
                                forEachField($form, fields, $fields, localRules, cb, i+1);
                            else
                                return forEachField($form, fields, $fields, localRules, cb, i+1);
                        }
                    }

                }


                if ( typeof(rules[field]) == 'undefined' ) continue;


                // check against rule
                for (var rule in rules[field]) {
                    // check for rule params
                    try {

                        if ( Array.isArray(rules[field][rule]) ) { // has args
                            //convert array to arguments
                            args = rules[field][rule];
                            d[field][rule].apply(d[field], args);
                        } else {
                            d[field][rule](rules[field][rule]);
                        }

                    } catch (err) {
                        if (rule == 'conditions') {
                            throw new Error('[ ginaFormValidator ] could not evaluate `'+field+'->'+rule+'()` where `conditions` must be a `collection` (Array)\nStack:\n'+ (err.stack|err.message))
                        } else {
                            throw new Error('[ ginaFormValidator ] could not evaluate `'+field+'->'+rule+'()`\nStack:\n'+ (err.stack|err.message))
                        }
                    }

                }
            }


            --i;

            if (i < 0) {

                var errors = d['getErrors']();

                // adding data attribute to handle display refresh
                for (var field in errors) {
                    for (rule in errors[field]) {
                        if (!fieldErrorsAttributes[field]) {
                            fieldErrorsAttributes[field] = ''
                        }

                        if (fieldErrorsAttributes[field].indexOf(rule) < 0)
                            fieldErrorsAttributes[field] += rule +' ';
                    }

                    if (isGFFCtx)
                        $fields[field].setAttribute('data-gina-form-errors', fieldErrorsAttributes[field].substr(0, fieldErrorsAttributes[field].length-1))
                }

                //calling back
                try {
                    data = formatData( d['toData']() );

                    if ( typeof(window.ginaToolbar) == 'object' ) {
                        // update toolbar
                        if (!gina.forms.sent)
                            gina.forms.sent = {};

                        gina.forms.sent = data;

                        window.ginaToolbar.update('forms', gina.forms);
                    }
                } catch (err) {
                    throw err
                }

                if ( typeof(cb) != 'undefined' && typeof(cb) === 'function' ) {

                    cb({
                        'isValid'   : d['isValid'],
                        'errors'    : errors,
                        'data'      : data
                    })

                } else {

                    return {
                        'isValid'   : d['isValid'],
                        'errors'    : errors,
                        'data'      : data
                    }
                }
            }
        }

        if (isGFFCtx)
            forEachField($form, fields, $fields, rules, cb, 0);
        else
            return forEachField($form, fields, $fields, rules, cb, 0);
    }

    var setupInstanceProto = function() {

        instance.setOptions             = setOptions;
        instance.getFormById            = getFormById;
        instance.validateFormById       = validateFormById;
        instance.target                 = document;
        instance.validateFormById       = validateFormById;
        instance.resetErrorsDisplay     = resetErrorsDisplay;
        instance.resetFields            = resetFields;
        instance.handleErrorsDisplay    = handleErrorsDisplay;
        instance.send                   = send;
    }

    if (isGFFCtx) {
        return init(rules)
    } else {
        return backendInit(rules, data, formId)
    }

}
863360
function RuleTypeToRule(rule) {
        if (rule instanceof Rule)
            return rule;
        if (typeof (rule) === "string")
            return text(rule);
        if (typeof (rule) === "boolean")
            return rule ? Myna.truePredicate : Myna.falsePredicate;
        throw new Error("Invalid rule type: " + rule);
    }
345200
function uuid(version) {
    var v = (version) ? version : '';

    // verify its a valid string
    this.assert(
      (typeof this._obj === 'string' || this._obj instanceof String),
      'expected #{this} to be of type #{exp} but got #{act}',
      'expected #{this} to not be of type #{exp}',
      'string',
      typeof(this._obj)
    );

    // assert it is a valid uuid
    var regex = getRegEx(v);
    this.assert(
      regex.test(this._obj),
      'expected #{this} to be a valid UUID ' + v,
      'expected #{this} to not be a UUID ' + v
    );    
  }
57010
function validateData(data, schema, fn) {
        debug('Schema:', schema);
        debug('Data:', data);

        if (!tv4.validate(data, schema)) {
            return fn(tv4.error);
        }

        return fn(null, true);
    }
1000810
function validateInput(prop, ctx, validate) {
  debug('validating %s', prop);

  const request = ctx.request;
  const res = Joi.validate(request[prop], validate[prop]);

  if (res.error) {
    res.error.status = validate.failure;
    return res.error;
  }

  // update our request w/ the casted values
  switch (prop) {
    case 'header': // request.header is getter only, cannot set it
    case 'query': // setting request.query directly causes casting back to strings
      Object.keys(res.value).forEach((key) => {
        request[prop][key] = res.value[key];
      });
      break;
    case 'params':
      request.params = ctx.params = res.value;
      break;
    default:
      request[prop] = res.value;
  }
}
1280795
function BusinessRules(Data) {
            this.Data = Data;
            this.InvoiceValidator = this.createInvoiceValidator().CreateRule("Data");

            this.ValidationResult = this.InvoiceValidator.ValidationResult;
        }
424959
public static function url(Validator $validator, $data, $pattern, $rule)
    {
        foreach (Validator::getValues($data, $pattern) as $attribute => $value) {
            if (null === $value || empty($value)) {
                continue;
            }
            if (false !== filter_var($value, FILTER_VALIDATE_URL)) {
                continue;
            }

            $validator->addError($attribute, $rule);
        }
    }
579339
function (handler, group, callback) {

    if (typeof group === 'function') {
      callback = group;
      group = undefined;
    }

    const validation = cache.get(constant.SYSTEM_DB_VALIDATOR).filter(item => {
      return item.group === (group || handler.req.path);
    });

    async.mapSeries(validation, (item, next) => {

      const func = rule[item.rule];
      if (func) {
        return func.call(this, handler, item, next);
      }

      log.debug('Rule does not define : ' + item.rule);
      next();
    }, (err, result) => {
      if (err) {
        return callback(err);
      }

      result = _.compact(_.flatten(result));
      if (_.isEmpty(result)) {
        return callback();
      }

      log.info(result);
      callback(new error.ParamError(), result);
    });
  }
1041900
function _parseRule(rule) {
    var parameters = [];

    // The format for specifying validation rules and parameters follows an
    // easy {rule}:{parameters} formatting convention. For instance the
    // rule "Max:3" states that the value may only be three letters.
    if (rule.indexOf(':')) {
      var ruleInfo  = rule.split(':');
      parameters = _parseParameters(ruleInfo[0], ruleInfo[1]);
    }

    return { parameters: parameters, rule: ruleInfo[0]};
  }
206448
function processRule ( rule , returnObj ) {
    // some additional properties
    //var selector_arr = rule.selector.split( " " );
    rule.name = getSelectorName( rule.selector );//selector_arr.pop();
    // give a shorter unique id to it
    //var cssString = ruleToCSSString( rule );
    // "-" causes Router to get confused
    // only want something unique, so replacing with an "n"
    rule.uuid = getRuleUUID( rule );
    rule.children = [];
    rule.total_child_rules = 0;// just simple rules, not everything
    rule.total_child_comps = 0;
    rule.is_duplicate = false;

    // find depth
    rule.depth = Math.min( 6 , rule.selector.split(" ").length-1 );
    if ( !returnObj.totals.depths[ rule.depth ] )
        returnObj.totals.depths[ rule.depth ] = 0;
    returnObj.totals.depths[ rule.depth ]++;
    if ( !returnObj.depths_all[ rule.depth ] )
        returnObj.depths_all[ rule.depth ] = [];
    returnObj.depths_all[ rule.depth ].push( rule.uuid );

    rule.metadata = {};
    rule.states = [];
    rule.pseudos = [];

    if ( rule.type == "rule" ) {
        returnObj.totals.rules++;
        returnObj.rules.push( rule );
        if ( !returnObj.totals.depths_rules[ rule.depth ] )
            returnObj.totals.depths_rules[ rule.depth ] = 0;
        returnObj.totals.depths_rules[ rule.depth ]++;
    }else if ( rule.type == "tagged_rule" ){
        processComponent( rule , returnObj );
    }

    returnObj.totals.overall++;

    // selector hash
    if ( !returnObj.selector_hash[rule.selector] ) {
        returnObj.selector_hash[rule.selector] = rule;
    }else{
        console.log("ERROR, selector not unique!" , rule.selector );
    }

    returnObj.uuid_hash[ rule.uuid ] = rule;

    //finish up...
    returnObj.selector_hash[rule.name] = rule;
    returnObj.selectors.push( rule );
}
908120
function rule_match_post_format( $match, $rule, $options )
	{
		// vars
		$post_format = $options['post_format'];
		if( !$post_format )
		{	
			// validate
			if( !$options['post_id'] )
			{
				return false;
			}
			
			
			// post type
			if( !$options['post_type'] )
			{
				$options['post_type'] = get_post_type( $options['post_id'] );
			}
			
		
			// does post_type support 'post-format'
			if( post_type_supports( $options['post_type'], 'post-formats' ) )
			{
				$post_format = get_post_format( $options['post_id'] );
				
				if( $post_format === false )
				{
					$post_format = 'standard';
				}
			}
		}

       	
       	if($rule['operator'] == "==")
        {
        	$match = ( $post_format === $rule['value'] );
        	 
        }
        elseif($rule['operator'] == "!=")
        {
        	$match = ( $post_format !== $rule['value'] );
        }
        
        
        
        return $match;
        
    }
1187955
def loadRule(rule_json_object):
    
    name = rule_json_object['name']
    rule_type = rule_json_object['rule_type']
    validation_regex = None
    required = False
    removehtml = False
    include_end_regex = False #Default to false for bakward compatibility
    strip_end_regex = None
    sub_rules = []
    begin_stripe_id = None
    end_stripe_id = None
    begin_shift = 0
    end_shift = 0

    if 'sub_rules' in rule_json_object:
        sub_rules = rule_json_object['sub_rules']
    
    if 'validation_regex' in rule_json_object:
        validation_regex = rule_json_object['validation_regex']
    
    if 'required' in rule_json_object:
        required = rule_json_object['required']
    
    if 'removehtml' in rule_json_object:
        removehtml = rule_json_object['removehtml']
        
    if 'include_end_regex' in rule_json_object:
        include_end_regex = rule_json_object['include_end_regex']
    
    if 'strip_end_regex' in rule_json_object:
        strip_end_regex = rule_json_object['strip_end_regex']

    if 'begin_stripe_id' in rule_json_object:
        begin_stripe_id = rule_json_object['begin_stripe_id']

    if 'end_stripe_id' in rule_json_object:
        end_stripe_id = rule_json_object['end_stripe_id']

    if 'begin_shift' in rule_json_object:
        begin_shift = rule_json_object['begin_shift']

    if 'end_shift' in rule_json_object:
        end_shift = rule_json_object['end_shift']

    rule = {}
    
    """ This is where we add our new type """
    if rule_type == ITEM_RULE or rule_type == 'RegexRule':
        begin_regex = rule_json_object['begin_regex']
        end_regex = rule_json_object['end_regex']
        rule = ItemRule(name, begin_regex, end_regex, include_end_regex, strip_end_regex, validation_regex, required,
                        removehtml, sub_rules, begin_stripe_id, end_stripe_id, begin_shift, end_shift)
    if rule_type == ITERATION_RULE or rule_type == 'RegexIterationRule':
        begin_regex = rule_json_object['begin_regex']
        end_regex = rule_json_object['end_regex']
        iter_begin_regex = rule_json_object['iter_begin_regex']
        iter_end_regex = rule_json_object['iter_end_regex']
        no_first_begin_iter_rule = False
        if 'no_first_begin_iter_rule' in rule_json_object:
            no_first_begin_iter_rule = rule_json_object['no_first_begin_iter_rule']
        no_last_end_iter_rule = False
        if 'no_last_end_iter_rule' in rule_json_object:
            no_last_end_iter_rule = rule_json_object['no_last_end_iter_rule']
        
        rule = IterationRule(name, begin_regex, end_regex, iter_begin_regex, iter_end_regex,
                                  include_end_regex, strip_end_regex, no_first_begin_iter_rule,
                                  no_last_end_iter_rule, validation_regex, required, removehtml,
                                  sub_rules, begin_shift=begin_shift, end_shift=end_shift)

    if 'id' in rule_json_object:
        rule.id = rule_json_object['id']
    
    return rule
218522
function validate(message, type) {
  assert(
    typeOf(message) === 'object',
    'You must pass a message object.',
  );

  rules.forEach((rule) => {
    if (message[rule.name]) {
      const types = [].concat(rule.types);

      assert(
        types.some(t => typeOf(message[rule.name]) === t),
        `"${rule.name}" must be ${types.join(' or ')}.`,
      );
    }
  });

  if (asserts[type]) {
    asserts[type](message);
  } else {
    throw new TypeError('Invalid event type');
  }
}
339824
def validate_rules(lexer)
      if lexer.state_re.empty?
        complain("No rules of the form rule(name, pattern) are defined")
      end

      lexer.state_info.each do |state_name, state_type|
        if !lexer.state_re.key?(state_name.to_sym)
          complain("No rules defined for state '#{state_name}'")
        end
      end
    end
590833
function FormValidatorUtil(data, $fields) {

    var isGFFCtx        = ( ( typeof(module) !== 'undefined' ) && module.exports ) ? false : true;

    if (isGFFCtx && !$fields )
        throw new Error('No `Validator` instance found.\nTry:\nvar FormValidator = require("gina/validator"):\nvar formValidator = new FormValidator(...);')

    var merge           = (isGFFCtx) ? require('utils/merge') : require('../../../../utils/lib/merge');
    var helpers         = (isGFFCtx) ? {} : require('../../../../utils/helpers');
    var dateFormat      = (isGFFCtx) ? require('helpers/dateFormat') : helpers.dateFormat;

    var local = {
        'errors': {},
        'keys': {
            '%l': 'label', // %l => label: needs `data-gina-form-field-label` attribute (frontend only)
            '%n': 'name', // %n => field name
            '%s': 'size' // %s => length
        },
        'errorLabels': {},
        'data': {}, // output to send
        'excluded': []
    };

    local.errorLabels = {
        'is': 'Condition not satisfied',
        'isEmail': 'A valid email is required',
        'isRequired': 'Cannot be left empty',
        'isBoolean': 'Must be a valid boolean',
        'isNumber': 'Must be a number: allowed values are integers or floats',
        'isNumberLength': 'Must contain %s characters',
        'isNumberMinLength': 'Should be at least %s characters',
        'isNumberMaxLength': 'Should not be more than %s characters',
        'isInteger': 'Must be an integer',
        'isIntegerLength': 'Must have %s characters',
        'isIntegerMinLength': 'Should be at least %s characters',
        'isIntegerMaxLength': 'Should not be more than %s characters',
        'toInteger': 'Could not be converted to integer',
        'isFloat': 'Must be a proper float',
        'isFloatException': 'Float exception found: %n',
        'toFloat': 'Could not be converted to float',
        'toFloatNAN': 'Value must be a valid number',
        'isDate': 'Must be a valid Date',
        'isString': 'Must be a string',
        'isStringLength': 'Must have %s characters',
        'isStringMinLength': 'Should be at least %s characters',
        'isStringMaxLength': 'Should not be more than %s characters'
    };

    if (!data) {
        throw new Error('missing data param')
    } else {
        // cloning
        var self  = JSON.parse( JSON.stringify(data) );
        local.data = JSON.parse( JSON.stringify(data) )
    }


    var val = null, label = null;
    for (var el in self) {

        if ( typeof(self[el]) == 'object' ) {
            try {
                val = JSON.parse( JSON.stringify( self[el] ))
            } catch (err) {
                val = self[el]
            }
        } else {
            val = self[el]
        }

        label = '';
        if ( isGFFCtx && typeof($fields) != 'undefined' ) { // frontend only
            label = $fields[el].getAttribute('data-gina-form-field-label') || '';
        }

        // keys are stringyfied because of the compiler !!!
        self[el] = {
            'target': (isGFFCtx) ? $fields[el] : null,
            'name': el,
            'value': val,
            'valid': false,
            // is name by default, but you should use setLabe(name) to change it if you need to
            'label': label,
            // check as field to exclude while sending datas to the model
            'exclude': false
        };

        /**
         *
         * is(condition)       -> validate if value matches `condition`
         *
         *  When entered in a JSON rule, you must double the backslashes
         *
         *  e.g.:
         *       "/\\D+/"       -> like [^0-9]
         *       "!/^\\\\s+/"   -> not starting by white space allow
         *       "/^[0-9]+$/"   -> only numbers
         *       "$field === $fieldOther"   -> will be evaluated
         *
         * @param {object|string} condition - RegExp object, or condition to eval, or eval result
         * @param {string} [errorMessage] - error message
         * @param {string} [errorStack] - error stack
         *
         * */
        self[el]['is'] = function(condition, errorMessage, errorStack) {
            var valid   = false;
            var errors  = {};

            if ( /\$[-_\[\]a-z 0-9]+|^!\//i.test(condition) ) {

                var variables = condition.match(/\${0}[-_\[\]a-z0-9]+/ig); // without space(s)
                var compiledCondition = condition;
                var re = null
                for (var i = 0, len = variables.length; i < len; ++i) {
                    if ( typeof(self[ variables[i] ]) != 'undefined' && variables[i]) {
                        re = new RegExp("\\$"+ variables[i] +"(?!\\S+)", "g");
                        if ( self[ variables[i] ].value == "" ) {
                            compiledCondition = compiledCondition.replace(re, '"');
                        } else if ( typeof(self[ variables[i] ].value) == 'string' ) {
                            compiledCondition = compiledCondition.replace(re, '"'+ self[ variables[i] ].value +'"');
                        } else {
                            compiledCondition = compiledCondition.replace(re, self[ variables[i] ].value);
                        }
                    }
                }

                try {
                    // security checks
                    compiledCondition = compiledCondition.replace(/(\(|\)|return)/g, '');
                    valid = eval(compiledCondition+'.test("'+ this.value +'")')
                } catch (err) {
                    throw new Error(err.stack||err.message)
                }
            } else if ( condition instanceof RegExp ) {

                valid = condition.test(this.value) ? true : false;

            } else if( typeof(condition) == 'boolean') {

                valid = (condition) ? true : false;

            } else {
                try {
                    // TODO - motif /gi to pass to the second argument
                    valid = new RegExp(condition.replace(/\//g, '')).test(this.value)
                } catch (err) {
                    throw new Error(err.stack||err.message)
                }
            }

            if (!valid) {
                errors['is'] = replace(this.error || errorMessage || local.errorLabels['is'], this);
                if ( typeof(errorStack) != 'undefined' )
                    errors['stack'] = errorStack;
            }

            this.valid = valid;
            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }

        self[el]['isEmail'] = function() {


            this.value      = local['data'][this.name] = this.value.toLowerCase();

            var rgx         = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
            var isValid     = rgx.test(this['value']) ? true : false;
            var errors      = self[this['name']]['errors'] || {};

            if ( !errors['isRequired'] && this.value == '' ) {
                isValid = true;
            }

            if (!isValid) {
                errors['isEmail'] = replace(this['error'] || local.errorLabels['isEmail'], this)
            }

            this.valid = isValid;

            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this['name']]
        }

        /**
         * Check if boolean and convert to `true/false` booloean if value is a string or a number
         * Will include `false` value if isRequired
         * */
        self[el]['isBoolean'] = function() {
            var val     = null
                , errors = self[this['name']]['errors'] || {}
            ;

            if ( errors['isRequired'] && this.value == false ) {
                isValid = true;
                delete errors['isRequired'];
                this['errors'] = errors;
            }

            switch(this.value) {
                case 'true':
                case true:
                case 1:
                    val = this.value = local.data[this.name] = true;
                    break;
                case 'false':
                case false:
                case 0:
                    val = this.value = local.data[this.name] = false;
                    break;
            }
            var valid = (val !== null) ? true : false;

            if (!valid) {
                errors['isBoolean'] = replace(this.error || local.errorLabels['isBoolean'], this)
            }

            this.valid = valid;
            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }

        /**
         * Check if value is an a Number.
         *  - valid if a number is found
         *  - cast into a number if a string is found
         *  - if string is blank, no transformation will be done: valid if not required
         *
         *  @param {number} minLength
         *  @param {number} maxLength
         *
         *  @return {object} result
         * */
        self[el]['isNumber'] = function(minLength, maxLength) {
            var val             = this.value
                , len           = 0
                , isValid       = false
                , isMinLength   = true
                , isMaxLength   = true
                , errors        = {}
                ;


            // test if val is a number
            try {
                // if val is a string replaces comas by points
                if ( typeof(val) == 'string' && /,/g.test(val) ) {
                    val = this.value = parseFloat( val.replace(/,/g, '.').replace(/\s+/g, '') );
                } else if ( typeof(val) == 'string' && val != '') {
                    val = this.value = parseInt( val );
                }

            } catch (err) {
                errors['isNumber'] = replace(this.error || local.errorLabels['isNumber'], this);
                this.valid = false;
                if ( errors.count() > 0 )
                    this['errors'] = errors;
            }

            if ( +val === +val ) {
                isValid = true;
                if ( !errors['isRequired'] && val != '' ) {
                    len = val.toString().length;
                    // if so also test max and min length if defined
                    if (minLength && typeof(minLength) == 'number' && len < minLength) {
                        isMinLength = false;
                        this['size'] = minLength;
                    }
                    if (maxLength && typeof(maxLength) == 'number' && len > maxLength) {
                        isMaxLength = false;
                        this['size'] = maxLength;
                    }
                }
            }

            // if val is invalid return error message
            if ( !isValid || !isMinLength || !isMaxLength ) {

                if ( !isValid )
                    errors['isNumber'] = replace(this.error || local.errorLabels['isNumber'], this);
                if ( !isMinLength || !isMaxLength ) {
                    if ( !isMinLength )
                        errors['isNumberLength'] = replace(this.error || local.errorLabels['isNumberMinLength'], this);
                    if ( !isMaxLength )
                        errors['isNumberLength'] = replace(this.error || local.errorLabels['isNumberMaxLength'], this);
                    if ( minLength === maxLength )
                        errors['isNumberLength'] = replace(this.error || local.errorLabels['isNumberLength'], this);
                }

                isValid = false;
            }

            this.valid = isValid;
            val = this.value = local.data[this.name] = ( val != '' ) ? Number(val) : val;
            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }

        self[el]['toInteger'] = function() {
            var val = this.value, errors = {};

            if (!val) {
                return self[this.name]
            } else {
                try {
                    //val = this.value = local.data[this.name] = ~~(val.match(/[0-9]+/g).join(''));
                    val = this.value = local.data[this.name] = Math.round(val);
                } catch (err) {

                    errors['toInteger'] = replace(this.error || local.errorLabels['toInteger'], this);
                    this.valid = false;
                    if ( errors.count() > 0 )
                        this['errors'] = errors;
                }

            }

            return self[this.name]
        }

        self[el]['isInteger'] = function(minLength, maxLength) {
            var val             = this.value
                , isValid       = false
                , isMinLength   = true
                , isMaxLength   = true
                , errors        = {}
                ;

            // test if val is a number
            if ( +val === +val && val % 1 === 0 ) {
                isValid = true;
                if ( !errors['isRequired'] && val != '' ) {
                    // if so also test max and min length if defined
                    if (minLength && typeof(minLength) == 'number' && val.length < minLength) {
                        isMinLength = false;
                        this['size'] = minLength;
                    }
                    if (maxLength && typeof(maxLength) == 'number' && val.length > maxLength) {
                        isMaxLength = false;
                        this['size'] = maxLength;
                    }
                }
            }
            // if val is invalid return error message
            if ( !isValid || !isMinLength || !isMaxLength ) {

                if ( !isValid )
                    errors['isInteger'] = replace(this.error || local.errorLabels['isInteger'], this);

                if ( !isMinLength || !isMaxLength ) {

                    if ( !isMinLength ) {
                        errors['isIntegerLength'] = replace(this.error || local.errorLabels['isIntegerMinLength'], this);
                        isValid = false;
                    }

                    if ( !isMaxLength ) {
                        errors['isIntegerLength'] = replace(this.error || local.errorLabels['isIntegerMaxLength'], this);
                        isValid = false;
                    }

                    if ( minLength === maxLength ) {
                        errors['isIntegerLength'] = replace(this.error || local.errorLabels['isIntegerLength'], this);
                        isValid = false;
                    }
                }
            }

            this.valid = isValid;
            val = this.value = local.data[this.name] = Number(val);

            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }


        self[el]['toFloat'] = function(decimals) {
            if ( typeof(this.value) == 'string' ) {
                this.value = this.value.replace(/\s+/g, '');
            }

            var val = this.value, errors = {}, isValid = true;

            if (decimals) {
                this['decimals'] = parseInt(decimals)
            } else if ( typeof(this['decimals']) == 'undefined' ) {
                this['decimals'] = 2
            }

            if (!val) {
                return self[this.name]
            } else {
                if ( this['isNumber']().valid ) {
                    try {

                        if ( !Number.isFinite(val) ) {
                            val = this.value = local.data[this.name] = new Number(parseFloat(val.match(/[0-9.,]+/g).join('').replace(/,/, '.')));// Number <> number
                        }

                        this.target.setAttribute('value', val);
                    } catch(err) {
                        isValid = false;
                        errors['toFloat'] = replace(this.error || local.errorLabels['toFloat'], this);
                        this.valid = false;
                        if ( errors.count() > 0 )
                            this['errors'] = errors;
                    }
                } else {
                    isValid = false;
                    errors['toFloat'] = replace(this.error || local.errorLabels['toFloatNAN'], this)
                }
            }

            if (this['decimals'] && val && !errors['toFloat']) {
                this.value = local.data[this.name] = parseFloat(this.value.toFixed(this['decimals']));
            }

            this.valid = isValid;
            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }

        /**
         * Check if value is float. No transformation is done here.
         * Can be used in combo preceded by *.toFloat(2) to transform data if needed:
         *  1 => 1.0
         *  or
         *  3 500,5 => 3500.50
         *
         *
         * @param {number} [ decimals ]
         *
         * TODO - decimals transformation
         * */
        self[el]['isFloat'] = function(decimals) {

            if ( typeof(this.value) == 'string' ) {
                this.value = this.value.replace(/\s+/g, '');
            }

            var val         = this.value
                , isValid   = false
                , errors    = {};


            if ( typeof(val) == 'string' && /\./.test(val) && Number.isFinite( Number(val) ) ) {
                isValid = true
            }

            // if string replaces comas by points
            if (typeof(val) == 'string' && /,/g.test(val)) {
                val =  this.value = local.data[this.name] = Number(val.replace(/,/g, '.'))
            }

            // test if val is strictly a float
            if ( Number(val) === val && val % 1 !== 0 ) {
                this.value = local.data[this.name] = Number(val);
                isValid = true
            } else {
                isValid = false
            }

            if ( !errors['isRequired'] && this.value == '' ) {
                isValid = true
            }

            if (!isValid) {
                errors['isFloat'] = replace(this.error || local.errorLabels['isFloat'], this)
            }

            this.valid = isValid;
            if ( errors.count() > 0 )
                this['errors'] = errors;

            return self[this.name]
        }

        self[el]['isRequired'] = function(isApplicable) {

            if ( typeof(isApplicable) == 'boolean' && !isApplicable ) {

                this.valid = true;

                return self[this.name]
            }

            // radio group case
            if ( isGFFCtx && this.target.tagName == 'INPUT' && typeof(this.target.type) != 'undefined' && this.target.type == 'radio' ) {
                var radios = document.getElementsByName(this.name);
                for (var i = 0, len = radios.length; i < len; ++i) {
                    if (radios[i].checked) {
                        if ( /true|false/.test(radios[i].value) ) {
                            this.value = local.data[this.name] = ( /true/.test(radios[i].value) ) ? true : false
                        } else {
                            this.value = local.data[this.name] = radios[i].value;
                        }

                        this.valid = true;
                        break;
                    }
                }
            }


            var isValid = ( typeof(this.value) != 'undefined' && this.value != null && this.value != '' && !/^\s+/.test(this.value) ) ? true : false;
            var errors  = {};


            if (!isValid) {
                errors['isRequired'] = replace(this.error || local.errorLabels['isRequired'], this)
            }

            this.valid = isValid;
            if (errors.count() > 0)
                this['errors'] = errors;

            return self[this.name]
        }
        /**
         *
         * isString()       -> validate if value is string
         * isString(10)     -> validate if value is at least 10 chars length
         * isString(0, 45)  -> no minimum length, but validate if value is maximum 45 chars length
         *
         * @param {number|undefined} [ minLength ]
         * @param {number} [ maxLength ]
         * */
        self[el]['isString'] = function(minLength, maxLength) {

            var val             = this.value
                , isValid       = false
                , isMinLength   = true
                , isMaxLength   = true
                , errors        = {}
                ;


            // test if val is a string
            if ( typeof(val) == 'string' ) {
                isValid = true;

                if ( !errors['isRequired'] && val != '' ) {
                    // if so also test max and min length if defined
                    if (minLength && typeof(minLength) == 'number' && val.length < minLength) {
                        isMinLength = false;
                        this['size'] = minLength;
                    }
                    if (maxLength && typeof(maxLength) == 'number' && val.length > maxLength) {
                        isMaxLength = false;
                        this['size'] = maxLength;
                    }
                }

            }

            // if val is invalid return error message
            if (!isValid || !isMinLength || !isMaxLength ) {

                if (!isValid && errors['isRequired'] && val == '') {
                    isValid = false;
                    errors['isString'] = replace(this['error'] || local.errorLabels['isString'], this);
                } else if (!isValid && !errors['isRequired']) {
                    isValid = true;
                }

                if ( !isMinLength || !isMaxLength) {
                    isValid = false;

                    if ( !isMinLength )
                        errors['isStringLength'] = replace(this['error'] || local.errorLabels['isStringMinLength'], this);
                    if ( !isMaxLength )
                        errors['isStringLength'] = replace(this['error'] || local.errorLabels['isStringMaxLength'], this);
                    if (minLength === maxLength)
                        errors['isStringLength'] = replace(this['error'] || local.errorLabels['isStringLength'], this);
                }

            }

            this.valid = isValid;
            if ( errors.count() > 0 )
                this['errors'] = errors;


            return self[this.name]
        }

        /**
         * Check if date
         *
         * @param {string} [mask] - by default "yyyy-mm-dd"
         *
         * @return {date} date - extended by gina::utils::dateFormat; an adaptation of Steven Levithan's code
         * */
        self[el]['isDate'] = function(mask) {
            var val         = this.value
                , isValid   = false
                , errors    = {}
                ;
            if (!val) return self[this.name];

            var m = mask.match(/[^\/\- ]+/g);
            val = val.match(/[^\/\- ]+/g);
            var dic = {}, d, len;
            for (d=0, len=m.length; d<len; ++d) {
                dic[m[d]] = val[d]
            }
            var newMask = 'yyyy-mm-dd';
            for (var v in dic) {
                newMask = newMask.replace(new RegExp(v, "g"), dic[v])
            }

            var date = this.value = local.data[this.name] = new Date(newMask);

            if ( date instanceof Date ) {
                isValid = true;
            } else {
                if ( !errors['isRequired'] && this.value == '' ) {
                    isValid = true
                } else {
                    errors['isDate'] = replace(this.error || local.errorLabels['isDate'], this);
                }

                this.valid = isValid;
                if ( errors.count() > 0 )
                    this['errors'] = errors;

                return self[this.name]
            }

            this.valid = isValid;

            return date
        }

        /**
         * Formating date using DateFormatHelper
         * Check out documentation in the helper source: `utils/helpers/dateFormat.js`
         * e.g.:
         *      d.start
         *        .isDate('dd/mm/yyyy')
         *        .format('isoDateTime');
         *
         *
         * */
        self[el]['format'] = function(mask, utc) {
            var val = this.value;
            if (!val) return self[this.name];

            return val.format(mask, utc)
        };

        /**
         * Set flash
         *
         * @param {str} flash
         * */
        self[el]['setFlash'] = function(regex, flash) {
            if ( typeof(flash) != 'undefined' && flash != '') {
                this.error = flash
            }
            return self[this.name]
        }

        /**
         * Set label
         *
         * @param {str} label
         * */
        self[el]['setLabel'] = function(label) {
            if ( typeof(label) != 'undefined' && label != '') {
                this.label = label
            }
            return self[this.name]
        }

        /**
         * Exclude when converting back to datas
         *
         * @return {object} data
         * */
        self[el]['exclude'] = function(isApplicable) {

            if ( typeof(isApplicable) == 'boolean' && !isApplicable ) {

                local.data[this.name] = this.value;

                return self[this.name]
            }

            // list field to be purged
            local.excluded.push(this.name);
        }

    } // EO for (var el in self)

    /**
     * Check if errors found during validation
     *
     * @return {boolean}
     * */
    self['isValid'] = function() {

        var i = self['getErrors']().count();
        var valid = true;

        if (i > 0) {
            valid = false;
        }

        return valid
    }

    self['getErrors'] = function() {
        var errors = {};

        for (var field in self) {
            if ( typeof(self[field]) != 'function' && typeof(self[field]['errors']) != 'undefined' ) {
                if ( self[field]['errors'].count() > 0)
                    errors[field] = self[field]['errors'];
            }
        }

        return errors
    }

    self['toData'] = function() {

        // cleaning data
        if (local.excluded.length > 0) {
            for (var i = 0, len = local.excluded.length; i < len; ++i) {
                if ( typeof(local.data[ local.excluded[i] ]) != 'undefined' ) {
                    delete local.data[ local.excluded[i] ]
                }
            }
        }

        return local.data
    }

    var replace = function(target, fieldObj) {
        var keys = target.match(/%[a-z]+/gi);
        if (keys) {
            for (var k = 0, len = keys.length; k < len; ++k) {
                target = target.replace(new RegExp(keys[k], 'g'), fieldObj[local.keys[keys[k]]])
            }
        }

        return target
    }

    self['setErrorLabels'] = function (errorLabels) {
        if ( typeof(errorLabels) != 'undefined') {
            local.errorLabels = merge(errorLabels, local.errorLabels)
        }
    }

    return self
}
1196152
function isValidAtomicRule(rule) {
  const decls = rule.nodes.filter(node => node.type === "decl");
  if (decls.length < 0) {
    return { isValid: true };
  }

  for (let i = 1; i < decls.length; i++) {
    const current = decls[i].prop;
    const prev = decls[i - 1].prop;
    if (current < prev) {
      const loc = {
        start: {
          line: decls[i - 1].source.start.line,
          column: decls[i - 1].source.start.column - 1
        },
        end: {
          line: decls[i].source.end.line,
          column: decls[i].source.end.column - 1
        }
      };

      return { isValid: false, loc };
    }
  }

  return { isValid: true };
}
1254281
function Validation(schema, custom) {
		Inspection.prototype.constructor.call(this, schema, _merge(Validation.custom, custom));
		var _error = [];

		this._basicFields = Object.keys(_validationAttribut);
		this._customFields = Object.keys(this._custom);
		this.origin = null;

		this.report = function (message, code, reason) {
			var newErr = {
				code: code || this.userCode || null,
				reason: reason || 'unknown',
				message: this.userError || message || 'is invalid',
				property: this.userAlias ? (this.userAlias + ' (' + this._dumpStack() + ')') : this._dumpStack()
			};
			_error.push(newErr);
			return this;
		};

		this.result = function () {
			return {
				error: _error,
				valid: _error.length === 0,
				format: function () {
					if (this.valid === true) {
						return 'Candidate is valid';
					}
					return this.error.map(function (i) {
						return 'Property ' + i.property + ': ' + i.message;
					}).join('\n');
				}
			};
		};
	}