<commit_before>'use strict';

var util = require('util'),
    EventEmitter = require('events').EventEmitter,
    Sequelize = require('sequelize'),
    Promise = require('bluebird'),
    models = require('./models'),
    _ = require('lodash');

var logger = require('../core/Logger')('storage');

/**
 * Manage the database abstraction for xrocket
 */
var Storage = function (options) {
    EventEmitter.call(this);

    if (!options) {
        throw new Error('no database options set');
    }
    this.opt = options;
};

util.inherits(Storage, EventEmitter);

Storage.prototype.loadModels = function () {
    // load all models as own properties
    models(this.sequelize, this);
};

/**
 * Initialize the datababe and sync the tables if they are not
 * already there.
 */
Storage.prototype.initialize = function (syncOpts) {
    logger.debug('initialize');

    syncOpts = syncOpts ||  {};
    var self = this;
    return new Promise(function (resolve, reject) {

        var maxConcurrentQueries = self.opt.maxConcurrentQueries ||  100;
        var maxConnections = self.opt.maxConnections ||  1;
        var maxIdleTime = self.opt.maxIdleTime ||  30;

        // base options
        var options = {
            language: 'en',
            maxConcurrentQueries: maxConcurrentQueries,
            pool: {
                maxConnections: maxConnections,
                maxIdleTime: maxIdleTime
            }
        };

        // could be sqlite, postgres, mysql
        if (self.opt.dialect) {
            options.dialect = self.opt.dialect;
        }

        if (self.opt.host) {
            options.host = self.opt.host;
        }

        if (self.opt.port) {
            options.port = self.opt.port;
        }

        // path of the db file for sqlite
        if (self.opt.storage) {
            options.storage = self.opt.storage;
        }

        // initialize db connection
        var sequelize = new Sequelize(
            self.opt.database,
            self.opt.user,
            self.opt.password, options);
        self.sequelize = sequelize;

        self.loadModels();

        // sync models with database
        sequelize.sync(syncOpts)
            .complete(function (err) {
                if (err) {
                    logger.error(err);
                    reject(err);
                } else {
                    resolve(self);
                }
            });
    });
};

Storage.prototype.findUser = function (jid, options) {
    var storage = this;

    if (!jid) {
        throw new Error('jid is missing')
        return;
    }

    return storage.User.find({
        where: {
            jid: jid
        }
    }, options).then(function (user) {
        if (!user) {
            throw new Error('could not find user ')
        }
        return user;
    })

};

Storage.prototype.findOrCreateUser = function (jid) {
    var storage = this;

    if (!jid) {
        throw new Error('jid is missing')
        return;
    }

    return storage.User.findOrCreate({
        where : {
            jid: jid
        }
    }).spread(function (user, created) {
        if (created) {
            logger.debug('created user ' + JSON.stringify(user));
        }
        return user;
    })

};

Storage.prototype.findRoom = function (roomname) {
    var storage = this;

    if (!roomname) {
        throw new Error('roomname is missing')
        return;
    }

    return storage.Room.find({
        where: {
            name: roomname
        }
    }).then(function (room) {
        if (!room) {
            throw new Error('could not find room ')
        }
        return room;
    })

};

/**
 * owner is an instance of the user
 */
Storage.prototype.getRoom = function (owner, roomname) {
    var storage = this;

    if (!owner ||  !roomname) {
        throw new Error('getRoom: no owner or roomname');
        return;
    }

    var affiliation = [];
    affiliation.push(storage.RoomMember.Affiliation.Owner);

    // Owner as default affiliation
    return owner.getRooms({
        where: {
            name: roomname,
            "RoomMember.affiliation": affiliation
        }
    }).then(function (ownerRooms) {
        logger.debug('found rooms ' + JSON.stringify(ownerRooms));
        var room = _.first(ownerRooms)
        if (room) {
            return room;
        } else {
            throw new Error('could not find room ' + roomname);
        }
    })
};

Storage.prototype.getRooms = function (user, type) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    type = type || 'all';

    var affiliation = [];

    switch (type) {
    case 'owner':
        affiliation.push(storage.RoomMember.Affiliation.Owner);
        break;
    case 'member':
        affiliation.push(storage.RoomMember.Affiliation.Member);
        break;
    default: // all
        affiliation.push(storage.RoomMember.Affiliation.Owner);
        affiliation.push(storage.RoomMember.Affiliation.Member);
        break;
    }

    // Owner as default affiliation
    return user.getRooms({
        attributes: ['id'],
        where: {
            "RoomMember.affiliation": affiliation,
            "RoomMember.state" : [storage.RoomMember.State.Accepted, storage.RoomMember.State.Pending]
        }
    }).then(function (userRooms) {

        var ids = userRooms.map(function (val) {
            return val.id;
        });

        logger.debug(JSON.stringify(ids));

        // read rooms with members
        return storage.Room.findAll({
            // include owner
            include: [{
                model: storage.User,
                attributes: ['jid'],
                as: 'Members'
            }],
            where: {
                id: ids
            }
        })
    })
};

Storage.prototype.addRoom = function (user, data, options) {
    var storage = this;

    if (!user || !data) {
        throw new Error('no user or data');
    }

    logger.debug('add room ' + data.name + ' to user user' + JSON.stringify(user));
    return storage.Room.create({
        name: data.name,
        subject: data.subject,
        description: data.description
    }, options).then(function (room) {
        logger.debug('add member')
        return room.addMember(user, {
            'role': storage.RoomMember.Role.Moderator,
            'affiliation': storage.RoomMember.Affiliation.Owner,
            'nickname': '',
            'transaction' : options.transaction
        }).then(function(){
            return room;
        });
    })

};

Storage.prototype.updateRoom = function (room, data) {

    if (!room ||  !data) {
        throw new Error('no room or data');
    }

    var updates = {};

    if (data.subject) {
        updates.subject = data.subject;
    }

    if (data.description) {
        updates.description = data.description;
    }

    logger.debug('update room ' + room.name);
    return room.updateAttributes(updates)
};

Storage.prototype.delRoom = function (room) {
    var storage = this;

    if (!room) {
        throw new Error('no room');
    }

    logger.debug('remove room ' + room.name);

    // remove members because cascading delete does not work for through tables
    return room.destroy();

};

Storage.prototype.addMember = function (room, user, options) {

    logger.debug('add member');

    var storage = this;

    if (!room ||  !user) {
        throw new Error('no room or user');
    }

    return room.addMember(user, {
        'RoomMember.role': storage.RoomMember.Role.Participant,
        'RoomMember.affiliation': storage.RoomMember.Affiliation.Member,
        'RoomMember.nickname': '',
        'RoomMember.state' : storage.RoomMember.State.Accepted
    }, options)

};

Storage.prototype.inviteMember = function (data, options) {
    // TODO do not overwrite existing membership with invitation

    logger.debug('invite member');

    var storage = this;

    if (!data || !data.room || !data.invitee || !data.inviter) {
        throw new Error('room or invitee is missing');
    }

    logger.debug('compare ' + data.invitee.jid + ' ' + data.inviter.jid );
    if (data.invitee.jid === data.inviter.jid) {
        throw new Error('cannot invite inviter');
    }

    var room = data.room;

    // check if room has this member already, we cannot invite members
    return room.isMember(data.invitee).then(function(){
        // alright, we have nothing to do
        return;
    }).catch(function(){
        // add a user as pending
        return room.addMember(data.invitee, {
            'RoomMember.role': storage.RoomMember.Role.Participant,
            'RoomMember.affiliation': storage.RoomMember.Affiliation.Member,
            'RoomMember.nickname': '',
            'RoomMember.state' : storage.RoomMember.State.Pending
        }, options).then(function () {
            // added member to room
            storage.emit('invite', {
                room: room,
                invitee: data.invitee,
                inviter: data.inviter,
                reason: data.reason
            });

        })
    });
};

Storage.prototype.declineMembership = function (room, invitee, inviter, reason) {
    var storage = this;

    if (!room ||  !invitee) {
        throw new Error('no room or invitee');
    }

    // checkout if the current user is member
    return room.getMembers({
        where: {
            userid : invitee.id
        }
    }).then(function(users){
        console.log('found users: '+ JSON.stringify(users));

        // user is already part of this room
        if (users && users.length > 0) {
            var roomUser = users[0];

            // update data
            roomUser.RoomMember.state = storage.RoomMember.State.Declined;
            roomUser.RoomMember.save();

            // added member to room
            storage.emit('declined', {
                room: room,
                invitee: invitee,
                inviter: inviter,
                reason: reason
            });
            return roomUser;
        }
    })
};

Storage.prototype.removeMember = function (room, user) {

    if (!room ||  !user) {
        throw new Error('no room or user');
        return;
    }

    return room.removeMember(user);
},

Storage.prototype.getChannel = function (owner, channelname) {
    var storage = this;

    if (!owner ||  !channelname) {
        throw new Error('getChannel: no owner or channelname');
        return;
    }

    var affiliation = [];
    affiliation.push(storage.ChannelSub.Affiliation.Owner);

    // Owner as default affiliation
    return owner.getChannels({
        where: {
            name: channelname,
            affiliation: affiliation
        }
    }).then(function (ownerChannels) {

        var user = _.first(ownerChannels)
        if (user) {
            return user;
        } else {
            throw new Error('owner channels are missing');
        }
    })

},

Storage.prototype.getChannels = function (user, type) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    type = type || 'all';

    var affiliation = [];

    switch (type) {
    case 'owner':
        affiliation.push(storage.ChannelSub.Affiliation.Owner);
        break;
    case 'member':
        affiliation.push(storage.ChannelSub.Affiliation.Member);
        break;
    case 'publisher':
        affiliation.push(storage.ChannelSub.Affiliation.Publisher);
        break;
    default: // all
        affiliation.push(storage.ChannelSub.Affiliation.Owner);
        affiliation.push(storage.ChannelSub.Affiliation.Member);
        affiliation.push(storage.ChannelSub.Affiliation.Publisher);
        break;
    }

    // Owner as default affiliation
    return user.getChannels({
        where: {
            affiliation: affiliation
        }
    })
};

Storage.prototype.addChannel = function (user, data, options) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    return storage.Channel.create({
        name: data.name
    }, options).then(function (channel) {
        return user.addChannel(channel, {
            'affiliation': storage.ChannelSub.Affiliation.Owner,
            'substate': storage.ChannelSub.SubState.Member,
            'transaction' : options.transaction
        }).then(function(){
            return channel;
        })
    })

};

Storage.prototype.delChannel = function (channel) {
    var storage = this;

    if (!channel) {
        throw new Error('no channel');
        return;
    }

    // remove subscribers because cascading delete does not work for through tables
    /*storage.ChannelSub.destroy({
        ChannelId: channel.id
    }, {});*/

    // delete channel
    return channel.destroy()
};

Storage.prototype.findChannel = function (channelname) {
    var storage = this;
    return storage.Channel.find({
        where: {
            name: channelname
        }
    })
};

/**
 * finds or creates a channel
 */
Storage.prototype.findOrCreateChannel = function (channelname, user) {
    var storage = this;

    return storage.Channel.findOrCreate({
        where: {
            name: channelname
        }
    })
    .spread(function (channel, created) {

        if (created) {
            // assign channel to user
            return channel.associateUser(channel, user, []).then(function () {
                logger.debug('Found channel: ' + channel);
                return channel;
            })
        } else {
            return channel;
        }
    })
};

