/**
* Config is a Static class so does not need to be instantiated. If you wich to however a default constructor is provided.
* @class
*
* @classdesc config is a static class that is used to obtain literals and other configuration information for
* various Javascript Organic Chemistry Learning Objects applications. <p> You should view the javascript file to see the members as
* they are ommitted here so as to keep the script itself readable.
*
* If a literal is being used or should be configurable then it must be created here, and only here.
*
* @tutorial ArchitectureOverview
*
*/
function config() {
}
/*
* testing related section: Contains URLs and booleans
* to use while developing and/or testing the application.
* also contains the LIVE site URL.
*
*/
config.WHICH_URL = "VM";
config.TEST_BASEURL = "http://localhost:8080/JSOCLOWebsite/";
config.CPANEL_URL = "http://chem.cpsc.ucalgary.ca/WebContent/";
config.LIVE_BASEURL = "/courses/351/orgnom/javanom";
config.TEST_BASEURL_VM = "http://192.168.155.132:8080/JSOCLOWebsite/";
/*
* application behaviour related section: Contains booleans to control
* the application behaviour. Things like cycling the next and previous buttons,
* alerting hints and persist messges or showing them in the response area, locking
* questions. The number of max attempts.. ect.
*/
config.UNLOCK_QUESTION = false; //for Nomenclature
config.CYCLE_NEXT_PREV = true;
config.ALERT_HINTS_MSGS = false;
config.ALERT_PERSIST_MSGS = false;
config.DONT_SHOW_FEEDBACK_ON_CORRECT = false;
config.SPEC_UNLOCK_ALL_QUESTIONS = false;
config.APPEND_CORRECT_VALUES = true;
config.PREPEND_INCORRECT_VALUES = true;
config.MAXATTEMPTS = 5;
config.ALLOW_HINTS_AT = 3;
/*
* User interaction section: Strings that are displayed to the
* user during there interactions with the applicaiton(s).
*/
config.NO_ANSWER = "Please enter an answer before submitting.";
config.DUPLICATE_ANSWER = "Sorry, but you tried that answer already.";
config.ALREADY_CORRECT = " \n(you've finished the question previously)";
config.FORFEIT = " \n(you viewed the solution before getting it correct)";
config.FORFEIT_CONFIRMATION = "If you view the solution you'll receive a zero on the question.\nSelect ok to view the solution.";
config.NO_CATEGORY_CHANGE = "You need to finish the current question first.\n";
config.NO_NEXT_QUESTION = "You're on the last question of the category, \nchoose a previous question or new category to move on";
config.NO_PREV_QUESTION = "You're on the first question of the category, \nchoose another question or new category to move on";
config.UNRECOGNIZED_ANWER = "We weren't able to make sense of the answer you gave. You'll have to try another";
config.PERSIST_MESSAGES = [ "It'll be worth it to persist a little longer.", "I think you're close, keep at it.",
"Patience grasshopper. Keep at it!", "I bet you'll get it next try. Keep going",
"Just give it one more try. You'll get it soon." ];
config.HINTS_ALERT_MSG = "Hints are available now";
config.TRY_FIRST = "Try another answer first";
/*
* Error and support section: Contains error messages and support information
* to display when required.
*/
config.SUPPORT_EMAIL_STRING = " Dr. Ian Hunt (irhunt@ucalgary.ca) ";
config.STN = "/questions/";
config.QDIR_PREFIX = "/question";
config.ANSWER_FILE_NAME = "question.txt";
config.AF_LOAD_ERROR = "We were unable to load that questions information properly. \n\nPlease try again later.\n\n"
+ "If the problem persists please report it to " + config.SUPPORT_EMAIL_STRING
+ "giving him the information below. \n\nThank you and sorry for the trouble.";
config.NO_HELP_LINK = "A help page for the question isn't available";
config.FG_NOT_FOUND = "The chemicalInformation class does not have information for the following functional group(s): ";
/*
* Nomenclature specific section: Strings specific to nomenclature
*
*/
config.ORGNOM_FCLASS = "orgnom";
config.DEFAULT_NOM_TYPE_MSG = "Enter the IUPAC name ";
config.ORGOM_MAX_QUESTION_SCORE = 10;
config.ORGNOM_QNAME = "Question - ";
config.ORGNOM_TITLE = "Basic Organic Nomenclature";
config.ORGNOM_CONF = "json/orgnom.json";
/*
* Spectroscopy specific section: Strings specific to spectroscopy
*/
config.SPEC_FCLASS = "spectroscopy";
config.SPEC_TITLE = "Interactive Spectroscopy";
config.SPEC_QNAME = "Question - ";
config.SPEC_MASS_IMG = "MS.gif";
config.SPEC_IR_IMG = "IR.gif";
config.SPEC_CARBON_IMG = "C.gif";
config.SPEC_HYDROGEN_IMG = "H.gif";
config.SPEC_ALLSPEC_PAGE = "allSpectra.htm";
config.SPEC_ANSWER_IMG = "ans.gif";
config.SPEC_ANSWER_PAGE = "Ans.html";
config.SPEC_MAX_QUESTION_SCORE = 10;
config.SPEC_CONF = "json/spectroscopy.json";
config.SPEC_MAX_NUMBER_OF_QUESIONS = 40;
/*
* The number of questions in the largest category. this value is used in rdUtilis as a starting point in determining the number
* of questions for each category in Nomenclature
*
* @type {Number}
*/
config.MAX_NUM_QUESTIONS = 40;
/**
* @returns One of the config.PERSIST_MESSAGES, randomly chosen.
*/
config.getPersistMessage = function() {
var min = 0;
var max = config.PERSIST_MESSAGES.length - 1;
var index = Math.floor(Math.random() * (max - min + 1) + min);
return config.PERSIST_MESSAGES[index];
};
/**
* Returns one of the Base URLs.
*/
config.getBaseUrl = function() {
switch (config.WHICH_URL) {
case "VM":
return config.TEST_BASEURL_VM;
break;
case "CPANEL":
return config.CPANEL_URL;
break;
case "LOCAL":
return config.TEST_BASEURL;
break;
case "LIVE":
return config.LIVE_BASEURL;
break;
default:
throw "Which URL is not set properly in config";
}
};
/**
* Relies on Jquery
*
* Prevents anything other than the input box submitting the 'Enter' key.
*
* @param e
*/
function preventReturn(e) {
if (e.which == 13) {
e.preventDefault();
}
}
/**
*
* {@link RegExp} RegExp.escape is used to escape any special chars in a string, so that RegExp will be ok with it. We use it in the
* Searcher class when creating regular expressions out of the strings in the question file.
*
* It does not escape | or - as they appear in the question answers
*
* @param string
* @returns string with regular expression special chars escaped.
*/
RegExp.escape = function(string) {
return string.replace(/[\/\\^$*+?.()[\]{}]/g, '\\$&');
};
/**
* Sort comparator to sort strings in a case insensitive manner.
*
* @param a
* @param b
* @returns {Number}
*/
function compareCaseInsenstive(a, b) {
var st1 = a[0].toLowerCase();
var st2 = b[0].toLowerCase();
if (st1 < st2) {
return -1;
}
if (st1 > st2) {
return 1;
}
return 0;
}
/**
* Molecular formula comparator; used
* to sort the molecular formula array in molecularFormulaSearcher
* @param a a - A string of at least 1 in length
* @param b b - a string of at least 1 in length
*
* The ordering is alphabetic except for halogens which go at the
* end of the formula but are otherwise in alphabetic order themselves.
*/
function compareMolFormula(a,b){
//if they are both NOT halogens, it's alphabetic.
if(!chemicalInformation.isHalogen(a) && !chemicalInformation.isHalogen(b)){
return compareCaseInsenstive(a, b);
}
//if the are both halogens, it's alphabetic
if(chemicalInformation.isHalogen(a) && chemicalInformation.isHalogen(b)){
return compareCaseInsenstive(a, b);
}
// now one or the other is halogen, but not both.
// if it is a, then a is greater than b.
if(chemicalInformation.isHalogen(a)){
return 1;
}
return -1;
}
/**
* Used to sort numerically
*
* @param a
* @param b
* @returns {Number}
*/
function compareNumber(a, b) {
return a - b;
}
// =========================Supporting Classes =====================
/**
* Constructor for a new Searcher
*
* @class
*
* @param {RegExp}
* regEx the regular expression used to test the answers with.
* @param {String}
* foundMsg the message to return when the test is true.
* @param {String}
* notFoundMsg the message to return when the test is false.
*
* @classdesc A supporting class used to check answers against the values that were parsed from the question file. It can test the
* answer, check for an exact match and return positive and negative messages based on the testing.
* <p>
* It serves as a base class for other inheriting searchers. (except lociSearcher).
*
* @see {@link chargeSearcher}
* @see {@link functionalGroupsSearcher}
* @see {@link molecularFormulaSearcher}
* @see {@link molecularWeightSearcher}
* @see {@link numHydrogenSearcher}
* @see {@link numCarbonSearcher}
* @see {@link numTypesHydrogenSearcher}
* @see {@link numTypesCarbonSearcher}
* @see {@link ringsSearcher}
*
* @tutorial ArchitectureOverview
*/
function searcher(regEx, foundMsg, notFoundMsg) {
// private undocumented members
// RegExp.escape is something I added to RegExp. Its
// definition is in jsocloUtils.js Go look at it now
var regString = regEx;
var regExp = null;
if (regEx != null) {
regExp = new RegExp(RegExp.escape(regEx), "i");
} else {
regExp = new RegExp("");
}
var positiveMsg = "";
var negativeMsg = "";
// we don't want 'null' to be displayed
if (foundMsg == null) {
foundMsg = "";
}
if (notFoundMsg == null) {
notFoundMsg = "";
}
// clean up the input. trim spaces
// and replace end of lines chars.
positiveMsg = foundMsg.replace(/^\s+|\s+$/g, '');
negativeMsg = notFoundMsg.replace(/^\s+|\s+$/g, '');
positiveMsg = positiveMsg.replace(/\n|\r|\f/g, '');
negativeMsg = negativeMsg.replace(/\n|\r|\f/g, '');
// =========================privileged methods =====================
/**
* Uses its regular expression to test an answer and
* will return the approriate message
* from the question file.
*
* @param {String}
* answer the input to test
* @returns {String}
* @method
* @access protected
*/
this.search = function(answer) {
// alert(regExp);
if (regExp.test(answer)) {
return positiveMsg;
}
return negativeMsg;
};
/**
* Tests an answer to determine its correctness.
*
* @param {String}
* answer, the input to test
* @returns {Booean} True if the answer is correct. False otherwise.
* @method
* @access protected
*/
this.test = function(answer) {
return regExp.test(answer);
};
/**
* @returns {RegExp}
*/
this.getRegExp = function() {
return regExp;
};
/**
* @returns {String}
*/
this.getfoundMsg = function() {
return positiveMsg;
};
/**
* @returns {String}
*/
this.getNotFoundMsg = function() {
return notFoundMsg;
};
/**
* @returns {String}
*/
this.getRegExpString = function() {
return regString;
};
}
// =======================================================================================
/**
* chemicalInformation does not need to be instantiated as all of its members and methods
* are static, however, if you really want to, a default contructor.
* @class
*
* @classdesc Static class that contains chemical information and functions
* that are needed in the spectroscopy application.
*
* @tutorial ArchitectureOverview
*
*
*/
function chemicalInformation() {
}
/**
* A data structure used to map functional group
* names to an array of regular expressions to match
* agains SMILE answers.
*
* Used in the functionalGroupsSearcher class.
*
* @example
*
* {"functional Group Name" : [regexp1,regexp2,..,regexpN] }
*/
chemicalInformation.functionalGroups = {
"alcohol" : [ /[^=]O[\)|$|\s*]/],
"alkane" : [ /CC/ , /C\d*C/ ],
"alkene" : [ /C=C/, /cc/, /c\d*c/ ],
"alkyne" : [ /C#C/ ],
"amine" : [ /CN/, /NC/, /Nc\d*/, /cn/, /nc/, /c\d*n/ ],
"acyl Halides" : [ /C\(=O\)F/, /O=C\(F\)/, /C\(=O\)Cl/, /O=C\(Cl\)/, /C\(=O\)Br/, /O=C\(BR\)/, /C\(=O\)I/, /O=C\(I\)/ ],
"aldehyde" : [ /CC=O/, /O=CC/ ],
"ether" : [/COC/i,/c\d+Oc/i, /C\(OC/i],
"ketone" : [/CC\(C\)=O/, /O=C\d*CC/i, /CC\(=O\)C\d*/, /cc\d*c=0/],
"ester" : [/CC\(=O\)OC/i, /COC\(C\)=O/]
};
/**
* @returns {Array} an array with molecular symbols used as
* keys. Keys map to the molecular weight of the symbol.
*/
chemicalInformation.getMolWeightMap = function() {
var weightMap = new Array();
weightMap["C"] = 12;
weightMap["F"] = 19;
weightMap["H"] = 1;
weightMap["I"] = 127;
weightMap["N"] = 14;
weightMap["O"] = 16;
weightMap["P"] = 31;
weightMap["S"] = 32;
weightMap["Cl"] = 35;
weightMap["Br"] = 80;
return weightMap;
};
/**
* @returns {Array} an array with molecular symbols used as
* keys. Keys map to the valence of the symbol.
*/
chemicalInformation.getValenceMap = function() {
var valences = new Array();
valences["C"] = 4;
valences["F"] = 1;
valences["H"] = 1;
valences["I"] = 1;
valences["N"] = 3;
valences["O"] = 2;
valences["P"] = 3;
valences["S"] = 2;
valences["Cl"] = 1;
valences["Br"] = 1;
return valences;
};
/**
* The Halogens in an array (F,Cl,Br,I)
*/
chemicalInformation.halogens = [ "F", "Cl", "Br", "I" ];
/**
*
* @param {String} candidateHalogen Presumably an Atomic symbol
* @returns {Boolean} true if the candidate is a Halogen.
*/
chemicalInformation.isHalogen = function(halc){
for (var i = 0; i < chemicalInformation.halogens.length; i++) {
var hal = chemicalInformation.halogens[i];
if(halc == hal){
return true;
}
}
return false;
};
/**
*
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number} the number of halogen atoms found
*/
chemicalInformation.countHalogens = function(tokens) {
var count = 0;
for (var i = 0; i < chemicalInformation.halogens.length; i++) {
var halogen = chemicalInformation.halogens[i];
count += chemicalInformation.countAtomsOf(halogen, tokens);
}
return count;
};
/**
* This will count all atoms, except Hydrogen atoms.
*
* @param {String}
* symbol symbol of the element you want to count
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number} the number of atoms of type 'symbol' found
*/
chemicalInformation.countAtomsOf = function(symbol, tokens) {
var token;
var count = 0;
for (var i = 0; i < tokens.length; i++) {
token = tokens[i];
if (token.type == "atom") {
if (token.symbol.toString() == symbol) {
count++;
}
}
}
return count;
};
/**
* Counts the number of rings in the answer.
*
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number} the number of ring closures
*/
chemicalInformation.countRings = function(tokens) {
var count = 0;
var closures = new Array();
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "ring-closure") {
if (closures[token.index] != "x") {
closures[token.index] = "x";
count++;
continue;
}
}
}
return count;
};
/**
* We use the SMILE here because of c1ccccc1 : ie small letter c. We use the SMILE instead of the aromatic property in the tokens
* because JSME will only produce small c's when the the double bonded hexagon tool is used. In all other cases it produce C=C.
* Those we count using the tokens.
*
* <p>
* Relying on this property of JSME allows us to count the number of small c's and then divide the answer by two to get the number
* of double bonds.
*
* <p>
* The aromatic property in the tokens could be used for the same purpose but has 2 problems for me: 1. I don't know if other atoms
* can be aromatic or not and 2. If they can the code needed would be significantly greater to do this.
*
* <p>
* We can change to the tokens later if it is needed.
*
*
* @param {SMILE} smile
* the SMILE string
* @returns {Number} The number of double bonds
*/
chemicalInformation.countDoubleBonds = function(smile) {
var count = 0;
var smallC = 0;
// first count the small c's in the SMILE
for (var i = 0; i < smile.length; i++) {
var char = smile[i];
if (char == 'c') {
smallC++;
}
}
// small C should be evenly divisible by 6
// and the number of double bonds is smallC/2
if (smallC % 6 != 0) {
//alert("chemicallInformation.countDoublebonds: Calculation for SMILE " + smile + " produced an unexpected result");
}
count = ~~(smallC / 2); // shift it to an INT.
var tokens = Parser.parse(smile);
// now we'll count the number of double bonds in the
// tokens.
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "bond") {
if (token.order == 2) {
count++;
}
}
}
return count;
};
/**
* Counts the number of triple bonds in the answer.
*
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number} the number of triple bonds found
*/
chemicalInformation.countTripleBonds = function(tokens) {
var count = 0;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "bond") {
if (token.order == 3) {
count++;
}
}
}
return count;
};
/**
* Since H will not be in the smile (we use JSME without explicit Hydrogens). counting them is not as easy as the others. We are
* going to use the Index of Hydrogen Deficiency to do this. The formula...
* <br>
* Y = -1(2IHD -2X -2) where <br>
* Y = #H + #Halogens + #N and <br>
* X = #C + #N and <br>
* IHD = #rings + #double bonds + 2(#triple bonds)<br><br>
*
* so, #H = Y -#halogens - #N<br>
*
*
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number} of Hydrogens in the compound.
*/
chemicalInformation.countHydrogens = function(smile, tokens) {
var hydrogens = 0;
var N = chemicalInformation.countAtomsOf("N", tokens);
var x = N + chemicalInformation.countAtomsOf("C", tokens);
var hal = chemicalInformation.countHalogens(tokens);
var IHD = chemicalInformation.calculateIHD(smile, tokens);
var y = -1 * (2 * IHD - 2 * x - 2);
hydrogens = y - hal - N;
return hydrogens;
};
/**
* Calculates the Index of Hydrogen Deficiency (IHD)
*
* @param smile
* @param {tokens} tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number}
*/
chemicalInformation.calculateIHD = function(smile, tokens) {
var count = 0;
count += chemicalInformation.countRings(tokens);
count += chemicalInformation.countDoubleBonds(smile);
count += 2 * chemicalInformation.countTripleBonds(tokens);
return count;
};
/**
* Counts the charge of the answer
*
* @param {tokens}
* tokens can be obtained from smidge by Parse.parse(smile)
* @returns {Number}
*/
chemicalInformation.countCharge = function(tokens) {
var count = 0;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "atom") {
count += token.charge;
}
}
return count;
};
/**
* Counts the number of disctinct atoms in the answer.
*
* @param {tokens}
* @returns {Array} of strings where each element is in the answer at
* least one time. No duplicate strings exist in the Array.
*/
chemicalInformation.getDistinctAtoms = function(tokens) {
var atoms = new Array();
var alreadyThere = new Array();
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "atom") {
if (alreadyThere[token.symbol.toString()] != "x") {
alreadyThere[token.symbol.toString()] = "x";
atoms.push(token.symbol.toString());
}
}
}
return atoms;
};
/**
* Searcher for Molecular Formula
*
* @class
*
* @extends searcher
* @param {String}
* mf the molecular formula from the question file.
*
* @tutorial ArchitectureOverview
*
*/
function molecularFormulaSearcher(mf, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
// the question files use Z (Br) and Y(Cl)
// we don't, so we'll replace any Z and Y in
// the formula.
var newMF = mf.replace(/Y/ig, "Cl").replace(/Z/ig, "Br");
var molecularFormula = newMF;
var tmp = "";
/**
* override {@link searcher#test}. will return true if the molecular formula of the answer
* is equal to the molecular formula of the question.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
var molForm = new Array();
var formula = "";
var atoms = chemicalInformation.getDistinctAtoms(tokens);
var numH = chemicalInformation.countHydrogens(smileAnswer, tokens);
//add the elements with their counts to an array
for (var i = 0; i < atoms.length; i++) {
var atom = atoms[i];
var numAtom = chemicalInformation.countAtomsOf(atom, tokens);
molForm.push(atom + (numAtom > 1 ? numAtom : ""));
}
//now we deal with Hydrogen
if(numH > 0){
molForm.push("H" + (numH > 1 ? numH : ""));
}
//sort the array
molForm.sort(compareMolFormula);
//build the formula
for (var i = 0; i < molForm.length; i++) {
var mol = molForm[i];
formula += mol;
}
if (formula == molecularFormula) {
return true;
}
tmp = formula;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
if (config.APPEND_CORRECT_VALUES) {
return this.getfoundMsg() + "<span class='correctColor'>(" + molecularFormula + ")</span>";
}
return this.getfoundMsg();
}
if (config.PREPEND_INCORRECT_VALUES) {
var me = tmp;
tmp = "";
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
return this.getNotFoundMsg();
};
this.getMolecularFormula = function() {
return molecularFormula;
};
}
// inherit
molecularFormulaSearcher.prototype = new searcher();
molecularFormulaSearcher.prototype.constructor = molecularFormulaSearcher;
/**
* Searcher for Molecular weight.
*
* @class
*
* @extends searcher
* @param {Number}
* mw molecular weight
*
* @tutorial ArchitectureOverview
*
*/
function molecularWeightSearcher(mw, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var molecularWeight = new Number(mw).valueOf();
var tmp = 0;
/**
* override {@link searcher#test}. will return true if the molecular weight of the answer
* is equal to the molecular weight of the question.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
var totalWeight = 0;
var weightMap = chemicalInformation.getMolWeightMap();
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type == "atom") {
totalWeight += weightMap[token.symbol.toString()];
}
}
// now count the hydrogens
var hydrogens = chemicalInformation.countHydrogens(smileAnswer, tokens);
// alert("Hydrogens: " + hydrogens);
totalWeight += hydrogens;
// alert("Total weight: " + totalWeight);
if (totalWeight == molecularWeight) {
return true;
}
tmp = totalWeight;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
if (config.APPEND_CORRECT_VALUES) {
return this.getfoundMsg() + "<span class='correctColor'>(" + molecularWeight + ")</span>";
}
return this.getfoundMsg();
}
if (config.PREPEND_INCORRECT_VALUES) {
var me = tmp;
tmp = 0;
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
return this.getNotFoundMsg();
};
this.getMolecularWeight = function() {
return molecularWeight;
};
}
// inherit
molecularWeightSearcher.prototype = new searcher();
molecularWeightSearcher.prototype.constructor = molecularWeightSearcher;
/**
* Searcher for Number of Carbons.
*
* @class
*
* @extends searcher
* @param {Number}
* nm number of carbons
*
* @tutorial ArchitectureOverview
*
*/
function numCarbonSearcher(nm, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var numCarbon = new Number(nm).valueOf();
var tmp = 0;
/**
* override {@link searcher#test}. will return true if the number of Carbons in the answer
* is equal to the number of Carbons in the question.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
var numC = chemicalInformation.countAtomsOf("C", tokens);
if (numC == numCarbon) {
return true;
}
tmp = numC;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
if (config.APPEND_CORRECT_VALUES) {
return this.getfoundMsg() + "<span class='correctColor'>(" + numCarbon + ")</span>";
}
return this.getfoundMsg();
}
if (config.PREPEND_INCORRECT_VALUES) {
var me = tmp;
tmp = 0;
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
};
this.getNumCarbons = function() {
return numCarbon;
};
}
// inherit
numCarbonSearcher.prototype = new searcher();
numCarbonSearcher.prototype.constructor = numCarbonSearcher;
/**
* Searcher for Number of types of Carbon.
*
* @class
*
* @extends searcher
* @param {Number}
* nm number of types of Carbon
*
* @tutorial ArchitectureOverview
*/
function numTypesCarbonSearcher(nm, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var numCarbon = nm;
/**
* override {@link searcher#test}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
return "Needs to be implemented)";
};
this.getNumTypesCarbons = function() {
return numCarbon;
};
}
// inherit
numTypesCarbonSearcher.prototype = new searcher();
numTypesCarbonSearcher.prototype.constructor = numTypesCarbonSearcher;
/**
* Searcher for Number of hydrogens.
*
* @class
*
* @extends searcher
* @param {Number}
* nm number of hydrogens
*
* @tutorial ArchitectureOverview
*/
function numHydrogenSearcher(nm, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var numHydrogen = new Number(nm).valueOf();
var tmp = 0;
/**
* override {@link searcher#test}. will return true if the number of hydrogens in the answer
* is equal to the number of Carbons in the question.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
numH = chemicalInformation.countHydrogens(smileAnswer, tokens);
if (numH == numHydrogen) {
return true;
}
tmp = numH;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
if (config.APPEND_CORRECT_VALUES) {
return this.getfoundMsg() + "<span class='correctColor'>(" + numHydrogen + ")</span>";
}
return this.getfoundMsg();
}
if (config.PREPEND_INCORRECT_VALUES) {
var me = tmp;
tmp = 0;
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
};
this.getNumHydrogens = function() {
return numHydrogen;
};
}
// inherit
numHydrogenSearcher.prototype = new searcher();
numHydrogenSearcher.prototype.constructor = numHydrogenSearcher;
/**
* Searcher for Number of types of hydrogens.
*
* @class
*
* @extends searcher
* @param {Number}
* nm number of types of hydrogens
*
* @tutorial ArchitectureOverview
*/
function numTypesHydrogenSearcher(nm, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var numHydrogen = nm;
/**
* override {@link searcher#test}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
return "Needs to be implemented)";
};
this.getNumTypesHydrogens = function() {
return numHydrogen;
};
}
// inherit
numTypesHydrogenSearcher.prototype = new searcher();
numTypesHydrogenSearcher.prototype.constructor = numTypesHydrogenSearcher;
/**
* Searcher for charge.
*
* @class
*
* @extends searcher
* @param {Number}
* nm the charge from the question file
*
* @tutorial ArchitectureOverview
*
*/
function chargeSearcher(nm, wrongMsg) {
searcher.call(this, null, "", wrongMsg);
var charge = nm;
var tmp = 0;
/**
* override {@link searcher#test}. will return true if the charge of the answer
* is equal to the charge of the question.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
var fCharge = chemicalInformation.countCharge(tokens);
if (fCharge == charge) {
return true;
}
tmp = fCharge;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
return this.getfoundMsg();
}
if(config.PREPEND_INCORRECT_VALUES){
var me = tmp;
tmp = 0;
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
return this.getNotFoundMsg();
};
this.getCharge = function() {
return charge;
};
}
// inherit
chargeSearcher.prototype = new searcher();
chargeSearcher.prototype.constructor = chargeSearcher;
/**
* Searcher for rings.
*
* @class
*
* @extends searcher
* @param {Number}
* nm the number of rings from the question file.
*
* @tutorial ArchitectureOverview
*/
function ringsSearcher(nm, wrongMsg) {
searcher.call(this, null, "", wrongMsg);
var rings = nm;
var tmp = 0;
/**
* override {@link searcher#test}. will return true if the number of rings in the answer
* is equal to the number of rings in the question.
* @method
* @access privileged
* @param smileAnswer
*
*/
this.test = function(smileAnswer) {
var tokens = Parser.parse(smileAnswer);
var numR = chemicalInformation.countRings(tokens);
if (numR == rings) {
return true;
}
tmp = numR;
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (this.test(smileAnswer)) {
return this.getfoundMsg(); //empty message
}
if(config.PREPEND_INCORRECT_VALUES){
var me = tmp;
tmp = 0;
return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
}
return this.getNotFoundMsg();
};
this.getNumberOfRings = function() {
return rings;
};
}
// inherit
ringsSearcher.prototype = new searcher();
ringsSearcher.prototype.constructor = ringsSearcher;
/**
* Searcher for Functional Groups.
*
* @class
*
* @extends searcher
* @param {String}
* fgString fgString is a string of the following form.
* <p>
* FG1,FG2,..FGN,
* @param {String} AllFoundMsg
* @param {String} AllNotFoundMsg
*
* @tutorial ArchitectureOverview
*
*
*/
function functionalGroupsSearcher(fgString, correctMsg, wrongMsg) {
searcher.call(this, null, correctMsg, wrongMsg);
var fGroups = null;
var noGroups = false;
// deal with when we instatiate one
//in the analyzer constructor and reset function.
if (fgString == null) {
noGroups = true;
fgString = "";
}
fgString = fgString.replace(/,\s*$/, '');
fGroups = fgString.split(",");
if (noGroups) {
fGroups = new Array(); // empty array
}
/**
* override {@link searcher#test}. will return true if all of the functional groups
* in the quesion file are found in the answer.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.test = function(smileAnswer) {
var regexps = null;
var regex = null;
var fGroupsCount = 0;
var errors = config.FG_NOT_FOUND;
for (var i = 0; i < fGroups.length; i++) {
regexps = chemicalInformation.functionalGroups[fGroups[i]];
try {
for (var j = 0; j < regexps.length; j++) {
regex = regexps[j];
//alert(regex);
if (regex.test(smileAnswer)) {
fGroupsCount++; //one of each group is enough to find.
break;
}
}
} catch (e) {
errors += fGroups[i] + ", ";
continue;
}
}
if(errors != config.FG_NOT_FOUND){
throw errors.substr(0, errors.length -2);
}
if(fGroupsCount == fGroups.length){
return true;
}
return false;
};
/**
* override {@link searcher#search}. will return the approriate message
* from the question file.
* @method
* @access protected
* @param {String} smileAnswer The submitted SMILE string.
*
*/
this.search = function(smileAnswer) {
if (noGroups) {
return "";
}
if (this.test(smileAnswer)) {
if (config.APPEND_CORRECT_VALUES) {
var groups = "";
for (var i = 0; i < fGroups.length; i++) {
var group = fGroups[i];
groups += group;
if (i != fGroups.length -1) {
groups += ",";
}
}
return this.getfoundMsg() + "<span class='correctColor'>(" + groups + ")</span>";
}
return this.getfoundMsg();
}
return this.getNotFoundMsg();
};
this.getFunctionalGroups = function() {
return fGroups;
};
this.getNoGroups = function() {
return noGroups;
};
}
// inherit
functionalGroupsSearcher.prototype = new searcher();
functionalGroupsSearcher.prototype.constructor = functionalGroupsSearcher;
// =======================================================================================
/**
* Constructor for a new Loci Searcher
*
* @class
* @param {Number}
* numOfLoci
* @param {String} notfoundMsg The string when the number of Loci is incorret in an answer.
*
*
* @classdesc lociSearcher is used to search an answer for the correct number of loci and return a message based on the search. What
* counts as a loci was determined by what was counted in the original Java version of the application.
* <p>
* The search is based on the tutor.java version. It will count numbers and capital letters N, found outside of [], per
* tutor.java's code.
*
*
*/
function lociSearcher(numOfLoci, notfoundMsg) {
var nLoci = new Number(numOfLoci);
var negativeMsg = notfoundMsg;
if (nLoci == null) {
nLoci = new Number(-1);
}
if (negativeMsg == null) {
negativeMsg = "";
}
// clean up the input. trim spaces
// and replace end of lines chars.
negativeMsg = negativeMsg.replace(/^\s+|\s+$/g, '');
negativeMsg = negativeMsg.replace(/\n|\r|\f/g, '');
/**
* Searches an answer, counting the number of Loci present and returns either the notFoundMsg, if the number found in the answer
* is not the same as the Loci in the answer or the empty string if it is.
*
* @param {String}
* answer, the input to test
* @returns {String} the empty or not found string.
* @method
* @access protected
*/
this.search = function(answer) {
var nLociFound = new Number(0);
var closedBrackets = new Array();
var openBrackets = new Array();
// 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 open and closed
// brackets is mismatched.
var mmb = openBrackets.length != closedBrackets.length;
// find all numbers or capitial N's
regex = /(\d\d*|NN*)/g;
match = regex.exec(answer);
while (match != null) {
// before first [, after last ], mismatched and no square brackets
// in answer. counts.
if (match.index < eob || match.index > boe || mmb) {
nLociFound++;
match = regex.exec(answer);
continue; // the while
}
// 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; // the for
}
}
if (!inSquare) { // then count it.
nLociFound++;
}
// next match if there is one.
match = regex.exec(answer);
}
if (nLociFound.valueOf() != nLoci.valueOf()) {
return negativeMsg;
}
return "";
};
}