diff --git a/application/helpers/expressions/em_core_helper.php b/application/helpers/expressions/em_core_helper.php index 3848a30bd62..526427b2dbf 100644 --- a/application/helpers/expressions/em_core_helper.php +++ b/application/helpers/expressions/em_core_helper.php @@ -227,6 +227,7 @@ function __construct() 'sum' => array('array_sum', 'LEMsum', gT('Calculate the sum of values in an array'), 'number sum(arg1, arg2, ... argN)', '', -2), 'sumifop' => array('exprmgr_sumifop', 'LEMsumifop', gT('Sum the values of answered questions in the list which pass the critiera (arg op value)'), 'number sumifop(op, value, arg1, arg2, ... argN)', '', -3), 'tan' => array('tan', 'Math.tan', gT('Tangent'), 'number tan(arg)', 'http://www.php.net/manual/en/function.tan.php', 1), +'convert_value' => array('exprmgr_convert_value', 'LEMconvert_value', gT('Convert a numerical value using a inputTable and outputTable of numerical values'), 'number convert_value(fValue, iStrict, sTranslateFromList, sTranslateToList)', '', 4), 'time' => array('time', 'time', gT('Return current UNIX timestamp'), 'number time()', 'http://www.php.net/manual/en/function.time.php', 0), 'trim' => array('trim', 'trim', gT('Strip whitespace (or other characters) from the beginning and end of a string'), 'string trim(string [, charlist])', 'http://www.php.net/manual/en/function.trim.php', 1,2), 'ucwords' => array('ucwords', 'ucwords', gT('Uppercase the first character of each word in a string'), 'string ucwords(string)', 'http://www.php.net/manual/en/function.ucwords.php', 1), @@ -2576,6 +2577,52 @@ function exprmgr_sumifop($args) return $result; } +/** + * Find the closest matching numerical input values in a list an replace it by the + * corresponding value within another list + * + * @author Johannes Weberhofer, 2013 + * + * @param numeric $fValueToReplace + * @param numeric $iStrict - 1 for exact matches only otherwise interpolation the + * closest value should be returned + * @param string $sTranslateFromList - comma seperated list of numeric values to translate from + * @param string $sTranslateToList - comma seperated list of numeric values to translate to + * @return numeric + */ +function exprmgr_convert_value($fValueToReplace, $iStrict, $sTranslateFromList, $sTranslateToList) +{ + if ( (is_numeric($fValueToReplace)) && ($iStrict!=null) && ($sTranslateFromList!=null) && ($sTranslateToList!=null) ) + { + $aFromValues = explode( ',', $sTranslateFromList); + $aToValues = explode( ',', $sTranslateToList); + if ( (count($aFromValues) > 0) && (count($aFromValues) == count($aToValues)) ) + { + $fMinimumDiff = null; + $iNearestIndex = 0; + for ( $i = 0; $i < count($aFromValues); $i++) { + if ( !is_numeric($aFromValues[$i])) { + // break processing when non-numeric variables are about to be processed + return null; + } + $fCurrentDiff = abs($aFromValues[$i] - $fValueToReplace); + if ($fCurrentDiff === 0) { + return $aToValues[$i]; + } else if ($i === 0) { + $fMinimumDiff = $fCurrentDiff; + } else if ( $fMinimumDiff > $fCurrentDiff ) { + $fMinimumDiff = $fCurrentDiff; + $iNearestIndex = $i; + } + } + if ( $iStrict !== 1 ) { + return $aToValues[$iNearestIndex]; + } + } + } + return null; +} + /** * If $test is true, return $ok, else return $error * @param $test diff --git a/application/tests/unit/ExpressionManagerTest.php b/application/tests/unit/ExpressionManagerTest.php index 5354dcac515..dbba1201246 100644 --- a/application/tests/unit/ExpressionManagerTest.php +++ b/application/tests/unit/ExpressionManagerTest.php @@ -664,6 +664,18 @@ public function oldTestEvaluator() NULL~NUMBEROFQUESTIONS-=6 NULL~'Tom'='tired' NULL~max() +NULL~convert_value( 10, 1, '0,5,10,15,20', '0,5,10,15') +100~convert_value( 10, 1, '0,5,10,15,20', '0,50,100,150,200') +NULL~convert_value( 10, 0, '0,5,10,15,20', '0,50,100,150,200') +100~convert_value( 8, 0, '0,5,10,15,20', '0,50,100,150,200') +100~convert_value( 12, 0, '0,5,10,15,20', '0,50,100,150,200') +0~convert_value( 0, 0, '0,5,10,15,20', '0,50,100,150,200') +0~convert_value( -10000, 0, '0,5,10,15,20', '0,50,100,150,200') +NULL~convert_value( -10000, 1, '0,5,10,15,20', '0,50,100,150,200') +200~convert_value( 20, 0, '0,5,10,15,20', '0,50,100,150,200') +200~convert_value( 20, 1, '0,5,10,15,20', '0,50,100,150,200') +200~convert_value( 30, 0, '0,5,10,15,20', '0,50,100,150,200') +NULL~convert_value( 30, 1, '0,5,10,15,20', '0,50,100,150,200') EOD; $atests = explode("\n",$tests); @@ -895,4 +907,4 @@ public function oldTokenizer() } } -?> \ No newline at end of file +?> diff --git a/scripts/expressions/em_javascript.js b/scripts/expressions/em_javascript.js index 3965cefeeac..f263a83e5db 100644 --- a/scripts/expressions/em_javascript.js +++ b/scripts/expressions/em_javascript.js @@ -191,6 +191,53 @@ function LEMis_string(a) return isNaN(a); } +/** + * Find the closest matching numerical input values in a list an replace it by the + * corresponding value within another list + * + * @author Johannes Weberhofer, 2013 + * + * @param numeric fValueToReplace + * @param numeric iStrict - 1 for exact matches only otherwise interpolation the + * closest value should be returned + * @param string sTranslateFromList - comma seperated list of values to translate from + * @param string sTranslateToList - comma seperated list of values to translate to + * @return numeric + */ +function LEMconvert_value( fValueToReplace, iStrict, sTranslateFromList, sTranslateToList) +{ + if ( isNaN(fValueToReplace) || (iStrict==null) || (sTranslateFromList==null) || (sTranslateToList==null) ) + { + return null; + } + aFromValues = sTranslateFromList.split(","); + aToValues = sTranslateToList.split(","); + if ( (aFromValues.length > 0) && (aFromValues.length == aToValues.length) ) + { + fMinimumDiff = null; + iNearestIndex = 0; + for ( i = 0; i < aFromValues.length; i++) { + if ( isNaN(aFromValues[i]) ) { + // break processing when non-numeric variables are about to be processed + return null; + } + fCurrentDiff = Math.abs(aFromValues[i] - fValueToReplace); + if (fCurrentDiff === 0) { + return aToValues[i]; + } else if (i === 0) { + fMinimumDiff = fCurrentDiff; + } else if ( fMinimumDiff > fCurrentDiff ) { + fMinimumDiff = fCurrentDiff; + iNearestIndex = i; + } + } + if ( iStrict !== 1 ) { + return aToValues[iNearestIndex]; + } + } + return null; +} + function LEMif(a,b,c) { // implements conditional logic. Note double negation of a to ensure it is cast to Boolean