module.exports = Storage;<commit_msg>bugfix: fix wrong join table attribute names<commit_after>'use strict';

var util = require('util'),
    EventEmitter = require('events').EventEmitter,
    Sequelize = require('sequelize'),
    Promise = require('bluebird'),
    models = require('./models'),
    _ = require('lodash');

var logger = require('../core/Logger')('storage');

/**
 * Manage the database abstraction for xrocket
 */
var Storage = function (options) {
    EventEmitter.call(this);

    if (!options) {
        throw new Error('no database options set');
    }
    this.opt = options;
};

util.inherits(Storage, EventEmitter);

Storage.prototype.loadModels = function () {
    // load all models as own properties
    models(this.sequelize, this);
};

/**
 * Initialize the datababe and sync the tables if they are not
 * already there.
 */
Storage.prototype.initialize = function (syncOpts) {
    logger.debug('initialize');

    syncOpts = syncOpts ||  {};
    var self = this;
    return new Promise(function (resolve, reject) {

        var maxConcurrentQueries = self.opt.maxConcurrentQueries ||  100;
        var maxConnections = self.opt.maxConnections ||  1;
        var maxIdleTime = self.opt.maxIdleTime ||  30;

        // base options
        var options = {
            language: 'en',
            maxConcurrentQueries: maxConcurrentQueries,
            pool: {
                maxConnections: maxConnections,
                maxIdleTime: maxIdleTime
            }
        };

        // could be sqlite, postgres, mysql
        if (self.opt.dialect) {
            options.dialect = self.opt.dialect;
        }

        if (self.opt.host) {
            options.host = self.opt.host;
        }

        if (self.opt.port) {
            options.port = self.opt.port;
        }

        // path of the db file for sqlite
        if (self.opt.storage) {
            options.storage = self.opt.storage;
        }

        // initialize db connection
        var sequelize = new Sequelize(
            self.opt.database,
            self.opt.user,
            self.opt.password, options);
        self.sequelize = sequelize;

        self.loadModels();

        // sync models with database
        sequelize.sync(syncOpts)
            .complete(function (err) {
                if (err) {
                    logger.error(err);
                    reject(err);
                } else {
                    resolve(self);
                }
            });
    });
};

Storage.prototype.findUser = function (jid, options) {
    var storage = this;

    if (!jid) {
        throw new Error('jid is missing')
        return;
    }

    return storage.User.find({
        where: {
            jid: jid
        }
    }, options).then(function (user) {
        if (!user) {
            throw new Error('could not find user ')
        }
        return user;
    })

};

Storage.prototype.findOrCreateUser = function (jid) {
    var storage = this;

    if (!jid) {
        throw new Error('jid is missing')
        return;
    }

    return storage.User.findOrCreate({
        where : {
            jid: jid
        }
    }).spread(function (user, created) {
        if (created) {
            logger.debug('created user ' + JSON.stringify(user));
        }
        return user;
    })

};

Storage.prototype.findRoom = function (roomname) {
    var storage = this;

    if (!roomname) {
        throw new Error('roomname is missing')
        return;
    }

    return storage.Room.find({
        where: {
            name: roomname
        }
    }).then(function (room) {
        if (!room) {
            throw new Error('could not find room ')
        }
        return room;
    })

};

/**
 * owner is an instance of the user
 */
Storage.prototype.getRoom = function (owner, roomname) {
    var storage = this;

    if (!owner ||  !roomname) {
        throw new Error('getRoom: no owner or roomname');
        return;
    }

    var affiliation = [];
    affiliation.push(storage.RoomMember.Affiliation.Owner);

    // Owner as default affiliation
    return owner.getRooms({
        where: {
            name: roomname,
            "RoomMember.affiliation": affiliation
        }
    }).then(function (ownerRooms) {
        logger.debug('found rooms ' + JSON.stringify(ownerRooms));
        var room = _.first(ownerRooms)
        if (room) {
            return room;
        } else {
            throw new Error('could not find room ' + roomname);
        }
    })
};

Storage.prototype.getRooms = function (user, type) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    type = type || 'all';

    var affiliation = [];

    switch (type) {
    case 'owner':
        affiliation.push(storage.RoomMember.Affiliation.Owner);
        break;
    case 'member':
        affiliation.push(storage.RoomMember.Affiliation.Member);
        break;
    default: // all
        affiliation.push(storage.RoomMember.Affiliation.Owner);
        affiliation.push(storage.RoomMember.Affiliation.Member);
        break;
    }

    // Owner as default affiliation
    return user.getRooms({
        attributes: ['id'],
        where: {
            "RoomMember.affiliation": affiliation,
            "RoomMember.state" : [storage.RoomMember.State.Accepted, storage.RoomMember.State.Pending]
        }
    }).then(function (userRooms) {

        var ids = userRooms.map(function (val) {
            return val.id;
        });

        logger.debug(JSON.stringify(ids));

        // read rooms with members
        return storage.Room.findAll({
            // include owner
            include: [{
                model: storage.User,
                attributes: ['jid'],
                as: 'Members'
            }],
            where: {
                id: ids
            }
        })
    })
};

Storage.prototype.addRoom = function (user, data, options) {
    var storage = this;

    if (!user || !data) {
        throw new Error('no user or data');
    }

    logger.debug('add room ' + data.name + ' to user user' + JSON.stringify(user));
    return storage.Room.create({
        name: data.name,
        subject: data.subject,
        description: data.description
    }, options).then(function (room) {
        logger.debug('add member')
        return room.addMember(user, {
            'role': storage.RoomMember.Role.Moderator,
            'affiliation': storage.RoomMember.Affiliation.Owner,
            'nickname': '',
            'transaction' : options.transaction
        }).then(function(){
            return room;
        });
    })

};

Storage.prototype.updateRoom = function (room, data) {

    if (!room ||  !data) {
        throw new Error('no room or data');
    }

    var updates = {};

    if (data.subject) {
        updates.subject = data.subject;
    }

    if (data.description) {
        updates.description = data.description;
    }

    logger.debug('update room ' + room.name);
    return room.updateAttributes(updates)
};

Storage.prototype.delRoom = function (room) {
    var storage = this;

    if (!room) {
        throw new Error('no room');
    }

    logger.debug('remove room ' + room.name);

    // remove members because cascading delete does not work for through tables
    return room.destroy();

};

Storage.prototype.addMember = function (room, user, options) {

    logger.debug('add member');

    var storage = this;

    if (!room ||  !user) {
        throw new Error('no room or user');
    }

    return room.addMember(user, {
        'role': storage.RoomMember.Role.Participant,
        'affiliation': storage.RoomMember.Affiliation.Member,
        'nickname': '',
        'state' : storage.RoomMember.State.Accepted
    }, options)

};

Storage.prototype.inviteMember = function (data, options) {
    // TODO do not overwrite existing membership with invitation

    logger.debug('invite member');

    var storage = this;

    if (!data || !data.room || !data.invitee || !data.inviter) {
        throw new Error('room or invitee is missing');
    }

    logger.debug('compare ' + data.invitee.jid + ' ' + data.inviter.jid );
    if (data.invitee.jid === data.inviter.jid) {
        throw new Error('cannot invite inviter');
    }

    var room = data.room;

    // check if room has this member already, we cannot invite members
    return room.isMember(data.invitee).then(function(){
        // alright, we have nothing to do
        return;
    }).catch(function(){
        // add a user as pending
        return room.addMember(data.invitee, {
            'RoomMember.role': storage.RoomMember.Role.Participant,
            'RoomMember.affiliation': storage.RoomMember.Affiliation.Member,
            'RoomMember.nickname': '',
            'RoomMember.state' : storage.RoomMember.State.Pending
        }, options).then(function () {
            // added member to room
            storage.emit('invite', {
                room: room,
                invitee: data.invitee,
                inviter: data.inviter,
                reason: data.reason
            });

        })
    });
};

Storage.prototype.declineMembership = function (room, invitee, inviter, reason) {
    var storage = this;

    if (!room ||  !invitee) {
        throw new Error('no room or invitee');
    }

    // checkout if the current user is member
    return room.getMembers({
        where: {
            userid : invitee.id
        }
    }).then(function(users){
        console.log('found users: '+ JSON.stringify(users));

        // user is already part of this room
        if (users && users.length > 0) {
            var roomUser = users[0];

            // update data
            roomUser.RoomMember.state = storage.RoomMember.State.Declined;
            roomUser.RoomMember.save();

            // added member to room
            storage.emit('declined', {
                room: room,
                invitee: invitee,
                inviter: inviter,
                reason: reason
            });
            return roomUser;
        }
    })
};

Storage.prototype.removeMember = function (room, user) {

    if (!room ||  !user) {
        throw new Error('no room or user');
        return;
    }

    return room.removeMember(user);
},

Storage.prototype.getChannel = function (owner, channelname) {
    var storage = this;

    if (!owner ||  !channelname) {
        throw new Error('getChannel: no owner or channelname');
        return;
    }

    var affiliation = [];
    affiliation.push(storage.ChannelSub.Affiliation.Owner);

    // Owner as default affiliation
    return owner.getChannels({
        where: {
            name: channelname,
            affiliation: affiliation
        }
    }).then(function (ownerChannels) {

        var user = _.first(ownerChannels)
        if (user) {
            return user;
        } else {
            throw new Error('owner channels are missing');
        }
    })

},

Storage.prototype.getChannels = function (user, type) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    type = type || 'all';

    var affiliation = [];

    switch (type) {
    case 'owner':
        affiliation.push(storage.ChannelSub.Affiliation.Owner);
        break;
    case 'member':
        affiliation.push(storage.ChannelSub.Affiliation.Member);
        break;
    case 'publisher':
        affiliation.push(storage.ChannelSub.Affiliation.Publisher);
        break;
    default: // all
        affiliation.push(storage.ChannelSub.Affiliation.Owner);
        affiliation.push(storage.ChannelSub.Affiliation.Member);
        affiliation.push(storage.ChannelSub.Affiliation.Publisher);
        break;
    }

    // Owner as default affiliation
    return user.getChannels({
        where: {
            affiliation: affiliation
        }
    })
};

Storage.prototype.addChannel = function (user, data, options) {
    var storage = this;

    if (!user) {
        throw new Error('no user');
    }

    return storage.Channel.create({
        name: data.name
    }, options).then(function (channel) {
        return user.addChannel(channel, {
            'affiliation': storage.ChannelSub.Affiliation.Owner,
            'substate': storage.ChannelSub.SubState.Member,
            'transaction' : options.transaction
        }).then(function(){
            return channel;
        })
    })

};

Storage.prototype.delChannel = function (channel) {
    var storage = this;

    if (!channel) {
        throw new Error('no channel');
        return;
    }

    // remove subscribers because cascading delete does not work for through tables
    /*storage.ChannelSub.destroy({
        ChannelId: channel.id
    }, {});*/

    // delete channel
    return channel.destroy()
};

Storage.prototype.findChannel = function (channelname) {
    var storage = this;
    return storage.Channel.find({
        where: {
            name: channelname
        }
    })
};

/**
 * finds or creates a channel
 */
