dhis2-devs team mailing list archive
-
dhis2-devs team
-
Mailing list archive
-
Message #24823
[Merge] lp:~sis-ma/dhis2/SISMA-80 into lp:dhis2
Leandro Soares has proposed merging lp:~sis-ma/dhis2/SISMA-80 into lp:dhis2.
Requested reviews:
DHIS 2 core developers (dhis2-devs-core)
For more details, see:
https://code.launchpad.net/~sis-ma/dhis2/SISMA-80/+merge/187163
--
https://code.launchpad.net/~sis-ma/dhis2/SISMA-80/+merge/187163
Your team DHIS 2 developers is subscribed to branch lp:dhis2.
=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java 2013-09-24 08:26:24 +0000
@@ -0,0 +1,124 @@
+package org.hisp.dhis.de.action;
+
+import com.opensymphony.xwork2.Action;
+
+import org.hisp.dhis.constant.Constant;
+import org.hisp.dhis.constant.ConstantService;
+import org.hisp.dhis.validation.ValidationRule;
+import org.hisp.dhis.validation.ValidationRuleService;
+
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * @author Stefan Börjesson
+ */
+
+public class CheckValidationRulesAction implements Action {
+
+ // -------------------------------------------------------------------------
+ // Dependencies
+ // -------------------------------------------------------------------------
+
+ private ValidationRuleService validationRuleService;
+
+ public void setValidationRuleService(
+ ValidationRuleService validationRuleService) {
+ this.validationRuleService = validationRuleService;
+ }
+
+ private ConstantService constantService;
+
+ public void setConstantService(ConstantService constantService) {
+ this.constantService = constantService;
+ }
+
+ // -------------------------------------------------------------------------
+ // Output
+ // -------------------------------------------------------------------------
+
+ private long validationRulesTs;
+
+ public long getValidationRulesTs() {
+ return this.validationRulesTs;
+ }
+
+ private int numberOfValidationRules;
+
+ public int getNumberOfValidationRules() {
+ return this.numberOfValidationRules;
+ }
+
+ private long constantsTs;
+
+ public long getConstantsTs() {
+ return constantsTs;
+ }
+
+ private int numberOfConstants;
+
+ public int getNumberOfConstants() {
+ return this.numberOfConstants;
+ }
+
+ // -------------------------------------------------------------------------
+ // Action implementation
+ // -------------------------------------------------------------------------
+
+ public String execute() {
+ this.validationRulesTs = 0;
+ this.constantsTs = 0;
+ Date lastUpdatedDate = null;
+
+ // Validation Rules
+ Collection<ValidationRule> rules = this.validationRuleService
+ .getAllValidationRules();
+
+ // Loop to get the largest timestamp from the existing validation rules
+ for (ValidationRule rule : rules) {
+ if (lastUpdatedDate != null) {
+ if (rule.getLastUpdated() != null) {
+ if (rule.getLastUpdated().after(lastUpdatedDate)) {
+ lastUpdatedDate = rule.getLastUpdated();
+ }
+ }
+ } else {
+ lastUpdatedDate = rule.getLastUpdated();
+ }
+
+ }
+ // If date is null set TS to Long.MAX_VALUE to force update as rules
+ // might have been deleted.
+ this.validationRulesTs = lastUpdatedDate == null ? Long.MAX_VALUE
+ : lastUpdatedDate.getTime();
+
+ // Setting the number of validation rules to the size of the collection
+ // returned.
+ this.numberOfValidationRules = rules.size();
+
+ // Constants
+ lastUpdatedDate = null;
+ Collection<Constant> constants = constantService.getAllConstants();
+ for (Constant constant : constants) {
+ if (lastUpdatedDate != null) {
+ if (constant.getLastUpdated() != null) {
+ if (constant.getLastUpdated().after(lastUpdatedDate)) {
+ lastUpdatedDate = constant.getLastUpdated();
+ }
+ }
+ } else {
+ lastUpdatedDate = constant.getLastUpdated();
+ }
+
+ }
+
+ this.constantsTs = lastUpdatedDate == null ? Long.MAX_VALUE
+ : lastUpdatedDate.getTime();
+
+ // Setting the number of constants to the size of the collection
+ // returned.
+ this.numberOfConstants = constants.size();
+
+ return SUCCESS;
+ }
+}
=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java 2013-09-24 08:26:24 +0000
@@ -0,0 +1,98 @@
+package org.hisp.dhis.de.action;
+
+import java.text.DecimalFormat;
+import java.util.Collection;
+
+import org.hisp.dhis.constant.Constant;
+import org.hisp.dhis.constant.ConstantService;
+import org.hisp.dhis.expression.ExpressionService;
+import org.hisp.dhis.validation.ValidationRule;
+import org.hisp.dhis.validation.ValidationRuleService;
+
+import com.opensymphony.xwork2.Action;
+
+/**
+ * @author Stefan Börjesson
+ */
+public class GetValidationRulesAction implements Action {
+
+ private ValidationRuleService validationRuleService;
+
+ public void setValidationRuleService(
+ ValidationRuleService validationRuleService) {
+ this.validationRuleService = validationRuleService;
+ }
+
+
+
+ private ConstantService constantService;
+
+ public void setConstantService( ConstantService constantService )
+ {
+ this.constantService = constantService;
+ }
+
+ private ExpressionService expressionService;
+
+ public void setExpressionService( ExpressionService expressionService)
+ {
+ this.expressionService = expressionService;
+ }
+
+ public ExpressionService getExpressionService()
+ {
+ return this.expressionService;
+ }
+
+ private Collection<ValidationRule> validationRules;
+
+ public void setValidationRules(Collection<ValidationRule> validationRules) {
+ this.validationRules = validationRules;
+ }
+
+ public Collection<ValidationRule> getValidationRules() {
+ return this.validationRules;
+ }
+
+ private Collection<Constant> constants;
+
+ public Collection<Constant> getConstants() {
+ return constants;
+ }
+
+ public void setConstants(Collection<Constant> constants) {
+ this.constants = constants;
+ }
+
+ private DecimalFormat df;
+
+ public DecimalFormat getDf(){
+ return this.df;
+ }
+
+ @Override
+ public String execute() throws Exception {
+ df = new DecimalFormat();
+ df.setMaximumFractionDigits(12);
+ df.setMinimumFractionDigits(0);
+ df.setMaximumFractionDigits(1);
+ df.setGroupingUsed(false);
+
+ this.validationRules = this.validationRuleService
+ .getAllValidationRules();
+ /*for(ValidationRule rule: this.validationRules){
+ System.out.println("rule.getPeriodType().getName() = " + rule.getPeriodType().getName());
+ System.out.println("rule.getType() = " + rule.getType());
+ System.out.println("rule.getOperator().getMathematicalOperator() = " + rule.getOperator().getMathematicalOperator());
+ System.out.println("rule.getLeftSide().getDescription() = " + rule.getLeftSide().getDescription());
+ System.out.println("rule.getLeftSide().getExpression() = " + rule.getLeftSide().getExpression());
+ System.out.println("rule.getRightSide().getDescription() = " + rule.getRightSide().getDescription());
+ System.out.println("rule.getRightSide().getExpression() = " + rule.getRightSide().getExpression());
+ System.out.println("rule.getRightSide().isNullIfBlank() = " + rule.getRightSide().isNullIfBlank());
+ }*/
+
+ this.constants = constantService.getAllConstants();
+
+ return SUCCESS;
+ }
+}
=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml 2012-12-14 13:46:47 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml 2013-09-24 08:26:24 +0000
@@ -129,4 +129,17 @@
<property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
</bean>
+ <bean id="org.hisp.dhis.de.action.CheckValidationRulesAction" class="org.hisp.dhis.de.action.CheckValidationRulesAction"
+ scope="prototype">
+ <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+ <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
+ </bean>
+
+ <bean id="org.hisp.dhis.de.action.GetValidationRulesAction" class="org.hisp.dhis.de.action.GetValidationRulesAction"
+ scope="prototype">
+ <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+ <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
+ <property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
+ </bean>
+
</beans>
=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml 2013-07-25 09:37:43 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml 2013-09-24 08:26:24 +0000
@@ -13,7 +13,7 @@
<result name="success" type="velocity">/main.vm</result>
<param name="page">/dhis-web-dataentry/select.vm</param>
<param name="menu">/dhis-web-dataentry/menu.vm</param>
- <param name="javascripts">../dhis-web-commons/ouwt/ouwt.js,javascript/form.js,javascript/entry.js,javascript/history.js</param>
+ <param name="javascripts">../dhis-web-commons/ouwt/ouwt.js,javascript/form.js,javascript/entry.js,javascript/history.js,javascript/ruleValidation.js</param>
<param name="stylesheets">style/dhis-web-dataentry.css</param>
<param name="manifest">../dhis-web-commons/cacheManifest.action</param>
</action>
@@ -97,5 +97,13 @@
<result name="success" type="chart"></result>
</action>
+ <action name="checkValidationRules" class="org.hisp.dhis.de.action.CheckValidationRulesAction">
+ <result name="success" type="velocity-json">/dhis-web-dataentry/checkValidationRules.vm</result>
+ </action>
+
+ <action name="getValidationRules" class="org.hisp.dhis.de.action.GetValidationRulesAction">
+ <result name="success" type="velocity-json">/dhis-web-dataentry/getValidationRules.vm</result>
+ </action>
+
</package>
</struts>
=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm 2013-09-24 08:26:24 +0000
@@ -0,0 +1,6 @@
+{
+ "validationRulesTs":"${validationRulesTs}",
+ "numberOfValidationRules":"${numberOfValidationRules}",
+ "constantsTs":"${constantsTs}",
+ "numberOfConstants":"${numberOfConstants}"
+}
\ No newline at end of file
=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm 2013-09-24 08:26:24 +0000
@@ -0,0 +1,31 @@
+#set( $size = $validationRules.size() )
+#set( $size2 = $constants.size() )
+{ "validationRules": [
+#foreach( $value in $validationRules )
+{
+ "id":"${value.id}",
+ "description":"${encoder.jsonEncode( $value.description)}",
+ "leftSide":{
+ "expression":"${encoder.jsonEncode( $value.leftSide.expression)}",
+ "isNullIfBlank":${value.leftSide.nullIfBlank},
+ "description":"${encoder.jsonEncode($expressionService.getExpressionDescription($value.leftSide.expression))}"
+ },
+ "rightSide":{
+ "expression":"${encoder.jsonEncode( $value.rightSide.expression)}",
+ "isNullIfBlank":${value.rightSide.nullIfBlank},
+ "description":"${encoder.jsonEncode($expressionService.getExpressionDescription($value.rightSide.expression))}"
+ },
+ "operator":"${value.operator.mathematicalOperator}",
+ "validationResult": null
+}#if( $velocityCount < $size ),#end
+#end ],
+"constants": [
+#foreach( $constant in $constants)
+{
+ "id":"${constant.id}",
+ "description":"${encoder.jsonEncode($constant.displayName)}",
+ "value":"$df.format($constant.value)",
+ "uid":"${constant.uid}"
+}#if( $velocityCount < $size2 ),#end
+#end ]
+}
=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js 2013-09-23 09:57:48 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js 2013-09-24 08:26:24 +0000
@@ -222,6 +222,7 @@
log( 'Meta-data loaded' );
updateForms();
+ updateValidationRules();
}
} );
}
@@ -1432,6 +1433,30 @@
} );
}
+
+function validate_offline( ignoreSuccessfulValidation, successCallback)
+{
+ var success = true;
+ var response = runOfflineValidations(ignoreSuccessfulValidation);
+
+ if(response == ''){
+ var successHtml = '<h3>' + i18n_validation_result + ' <img src="../images/success_small.png"></h3>' +
+ '<p class="bold">' + i18n_successful_validation + '</p>';
+ if(!ignoreSuccessfulValidation){
+ displayValidationDialog( successHtml, 200 );
+ }
+ } else{
+ success = false;
+ displayValidationDialog( response, 500 );
+ }
+
+
+ if ( success && $.isFunction( successCallback ) )
+ {
+ successCallback.call();
+ }
+}
+
function validate( ignoreSuccessfulValidation, successCallback )
{
var compulsoryCombinationsValid = validateCompulsoryCombinations();
@@ -1459,34 +1484,46 @@
params['organisationUnitId'] = getCurrentOrganisationUnit();
params['multiOrganisationUnit'] = multiOrganisationUnit;
- $( '#validationDiv' ).load( 'validate.action', params, function( response, status, xhr ) {
- var success = null;
-
- if ( status == 'error' && !ignoreSuccessfulValidation )
- {
- window.alert( i18n_operation_not_available_offline );
- success = true; // Accept if offline
- }
- else
- {
- var hasViolations = isDefined( response ) && $.trim( response ).length > 0;
- var success = !( hasViolations && validCompleteOnly );
-
- if ( hasViolations )
- {
- displayValidationDialog( response, 500 );
- }
- else if ( !ignoreSuccessfulValidation )
- {
- displayValidationDialog( successHtml, 200 );
- }
- }
-
- if ( success && $.isFunction( successCallback ) )
- {
- successCallback.call();
- }
- } );
+ if(!dhis2.availability._isAvailable)
+ {
+ success = validate_offline( ignoreSuccessfulValidation, successCallback);
+ }
+ else
+ {
+ $( '#validationDiv' ).load( 'validate.action', params, function( response, status, xhr ) {
+ var success = null;
+
+ if ( status == 'error' && !ignoreSuccessfulValidation )
+ {
+ //window.alert( i18n_operation_not_available_offline );
+ //success = true; // Accept if offline
+ success = validate_offline( ignoreSuccessfulValidation, successCallback);
+ if ( success && $.isFunction( successCallback ) )
+ {
+ successCallback.call();
+ }
+ }
+ else
+ {
+ var hasViolations = isDefined( response ) && $.trim( response ).length > 0;
+ var success = !( hasViolations && validCompleteOnly );
+
+ if ( hasViolations )
+ {
+ displayValidationDialog( response, 500 );
+ }
+ else if ( !ignoreSuccessfulValidation )
+ {
+ displayValidationDialog( successHtml, 200 );
+ }
+ }
+
+ if ( success && $.isFunction( successCallback ) )
+ {
+ successCallback.call();
+ }
+ } );
+ }
}
function validateCompulsoryCombinations()
@@ -1642,6 +1679,49 @@
} );
}
+//-----------------------------------------------------------------------------
+//Local storage of Validation Rules
+//-----------------------------------------------------------------------------
+
+function updateValidationRules()
+{
+ log("Before checkValidationRules.action");
+ $.ajax( {
+ url: 'checkValidationRules.action',
+ dataType: 'json',
+ success: function( data, textStatus, jqXHR )
+ {
+ var validationRulesControl = data;
+ var localValidationRulesControl = storageManager.getValidationRulesControl();
+
+ if(localValidationRulesControl.validationRulesTs != validationRulesControl.validationRulesTs ||
+ localValidationRulesControl.numberOfValidationRules != validationRulesControl.numberOfValidationRules ||
+ localValidationRulesControl.constantsTs != validationRulesControl.constantsTs ||
+ localValidationRulesControl.numberOfConstants != validationRulesControl.numberOfConstants){
+
+ storageManager.purgeValidationRules();
+ storageManager.purgeConstants();
+
+ $.ajax( {
+ url: 'getValidationRules.action',
+ dataType: 'json',
+ success: function( data, textStatus, jqXHR )
+ {
+ storageManager.saveValidationRules(data);
+ storageManager.saveConstants(data);
+ storageManager.saveValidationRulesControl(validationRulesControl);
+ }
+ } );
+ }
+ else
+ {
+ log("Validation rules in localstorage are up to date.");
+ }
+ }
+ } );
+}
+
+
// TODO break if local storage is full
// -----------------------------------------------------------------------------
@@ -1661,6 +1741,9 @@
var KEY_FORM_VERSIONS = 'formversions';
var KEY_DATAVALUES = 'datavalues';
var KEY_COMPLETEDATASETS = 'completedatasets';
+ var KEY_VALIDATIONRULESCONTROL = 'validationrulescontrol';
+ var KEY_VALIDATIONRULE = 'validationrule-';
+ var KEY_CONSTANT = 'validationrulesconstants';
/**
* Returns the total number of characters currently in the local storage.
@@ -2150,6 +2233,179 @@
return true;
};
+
+
+ /**
+ * Method to retrive the timestamp of the validation rules in localstorage.
+ *
+ * @return The timestamp of the validation rules in localstorage.
+ */
+
+
+ this.getValidationRulesControl = function()
+ {
+ var validationRuleControl = localStorage[KEY_VALIDATIONRULESCONTROL];
+ if(validationRuleControl == null)
+ {
+ validationRuleControl = new Object();
+ validationRuleControl.validationRulesTs = 0;
+ validationRuleControl.numberOfValidationRules = 0;
+ validationRuleControl.constantsTs = 0;
+ validationRuleControl.numberOfValidationConstants = 0;
+ return 0;
+ }
+ else
+ {
+ return JSON.parse(validationRuleControl);
+ }
+
+ };
+
+ /**
+ * Method to save a new timestamp of the validation rules in localstorage.
+ *
+ * @param The timestamp to be saved.
+ */
+
+ this.saveValidationRulesControl = function(newValidationRulesControl)
+ {
+ try
+ {
+ localStorage[KEY_VALIDATIONRULESCONTROL] = JSON.stringify(newValidationRulesControl);
+ }
+ catch ( e )
+ {
+ log( 'Max local storage quota reached, validation rule timestamp not saved locally.');
+ return false;
+ }
+ return true;
+ };
+
+
+ /**
+ * Method that purges the validation rules stored in localstorage.
+ *
+ */
+
+ this.purgeValidationRules = function()
+ {
+ for ( var i = 0; i < localStorage.length; i++ )
+ {
+ var key = localStorage.key( i );
+
+ if ( key.substring( 0, KEY_VALIDATIONRULE.length) == KEY_VALIDATIONRULE)
+ {
+ localStorage.removeItem(key);
+ }
+ }
+ };
+
+
+ /**
+ * Method that purges the validation rules stored in localstorage.
+ * @param json The json object containing the new validation rules
+ */
+
+ this.saveValidationRules = function(json)
+ {
+ validationRules = json.validationRules;
+ for(var i = 0; i < validationRules.length; i++)
+ {
+ try
+ {
+ localStorage[KEY_VALIDATIONRULE + validationRules[i].id] = JSON.stringify(validationRules[i]);
+ }
+ catch ( e )
+ {
+ log( 'Max local storage quota reached, ignored validation rule: ' + validationRules[i].id );
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * Method that returns all validation rules from localstorage.
+ * @return An object contining all validation rules.
+ */
+
+ this.getValidationRules = function()
+ {
+ var validationRules = [];
+
+ var validationRuleIndex = 0;
+
+ for ( var i = 0; i < localStorage.length; i++ )
+ {
+ var key = localStorage.key( i );
+
+ if ( key.substring( 0, KEY_VALIDATIONRULE.length) == KEY_VALIDATIONRULE)
+ {
+ validationRules[validationRuleIndex++] = JSON.parse(localStorage[key]);
+ }
+ }
+ return validationRules;
+ };
+
+
+
+ /**
+ * Method that purges the constants stored in localstorage.
+ *
+ */
+
+ this.purgeConstants = function()
+ {
+ for ( var i = 0; i < localStorage.length; i++ )
+ {
+ var key = localStorage.key( i );
+
+ if ( key.substring( 0, KEY_CONSTANT.length) == KEY_CONSTANT)
+ {
+ localStorage.removeItem(key);
+ }
+ }
+ };
+
+
+ /**
+ * Method that saves constants in localstorage.
+ * @param json The json object containing the new constnats
+ */
+
+ this.saveConstants = function(json)
+ {
+ constants = json.constants;
+ if(constants != null){
+ try
+ {
+ localStorage[KEY_CONSTANT] = JSON.stringify(constants);
+ }
+ catch ( e )
+ {
+ log( 'Max local storage quota reached, ignored constants: ' + constants);
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * Method that returns all constants localstorage.
+ * @return An object contining all constants.
+ */
+
+ this.getConstants = function()
+ {
+ var constants = [];
+ if (localStorage[KEY_CONSTANT] != null){
+ constants = JSON.parse(localStorage[KEY_CONSTANT]);
+ }
+
+ return constants;
+ };
+
+
}
// -----------------------------------------------------------------------------
=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js 2013-09-24 08:26:24 +0000
@@ -0,0 +1,1474 @@
+
+/*
+ Based on ndef.parser, by Raphael Graf(r@xxxxxxxxxxxx)
+ http://www.undefined.ch/mparser/index.html
+
+ Ported to JavaScript and modified by Matthew Crumley (email@xxxxxxxxxxxxxxxxxx, http://silentmatt.com/)
+
+ Modified by Stefan Börjesson to correspond to the functionality in the JAVA JEP library's standard function set.
+
+*/
+
+// Added by stlsmiths 6/13/2011
+// re-define Array.indexOf, because IE doesn't know it ...
+//
+// from http://stellapower.net/content/javascript-support-and-arrayindexof-ie
+
+
+/*if (!Array.indexOf) {
+ Array.prototype.indexOf = function (obj, start) {
+ for (var i = (start || 0); i < this.length; i++) {
+ if (this[i] === obj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}*/
+
+
+Number.prototype.format = function() {
+ return this.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
+};
+
+
+var Parser = (function (scope)
+{
+ function object(o)
+ {
+ function F() {}
+ F.prototype = o;
+ return new F();
+ }
+
+ var TNUMBER = 0;
+ var TOP1 = 1;
+ var TOP2 = 2;
+ var TVAR = 3;
+ var TFUNCALL = 4;
+
+ function Token(type_, index_, prio_, number_)
+ {
+ this.type_ = type_;
+ this.index_ = index_ || 0;
+ this.prio_ = prio_ || 0;
+ this.number_ = (number_ !== undefined && number_ !== null) ? number_ : 0;
+ this.toString = function ()
+ {
+ switch (this.type_)
+ {
+ case TNUMBER:
+ return this.number_;
+ case TOP1:
+ case TOP2:
+ case TVAR:
+ return this.index_;
+ case TFUNCALL:
+ return "CALL";
+ default:
+ return "Invalid Token";
+ }
+ };
+ }
+
+ function Expression(tokens, ops1, ops2, functions) {
+ this.tokens = tokens;
+ this.ops1 = ops1;
+ this.ops2 = ops2;
+ this.functions = functions;
+ }
+
+ // Based on http://www.json.org/json2.js
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\'\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ "'" : "\\'",
+ '\\': '\\\\'
+ };
+
+ function escapeValue(v)
+ {
+ if (typeof v === "string")
+ {
+ escapable.lastIndex = 0;
+ return escapable.test(v) ?
+ "'" + v.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + "'" :
+ "'" + v + "'";
+ }
+ return v;
+ }
+
+ Expression.prototype = {
+ simplify: function (values) {
+ values = values || {};
+ var nstack = [];
+ var newexpression = [];
+ var n1;
+ var n2;
+ var f;
+ var L = this.tokens.length;
+ var item;
+ var i = 0;
+ for (i = 0; i < L; i++)
+ {
+ item = this.tokens[i];
+ var type_ = item.type_;
+ if (type_ === TNUMBER)
+ {
+ nstack.push(item);
+ }
+ else if (type_ === TVAR && (item.index_ in values))
+ {
+ item = new Token(TNUMBER, 0, 0, values[item.index_]);
+ nstack.push(item);
+ }
+ else if (type_ === TOP2 && nstack.length > 1)
+ {
+ n2 = nstack.pop();
+ n1 = nstack.pop();
+ f = this.ops2[item.index_];
+ item = new Token(TNUMBER, 0, 0, f(n1.number_, n2.number_));
+ nstack.push(item);
+ }
+ else if (type_ === TOP1 && nstack.length > 0)
+ {
+ n1 = nstack.pop();
+ f = this.ops1[item.index_];
+ item = new Token(TNUMBER, 0, 0, f(n1.number_));
+ nstack.push(item);
+ }
+ else
+ {
+ while (nstack.length > 0)
+ {
+ newexpression.push(nstack.shift());
+ }
+ newexpression.push(item);
+ }
+ }
+ while (nstack.length > 0)
+ {
+ newexpression.push(nstack.shift());
+ }
+
+ return new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
+ },
+
+ substitute: function (variable, expr) {
+ if (!(expr instanceof Expression))
+ {
+ expr = new Parser().parse(String(expr));
+ }
+ var newexpression = [];
+ var L = this.tokens.length;
+ var item;
+ var i = 0;
+ for (i = 0; i < L; i++)
+ {
+ item = this.tokens[i];
+ var type_ = item.type_;
+ if (type_ === TVAR && item.index_ === variable)
+ {
+ for (var j = 0; j < expr.tokens.length; j++)
+ {
+ var expritem = expr.tokens[j];
+ var replitem = new Token(expritem.type_, expritem.index_, expritem.prio_, expritem.number_);
+ newexpression.push(replitem);
+ }
+ }
+ else
+ {
+ newexpression.push(item);
+ }
+ }
+
+ var ret = new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
+ return ret;
+ },
+
+ evaluate: function (values) {
+ values = values || {};
+ var nstack = [];
+ var n1;
+ var n2;
+ var f;
+ var L = this.tokens.length;
+ var item;
+ var i = 0;
+ for (i = 0; i < L; i++)
+ {
+ item = this.tokens[i];
+ var type_ = item.type_;
+ if (type_ === TNUMBER)
+ {
+ nstack.push(item.number_);
+ }
+ else if (type_ === TOP2)
+ {
+ n2 = nstack.pop();
+ n1 = nstack.pop();
+ f = this.ops2[item.index_];
+ nstack.push(f(n1, n2));
+ }
+ else if (type_ === TVAR)
+ {
+ if (item.index_ in values)
+ {
+ nstack.push(values[item.index_]);
+ }
+ else if (item.index_ in this.functions)
+ {
+ nstack.push(this.functions[item.index_]);
+ }
+ else
+ {
+ throw new Error("undefined variable: " + item.index_);
+ }
+ }
+ else if (type_ === TOP1)
+ {
+ n1 = nstack.pop();
+ f = this.ops1[item.index_];
+ nstack.push(f(n1));
+ }
+ else if (type_ === TFUNCALL)
+ {
+ n1 = nstack.pop();
+ f = nstack.pop();
+ if (f.apply && f.call)
+ {
+ if (Object.prototype.toString.call(n1) == "[object Array]")
+ {
+ nstack.push(f.apply(undefined, n1));
+ }
+ else
+ {
+ nstack.push(f.call(undefined, n1));
+ }
+ }
+ else
+ {
+ throw new Error(f + " is not a function");
+ }
+ }
+ else
+ {
+ throw new Error("invalid Expression");
+ }
+ }
+ if (nstack.length > 1)
+ {
+ throw new Error("invalid Expression (parity)");
+ }
+ return nstack[0];
+ },
+
+ toString: function (toJS) {
+ var nstack = [];
+ var n1;
+ var n2;
+ var f;
+ var L = this.tokens.length;
+ var item;
+ var i = 0;
+ for (i = 0; i < L; i++)
+ {
+ item = this.tokens[i];
+ var type_ = item.type_;
+ if (type_ === TNUMBER)
+ {
+ nstack.push(escapeValue(item.number_));
+ }
+ else if (type_ === TOP2)
+ {
+ n2 = nstack.pop();
+ n1 = nstack.pop();
+ f = item.index_;
+ if (toJS && f == "^")
+ {
+ nstack.push("Math.pow(" + n1 + "," + n2 + ")");
+ }
+ else {
+ nstack.push("(" + n1 + f + n2 + ")");
+ }
+ }
+ else if (type_ === TVAR)
+ {
+ nstack.push(item.index_);
+ }
+ else if (type_ === TOP1)
+ {
+ n1 = nstack.pop();
+ f = item.index_;
+ if (f === "-")
+ {
+ nstack.push("(" + f + n1 + ")");
+ }
+ else
+ {
+ nstack.push(f + "(" + n1 + ")");
+ }
+ }
+ else if (type_ === TFUNCALL)
+ {
+ n1 = nstack.pop();
+ f = nstack.pop();
+ nstack.push(f + "(" + n1 + ")");
+ }
+ else
+ {
+ throw new Error("invalid Expression");
+ }
+ }
+ if (nstack.length > 1)
+ {
+ throw new Error("invalid Expression (parity)");
+ }
+ return nstack[0];
+ },
+
+ variables: function () {
+ var L = this.tokens.length;
+ var vars = [];
+ for (var i = 0; i < L; i++)
+ {
+ var item = this.tokens[i];
+ if (item.type_ === TVAR && (vars.indexOf(item.index_) == -1)) {
+ vars.push(item.index_);
+ }
+ }
+
+ return vars;
+ },
+
+ toJSFunction: function (param, variables) {
+ var f = new Function(param, "with(Parser.values) { return " + this.simplify(variables).toString(true) + "; }");
+ return f;
+ }
+ };
+
+ function add(a, b)
+ {
+ return Number(a) + Number(b);
+ }
+
+ function sub(a, b)
+ {
+ return a - b;
+ }
+
+ function mul(a, b)
+ {
+ return a * b;
+ }
+
+ function div(a, b)
+ {
+ return a / b;
+ }
+
+ function mod(a, b)
+ {
+ return a % b;
+ }
+
+ function sum(a, b, c)
+ {
+ return a+b+c;
+ }
+
+ function concat(a, b)
+ {
+ return "" + a + b;
+ }
+
+ function neg(a)
+ {
+ return -a;
+ }
+
+ function random()
+ {
+ return Math.random();
+ }
+
+ function fac(a) //a!
+ {
+ a = Math.floor(a);
+ var b = a;
+ while (a > 1)
+ {
+ b = b * (--a);
+ }
+ return b;
+ }
+
+ function pyt(a, b)
+ {
+ return Math.sqrt(a * a + b * b);
+ }
+
+ function log10(a)
+ {
+ return Math.log(a) / Math.LN10;
+ }
+
+ function sinh(a)
+ {
+ var myTerm1 = Math.pow(Math.E, a);
+ var myTerm2 = Math.pow(Math.E, -a);
+
+ return (myTerm1-myTerm2)/2;
+ }
+
+ function cosh(a)
+ {
+ var myTerm1 = Math.pow(Math.E, a);
+ var myTerm2 = Math.pow(Math.E, -a);
+
+ return (myTerm1+myTerm2)/2;
+ }
+
+ function tanh (a)
+ {
+ return (Math.exp(a)-Math.exp(-a))/(Math.exp(a)+Math.exp(-a));
+ }
+
+ function asinh(a)
+ {
+ return Math.log(a + Math.sqrt(a*a + 1));
+ }
+
+ function acosh (a)
+ {
+ return Math.log(a+Math.sqrt(a*a-1));
+ }
+
+ function atanh (a)
+ {
+ return 0.5*Math.log((1+a)/(1-a));
+ }
+
+ function binom(n, k)
+ {
+ var coeff = 1;
+ for (var i = n-k+1; i <= n; i++) coeff *= i;
+ for (var i = 1; i <= k; i++) coeff /= i;
+ return coeff;
+ }
+
+ function append(a, b)
+ {
+ if (Object.prototype.toString.call(a) != "[object Array]")
+ {
+ return [a, b];
+ }
+ a = a.slice();
+ a.push(b);
+ return a;
+ }
+
+ function Parser()
+ {
+ this.success = false;
+ this.errormsg = "";
+ this.expression = "";
+
+ this.pos = 0;
+
+ this.tokennumber = 0;
+ this.tokenprio = 0;
+ this.tokenindex = 0;
+ this.tmpprio = 0;
+
+ this.ops1 = {
+ "sin": Math.sin, //In JEP
+ "cos": Math.cos, //In JEP
+ "tan": Math.tan, //In JEP
+ "asin": Math.asin, //In JEP
+ "acos": Math.acos, //In JEP
+ "atan": Math.atan, //In JEP
+ "sinh": sinh, //In JEP
+ "cosh": cosh, //In JEP
+ "tanh": tanh, //In JEP
+ "asinh": asinh, //In JEP
+ "acosh": acosh, //In JEP
+ "exp": Math.exp, //In JEP
+ "sqrt": Math.sqrt, //In JEP
+ "ln": Math.log, //In JEP
+ "log": log10, //In JEP
+ "abs": Math.abs, //In JEP
+ "ceil": Math.ceil,
+ "floor": Math.floor,
+ "round": Math.round,
+ "-": neg
+
+ };
+
+ this.ops2 = {
+ "+": add,
+ "-": sub,
+ "*": mul,
+ "/": div,
+ "%": mod,
+ "^": Math.pow,
+ ",": append,
+ "||": concat
+ };
+
+ this.functions =
+ {
+ "rand": random, //In JEP
+ "fac": fac,
+ "min": Math.min,
+ "max": Math.max,
+ "pyt": pyt,
+ "pow": Math.pow,
+ "mod": mod, //In JEP
+ "sum": sum, //In JEP
+ "binom" : binom, //In JEP
+ "atan2": Math.atan2
+ };
+
+ this.consts =
+ {
+ "E": Math.E,
+ "PI": Math.PI
+ };
+ }
+
+ Parser.parse = function (expr)
+ {
+ return new Parser().parse(expr);
+ };
+
+ Parser.evaluate = function (expr, variables)
+ {
+ return Parser.parse(expr).evaluate(variables);
+ };
+
+ Parser.Expression = Expression;
+
+ Parser.values = {
+ sin: Math.sin,
+ cos: Math.cos,
+ tan: Math.tan,
+ asin: Math.asin,
+ acos: Math.acos,
+ atan: Math.atan,
+ sqrt: Math.sqrt,
+ log: Math.log,
+ abs: Math.abs,
+ ceil: Math.ceil,
+ floor: Math.floor,
+ round: Math.round,
+ random: random,
+ fac: fac,
+ exp: Math.exp,
+ min: Math.min,
+ max: Math.max,
+ pyt: pyt,
+ pow: Math.pow,
+ atan2: Math.atan2,
+ E: Math.E,
+ PI: Math.PI
+ };
+
+ var PRIMARY = 1 << 0;
+ var OPERATOR = 1 << 1;
+ var FUNCTION = 1 << 2;
+ var LPAREN = 1 << 3;
+ var RPAREN = 1 << 4;
+ var COMMA = 1 << 5;
+ var SIGN = 1 << 6;
+ var CALL = 1 << 7;
+ var NULLARY_CALL = 1 << 8;
+
+ Parser.prototype = {
+ parse: function (expr) {
+ this.errormsg = "";
+ this.success = true;
+ var operstack = [];
+ var tokenstack = [];
+ this.tmpprio = 0;
+ var expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
+ var noperators = 0;
+ this.expression = expr;
+ this.pos = 0;
+
+ while (this.pos < this.expression.length)
+ {
+ if (this.isOperator())
+ {
+ if (this.isSign() && (expected & SIGN))
+ {
+ if (this.isNegativeSign())
+ {
+ this.tokenprio = 2;
+ this.tokenindex = "-";
+ noperators++;
+ this.addfunc(tokenstack, operstack, TOP1);
+ }
+ expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
+ }
+ else if (this.isComment())
+ {
+
+ }
+ else
+ {
+ if ((expected & OPERATOR) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected operator");
+ }
+ noperators += 2;
+ this.addfunc(tokenstack, operstack, TOP2);
+ expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
+ }
+ }
+ else if (this.isNumber())
+ {
+ if ((expected & PRIMARY) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected number");
+ }
+ var token = new Token(TNUMBER, 0, 0, this.tokennumber);
+ tokenstack.push(token);
+
+ expected = (OPERATOR | RPAREN | COMMA);
+ }
+ else if (this.isString())
+ {
+ if ((expected & PRIMARY) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected string");
+ }
+ var token = new Token(TNUMBER, 0, 0, this.tokennumber);
+ tokenstack.push(token);
+
+ expected = (OPERATOR | RPAREN | COMMA);
+ }
+ else if (this.isLeftParenth())
+ {
+ if ((expected & LPAREN) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected \"(\"");
+ }
+
+ if (expected & CALL)
+ {
+ noperators += 2;
+ this.tokenprio = -2;
+ this.tokenindex = -1;
+ this.addfunc(tokenstack, operstack, TFUNCALL);
+ }
+
+ expected = (PRIMARY | LPAREN | FUNCTION | SIGN | NULLARY_CALL);
+ }
+ else if (this.isRightParenth())
+ {
+ if (expected & NULLARY_CALL)
+ {
+ var token = new Token(TNUMBER, 0, 0, []);
+ tokenstack.push(token);
+ }
+ else if ((expected & RPAREN) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected \")\"");
+ }
+
+ expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
+ }
+ else if (this.isComma())
+ {
+ if ((expected & COMMA) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected \",\"");
+ }
+ this.addfunc(tokenstack, operstack, TOP2);
+ noperators += 2;
+ expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
+ }
+ else if (this.isConst())
+ {
+ if ((expected & PRIMARY) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected constant");
+ }
+ var consttoken = new Token(TNUMBER, 0, 0, this.tokennumber);
+ tokenstack.push(consttoken);
+ expected = (OPERATOR | RPAREN | COMMA);
+ }
+ else if (this.isOp2())
+ {
+ if ((expected & FUNCTION) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected function");
+ }
+ this.addfunc(tokenstack, operstack, TOP2);
+ noperators += 2;
+ expected = (LPAREN);
+ }
+ else if (this.isOp1())
+ {
+ if ((expected & FUNCTION) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected function");
+ }
+ this.addfunc(tokenstack, operstack, TOP1);
+ noperators++;
+ expected = (LPAREN);
+ }
+ else if (this.isVar())
+ {
+ if ((expected & PRIMARY) === 0)
+ {
+ this.error_parsing(this.pos, "unexpected variable");
+ }
+ var vartoken = new Token(TVAR, this.tokenindex, 0, 0);
+ tokenstack.push(vartoken);
+
+ expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
+ }
+ else if (this.isWhite())
+ {
+ }
+ else
+ {
+ if (this.errormsg === "")
+ {
+ this.error_parsing(this.pos, "unknown character");
+ }
+ else
+ {
+ this.error_parsing(this.pos, this.errormsg);
+ }
+ }
+ }
+ if (this.tmpprio < 0 || this.tmpprio >= 10)
+ {
+ this.error_parsing(this.pos, "unmatched \"()\"");
+ }
+ while (operstack.length > 0)
+ {
+ var tmp = operstack.pop();
+ tokenstack.push(tmp);
+ }
+ if (noperators + 1 !== tokenstack.length)
+ {
+ this.error_parsing(this.pos, "parity");
+ }
+
+ return new Expression(tokenstack, object(this.ops1), object(this.ops2), object(this.functions));
+ },
+
+ evaluate: function (expr, variables)
+ {
+ return this.parse(expr).evaluate(variables);
+ },
+
+ error_parsing: function (column, msg)
+ {
+ this.success = false;
+ this.errormsg = "parse error [column " + (column) + "]: " + msg;
+ throw new Error(this.errormsg);
+ },
+
+ addfunc: function (tokenstack, operstack, type_) {
+ var operator = new Token(type_, this.tokenindex, this.tokenprio + this.tmpprio, 0);
+ while (operstack.length > 0)
+ {
+ if (operator.prio_ <= operstack[operstack.length - 1].prio_)
+ {
+ tokenstack.push(operstack.pop());
+ }
+ else
+ {
+ break;
+ }
+ }
+ operstack.push(operator);
+ },
+
+ isNumber: function () {
+ var r = false;
+ var str = "";
+ while (this.pos < this.expression.length)
+ {
+ var code = this.expression.charCodeAt(this.pos);
+ if ((code >= 48 && code <= 57) || code === 46)
+ {
+ str += this.expression.charAt(this.pos);
+ this.pos++;
+ this.tokennumber = parseFloat(str);
+ r = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return r;
+ },
+
+ unescape: function(v, pos) {
+ var buffer = [];
+ var escaping = false;
+
+ for (var i = 0; i < v.length; i++)
+ {
+ var c = v.charAt(i);
+
+ if (escaping)
+ {
+ switch (c)
+ {
+ case "'":
+ buffer.push("'");
+ break;
+ case '\\':
+ buffer.push('\\');
+ break;
+ case '/':
+ buffer.push('/');
+ break;
+ case 'b':
+ buffer.push('\b');
+ break;
+ case 'f':
+ buffer.push('\f');
+ break;
+ case 'n':
+ buffer.push('\n');
+ break;
+ case 'r':
+ buffer.push('\r');
+ break;
+ case 't':
+ buffer.push('\t');
+ break;
+ case 'u':
+ // interpret the following 4 characters as the hex of the unicode code point
+ var codePoint = parseInt(v.substring(i + 1, i + 5), 16);
+ buffer.push(String.fromCharCode(codePoint));
+ i += 4;
+ break;
+ default:
+ throw this.error_parsing(pos + i, "Illegal escape sequence: '\\" + c + "'");
+ }
+ escaping = false;
+ }
+ else
+ {
+ if (c == '\\')
+ {
+ escaping = true;
+ }
+ else
+ {
+ buffer.push(c);
+ }
+ }
+ }
+
+ return buffer.join('');
+ },
+
+ isString: function ()
+ {
+ var r = false;
+ var str = "";
+ var startpos = this.pos;
+ if (this.pos < this.expression.length && this.expression.charAt(this.pos) == "'")
+ {
+ this.pos++;
+ while (this.pos < this.expression.length)
+ {
+ var code = this.expression.charAt(this.pos);
+ if (code != "'" || str.slice(-1) == "\\")
+ {
+ str += this.expression.charAt(this.pos);
+ this.pos++;
+ }
+ else
+ {
+ this.pos++;
+ this.tokennumber = this.unescape(str, startpos);
+ r = true;
+ break;
+ }
+ }
+ }
+ return r;
+ },
+
+ isConst: function () {
+ var str;
+ for (var i in this.consts)
+ {
+ if (true)
+ {
+ var L = i.length;
+ str = this.expression.substr(this.pos, L);
+ if (i === str)
+ {
+ this.tokennumber = this.consts[i];
+ this.pos += L;
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ isOperator: function () {
+ var code = this.expression.charCodeAt(this.pos);
+ if (code === 43) // +
+ {
+ this.tokenprio = 0;
+ this.tokenindex = "+";
+ }
+ else if (code === 45) // -
+ {
+ this.tokenprio = 0;
+ this.tokenindex = "-";
+ }
+ else if (code === 124) // |
+ {
+ if (this.expression.charCodeAt(this.pos + 1) === 124)
+ {
+ this.pos++;
+ this.tokenprio = 0;
+ this.tokenindex = "||";
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (code === 42) // *
+ {
+ this.tokenprio = 1;
+ this.tokenindex = "*";
+ }
+ else if (code === 47) // /
+ {
+ this.tokenprio = 2;
+ this.tokenindex = "/";
+ }
+ else if (code === 37) // %
+ {
+ this.tokenprio = 2;
+ this.tokenindex = "%";
+ }
+ else if (code === 94) // ^
+ {
+ this.tokenprio = 3;
+ this.tokenindex = "^";
+ }
+ else
+ {
+ return false;
+ }
+ this.pos++;
+ return true;
+ },
+
+ isSign: function () {
+ var code = this.expression.charCodeAt(this.pos - 1);
+ if (code === 45 || code === 43) // -
+ {
+ return true;
+ }
+ return false;
+ },
+
+ isPositiveSign: function () {
+ var code = this.expression.charCodeAt(this.pos - 1);
+ if (code === 43) // -
+ {
+ return true;
+ }
+ return false;
+ },
+
+ isNegativeSign: function () {
+ var code = this.expression.charCodeAt(this.pos - 1);
+ if (code === 45) // -
+ {
+ return true;
+ }
+ return false;
+ },
+
+ isLeftParenth: function () {
+ var code = this.expression.charCodeAt(this.pos);
+ if (code === 40) // (
+ {
+ this.pos++;
+ this.tmpprio += 10;
+ return true;
+ }
+ return false;
+ },
+
+ isRightParenth: function () {
+ var code = this.expression.charCodeAt(this.pos);
+ if (code === 41) // )
+ {
+ this.pos++;
+ this.tmpprio -= 10;
+ return true;
+ }
+ return false;
+ },
+
+ isComma: function () {
+ var code = this.expression.charCodeAt(this.pos);
+ if (code === 44) // ,
+ {
+ this.pos++;
+ this.tokenprio = -1;
+ this.tokenindex = ",";
+ return true;
+ }
+ return false;
+ },
+
+ isWhite: function () {
+ var code = this.expression.charCodeAt(this.pos);
+ if (code === 32 || code === 9 || code === 10 || code === 13)
+ {
+ this.pos++;
+ return true;
+ }
+ return false;
+ },
+
+ isOp1: function () {
+ var str = "";
+ for (var i = this.pos; i < this.expression.length; i++)
+ {
+ var c = this.expression.charAt(i);
+ if (c.toUpperCase() === c.toLowerCase())
+ {
+ if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
+ break;
+ }
+ }
+ str += c;
+ }
+ if (str.length > 0 && (str in this.ops1))
+ {
+ this.tokenindex = str;
+ this.tokenprio = 5;
+ this.pos += str.length;
+ return true;
+ }
+ return false;
+ },
+
+ isOp2: function () {
+ var str = "";
+ for (var i = this.pos; i < this.expression.length; i++)
+ {
+ var c = this.expression.charAt(i);
+ if (c.toUpperCase() === c.toLowerCase())
+ {
+ if (i === this.pos || (c != '_' && (c < '0' || c > '9')))
+ {
+ break;
+ }
+ }
+ str += c;
+ }
+ if (str.length > 0 && (str in this.ops2))
+ {
+ this.tokenindex = str;
+ this.tokenprio = 5;
+ this.pos += str.length;
+ return true;
+ }
+ return false;
+ },
+
+ isVar: function () {
+ var str = "";
+ for (var i = this.pos; i < this.expression.length; i++)
+ {
+ var c = this.expression.charAt(i);
+ if (c.toUpperCase() === c.toLowerCase())
+ {
+ if (i === this.pos || (c != '_' && (c < '0' || c > '9')))
+ {
+ break;
+ }
+ }
+ str += c;
+ }
+ if (str.length > 0)
+ {
+ this.tokenindex = str;
+ this.tokenprio = 4;
+ this.pos += str.length;
+ return true;
+ }
+ return false;
+ },
+
+ isComment: function () {
+ var code = this.expression.charCodeAt(this.pos - 1);
+ if (code === 47 && this.expression.charCodeAt(this.pos) === 42)
+ {
+ this.pos = this.expression.indexOf("*/", this.pos) + 2;
+ if (this.pos === 1)
+ {
+ this.pos = this.expression.length;
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ scope.Parser = Parser;
+ return Parser;
+})(typeof exports === 'undefined' ? {} : exports);
+
+
+/*
+ * Object for storing the result of the expression evaluation.
+ */
+
+function ValidationResult()
+{
+ this.rightSide;
+ this.leftSide;
+ this.result;
+}
+
+//
+// Method to calculate the value of an expression
+//
+
+function calculateExpression(exprIn)
+{
+ if(exprIn == null){
+ return null;
+ }
+ var expr = Parser.parse(exprIn);
+
+ var result = expr.evaluate();
+
+ return result;
+}
+
+//
+// Method to retrieve an array of the applicable validation rules.
+//
+
+function getApplicableRules()
+{
+ var applicableRules = new Array();
+ var allValidationRules = storageManager.getValidationRules();
+ for(var i = 0; i < allValidationRules.length; i++)
+ {
+ if(isDataFieldsInCurrentForm(allValidationRules[i].leftSide.expression) && isDataFieldsInCurrentForm(allValidationRules[i].rightSide.expression))
+ {
+ applicableRules.push(allValidationRules[i]);
+ }
+ }
+
+ return applicableRules;
+}
+
+//
+// Method to retrieve the data fields in an Expression
+//
+
+function isDataFieldsInCurrentForm(expr)
+{
+ var regex1 = /#{\w+\.\w+}/g;
+ var match1 = expr.match(regex1);
+ if(match1 != null)
+ {
+ for (var i = 0; i<match1.length; i++)
+ {
+ var dataField = match1[i].substring(2, match1[i].length -1);
+ var ids = dataField.split('.');
+ var fieldId = '#' + ids[0] + '-' + ids[1] + '-val';
+
+ if(!($(fieldId).length > 0))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+//
+// Method to substitute the days value in an expression
+//
+
+
+function substituteDaysValues(expr, days)
+{
+ var regex1 = /\[days\]/g;
+ var outExpr = expr;
+ if(days != null)
+ {
+ outExpr = outExpr.replace(regex1, days);
+ }
+ else
+ {
+ outExpr = outExpr.replace(regex1, "0");
+ }
+ return outExpr;
+}
+
+//
+// Method to substitute the data value token for its value.
+//
+
+function substituteDataValues(expr, nullIfBlank)
+{
+ var regex1 = /#{\w+\.\w+}/g;
+
+ var outExpr = expr;
+
+ var match1 = expr.match(regex1);
+ if(match1 != null)
+ {
+ for (var i = 0; i<match1.length; i++)
+ {
+ var dataField = match1[i].substring(2, match1[i].length -1);
+ var ids = dataField.split('.');
+ var fieldId = '#' + ids[0] + '-' + ids[1] + '-val';
+ if($( fieldId ).val() != null && $( fieldId ).val() != '')
+ {
+ var value = $( fieldId ).val();
+ outExpr = outExpr.replace(match1[i], value);
+ }
+ else
+ {
+ if(nullIfBlank)
+ {
+ return null;
+ }
+ else
+ {
+ outExpr = outExpr.replace(match1[i], "0");
+ }
+
+ }
+ }
+ }
+
+ return outExpr;
+}
+
+//
+// Method to substitute a constant for its value.
+//
+
+
+function substituteConstantValues(expr)
+{
+ var regex = /C{\w+\}/g;
+ var outExpr = expr;
+ var match1 = expr.match(regex);
+ if(match1 != null)
+ {
+ for (var i = 0; i<match1.length; i++)
+ {
+ var constantFieldId = match1[i].substring(2, match1[i].length -1);
+ var constants = storageManager.getConstants();
+
+ for(var j = 0; j < constants.length; j++)
+ {
+ if(constants[j].uid == constantFieldId)
+ {
+ value = constants[j].value;
+ outExpr = outExpr.replace(match1[i], value);
+ }
+ }
+ }
+ }
+
+ return outExpr;
+}
+
+//
+// Method that substitutes all elements for their respective values.
+//
+
+function substituteValues(expr, nullIfBlank)
+{
+ var outExpression = expr;
+ outExpression = substituteDataValues(outExpression, nullIfBlank);
+ if(outExpression != null)
+ {
+ outExpression = substituteConstantValues(outExpression);
+ if(outExpression != null)
+ {
+ return substituteDaysValues(outExpression,null);
+ }
+ }
+ return null;
+}
+
+
+//
+// Method that validates an expression as true or false.
+//
+
+function validateExpression(leftSide, operator, rightSide)
+{
+ var result = new ValidationResult();
+ var leftSideValue = calculateExpression(leftSide);
+ var rightSideValue = calculateExpression(rightSide);
+
+ result.leftSide = leftSideValue;
+ result.rightSide = rightSideValue;
+
+ if(leftSideValue != null || operator == '[Compulsory pair]')
+ {
+
+ if(rightSideValue != null || operator == '[Compulsory pair]')
+ {
+ switch(operator)
+ {
+ case '==':
+ result.result = (leftSideValue == rightSideValue);
+ return result;
+ case '!=':
+ result.result = (leftSideValue != rightSideValue);
+ return result;
+ case '>':
+ result.result = (leftSideValue > rightSideValue);
+ return result;
+ case '>=':
+ result.result = (leftSideValue >= rightSideValue);
+ return result;
+ case '<':
+ result.result = (leftSideValue < rightSideValue);
+ return result;
+ case '<=':
+ result.result = (leftSideValue <= rightSideValue);
+ return result;
+ case '[Compulsory pair]':
+ result.result = (leftSide == null && rightSide == null) || (leftSide != null && rightSide != null);
+ return result;
+ default:
+ log("Validation rule has an invalid operator: " + operator);
+ throw "Invalid operator in expression";
+ }
+ }
+ else
+ {
+ result.result = true;
+ return result;
+ }
+ }
+ else
+ {
+ result.result = true;
+ return result;
+ }
+}
+
+//
+// Method to run the validation rules offline from localstorage, based on the filled in information.
+//
+
+function runOfflineValidations()
+{
+ var violations = new Array();
+ var validationRules = getApplicableRules();
+ for(var i = 0; i < validationRules.length; i++)
+ {
+ var leftSide = validationRules[i].leftSide.expression;
+ var rightSide = validationRules[i].rightSide.expression;
+
+ leftSide = substituteValues(leftSide, validationRules[i].leftSide.isNullIfBlank);
+ rightSide = substituteValues(rightSide, validationRules[i].rightSide.isNullIfBlank);
+
+ var result = validateExpression(leftSide, validationRules[i].operator, rightSide);
+
+ if(!result.result)
+ {
+ validationRules[i].validationResult = result;
+ violations.push(validationRules[i]);
+ }
+ }
+
+ var response = '';
+
+ if(violations.length == 0)
+ {
+ response = '';
+
+ }
+ else
+ {
+
+ var orgUnit = $('#selectedOrganisationUnit').val();
+ //Building error response
+ response += '<h3>' + i18n_validation_result + ' <img src="../images/warning_small.png"></h3>';
+ response += '<p class="bold">' + i18n_data_entry_screen_has_following_errors + '</p>';
+ if(violations.length > 0)
+ {
+ //Table header
+ response += '<h3>' + orgUnit + '</h3>';
+ response += '<table class="listTable" width="100%">';
+ response += '<tr>';
+ response += '<th>' + i18n_validation_rule + '</th>';
+ response += '<th>' + i18n_expression + '</th>';
+ response += '<th style="width:65px">' + i18n_left_side + '</th>';
+ response += '<th style="width:65px">' + i18n_operator + '</th>';
+ response += '<th style="width:65px">' + i18n_right_side + '</th>';
+ response += '</tr>';
+
+ var listRow = 'class="listRow"';
+ var listAltRow = 'class="listAlternateRow"';
+ var rowClass = listRow;
+
+ for(var i = 0; i < violations.length; i++)
+ {
+ response += '<tr>';
+ response += '<td style="height:32px" ' + rowClass + '>' + violations[i].description + '</td>';
+ //response += '<td ' + rowClass + '>' + $('<div/>').text(violations[i].rightSide.description).html() + ' ' + $('<div/>').text(violations[i].operator).html() + ' ' + $('<div/>').text(violations[i].leftSide.description).html() + '</td>';
+ response += '<td ' + rowClass + '>' + violations[i].rightSide.description + ' ' + violations[i].operator + ' ' + violations[i].leftSide.description + '</td>';
+ response += '<td ' + rowClass + '>' + (violations[i].validationResult.leftSide == null? '0':violations[i].validationResult.leftSide.format())+ '</td>';
+ response += '<td ' + rowClass + '>' + violations[i].operator + '</td>';
+ response += '<td ' + rowClass + '>' + (violations[i].validationResult.rightSide == null? '0':violations[i].validationResult.rightSide.format() ) + '</td>';
+ response += '</tr>';
+
+ if(rowClass == listRow)
+ {
+ rowClass = listAltRow;
+ }
+ else
+ {
+ rowClass = listRow;
+ }
+ }
+
+ response += '</table><br>';
+ }
+ }
+
+ return response;
+
+}
+
=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm 2013-07-18 10:10:22 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm 2013-09-24 08:26:24 +0000
@@ -45,6 +45,12 @@
var i18n_childrens_forms = '$encoder.jsEscape( $i18n.getString( "childrens_forms" ) , "'")';
var i18n_no_periods_click_prev_year_button = '$encoder.jsEscape( $i18n.getString( "no_periods_click_prev_year_button" ) , "'")';
var i18n_view_comment = '$encoder.jsEscape( $i18n.getString( "view_comment" ) , "'")';
+var i18n_data_entry_screen_has_following_errors = '$encoder.jsEscape( $i18n.getString( "data_entry_screen_has_following_errors" ) , "'")';
+var i18n_validation_rule = '$encoder.jsEscape( $i18n.getString( "validation_rule" ) , "'")';
+var i18n_expression = '$encoder.jsEscape( $i18n.getString( "expression" ) , "'")';
+var i18n_left_side = '$encoder.jsEscape( $i18n.getString( "left_side" ) , "'")';
+var i18n_operator = '$encoder.jsEscape( $i18n.getString( "operator" ) , "'")';
+var i18n_right_side = '$encoder.jsEscape( $i18n.getString( "right_side" ) , "'")';
</script>
<style type="text/css" media="print">