var storedVariableValues = {};
var botAttributes = {};
var lastWildCardValue = '';
var wildCardArray = [];
var domArray = [];
var domIndex = 0;
var isAIMLFileLoadingStarted = false;
var isAIMLFileLoaded = false;
var previousAnswer = '';
var previousThinkTag = false;
//botAttributes contain things like name, age, master, gender...
var AIMLInterpreter = function(botAttributesParam){
var self = this;
botAttributes = botAttributesParam;
this.loadAIMLFilesIntoArray = function(fileArray){
isAIMLFileLoadingStarted = true;
var fileIndex = 0;
var readAIMLFile = function(filename){
fileIndex++;
//console.log(document.getElementById('botdata').import);
var datastring;
$(document).ready(function() {
$.ajax({
url : filename,
dataType: "text",
success : function (data) {
datastring = data;
//console.log(datastring);
new DomJS().parse(datastring, function(err, dom) {
if(dom == null){
dom = "";
}
var topCategories, topics;
if (err) {
// return cb(err);
}
if (dom.name === !'aiml') {
// return cb('Unsupported file');
}
domArray[domIndex] = dom;
domIndex++;
if(fileIndex < fileArray.length){
readAIMLFile(fileArray[fileIndex]);
}
else{
console.log('AIML file is loaded!');
isAIMLFileLoaded = true;
}
});
}
});
});
}
readAIMLFile(fileArray[fileIndex]);
};
this.findAnswerInLoadedAIMLFiles = function(clientInput, cb){
//check if all AIML files have been loaded. If not, call this method again after a delay
if(isAIMLFileLoaded){
wildCardArray = [];
var result = '';
for(var i = 0; i < domArray.length; i++){
cleanDom(domArray[i].children);
result = findCorrectCategory(clientInput, domArray[i].children);
if(result){
break;
}
}
if(result){
result = cleanStringFormatCharacters(result);
previousAnswer = result;
}
cb(result, wildCardArray, clientInput);
}
else{
var findAnswerInLoadedAIMLFilesWrapper = function(clientInput, cb){
return function(){
self.findAnswerInLoadedAIMLFiles(clientInput, cb);
};
};
setTimeout(findAnswerInLoadedAIMLFilesWrapper(clientInput, cb), 1000);
}
};
//restart the DOM in order to load a new AIML File
this.restartDom = function(){
domArray=[];
domIndex=0;
};
};
// remove string control characters (like line-breaks '\r\n', leading / trailing spaces etc.)
var cleanStringFormatCharacters = function(str){
var cleanedStr = str.replace(/\r\n/gi, '');
cleanedStr = cleanedStr.replace(/^\s*/, '');
cleanedStr = cleanedStr.replace(/\s*$/,'');
return cleanedStr;
}
var cleanDom = function(childNodes){
for(var i = 0; i < childNodes.length; i++){
if(childNodes[i].hasOwnProperty('text') & typeof(childNodes[i].text) === 'string'){
// remove all nodes of type 'text' when they just contain '\r\n'. This indicates line break in the AIML file
if(childNodes[i].text.match(/^\s*\r\n\s*$/gi)){
childNodes.splice(i, 1);
}
}
}
// traverse through whole tree by recursive calls
for(var j = 0; j < childNodes.length; j++){
if(childNodes[j].hasOwnProperty('children')){
cleanDom(childNodes[j].children);
}
}
};
var findCorrectCategory = function(clientInput, domCategories){
//indexOfSetTagAmountWithWildCard indicates how many sets with wildcard occur so that those sets store the correct wildcard value
var indexOfSetTagAmountWithWildCard = 0;
var travereseThroughDomToFindMatchingPattern= function(categories){
for(var i = categories.length-1; i >= 0; i--){
if(categories[i].name === 'category'){
//traverse through the dom
//text gets the value of the current pattern node
var text = travereseThroughDomToFindMatchingPattern(categories[i].children);
//check if the input of the user matches the pattern text
var matches = checkIfMessageMatchesPattern(clientInput, text);
if(matches){
//check if a 'that' tag is existing. If yes, check if the text of the that tag matches the previous given answer.
//If it does not match, continue the traversion through the AIML file
var isMatchingThat = checkForThatMatching(categories[i].children);
if(isMatchingThat){
var text = findFinalTextInTemplateNode(categories[i].children);
if(text){
return text;
}
break;
}
}
}
else if(categories[i].name === 'pattern'){
var text = resolveChildNodesInPatternNode(categories[i].children);
return text;
}
}
}
var checkForThatMatching = function(categoryChildNodes){
for(var i = 0; i < categoryChildNodes.length; i++){
if(categoryChildNodes[i].name === 'that'){
//if the previous answer of the bot does not match the that-tag text, then return undefined!
if(categoryChildNodes[i].children[0].text != previousAnswer){
return false;
}
else{
return true;
}
}
}
//if no that tag was found, everything 'fits'
return true;
}
var resolveChildNodesInPatternNode = function(patternChildNodes){
var text = '';
for(var i = 0; i < patternChildNodes.length; i++){
if(patternChildNodes[i].name === 'bot'){
text = text + botAttributes[patternChildNodes[i].attributes.name];
}
else if(patternChildNodes[i].name === 'get'){
text = text + storedVariableValues[patternChildNodes[i].attributes.name];
}
else if(patternChildNodes[i].name === 'set'){
text = text + patternChildNodes[i].children[0].text;
}
else{
text = text + patternChildNodes[i].text;
}
}
return text;
}
var findFinalTextInTemplateNode = function(childNodesOfTemplate){
var text = '';
//traverse through template nodes until final text is found
//return it then to very beginning
for(var i = 0; i < childNodesOfTemplate.length; i++){
if(childNodesOfTemplate[i].name === 'template'){
//traverse as long through the dom until final text was found
//final text -> text after special nodes (bot, get, set,...) were resolved
return findFinalTextInTemplateNode(childNodesOfTemplate[i].children);
}
else if(childNodesOfTemplate[i].name === 'condition'){
return resolveSpecialNodes(childNodesOfTemplate);
}
else if(childNodesOfTemplate[i].name === 'random'){
//if random node was found, its children are 'li' nodes.
return resolveSpecialNodes(childNodesOfTemplate);
}
else if(childNodesOfTemplate[i].name === 'srai'){
//take pattern text of srai node to get answer of another category
var sraiText = '' + findFinalTextInTemplateNode(childNodesOfTemplate[i].children);
sraiText = sraiText.toUpperCase();
var referredPatternText = sraiText;
//call findCorrectCategory again to find the category that belongs to the srai node
var text = findCorrectCategory(referredPatternText, domCategories);
return text;
}
else if(childNodesOfTemplate[i].name === 'li'){
return findFinalTextInTemplateNode(childNodesOfTemplate[i].children);
}
else if(childNodesOfTemplate[i].name === 'br'){
//br elements are used for putting '\n' into the text
return resolveSpecialNodes(childNodesOfTemplate);
}
else if(childNodesOfTemplate[i].name === 'pattern'){
//(here it is already checked that this is the right pattern that matches the user input)
//make use of the functions of the special nodes - bot, set, get...
resolveSpecialNodes(childNodesOfTemplate[i].children);
continue;
}
else if(childNodesOfTemplate[i].name === 'think'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'bot'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'set'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'get'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'sr'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'star'){
text = resolveSpecialNodes(childNodesOfTemplate);
return text;
}
else if(childNodesOfTemplate[i].name === 'that'){
}
else{
//this is the text of template node
//after all special functions (bot, get, set,...) were resolved
//return that text
text = resolveSpecialNodes(childNodesOfTemplate);
if((text.match('[\\n|\\t]*[^A-Z|^a-z|^!|^?]*')[0] === '') && (text.indexOf('function ()') === -1)){
return (text);
}
}
}
};
var resolveSpecialNodes = function(innerNodes){
var text = '';
//concatenate string of all node children - normal text, bot tags, get tags, set tags...
for(var i = 0; i < innerNodes.length; i++){
if(innerNodes[i].name === 'bot'){
//replace bot tags by the belonging bot attribute value
text = text + botAttributes[innerNodes[i].attributes.name];
}
else if(innerNodes[i].name === 'get'){
//replace get tag by belonging variable value
var getAux = storedVariableValues[innerNodes[i].attributes.name];
if(getAux === undefined){
text = text + '';
}else{
text = text + getAux;
}
}
else if(innerNodes[i].name === 'set'){
//store value of set tag text into variable (variable name = attribute of set tag)
//replace than set tag by the text value
var aux='';
if(innerNodes[i].children[0].name === 'star'){
aux = resolveSpecialNodes(innerNodes[i].children);
storedVariableValues[innerNodes[i].attributes.name] = aux;
if(!previousThinkTag){
text = text + aux;
}
}
else if(innerNodes[i].children[0].text === '*'){
//the first set-Tag with wildCard gets the first wildCardValue, the second set-Tag with wildCard gets the second wildCardValue etc.
storedVariableValues[innerNodes[i].attributes.name] = wildCardArray[indexOfSetTagAmountWithWildCard];
indexOfSetTagAmountWithWildCard++;
}else{
storedVariableValues[innerNodes[i].attributes.name] = innerNodes[i].children[0].text;
}
//If this set tag is a think tag's child
if(previousThinkTag){
previousThinkTag=false;
text= text + '';
}else{
text = text + resolveSpecialNodes(innerNodes[i].children);
}
}
else if(innerNodes[i].name === 'br'){
text = text + '\n';
}
else if(innerNodes[i].name === 'think'){
previousThinkTag=true;
text = text + resolveSpecialNodes(innerNodes[i].children);
}
else if(innerNodes[i].name === 'sr'){
var result;
//for-loop to go through all loaded AIML files
for(var j = 0; j < domArray.length; j++){
result = findCorrectCategory(lastWildCardValue, domArray[j].children);
//if in one of the dom trees a matching pattern was found, exit this inner loop
if(result){
text = text + result;
break;
}
}
}
else if(innerNodes[i].name === 'random'){
//Get a random number and find the li tag chosen
var randomNumber = Math.floor(Math.random() * (innerNodes[i].children.length));
text = text + findFinalTextInTemplateNode([innerNodes[i].children[randomNumber]]);
;
}
else if(innerNodes[i].name === 'star'){
text = text + lastWildCardValue;
}
else if(innerNodes[i].name === 'srai'){
//take pattern text of srai node to get answer of another category
var sraiText = '' + findFinalTextInTemplateNode(innerNodes[i].children);
sraiText = sraiText.toUpperCase();
var referredPatternText = sraiText;
//call findCorrectCategory again to find the category that belongs to the srai node
text = text + findCorrectCategory(referredPatternText, domCategories);
}
else if(innerNodes[i].name === 'condition') {
// condition tag specification: list condition tag
if(innerNodes[i].attributes.name === undefined){
if(innerNodes[i].children === undefined){
return undefined;
}
var child;
for(var c in innerNodes[i].children){
child = innerNodes[i].children[c];
if(child.name === 'li'){
if(child.attributes.value == undefined
|| storedVariableValues[child.attributes.name] === child.attributes.value.toUpperCase()){
return findFinalTextInTemplateNode(child.children);
}
}
}
}
// condition tag specification: multi condition tag
else if(innerNodes[i].attributes.value !== undefined){
if (storedVariableValues[innerNodes[i].attributes.name] === innerNodes[i].attributes.value.toUpperCase()) {
text = text + resolveSpecialNodes(innerNodes[i].children);
}
}
// condition tag specification: single name list condition tags
else if(innerNodes[i].children !== undefined){
var child;
for(var c in innerNodes[i].children){
child = innerNodes[i].children[c];
if(child.name === 'li'){
if(child.attributes.value === undefined
|| storedVariableValues[innerNodes[i].attributes.name] === child.attributes.value.toUpperCase()){
return resolveSpecialNodes(child.children);
}
}
}
return undefined;
}
}
else if(innerNodes[i].name === undefined){
//normal text (no special tag)
text = text + innerNodes[i].text;
}
}
text = cleanStringFormatCharacters(text);
return text;
}
return travereseThroughDomToFindMatchingPattern(domCategories);
}
var checkIfMessageMatchesPattern = function(userInput, patternText){
//convert wildcards in of the pattern node into a regex that matches every char
var regexPattern = convertWildcardToRegex(patternText);
//add one with the text in function 'convertWildcardToRegex' here a space is added before and after the user input
//to prevent false matching
if(userInput.charAt(0) != " "){
userInput = " " + userInput;
}
var lastCharacterPosition = userInput.length - 1;
var lastCharacter = userInput.charAt(lastCharacterPosition);
if(lastCharacter != " "){
userInput = userInput + " ";
}
//match userInput with the regex pattern
//if it matches, matchedString is defined
var matchedString = userInput.toUpperCase().match(regexPattern);
if(matchedString){
//the matched pattern must be at least as long as the user input or must contain the regex
if(matchedString[0].length >= userInput.length || regexPattern.indexOf('[A-Z|0-9|\\s]*[A-Z|0-9|-]*[A-Z|0-9]*[!|.|?|\\s]*') > -1){
//if patternText contained a wild card, get the user input that were put into this wild card
//use original patternText (* is not replaced by regex!)
var information = getWildCardValue(userInput, patternText);
return true;
}
}
else{
return false;
}
}
var convertWildcardToRegex = function(text){
var firstCharacter = text.charAt(0);
//add a space before and after the pattern text (THIS IS LATER ALSO DONE FOR THE USER INPUT)
//prevents false matchings
//e.g. (HI as regex also matches HIM or HISTORY, but HI does only match HI)
if(firstCharacter != "*"){
var text = " " + text;
}
var lastCharacterPosition = text.length - 1;
var lastCharacter = text.charAt(lastCharacterPosition);
//replace space before wildcard
var modifiedText = text.replace(' *', '*');
//replace wildcard (*) by regex
modifiedText = modifiedText.replace(/\*/g, '[A-Z|0-9|\\s]*[A-Z|0-9|\*|-]*[A-Z|0-9]*[!|.|?|\\s]*');
if(lastCharacter != "*"){
// text = text + " ";
//pattern should also match when user inputs ends with a space, ?, ! or .
modifiedText = modifiedText + '[\\s|?|!|.]*';
}
return modifiedText;
}
var getWildCardValue = function(userInput, patternText){
//get all strings of the pattern that are divided by a *
//e.g. WHAT IS THE RELATION BETWEEN * AND * -> [WHAT IS THE RELATION BETWEEN , AND ]
var replaceArray = patternText.split('*');
var wildCardInput = userInput;
if(replaceArray.length > 1){
//replace the string of the userInput which is fixed by the pattern
for(var i = 0; i < replaceArray.length; i++){
wildCardInput = wildCardInput.replace(new RegExp(replaceArray[i], 'i'), '|');
}
//split the wildCardInput string by | to differentiate multiple * inputs
//e.g. userInput = WHAT IS THE RELATION BETWEEN TIM AND STRUPPI?
//-> | TIM | STRUPPI
//-> [TIM, STRUPPI]
wildCardInput = wildCardInput.split('|');
//split function can create an array which also includes spaces etc. -> e.g. [TIM, " ", "", STRUPPI, " "]
//we just want the information
var wildCardArrayIndex = 0;
for(var i = 0; i < wildCardInput.length; i++){
if(wildCardInput[i] != '' && wildCardInput[i] != ' ' && wildCardInput != undefined){
var wildCard = wildCardInput[i];
var wildCardLastCharIndex = wildCard.length - 1;
var firstCharOfWildCard = wildCard.charAt(0);
var lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
try{
//harmonize the wildcard string
//remove first char if it is a space.
//calculate the last index again since the length of the string changed
if(firstCharOfWildCard === ' '){
wildCard = wildCard.splice(0);
wildCardLastCharIndex = wildCard.length - 1;
lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
}
//if the last char is a space, remove it
//calculate the last index again since the length of the string changed
if(lastCharOfWildCard === ' '){
wildCard = wildCard.substr(0, wildCardLastCharIndex);
wildCardLastCharIndex = wildCard.length - 1;
lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
}
if(lastCharOfWildCard === '?'){
wildCard = wildCard.substr(0, wildCardLastCharIndex);
}
}
catch(e){
}
wildCardArray[wildCardArrayIndex] = wildCard;
wildCardArrayIndex++;
}
}
}
if(wildCardArray.length - 1 >= 0){
lastWildCardValue = wildCardArray[wildCardArray.length - 1];
}
return wildCardArray;
}