Storage.prototype.findOrCreateChannel = function (channelname, user) {
    var storage = this;

    return storage.Channel.findOrCreate({
        where: {
            name: channelname
        }
    })
    .spread(function (channel, created) {

        if (created) {
            // assign channel to user
            return channel.associateUser(channel, user, []).then(function () {
                logger.debug('Found channel: ' + channel);
                return channel;
            })
        } else {
            return channel;
        }
    })
};

module.exports = Storage;<|endoftext|>


<commit_before>/*
	Qatrix JavaScript v0.9

	Copyright (c) 2012, The Qatrix project, Angel Lai
	The Qatrix project is under MIT license.
	For details, see the Qatrix web site: http://qatrix.com
*/

var Qatrix = {
		version: '0.9',

		rtrim: /(^\s*)|(\s*$)/g,
		rcamelCase: /-([a-z])/ig,
		rdigit: /\d/,
		rnum: /[0-9\.]/ig,
		rvalidchars: /^[\],:{}\s]*$/,
		rvalidescape: /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
		rvalidtokens: /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
		rvalidbraces: /(?:^|:|,)(?:\s*\[)+/g,

		nodeManip: function (elem, node)
		{
			var type = typeof node;
			if (type === 'string')
			{
			    var doc = (elem && elem.ownerDocument) ? elem.ownerDocument : document,
			        fragment = doc.createDocumentFragment(),
			        div = document.createElement("div"),
			        ret = [];

			    div.innerHTML = node;
			    while (div.childNodes[0] != null)
			    {
			        fragment.appendChild(div.childNodes[0]);
			    }
			    node = fragment;

			    //Release  memory
			    div = null;
			}

			if (type === 'number')
			{
			    node += '';
			}

			return node;
		}
	},
	//Compatible for other $ based libraries
	$ = ( $ === undefined ) ? function (id)
	{
		//Qatrix just returns getElementById directly without additional process for highest performance.
		//For more complex manipulation, use $id.
		return document.getElementById(id);
	}:
	$,
	$each = function (haystack, callback)
	{
		var i = 0,
		    length = haystack.length,
		    name;
		if (length)
		{
		    for (; i < length; ++i)
		    {
		        callback.call(haystack[i], i, haystack[i]);
		    }
		}
		else
		{
		    for (name in haystack)
		    {
		        callback.call(haystack[name], name, haystack[name]);
		    }
		}
	},
	$id = function (id, callback)
	{
		var match = [],
		    elem;
		if(typeof id === 'string')
		{
			elem = $(id);
			if(elem != null && callback)
			{
				callback(elem);
			}
			return elem;
		}
		$each(id, function (i, item)
		{
		    elem = $(item);
		    if (elem != null)
		    {
		        match.push(elem);
		    }
		});
		if (callback && match.length > 0)
		{
		    $each(match, function (i, item)
		    {
		        callback(item);
		    });
		}
		return match;
	},
	$tag = function (elem, name, callback)
	{
		var match = elem.getElementsByTagName(name);
		if (callback && match.length > 0)
		{
		    $each(match, function (i, item)
		    {
		        callback(item);
		    });
		}
		return match;
	},
	$class = ( document.getElementsByClassName ) ?
		function (elem, className, callback)
		{
		    var match = elem.getElementsByClassName(className);
		    if (callback && match.length > 0)
		    {
		        $each(match, function (i, item)
		        {
		            callback(item);
		        });
		    }
		    return match;
		}:
		function (elem, className, callback)
		{
		    var match = [],
		        rclass = new RegExp('\\b' + className + '\\b'),
		        childs = elem.getElementsByTagName('*');
		    $each(childs, function (i, item)
		    {
		        if (rclass.test(item.className))
		        {
		            match.push(item);
		        }
		    });
		    if (callback && match.length > 0)
		    {
		        $each(match, function (i, item)
		        {
		            callback(item);
		        });
		    }
		    return match;
		},
	$select = ( document.querySelectorAll ) ?
		function (selector, callback)
		{
		    var match = document.querySelectorAll(selector);
		    if (callback && match.length > 0)
		    {
		        $each(match, function (i, item)
		        {
		            callback(item);
		        });
		    }
		}:
		//Hack native CSS selector quering matched element for IE6/7.
		function (selector, callback)
		{
		    var style = Qatrix.Qselector.styleSheet,
		        match = [];
		    style.addRule(selector, 'q:a');
		    $tag(document, '*', function (item)
		    {
		        if (item.currentStyle.q === 'a')
		        {
		            match.push(item);
		        }
		    });
		    style.cssText = '';
		    if (callback && match.length > 0)
		    {
		        $each(match, function (i, item)
		        {
		            callback(item);
		        });
		    }
		    return match;
		},
	$new = function (tag, properties)
	{
		var elem = document.createElement(tag);
		if (properties)
		{
		    $each(properties, function (name, property)
		    {
		        switch (name)
		        {
		        case 'css':
		        case 'style':
		            $each(property[name], function (css, value)
		            {
		                $style.set(elem, css, value);
		            });
		            break;

		        case 'innerHTML':
		        case 'html':
		            $html(elem, property);
		            break;

		        case 'text':
		            $text(elem, property);
		            break;
		        default:
		            elem[name] = properties[name];
		            break;
		        }
		    });
		}
		return elem;
	},
	$string = {
		camelCase: function (string)
		{
		    return string.replace('-ms-', 'ms-').replace(Qatrix.rcamelCase, function (match, letter)
		    {
		        return (letter + '').toUpperCase();
		    });
		},
		replace: function (string, replacements)
		{
		    for (var key in replacements)
		    {
		        string = string.replace(new RegExp(key, 'ig'), replacements[key]);
		    }
		    return string;
		},
		trim: (String.prototype.trim) ?
		function (string)
		{
			return string.trim();
		}:
		function (string)
		{
		    return string.replace(Qatrix.rtrim, '');
		}
	},
	$attr = {
	    get: function (elem, name)
	    {
	        return elem.getAttribute(name);
	    },
	    set: function (elem, name, value)
	    {
	        return elem.setAttribute(name, value);
	    },
	    remove: function (elem, name)
	    {
	        return elem.removeAttribute(name);
	    }
	},
	$data = {
	    get: function (elem, name)
	    {
	        var value = $attr.get(elem, 'data-' + name);
	        return value === "true" ? true :
						value === "false" ? false :
						value === "null" ? '' :
						value === null ? '' :
						value === '' ? '' :
						Qatrix.rdigit.test(value) ? parseInt(value) :
						$json.isJSON(value) ? $json.decode(value) :
						value;
	    },
	    set: function (elem, name, value)
	    {
	        if (typeof name === 'object')
	        {
	            $each(name, function (key, value)
	            {
	                $attr.set(elem, 'data-' + key, value);
	            });
	        }
	        else
	        {
	            $attr.set(elem, 'data-' + name, value);
	        }
	        return elem;
	    },
	    remove: function (elem, name)
	    {
	        return $attr.remove(elem, 'data-' + name);
	    }
	},
	$cache = {
	    data: {},
	    get: function (key)
	    {
	        var data = $cache.data[key];
	        return (data || typeof data === 'number') ? data : null;
	    },
	    set: function (key, value)
	    {
	        $cache.data[key] = value;
	        return value;
	    },
	    inc: function (key)
	    {
	        var data = $cache.data[key];
	        return (typeof data === 'number') ? $cache.data[key]++ : data;
	    },
	    dec: function (key)
	    {
	        var data = $cache.data[key];
	        return (typeof data === 'number') ? $cache.data[key]-- : data;
	    },
	    remove: function (key)
	    {
	        delete $cache.data[key];
	        return true;
	    },
	    flush: function ()
	    {
	        $cache.data = {};
	        return true;
	    }
	},
	$event = {
	    add: function (elem, type, handler, check_attached)
	    {
	        if (check_attached == true)
	        {
	            if ($data.get(elem, 'event-' + type))
	            {
	                return false;
	            }
	        }
	        $data.set(elem, 'event-' + type, true);
	        if (type === 'mouseenter' || type === 'mouseleave')
	        {
	            type = (type === 'mouseenter') ? 'mouseover' : 'mouseout';
	            handle = $event.handler.mouseenter(handler);
	        }
	        if (elem.addEventListener)
	        {
	            elem.addEventListener(type, handler, false);
	        }
	        else if (elem.attachEvent)
	        {
	            elem.attachEvent('on' + type, handler);
	        }
	        return elem;
	    },
	    remove: function (elem, type, handler)
	    {
	        if (elem.removeEventListener)
	        {
	            elem.removeEventListener(type, handler, false);
	        }
	        else if (elem.detachEvent)
	        {
	            elem.detachEvent('on' + type, handler);
	        }
	        $data.remove(elem, 'event-' + type);
	        return elem;
	    },
	    handler: {
	        mouseenter: function (handler)
	        {
	            return function (event)
	            {
	                function is_child(elem_parent, elem_child)
	                {
	                    if (elem_parent === elem_child)
	                    {
	                        return false;
	                    }
	                    while (elem_child && elem_child !== elem_parent)
	                    {
	                        elem_child = elem_child.parentNode;
	                    }
	                    return elem_child === elem_parent;
	                }
	                var target = event.relatedTarget;
	                if (this === target || is_child(this, target))
	                {
	                    return;
	                }
	                handler.call(this, event);
	            }
	        }
	    },
	    key: function (event)
	    {
	        if (event.which == null && (event.charCode != null || event.keyCode != null))
	        {
	            event.which = event.charCode != null ? event.charCode : event.keyCode;
	        }
	        return event.which;
	    },
	    metaKey: function (event)
	    {
	        return (!event.metaKey && event.ctrlKey) ? event.ctrlKey : event.metaKey;
	    },
	    target: function (event)
	    {
	        if (!event.target)
	        {
	            event.target = event.srcElement || document;
	        }
	        return event.target;
	    },
	    mouseOver: function (event, originElement)
	    {
	        if (!event.relatedTarget && event.fromElement)
	        {
	            event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
	        }
	        var parent = event.relatedTarget;
	        while (parent && parent !== originElement)
	        {
	            parent = parent.parentNode;
	        }
	        if (parent !== originElement)
	        {
	            return true;
	        }
	        return false;
	    }
	},
	$clear = function (timer)
	{
	    if (timer)
	    {
	        clearTimeout(timer);
	        clearInterval(timer);
	    }
	    return null;
	},
	$ready = function (callback)
	{
		if (document.readyState === 'complete')
		{
			return setTimeout(callback, 1);
		}
		//Hack IE DOM for ready event.
	    function domready()
	    {
	        try
	        {
	            document.documentElement.doScroll('left');
	            callback();
	        }
	        catch (e)
	        {
				setTimeout(domready, 1);
	            return;
	        }
	    }
	    domready();
	},
	$css = {
	    get: function (elem, name)
	    {
	        return $style.get(elem, name);
	    },
	    set: function (elem, name, value)
	    {
	        if (typeof name === 'object')
	        {
	            $each(name, function (propertyName, value)
	            {
	                $style.set(elem, $string.camelCase(propertyName), $css.fix(propertyName, value));
	            });
	        }
	        else
	        {
	            $style.set(elem, $string.camelCase(name), $css.fix(name, value));
	        }
	        return elem;
	    },
	    number: {
	        'fontWeight': true,
	        'lineHeight': true,
	        'opacity': true
	    },
	    unit: function (name, value)
	    {
	        if ($css.number[name])
	        {
	            return '';
	        }
	        var unit = value.toString().replace(Qatrix.rnum, '');
	        return (unit === '') ? 'px' : unit;
	    },
	    fix: function (name, value)
	    {
	        var type = typeof value;
	        if (type === 'number' && !$css.number[name])
	        {
	            value += 'px';
	        }
	        if (value == null && isNaN(value))
	        {
	            return false;
	        }
	        return value;
	    }
	},
	$style = {
		get: (window.getComputedStyle) ?
		function (elem, property)
		{
		    if (elem !== null)
		    {
		        return document.defaultView.getComputedStyle(elem, null).getPropertyValue(property);
		    }
		}:
		function (elem, property)
		{
		    if (elem !== null)
		    {
		        if (property === 'width' && elem.currentStyle['width'] === 'auto')
		        {
		            return elem.offsetWidth;
		        }
		        if (property === 'height' && elem.currentStyle['height'] === 'auto')
		        {
		            return elem.offsetHeight;
		        }
		        return elem.currentStyle[$string.camelCase(property)];
		    }
		},
		set: (document.documentElement.style.opacity !== undefined) ?
		function (elem, name, value)
		{
		    elem.style[name] = value;
		}:
		function (elem, name, value)
		{
		    if (!elem.currentStyle || !elem.currentStyle.hasLayout)
		    {
		        elem.style.zoom = 1;
		    }
		    if (name === 'opacity')
		    {
		        elem.style.filter = 'alpha(opacity=' + value * 100 + ')';
		    }
		    else
		    {
		        elem.style[name] = value;
		    }
		}
	},
	$pos = function (elem, x, y)
	{
		$style.set(elem, 'left', x + 'px');
		$style.set(elem, 'top', y + 'px');
		return elem;
	},
	$offset = function (elem)
	{
	    var left = elem.offsetLeft,
	        top = elem.offsetTop,
	        offsetParent = elem.offsetParent;
	    while (offsetParent !== null)
	    {
	        left += offsetParent.offsetLeft + offsetParent.clientLeft;
	        top += offsetParent.offsetTop + offsetParent.clientTop;
	        if (offsetParent != document.body && offsetParent != document.documentElement)
	        {
	            left -= offsetParent.scrollLeft;
	            top -= offsetParent.scrollTop;
	        }
	        offsetParent = offsetParent.offsetParent;
	    }
	    return {
	        top: top,
	        left: left
	    };
	},
	$append = function (elem, node)
	{
	    return elem.appendChild(Qatrix.nodeManip(elem, node));
	},
	$prepend = function (elem, node)
	{
	    return (elem.firstChild) ? elem.insertBefore(Qatrix.nodeManip(elem, node), elem.firstChild) : elem.appendChild(Qatrix.nodeManip(elem, node));
	},
	$before = function (elem, node)
	{
	    return elem.parentNode.insertBefore(Qatrix.nodeManip(elem, node), elem);
	},
	$after = function (elem, node)
	{
	    return (elem.nextSibling) ? elem.parentNode.insertBefore(Qatrix.nodeManip(elem, node), elem.nextSibling) : elem.parentNode.appendChild(Qatrix.nodeManip(elem, node));
	},
	$remove = function (elem)
	{
	    if (elem != null && elem.parentNode)
	    {
	        elem.parentNode.removeChild(elem);
	    }
	    return elem;
	},
	$empty = function (elem)
	{
	    //Directly setting the innerHTML attribute to blank will release memory for browser.
	    //Only remove the childNodes will not able to do this for some browsers.
	    elem.innerHTML = '';
	    return elem;
	},
	$html = function (elem, html)
	{
	    try
	    {
	        elem.innerHTML = html;
	    }
	    catch (e)
	    {
	        $append($empty(elem), html);
	    }
	    return elem;
	},
	$text = function (elem, text)
	{
	    $empty(elem);
	    elem.appendChild(document.createTextNode(text));
	    return elem;
	},
	$className = {
	    add: function (elem, name)
	    {
	        if (elem.className === '')
	        {
	            elem.className = name;
	        }
	        else
	        {
	            if (elem.className !== name)
	            {
	                var haystack = elem.className.split(/\s+/),
	                    match;
	                $each(haystack, function (i, item)
	                {
	                    if (item === name)
	                    {
	                        match = true;
	                    }
	                });
	                if (!match)
	                {
	                    haystack.push(name);
	                    elem.className = haystack.join(' ');
	                }
	            }
	        }
	        return elem;
	    },
	    set: function (elem, name)
	    {
	        elem.className = name;
	        return elem;
	    },
		has: function (elem, name)
		{
			var className = elem.className,
				haystack = name.split(" "),
				i = 0,
				l = haystack.length;
			for( ; i < l ; ++i )
			{
				if( className.indexOf( " " + haystack[i] + " " ) > -1 || className.indexOf( haystack[i] + " " ) > -1 || className.indexOf( " " + haystack[i] ) > -1 || className == haystack[i] )
				{
					return true;
				}
			}
			return false;
		},
	    remove: function (elem, name)
	    {
	        elem.className = (name) ? $string.trim(elem.className.replace(name, '').split(/\s+/).join(' ')) : '';
	        return elem;
	    }
	},
	$hide = function (elem)
	{
	    $each(arguments, function (i, argument)
	    {
	        (typeof argument === 'string') ? $(argument).style.display = 'none' : (typeof argument === 'object' && argument.length) ? $each(argument, function (i, elem)
	        {
	            elem.style.display = 'none';
	        }) : argument.style.display = 'none';
	    });
	},
    $show = function (elem)
    {
        $each(arguments, function (i, argument)
        {
            (typeof argument === 'string') ? $(argument).style.display = 'block' : (typeof argument === 'object' && argument.length) ? $each(argument, function (i, elem)
            {
                elem.style.display = 'block';
            }) : argument.style.display = 'block';
        });
    },
	$animate = (

		//Using CSS3 transition for animation as possible.
		(function()
		{
			var elem_style = document.documentElement.style;
			return ( elem_style.webkitTransition !== undefined ||
						elem_style.MozTransition !== undefined ||
						elem_style.OTransition !== undefined ||
						elem_style.MsTransition !== undefined ||
						elem_style.transition !== undefined );
		}())
	) ?
	(function ()
	{
		var elem_style = document.documentElement.style,
		    prefix_name =
				(elem_style.webkitTransition !== undefined) ? '-webkit-' :
				(elem_style.MozTransition !== undefined) ? '-moz-' :
				(elem_style.OTransition !== undefined) ? '-o-' :
				(elem_style.MsTransition !== undefined) ? '-ms-' : '';
		transition_name = prefix_name + 'transition';
		return function (elem, properties, duration, callback)
		{
		    var property_name = [],
		        property_to_value = [],
		        property_camelCase = [],
		        unit = [],
		        css, translate3d, AUID, rule;
			duration = (duration) ? duration : '500';
		    for (css in properties)
		    {
		        property_camelCase[css] = $string.camelCase(css);
		        if (properties[css].from != null)
		        {
		            property_to_value[css] = (!$css.number[css]) ? parseInt(properties[css].to) : properties[css].to;
		            unit[css] = $css.unit(css, properties[css].to);
		            $style.set(elem, property_camelCase[css], parseInt(properties[css].from) + unit[css]);
		        }
		        else
		        {
		            property_to_value[css] = (!$css.number[css]) ? parseInt(properties[css]) : properties[css];
		            unit[css] = $css.unit(css, properties[css]);
		            $style.set(elem, property_camelCase[css], $style.get(elem, property_camelCase[css]));
		        }
		        property_name.push(css);
		    }

		    //Using CSS3 transform function will enable to use hardware acceleration to handle this animation.
		    if (properties['left'] || properties['top'])
		    {
		        property_name.push(prefix_name + 'transform');
		    }

		    AUID = 'Qatrix_animation_' + Math.random().toString().replace('0.', '');
		    rule = '.' + AUID + '{' + transition_name + ':all ' + duration + 'ms;}';

		    Qatrix.Qanimate.sheet.insertRule( '.' + AUID + '{' + transition_name + ':all ' + duration + 'ms;}', 0);
		    setTimeout(function ()
		    {
		        $className.add(elem, AUID);
		        if (properties['left'] || properties['top'])
		        {
		            var transform = $string.camelCase(prefix_name + 'transform'),
		                offset = $offset(elem);
		            translate3d = (properties['left'] && properties['top']) ? 'translate3d(' + (property_to_value['left'] - offset.left) + unit['left'] + ',' + (property_to_value['top'] - offset.top) + unit['top'] + ',0)' : (properties['left']) ? 'translate3d(' + (property_to_value['left'] - offset.left) + unit['left'] + ',0,0)' : 'translate3d(0,' + (property_to_value['top'] - offset.top) + unit['top'] + ',0)';
		            $style.set(elem, transform, translate3d);
		            $style.set(elem, transform, '');
		        }
		        for (css in properties)
		        {
		            $style.set(elem, property_camelCase[css], property_to_value[css] + unit[css]);
		        }
		    }, 50);

			//Animation completed
		    setTimeout(function ()
		    {
		        $className.remove(elem, AUID);
		        Qatrix.Qanimate.sheet.textContent = Qatrix.Qanimate.textContent.replace(rule, '');
		        if (callback)
		        {
		            callback();
		        }
		    }, duration);

		    return elem;
		}
	})():
	function (elem, properties, duration, callback)
	{
		var $timer = [],
		    $step = 0,
		    p = 30,
		    ori = [],
		    diff = [],
		    unit = [],
		    i = 0,
		    css, property_name = [],
		    property_to_value = [],
		    property_camelCase = [],
		    from_value, style_value;
		duration = (duration) ? duration : '500';
		for (css in properties)
		{
		    property_camelCase[css] = $string.camelCase(css);
		    if (properties[css].from != null)
		    {
		        property_to_value[css] = (!$css.number[css]) ? parseInt(properties[css].to) : properties[css].to;
		        unit[css] = $css.unit(css, properties[css].to);
		        from_value = (!$css.number[css]) ? parseInt(properties[css].from) : properties[css].from;
		        $style.set(elem, property_camelCase[css], from_value + unit[css]);
		        diff[css] = (property_to_value[css] - from_value) / p;
		        ori[css] = from_value;
		    }
		    else
		    {
		        style_value = parseInt($style.get(elem, property_camelCase[css]));
		        property_to_value[css] = (!$css.number[css]) ? parseInt(properties[css]) : properties[css];
		        unit[css] = $css.unit(css, properties[css]);
		        $style.set(elem, property_camelCase[css], $style.get(elem, property_camelCase[css]));
		        diff[css] = (property_to_value[css] - style_value) / p;
		        ori[css] = style_value;
		    }
		}
		for (; i < p; ++i)
		{
		    $timer[i] = setTimeout(function ()
		    {
		        for (css in properties)
		        {
		            $style.set(elem, property_camelCase[css], (ori[css] + (diff[css] * $step)) + unit[css]);
		        }
		        $step++;
		    }, (duration / p) * i);
		}
		$timer[p] = setTimeout(function ()
		{
		    for (css in properties)
		    {
		        $style.set(elem, property_camelCase[css], property_to_value[css] + unit[css]);
		    }
		    if (callback)
		    {
		        callback();
		    }
		}, duration);

		return elem;
	},
	$fadeout = function(elem, duration, callback)
	{
		duration = (duration) ? duration : '500';
		return $animate(elem, {
		    'opacity': {
		        from: 1,
		        to: 0
		    }
		}, duration, callback);
	},
	$fadein = function (elem, duration, callback)
	{
		duration = (duration) ? duration : '500';
		return $animate(elem, {
			'opacity': {
				from: 0,
				to: 1
			}
		}, duration, callback);
	},
	$cookie = {
		get: function (key)
		{
		    var tempArr = document.cookie.split('; '),
		        i = 0,
		        l = tempArr.length,
		        temp;
		    for (; i < l; i++)
		    {
		        temp = tempArr[i].split('=');
		        if (temp[0] == key)
		        {
		            return unescape(temp[1]);
		        }
		    }
		},
		set: function (key, value, expires)
		{
		    var today = new Date(),
		        expires_date;
		    today.setTime(today.getTime());
		    if (expires)
		    {
		        expires = expires * 86400000;
		    }
		    expires_date = new Date(today.getTime() + (expires));
		    document.cookie = key + '=' + escape(value) + ((expires) ? ';expires=' + expires_date.toGMTString() : '') + '; path=/';
		},
		remove: function ()
		{
		    $each(arguments, function (i, key)
		    {
		        $cookie.set(key, '', -1);
		    });
		}
	},
	$json = {
		decode: ( window.JSON && window.JSON.parse ) ?
		function (data)
		{
			return JSON.parse(data);
		}:
		function (data)
		{
			if( $json.isJSON( data ) )
			{
				return eval('(' + data + ')');
			}
			else
			{
				return false;
			}
		},
		isJSON: function (string)
		{
			if(typeof string === 'string' && string !== '')
			{
				return Qatrix.rvalidchars.test(string.replace(Qatrix.rvalidescape, '@')
							.replace(Qatrix.rvalidtokens, ']')
							.replace(Qatrix.rvalidbraces, ''));
			}
			else
			{
				return false;
			}
		}
	},
	$ajax = function (url, options)
	{
		if (typeof url === 'object')
		{
		    options = url;
		    url = undefined;
		}
		options = options || {};
		var request = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
		    param = [],
		    type = (options.type !== undefined) ? options.type : 'POST',
		    response, url;
		if (request)
		{
		    url = (url) ? url : options.url;
		    request.open(type, url, true);
		    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');

		    if (options.header !== undefined)
		    {
		        $each(options.header, function (key, value)
		        {
		            request.setRequestHeader(key, value);
		        });
		    }

		    if (options.data !== undefined)
		    {
		        $each(options.data, function (key, value)
		        {
		            param.push($url(key) + '=' + $url(value));
		        });
		    }

		    request.send(param.join('&').replace(/%20/g, '+'));
		    request.onreadystatechange = function ()
		    {
		        if (request.readyState == 4 && request.status == 200)
		        {
		            if (options.success !== undefined)
		            {
		                response = request.responseText;
		                if (response != '' && $json.isJSON(response))
		                {
		                    options.success($json.decode(response));
		                }
		                else
		                {
		                    options.success(response);
		                }
		            }
		        }
		    };
		}
	},
	$loadscript = function (src)
	{
	    var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
	    script = $new('script', {
	        'type': 'text/javascript',
	        'async': true,
	        'src': src
	    });
	    $before(head, script);
	    return script;
	},
	$url = function (data)
	{
	    return encodeURIComponent(data);
	},
	$rand = function (min, max)
	{
	    return Math.floor(Math.random() * (max - min + 1) + min);
	};

