Source: jsoclo/orgnomAnalyzer.js

/**
 * Contsructor for OrgnomAnalyzer
 * @class
 * 
 * @classdesc Inherits from analyzerBase and provides Nomenclature specific 
 * feedback, scoring and syntax checking. <p> 
 * 
 * @extends analyzerBase
 * 
 * @tutorial NomenclatureQuestionFileFormat
 * 
 * @see {@link searcher}
 * 
 * @tutorial ArchitectureOverview
 */
function OrgnomAnalyzer(){
	//========================= Constructor =====================
	//super constructor
	analyzerBase.call(this);
	var that = this;
	
	// public instance variables
	/**
	 * The question score for this question only
	 */
	this.questionScore = config.ORGOM_MAX_QUESTION_SCORE; 
	
	//Load the question display and feedback information
	this.getQuestionFile();
	
	//=========================privileged methods =====================
	
	
	/**
	 * Overrides {@link analyzerBase} checkSyntax
	 * to provide syntax validation for Organic Nomenclature 
	 * answers.  The syntax validation is not complete, but rather,
	 * focuses on more common syntax errors. 
	 * 
	 * @returns a string of feedback based on the syntax of the answer
	 * 
	 * This method is called in getFeedback(String). 
	 * 
	 * This method replaces the functionality from tutor.java checkSyntax. It produces the 
	 * same result as that method given the same input.  
	 * 
	 */
	this.checkSyntax = function(answer){
		
		var response = ""; 
		var regex = null;
		var substr = ""; 
		var match = null;
		var openBrackets = []; 
		var closedBrackets = []; 
		
		if(/\d[^\-][a-z]/i.test(answer)){
			response += "Syntax error: numbers must be seperated from letters by hyphens.\n";
		}
		
		if(/[a-z][^\sa-z][a-z]/i.test(answer)){
			response += "Syntax error: letters must be seperated from each other only by spaces.\n";
		}
		
		if(/[RSEZ][^\),]/.test(answer)){
			response += "Syntax error: stereogenic centers should be in brackets and seperated by commas.\n";
		}
		
		if(/[A-DF-MO-QT-Y]/.test(answer)){
			response += "Syntax error: make sure you only use uppercase letters to define stereogenic centers and the locant N.\n";
		}
		
		//deal with numbers found inside of [] 
		regex = /\[([^\]]*\d[^\[]*)\]/g;
		match = regex.exec(answer);
		while (match != null){
			substr = match[1]; 
			//there is at least 1 digit in substr at this point
			if(/\d[^\.|\d]\d/i.test(substr)){
				response += "Syntax error: numbers in square brackets must be seperated from eachother by periods.\n";
				break;  //avoid duplicates of the same error message
			}		
			//next match, if there is one. 
			match = regex.exec(answer);
		}
		
		//deal with numbers not in [] 
		
		//find the index(es) of open square brackets
		regex = /\[/g;
		match = regex.exec(answer);
		while (match != null){
			openBrackets.push(match.index);
			match = regex.exec(answer);
		}
		
		//find the index(es) of closed square brackets
		regex = /\]/g;
		match = regex.exec(answer);
		while (match != null){
			closedBrackets.push(match.index);
			match = regex.exec(answer);
		}
		
		//before the first [ value  
		var eob = 0; 
		if(openBrackets.length > 0){
			eob = openBrackets[0]; 
		} 
		// after the last ] value	
		var boe = 0; 
		if(closedBrackets.length >0){
			boe = closedBrackets[closedBrackets.length -1]; 
		}
		
		//mmb will be true if the number of brackets is mismatched. 
		var mmb = openBrackets.length != closedBrackets.length; 
		
	
		//find all numbers not separated by commas 
		regex = /(\d\s*[^,|\d]\s*\d)/g; 
		match = regex.exec(answer);	
		while (match != null){
			//before first [, after last ], mismatched and no square brackets in answer. 
			if(match.index < eob || match.index > boe || mmb){
				response += "Syntax error: numbers not in square brackets must be seperated from eachother by commas.\n";
				//alert("error on : " + match[1]);
				break;
			}
			//check in between ] and [ 
			var inSquare = false;
			//iterate over the equally sized arrays
			for(var i = 0; i < openBrackets.length ; i++){
				if(match.index > openBrackets[i] && match.index < closedBrackets[i]){
					inSquare = true;
					break; 
				}
				
			}
			if(!inSquare){ //then it's an error. 
				response += "Syntax error: numbers not in square brackets must be seperated from eachother by commas.\n";
				break;
				
			}
			//next match, if there is one. 
			match = regex.exec(answer);
		}
		
		return response;
		
	};
	
	
	/**
	 * Overrides {@link analyzerBase getFeedback}
	 * to provide feedback for Organic Nomenclature 
	 * answers.  
	 * @param {String} answer 
	 * @returns a string of feedback based on the answer content.
	 * 
	 * This method should be called from the presentation layer. 
	 * 
	 */
	this.getFeedback = function(answer){
		var response = "";
		
		if(this.isCorrect(answer)){
			return response + this.getCorrectMsg(); 
		}
		//if its not correct then...
		
		response +=  this.checkSyntax(answer);

		var commonSearcher = this.getCommonSearcher(); 
	
		if(commonSearcher != null){
			var tmp = commonSearcher.search(answer);
			if(tmp != ""){response +=  tmp +"\n";}
		}
		
		var searchers = this.getSearchers();
		for (var int = 0; int < searchers.length; int++) {
			var element =  searchers[int];
			var tmp = element.search(answer);
			if(tmp != ""){response += tmp + "\n";} 
			
		}

		//finally check the number of loci
		var lse = this.getLociSearcher(); 
		if( lse != null){
			response += lse.search(answer) + "\n"; 
		}
		
		return response; 
		
	};
	
}

