четверг, 28 июля 2016 г.

Мой вариант JSON Schema

;(function (global, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = factory();
    } else {
        global.JSONSchema = factory();
    }
})(this, function () {

    // Валидация данных в формате JSON согласно схеме проверки.

    // Допустимые для типы значений:
    // - types - массив с возможными вариантами типов значений
    // - type - тип значения: null, boolean, number, string, array, object
    // - min - минимальная длина значения или число элементов, входящих в него
    // - max - максимальная длина значения или число элементов, входящих в него
    // - regexp: {pattern: 'abcde', flags: 'gi'} - регулярное выражение для проверки содержимого строки
    // - items - объект, содержащий в себе описание схемы для проверки элементов внутри массива, имеющих числовые индексы
    // - allItems - массив, содержащий в себе описание схемы для проверки всех элементов внутри массива, имеющих одинаковый тип
    // - properties - объект, содержащий в себе описание схемы для проверки элементов внутри объекта
    // - optional: true - булево значение, всегда равное true и указывающее на то, что не является ошибкой, если данный элемент будет отсутствовать
    //
    // undefined:
    // - type
    // - optional: true
    //
    // null:
    // - type
    // - optional: true
    //
    // boolean:
    // - type
    // - optional: true
    //
    // number:
    // - type
    // - min
    // - max
    // - optional: true
    //
    // string:
    // - type
    // - min
    // - max
    // - regexp
    // - optional: true
    //
    // array:
    // - type
    // - min
    // - max
    // - items
    // - allItems
    // - optional: true
    //
    // object:
    // - type
    // - min
    // - max
    // - properties
    // - optional: true

    // Пример использования:
    //
    // var data = {
    //       undef: undefined
    //     , nul: null
    //     , bool: false
    //     , num: 5
    //     , str: 'ab1c2de'
    //     , arr: [1, 2, [3, 4]]
    //     , obj: {one: '', two: {three: 3}}
    //     , anyTypeFrom: 5
    //     , all: [1, 2, 3, 4]
    // };
    //
    // var schema = {
    //       undef: {type: 'undefined'}
    //     , nul: {type: 'null'}
    //     , bool: {type: 'boolean'}
    //     , num: {type: 'number', min: 0, max: 10}
    //     , str: {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
    //     , arr: {type: 'array', min: 0, max: 10, items: {
    //             '0': {type: 'number'}
    //           , '1': {type: 'number'}
    //           , '2': {type: 'array', items: {
    //                     '0': {type: 'number'}
    //                   , '1': {type: 'string'}
    //               }
    //             }
    //         }
    //       }
    //     , obj: {type: 'object', min: 0, max: 10, properties: {
    //              one: {type: 'number'}
    //            , two: {type: 'object', properties: {
    //                   three: {type: 'string'}
    //                 , four: {type: 'string', optional: true}
    //              }
    //            }
    //        }
    //       }
    //     , anyTypeFrom: {
    //         types: [
    //              {type: 'number', min: 0, max: 10}
    //            , {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
    //         ]
    //       }
    //     , all: {type: 'array', allItems: {type: 'number'}}
    //     , notExist1: {type: 'array', optional: true}
    //     , notExist2: {
    //           types: [
    //                  {type: 'number', min: 0, max: 10}
    //                , {type: 'string', min: 0, max: 10, regexp: {pattern: 'abcde', flags: 'gi'}}
    //           ]
    //         , optional: true
    //       }
    // };
    //
    // var result = JSONSchema.validate(data, schema);
    //
    // if (!result.valid) {
    //     for (var i = 0, len = result.errors.length; i < len; i++) {
    //         console.log('Ошибка: ' + result.errors[i].message);
    //         console.log('Путь до элемента: ' + result.errors[i].path);
    //     }
    // }

    function has (object, key) {
        return Object.prototype.hasOwnProperty.call(object, key);
    }

    function isTypeOf (value, type) {
        return Object.prototype.toString.call(value).toLowerCase().slice(8, -1) === type;
    }

    function objectLength (object) {
        var length = 0
            , key;
        for (key in object) {if (has(object, key)) {
            length++;
        }}
        return length;
    }

    function validate (json, schema) {

        var result = {
              valid: true
            , errors: []
        };

        function addError (path, message) {
            result.valid = false;
            result.errors.push({path: path, message: message});
        }

        if (!isTypeOf(json, 'object')) {
            addError('корневой элемент', 'Корневой элемент должен быть объектом.');
        } else {
            for (var key in schema) {if (has(schema, key)) {
                if (
                          !has(json, key)
                    && (
                            (has(schema[key], 'type') && schema[key].type !== 'undefined' && !has(schema[key], 'optional'))
                        || (has(schema[key], 'types') && !has(schema[key], 'optional'))
                    )
                ) {
                    addError('корневой объект', 'Элемент "' + key + '" должен присутствовать в корневом объекте.');
                }
                           if (has(schema[key], 'type')) {validateSingleRootType(json, schema, key);
                } else if (has(schema[key], 'types')) {validateManyRootTypes(json, schema, key);
                }
            }}
        }

        function validateSingleRootType (json, schema, key) {
            if (!has(schema[key], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + key + '" корневого объекта.');}
                       if (schema[key].type === 'undefined') {validateUndefined(key, json[key], schema[key], '');
            } else if (schema[key].type === 'null') {validateNull(key, json[key], schema[key], '');
            } else if (schema[key].type === 'boolean') {validateBoolean(key, json[key], schema[key], '');
            } else if (schema[key].type === 'number') {validateNumber(key, json[key], schema[key], '');
            } else if (schema[key].type === 'string') {validateString(key, json[key], schema[key], '');
            } else if (schema[key].type === 'array') {validateArray(key, json[key], schema[key], '');
            } else if (schema[key].type === 'object') {validateObject(key, json[key], schema[key], '');
            }
        }

        function validateManyRootTypes (json, schema, key) {
            var elementType
                , requiredElements = [];
            for (var i = 0, len = schema[key].types.length; i < len; i++) {
                if (!has(schema[key].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + key + '" корневого объекта.');}
                           if (schema[key].types[i].type === 'undefined' && isTypeOf(json[key], 'undefined')) {elementType = 'undefined'; break;
                } else if (schema[key].types[i].type === 'null' && isTypeOf(json[key], 'null')) {elementType = 'null'; break;
                } else if (schema[key].types[i].type === 'boolean' && isTypeOf(json[key], 'boolean')) {elementType = 'boolean'; break;
                } else if (schema[key].types[i].type === 'number' && isTypeOf(json[key], 'number')) {elementType = 'number'; validateNumber(key, json[key], schema[key].types[i], ''); break;
                } else if (schema[key].types[i].type === 'string' && isTypeOf(json[key], 'string')) {elementType = 'string'; validateString(key, json[key], schema[key].types[i], ''); break;
                } else if (schema[key].types[i].type === 'array' && isTypeOf(json[key], 'array')) {elementType = 'array'; validateArray(key, json[key], schema[key].types[i], ''); break;
                } else if (schema[key].types[i].type === 'object' && isTypeOf(json[key], 'object')) {elementType = 'object'; validateObject(key, json[key], schema[key].types[i], ''); break;
                } else {requiredElements.push('"' + schema[key].types[i].type + '"');
                }
            }
            if (elementType === undefined && !has(schema[key], 'optional')) {addError('корневой объект', 'Элемент "' + key + '" корневого объекта должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
        }

        function validateUndefined (/*key, value, schemaForUndefined, pathToElement*/) {
            // Сообщение об ошибке выводить не нужно. Если элемента нет, то это допустимо.
            // if (!isTypeOf(value, 'undefined') && !has(schemaForNull, 'optional')) {addError(pathToElement + key, 'Элемент "' + key + '" не должен присутствовать.');}
        }

        function validateNull (key, value, schemaForNull, pathToElement) {
            if (isTypeOf(value, 'undefined') && has(schemaForNull, 'optional')) {return;}
            if (!isTypeOf(value, 'null')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь значение null.');}
        }

        function validateBoolean (key, bool, schemaForBoolean, pathToElement) {
            if (isTypeOf(bool, 'undefined') && has(schemaForBoolean, 'optional')) {return;}
            if (!isTypeOf(bool, 'boolean')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь значение true или false.');}
        }

        function validateNumber (key, number, schemaForNumber, pathToElement) {
            if (isTypeOf(number, 'undefined') && has(schemaForNumber, 'optional')) {return;}
            if (!isTypeOf(number, 'number')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения число.');
            } else {
                if (number !== number) {addError(pathToElement + key, 'Элемент "' + key + '" не должен иметь в качестве значения NaN.');}
                if (has(schemaForNumber, 'min') && number < schemaForNumber.min) {addError(pathToElement + key, 'Значение элемента "' + key + '" должно быть больше или равно ' + schemaForNumber.min + '.');}
                if (has(schemaForNumber, 'max') && number > schemaForNumber.max) {addError(pathToElement + key, 'Значение элемента "' + key + '" должно быть меньше или равно ' + schemaForNumber.max + '.');}
            }
        }

        function validateString (key, string, schemaForString, pathToElement) {
            if (isTypeOf(string, 'undefined') && has(schemaForString, 'optional')) {return;}
            if (!isTypeOf(string, 'string')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения строку.');
            } else {
                if (has(schemaForString, 'min') && schemaForString.min === 0 && string.length === 0) {return;}
                if (has(schemaForString, 'min') && string.length < schemaForString.min) {addError(pathToElement + key, 'Число символов в строке "' + key + '" должно быть больше или равно ' + schemaForString.min + '.');}
                if (has(schemaForString, 'max') && string.length > schemaForString.max) {addError(pathToElement + key, 'Число символов в строке "' + key + '" должно быть меньше или равно ' + schemaForString.max + '.');}
                if (has(schemaForString, 'regexp') && !(new RegExp(schemaForString.regexp.pattern, schemaForString.regexp.flags).test(string))) {addError(pathToElement + key, 'Значение элемента "' + key + '" не соответствует регулярному выражению: new RegExp("' + schemaForString.regexp.pattern + '", "' + schemaForString.regexp.flags + '")');}
            }
        }

        function validateArray (key, array, schemaForArray, pathToElement) {
            var index
                , arrayLength;
            if (isTypeOf(array, 'undefined') && has(schemaForArray, 'optional')) {return;}
            if (!isTypeOf(array, 'array')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения массив.');
            } else {
                if (has(schemaForArray, 'min') && schemaForArray.min === 0 && array.length === 0) {return;}
                if (has(schemaForArray, 'min') && array.length < schemaForArray.min) {addError(pathToElement + key, 'Число элементов в массиве "' + key + '" должно быть больше или равно ' + schemaForArray.min + '.');}
                if (has(schemaForArray, 'max') && array.length > schemaForArray.max) {addError(pathToElement + key, 'Число элементов в массиве "' + key + '" должно быть меньше или равно ' + schemaForArray.max + '.');}
                if (has(schemaForArray, 'items')) {
                    for (index in schemaForArray.items) {if (has(schemaForArray.items, index)) {
                        if (array[index] === undefined) {addError(pathToElement + key, 'Элемент с индексом "' + index + '" должен присутствовать в массиве "' + key + '".');}
                                   if (has(schemaForArray.items[index], 'type')) {validateSingleArrayType(array, schemaForArray, index, key, pathToElement);
                        } else if (has(schemaForArray.items[index], 'types')) {validateManyArrayTypes(array, schemaForArray, index, key, pathToElement);
                        }
                    }}
                }
                if (has(schemaForArray, 'allItems')) {
                    for (index = 0, arrayLength = array.length; index < arrayLength; index++) {
                        if (!has(schemaForArray.allItems, 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для всех элементов массива "' + key + '".');}
                                   if (schemaForArray.allItems.type === 'undefined') {validateUndefined(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'null') {validateNull(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'boolean') {validateBoolean(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'number') {validateNumber(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'string') {validateString(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'array') {validateArray(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        } else if (schemaForArray.allItems.type === 'object') {validateObject(index, array[index], schemaForArray.allItems, pathToElement + key + '.');
                        }
                    }
                }
            }
        }

        function validateSingleArrayType (array, schemaForArray, index, key, pathToElement) {
            if (!has(schemaForArray.items[index], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента с индексом "' + index + '" массива "' + key + '".');}
                       if (schemaForArray.items[index].type === 'undefined') {validateUndefined(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'null') {validateNull(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'boolean') {validateBoolean(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'number') {validateNumber(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'string') {validateString(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'array') {validateArray(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            } else if (schemaForArray.items[index].type === 'object') {validateObject(index, array[index], schemaForArray.items[index], pathToElement + key + '.');
            }
        }

        function validateManyArrayTypes (array, schemaForArray, index, key, pathToElement) {
            var elementType
                , requiredElements = [];
            for (var i = 0, len = schemaForArray.items[index].types.length; i < len; i++) {
                if (!has(schemaForArray.items[index].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента с индексом "' + index + '" массива "' + key + '".');}
                           if (schemaForArray.items[index].types[i].type === 'undefined' && isTypeOf(array[index], 'undefined')) {elementType = 'undefined'; break;
                } else if (schemaForArray.items[index].types[i].type === 'null' && isTypeOf(array[index], 'null')) {elementType = 'null'; break;
                } else if (schemaForArray.items[index].types[i].type === 'boolean' && isTypeOf(array[index], 'boolean')) {elementType = 'boolean'; break;
                } else if (schemaForArray.items[index].types[i].type === 'number' && isTypeOf(array[index], 'number')) {elementType = 'number'; validateNumber(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
                } else if (schemaForArray.items[index].types[i].type === 'string' && isTypeOf(array[index], 'string')) {elementType = 'string'; validateString(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
                } else if (schemaForArray.items[index].types[i].type === 'array' && isTypeOf(array[index], 'array')) {elementType = 'array'; validateArray(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
                } else if (schemaForArray.items[index].types[i].type === 'object' && isTypeOf(array[index], 'object')) {elementType = 'object'; validateObject(index, array[index], schemaForArray.items[index].types[i], pathToElement + key + '.'); break;
                } else {requiredElements.push('"' + schemaForArray.items[index].types[i].type + '"');
                }
            }
            if (elementType === undefined && !has(schemaForArray.items[index], 'optional')) {addError(pathToElement + key + '.' + index, 'Элемент с индексом "' + index + '" массива "' + key + '" должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
        }

        function validateObject (key, object, schemaForObject, pathToElement) {
            if (isTypeOf(object, 'undefined') && has(schemaForObject, 'optional')) {return;}
            if (!isTypeOf(object, 'object')) {addError(pathToElement + key, 'Элемент "' + key + '" должен иметь в качестве значения объект.');
            } else {
                if (has(schemaForObject, 'min') && schemaForObject.min === 0 && objectLength(object) === 0) {return;}
                if (has(schemaForObject, 'min') && objectLength(object) < schemaForObject.min) {addError(pathToElement + key, 'Число свойств в объекте "' + key + '" должно быть больше или равно ' + schemaForObject.min + '.');}
                if (has(schemaForObject, 'max') && objectLength(object) > schemaForObject.max) {addError(pathToElement + key, 'Число свойств в объекте "' + key + '" должно быть меньше или равно ' + schemaForObject.max + '.');}
                if (has(schemaForObject, 'properties')) {
                    for (var property in schemaForObject.properties) {if (has(schemaForObject.properties, property)) {
                        if (
                                  !has(object, property)
                            && (
                                    (has(schemaForObject.properties[property], 'type') && schemaForObject.properties[property].type !== 'undefined'  && !has(schemaForObject.properties[property], 'optional'))
                                || (has(schemaForObject.properties[property], 'types') && !has(schemaForObject.properties[property], 'optional'))
                            )
                        ) {
                            addError(pathToElement + key, 'Элемент "' + property + '" должен присутствовать в объекте "' + key + '".');
                        }
                                   if (has(schemaForObject.properties[property], 'type')) {validateSingleObjectType(object, schemaForObject, property, key, pathToElement);
                        } else if (has(schemaForObject.properties[property], 'types')) {validateManyObjectTypes(object, schemaForObject, property, key, pathToElement);
                        }
                    }}
                }
            }
        }

        function validateSingleObjectType (object, schemaForObject, property, key, pathToElement) {
            if (!has(schemaForObject.properties[property], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + property + '" объекта "' + key + '".');}
                       if (schemaForObject.properties[property].type === 'undefined') {validateUndefined(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'null') {validateNull(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'boolean') {validateBoolean(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'number') {validateNumber(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'string') {validateString(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'array') {validateArray(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            } else if (schemaForObject.properties[property].type === 'object') {validateObject(property, object[property], schemaForObject.properties[property], pathToElement + key + '.');
            }
        }

        function validateManyObjectTypes (object, schemaForObject, property, key, pathToElement) {
            var elementType
                , requiredElements = [];
            for (var i = 0, len = schemaForObject.properties[property].types.length; i < len; i++) {
                if (!has(schemaForObject.properties[property].types[i], 'type')) {throw new Error('Описание "type" всегда должно присутствовать в схеме для элемента "' + property + '" объекта "' + key + '".');}
                           if (schemaForObject.properties[property].types[i].type === 'undefined' && isTypeOf(object[property], 'undefined')) {elementType = 'undefined'; break;
                } else if (schemaForObject.properties[property].types[i].type === 'null' && isTypeOf(object[property], 'null')) {elementType = 'null'; break;
                } else if (schemaForObject.properties[property].types[i].type === 'boolean' && isTypeOf(object[property], 'boolean')) {elementType = 'boolean'; break;
                } else if (schemaForObject.properties[property].types[i].type === 'number' && isTypeOf(object[property], 'number')) {elementType = 'number'; validateNumber(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
                } else if (schemaForObject.properties[property].types[i].type === 'string' && isTypeOf(object[property], 'string')) {elementType = 'string'; validateString(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
                } else if (schemaForObject.properties[property].types[i].type === 'array' && isTypeOf(object[property], 'array')) {elementType = 'array'; validateArray(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
                } else if (schemaForObject.properties[property].types[i].type === 'object' && isTypeOf(object[property], 'object')) {elementType = 'object'; validateObject(property, object[property], schemaForObject.properties[property].types[i], pathToElement + key + '.'); break;
                } else {requiredElements.push('"' + schemaForObject.properties[property].types[i].type + '"');
                }
            }
            if (elementType === undefined && !has(schemaForObject.properties[property], 'optional')) {addError(pathToElement + key + '.' + property, 'Элемент "' + property + '" объекта "' + key + '" должен иметь значение с типом: ' + requiredElements.join(', ') + '.');}
        }

        if (result.errors.length > 0) {
            (function(){
                var pathElements
                    , pathElementsLength
                    , resultPath = ''
                    , separator
                    , errorsLength = result.errors.length
                    , i;
                while (errorsLength--) {
                    pathElements = result.errors[errorsLength].path.split('.');
                    pathElementsLength = pathElements.length;
                    for (i = 0; i < pathElementsLength; i++) {
                        if (i === 0) {
                            separator = '';
                        } else {
                            separator = '.';
                        }
                                   if ((/^\d+$/g).test(pathElements[i])) {resultPath += '[' + pathElements[i] + ']';
                        } else if ((/^\d+/g).test(pathElements[i])) {resultPath += '["' + pathElements[i] + '"]';
                        } else {resultPath += separator + pathElements[i];
                        }
                    }
                    result.errors[errorsLength].path = resultPath;
                    resultPath = '';
                }
            })();
        }

        return result;

    }

    return {validate: validate};

});

среда, 27 июля 2016 г.

JavaScript CSS

// JS CSS

// Variables

var color = "#4d926f";
module.exports = {
  "#header": {
    color: color
  }
, "h2": {
    color: color
  }
};

// Compiled CSS

#header {
  color: #4d926f;
}
h2 {
  color: #4d926f;
}

// Mixins

function roundedCorners (radius) {
  radius || (radius = "5px");
  return {
        "-webkit-border-radius": radius
      , "-moz-border-radius": radius
      , "border-radius": radius
  };
}

module.exports = {
  "#header": roundedCorners()
, "#footer": roundedCorners("10px")
};

// Compiled CSS

#header {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}
#footer {
  -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
  border-radius: 10px;
}

// Nested Rules

module.exports = {
  "#header": {
    "h1": {
      "font-size": "26px"
    , "font-weight": "bold"
    }
  , "p": {
      "font-size": "12px"
    , "a": {
        "text-decoration": "none"
      }
    , "> .poop": {
        "color": "brown"
      }
    }
  }
};

// Compiled CSS

#header h1 {
  font-size: 26px;
  font-weight: bold;
}
#header p {
  font-size: 12px;
}
#header p a {
  text-decoration: none;
}
#header p a:hover {
  border-width: 1px;
}
#header p > .poop {
  color: brown;
}

// Functions and Operators

// Currently all I have is hex math

module.exports = {
  ".jibber-jabber": {
    color: globals.baseColor
    // Pretty stupid but whatever
  , "background-color": hex.multiply(globals.baseColor, globals.accent, globals.backgroundColor)
  }
}

// Animations, Media Queries, and Other More-Nested Features

module.exports = {
  ".columns": {
    "display": "table"
  , "width": "100%"
  }
, ".columns > .column": {
    "width": "50%"
  , "box-sizing": "border-box"
  , "margin-left": "20px"
  }
, "@-animation-keyframes spin": {
    "0%": {
      "transform": "rotate(0deg)"
    }
  , "100%": {
      "transform": "rotate(360deg)"
    }
  }
};

// Compiled CSS

/* ./layout.js*/

.columns {
  display: table;
  width: 100%;
}
.columns > .column {
  width: 50%;
  box-sizing: border-box;
  margin-left: 20px;
}
@-animation-keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

// Make a jscss Build File

// It's just a javascript file you execute with node.js.

var jscss = require('jscss')
  // Require your modules
, globals = require('./css/globals.js')
, layout = require('./css/layout.js')
, themes = require('./css/themes.js')
, main = require('./css/main.js')
;

// Compile your css
jscss(
  // First parameter defines your output file
  './css/main.css'
  // Subsequent parameters define modules to be compiled
, globals
, layout
, themes
, main
);

// Or just pass in the paths
jscss(
  './css/main.css'
, './css/globals.js'
, './css/layout.js'
, './css/themes.js'
, './css/main.js'
);

// Since this is all javascript, use a package manager.
// Organize your code into self-contained javascript modules. Easily include everything you'll need

// In the Browser

<!DOCTYPE HTML>
<html>
  <head>
    <script type="text/javascript" src="../lib/index.js"></script>
    <script type="text/javascript">
      // Mixin
      function roundedCorners (radius{
        radius || (radius = "5px");
        return {
          "-webkit-border-radius": radius
        , "-moz-border-radius": radius
        , "border-radius": radius
        };
      }
      // Just a quick and dirty merge object
      function mix (obj1, obj2) {
        for (var key in obj2){
          obj1[key] = obj2[key];
        }
        return obj1;
      }
      // Css declarations
      var cssObj = {
        "body": {
          "background-color": "pink"
        }
      , ".testing": mix(
            {
                  "background-color": "#fff"
                , "padding": "22px"
                , "> .test": {
                                    "color": "blue"
                                }
             }
            , roundedCorners("10px")
         )
      };
      // Embed the compiled jscss object
      jscss.embed(jscss.compile(cssObj));
    </script>
  </head>
  <body>
    <div class="testing">
      <span class="test">Just testing</span>
    </div>
  </body>
</html>

// JS CSS Source

// File index.js

;(function(){
  var
    id = 0
  , indent = function(level){
      var out = "";
      for (var i = 0; i < level; i++){
        out += "  ";
      }
      return out
    }
  , _jscss = {
      compile: function(obj){
        var
          selectors = {}
          // Build nested rules into single rules
        , _buildSelectors = function(hash, selector){
            var curr, type, selector, key;
            for (key in hash){
              type = typeof (curr = hash[key]);
              if (type == "string"){
                if (!selectors[selector]) selectors[selector] = {};
                selectors[selector][key] = curr;
              }else if (type == "object"){
                // Media query or animation or something
                if (key[0] == "@"){
                  selectors[key] = curr;
                }else{
                  _buildSelectors(curr, (selector ? (selector + " ") : "") + key);
                }
              }
            }
          }
          // Take a flat css object and turn it into a string
        , _compile = function(hash, level){
            var spaces = "  ", out = "", level = level || 0, curr;
            for (var key in hash){
              curr = hash[key];
              out += indent(level) + key;
              if (typeof curr == "object"){
                out += " {\n" + _compile(curr, level + 1) + indent(level) + "}\n";
              }else{
                out += ": " + curr + ";\n";
              }
            }
            return out;
          }
        ;
        _buildSelectors(obj, "");
        return _compile(selectors, 0);
      }
      // Embeds a
    , embed: function(styles, styleId){
        if (typeof styles === "object") styles = _jscss(styles);
        styleId || (styleId = "jscss-" + (id++));
        var el = document.createElement('style');
        el.type = "text/css";
        el.rel = "stylesheet";
        el.id = styleId;
        el.innerHTML = "\n" + styles;
        document.head.appendChild(el);
      }
    }
  ;
  if (typeof define !== "undefined") {
    define('jscss', function(){ return _jscss; });
  }else if (typeof module !== "undefined" && module.exports){
    module.exports = _jscss;
  }else{
    window.jscss = _jscss;
  }
})();

// File jscss-old.js - заменен на index.js

var fs = require('fs');

var indent = function(level){
  var out = "";
  for (var i = 0; i < level; i++){
    out += "  ";
  }
  return out
};

// This is O(2s + 2d)
// Where s is the number of selectors
// And d is the number of properties
// I wonder if I can make it O(s + d)
var compile = function(obj){
  var selectors = {},
  // Make a predictable object we can work with
   _buildSelectors = function(hash, selector){
    var curr, type, selector, key;
    for (key in hash){
      type = typeof (curr = hash[key]);
      if (type == "string"){
        if (!selectors[selector]) selectors[selector] = {};
        selectors[selector][key] = curr;
      }else if (type == "object"){
        // Media query or animation or something
        if (key[0] == "@"){
          selectors[key] = curr;
        }else{
          _buildSelectors(curr, (selector ? (selector + " ") : "") + key);
        }
      }
    }
  },
  // Take a flat css object and turn it into a string
  _compile = function(hash, level){
    var spaces = "  ", out = "", level = level || 0, curr;
    for (var key in hash){
      curr = hash[key];
      out += indent(level) + key;
      if (typeof curr == "object"){
        out += " {\n" + _compile(curr, level + 1) + indent(level) + "}\n";
      }else{
        out += ": " + curr + ";\n";
      }
    }
    return out;
  };
  _buildSelectors(obj, "");
  // console.log(selectors);
  return _compile(selectors, 0);
};

module.exports = function(output){
  var stream = fs.createWriteStream(output);
  var files = Array.prototype.slice.call(arguments, 1);
  stream.once('open', function(){
    for (var i = 0, file; i < files.length; i++){
      file = require(files[i]);
      stream.write('/* ' + files[i] + ' */\n\n');
      stream.write(compile(file));
    }
  });
};

// File hex-math.js

var maxColor = 16777215
, colorToInt = function(hex){
    hex = hex.replace('#', '');
    // Change shorthand so we can do proper math
    if (hex.length == 3){
      var tmp = hex;
      hex = "";
      for (var i = 0; i < tmp.length; i++){
        hex += "" + tmp[i] + tmp[i];
      }
    }
    return ("0x" + hex - 0);
  }
, intToColor = function(int){
    int = int.toString(16);
    while (int.length < 6){
      int = "" + "0" + int;
    }
    return '#' + int;
  }
, colorIntClamp = function(int){
    if (int > maxColor) return maxColor;
    if (int < 0) return 0;
    return int;
  }
;

module.exports = {
  add: function(){
    var args = Array.prototype.slice.call(arguments, 0), total = colorToInt(args[0]);
    for (var i = 1; i < args.length; i++){
      total += colorToInt(args[i]);
    }
    return colorIntClamp(intToColor(total));
  }
, subtract: function(){
    var args = Array.prototype.slice.call(arguments, 0), total = colorToInt(args[0]);
    for (var i = 1; i < args.length; i++){
      total += colorToInt(args[i]);
    }
    return colorIntClamp(intToColor(total));
  }
, multiply: function(){
    var args = Array.prototype.slice.call(arguments, 0), total = colorToInt(args[0]);
    for (var i = 1; i < args.length; i++){
      total *= colorToInt(args[i]);
    }
    return colorIntClamp(intToColor(total));
  }
, divide: function(){
    var args = Array.prototype.slice.call(arguments, 0), total = colorToInt(args[0]);
    for (var i = 1; i < args.length; i++){
      total /= colorToInt(args[i]);
    }
    return colorIntClamp(intToColor(total));
  }
};

// Build example

// File build-jscss.js

var jscss = require('./jscss.js');

jscss(
  './style.css'
, './layout.js'
);

// File layout.js

module.exports = {
  '.columns': {
    'display': 'table'
  , 'width': '100%'
  }
, '.columns > .column': {
    'width': '50%'
  , 'box-sizing': 'border-box'
  , 'margin-left': "20px"
  }

, '#header': {
    "h1": {
      "font-size": "26px"
    , "font-weight": "bold"
    }
  , "p": {
      "font-size": "12px"
    , "a": {
        "text-decoration": "none"
      // , "&:hover": { "border-width": "1px" }
      }
    , '> .poop': {
        'color': 'brown'
      }
    }
  , ".test": {
      "font-size": "1.2rem"
    }
  }

, '@-animation-keyframes spin': {
    '0%': {
      'transform': 'rotate(0deg)'
    }
  , '100%': {
      'transform': 'rotate(360deg)'
    }
  }
};

// File style.css

/* ./layout.js*/

.columns {
  display: table;
  width: 100%;
}
.columns > .column {
  width: 50%;
  box-sizing: border-box;
  margin-left: 20px;
}
#header h1 {
  font-size: 26px;
  font-weight: bold;
}
#header p {
  font-size: 12px;
}
#header p a {
  text-decoration: none;
}
#header p > .poop {
  color: brown;
}
#header .test {
  font-size: 1.2rem;
}
@-animation-keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

пятница, 8 июля 2016 г.

JavaScript Get Browser version

/**
 * @class window.env.Browser
 * Provides information about browser.
 *
 * Should not be manually instantiated unless for unit-testing.
 * Access the global instance stored in {@link window.env.browser} instead.
 * @private
 */
;(window.env || (window.env = {})).Browser = function (userAgent, publish) {
    // @define window.env.Browser
    // @define window.env.browser
   
   
    function isEmpty (value, allowEmptyString) {
        return (value === undefined || value === null) || (!allowEmptyString ? value === '' : false) || (isArray(value) && value.length === 0);
    }

    function isArray (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    }

    function apply (object, config, defaults) {
        var enumerables = ['valueOf', 'toLocaleString', 'toString', 'constructor'];
       
        if (defaults) {
            apply(object, defaults);
        }

        if (object && config && typeof config === 'object') {
            var i, j, k;

            for (i in config) {
                if (config.hasOwnProperty(i)) {
                    object[i] = config[i];
                }
            }

            if (enumerables) {
                for (j = enumerables.length; j--;) {
                    k = enumerables[j];
                    if (config.hasOwnProperty(k)) {
                        object[k] = config[k];
                    }
                }
            }
        }

        return object;
    }

    function getValues (object) {
        var values = [],
            property;

        for (property in object) {
            if (object.hasOwnProperty(property)) {
                values.push(object[property]);
            }
        }

        return values;
    }
   
    function getKey (object, value) {
        for (var property in object) {
            if (object.hasOwnProperty(property) && object[property] === value) {
                return property;
            }
        }

        return null;
    }
   

     function Version (version) {
            var me = this,
                padModes = me.padModes,
                ch, i, pad, parts, release, releaseStartIndex, ver;

            me.version = ver = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');

            ch = ver.charAt(0);
            if (ch in padModes) {
                ver = ver.substring(1);
                pad = padModes[ch];
            } else {
                pad = 0;
            }
            me.pad = pad;

            releaseStartIndex = ver.search(/([^\d\.])/);
            me.shortVersion = ver;

            if (releaseStartIndex !== -1) {
                me.release = release = ver.substr(releaseStartIndex, version.length);
                me.shortVersion = ver.substr(0, releaseStartIndex);
                release = Version.releaseValueMap[release] || release;
            }

            me.releaseValue = release || pad;
            me.shortVersion = me.shortVersion.replace(/[^\d]/g, '');

            /**
             * @property {Number[]} parts
             * The split array of version number components found in the version string.
             * For example, for "1.2.3", this would be `[1, 2, 3]`.
             * @readonly
             * @private
             */
            me.parts = parts = ver.split('.');
            for (i = parts.length; i--;) {
                parts[i] = parseInt(parts[i], 10);
            }
            if (pad === Infinity) {
                // have to add this to the end to create an upper bound:
                parts.push(pad);
            }

            /**
             * @property {Number} major
             * The first numeric part of the version number string.
             * @readonly
             */
            me.major = parts[0] || pad;

            return me;
    }
   
    Version.releaseValueMap = {
        dev:   -6,
        alpha: -5,
        a:     -5,
        beta:  -4,
        b:     -4,
        rc:    -3,
        '#':   -2,
        p:     -1,
        pl:    -1
    };
   
    Version.prototype = {
        padModes: {
            '~': NaN,
            '^': Infinity
        },
       
        /**
         * Returns the major component value.
         * @return {Number}
         */
        getMajor: function() {
            return this.major;
        },
       
        /**
         * Returns shortVersion version without dots and release
         * @return {String}
         */
        getShortVersion: function() {
            return this.shortVersion;
        }
    };

    var me = this,
        browserPrefixes = {
            ie: 'MSIE ',
            edge: 'Edge/',
            firefox: 'Firefox/',
            chrome: 'Chrome/',
            safari: 'Version/',
            opera: 'OPR/',
            dolfin: 'Dolfin/',
            webosbrowser: 'wOSBrowser/',
            chromeMobile: 'CrMo/',
            chromeiOS: 'CriOS/',
            silk: 'Silk/'
        },
        browserNames = {
            ie: 'IE',
            firefox: 'Firefox',
            safari: 'Safari',
            chrome: 'Chrome',
            opera: 'Opera',
            dolfin: 'Dolfin',
            edge: 'Edge',
            webosbrowser: 'webOSBrowser',
            chromeMobile: 'ChromeMobile',
            chromeiOS: 'ChromeiOS',
            silk: 'Silk',
            other: 'Other'
        },
        enginePrefixes = me.enginePrefixes,
        engineNames = me.engineNames,
        browserMatch = userAgent.match(new RegExp('((?:' + getValues(browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')),
        engineMatch = userAgent.match(new RegExp('((?:' + getValues(enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')),
        browserName = browserNames.other,
        engineName = engineNames.other,
        browserVersion = '',
        engineVersion = '',
        majorVer = '',
        isWebView = false,
        i, prefix, mode, name, maxIEVersion;

    /**
     * @property {String}
     * Browser User Agent string.
     */
    me.userAgent = userAgent;

    /**
     * A "hybrid" property, can be either accessed as a method call, for example:
     *
     *     if (window.env.browser.is('IE')) {
     *         // ...
     *     }
     *
     * Or as an object with Boolean properties, for example:
     *
     *     if (window.env.browser.is.IE) {
     *         // ...
     *     }
     *
     * Versions can be conveniently checked as well. For example:
     *
     *     if (window.env.browser.is.IE10) {
     *         // Equivalent to (window.env.browser.is.IE && window.env.browser.version.equals(10))
     *     }
     *
     * __Note:__ Only {@link Version#getMajor major component}  and {@link Version#getShortVersion simplified}
     * value of the version are available via direct property checking.
     *
     * Supported values are:
     *
     * - IE
     * - Firefox
     * - Safari
     * - Chrome
     * - Opera
     * - WebKit
     * - Gecko
     * - Presto
     * - Trident
     * - WebView
     * - Other
     *
     * @param {String} name The OS name to check.
     * @return {Boolean}
     */
    this.is = function (name) {
        // Since this function reference also acts as a map, we do not want it to be
        // shared between instances, so it is defined here, not on the prototype.
        return !!this.is[name];
    };

    // Edge has a userAgent with All browsers so we manage it separately
    // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
    if (/Edge\//.test(userAgent)) {
        browserMatch = userAgent.match(/(Edge\/)([\w.]+)/);
    }

    if (browserMatch) {
        browserName = browserNames[getKey(browserPrefixes, browserMatch[1])];
        //<feature legacyBrowser>
        if (browserName === 'Safari' && /^Opera/.test(userAgent)) {
            // Prevent Opera 12 and earlier from being incorrectly reported as Safari
            browserName = 'Opera';
        }
        //</feature>
        browserVersion = new Version(browserMatch[2]);
    }

    if (engineMatch) {
        engineName = engineNames[getKey(enginePrefixes, engineMatch[1])];
        engineVersion = new Version(engineMatch[2]);
    }

    if (engineName === 'Trident' && browserName !== 'IE') {
        browserName = 'IE';
        var version = userAgent.match(/.*rv:(\d+.\d+)/);
        if (version && version.length) {
            version = version[1];
            browserVersion = new Version(version);
        }
    }
   
    /**
     * @property chromeVersion
     * The current version of Chrome (0 if the browser is not Chrome).
     * @readonly
     * @type Number
     */

    /**
     * @property firefoxVersion
     * The current version of Firefox (0 if the browser is not Firefox).
     * @readonly
     * @type Number
     */

    /**
     * @property ieVersion
     * The current version of IE (0 if the browser is not IE). This does not account
     * for the documentMode of the current page, which is factored into {@link #isIE8},
     * and {@link #isIE9}. Thus this is not always true:
     *
     *     window.env.isIE8 == (window.env.ieVersion == 8)
     *
     * @readonly
     * @type Number
     */

    /**
     * @property isChrome
     * True if the detected browser is Chrome.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isGecko
     * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE
     * True if the detected browser is Internet Explorer.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE8
     * True if the detected browser is Internet Explorer 8.x.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE8m
     * True if the detected browser is Internet Explorer 8.x or lower.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE8p
     * True if the detected browser is Internet Explorer 8.x or higher.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE9
     * True if the detected browser is Internet Explorer 9.x.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE9m
     * True if the detected browser is Internet Explorer 9.x or lower.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE9p
     * True if the detected browser is Internet Explorer 9.x or higher.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE10
     * True if the detected browser is Internet Explorer 10.x.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE10m
     * True if the detected browser is Internet Explorer 10.x or lower.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE10p
     * True if the detected browser is Internet Explorer 10.x or higher.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE11
     * True if the detected browser is Internet Explorer 11.x.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE11m
     * True if the detected browser is Internet Explorer 11.x or lower.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isIE11p
     * True if the detected browser is Internet Explorer 11.x or higher.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isEdge
     * True if the detected browser is Edge.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isLinux
     * True if the detected platform is Linux.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isMac
     * True if the detected platform is Mac OS.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isOpera
     * True if the detected browser is Opera.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isSafari
     * True if the detected browser is Safari.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isWebKit
     * True if the detected browser uses WebKit.
     * @readonly
     * @type Boolean
     */

    /**
     * @property isWindows
     * True if the detected platform is Windows.
     * @readonly
     * @type Boolean
     */

    /**
     * @property operaVersion
     * The current version of Opera (0 if the browser is not Opera).
     * @readonly
     * @type Number
     */

    /**
     * @property safariVersion
     * The current version of Safari (0 if the browser is not Safari).
     * @readonly
     * @type Number
     */

    /**
     * @property webKitVersion
     * The current version of WebKit (0 if the browser does not use WebKit).
     * @readonly
     * @type Number
     */

    // Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information
    // about the browser, so we have to detect that and fake it...
    if (userAgent.match(/FB/) && browserName === "Other") {
        browserName = browserNames.safari;
        engineName = engineNames.webkit;
    }

    if (userAgent.match(/Android.*Chrome/g)) {
        browserName = 'ChromeMobile';
    }

    if (userAgent.match(/OPR/)) {
        browserName = 'Opera';
        browserMatch = userAgent.match(/OPR\/(\d+.\d+)/);
        browserVersion = new Version(browserMatch[1]);
    }

    apply(this, {
        engineName: engineName,
        engineVersion: engineVersion,
        name: browserName,
        version: browserVersion
    });

    this.setFlag(browserName, true, publish); // e.g., window.env.isIE

    if (browserVersion) {
        majorVer = browserVersion.getMajor() || '';
        //<feature legacyBrowser>
        if (me.is.IE) {
            majorVer = parseInt(majorVer, 10);
            mode = document.documentMode;

            // IE's Developer Tools allows switching of Browser Mode (userAgent) and
            // Document Mode (actual behavior) independently. While this makes no real
            // sense, the bottom line is that document.documentMode holds the key to
            // getting the proper "version" determined. That value is always 5 when in
            // Quirks Mode.

            if (mode === 7 || (majorVer === 7 && mode !== 8 && mode !== 9 && mode !== 10)) {
                majorVer = 7;
            } else if (mode === 8 || (majorVer === 8 && mode !== 8 && mode !== 9 && mode !== 10)) {
                majorVer = 8;
            } else if (mode === 9 || (majorVer === 9 && mode !== 7 && mode !== 8 && mode !== 10)) {
                majorVer = 9;
            } else if (mode === 10 || (majorVer === 10 && mode !== 7 && mode !== 8 && mode !== 9)) {
                majorVer = 10;
            } else if (mode === 11 || (majorVer === 11 && mode !== 7 && mode !== 8 && mode !== 9 && mode !== 10)) {
                majorVer = 11;
            }

            maxIEVersion = Math.max(majorVer, 12);
            for (i = 7; i <= maxIEVersion; ++i) {
                prefix = 'isIE' + i;
                if (majorVer <= i) {
                    window.env[prefix + 'm'] = true;
                }

                if (majorVer === i) {
                    window.env[prefix] = true;
                }

                if (majorVer >= i) {
                    window.env[prefix + 'p'] = true;
                }
            }
        }

        if (me.is.Opera && parseInt(majorVer, 10) <= 12) {
            window.env.isOpera12m = true;
        }
        //</feature>

        window.env.chromeVersion = window.env.isChrome ? majorVer : 0;
        window.env.firefoxVersion = window.env.isFirefox ? majorVer : 0;
        window.env.ieVersion = window.env.isIE ? majorVer : 0;
        window.env.operaVersion = window.env.isOpera ? majorVer : 0;
        window.env.safariVersion = window.env.isSafari ? majorVer : 0;
        window.env.webKitVersion = window.env.isWebKit ? majorVer : 0;

        this.setFlag(browserName + majorVer, true, publish); // window.env.isIE10
        this.setFlag(browserName + browserVersion.getShortVersion());
    }

    for (i in browserNames) {
        if (browserNames.hasOwnProperty(i)) {
            name = browserNames[i];

            this.setFlag(name, browserName === name);
        }
    }

    this.setFlag(name);

    if (engineVersion) {
        this.setFlag(engineName + (engineVersion.getMajor() || ''));
        this.setFlag(engineName + engineVersion.getShortVersion());
    }

    for (i in engineNames) {
        if (engineNames.hasOwnProperty(i)) {
            name = engineNames[i];

            this.setFlag(name, engineName === name, publish);
        }
    }

    this.setFlag('Standalone', !!navigator.standalone);

    this.setFlag('Ripple', !!document.getElementById("tinyhippos-injected") && !isEmpty(window.top.ripple));
    this.setFlag('WebWorks', !!window.blackberry);

    if (window.PhoneGap !== undefined || window.Cordova !== undefined || window.cordova !== undefined) {
        isWebView = true;
        this.setFlag('PhoneGap');
        this.setFlag('Cordova');
    }

    // Check if running in UIWebView
    if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) {
        isWebView = true;
    }

    // Flag to check if it we are in the WebView
    this.setFlag('WebView', isWebView);

    /**
     * @property {Boolean}
     * `true` if browser is using strict mode.
     */
    this.isStrict = window.env.isStrict = document.compatMode === "CSS1Compat";

    /**
     * @property {Boolean}
     * `true` if page is running over SSL.
     */
    this.isSecure = /^https/i.test(window.location.protocol);

    // IE10Quirks, Chrome26Strict, etc.
    this.identity = browserName + majorVer + (this.isStrict ? 'Strict' : 'Quirks');
};

window.env.Browser.prototype = {
    constructor: window.env.Browser,

    engineNames: {
        webkit: 'WebKit',
        gecko: 'Gecko',
        presto: 'Presto',
        trident: 'Trident',
        other: 'Other'
    },

    enginePrefixes: {
        webkit: 'AppleWebKit/',
        gecko: 'Gecko/',
        presto: 'Presto/',
        trident: 'Trident/'
    },

    styleDashPrefixes: {
        WebKit: '-webkit-',
        Gecko: '-moz-',
        Trident: '-ms-',
        Presto: '-o-',
        Other: ''
    },

    stylePrefixes: {
        WebKit: 'Webkit',
        Gecko: 'Moz',
        Trident: 'ms',
        Presto: 'O',
        Other: ''
    },

    propertyPrefixes: {
        WebKit: 'webkit',
        Gecko: 'moz',
        Trident: 'ms',
        Presto: 'o',
        Other: ''
    },

    // scope: window.env.Browser.prototype

    /**
     * The full name of the current browser.
     * Possible values are:
     *
     * - IE
     * - Firefox
     * - Safari
     * - Chrome
     * - Opera
     * - Other
     * @type String
     * @readonly
     */
    name: null,

    /**
     * Refer to {@link Version}.
     * @type Version
     * @readonly
     */
    version: null,

    /**
     * The full name of the current browser's engine.
     * Possible values are:
     *
     * - WebKit
     * - Gecko
     * - Presto
     * - Trident
     * - Other
     * @type String
     * @readonly
     */
    engineName: null,

    /**
     * Refer to {@link Version}.
     * @type Version
     * @readonly
     */
    engineVersion: null,

    setFlag: function(name, value, publish) {
        if (value === undefined) {
            value = true;
        }

        this.is[name] = value;
        this.is[name.toLowerCase()] = value;
        if (publish) {
            window.env['is' + name] = value;
        }

        return this;
    },

    getStyleDashPrefix: function() {
        return this.styleDashPrefixes[this.engineName];
    },

    getStylePrefix: function() {
        return this.stylePrefixes[this.engineName];
    },

    getVendorProperyName: function(name) {
        function capitalize (string) {
            if (string) {
                string = string.charAt(0).toUpperCase() + string.substr(1);
            }
            return string || '';
        }
       
        var prefix = this.propertyPrefixes[this.engineName];

        if (prefix.length > 0) {
            return prefix + capitalize(name);
        }

        return name;
    },

    getPreferredTranslationMethod: function(config) {
        if (typeof config === 'object' && 'translationMethod' in config && config.translationMethod !== 'auto') {
            return config.translationMethod;
        } else {
            return 'csstransform';
        }
    }

};

/**
 * @class window.env.browser
 * @extends window.env.Browser
 * @singleton
 * Provides useful information about the current browser.
 *
 * Example:
 *
 *     if (window.env.browser.is.IE) {
 *         // IE specific code here
 *     }
 *
 *     if (window.env.browser.is.WebKit) {
 *         // WebKit specific code here
 *     }
 *
 *     console.log("Version " + window.env.browser.version);
 *
 * For a full list of supported values, refer to {@link #is} property/method.
 *
 */
;(function (userAgent) {
    window.env.browser = new window.env.Browser(userAgent, true);
    window.env.userAgent = userAgent.toLowerCase();

    /**
     * @property {String} SSL_SECURE_URL
     * URL to a blank file used when in secure mode for iframe src and onReady src
     * to prevent the IE insecure content warning (`'about:blank'`, except for IE
     * in secure mode, which is `'javascript:""'`).
     */
    window.env.SSL_SECURE_URL = /^https/i.test(window.location.protocol) && window.env.isIE ? 'javascript:\'\'' : 'about:blank'; // jshint ignore:line
})(window.navigator.userAgent);

console.log(window.env.browser.name);
console.log(window.env.browser.version.version);

четверг, 7 июля 2016 г.

Parse XML to JSON

var xml = '<?xml version="1.0" encoding="UTF-8"?>'
            + '<catalog>'
            + '<book id="bk101">'
            + '<author>Gambardella, Matthew</author>'
            + '<title>XML Developer\'s Guide</title>'
            + '<genre>Computer</genre>'
            + '<price>44.95</price>'
            + '<publish_date>2000-10-01</publish_date>'
            + '<description>An in-depth look at creating applications with XML.</description>'
            + '</book>'
            + '</catalog>';

function parseXML (data) {
    var xml, tmp;
    if (!data || typeof data !== "string") {
        return null;
    }
    try {
        if (window.DOMParser) { // Standard
            tmp = new DOMParser();
            xml = tmp.parseFromString(data, "text/xml");
        } else { // IE
            xml = new ActiveXObject("Microsoft.XMLDOM");
            xml.async = "false";
            xml.loadXML(data);
        }
    } catch(e) {
        xml = undefined;
    }
    if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length) {
        throw new Error("Invalid XML: " + data);
    }
    return xml;
}

// Changes XML to JSON
function xmlToJson(xml) {
   
    // Create the return object
    var obj = {};

    if (xml.nodeType == 1) { // element
        // do attributes
        if (xml.attributes.length > 0) {
        obj["@attributes"] = {};
            for (var j = 0; j < xml.attributes.length; j++) {
                var attribute = xml.attributes.item(j);
                obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
            }
        }
    } else if (xml.nodeType == 3) { // text
        obj = xml.nodeValue;
    }

    // do children
    if (xml.hasChildNodes()) {
        for(var i = 0; i < xml.childNodes.length; i++) {
            var item = xml.childNodes.item(i);
            var nodeName = item.nodeName;
            if (typeof(obj[nodeName]) == "undefined") {
                obj[nodeName] = xmlToJson(item);
            } else {
                if (typeof(obj[nodeName].push) == "undefined") {
                    var old = obj[nodeName];
                    obj[nodeName] = [];
                    obj[nodeName].push(old);
                }
                obj[nodeName].push(xmlToJson(item));
            }
        }
    }
    return obj;
}

function convertObjectToHtmlText (obj, options) {
    var indentString = '&nbsp;&nbsp;&nbsp;&nbsp;'
       , newLine = '<br />'
       , newLineJoin = ',' + newLine;
    if (options && options.rawHTML) {
        indentString = '    '; // 4 пробела
        newLine = '\n';
        newLineJoin = ',' + newLine;
    }
    // Функция определения типа объекта
    function objectType (obj) {
        var types = {
              'null': 'null'
            , 'undefined': 'undefined'
            , 'number': 'number'
            , 'boolean': 'boolean'
            , 'string': 'string'
            , '[object Function]': 'function'
            , '[object RegExp]': 'regexp'
            , '[object Array]': 'array'
            , '[object Date]': 'date'
            , '[object Error]': 'error'
        };
        return types[typeof obj] || types[Object.prototype.toString.call(obj)] || (obj ? 'object' : 'null');
    }
    // Функция определения числа элементов в объекте
    function objectSize (obj) {
        var size = 0
            , key;
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {size++;}
        }
        return size;
    }
    // Привести элемент к нужному формату
    function formatElement (element, indent, indentFromArray) {
        indentFromArray = indentFromArray || '';
        switch (objectType(element)) {
            case 'null': return indentFromArray + 'null';
            case 'undefined': return indentFromArray + 'undefined';
            case 'number': return indentFromArray + element;
            case 'boolean': return indentFromArray + (element ? 'true' : 'false');
            case 'string': if (options && options.rawHTML) {
                                    return indentFromArray + '"'
                                                                         + element
                                                                         + '"';
                                } else {
                                    return indentFromArray + '&quot;'
                                                                         + element.replace(/&/g, '&amp;')
                                                                                        .replace(/</g, '&lt;')
                                                                                        .replace(/>/g, '&gt;')
                                                                                        .replace(/"/, '&quot;')
                                                                                        .replace(/'/g, '&#039;')
                                                                         + '&quot;';
                                }
            case 'array': return indentFromArray + (element.length > 0 ? '[' + newLine + formatArray(element, indent) + indent + ']' : '[]');
            case 'object': return indentFromArray + (objectSize(element) > 0 ? '{' + newLine + formatObject(element, indent) + indent + '}' : '{}');
            default: return indentFromArray + element.toString();
        }
    }
    // Привести массив к нужному формату
    function formatArray (array, indent) {
        var index
            , length = array.length
            , value = [];
        indent += indentString;
        for (index = 0; index < length; index += 1) {
            value.push(formatElement(array[index], indent, indent));
        }
        return value.join(newLineJoin) + newLine;
    }
    // Привести объект к нужному формату
    function formatObject (object, indent) {
        var value = []
            , property;
        indent += indentString;
        for (property in object) {
            if (object.hasOwnProperty(property)) {
                if (options && options.rawHTML) {
                    value.push(indent + '"' + property + '": ' + formatElement(object[property], indent));
                } else {
                    value.push(indent + '&quot;' + property + '&quot;: ' + formatElement(object[property], indent));
                }
            }
        }
        return value.join(newLineJoin) + newLine;
    }
    if (typeof obj === 'object') {return formatElement(obj, '');
    } else {throw new Error ('No javascript object has been provided to function convertObjectToHtmlText (obj) {...}');
    }
}

console.log(JSON.stringify(xmlToJson(parseXML(xml))));

console.log(convertObjectToHtmlText (xmlToJson(parseXML(xml)), {rawHTML: true}));