var $browser = {};
(function () {
    var ua = navigator.userAgent.toLowerCase(),
		rbrowser = {
			msie : /msie/,
			msie6 : /msie 6\.0/,
			msie7 : /msie 7\.0/,
			msie8 : /msie 8\.0/,
			msie9 : /msie 9\.0/,
			msie10: /msie 10\.0/,
			firefox : /firefox/,
			opera : /opera/,
			webkit : /webkit/,
			iPad : /ipad/,
			iPhone : /iphone/,
			android : /android/
			},
		i;
	$each( rbrowser, function ( key, value )
	{
		$browser[ key ] = rbrowser[key].test(ua);
	});
	//For hack CSS selector
	if (document.querySelectorAll === undefined)
	{
		$ready(function()
		{
			Qatrix.Qselector = $append(document.body, $new('style'));
		});
	}
	//For animation
	Qatrix.Qanimate = $append( document.head || document.getElementsByTagName('head')[0] || document.documentElement, $new('style'));
})();
<commit_msg>0.9 beta<commit_after><|endoftext|>


<commit_before>var events = require("events");
var fs = require('fs');
var http = require('http');
var https = require('https');
var util = require('util');

var Promise = require('promise');
var Speaker = require('speaker');
var lame = require('lame');
var mpg123Util = require('node-mpg123-util');