//inherit from the analyzerParent
OrgnomAnalyzer.prototype = new analyzerBase();
OrgnomAnalyzer.prototype.constructor = OrgnomAnalyzer; 


//=========================Public methods =====================

	/**
	 * 
	 * getCorrect() deals with all scoring.  getCorrect must be 
	 * called for any scoring to be be dealt with. 
	 * 
	 * Side affect:  will calculate the score on the first 
	 * and only the first correct answer. 
	 */
	OrgnomAnalyzer.prototype.getCorrect = function (answer){
	
		if(!this.isCompleted() && this.isCorrect(answer) ){
			this.calcScore();
			this.setComplete(); 			
			return true;
					
		}else if(this.questionScore > 0){
			this.questionScore--;
		}
		return false;
			
	};
	

	/**
	 * Gives the question a zero and calculates the new average 
	 * for the category. 
	 */
	OrgnomAnalyzer.prototype.forfeitQuestion = function (){
		
		this.questionScore = 0; 
		this.setComplete(); 
		this.calcScore();
			
	};
		
	/**
	 * 
	 *	This method should be called by either OrgnomAnalyzer.getCorrect(), 
	 *  or by OrgnomAnalyzer.forfeitQuestion() only.  
	 *  
	 *  It uses the cookie values to calculate the average score for 
	 *  the section.  It keeps, in cookies, the total score and the number 
	 *  of questions tried and simply calculates the average.  
	 *  
	 *  It sets the resultant average by calling analyzerBase.setScore.  
	 * 
	 */
	
	OrgnomAnalyzer.prototype.calcScore = function(){
		
		var theQScore = this.questionScore/config.ORGOM_MAX_QUESTION_SCORE*100; 
		var theRunningScore = new Number($.cookie("score"));
		var theNumberOfQuestions = new Number($.cookie("numQuestionsTried")) + 1; //number of questions
		var theNewRunningScore = theRunningScore + theQScore; 
		
		
		$.cookie("score",theNewRunningScore, {path: '/'});
		$.cookie("numQuestionsTried",theNumberOfQuestions, {path: '/'});
		
		
		this.setScore(theNewRunningScore/theNumberOfQuestions); // The new score

		
	};