e-ucm/rage-analytics-backend

View on GitHub
lib/csvToXAPI.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

var objectId = 'https://w3id.org/xapi/adb/objectid/';

var verbIds = {
    initialized: 'https://w3id.org/xapi/adb/verbs/initialized',
    progressed: 'http://adlnet.gov/expapi/verbs/progressed',
    completed: 'http://adlnet.gov/expapi/verbs/completed',
    accessed: 'https://w3id.org/xapi/seriousgames/verbs/accessed',
    skipped: 'http://id.tincanapi.com/verb/skipped',
    selected: 'https://w3id.org/xapi/adb/verbs/selected',
    unlocked: 'https://w3id.org/xapi/seriousgames/verbs/unlocked',
    interacted: 'http://adlnet.gov/expapi/verbs/interacted',
    used: 'https://w3id.org/xapi/seriousgames/verbs/used',
    unknown: 'https://w3id.org/xapi/seriousgames/verbs/unknown'
};

var objectIds = {
    // Completable
    game: 'https://w3id.org/xapi/seriousgames/activity-types/serious-game',
    session: 'https://w3id.org/xapi/seriousgames/activity-types/session',
    level: 'https://w3id.org/xapi/seriousgames/activity-types/level',
    quest: 'https://w3id.org/xapi/seriousgames/activity-types/quest',
    stage: 'https://w3id.org/xapi/seriousgames/activity-types/stage',
    combat: 'https://w3id.org/xapi/seriousgames/activity-types/combat',
    storynode: 'https://w3id.org/xapi/seriousgames/activity-types/story-node',
    race: 'https://w3id.org/xapi/seriousgames/activity-types/race',
    completable: 'https://w3id.org/xapi/seriousgames/activity-types/completable',

    // Acceesible
    screen: 'https://w3id.org/xapi/seriousgames/activity-types/screen',
    area: 'https://w3id.org/xapi/seriousgames/activity-types/area',
    zone: 'https://w3id.org/xapi/seriousgames/activity-types/zone',
    cutscene: 'https://w3id.org/xapi/seriousgames/activity-types/cutscene',
    accessible: 'https://w3id.org/xapi/seriousgames/activity-types/accessible',

    // Alternative
    question: 'http://adlnet.gov/expapi/activities/question',
    menu: 'https://w3id.org/xapi/seriousgames/activity-types/menu',
    dialog: 'https://w3id.org/xapi/seriousgames/activity-types/dialog-tree',
    path: 'https://w3id.org/xapi/seriousgames/activity-types/path',
    arena: 'https://w3id.org/xapi/seriousgames/activity-types/arena',
    alternative: 'https://w3id.org/xapi/seriousgames/activity-types/alternative',

    // GameObject
    enemy: 'https://w3id.org/xapi/seriousgames/activity-types/enemy',
    npc: 'https://w3id.org/xapi/seriousgames/activity-types/non-player-character',
    item: 'https://w3id.org/xapi/seriousgames/activity-types/item',
    gameobject: 'https://w3id.org/xapi/seriousgames/activity-types/game-object',

    unknown: 'https://w3id.org/xapi/seriousgames/unknown-type/unknown'

};


var parseCSV = function (trace) {
    trace = trace.replace('\r', '');
    var list = [];

    var escape = false;
    var start = 0;
    for (var i = 0; i < trace.length; i++) {
        switch (trace.charAt(i)) {
            case '\\': {
                escape = true;
                break;
            }
            case ',': {
                if (!escape) {
                    var subs = trace.substr(start, i - start);
                    list.push(subs.replace('\\,', ','));
                    start = i + 1;
                } else {
                    escape = false;
                }
                break;
            }
            default: {
                break;
            }
        }
    }
    list.push(trace.substring(start).replace('\\,', ','));

    return list;
};

var createObject = function (parts) {
    var obj = {};

    var type = parts[3];
    if (!type) {
        type = 'unknown';
    }
    var id = parts[4];

    var typeKey = objectIds[type];
    if (!typeKey) {
        console.info('Unknown definition el object (target) type: ' + type);
        typeKey = type;
    }

    obj.id = objectId + id;

    obj.definition = {
        type: typeKey
    };

    return obj;
};

var createVerb = function (event) {

    if (!event) {
        event = 'unknown';
    }

    var verb = {};

    var id = verbIds[event];

    if (!id) {
        console.info('Unknown definition el verb event: ' + event);
        id = event;
    }

    verb.id = id;

    return verb;
};

/**
 * Given a csv trace, returns the xAPI statement (serious game model)
 * @param statement, trace
 * @returns
 */
var toStatement = function (statement, trace) {

    var parts = parseCSV(trace);

    var username = parts[0];

    var timestamp = new Date(Number(parts[1])).toISOString();

    statement.timestamp = timestamp;

    var actor = {
        account: {
            homePage: 'http://localhost:3000/',
            name: 'offline'
        },
        name: username
    };

    statement.actor = actor;

    statement.verb = createVerb(parts[2]);

    statement.object = createObject(parts);

    if (parts.length > 5) {
        // Parse extensions

        var extCount = parts.length - 5;
        if (extCount > 0 && extCount % 2 === 0) {
            var extensions = {};
            statement.result = extensions;
            // Extensions come in <key, value> pairs

            for (var i = 5; i < parts.length; i += 2) {
                var key = parts[i];
                var value = parts[i + 1];
                if (!key || !value) {
                } else if (key === 'score') {

                    var valueResult = parseFloat(value);

                    extensions.score = {
                        raw: valueResult
                    };
                } else if (key === 'success') {
                    extensions.success = value === 'true';
                } else if (key === 'completion') {
                    extensions.completion = value === 'true';
                } else if (key === 'response') {
                    extensions.response = value;
                } else {
                    if (!extensions.extensions) {
                        extensions.extensions = {};
                    }
                    if (key === 'health') {

                        var valueResult2 = parseFloat(value);

                        extensions.extensions.health = valueResult2;
                    } else if (key === 'progress') {

                        var valueResult3 = parseFloat(value);

                        extensions.extensions.progress = valueResult3;
                    } else {
                        if (value === 'true') {
                            extensions.extensions[key] = true;
                        } else if (value === 'false') {
                            extensions.extensions[key] = false;
                        } else if (isNaN(value)) {
                            extensions.extensions[key] = value;
                        } else if (value) {
                            extensions.extensions[key] = Number(value);
                        } else {
                            extensions.extensions[key] = value;
                        }
                    }
                }
            }
        }
    }
};


/**
 *
 * Given a trace in CSV, it truns the trace into an xAPI statement..
 *
 * The format of the returned data is follows the xAPI statement format, using the Serious Games model.
 * @param CSV trace as a CSV string.
 */
module.exports = function (trace) {

    var statement = {};

    var error = toStatement(statement, trace);
    if (!error) {
        return {
            statement: statement
        };
    }

    return {
        error: error
    };
};