/**
 * @constructor
 */
var StupidPlayer = function(uri) {
	this._decoder = null;
	this._speaker = null;
	this._readStream = null;
	this._uri = null;

	this._state = StupidPlayer.State.STOP;

	this._onDecoderFormatted = this._onDecoderFormatted.bind(this);
	this._onDecoderClosed = this._onDecoderClosed.bind(this);
	this._onError = this._onError.bind(this);
};
util.inherits(StupidPlayer, events.EventEmitter);

/**
 * @param {string} uri
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.play = function(uri) {
	this._uri = uri;
	this._deinit();
	this._state = StupidPlayer.State.PLAY;
	this._emit(this.EVENT_PLAY, this._isStream(uri));

	if (this._isStream(uri)) {
		return this._playStream(uri);
	} else {
		return this._playFile(uri);
	}
};

/**
 *
 */
StupidPlayer.prototype.stop = function() {
	if (this._state !== StupidPlayer.State.STOP) {
		this._deinit();
		this._state = StupidPlayer.State.STOP;
		this._emit(this.EVENT_STOP);
	}
};

/**
 * @param {number} value 0..1
 * @return {Promise.<number>}
 */
StupidPlayer.prototype.setVolume = function(value) {
	return new Promise(function(resolve, reject) {
		var resolver = function() {
			resolve(value);
			this._emit(this.EVENT_VOLUME_CHANGE, value);
		}.bind(this);

		if (this._decoder) {
			mpg123Util.setVolume(this._decoder.mh, value);
			setTimeout(resolver, this.VOLUME_CHANGE_TIMEOUT);
		} else {
			reject(null);
		}
	}.bind(this));
};

/**
 * @return {?number} 0..1
 */
StupidPlayer.prototype.getVolume = function() {
	if (this._decoder) {
		return mpg123Util.getVolume(this._decoder.mh);
	} else {
		return null;
	}
};

/**
 * @return {StupidPlayer.State}
 */
StupidPlayer.prototype.getState = function() {
	return this._state;
};

/**
 * @param {string} url
 * @return {Promise.<Format>}
 * @protected
 */
StupidPlayer.prototype._playStream = function(url) {
	return this._request(url)
		.then(function(res) {
			return this._makeDecoder(res);
		}.bind(this));
};

/**
 * @param {string} url
 * @return {Promise.<Response>}
 * @protected
 */
StupidPlayer.prototype._request = function(url) {
	return new Promise(function(resolve, reject) {
		var request = url.indexOf('https') === 0 ? https : http;
		this._readStream = request.get(url, function(res, req) {
			if (url === this._uri) {
				resolve(res);
			} else {
				reject(null);
			}
		}.bind(this));
		this._readStream.on('error', this._onError);
	}.bind(this));
};

/**
 * @param {string} path
 * @return {Promise.<Format>}
 * @protected
 */
