← Back to team overview

dhis2-devs team mailing list archive

[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 + ' &nbsp;<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 + '&nbsp;<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">