tao-test/app/taoQtiTest/views/js/controller/creator/helpers/processingRule.js

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;
});