StupidPlayer.prototype._playFile = function(path) {
	return new Promise(function(resolve, reject) {
		this._readStream = fs.createReadStream(path);

		this._readStream.on('error', this._onError);

		return this
			._makeDecoder(readStream)
			.then(function(format) {
				resolve(format);
			});
	}.bind(this));
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._play = function(format) {
	if (this._state === StupidPlayer.State.STOP) {
		return;
	}
	this._speaker = new Speaker(format);
	this._pipedDecoder = this._decoder.pipe(this._speaker);
	this._pipedDecoder.on('close', this._onDecoderClosed);
};

/**
 *
 * @param {Response|ReadStream} res
 * @return {Promise.<Format>}
 * @protected
 */
StupidPlayer.prototype._makeDecoder = function(res) {
	this._decoder = new lame.Decoder();
	this._decoder.on('error', this._onError);
	return new Promise(function(resolve, reject) {
		if (this._checkConnect(res, this._onError) && this._state === StupidPlayer.State.PLAY) {// todo need simple method
			res.pipe(this._decoder);

			this._decoder.on('format', function(format) {
				this._onDecoderFormatted(format);
				resolve(format);
			}.bind(this));

		} else {
			this.stop();
		}
	}.bind(this));
};

/**
 *
 * @param res
 * @param callback
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._checkConnect = function(res, callback) {
	var isOk = res.statusCode === 200;
	var isAudio = (res.headers['content-type'].indexOf('audio/mpeg') !== -1);

	if (!isOk || !isAudio) {
		return false;
	}

	return true;
};

/**
 *
 * @param {string} uri
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._isStream = function(uri) {
	return uri.indexOf('http') === 0 || uri.indexOf('https') === 0;
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._deinit = function() {
	this._closeConnection();
	if (this._speaker) {
		this._decoder.removeListener('error', this._onError);
		this._pipedDecoder.removeListener('close', this._onDecoderClosed);

		this._decoder.unpipe();
		this._speaker.end();

		this._speaker = null;
		this._decoder = null;
	}
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._closeConnection = function() {
	if (this._readStream) {
		this._readStream.removeListener('error', this._onError);
		this._destroyConnection();
		this._readStream = null;
	}
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._destroyConnection = function() {
	if (!this._decoder) {
		this._readStream.on('error', function() {});// For catch error closing not oppened socket
	}
	this._readStream.destroy();
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._onDecoderFormatted = function(format) {
	this._play(format);
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._onDecoderClosed = function() {
	this.stop();
};

/**
 *
 * @param {string} error
 * @protected
 */
StupidPlayer.prototype._onError = function(error) {
	this._deinit();
	this._emit(this.EVENT_ERROR, error);
};

/**
 * @param {string} event
 * @param {*?} opt_data
 * @protected
 */
StupidPlayer.prototype._emit = function(event, opt_data) {
//	console.log('stupid-player emit', event);
	this.emit(event, opt_data);
};

/**
 * @type {StupidPlayer.State}
 */
StupidPlayer.prototype._state;

/**
 *
 */
StupidPlayer.prototype._decoder;

/**
 *
 */
StupidPlayer.prototype._speaker;

/**
 *
 */
StupidPlayer.prototype._pipedDecoder;

/**
 * @type {string}
 */
StupidPlayer.prototype._uri;

/**
 * @type {StupidPlayer.ReadStream}
 */
StupidPlayer.prototype._readStream;

/**
 * @const {number}
 */
StupidPlayer.prototype.VOLUME_CHANGE_TIMEOUT = 300;

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_PLAY = 'play';

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_STOP = 'stop';

/**
 * Fired with: {number} volume
 * @const {string}
 */
StupidPlayer.prototype.EVENT_VOLUME_CHANGE = 'volume-change';

/**
 * Fired with: string
 * @const {string}
 */
StupidPlayer.prototype.EVENT_ERROR = 'error';

/**
 * @enum {string}
 */
StupidPlayer.State = {
	PLAY: 'play',
	STOP: 'stop',
	PAUSE: 'pause'
};

/**
 * @type {ClientRequest|ReadStream}
 */
StupidPlayer.ReadStream;

module.exports = StupidPlayer;
<commit_msg>Refactor stupid-player using es6 syntax<commit_after>import { IStupidPlayer as IStupidPlayer } from './interfaces/i-stupid-player';

const Speaker = require('speaker');
const events = require('events');
const fs = require('fs');
const http = require('http');
const https = require('https');
const lame = require('lame');
const mpg123Util = require('node-mpg123-util');
const util = require('util');

/**
 * @constructor
 * @implements {IStupidPlayer}
 */
export class StupidPlayer extends IStupidPlayer {
	constructor() {
		super();

		/**
		 * @type {Object}
		 * @private
		 */
		this._decoder = null;

		/**
		 * @type {Object}
		 * @private
		 */
		this._speaker = null;

		/**
		 * @type {StupidPlayer.ReadStream}
		 * @private
		 */
		this._readStream = null;

		/**
		 * @type {string}
		 * @private
		 */
		this._uri = null;

		/**
		 * @type {StupidPlayer.State}
		 * @private
		 */
		this._state = StupidPlayer.State.STOP;

		/**
		 * @const {number}
		 */
		this.VOLUME_CHANGE_TIMEOUT = 300;

	}

	/**
	 * @override
	 */
	play(uri) {
		super.play(uri);

		this._uri = uri;
		this._deinit();
		this._state = StupidPlayer.State.PLAY;
		this._emit(this.EVENT_PLAY, this._isStream(uri));

		if (this._isStream(uri)) {
			return this._playStream(uri);
		} else {
			return this._playFile(uri);
		}
	}

	/**
	 * @override
	 */
	pause() {
		super.pause();

		return Promise.resolve();
	}

	/**
	 * @override
	 */
	stop() {
		super.stop();

		if (this._state !== StupidPlayer.State.STOP) {
			this._deinit();
			this._state = StupidPlayer.State.STOP;
			this._emit(this.EVENT_STOP);
		}

		return Promise.resolve();
	}

	/**
	 * @param {string} url
	 * @return {Promise.<Format>}
	 * @protected
	 */
	_playStream(url) {
		return this._request(url)
			.then(function(res) {
				return this._makeDecoder(res);
			});
	}

	/**
	 * @param {string} path
	 * @return {Promise.<Format>}
	 * @protected
	 */
	_playFile(path) {
		return new Promise(function(resolve, reject) {
			this._readStream = fs.createReadStream(path);

			this._readStream.on('error', this._onError);

			return this
				._makeDecoder(this._readStream)
				.then(function(format) {
					resolve(format);
				});
		});
	}

	/**
	 * @param {*} format
	 * @protected
	 */
	_play(format) {
		if (this._state === StupidPlayer.State.STOP) {
			return;
		}
		this._speaker = new Speaker(format);
		this._pipedDecoder = this._decoder.pipe(this._speaker);
		this._pipedDecoder.on('close', this._onDecoderClosed);
	}

	/**
	 * @param {string} url
	 * @return {Promise.<Response>}
	 * @protected
	 */
	_request(url) {
		return new Promise(function(resolve, reject) {
			var request = url.indexOf('https') === 0 ? https : http;
			this._readStream = request.get(url, function(res, req) {
				if (url === this._uri) {
					resolve(res);
				} else {
					reject(null);
				}
			}.bind(this));
			this._readStream.on('error', this._onError);
		});
	}

	/**
	 * @param {Response|ReadStream} res
	 * @return {Promise.<Format>}
	 * @protected
	 */
	_makeDecoder(res) {
		this._decoder = new lame.Decoder();
		this._decoder.on('error', this._onError);
		return new Promise(function(resolve, reject) {
			if (this._checkConnect(res, this._onError) && this._state === StupidPlayer.State.PLAY) {// todo need simple method
				res.pipe(this._decoder);

				this._decoder.on('format', function(format) {
					this._onDecoderFormatted(format);
					resolve(format);
				}.bind(this));

			} else {
				this.stop();
			}
		}.bind(this));
	}

	/**
	 * @param res
	 * @return {boolean}
	 * @protected
	 */
	_checkConnect(res) {
		var isOk = res.statusCode === 200;
		var isAudio = (res.headers['content-type'].indexOf('audio/mpeg') !== -1);

		return isOk && isAudio;
	}

	/**
	 * @param {string} uri
	 * @return {boolean}
	 * @protected
	 */
	_isStream(uri) {
		return uri.indexOf('http') === 0 || uri.indexOf('https') === 0;
	}

	/**
	 * @protected
	 */
	_closeConnection() {
		if (this._readStream) {
			this._readStream.removeListener('error', this._onError);
			this._destroyConnection();
			this._readStream = null;
		}
	}

	/**
	 * @protected
	 */
	_deinit() {
		this._closeConnection();
		if (this._speaker) {
			this._decoder.removeListener('error', this._onError);
			this._pipedDecoder.removeListener('close', this._onDecoderClosed);

			this._decoder.unpipe();
			this._speaker.end();

			this._speaker = null;
			this._decoder = null;
		}
	}

	/**
	 * @protected
	 */
	_destroyConnection() {
		if (!this._decoder) {
			this._readStream.on('error', function() {});// For catch error closing not oppened socket
		}
		this._readStream.destroy();
	}

	_emit(event, opt_data) {
	//	console.log('stupid-player emit', event);
		this.emit(event, opt_data);
	}

	/**
	 * @param format
	 * @protected
	 */
	_onDecoderFormatted() {}

	/**
	 * @protected
	 */
	_onDecoderClosed() {}

	/**
	 * @param {string} error
	 * @protected
	 */
	_onError(error) {}

	get volume() {
		if (this._decoder) {
			return mpg123Util.getVolume(this._decoder.mh);
		} else {
			return null;
		}
	}

	set volume(value) {
		return new Promise(function(resolve, reject) {
			var resolver = function() {
				resolve(value);
				this._emit(this.EVENT_VOLUME_CHANGE, value);
			}.bind(this);

			if (this._decoder) {
				mpg123Util.setVolume(this._decoder.mh, value);
				setTimeout(resolver, this.VOLUME_CHANGE_TIMEOUT);
			} else {
				reject(null);
			}
		});
	}

	/**
	 * @override
	 */
	get state() {
		return this._state;
	}

	/**
	 * @typedef {ClientRequest|ReadStream}
	 */
	static ReadStream;
}
<|endoftext|>


<commit_before>//process.env.YENV = 'production';

MAKE.decl('Arch', {

    getLibraries: function() {

        return {
            'bem-bl': {
                type: 'git',
                url: 'git://github.com/bem/bem-bl.git',
                // NOTE: Will be renamed in the nearest future
                treeish: 'i18n'
            }
        };

    }

});
<commit_msg>use branch 0.3 of bem-bl<commit_after>//process.env.YENV = 'production';

MAKE.decl('Arch', {

    getLibraries: function() {

        return {
            'bem-bl': {
                type: 'git',
                url: 'git://github.com/bem/bem-bl.git',
                treeish: '0.3'
            }
        };

    }

});
<|endoftext|>


<commit_before>"use strict";

const Promise = require('bluebird');
const request = Promise.promisifyAll(require("request"));
const cheerio = require('cheerio');
const Food = require("../models/food");
const Menu = require("../models/menu");
const config = require('../config');
const timeHelper = require('../helpers/timeHelper');

var MensaUrl = config.scraper.mensaUrl;
var UniwirtUrl = config.scraper.uniwirtUrl;
var MittagstischUrl = config.scraper.mittagstischUrl;
var PizzeriaUrl = config.scraper.unipizzeriaUrl;

function parseWeek(input, parseFunction) {
    var menus = [];
    for (let day = 0; day < 7; day++) {
        menus[day] = parseFunction(input, day);
    }

    return menus;
}

function getUniwirtWeekPlan() {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseUniwirt));
}

function getUniwirtPlan(day) {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseUniwirt(body, day));
}

function parseUniwirt(html, day) {
    var result = new Menu();

    var $ = cheerio.load(html);
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var $currentDayRows = $("#StandardWrapper").find(".col600 > .col360.noMargin").eq(dayInWeek).find('tr');

    $currentDayRows.each((index, item) => {
        var $tds = $(item).children();
        var name = $tds.eq(0).text();
        name = sanitizeName(name);
        var price = $tds.eq(2).text();

        if (contains(name, true, ["feiertag", "ruhetag", "wir machen pause"])) {
            result.closed = true;
        } else if (!price) {
            result.starters.push(new Food(name));
        } else {
            price = +price.substring(2).replace(',', '.');
            result.mains.push(new Food(name, price));
        }
    });

    if ($("#StandardWrapper").find(".col600 > .col360.noMargin > h3").html().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
    }

    return setErrorOnEmpty(result);
}

function getMensaWeekPlan() {
    return request.getAsync(MensaUrl)
        .then(res => res.body)
        .then(body => parseMensa(body));
}

function parseMensa(html) {
    var result = new Array(7);

    let closedMenu = new Menu();
    closedMenu.closed = true;
    result[5] = result[6] = closedMenu;

    var $ = cheerio.load(html);

    let $weekDates = $('#days').find('.date');
    if ($weekDates.text().indexOf(timeHelper.getMondayDate()) == -1) {
        for (let i = 0; i < 5; i++) {
            let outdatedMenu = new Menu();
            outdatedMenu.outdated = true;
            result[i] = outdatedMenu;
        }
        return result;
    }

    //Get days
    let days = $("#days").find(".day");
    for (let dayInWeek = 0; dayInWeek < 5; dayInWeek++) {
        let menu = new Menu();
        result[dayInWeek] = menu;

        let day = days.eq(dayInWeek);

        //Check if closed
        let firstContentText = day.find(".category-content").eq(0).text();
        if (contains(firstContentText, true, ["geschlossen", "feiertag", "ruhetag"])) {
            menu.closed = true;
            continue;
        }

        let classic1Category = day.find('#category133');
        let classic2Category = day.find('#category134');
        let dailySpecialCategory = day.find('#category247');

        try {
            let classic1Food = createFoodFromMensaCategory(classic1Category);
            menu.mains.push(classic1Food);

            let classic2Food = createFoodFromMensaCategory(classic2Category);
            menu.mains.push(classic2Food);

            let dailySpecialFood = createFoodFromMensaCategory(dailySpecialCategory);
            menu.mains.push(dailySpecialFood);
        } catch (ex) {
            //Do not log error, as it is most likely to be a parsing error, which we do not want to fill the log file
            menu.error = true;
        }

        //Just to be sure
        setErrorOnEmpty(menu);
    }
    return result;
}

