517 lines
19 KiB
JavaScript
517 lines
19 KiB
JavaScript
/**
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; under version 2
|
|
* of the License (non-upgradable).
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright (c) 2017 (original work) Open Assessment Technologies SA ;
|
|
*/
|
|
/**
|
|
* Basic helper that is intended to generate outcomes processing rules for a test model.
|
|
*
|
|
* @author Jean-Sébastien Conan <jean-sebastien@taotesting.com>
|
|
*/
|
|
define([
|
|
'lodash',
|
|
'taoQtiTest/controller/creator/helpers/outcomeValidator',
|
|
'taoQtiTest/controller/creator/helpers/qtiElement',
|
|
'taoQtiTest/controller/creator/helpers/baseType',
|
|
'taoQtiTest/controller/creator/helpers/cardinality'
|
|
], function (_, outcomeValidator, qtiElementHelper, baseTypeHelper, cardinalityHelper) {
|
|
'use strict';
|
|
|
|
var processingRuleHelper = {
|
|
/**
|
|
* Creates a basic processing rule
|
|
* @param {String} type
|
|
* @param {String} [identifier]
|
|
* @param {Array|Object} [expression]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the type is empty or is not a string
|
|
* @throws {TypeError} if the identifier is not valid
|
|
* @throws {TypeError} if the expression does not contain valid QTI elements
|
|
*/
|
|
create: function create(type, identifier, expression) {
|
|
var processingRule = qtiElementHelper.create(type, identifier && validateIdentifier(identifier));
|
|
|
|
if (expression) {
|
|
processingRuleHelper.setExpression(processingRule, expression);
|
|
}
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Sets an expression to a processing rule
|
|
* @param {Object} processingRule
|
|
* @param {Object|Array} expression
|
|
* @throws {TypeError} if the expression does not contain valid QTI elements
|
|
*/
|
|
setExpression: function setExpression(processingRule, expression) {
|
|
if (processingRule) {
|
|
if (_.isArray(expression)) {
|
|
if (processingRule.expression) {
|
|
processingRule.expression = null;
|
|
}
|
|
processingRule.expressions = validateOutcomeList(expression);
|
|
} else {
|
|
if (processingRule.expressions) {
|
|
processingRule.expressions = null;
|
|
}
|
|
if (expression) {
|
|
processingRule.expression = validateOutcome(expression);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds an expression to a processing rule
|
|
* @param {Object} processingRule
|
|
* @param {Object|Array} expression
|
|
* @throws {TypeError} if the expression does not contain valid QTI elements
|
|
*/
|
|
addExpression: function addExpression(processingRule, expression) {
|
|
if (processingRule && expression) {
|
|
if (processingRule.expression) {
|
|
processingRule.expressions = forceArray(processingRule.expression);
|
|
processingRule.expression = null;
|
|
}
|
|
|
|
if (_.isArray(expression)) {
|
|
processingRule.expressions = forceArray(processingRule.expressions).concat(validateOutcomeList(expression));
|
|
} else {
|
|
if (processingRule.expressions) {
|
|
processingRule.expressions.push(expression);
|
|
} else {
|
|
processingRule.expression = validateOutcome(expression);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a `setOutcomeValue` rule
|
|
* @param {String} identifier
|
|
* @param {Object|Array} [expression]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the identifier is empty or is not a string
|
|
* @throws {TypeError} if the expression does not contain valid QTI elements
|
|
*/
|
|
setOutcomeValue: function setOutcomeValue(identifier, expression) {
|
|
return processingRuleHelper.create('setOutcomeValue', validateIdentifier(identifier), expression);
|
|
},
|
|
|
|
/**
|
|
* Creates a `gte` rule
|
|
* @param {Object|Array} left - the left operand
|
|
* @param {Object|Array} right - the right operand
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the left and right operands are not valid QTI elements
|
|
*/
|
|
gte: function gte(left, right) {
|
|
return binaryOperator('gte', left, right);
|
|
},
|
|
|
|
/**
|
|
* Creates a `lte` rule
|
|
* @param {Object|Array} left - the left operand
|
|
* @param {Object|Array} right - the right operand
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the left and right operands are not valid QTI elements
|
|
*/
|
|
lte: function lte(left, right) {
|
|
return binaryOperator('lte', left, right);
|
|
},
|
|
|
|
/**
|
|
* Creates a `divide` rule
|
|
* @param {Object|Array} left - the left operand
|
|
* @param {Object|Array} right - the right operand
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the left and right operands are not valid QTI elements
|
|
*/
|
|
divide: function divide(left, right) {
|
|
return binaryOperator('divide', left, right);
|
|
},
|
|
|
|
/**
|
|
* Creates a `sum` rule
|
|
* @param {Object|Array} terms
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the terms are not valid QTI elements
|
|
*/
|
|
sum: function sum(terms) {
|
|
var processingRule = processingRuleHelper.create('sum', null, forceArray(terms));
|
|
|
|
processingRule.minOperands = 1;
|
|
processingRule.maxOperands = -1;
|
|
processingRule.acceptedCardinalities = [cardinalityHelper.SINGLE, cardinalityHelper.MULTIPLE, cardinalityHelper.ORDERED];
|
|
processingRule.acceptedBaseTypes = [baseTypeHelper.INTEGER, baseTypeHelper.FLOAT];
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `testVariables` rule
|
|
* @param {String} identifier
|
|
* @param {String|Number} [type]
|
|
* @param {String} weightIdentifier
|
|
* @param {String|String[]} [includeCategories]
|
|
* @param {String|String[]} [excludeCategories]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the identifier is empty or is not a string
|
|
*/
|
|
testVariables: function testVariables(identifier, type, weightIdentifier, includeCategories, excludeCategories) {
|
|
var processingRule = processingRuleHelper.create('testVariables');
|
|
|
|
processingRule.variableIdentifier = validateIdentifier(identifier);
|
|
processingRule.baseType = baseTypeHelper.getValid(type);
|
|
addWeightIdentifier(processingRule, weightIdentifier);
|
|
addSectionIdentifier(processingRule, '');
|
|
addCategories(processingRule, includeCategories, excludeCategories);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `outcomeMaximum` rule
|
|
* @param {String} identifier
|
|
* @param {String} weightIdentifier
|
|
* @param {String|String[]} [includeCategories]
|
|
* @param {String|String[]} [excludeCategories]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the identifier is empty or is not a string
|
|
*/
|
|
outcomeMaximum: function outcomeMaximum(identifier, weightIdentifier, includeCategories, excludeCategories) {
|
|
var processingRule = processingRuleHelper.create('outcomeMaximum');
|
|
|
|
processingRule.outcomeIdentifier = validateIdentifier(identifier);
|
|
|
|
addWeightIdentifier(processingRule, weightIdentifier);
|
|
addSectionIdentifier(processingRule, '');
|
|
addCategories(processingRule, includeCategories, excludeCategories);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `numberPresented` rule
|
|
* @param {String|String[]} [includeCategories]
|
|
* @param {String|String[]} [excludeCategories]
|
|
* @returns {Object}
|
|
*/
|
|
numberPresented: function numberPresented(includeCategories, excludeCategories) {
|
|
var processingRule = processingRuleHelper.create('numberPresented');
|
|
|
|
addSectionIdentifier(processingRule, '');
|
|
addCategories(processingRule, includeCategories, excludeCategories);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `baseValue` rule
|
|
* @param {*} [value]
|
|
* @param {String|Number} [type]
|
|
* @returns {Object}
|
|
*/
|
|
baseValue: function baseValue(value, type) {
|
|
var processingRule = processingRuleHelper.create('baseValue');
|
|
|
|
processingRule.baseType = baseTypeHelper.getValid(type, baseTypeHelper.FLOAT);
|
|
processingRule.value = baseTypeHelper.getValue(processingRule.baseType, value);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
** Creates a `variable` rule
|
|
* @param {String} identifier
|
|
* @param {String} [weightIdentifier]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the identifier is not valid
|
|
* @throws {TypeError} if the weight identifier is not valid
|
|
*/
|
|
variable: function variable(identifier, weightIdentifier) {
|
|
var processingRule = processingRuleHelper.create('variable', validateIdentifier(identifier));
|
|
|
|
addWeightIdentifier(processingRule, weightIdentifier);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `match` rule
|
|
* @param {Object|Array} left - the left operand
|
|
* @param {Object|Array} right - the right operand
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the left and right operands are not valid QTI elements
|
|
*/
|
|
match: function match(left, right) {
|
|
return binaryOperator('match', left, right, cardinalityHelper.SAME, cardinalityHelper.SAME);
|
|
},
|
|
|
|
/**
|
|
* Creates a `isNull` rule
|
|
* @param {Object|Array} expression - the operand
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the operand is not valid QTI element
|
|
*/
|
|
isNull: function isNull(expression) {
|
|
return unaryOperator('isNull', expression, baseTypeHelper.ANY, cardinalityHelper.ANY);
|
|
},
|
|
|
|
/**
|
|
* Creates a `outcomeCondition` rule
|
|
* @param {Object} outcomeIf
|
|
* @param {Object} outcomeElse
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the outcomeIf and outcomeElse operands are not valid QTI elements
|
|
*/
|
|
outcomeCondition: function outcomeCondition(outcomeIf, outcomeElse) {
|
|
var processingRule = processingRuleHelper.create('outcomeCondition');
|
|
|
|
if (!outcomeValidator.validateOutcome(outcomeIf, false, 'outcomeIf')) {
|
|
throw new TypeError('You must provide a valid outcomeIf element!');
|
|
}
|
|
|
|
if (outcomeElse && !outcomeValidator.validateOutcome(outcomeElse, false, 'outcomeElse')) {
|
|
throw new TypeError('You must provide a valid outcomeElse element!');
|
|
}
|
|
|
|
processingRule.outcomeIf = outcomeIf;
|
|
processingRule.outcomeElseIfs = [];
|
|
|
|
if (outcomeElse) {
|
|
processingRule.outcomeElse = outcomeElse;
|
|
}
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `outcomeIf` rule
|
|
* @param {Object} expression
|
|
* @param {Object|Object[]} instruction
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the expression and instruction operands are not valid QTI elements
|
|
*/
|
|
outcomeIf: function outcomeIf(expression, instruction) {
|
|
var processingRule = processingRuleHelper.create('outcomeIf');
|
|
|
|
if (!_.isArray(instruction)) {
|
|
instruction = [instruction];
|
|
}
|
|
|
|
processingRule.expression = validateOutcome(expression);
|
|
processingRule.outcomeRules = validateOutcomeList(instruction);
|
|
|
|
return processingRule;
|
|
},
|
|
|
|
/**
|
|
* Creates a `outcomeElse` rule
|
|
* @param {Object|Object[]} instruction
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the instruction is not a valid QTI element
|
|
*/
|
|
outcomeElse: function outcomeElse(instruction) {
|
|
var processingRule = processingRuleHelper.create('outcomeElse');
|
|
|
|
if (!_.isArray(instruction)) {
|
|
instruction = [instruction];
|
|
}
|
|
|
|
processingRule.outcomeRules = validateOutcomeList(instruction);
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Creates a unary operator rule
|
|
* @param {String} type - The rule type
|
|
* @param {Object|Array} expression - The operand
|
|
* @param {Number|Array} [baseType] - The accepted base type
|
|
* @param {Number|Array} [cardinality] - The accepted cardinality
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the type is empty or is not a string
|
|
* @throws {TypeError} if the operand is not valid QTI element
|
|
*/
|
|
function unaryOperator(type, expression, baseType, cardinality) {
|
|
var processingRule = processingRuleHelper.create(type, null, [expression]);
|
|
|
|
processingRule.minOperands = 1;
|
|
processingRule.maxOperands = 1;
|
|
|
|
addTypeAndCardinality(processingRule, baseType, cardinality);
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Creates a binary operator rule
|
|
* @param {String} type - The rule type
|
|
* @param {Object|Array} left - The left operand
|
|
* @param {Object|Array} right - The right operand
|
|
* @param {Number|Array} [baseType] - The accepted base type
|
|
* @param {Number|Array} [cardinality] - The accepted cardinality
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the type is empty or is not a string
|
|
* @throws {TypeError} if the left and right operands are not valid QTI elements
|
|
*/
|
|
function binaryOperator(type, left, right, baseType, cardinality) {
|
|
var processingRule = processingRuleHelper.create(type, null, [left, right]);
|
|
|
|
processingRule.minOperands = 2;
|
|
processingRule.maxOperands = 2;
|
|
|
|
addTypeAndCardinality(processingRule, baseType, cardinality);
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Appends the base type and the cardinality on a processing rule
|
|
* @param {Object} processingRule
|
|
* @param {Number|Array} [baseType] - The accepted base type
|
|
* @param {Number|Array} [cardinality] - The accepted cardinality
|
|
* @returns {Object}
|
|
*/
|
|
function addTypeAndCardinality(processingRule, baseType, cardinality) {
|
|
if (_.isUndefined(baseType)) {
|
|
baseType = [baseTypeHelper.INTEGER, baseTypeHelper.FLOAT];
|
|
}
|
|
|
|
if (_.isUndefined(cardinality)) {
|
|
cardinality = [cardinalityHelper.SINGLE];
|
|
}
|
|
|
|
processingRule.acceptedCardinalities = forceArray(cardinality);
|
|
processingRule.acceptedBaseTypes = forceArray(baseType);
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Extends a processing rule with categories
|
|
* @param {Object} processingRule
|
|
* @param {Array|String} [includeCategories]
|
|
* @param {Array|String} [excludeCategories]
|
|
* @returns {Object}
|
|
*/
|
|
function addCategories(processingRule, includeCategories, excludeCategories) {
|
|
processingRule.includeCategories = forceArray(includeCategories);
|
|
processingRule.excludeCategories = forceArray(excludeCategories);
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Appends the section identifier on a processing rule
|
|
* @param {Object} processingRule
|
|
* @param {String} [sectionIdentifier]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the weight identifier is not valid
|
|
*/
|
|
function addSectionIdentifier(processingRule, sectionIdentifier) {
|
|
if (sectionIdentifier) {
|
|
if (!outcomeValidator.validateIdentifier(sectionIdentifier)) {
|
|
throw new TypeError('You must provide a valid weight identifier!');
|
|
}
|
|
processingRule.sectionIdentifier = sectionIdentifier;
|
|
} else {
|
|
processingRule.sectionIdentifier = '';
|
|
}
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Appends the weight identifier on a processing rule
|
|
* @param {Object} processingRule
|
|
* @param {String} [weightIdentifier]
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the weight identifier is not valid
|
|
*/
|
|
function addWeightIdentifier(processingRule, weightIdentifier) {
|
|
if (weightIdentifier) {
|
|
if (!outcomeValidator.validateIdentifier(weightIdentifier)) {
|
|
throw new TypeError('You must provide a valid weight identifier!');
|
|
}
|
|
processingRule.weightIdentifier = weightIdentifier;
|
|
} else {
|
|
processingRule.weightIdentifier = '';
|
|
}
|
|
|
|
return processingRule;
|
|
}
|
|
|
|
/**
|
|
* Validates an identifier
|
|
* @param {String} identifier
|
|
* @returns {String}
|
|
* @throws {TypeError} if the identifier is not valid
|
|
*/
|
|
function validateIdentifier(identifier) {
|
|
if (!outcomeValidator.validateIdentifier(identifier)) {
|
|
throw new TypeError('You must provide a valid identifier!');
|
|
}
|
|
return identifier;
|
|
}
|
|
|
|
/**
|
|
* Validates an outcome
|
|
* @param {Object} outcome
|
|
* @returns {Object}
|
|
* @throws {TypeError} if the outcome is not valid
|
|
*/
|
|
function validateOutcome(outcome) {
|
|
if (!outcomeValidator.validateOutcome(outcome)) {
|
|
throw new TypeError('You must provide a valid QTI element!');
|
|
}
|
|
return outcome;
|
|
}
|
|
|
|
/**
|
|
* Validates a list of outcomes
|
|
* @param {Array} outcomes
|
|
* @returns {Array}
|
|
* @throws {TypeError} if an outcome is not valid
|
|
*/
|
|
function validateOutcomeList(outcomes) {
|
|
if (!outcomeValidator.validateOutcomes(outcomes)) {
|
|
throw new TypeError('You must provide a valid list of QTI elements!');
|
|
}
|
|
return outcomes;
|
|
}
|
|
|
|
/**
|
|
* Ensures a value is an array
|
|
* @param {*} value
|
|
* @returns {Array}
|
|
*/
|
|
function forceArray(value) {
|
|
if (!value) {
|
|
value = [];
|
|
}
|
|
if (!_.isArray(value)) {
|
|
value = [value];
|
|
}
|
|
return value;
|
|
}
|
|
|
|
return processingRuleHelper;
|
|
});
|