Source: Tree.js

definer('Tree', /** @exports Tree */ function(Template, is, object) {

    /**
     * Модуль работы с BEMJSON-деревом.
     *
     * @constructor
     * @param {object} tree BEMJSON-дерево
     * @param {Pool} pool Список шаблонов
     */
    function Tree(tree, pool) {

        /**
         * Дерево.
         *
         * @private
         * @type {object}
         */
        this._tree = tree;

        /**
         * Список шаблонов.
         *
         * @private
         * @type {Pool}
         */
        this._pool = pool;
    }

    Tree.prototype = {

        /**
         * Развернуть дерево в набор экземпляров БЭМ-узлов.
         *
         * @param {object} bemjson BEMJSON
         * @returns {Node}
         */
        expand: function(bemjson) {
            return this._getNode(bemjson);
        },

        /**
         * Получить строковое представление дерева.
         *
         * @returns {string}
         */
        toString: function() {
            return this.expand(this._tree).toString();
        },

        /**
         * Получить список узлов и примитивов
         * на основе массива контента.
         *
         * @private
         * @param {array} bemjson Массив
         * @param {object} data Данные по сущности в дереве
         * @param {object} [data.context] Информация о родительском контексте
         * @param {string} [data.context.block] Имя родительского блока
         * @param {object} [data.context.mods] Модификаторы родительского блока
         * @param {string} [data.context.elem] Имя родительского элемента
         * @param {object} [data.context.elemMods] Модификаторы родительского элемента
         * @returns {array}
         */
        _getContentList: function(bemjson, data) {
            var list = [];
            for(var index = 0, len = bemjson.length; index < len; index++) {
                var item = bemjson[index],
                    elemData = {
                        index: index,
                        length: bemjson.length
                    };

                if(item) {
                    elemData.context = data.context;
                }

                var node = is.array(item)
                    ? this._getContentList(item, data)
                    : this._getNode(item, elemData);

                list = list.concat(node);
            }
            return list;
        },

        /**
         * Рекурсивно получить БЭМ-узел с его контентом на основе BEMJSON
         * или просто вернуть полученный примитив.
         *
         * @private
         * @param {*} bemjson BEMJSON или примитив
         * @param {object} [data] Данные по сущности в дереве
         * @param {object} [data.index] Порядковый индекс сущности
         * @param {object} [data.length] Количество сущностей у родителя
         * @param {object} [data.context] Информация о контексте родительского блока
         * @param {string} [data.context.block] Имя родительского блока
         * @param {object} [data.context.mods] Модификаторы родительского блока
         * @param {string} [data.context.elem] Имя родительского элемента
         * @param {object} [data.context.elemMods] Модификаторы родительского элемента
         * @returns {Node|*}
         */
        _getNode: function(bemjson, data) {
            if(!is.map(bemjson)) return bemjson === undefined ? '' : bemjson;

            data = data || {};

            if(!bemjson.block && bemjson.elem) {
                bemjson.block = data.context.block;
                bemjson.mods = object.extend(data.context.mods, bemjson.mods || {});
            }

            var node = this._pool.find(bemjson, data) || Template.base(bemjson, data),
                nodeBemjson = node.bemjson();

            if(nodeBemjson.block) {
                data.context = {
                    block: nodeBemjson.block,
                    mods: object.clone(nodeBemjson.mods)
                };

                if(node.isElem()) {
                    data.context.elem = nodeBemjson.elem;
                    data.context.elemMods = nodeBemjson.elemMods;
                }
            }

            return node.content(this[
                is.array(nodeBemjson.content) ? '_getContentList' : '_getNode'
            ](nodeBemjson.content, data));
        }

    };

    return Tree;

});