function createFoodFromMensaCategory(category) {
    let categoryContent = category.find(".category-content");

    let meals = categoryContent.find("p").eq(0);
    meals.find("br").replaceWith(' ');

    //Names
    let foodNames = [];

    // ugly bugfix, courtesy of behackl
    if (categoryContent.find("p").length > 2) {
        let allContent = categoryContent.find("p")
        for (var i = 0; i < allContent.length-1; i++) {
            foodNames.push(sanitizeName(allContent.eq(i).text()));
	}
    }
    else {
    //Check Soup
    let contents = meals.contents();
    if (!contents.eq(0).is("strong")) {
        //WE HAVE A SUPPE
        foodNames.push(sanitizeName(contents.eq(0).text()));
        contents = contents.slice(1);
    }

    foodNames.push(sanitizeName(contents.text()));
    }

    //Price //more ugly bugfix
    let priceTag = categoryContent.find("p").eq(categoryContent.find("p").length-1);
    let match = priceTag.text().match(/€\s[0-9](,|.)[0-9]+/);

    let priceStr = null;
    if (match != null && match.length > 0) {
        priceStr = match[0].match(/[0-9]+(,|.)[0-9]+/)[0].replace(',', '.');
    }

    let price = +priceStr;
    //isInfo <=> price could not get parsed (or is empty --> 0) and there is only one line of text in .category-content
    var isInfo = (price === 0 || isNaN(price)) && categoryContent.children().length === 1;

    return new Food(foodNames, price, isInfo);
}

function getUniPizzeriaWeekPlan() {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => parseWeek(parseUniPizzeria(body), getUniPizzeriaDayPlan));
}

function getUniPizzeriaPlan(day) {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => {
            var weekMenu = parseUniPizzeria(body);
            return getUniPizzeriaDayPlan(weekMenu, day);
        });
}

function getUniPizzeriaDayPlan(weekMenu, day) {
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var menu = new Menu();

    if (weekMenu.outdated) {
        menu.outdated = true;
        return menu;
    }

    if (dayInWeek > 4) {
        let info = new Food("Kein Mittagsmenü.", null, true);
        menu.mains.push(info);
    } else if (dayInWeek < weekMenu.mains.length) {
        let combinedFood = weekMenu.mains[dayInWeek];

        //Handle holidays (no menu)
        if (contains(combinedFood.name, true, ["feiertag"])) {
            let info = new Food("Kein Mittagsmenü.", null, true);
            menu.mains.push(info);
        } else {

            let splitted = combinedFood.name.split("
");
            let starterExists = splitted.length > 1;

            if (starterExists) {
                //There is a starter
                let name = sanitizeName(splitted[0]);
                let starter = new Food(name);
                menu.starters.push(starter);
            }

            let i = starterExists ? 1 : 0;
            for (; i < splitted.length; i++) {
                let name = sanitizeName(splitted[i]);
                let main = new Food(name, combinedFood.price);
                menu.mains.push(main);
            }
        }
    }

    return setErrorOnEmpty(menu);
}

function parseUniPizzeria(html) {

    var result = new Menu();

    var $ = cheerio.load(html);
    var _days = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'je Mittagsteller € 7,80'];
    var _uniPizzeriaPrice = 7.80;

    var $menuContent = $('[itemprop="articleBody"]');

    if ($menuContent.find('p > strong').text().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
        return result;
    }

    var currentFood = null;
    $menuContent.find('p').each((index, item) => {

        var content = $(item).text().trim();

        // When the content of
 is a weekday, start new food
        if (_checkWeekday(content)) {
            if (currentFood != null) {
                result.mains.push(currentFood);
            }
            currentFood = new Food("", _uniPizzeriaPrice);
        }
        if (currentFood != null) {
            currentFood.name += _normalizeFood(content);
        }

    });

    if (currentFood != null && currentFood.name.length > 0) {
        result.mains.push(currentFood);
    }
    return setErrorOnEmpty(result);

    function _replaceWeekday(str) {
        for (var index in _days) {
            str = str.replace(_days[index], "");
        }
        return str;
    }

    function _checkWeekday(str) {
        var weekday = false;
        for (var index in _days) {
            if (str.indexOf(_days[index]) >= 0) {
                weekday = true;
            }
        }
        return weekday;
    }

    function _normalizeFood(str) {

        str = _replaceWeekday(str);
        str = str.replace(/\*+/, '
');

        return str;
    }

}

function getMittagstischWeekPlan() {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseMittagstisch(body));
}

function parseMittagstisch(html) {
    var result = new Array(7);

    let closedMenu = new Menu();
    closedMenu.closed = true;
    result[5] = result[6] = closedMenu;

    var $ = cheerio.load(html);

    var weekIsOutdated = $('.companyinfo').text().indexOf(timeHelper.getMondayDate()) == -1;

    //Get container divs for single days
    let days = $(".resp-tabs-container > div");

    for (let dayInWeek = 0; dayInWeek < 5; dayInWeek++) {
        let dayMenu = new Menu();
        result[dayInWeek] = dayMenu;

        let dayDatas = days.eq(dayInWeek).find(".daydata");

        //Check if current day is closed
        let isClosed = false;

        for (let d = 0; d < dayDatas.length; d++) {
            let dayData = dayDatas.eq(d);

            if (dayData.hasClass("closed"))
                isClosed = true;
            else if (contains(dayData.text(), true, ["geschlossen", "feiertag", "ruhetag"]))
                isClosed = true;

            if (isClosed)
                break;
        }

        if (isClosed) {
            dayMenu.closed = true;
            continue;
        }

        try {
            parseMittagstischDayMenu(dayDatas, dayMenu);
            //Just to be sure
            setErrorOnEmpty(dayMenu);

            if (!dayMenu.error && weekIsOutdated)
                dayMenu.outdated = true;
        } catch (ex) {
            //Do not log parsing errors. They will simply fill the log
            dayMenu.error = true;
        }
    }

    return result;
}

function parseMittagstischDayMenu(dayDatas, dayMenu) {
    var menu = dayDatas.eq(0);
    var menuRows = menu.find("tr");
    var first = menuRows.first();

    var soupRows = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free")) {
        soupRows.unshift(first);
    }

    soupRows.splice(0, 1);
    for (let s = 0; s < soupRows.length; s++) {
        let soupRow = soupRows.eq(s);
        let name = soupRow.children().first().text();
        name = sanitizeName(name);
        dayMenu.starters.push(new Food(name));
    }

    //Hauptspeisen
    var mainsRows = soupRows.last().next().nextUntil('.free', 'tr');
    //Remove "Hauptspeise" Info, but only if there is more then one entry and the price info does not hold a price
    if (mainsRows.length > 1 && mainsRows.eq(0).children().eq(1).text().search(/[0-9](,|.)?[0-9]*/) == -1)
        mainsRows.splice(0, 1);

    for (let m = 0; m < mainsRows.length; m++) {
        let mainsRow = mainsRows.eq(m);
        let name = mainsRow.children().first().text();
        name = sanitizeName(name);
        let price = (+mainsRow.children().eq(1).text().replace(",", ".")) || 7.4;
        dayMenu.mains.push(new Food(name, price));
    }

    //á la carte
    if (dayDatas.length > 1) {
        var aLaCarte = dayDatas.eq(1);

        let aLaCarteRows = aLaCarte.find("tr");
        first = aLaCarteRows.first();
        var aLaCartes = first.nextUntil('.free', 'tr');

        if (!first.hasClass("free") && aLaCartes.unshift) {
            aLaCartes.unshift(first);
        }

        aLaCartes.splice(0, 1);

        for (let a = 0; a < aLaCartes.length; a++) {
            let entries = aLaCartes.eq(a).children();
            let name = entries.first().text();
            name = sanitizeName(name);
            let price = +entries.eq(1).text().replace(",", ".");
            dayMenu.alacarte.push(new Food(name, price));
        }
    }
}

function isNotBlank(index, element) {
    return element.length !== 0 && element.trim();
}

function contains(str, ignoreCase, searches) {
    if (!str)
        return false;
    if (ignoreCase) {
        str = str.toLowerCase();
    }

    for (var i = 0; i < searches.length; i++) {
        var search = ignoreCase ? searches[i].toLowerCase() : searches[i];
        if (str.includes(search)) {
            return true;
        }
    }
    return false;
}

function setErrorOnEmpty(menu) {
    if (!(menu.starters.length || menu.mains.length || menu.alacarte.length)) {
        menu.error = true;
    }
    return menu;
}

function sanitizeName(val) {
    if (typeof val === "string") {
        val = val.replace(/€\s[0-9](,|.)[0-9]+/, ""); // Replace '€ 00.00'
        val = val.replace(/^[1-9].\s/, ""); // Replace '1. ', '2. '
        val = val.replace(/^[,\.\-\\\? ]+/, "");
        val = val.replace(/[,\.\-\\\? ]+$/, "");
        return val.trim();
    } else if (typeof val === "object" && val.length > 0) {
        for (let i = 0; i < val.length; i++) {
            val[i] = sanitizeName(val[i]);
        }
        return val;
    } else {
        return val;
    }
}

module.exports = {
    getUniwirtPlan: getUniwirtPlan,
    getUniwirtWeekPlan: getUniwirtWeekPlan,
    getMittagstischWeekPlan: getMittagstischWeekPlan,
    getMensaWeekPlan: getMensaWeekPlan,
    getUniPizzeriaPlan: getUniPizzeriaPlan,
    getUniPizzeriaWeekPlan: getUniPizzeriaWeekPlan
};
<commit_msg>escape . in regex; allow e/E for €<commit_after>"use strict";

const Promise = require('bluebird');
const request = Promise.promisifyAll(require("request"));
const cheerio = require('cheerio');
const Food = require("../models/food");
const Menu = require("../models/menu");
const config = require('../config');
const timeHelper = require('../helpers/timeHelper');

var MensaUrl = config.scraper.mensaUrl;
var UniwirtUrl = config.scraper.uniwirtUrl;
var MittagstischUrl = config.scraper.mittagstischUrl;
var PizzeriaUrl = config.scraper.unipizzeriaUrl;

function parseWeek(input, parseFunction) {
    var menus = [];
    for (let day = 0; day < 7; day++) {
        menus[day] = parseFunction(input, day);
    }

    return menus;
}

function getUniwirtWeekPlan() {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseUniwirt));
}

function getUniwirtPlan(day) {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseUniwirt(body, day));
}

function parseUniwirt(html, day) {
    var result = new Menu();

    var $ = cheerio.load(html);
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var $currentDayRows = $("#StandardWrapper").find(".col600 > .col360.noMargin").eq(dayInWeek).find('tr');

    $currentDayRows.each((index, item) => {
        var $tds = $(item).children();
        var name = $tds.eq(0).text();
        name = sanitizeName(name);
        var price = $tds.eq(2).text();

        if (contains(name, true, ["feiertag", "ruhetag", "wir machen pause"])) {
            result.closed = true;
        } else if (!price) {
            result.starters.push(new Food(name));
        } else {
            price = +price.substring(2).replace(',', '.');
            result.mains.push(new Food(name, price));
        }
    });

    if ($("#StandardWrapper").find(".col600 > .col360.noMargin > h3").html().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
    }

    return setErrorOnEmpty(result);
}

function getMensaWeekPlan() {
    return request.getAsync(MensaUrl)
        .then(res => res.body)
        .then(body => parseMensa(body));
}

function parseMensa(html) {
    var result = new Array(7);

    let closedMenu = new Menu();
    closedMenu.closed = true;
    result[5] = result[6] = closedMenu;

    var $ = cheerio.load(html);

    let $weekDates = $('#days').find('.date');
    if ($weekDates.text().indexOf(timeHelper.getMondayDate()) == -1) {
        for (let i = 0; i < 5; i++) {
            let outdatedMenu = new Menu();
            outdatedMenu.outdated = true;
            result[i] = outdatedMenu;
        }
        return result;
    }

    //Get days
    let days = $("#days").find(".day");
    for (let dayInWeek = 0; dayInWeek < 5; dayInWeek++) {
        let menu = new Menu();
        result[dayInWeek] = menu;

        let day = days.eq(dayInWeek);

        //Check if closed
        let firstContentText = day.find(".category-content").eq(0).text();
        if (contains(firstContentText, true, ["geschlossen", "feiertag", "ruhetag"])) {
            menu.closed = true;
            continue;
        }

        let classic1Category = day.find('#category133');
        let classic2Category = day.find('#category134');
        let dailySpecialCategory = day.find('#category247');

        try {
            let classic1Food = createFoodFromMensaCategory(classic1Category);
            menu.mains.push(classic1Food);

            let classic2Food = createFoodFromMensaCategory(classic2Category);
            menu.mains.push(classic2Food);

            let dailySpecialFood = createFoodFromMensaCategory(dailySpecialCategory);
            menu.mains.push(dailySpecialFood);
        } catch (ex) {
            //Do not log error, as it is most likely to be a parsing error, which we do not want to fill the log file
            menu.error = true;
        }

        //Just to be sure
        setErrorOnEmpty(menu);
    }
    return result;
}

function createFoodFromMensaCategory(category) {
    let categoryContent = category.find(".category-content");

    let meals = categoryContent.find("p").eq(0);
    meals.find("br").replaceWith(' ');

    //Names
    let foodNames = [];

    // ugly bugfix, courtesy of behackl
    if (categoryContent.find("p").length > 2) {
        let allContent = categoryContent.find("p")
        for (var i = 0; i < allContent.length-1; i++) {
            foodNames.push(sanitizeName(allContent.eq(i).text()));
	}
    }
    else {
    //Check Soup
    let contents = meals.contents();
    if (!contents.eq(0).is("strong")) {
        //WE HAVE A SUPPE
        foodNames.push(sanitizeName(contents.eq(0).text()));
        contents = contents.slice(1);
    }

    foodNames.push(sanitizeName(contents.text()));
    }

    //Price //more ugly bugfix
    let priceTag = categoryContent.find("p").eq(categoryContent.find("p").length-1);
    let match = priceTag.text().match(/(€|e|E)\s[0-9](,|\.)[0-9]+/);

    let priceStr = null;
    if (match != null && match.length > 0) {
        priceStr = match[0].match(/[0-9]+(,|\.)[0-9]+/)[0].replace(',', '.');
    }

    let price = +priceStr;
    //isInfo <=> price could not get parsed (or is empty --> 0) and there is only one line of text in .category-content
    var isInfo = (price === 0 || isNaN(price)) && categoryContent.children().length === 1;

    return new Food(foodNames, price, isInfo);
}

function getUniPizzeriaWeekPlan() {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => parseWeek(parseUniPizzeria(body), getUniPizzeriaDayPlan));
}

function getUniPizzeriaPlan(day) {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => {
            var weekMenu = parseUniPizzeria(body);
            return getUniPizzeriaDayPlan(weekMenu, day);
        });
}

function getUniPizzeriaDayPlan(weekMenu, day) {
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var menu = new Menu();

    if (weekMenu.outdated) {
        menu.outdated = true;
        return menu;
    }

    if (dayInWeek > 4) {
        let info = new Food("Kein Mittagsmenü.", null, true);
        menu.mains.push(info);
    } else if (dayInWeek < weekMenu.mains.length) {
        let combinedFood = weekMenu.mains[dayInWeek];

        //Handle holidays (no menu)
        if (contains(combinedFood.name, true, ["feiertag"])) {
            let info = new Food("Kein Mittagsmenü.", null, true);
            menu.mains.push(info);
        } else {

            let splitted = combinedFood.name.split("
");
            let starterExists = splitted.length > 1;

            if (starterExists) {
                //There is a starter
                let name = sanitizeName(splitted[0]);
                let starter = new Food(name);
                menu.starters.push(starter);
            }

            let i = starterExists ? 1 : 0;
            for (; i < splitted.length; i++) {
                let name = sanitizeName(splitted[i]);
                let main = new Food(name, combinedFood.price);
                menu.mains.push(main);
            }
        }
    }

    return setErrorOnEmpty(menu);
}

function parseUniPizzeria(html) {

    var result = new Menu();

    var $ = cheerio.load(html);
    var _days = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'je Mittagsteller € 7,80'];
    var _uniPizzeriaPrice = 7.80;

    var $menuContent = $('[itemprop="articleBody"]');

    if ($menuContent.find('p > strong').text().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
        return result;
    }

    var currentFood = null;
    $menuContent.find('p').each((index, item) => {

        var content = $(item).text().trim();

        // When the content of
 is a weekday, start new food
        if (_checkWeekday(content)) {
            if (currentFood != null) {
                result.mains.push(currentFood);
            }
            currentFood = new Food("", _uniPizzeriaPrice);
        }
        if (currentFood != null) {
            currentFood.name += _normalizeFood(content);
        }

    });

    if (currentFood != null && currentFood.name.length > 0) {
        result.mains.push(currentFood);
    }
    return setErrorOnEmpty(result);

    function _replaceWeekday(str) {
        for (var index in _days) {
            str = str.replace(_days[index], "");
        }
        return str;
    }

    function _checkWeekday(str) {
        var weekday = false;
        for (var index in _days) {
            if (str.indexOf(_days[index]) >= 0) {
                weekday = true;
            }
        }
        return weekday;
    }

    function _normalizeFood(str) {

        str = _replaceWeekday(str);
        str = str.replace(/\*+/, '
');

        return str;
    }

}

function getMittagstischWeekPlan() {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseMittagstisch(body));
}

function parseMittagstisch(html) {
    var result = new Array(7);

    let closedMenu = new Menu();
    closedMenu.closed = true;
    result[5] = result[6] = closedMenu;

    var $ = cheerio.load(html);

    var weekIsOutdated = $('.companyinfo').text().indexOf(timeHelper.getMondayDate()) == -1;

    //Get container divs for single days
    let days = $(".resp-tabs-container > div");

    for (let dayInWeek = 0; dayInWeek < 5; dayInWeek++) {
        let dayMenu = new Menu();
        result[dayInWeek] = dayMenu;

        let dayDatas = days.eq(dayInWeek).find(".daydata");

        //Check if current day is closed
        let isClosed = false;

        for (let d = 0; d < dayDatas.length; d++) {
            let dayData = dayDatas.eq(d);

            if (dayData.hasClass("closed"))
                isClosed = true;
            else if (contains(dayData.text(), true, ["geschlossen", "feiertag", "ruhetag"]))
                isClosed = true;

            if (isClosed)
                break;
        }

        if (isClosed) {
            dayMenu.closed = true;
            continue;
        }

        try {
            parseMittagstischDayMenu(dayDatas, dayMenu);
            //Just to be sure
            setErrorOnEmpty(dayMenu);

            if (!dayMenu.error && weekIsOutdated)
                dayMenu.outdated = true;
        } catch (ex) {
            //Do not log parsing errors. They will simply fill the log
            dayMenu.error = true;
        }
    }

    return result;
}

function parseMittagstischDayMenu(dayDatas, dayMenu) {
    var menu = dayDatas.eq(0);
    var menuRows = menu.find("tr");
    var first = menuRows.first();

    var soupRows = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free")) {
        soupRows.unshift(first);
    }

    soupRows.splice(0, 1);
    for (let s = 0; s < soupRows.length; s++) {
        let soupRow = soupRows.eq(s);
        let name = soupRow.children().first().text();
        name = sanitizeName(name);
        dayMenu.starters.push(new Food(name));
    }

    //Hauptspeisen
    var mainsRows = soupRows.last().next().nextUntil('.free', 'tr');
    //Remove "Hauptspeise" Info, but only if there is more then one entry and the price info does not hold a price
    if (mainsRows.length > 1 && mainsRows.eq(0).children().eq(1).text().search(/[0-9](,|.)?[0-9]*/) == -1)
        mainsRows.splice(0, 1);

    for (let m = 0; m < mainsRows.length; m++) {
        let mainsRow = mainsRows.eq(m);
        let name = mainsRow.children().first().text();
        name = sanitizeName(name);
        let price = (+mainsRow.children().eq(1).text().replace(",", ".")) || 7.4;
        dayMenu.mains.push(new Food(name, price));
    }

    //á la carte
    if (dayDatas.length > 1) {
        var aLaCarte = dayDatas.eq(1);

        let aLaCarteRows = aLaCarte.find("tr");
        first = aLaCarteRows.first();
        var aLaCartes = first.nextUntil('.free', 'tr');

        if (!first.hasClass("free") && aLaCartes.unshift) {
            aLaCartes.unshift(first);
        }

        aLaCartes.splice(0, 1);

        for (let a = 0; a < aLaCartes.length; a++) {
            let entries = aLaCartes.eq(a).children();
            let name = entries.first().text();
            name = sanitizeName(name);
            let price = +entries.eq(1).text().replace(",", ".");
            dayMenu.alacarte.push(new Food(name, price));
        }
    }
}

function isNotBlank(index, element) {
    return element.length !== 0 && element.trim();
}

function contains(str, ignoreCase, searches) {
    if (!str)
        return false;
    if (ignoreCase) {
        str = str.toLowerCase();
    }

    for (var i = 0; i < searches.length; i++) {
        var search = ignoreCase ? searches[i].toLowerCase() : searches[i];
        if (str.includes(search)) {
            return true;
        }
    }
    return false;
}

function setErrorOnEmpty(menu) {
    if (!(menu.starters.length || menu.mains.length || menu.alacarte.length)) {
        menu.error = true;
    }
    return menu;
}

function sanitizeName(val) {
    if (typeof val === "string") {
        val = val.replace(/€\s[0-9](,|.)[0-9]+/, ""); // Replace '€ 00.00'
        val = val.replace(/^[1-9].\s/, ""); // Replace '1. ', '2. '
        val = val.replace(/^[,\.\-\\\? ]+/, "");
        val = val.replace(/[,\.\-\\\? ]+$/, "");
        return val.trim();
    } else if (typeof val === "object" && val.length > 0) {
        for (let i = 0; i < val.length; i++) {
            val[i] = sanitizeName(val[i]);
        }
        return val;
    } else {
        return val;
    }
}

module.exports = {
    getUniwirtPlan: getUniwirtPlan,
    getUniwirtWeekPlan: getUniwirtWeekPlan,
    getMittagstischWeekPlan: getMittagstischWeekPlan,
    getMensaWeekPlan: getMensaWeekPlan,
    getUniPizzeriaPlan: getUniPizzeriaPlan,
    getUniPizzeriaWeekPlan: getUniPizzeriaWeekPlan
};
<|endoftext|>