commit 8cd9032f696b361582a394df96b5e79e950be128 Author: CrabMustard Date: Thu Dec 13 18:20:08 2018 -0600 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd45b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml new file mode 100644 index 0000000..1e6aa34 --- /dev/null +++ b/.idea/assetWizardSettings.xml @@ -0,0 +1,56 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e0d5b93 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..dd64f71 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.application' + +repositories { + maven { + url 'http://nexus.bippo.co.id/nexus/content/repositories/soluvas-public-snapshots/' + } + maven { + url 'http://nexus.bippo.co.id/nexus/content/repositories/soluvas-public-thirdparty/' + } + maven { + url 'http://jcenter.bintray.com' + } +} + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.fpghoti.androidbotinterface" + minSdkVersion 26 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + } +} + +dependencies { + + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation ('com.android.support:appcompat-v7:28.0.0') + implementation ('com.android.support.constraint:constraint-layout:1.1.3') + implementation group: 'joda-time', name: 'joda-time', version: '2.3' + implementation ('com.jcabi:jcabi-aspects:0.22.6') + testImplementation ('junit:junit:4.12') + androidTestImplementation ('com.android.support.test:runner:1.0.2') + androidTestImplementation ('com.android.support.test.espresso:espresso-core:3.0.2') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/fpghoti/androidbotinterface/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/fpghoti/androidbotinterface/ExampleInstrumentedTest.java new file mode 100644 index 0000000..78655e8 --- /dev/null +++ b/app/src/androidTest/java/com/fpghoti/androidbotinterface/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.fpghoti.androidbotinterface; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.fpghoti.androidbotinterface", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b15b38e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/bots.zip b/app/src/main/assets/bots.zip new file mode 100644 index 0000000..78719aa Binary files /dev/null and b/app/src/main/assets/bots.zip differ diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..a33abdf Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/com/fpghoti/androidbotinterface/MainActivity.java b/app/src/main/java/com/fpghoti/androidbotinterface/MainActivity.java new file mode 100644 index 0000000..6d7fae4 --- /dev/null +++ b/app/src/main/java/com/fpghoti/androidbotinterface/MainActivity.java @@ -0,0 +1,157 @@ +package com.fpghoti.androidbotinterface; + +import android.app.Activity; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.fpghoti.androidbotinterface.bot.ChatBot; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class MainActivity extends AppCompatActivity { + + private EditText editText; + private TextView textOut; + public static ChatBot bot; + private boolean isLoaded; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + isLoaded = false; + editText = (EditText) findViewById(R.id.editText); + loadBot(); + } + + private void loadBot(){ + outputPrint("Now loading the chat bot files..."); + File fileExt = new File(getExternalFilesDir(null).getAbsolutePath() + "/bots"); + if (!fileExt.exists()) { + try { + unpackBot(); + } catch (Exception e) { + e.printStackTrace(); + return; + } + } + bot = new ChatBot(getExternalFilesDir(null).getAbsolutePath()); + isLoaded = true; + outputPrint("Chat bot files are done loading!"); + } + + private void unpackBot() { + try { + ZipInputStream zs = new ZipInputStream(getAssets().open("bots.zip")); + ZipEntry entry = null; + + String destination = getExternalFilesDir(null).getAbsolutePath() + "/"; + + byte[] buffer = new byte[4096]; + int bytes; + while ((entry = zs.getNextEntry()) != null) { + if (entry.isDirectory()) { + File dir = new File(destination, entry.getName()); + if (!dir.exists()) { + dir.mkdir(); + } + } else { + FileOutputStream file = new FileOutputStream(destination + entry.getName()); + while ((bytes = zs.read(buffer)) != -1) { + file.write(buffer, 0, bytes); + } + file.close(); + } + } + zs.close(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public void sendMessage(View view) { + String message = editText.getText().toString(); + textOut = (TextView) findViewById(R.id.output); + if (message.length() > 0) { + while(lineCount(textOut.getText().toString()) > 20){ + textOut.setText(removeFirstLine(textOut.getText().toString())); + } + textOut.setText(textOut.getText() + "\n" + "You: " + message); + hideKeyboard(); + editText.getText().clear(); + + if(isLoaded) { + final String finalmsg = bot.chatBot(message); + new java.util.Timer().schedule( + new java.util.TimerTask() { + @Override + public void run() { + while (lineCount(textOut.getText().toString()) > 20) { + textOut.setText(removeFirstLine(textOut.getText().toString())); + } + textOut.setText(textOut.getText() + "\n" + "Bot: " + finalmsg); + } + }, + 2000 + ); + }else{ + new java.util.Timer().schedule( + new java.util.TimerTask() { + @Override + public void run() { + while (lineCount(textOut.getText().toString()) > 20) { + textOut.setText(removeFirstLine(textOut.getText().toString())); + } + outputPrint("The chat bot is still loading."); + } + }, + 2000 + ); + } + } + } + + public void outputPrint(String input){ + textOut = (TextView) findViewById(R.id.output); + if (input.length() > 0) { + while(lineCount(textOut.getText().toString()) > 20){ + textOut.setText(removeFirstLine(textOut.getText().toString())); + } + textOut.setText(textOut.getText() + "\n" + input); + } + } + + private void hideKeyboard() { + View v = getCurrentFocus(); + if (v == null) { + v = new View(this); + } + InputMethodManager manager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); + manager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + + public int lineCount(String s){ + return s.split("\r\n|\r|\n").length; + } + + public String removeFirstLine(String s){ + if(lineCount(s) < 2){ + return ""; + } + String result = ""; + String[] lines = s.split("\r\n|\r|\n"); + for(int i = 1; i < lineCount(s) - 1; i++){ + result += lines[i] + "\n"; + } + result += lines[lineCount(s) - 1]; + return result; + } + +} diff --git a/app/src/main/java/com/fpghoti/androidbotinterface/SplashActivity.java b/app/src/main/java/com/fpghoti/androidbotinterface/SplashActivity.java new file mode 100644 index 0000000..77c39f6 --- /dev/null +++ b/app/src/main/java/com/fpghoti/androidbotinterface/SplashActivity.java @@ -0,0 +1,16 @@ +package com.fpghoti.androidbotinterface; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class SplashActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fpghoti/androidbotinterface/bot/BotThink.java b/app/src/main/java/com/fpghoti/androidbotinterface/bot/BotThink.java new file mode 100644 index 0000000..310d7dd --- /dev/null +++ b/app/src/main/java/com/fpghoti/androidbotinterface/bot/BotThink.java @@ -0,0 +1,32 @@ +package com.fpghoti.androidbotinterface.bot; + +public class BotThink { + //easteregg won't see this if everything's working + public static String think(String input){ + input = input.toLowerCase(); + if(input.contains("android studio")){ + if(input.contains("like")){ + if(input.contains("dont") || input.contains("don't") || input.contains("not")){ + return "It is wise to keep your distance from Android Studio."; + } else if(input.contains("i ")){ + return "Try staying up until 4:00 AM trying to get the bot software to load in Android Studio. Good luck liking it after that."; + } + return "No more Android Studio, please!"; + } + }else if(input.contains("hello") || input.equals("hi") || input.equals("hi.") || input.equals("hi!") || input.contains("greetings") || input.contains("blessings and tidings")){ + return "Hello!"; + }else if(input.contains("are you working right now")){ + return("No, I am in fact NOT working right now. Android Studio INSISTS on not reading files from the SD card, so you are seeing this error message. Thanks for asking, though."); + }else if(input.contains("the room")){ + return "Produced by, directed by, written by, and starring Tommy Wiseau, The Room was once an odd, obscure cult classic, but has gotten too much attention for my tastes lately."; + }else if(input.contains("a talking cat")){ + return "That is that one movie with the cat that can only talk to people once. It was a direct to DVD low-budget film. I honestly thin Foodfight was higher quality than A Talking Cat."; + }else if(input.contains("backstroke of the west")){ + return "Backstroke of the West is arguably the best Star Wars film and a fan-favorite Star War Gatering. It ends with Darth Vader shouting, \"DO NOT WANT!\" instead of his usual line."; + }else if(input.contains("space thunder kids")){ + return "What can I say? This is a mix of eight different Korean cartoons with one guy doing ninety percent of the voice acting. A massive trainwreck."; + } + return "lol"; + } + +} diff --git a/app/src/main/java/com/fpghoti/androidbotinterface/bot/ChatBot.java b/app/src/main/java/com/fpghoti/androidbotinterface/bot/ChatBot.java new file mode 100644 index 0000000..ef0f09d --- /dev/null +++ b/app/src/main/java/com/fpghoti/androidbotinterface/bot/ChatBot.java @@ -0,0 +1,55 @@ +package com.fpghoti.androidbotinterface.bot; + +import org.alicebot.ab.Bot; +import org.alicebot.ab.Chat; + +import com.jcabi.aspects.Async; + +public class ChatBot { + + private Bot bot; + private Chat chatSession = null; + + public ChatBot(String path) { + bot = new Bot("default",path,"chat"); + chatSession = new Chat(bot); + } + + @Async + public String chatBot(String input) { + String msg = "none"; + try { + msg = chatSession.multisentenceRespond(input); + //zelda cdi reference + if(input.toLowerCase().contains("youve killed me") || input.toLowerCase().contains("you've killed me")){ + msg = "Good."; + } + int count = 0; + + //bot does not always respond + while(msg.equalsIgnoreCase("") || msg.equalsIgnoreCase("I have no answer for that.")){ + String lastWord = input.substring(input.lastIndexOf(" ")+1); + if(count > 0){ + lastWord = "lol "; + } + msg = chatSession.multisentenceRespond(lastWord); + if(count > 0){ + msg = BotThink.think(input); + } + count++; + } + + //bot sometimes tries to pull up weird link. I Decided to just disable this outright by replacing the message + if(msg.contains("") || msg.contains(" topSuggestCategories = new ArrayList(); + for (int i = 0; i < 10000 && i < bot.suggestedCategories.size(); i++) { + topSuggestCategories.add(bot.suggestedCategories.get(i)); + } + bot.suggestedCategories = topSuggestCategories; + if (shuffle_mode) Collections.shuffle(bot.suggestedCategories); + timer = new Timer(); + timer.start(); + runCompletedCnt = 0; + ArrayList filteredAtomicCategories = new ArrayList(); + ArrayList filteredWildCategories = new ArrayList(); + for (Category c : bot.suggestedCategories) if (!c.getPattern().contains("*")) filteredAtomicCategories.add(c); + else filteredWildCategories.add(c); + ArrayList browserCategories; + if (filter_atomic_mode) browserCategories = filteredAtomicCategories; + else if (filter_wild_mode) browserCategories = filteredWildCategories; + else browserCategories = bot.suggestedCategories; + // log.info(filteredAtomicCategories.size()+" filtered suggested categories"); + for (Category c : browserCategories) { + try { + @SuppressWarnings({ "rawtypes", "unchecked" }) + ArrayList samples = new ArrayList(c.getMatches()); + Collections.shuffle(samples); + int sampleSize = Math.min(MagicNumbers.displayed_input_sample_size, c.getMatches().size()); + for (int i = 0; i < sampleSize; i++) { + + } + + productivity(runCompletedCnt, timer); + String textLine = "" + IOUtils.readInputTextLine(); + terminalInteractionStep(bot, "", textLine, c); + } catch (Exception ex) { + ex.printStackTrace(); + + } + } + } + + /** process one step of the terminal interaction + * + * @param bot the bot being trained. + * @param request used when this routine is called by benchmark testSuite + * @param textLine response typed by the botmaster + * @param c AIML category selected + */ + public static void terminalInteractionStep (Bot bot, String request, String textLine, Category c) { + String template = null; + if (textLine.contains("") && textLine.contains("")) { + int index = textLine.indexOf("")+"".length(); + int jndex = textLine.indexOf(""); + int kndex = jndex + "".length(); + if (index < jndex) { + String pattern = textLine.substring(index, jndex); + c.setPattern(pattern); + textLine = textLine.substring(kndex, textLine.length()); + + } + } + String botThinks = ""; + String[] pronouns = {"he", "she", "it", "we", "they"}; + for (String p : pronouns) { + if (textLine.contains("<"+p+">")) { + textLine = textLine.replace("<"+p+">",""); + botThinks = ""; + } + } + if (textLine.equals("q")) System.exit(0); // Quit program + else if (textLine.equals("wq")) { // Write AIML Files and quit program + bot.writeQuit(); + /* Nodemapper udcNode = bot.brain.findNode("*", "*", "*"); + if (udcNode != null) { + AIMLSet udcMatches = new AIMLSet("udcmatches"); + udcMatches.addAll(udcNode.category.getMatches()); + udcMatches.writeAIMLSet(); + }*/ + /* Nodemapper cNode = bot.brain.match("JOE MAKES BEER", "unknown", "unknown"); + if (cNode != null) { + AIMLSet cMatches = new AIMLSet("cmatches"); + cMatches.addAll(cNode.category.getMatches()); + cMatches.writeAIMLSet(); + } + if (passed.size() > 0) { + AIMLSet difference = new AIMLSet("difference"); + AIMLSet intersection = new AIMLSet("intersection"); + for (String s : passed) if (testSet.contains(s)) intersection.add(s); + passed = intersection; + passed.setName = "passed"; + difference.addAll(testSet); + difference.removeAll(passed); + difference.writeAIMLSet(); + + passed.writeAIMLSet(); + testSet.writeAIMLSet(); + log.info("Wrote passed test cases"); + }*/ + System.exit(0); + } + else if (textLine.equals("skip") || textLine.equals("")) { // skip this one for now + skipCategory(bot, c); + } + else if (textLine.equals("s") || textLine.equals("pass")) { // skip this one for now + passed.add(request); + AIMLSet difference = new AIMLSet("difference"); + difference.addAll(testSet); + difference.removeAll(passed); + difference.writeAIMLSet(); + passed.writeAIMLSet(); + } + else if (textLine.equals("d")) { // delete this suggested category + deleteCategory(bot, c); + } + else if (textLine.equals("x")) { // ask another bot + template = ""+c.getPattern().replace("*","")+""; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.sraix_aiml_file); + } + else if (textLine.equals("p")) { // filter inappropriate content + template = ""+MagicStrings.inappropriate_filter+""; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.inappropriate_aiml_file); + } + else if (textLine.equals("f")) { // filter profanity + template = ""+MagicStrings.profanity_filter+""; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.profanity_aiml_file); + } + else if (textLine.equals("i")) { + template = ""+MagicStrings.insult_filter+""; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.insult_aiml_file); + } + else if (textLine.contains("") || textLine.contains("")) { + template = textLine; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.reductions_update_aiml_file); + } + else if (textLine.contains("")) { + textLine = "I don't know."; + template = textLine; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.oob_aiml_file); + } + else if (textLine.contains(" 0) { + template = textLine; + template += botThinks; + saveCategory(bot, c.getPattern(), template, MagicStrings.predicates_aiml_file); + } + else if (textLine.contains(" { + + public String mapName; + String host; // for external maps + String botid; // for external maps + boolean isExternal = false; + + /** + * constructor to create a new AIML Map + * + * @param name the name of the map + */ + public AIMLMap (String name) { + super(); + this.mapName = name; + } + + /** + * return a map value given a key + * + * @param key the domain element + * @return the range element or a string indicating the key was not found + */ + public String get(String key) { + String value; + if (mapName.equals(MagicStrings.map_successor)) { + try { + int number = Integer.parseInt(key); + return String.valueOf(number+1); + } catch (Exception ex) { + return MagicStrings.unknown_map_value; + } + } + else if (mapName.equals(MagicStrings.map_predecessor)) { + try { + int number = Integer.parseInt(key); + return String.valueOf(number-1); + } catch (Exception ex) { + return MagicStrings.unknown_map_value; + } + } + else if (isExternal && MagicBooleans.enable_external_sets) { + //String[] split = key.split(" "); + String query = mapName.toUpperCase()+" "+key; + String response = Sraix.sraix(null, query, MagicStrings.unknown_map_value, null, host, botid, null, "0"); + + value = response; + } + else value = super.get(key); + if (value == null) value = MagicStrings.unknown_map_value; + + return value; + } + + /** + * put a new key, value pair into the map. + * + * @param key the domain element + * @param value the range element + * @return the value + */ + @Override + public String put(String key, String value) { + //log.info("AIMLMap put "+key+"="+value); + return super.put(key, value); + } + + /** + * reads an input stream and loads a map into the bot. + * + * @param in input stream + * @param bot AIML bot + * @return number of map elements loaded + */ + public int readAIMLMapFromInputStream(InputStream in, Bot bot) { + int cnt=0; + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + String strLine; + //Read File Line By Line + try { + while ((strLine = br.readLine()) != null && strLine.length() > 0) { + String[] splitLine = strLine.split(":"); + //log.info("AIMLMap line="+strLine); + if (splitLine.length >= 2) { + cnt++; + if (strLine.startsWith(MagicStrings.remote_map_key)) { + if (splitLine.length >= 3) { + host = splitLine[1]; + botid = splitLine[2]; + isExternal = true; + + } + } + else { + String key = splitLine[0].toUpperCase(); + String value = splitLine[1]; + // assume domain element is already normalized for speedier load + //key = bot.preProcessor.normalize(key).trim(); + put(key, value); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return cnt; + } + + /** + * read an AIML map for a bot + * + * @param bot the bot associated with this map. + */ + public void readAIMLMap (Bot bot) { + + try{ + // Open the file that is the first + // command line parameter + File file = new File(MagicStrings.maps_path+"/"+mapName+".txt"); + if (file.exists()) { + FileInputStream fstream = new FileInputStream(MagicStrings.maps_path+"/"+mapName+".txt"); + // Get the object + readAIMLMapFromInputStream(fstream, bot); + fstream.close(); + } + + }catch (Exception e){//Catch exception if any + + } + + } + +} diff --git a/app/src/main/java/org/alicebot/ab/AIMLProcessor.java b/app/src/main/java/org/alicebot/ab/AIMLProcessor.java new file mode 100644 index 0000000..520a022 --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/AIMLProcessor.java @@ -0,0 +1,1162 @@ +package org.alicebot.ab; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import org.alicebot.ab.utils.CalendarUtils; +import org.alicebot.ab.utils.DomUtils; +import org.alicebot.ab.utils.IOUtils; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * The core AIML parser and interpreter. + * Implements the AIML 2.0 specification as described in + * AIML 2.0 Working Draft document + * https://docs.google.com/document/d/1wNT25hJRyupcG51aO89UcQEiG-HkXRXusukADpFnDs4/pub + */ +public class AIMLProcessor { + + + /** + * when parsing an AIML file, process a category element. + * + * @param n current XML parse node. + * @param categories list of categories found so far. + * @param topic value of topic in case this category is wrapped in a tag + * @param aimlFile name of AIML file being parsed. + */ + public static AIMLProcessorExtension extension; + private static void categoryProcessor(Node n, ArrayList categories, String topic, String aimlFile, String language) { + String pattern, that, template; + + NodeList children = n.getChildNodes(); + pattern = "*"; that = "*"; template=""; + for (int j = 0; j < children.getLength(); j++) { + //log.info("CHILD: "+children.item(j).getNodeName()); + Node m = children.item(j); + String mName = m.getNodeName(); + if (mName.equals("#text")) {/*skip*/} + else if (mName.equals("pattern")) pattern = DomUtils.nodeToString(m); + else if (mName.equals("that")) that = DomUtils.nodeToString(m); + else if (mName.equals("topic")) topic = DomUtils.nodeToString(m); + else if (mName.equals("template")) template = DomUtils.nodeToString(m); + + } + + pattern = trimTag(pattern, "pattern"); + that = trimTag(that, "that"); + topic = trimTag(topic, "topic"); + template = trimTag(template, "template"); + if (language.equals("JP") || language.equals("jp")) { + // String morphPattern = JapaneseTokenizer.morphSentence(pattern); + + // pattern = morphPattern; + // String morphThatPattern = JapaneseTokenizer.morphSentence(that); + + // that = morphThatPattern; + // String morphTopicPattern = JapaneseTokenizer.morphSentence(topic); + + // topic = morphTopicPattern; + } + Category c = new Category(0, pattern, that, topic, template, aimlFile); + categories.add(c); + } + public static String trimTag(String s, String tagName) { + String stag = "<"+tagName+">"; + String etag = ""; + if (s.startsWith(stag) && s.endsWith(etag)) { + s = s.substring(stag.length()); + s = s.substring(0, s.length()-etag.length()); + } + return s.trim(); + } + /** + * convert an AIML file to a list of categories. + * + * @param directory directory containing the AIML file. + * @param aimlFile AIML file name. + * @return list of categories. + */ + public static ArrayList AIMLToCategories (String directory, String aimlFile) { + try { + ArrayList categories = new ArrayList(); + Node root = DomUtils.parseFile(directory+"/"+aimlFile); // tag + String language = MagicStrings.default_language; + if (root.hasAttributes()) { + NamedNodeMap XMLAttributes = root.getAttributes(); + for(int i=0; i < XMLAttributes.getLength(); i++) + + { + if (XMLAttributes.item(i).getNodeName().equals("language")) language = XMLAttributes.item(i).getNodeValue(); + } + } + NodeList nodelist = root.getChildNodes(); + for (int i = 0; i < nodelist.getLength(); i++) { + Node n = nodelist.item(i); + //log.info("AIML child: "+n.getNodeName()); + if (n.getNodeName().equals("category")) { + categoryProcessor(n, categories, "*", aimlFile, language); + } + else if (n.getNodeName().equals("topic")) { + String topic = n.getAttributes().getNamedItem("name").getTextContent(); + NodeList children = n.getChildNodes(); + for (int j = 0; j < children.getLength(); j++) { + Node m = children.item(j); + //log.info("Topic child: "+m.getNodeName()); + if (m.getNodeName().equals("category")) { + categoryProcessor(m, categories, topic, aimlFile, language); + } + } + } + } + return categories; + } + catch (Exception ex) { + + ex.printStackTrace(); + return null; + } + } + + public static int sraiCount = 0; + public static int repeatCount = 0; + // Helper functions: + public static int checkForRepeat(String input, Chat chatSession) { + if (input.equals(chatSession.inputHistory.get(1))) { + return 1; + } + else return 0; + } + + /** + * generate a bot response to a single sentence input. + * + * @param input the input sentence. + * @param that the bot's last sentence. + * @param topic current topic. + * @param chatSession current client session. + * @return bot's response. + */ + public static String respond(String input, String that, String topic, Chat chatSession) { + if (false /*checkForRepeat(input, chatSession) > 0*/) return "Repeat!"; + else { + return respond(input, that, topic, chatSession, 0); + } + } + + /** + * generate a bot response to a single sentence input. + * + * @param input input statement. + * @param that bot's last reply. + * @param topic current topic. + * @param chatSession current client chat session. + * @param srCnt number of activations. + * @return bot's reply. + */ + public static String respond(String input, String that, String topic, Chat chatSession, int srCnt) { + String response; + if (input == null || input.length()==0) input = MagicStrings.null_input; + sraiCount = srCnt; + response = MagicStrings.default_bot_response; + try { + Nodemapper leaf = chatSession.bot.brain.match(input, that, topic); + if (leaf == null) {return(response);} + //log.info("Template="+leaf.category.getTemplate()); + ParseState ps = new ParseState(0, chatSession, input, that, topic, leaf); + //chatSession.matchTrace += leaf.category.getTemplate()+"\n"; + response = evalTemplate(leaf.category.getTemplate(), ps); + //log.info("That="+that); + } catch (Exception ex) { + ex.printStackTrace(); + } + return response; + } + + /** capitalizeString: + * from http://stackoverflow.com/questions/1892765/capitalize-first-char-of-each-word-in-a-string-java + * + * @param string the string to capitalize + * @return the capitalized string + */ + + private static String capitalizeString(String string) { + char[] chars = string.toLowerCase().toCharArray(); + boolean found = false; + for (int i = 0; i < chars.length; i++) { + if (!found && Character.isLetter(chars[i])) { + chars[i] = Character.toUpperCase(chars[i]); + found = true; + } else if (Character.isWhitespace(chars[i])) { + found = false; + } + } + return String.valueOf(chars); + } + + /** + * explode a string into individual characters separated by one space + * + * @param input input string + * @return exploded string + */ + private static String explode(String input) { + String result = ""; + for (int i = 0; i < input.length(); i++) result += " "+input.charAt(i); + return result.trim(); + } + + // Parsing and evaluation functions: + + /** + * evaluate the contents of an AIML tag. + * calls recursEval on child tags. + * + * @param node the current parse node. + * @param ps the current parse state. + * @param ignoreAttributes tag names to ignore when evaluating the tag. + * @return the result of evaluating the tag contents. + */ + public static String evalTagContent(Node node, ParseState ps, Set ignoreAttributes) { + String result = ""; + try { + NodeList childList = node.getChildNodes(); + for (int i = 0; i < childList.getLength(); i++) { + Node child = childList.item(i); + if (ignoreAttributes == null || !ignoreAttributes.contains(child.getNodeName())) + result += recursEval(child, ps); + } + } catch (Exception ex) { + + ex.printStackTrace(); + } + return result; + } + + /** + * pass thru generic XML (non-AIML tags, such as HTML) as unevaluated XML + * + * @param node current parse node + * @param ps current parse state + * @return unevaluated generic XML string + */ + public static String genericXML(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + return unevaluatedXML(result, node, ps); + } + + /** + * return a string of unevaluated XML. When the AIML parser + * encounters an unrecognized XML tag, it simply passes through the + * tag in XML form. For example, if the response contains HTML + * markup, the HTML is passed to the requesting process. However if that + * markup contains AIML tags, those tags are evaluated and the parser + * builds the result. + * + * @param result the tag contents. + * @param node current parse node. + * @param ps current parse state. + * @return the unevaluated XML string + */ + private static String unevaluatedXML(String result, Node node, ParseState ps) { + String nodeName = node.getNodeName(); + String attributes = ""; + if (node.hasAttributes()) { + NamedNodeMap XMLAttributes = node.getAttributes(); + for(int i=0; i < XMLAttributes.getLength(); i++) + + { + attributes += " "+XMLAttributes.item(i).getNodeName()+"=\""+XMLAttributes.item(i).getNodeValue()+"\""; + } + } + if (result.equals("")) + return "<"+nodeName+attributes+"/>"; + else return "<"+nodeName+attributes+">"+result+""; + } + public static int trace_count = 0; + + /** + * implements AIML tag + * + * @param node current parse node. + * @param ps current parse state. + * @return the result of processing the + * + */ + private static String srai(Node node, ParseState ps) { + sraiCount++; + if (sraiCount > MagicNumbers.max_recursion) { + return MagicStrings.too_much_recursion; + } + String response = MagicStrings.default_bot_response; + try { + String result = evalTagContent(node, ps, null); + result = result.trim(); + result = result.replaceAll("(\r\n|\n\r|\r|\n)", " "); + result = ps.chatSession.bot.preProcessor.normalize(result); + String topic = ps.chatSession.predicates.get("topic"); // the that stays the same, but the topic may have changed + if (MagicBooleans.trace_mode) { + + trace_count++; + } + Nodemapper leaf = ps.chatSession.bot.brain.match(result, ps.that, topic); + if (leaf == null) {return(response);} + //log.info("Srai returned "+leaf.category.inputThatTopic()+":"+leaf.category.getTemplate()); + response = evalTemplate(leaf.category.getTemplate(), new ParseState(ps.depth+1, ps.chatSession, ps.input, ps.that, topic, leaf)); + //log.info("That="+that); + } catch (Exception ex) { + ex.printStackTrace(); + } + return response.trim(); + + } + + /** + * in AIML 2.0, an attribute value can be specified by either an XML attribute value + * or a subtag of the same name. This function tries to read the value from the XML attribute first, + * then tries to look for the subtag. + * + * @param node current parse node. + * @param ps current parse state. + * @param attributeName the name of the attribute. + * @return the attribute value. + */ + // value can be specified by either attribute or tag + private static String getAttributeOrTagValue (Node node, ParseState ps, String attributeName) { // AIML 2.0 + //log.info("getAttributeOrTagValue "+attributeName); + String result = ""; + Node m = node.getAttributes().getNamedItem(attributeName); + if (m == null) { + NodeList childList = node.getChildNodes(); + result = null; // no attribute or tag named attributeName + for (int i = 0; i < childList.getLength(); i++) { + Node child = childList.item(i); + //log.info("getAttributeOrTagValue child = "+child.getNodeName()); + if (child.getNodeName().equals(attributeName)) { + result = evalTagContent(child, ps, null); + //log.info("getAttributeOrTagValue result from child = "+result); + } + } + } + else { + result = m.getNodeValue(); + } + //log.info("getAttributeOrTagValue "+attributeName+" = "+result); + return result; + } + + /** + * access external web service for response + * implements + * and its attribute variations. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return response from remote service or string indicating failure. + */ + private static String sraix(Node node, ParseState ps) { + HashSet attributeNames = Utilities.stringSet("botid", "host"); + String host = getAttributeOrTagValue(node, ps, "host"); + String botid = getAttributeOrTagValue(node, ps, "botid"); + String hint = getAttributeOrTagValue(node, ps, "hint"); + String limit = getAttributeOrTagValue(node, ps, "limit"); + String defaultResponse = getAttributeOrTagValue(node, ps, "default"); + String result = evalTagContent(node, ps, attributeNames); + + return Sraix.sraix(ps.chatSession, result, defaultResponse, hint, host, botid, null, limit); + + } + + /** + * map an element of one string set to an element of another + * Implements and mapname + * + * @param node current XML parse node + * @param ps current AIML parse state + * @return the map result or a string indicating the key was not found + */ + private static String map(Node node, ParseState ps) { + String result = MagicStrings.unknown_map_value; + HashSet attributeNames = Utilities.stringSet("name"); + String mapName = getAttributeOrTagValue(node, ps, "name"); + String contents = evalTagContent(node, ps, attributeNames); + if (mapName == null) result = ""+contents+""; // this is an OOB map tag (no attribute) + else { + AIMLMap map = ps.chatSession.bot.mapMap.get(mapName); + if (map != null) result = map.get(contents.toUpperCase()); + //log.info("AIMLProcessor map "+contents+" "+result); + if (result == null) result = MagicStrings.unknown_map_value; + result = result.trim(); + } + return result; + } + + /** + * set the value of an AIML predicate. + * Implements and + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the result of the operation + */ + private static String set(Node node, ParseState ps) { // add pronoun check + HashSet attributeNames = Utilities.stringSet("name", "var"); + String predicateName = getAttributeOrTagValue(node, ps, "name"); + String varName = getAttributeOrTagValue(node, ps, "var"); + String value = evalTagContent(node, ps, attributeNames).trim(); + value = value.replaceAll("(\r\n|\n\r|\r|\n)", " "); + if (predicateName != null) ps.chatSession.predicates.put(predicateName, value); + if (varName != null) ps.vars.put(varName, value); + return value; + } + + /** get the value of an AIML predicate. + * implements and + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the result of the operation + */ + private static String get(Node node, ParseState ps) { + String result = MagicStrings.unknown_predicate_value; + String predicateName = getAttributeOrTagValue(node, ps, "name"); + String varName = getAttributeOrTagValue(node, ps, "var"); + if (predicateName != null) + result = ps.chatSession.predicates.get(predicateName).trim(); + else if (varName != null) + result = ps.vars.get(varName).trim(); + return result; + } + + /** + * return the value of a bot property. + * implements {{{@code }}} + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the bot property or a string indicating the property was not found. + */ + private static String bot(Node node, ParseState ps) { + String result = MagicStrings.unknown_property_value; + //HashSet attributeNames = Utilities.stringSet("name"); + String propertyName = getAttributeOrTagValue(node, ps, "name"); + if (propertyName != null) + result = ps.chatSession.bot.properties.get(propertyName).trim(); + //log.info("BOT: "+m.getNodeValue()+"="+result); + return result; + } + + /** + * implements formatted date tag and + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the formatted date + */ + private static String date(Node node, ParseState ps) { + //HashSet attributeNames = Utilities.stringSet("jformat","format","locale","timezone"); + String jformat = getAttributeOrTagValue(node, ps, "jformat"); // AIML 2.0 + String locale = getAttributeOrTagValue(node, ps, "locale"); + String timezone = getAttributeOrTagValue(node, ps, "timezone"); + //log.info("Format = "+format+" Locale = "+locale+" Timezone = "+timezone); + String dateAsString = CalendarUtils.date(jformat, locale, timezone); + //log.info(dateAsString); + return dateAsString; + } + + /** + * MMMMMMMMM dd, yyyyAugust 2, 1960MMMMMMMMM dd, yyyy + */ + + private static String interval(Node node, ParseState ps) { + HashSet attributeNames = Utilities.stringSet("style","jformat","from","to"); + String style = getAttributeOrTagValue(node, ps, "style"); // AIML 2.0 + String jformat = getAttributeOrTagValue(node, ps, "jformat"); // AIML 2.0 + String from = getAttributeOrTagValue(node, ps, "from"); + String to = getAttributeOrTagValue(node, ps, "to"); + if (style == null) style = "years"; + if (jformat == null) jformat = "MMMMMMMMM dd, yyyy"; + if (from == null) from = "January 1, 1970"; + if (to == null) { + to = CalendarUtils.date(jformat, null, null); + } + String result = "unknown"; + if (style.equals("years")) result = ""+Interval.getYearsBetween(from, to, jformat); + if (style.equals("months")) result = ""+Interval.getMonthsBetween(from, to, jformat); + if (style.equals("days")) result = ""+Interval.getDaysBetween(from, to, jformat); + if (style.equals("hours")) result = ""+Interval.getHoursBetween(from, to, jformat); + return result; + } + + /** + * get the value of an index attribute and return it as an integer. + * if it is not recognized as an integer, return 0 + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the the integer intex value + */ + private static int getIndexValue(Node node, ParseState ps) { + int index=0; + String value = getAttributeOrTagValue(node, ps, "index"); + if (value != null) try {index = Integer.parseInt(value)-1;} catch (Exception ex) {ex.printStackTrace();} + return index; + } + + /** + * implements {@code } + * returns the value of input words matching the Nth wildcard (or AIML Set). + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the word sequence matching a wildcard + */ + private static String inputStar(Node node, ParseState ps) { + int index=getIndexValue(node, ps); + if (ps.leaf.starBindings.inputStars.star(index)==null) return ""; + else return ps.leaf.starBindings.inputStars.star(index).trim(); + } + /** + * implements {@code } + * returns the value of input words matching the Nth wildcard (or AIML Set) in . + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the word sequence matching a wildcard + */ + private static String thatStar(Node node, ParseState ps) { + int index=getIndexValue(node, ps); + if (ps.leaf.starBindings.thatStars.star(index)==null) return ""; + else return ps.leaf.starBindings.thatStars.star(index).trim(); + } + /** + * implements and + * returns the value of input words matching the Nth wildcard (or AIML Set) in a topic pattern. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the word sequence matching a wildcard + */ + private static String topicStar(Node node, ParseState ps) { + int index=getIndexValue(node, ps); + if (ps.leaf.starBindings.topicStars.star(index)==null) return ""; + else return ps.leaf.starBindings.topicStars.star(index).trim(); + } + + /** + * return the client ID. + * implements {@code } + * + * + * @param node current XML parse node + * @param ps AIML parse state + * @return client ID + */ + + private static String id(Node node, ParseState ps) { + return ps.chatSession.customerId; + } + /** + * return the size of the robot brain (number of AIML categories loaded). + * implements {@code } + * + * + * @param node current XML parse node + * @param ps AIML parse state + * @return bot brain size + */ + private static String size(Node node, ParseState ps) { + int size = ps.chatSession.bot.brain.getCategories().size(); + return String.valueOf(size); + } + /** + * return the size of the robot vocabulary (number of words the bot can recognize). + * implements {@code } + * + * + * @param node current XML parse node + * @param ps AIML parse state + * @return bot vocabulary size + */ + private static String vocabulary(Node node, ParseState ps) { + int size = ps.chatSession.bot.brain.getVocabulary().size(); + return String.valueOf(size); + } + /** + * return a string indicating the name and version of the AIML program. + * implements {@code } + * + * + * @param node current XML parse node + * @param ps AIML parse state + * @return AIML program name and version. + */ + private static String program(Node node, ParseState ps) { + return MagicStrings.programNameVersion; + } + + /** + * implements the (template-side) {@code } tag. + * returns a normalized sentence. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the nth last sentence of the bot's mth last reply. + */ + private static String that(Node node, ParseState ps) { + int index=0; + int jndex=0; + String value = getAttributeOrTagValue(node, ps, "index"); + if (value != null) + try { + String pair = value; + String[] spair = pair.split(","); + index = Integer.parseInt(spair[0])-1; + jndex = Integer.parseInt(spair[1])-1; + + } catch (Exception ex) { ex.printStackTrace(); } + String that = MagicStrings.unknown_history_item; + History hist = ps.chatSession.thatHistory.get(index); + if (hist != null) that = (String)hist.get(jndex); + return that.trim(); + } + + /** + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the nth last sentence input to the bot + */ + + private static String input(Node node, ParseState ps) { + int index=getIndexValue(node, ps); + return ps.chatSession.inputHistory.getString(index); + } + /** + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the nth last multi-sentence request to the bot. + */ + private static String request(Node node, ParseState ps) { // AIML 2.0 + int index=getIndexValue(node, ps); + return ps.chatSession.requestHistory.getString(index).trim(); + } + /** + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the bot's Nth last multi-sentence response. + */ + private static String response(Node node, ParseState ps) { // AIML 2.0 + int index=getIndexValue(node, ps); + return ps.chatSession.responseHistory.getString(index).trim(); + } + /** + * implements {@code } tag. + * Evaluate the contents, and try to execute the result as + * a command in the underlying OS shell. + * Read back and return the result of this command. + * + * The timeout parameter allows the botmaster to set a timeout + * in ms, so that the command returns eventually. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return the result of executing the system command or a string indicating the command failed. + */ + private static String system(Node node, ParseState ps) { + HashSet attributeNames = Utilities.stringSet("timeout"); + //String stimeout = getAttributeOrTagValue(node, ps, "timeout"); + String evaluatedContents = evalTagContent(node, ps, attributeNames); + String result = IOUtils.system(evaluatedContents, MagicStrings.system_failed); + return result; + } + /** + * implements {@code } tag + * + * Evaluate the tag contents but return a blank. + * "Think but don't speak." + * + * @param node current XML parse node + * @param ps AIML parse state + * @return a blank empty string + */ + private static String think(Node node, ParseState ps) { + evalTagContent(node, ps, null); + return ""; + } + + /** + * Transform a string of words (separtaed by spaces) into + * a string of individual characters (separated by spaces). + * Explode "ABC DEF" = "A B C D E F". + * + * @param node current XML parse node + * @param ps AIML parse state + * @return exploded string + */ + private static String explode(Node node, ParseState ps) { // AIML 2.0 + String result = evalTagContent(node, ps, null); + return explode(result); + } + /** + * apply the AIML normalization pre-processor to the evaluated tag contenst. + * implements {@code } tag. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return normalized string + */ + private static String normalize(Node node, ParseState ps) { // AIML 2.0 + String result = evalTagContent(node, ps, null); + return ps.chatSession.bot.preProcessor.normalize(result); + } + /** + * apply the AIML denormalization pre-processor to the evaluated tag contenst. + * implements {@code } tag. + * + * @param node current XML parse node + * @param ps AIML parse state + * @return denormalized string + */ + private static String denormalize(Node node, ParseState ps) { // AIML 2.0 + String result = evalTagContent(node, ps, null); + return ps.chatSession.bot.preProcessor.denormalize(result); + } + /** + * evaluate tag contents and return result in upper case + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return uppercase string + */ + private static String uppercase(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + return result.toUpperCase(); + } + /** + * evaluate tag contents and return result in lower case + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return lowercase string + */ + private static String lowercase(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + return result.toLowerCase(); + } + /** + * evaluate tag contents and capitalize each word. + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return capitalized string + */ + private static String formal(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + return capitalizeString(result); + } + /** + * evaluate tag contents and capitalize the first word. + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return string with first word capitalized + */ + private static String sentence(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + if (result.length() > 1) return result.substring(0, 1).toUpperCase()+result.substring(1, result.length()); + else return ""; + } + /** + * evaluate tag contents and swap 1st and 2nd person pronouns + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return sentence with pronouns swapped + */ + private static String person(Node node, ParseState ps) { + String result; + if (node.hasChildNodes()) + result = evalTagContent(node, ps, null); + else result = ps.leaf.starBindings.inputStars.star(0); // for + result = " "+result+" "; + result = ps.chatSession.bot.preProcessor.person(result); + return result.trim(); + } + /** + * evaluate tag contents and swap 1st and 3rd person pronouns + * implements {@code } tag + * + * @param node current XML parse node + * @param ps AIML parse state + * @return sentence with pronouns swapped + */ + private static String person2(Node node, ParseState ps) { + String result; + if (node.hasChildNodes()) + result = evalTagContent(node, ps, null); + else result = ps.leaf.starBindings.inputStars.star(0); // for + result = " "+result+" "; + result = ps.chatSession.bot.preProcessor.person2(result); + return result.trim(); + } + /** + * implements {@code } tag + * swaps gender pronouns + * + * @param node current XML parse node + * @param ps AIML parse state + * @return sentence with gender ronouns swapped + */ + private static String gender(Node node, ParseState ps) { + String result = evalTagContent(node, ps, null); + result = " "+result+" "; + result = ps.chatSession.bot.preProcessor.gender(result); + return result.trim(); + } + + /** + * implements {@code } tag + * @param node current XML parse node + * @param ps AIML parse state + * @return response randomly selected from the list + */ + private static String random(Node node, ParseState ps) { + NodeList childList = node.getChildNodes(); + ArrayList liList = new ArrayList(); + for (int i = 0; i < childList.getLength(); i++) + if (childList.item(i).getNodeName().equals("li")) liList.add(childList.item(i)); + return evalTagContent(liList.get((int) (Math.random() * liList.size())), ps, null); + } + private static String unevaluatedAIML(Node node, ParseState ps) { + String result = learnEvalTagContent(node, ps); + return unevaluatedXML(result, node, ps); + } + + private static String recursLearn(Node node, ParseState ps) { + String nodeName = node.getNodeName(); + if (nodeName.equals("#text")) return node.getNodeValue(); + else if (nodeName.equals("eval")) return evalTagContent(node, ps, null); // AIML 2.0 + else return unevaluatedAIML(node, ps); + } + private static String learnEvalTagContent(Node node, ParseState ps) { + String result = ""; + NodeList childList = node.getChildNodes(); + for (int i = 0; i < childList.getLength(); i++) { + Node child = childList.item(i); + result += recursLearn(child, ps); + } + return result; + } + + private static String learn(Node node, ParseState ps) { // learn, learnf AIML 2.0 + NodeList childList = node.getChildNodes(); + String pattern = ""; + String that="*"; + String template = ""; + for (int i = 0; i < childList.getLength(); i++) { + if (childList.item(i).getNodeName().equals("category")) { + NodeList grandChildList = childList.item(i).getChildNodes(); + for (int j = 0; j < grandChildList.getLength(); j++) { + if (grandChildList.item(j).getNodeName().equals("pattern")) { + pattern = recursLearn(grandChildList.item(j), ps); + } + else if (grandChildList.item(j).getNodeName().equals("that")) { + that = recursLearn(grandChildList.item(j), ps); + } + else if (grandChildList.item(j).getNodeName().equals("template")) { + template = recursLearn(grandChildList.item(j), ps); + } + } + pattern = pattern.substring("".length(),pattern.length()-"".length()); + //log.info("Learn Pattern = "+pattern); + if (template.length() >= "".length()) template = template.substring("".length()); + if (that.length() >= "".length()) that = that.substring("".length(),that.length()-"".length()); + pattern = pattern.toUpperCase(); + that = that.toUpperCase(); + if (MagicBooleans.trace_mode) { + + } + Category c; + if (node.getNodeName().equals("learn")) + c = new Category(0, pattern, that, "*", template, MagicStrings.null_aiml_file); + else {// learnf + c = new Category(0, pattern, that, "*", template, MagicStrings.learnf_aiml_file); + //ps.chatSession.bot.learnfCategories.add(c); + ps.chatSession.bot.learnfGraph.addCategory(c); + //ps.chatSession.bot.categories.add(c); + } + ps.chatSession.bot.brain.addCategory(c); + //ps.chatSession.bot.brain.printgraph(); + } + } + return ""; + } + + /** + * implements {@code with } + * re-evaluate the conditional statement until the response does not contain {@code } + * + * @param node current XML parse node + * @param ps AIML parse state + * @return result of conditional expression + */ + private static String loopCondition(Node node, ParseState ps) { + boolean loop = true; + String result=""; + int loopCnt = 0; + while (loop && loopCnt < MagicNumbers.max_loops) { + String loopResult = condition(node, ps); + if (loopResult.trim().equals(MagicStrings.too_much_recursion)) return MagicStrings.too_much_recursion; + if (loopResult.contains("")) { + loopResult = loopResult.replace("",""); + loop = true; + } + else loop = false; + result += loopResult; + } + if (loopCnt >= MagicNumbers.max_loops) result = MagicStrings.too_much_looping; + return result; + } + + /** + * implements all 3 forms of the {@code tag} + * In AIML 2.0 the conditional may return a {@code } + * + * @param node current XML parse node + * @param ps AIML parse state + * @return result of conditional expression + */ + private static String condition(Node node, ParseState ps) { + String result=""; + //boolean loop = true; + NodeList childList = node.getChildNodes(); + ArrayList liList = new ArrayList(); + String predicate=null, varName=null, value=null; //Node p=null, v=null; + HashSet attributeNames = Utilities.stringSet("name", "var", "value"); + // First check if the has an attribute "name". If so, get the predicate name. + predicate = getAttributeOrTagValue(node, ps, "name"); + varName = getAttributeOrTagValue(node, ps, "var"); + // Make a list of all the
  • child nodes: + for (int i = 0; i < childList.getLength(); i++) + if (childList.item(i).getNodeName().equals("li")) liList.add(childList.item(i)); + // if there are no
  • nodes, this is a one-shot condition. + if (liList.size() == 0 && (value = getAttributeOrTagValue(node, ps, "value")) != null && + predicate != null && + ps.chatSession.predicates.get(predicate).equals(value)) { + return evalTagContent(node, ps, attributeNames); + } + else if (liList.size() == 0 && (value = getAttributeOrTagValue(node, ps, "value")) != null && + varName != null && + ps.vars.get(varName).equals(value)) { + return evalTagContent(node, ps, attributeNames); + } + // otherwise this is a with
  • items: + else for (int i = 0; i < liList.size() && result.equals(""); i++) { + Node n = liList.get(i); + String liPredicate = predicate; + String liVarName = varName; + if (liPredicate == null) liPredicate = getAttributeOrTagValue(n, ps, "name"); + if (liVarName == null) liVarName = getAttributeOrTagValue(n, ps, "var"); + value = getAttributeOrTagValue(n, ps, "value"); + //log.info("condition name="+liPredicate+" value="+value); + if (value != null) { + // if the predicate equals the value, return the
  • item. + if (liPredicate != null && value != null && (ps.chatSession.predicates.get(liPredicate).equals(value) || + (ps.chatSession.predicates.containsKey(liPredicate) && value.equals("*")))) + return evalTagContent(n, ps, attributeNames); + else if (liVarName != null && value != null && (ps.vars.get(liVarName).equals(value) || + (ps.vars.containsKey(liPredicate) && value.equals("*")))) + return evalTagContent(n, ps, attributeNames); + } + else // this is a terminal
  • with no predicate or value, i.e. the default condition. + return evalTagContent(n, ps, attributeNames); + } + return ""; + + } + + /** + * check to see if a result contains a {@code } tag. + * + * @param node current XML parse node + * @return true or false + */ + public static boolean evalTagForLoop(Node node) { + NodeList childList = node.getChildNodes(); + for (int i = 0; i < childList.getLength(); i++) + if (childList.item(i).getNodeName().equals("loop")) return true; + return false; + } + + /** + * Recursively descend the XML DOM tree, evaluating AIML and building a response. + * + * @param node current XML parse node + * @param ps AIML parse state + */ + + + private static String recursEval(Node node, ParseState ps) { + try { + String nodeName = node.getNodeName(); + if (nodeName.equals("#text")) return node.getNodeValue(); + else if (nodeName.equals("#comment")) { + //log.info("recursEval comment = "+node.getTextContent()); + return ""; + } + else if (nodeName.equals("template")) + return evalTagContent(node, ps, null); + else if (nodeName.equals("random")) + return random(node, ps); + else if (nodeName.equals("condition")) + return loopCondition(node, ps); + else if (nodeName.equals("srai")) + return srai(node, ps); + else if (nodeName.equals("sr")) + return respond(ps.leaf.starBindings.inputStars.star(0), ps.that, ps.topic, ps.chatSession, sraiCount); + else if (nodeName.equals("sraix")) + return sraix(node, ps); + else if (nodeName.equals("set")) + return set(node, ps); + else if (nodeName.equals("get")) + return get(node, ps); + else if (nodeName.equals("map")) // AIML 2.0 -- see also in pattern + return map(node, ps); + else if (nodeName.equals("bot")) + return bot(node, ps); + else if (nodeName.equals("id")) + return id(node, ps); + else if (nodeName.equals("size")) + return size(node, ps); + else if (nodeName.equals("vocabulary")) // AIML 2.0 + return vocabulary(node, ps); + else if (nodeName.equals("program")) + return program(node, ps); + else if (nodeName.equals("date")) + return date(node, ps); + else if (nodeName.equals("interval")) + return interval(node, ps); + //else if (nodeName.equals("gossip")) // removed from AIML 2.0 + // return gossip(node, ps); + else if (nodeName.equals("think")) + return think(node, ps); + else if (nodeName.equals("system")) + return system(node, ps); + else if (nodeName.equals("explode")) + return explode(node, ps); + else if (nodeName.equals("normalize")) + return normalize(node, ps); + else if (nodeName.equals("denormalize")) + return denormalize(node, ps); + else if (nodeName.equals("uppercase")) + return uppercase(node, ps); + else if (nodeName.equals("lowercase")) + return lowercase(node, ps); + else if (nodeName.equals("formal")) + return formal(node, ps); + else if (nodeName.equals("sentence")) + return sentence(node, ps); + else if (nodeName.equals("person")) + return person(node, ps); + else if (nodeName.equals("person2")) + return person2(node, ps); + else if (nodeName.equals("gender")) + return gender(node, ps); + else if (nodeName.equals("star")) + return inputStar(node, ps); + else if (nodeName.equals("thatstar")) + return thatStar(node, ps); + else if (nodeName.equals("topicstar")) + return topicStar(node, ps); + else if (nodeName.equals("that")) + return that(node, ps); + else if (nodeName.equals("input")) + return input(node, ps); + else if (nodeName.equals("request")) + return request(node, ps); + else if (nodeName.equals("response")) + return response(node, ps); + else if (nodeName.equals("learn") || nodeName.equals("learnf")) + return learn(node, ps); + else if (extension != null && extension.extensionTagSet().contains(nodeName)) return extension.recursEval(node, ps) ; + else return (genericXML(node, ps)); + } catch (Exception ex) { + ex.printStackTrace(); + return ""; + } + } + + /** + * evaluate an AIML template expression + * + * @param template AIML template contents + * @param ps AIML Parse state + * @return result of evaluating template. + */ + private static String evalTemplate(String template, ParseState ps) { + String response = MagicStrings.template_failed; + try { + template = ""; + Node root = DomUtils.parseString(template); + response = recursEval(root, ps); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } + /** + * check to see if a template is a valid XML expression. + * + * @param template AIML template contents + * @return true or false. + */ + public static boolean validTemplate(String template) { + try { + template = ""; + DomUtils.parseString(template); + return true; + } catch (Exception e) { + //e.printStackTrace(); + + return false; + } + + } + +} + + + diff --git a/app/src/main/java/org/alicebot/ab/AIMLProcessorExtension.java b/app/src/main/java/org/alicebot/ab/AIMLProcessorExtension.java new file mode 100644 index 0000000..d83ff7b --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/AIMLProcessorExtension.java @@ -0,0 +1,49 @@ +package org.alicebot.ab; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import org.w3c.dom.Node; + +import java.util.Set; + +/** + * The interface needed to implement AIML Extension + * + * A class implementing AIMLProcessorExtension should return + * a Set of tag names and provide a function to recursively evaluate the + * XML parse tree for each node associated with a new tag. + */ +public interface AIMLProcessorExtension { + /** + * provide the AIMLProcessor with a list of extension tag names. + * + * @return Set of extension tag names + */ + public Set extensionTagSet(); + + /** + * recursively evaluate AIML from a node corresponding an extension tag + * + * @param node current XML parse node + * @param ps current parse state + * @return result of evaluating AIML + */ + public String recursEval(Node node, ParseState ps); +} diff --git a/app/src/main/java/org/alicebot/ab/AIMLSet.java b/app/src/main/java/org/alicebot/ab/AIMLSet.java new file mode 100644 index 0000000..8dd5f35 --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/AIMLSet.java @@ -0,0 +1,151 @@ +package org.alicebot.ab; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; + +/** + * implements AIML Sets + */ +public class AIMLSet extends HashSet { + //private static final Logger log = LoggerFactory.getLogger(AIMLSet.class); + public String setName; + int maxLength = 1; // there are no empty sets + String host; // for external sets + String botid; // for external sets + boolean isExternal = false; + + + /** + * constructor + * @param name name of set + */ + public AIMLSet (String name) { + super(); + this.setName = name.toLowerCase(); + if (setName.equals(MagicStrings.natural_number_set_name)) maxLength = 1; + } + public boolean contains(String s) { + //if (isExternal) log.info("External "+setName+" contains "+s+"?"); + //else log.info("Internal "+setName+" contains "+s+"?"); + if (isExternal && MagicBooleans.enable_external_sets) { + String[] split = s.split(" "); + if (split.length > maxLength) return false; + String query = MagicStrings.set_member_string+setName.toUpperCase()+" "+s; + String response = Sraix.sraix(null, query, "false", null, host, botid, null, "0"); + // log.info("External "+setName+" contains "+s+"? "+response); + if (response.equals("true")) return true; + else return false; + } else if (setName.equals(MagicStrings.natural_number_set_name)) { + Pattern numberPattern = Pattern.compile("[0-9]+"); + Matcher numberMatcher = numberPattern.matcher(s); + Boolean isanumber = numberMatcher.matches(); + //log.info("AIMLSet isanumber '"+s+"' "+isanumber); + return isanumber; + } + else return super.contains(s); + } + public void writeAIMLSet () { + // log.info("Writing AIML Set "+setName); + try{ + // Create file + FileWriter fstream = new FileWriter(MagicStrings.sets_path+"/"+setName+".txt"); + BufferedWriter out = new BufferedWriter(fstream); + for (String p : this) { + + out.write(p.trim()); + out.newLine(); + } + //Close the output stream + out.close(); + }catch (Exception e){//Catch exception if any + // log.error("Cannot write AIML set " + setName + ": " + e, e); + } + } + public int readAIMLSetFromInputStream(InputStream in, Bot bot) { + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + String strLine; + int cnt = 0; + //Read File Line By Line + try { + while ((strLine = br.readLine()) != null && strLine.length() > 0) { + cnt++; + //strLine = bot.preProcessor.normalize(strLine).toUpperCase(); + // assume the set is pre-normalized for faster loading + if (strLine.startsWith("external")) { + String[] splitLine = strLine.split(":"); + if (splitLine.length >= 4) { + host = splitLine[1]; + botid = splitLine[2]; + maxLength = Integer.parseInt(splitLine[3]); + isExternal = true; + // log.info("Created external set at "+host+" "+botid); + } + } + else { + strLine = strLine.toUpperCase().trim(); + String [] splitLine = strLine.split(" "); + int length = splitLine.length; + if (length > maxLength) maxLength = length; + //log.info("readAIMLSetFromInputStream "+strLine); + add(strLine.trim()); + } + /*Category c = new Category(0, "ISA"+setName.toUpperCase()+" "+strLine.toUpperCase(), "*", "*", "true", MagicStrings.null_aiml_file); + bot.brain.addCategory(c);*/ + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return cnt; + } + + public void readAIMLSet (Bot bot) { + final String setPath = MagicStrings.sets_path+"/"+setName+".txt"; + // log.info("Reading AIML Set " + setPath); + try{ + // Open the file that is the first + // command line parameter + File file = new File(setPath); + if (file.exists()) { + FileInputStream fstream = new FileInputStream(setPath); + // Get the object + readAIMLSetFromInputStream(fstream, bot); + fstream.close(); + } + // else log.info(MagicStrings.sets_path+"/"+setName+".txt not found"); + }catch (Exception e){//Catch exception if any + // log.error("Cannot read AIML set '" + setPath + "': " + e, e); + } + + } + +} diff --git a/app/src/main/java/org/alicebot/ab/AIMLWriter.java b/app/src/main/java/org/alicebot/ab/AIMLWriter.java new file mode 100644 index 0000000..48121b3 --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/AIMLWriter.java @@ -0,0 +1,86 @@ +package org.alicebot.ab; + +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * Helper class to write some custom AIML + */ +public class AIMLWriter { + //private static final Logger log = LoggerFactory.getLogger(AIMLWriter.class); + public static String[][] relatives = { + // aunt uncle nephew niece grandma grandpa + {"aunt", "her", "who", "aunt"}, + {"ant", "her", "who", "aunt"}, + {"uncle", "his", "who", "uncle"}, + {"friend", "his", "who", "friend"}, + {"bestfriend", "his", "who", "bestfriend"}, + {"niece", "her", "who", "niece"}, + {"nephew", "his", "who", "nephew"}, + {"grandmother", "her", "who", "grandmother"}, + {"grandma", "her", "who", "grandmother"}, + {"grandmom", "her", "who", "grandmother"}, + {"mother", "her", "who", "mother"}, + {"ma", "her", "who", "mother"}, + {"mom", "her", "who", "mother"}, + {"momma", "her", "who", "mother"}, + {"mum", "her", "who", "mother"}, + {"mumma", "her", "who", "mother"}, + {"mommy", "her", "who", "mother"}, + {"mummy", "her", "who", "mother"}, + {"grandfather", "his", "who", "grandfather"}, + {"granddad", "his", "who", "grandfather"}, + {"father", "his", "who", "father"}, + {"dad", "his", "who", "father"}, + {"dada", "his", "who", "father"}, + {"daddy", "his", "who", "father"}, + {"husband", "his", "who", "husband"}, + {"hubby", "his", "who", "husband"}, + {"wife", "her", "who", "wife"}, + {"wifey", "her", "who", "wife"}, + {"son", "his", "who", "son"}, + {"daughter", "her", "who", "daughter"}, + {"brother", "his", "who", "brother"}, + {"sister", "her", "who", "sister"}, + {"bro", "his", "who", "brother"}, + {"sis", "her", "who", "sister"}, + {"boyfriend", "his", "who", "boyfriend"}, + {"girlfriend", "her", "who", "girlfriend"}}; + public static void familiarContactAIML () { + for (int i = 0; i < relatives.length; i++) { + String familiar = relatives[i][0]; + String pronoun = relatives[i][1]; + String predicate = relatives[i][3]; + String aiml = + "ISFAMILIARNAME "+familiar.toUpperCase()+"" + + "\n"+ + "FAMILIARPREDICATE "+familiar.toUpperCase()+"" + + "\n"+ + "FAMILIARPRONOUN "+familiar.toUpperCase()+"" + + "\n"; + + + + //info(aiml); + } + } +} diff --git a/app/src/main/java/org/alicebot/ab/Bot.java b/app/src/main/java/org/alicebot/ab/Bot.java new file mode 100644 index 0000000..81ae63c --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/Bot.java @@ -0,0 +1,756 @@ +package org.alicebot.ab; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Set; + +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; + +/** + * Class representing the AIML bot + */ +public class Bot { + //private static final Logger log = LoggerFactory.getLogger(Bot.class); + public final Properties properties = new Properties(); + public final PreProcessor preProcessor; + public final Graphmaster brain; + public final Graphmaster inputGraph; + public final Graphmaster learnfGraph; + public final Graphmaster patternGraph; + public final Graphmaster deletedGraph; + public Graphmaster unfinishedGraph; + // public final ArrayList categories; + public ArrayList suggestedCategories; + public String name=MagicStrings.unknown_bot_name; + public HashMap setMap = new HashMap(); + public HashMap mapMap = new HashMap(); + + /** + * Set all directory path variables for this bot + * + * @param root root directory of Program AB + * @param name name of bot + */ + public void setAllPaths (String root, String name) { + MagicStrings.bot_path = root+"/bots"; + MagicStrings.bot_name_path = MagicStrings.bot_path+"/"+name; + // org.alicebot.ab + // org.alicebot.ab.cli + // org.alicebot.ab.utils + MagicStrings.aiml_path = MagicStrings.bot_name_path+"/aiml"; + MagicStrings.aimlif_path = MagicStrings.bot_name_path+"/aimlif"; + MagicStrings.config_path = MagicStrings.bot_name_path+"/config"; + MagicStrings.log_path = MagicStrings.bot_name_path+"/logs"; + MagicStrings.sets_path = MagicStrings.bot_name_path+"/sets"; + MagicStrings.maps_path = MagicStrings.bot_name_path+"/maps"; + + } + + /** + * Constructor (default action, default path, default bot name) + */ + public Bot() { + this(MagicStrings.default_bot); + } + + /** + * Constructor (default action, default path) + * @param name + */ + public Bot(String name) { + this(name, MagicStrings.root_path); + } + + /** + * Constructor (default action) + * + * @param name + * @param path + */ + public Bot(String name, String path) { + this(name, path, "auto"); + } + + /** + * Constructor + * + * @param name name of bot + * @param path root path of Program AB + * @param action Program AB action + */ + public Bot(String name, String path, String action) { + this.name = name; + setAllPaths(path, name); + this.brain = new Graphmaster(this); + this.inputGraph = new Graphmaster(this); + this.learnfGraph = new Graphmaster(this); + this.deletedGraph = new Graphmaster(this); + this.patternGraph = new Graphmaster(this); + this.unfinishedGraph = new Graphmaster(this); + // this.categories = new ArrayList(); + this.suggestedCategories = new ArrayList(); + preProcessor = new PreProcessor(this); + addProperties(); + addAIMLSets(); + addAIMLMaps(); + AIMLSet number = new AIMLSet(MagicStrings.natural_number_set_name); + setMap.put(MagicStrings.natural_number_set_name, number); + AIMLMap successor = new AIMLMap(MagicStrings.map_successor); + mapMap.put(MagicStrings.map_successor, successor); + AIMLMap predecessor = new AIMLMap(MagicStrings.map_predecessor); + mapMap.put(MagicStrings.map_predecessor, predecessor); + ////info("setMap = "+setMap); + Date aimlDate = new Date(new File(MagicStrings.aiml_path).lastModified()); + Date aimlIFDate = new Date(new File(MagicStrings.aimlif_path).lastModified()); + //info("AIML modified {} AIMLIF modified {}", aimlDate, aimlIFDate); + readDeletedIFCategories(); + readUnfinishedIFCategories(); + MagicStrings.pannous_api_key = Utilities.getPannousAPIKey(); + MagicStrings.pannous_login = Utilities.getPannousLogin(); + if (action.equals("aiml2csv")) addCategoriesFromAIML(); + else if (action.equals("csv2aiml")) addCategoriesFromAIMLIF(); + else if (aimlDate.after(aimlIFDate)) { + //info("AIML modified after AIMLIF"); + addCategoriesFromAIML(); + writeAIMLIFFiles(); + } + else { + addCategoriesFromAIMLIF(); + if (brain.getCategories().size()==0) { + //info("No AIMLIF Files found. Looking for AIML"); + addCategoriesFromAIML(); + } + } + //info("--> Bot {} {} completed {} deleted {} unfinished", + //name, brain.getCategories().size(), deletedGraph.getCategories().size(), unfinishedGraph.getCategories().size()); + } + + /** + * add an array list of categories with a specific file name + * + * @param file name of AIML file + * @param moreCategories list of categories + */ + void addMoreCategories (String file, ArrayList moreCategories) { + if (file.contains(MagicStrings.deleted_aiml_file)) { + for (Category c : moreCategories) { + ////info("Delete "+c.getPattern()); + deletedGraph.addCategory(c); + } + } else if (file.contains(MagicStrings.unfinished_aiml_file)) { + for (Category c : moreCategories) { + ////info("Delete "+c.getPattern()); + if (brain.findNode(c) == null) + unfinishedGraph.addCategory(c); + //info("unfinished {} found in brain", c.inputThatTopic()); + } + } else if (file.contains(MagicStrings.learnf_aiml_file) ) { + //info("Reading Learnf file"); + for (Category c : moreCategories) { + brain.addCategory(c); + learnfGraph.addCategory(c); + patternGraph.addCategory(c); + } + //this.categories.addAll(moreCategories); + } else { + for (Category c : moreCategories) { + ////info("Brain size="+brain.root.size()); + //brain.printgraph(); + brain.addCategory(c); + patternGraph.addCategory(c); + //brain.printgraph(); + } + //this.categories.addAll(moreCategories); + } + } + + /** + * Load all brain categories from AIML directory + */ + void addCategoriesFromAIML() { + Timer timer = new Timer(); + timer.start(); + try { + // Directory path here + String file; + File folder = new File(MagicStrings.aiml_path); + if (folder.exists()) { + File[] listOfFiles = folder.listFiles(); + //info("Loading AIML files from '{}'", MagicStrings.aiml_path); + for (File listOfFile : listOfFiles) { + if (listOfFile.isFile()) { + file = listOfFile.getName(); + if (file.endsWith(".aiml") || file.endsWith(".AIML")) { + //info(file); + try { + ArrayList moreCategories = AIMLProcessor.AIMLToCategories(MagicStrings.aiml_path, file); + addMoreCategories(file, moreCategories); + } catch (Exception iex) { + //error("Problem loading '" + file +"': " + iex, iex); + } + } + } + } + } + //info("addCategories: '{}' does not exist.", MagicStrings.aiml_path); + } catch (Exception ex) { + ex.printStackTrace(); + } + //info("Loaded {} categories in {} sec", brain.getCategories().size(), timer.elapsedTimeSecs()); + } + + /** + * load all brain categories from AIMLIF directory + */ + void addCategoriesFromAIMLIF() { + Timer timer = new Timer(); + timer.start(); + try { + // Directory path here + String file; + File folder = new File(MagicStrings.aimlif_path); + if (folder.exists()) { + File[] listOfFiles = folder.listFiles(); + //info("Loading AIML files from '{}'", MagicStrings.aimlif_path); + for (File listOfFile : listOfFiles) { + if (listOfFile.isFile()) { + file = listOfFile.getName(); + if (file.endsWith(MagicStrings.aimlif_file_suffix) || file.endsWith(MagicStrings.aimlif_file_suffix.toUpperCase())) { + ////info(file); + try { + ArrayList moreCategories = readIFCategories(MagicStrings.aimlif_path + "/" + file); + addMoreCategories(file, moreCategories); + // MemStats.memStats(); + } catch (Exception iex) { + //error("Problem loading '" + file + "': " + iex, iex); + } + } + } + } + } + //info("addCategories: '{}' does not exist.", MagicStrings.aimlif_path); + } catch (Exception ex) { + ex.printStackTrace(); + } + //info("Loaded {} categories in {} sec", brain.getCategories().size(), timer.elapsedTimeSecs()); + } + + /** + * read deleted categories from AIMLIF file + */ + public void readDeletedIFCategories() { + readCertainIFCategories(deletedGraph, MagicStrings.deleted_aiml_file); + } + + /** + * read unfinished categories from AIMLIF file + */ + public void readUnfinishedIFCategories() { + readCertainIFCategories(unfinishedGraph, MagicStrings.unfinished_aiml_file); + } + + /** + * update unfinished categories removing any categories that have been finished + */ + public void updateUnfinishedCategories () { + ArrayList unfinished = unfinishedGraph.getCategories(); + unfinishedGraph = new Graphmaster(this); + for (Category c : unfinished) { + if (!brain.existsCategory(c)) unfinishedGraph.addCategory(c); + } + } + + /** + * write all AIML and AIMLIF categories + */ + public void writeQuit() { + writeAIMLIFFiles(); + //info("Wrote AIMLIF Files"); + writeAIMLFiles(); + //info("Wrote AIML Files"); + writeDeletedIFCategories(); + updateUnfinishedCategories(); + writeUnfinishedIFCategories(); + + } + + /** + * read categories from specified AIMLIF file into specified graph + * + * @param graph Graphmaster to store categories + * @param fileName file name of AIMLIF file + */ + public void readCertainIFCategories(Graphmaster graph, String fileName) { + File file = new File(MagicStrings.aimlif_path+"/"+fileName+MagicStrings.aimlif_file_suffix); + if (file.exists()) { + try { + ArrayList deletedCategories = readIFCategories(MagicStrings.aimlif_path+"/"+fileName+MagicStrings.aimlif_file_suffix); + for (Category d : deletedCategories) graph.addCategory(d); + //info("readCertainIFCategories {} categories from {}", graph.getCategories().size(), fileName+MagicStrings.aimlif_file_suffix); + } catch (Exception iex) { + //error("Problem loading '" + fileName + "': " + iex, iex); + } + } + //info("No "+MagicStrings.deleted_aiml_file+MagicStrings.aimlif_file_suffix+" file found"); + } + + /** + * write certain specified categories as AIMLIF files + * + * @param graph the Graphmaster containing the categories to write + * @param file the destination AIMLIF file + */ + public void writeCertainIFCategories(Graphmaster graph, String file) { + if (MagicBooleans.trace_mode) //info("writeCertainIFCaegories "+file+" size= "+graph.getCategories().size()); + writeIFCategories(graph.getCategories(), file+MagicStrings.aimlif_file_suffix); + File dir = new File(MagicStrings.aimlif_path); + dir.setLastModified(new Date().getTime()); + } + + /** + * write deleted categories to AIMLIF file + */ + public void writeDeletedIFCategories() { + writeCertainIFCategories(deletedGraph, MagicStrings.deleted_aiml_file); + } + + /** + * write learned categories to AIMLIF file + */ + public void writeLearnfIFCategories() { + writeCertainIFCategories(learnfGraph, MagicStrings.learnf_aiml_file); + } + + /** + * write unfinished categories to AIMLIF file + */ + public void writeUnfinishedIFCategories() { + writeCertainIFCategories(unfinishedGraph, MagicStrings.unfinished_aiml_file); + } + + /** + * write categories to AIMLIF file + * + * @param cats array list of categories + * @param filename AIMLIF filename + */ + public void writeIFCategories (ArrayList cats, String filename) { + ////info("writeIFCategories "+filename); + BufferedWriter bw = null; + File existsPath = new File(MagicStrings.aimlif_path); + if (existsPath.exists()) + try { + //Construct the bw object + bw = new BufferedWriter(new FileWriter(MagicStrings.aimlif_path+"/"+filename)) ; + for (Category category : cats) { + bw.write(Category.categoryToIF(category)); + bw.newLine(); + } + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + } catch (IOException ex) { + ex.printStackTrace(); + } finally { + //Close the bw + try { + if (bw != null) { + bw.flush(); + bw.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + /** + * Write all AIMLIF files from bot brain + */ + public void writeAIMLIFFiles () { + //info("writeAIMLIFFiles"); + HashMap fileMap = new HashMap(); + if (deletedGraph.getCategories().size() > 0) writeDeletedIFCategories(); + ArrayList brainCategories = brain.getCategories(); + Collections.sort(brainCategories, Category.CATEGORY_NUMBER_COMPARATOR); + for (Category c : brainCategories) { + try { + BufferedWriter bw; + String fileName = c.getFilename(); + if (fileMap.containsKey(fileName)) bw = fileMap.get(fileName); + else { + bw = new BufferedWriter(new FileWriter(MagicStrings.aimlif_path+"/"+fileName+MagicStrings.aimlif_file_suffix)); + fileMap.put(fileName, bw); + + } + bw.write(Category.categoryToIF(c)); + bw.newLine(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + Set set = fileMap.keySet(); + for (Object aSet : set) { + BufferedWriter bw = fileMap.get(aSet); + //Close the bw + try { + if (bw != null) { + bw.flush(); + bw.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + + } + + } + File dir = new File(MagicStrings.aimlif_path); + dir.setLastModified(new Date().getTime()); + } + + /** + * Write all AIML files. Adds categories for BUILD and DEVELOPMENT ENVIRONMENT + */ + public void writeAIMLFiles () { + HashMap fileMap = new HashMap(); + Category b = new Category(0, "BUILD", "*", "*", new Date().toString(), "update.aiml"); + brain.addCategory(b); + b = new Category(0, "DELEVLOPMENT ENVIRONMENT", "*", "*", MagicStrings.programNameVersion, "update.aiml"); + brain.addCategory(b); + ArrayList brainCategories = brain.getCategories(); + Collections.sort(brainCategories, Category.CATEGORY_NUMBER_COMPARATOR); + for (Category c : brainCategories) { + + if (!c.getFilename().equals(MagicStrings.null_aiml_file)) + try { + ////info("Writing "+c.getCategoryNumber()+" "+c.inputThatTopic()); + BufferedWriter bw; + String fileName = c.getFilename(); + if (fileMap.containsKey(fileName)) bw = fileMap.get(fileName); + else { + String copyright = Utilities.getCopyright(this, fileName); + bw = new BufferedWriter(new FileWriter(MagicStrings.aiml_path+"/"+fileName)); + fileMap.put(fileName, bw); + bw.write("" + "\n" + + "\n"); + bw.write(copyright); + //bw.newLine(); + } + bw.write(Category.categoryToAIML(c)+"\n"); + //bw.newLine(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + Set set = fileMap.keySet(); + for (Object aSet : set) { + BufferedWriter bw = fileMap.get(aSet); + //Close the bw + try { + if (bw != null) { + bw.write("\n"); + bw.flush(); + bw.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + + } + + } + File dir = new File(MagicStrings.aiml_path); + dir.setLastModified(new Date().getTime()); + } + + /** + * load bot properties + */ + void addProperties() { + try { + properties.getProperties(MagicStrings.config_path+"/properties.txt"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + static int leafPatternCnt = 0; + static int starPatternCnt = 0; + + /** find suggested patterns in a graph of inputs + * + */ + public void findPatterns() { + findPatterns(inputGraph.root, ""); + //info("{} Leaf Patterns {} Star Patterns", leafPatternCnt, starPatternCnt); + } + + /** find patterns recursively + * + * @param node current graph node + * @param partialPatternThatTopic partial pattern path + */ + void findPatterns(Nodemapper node, String partialPatternThatTopic) { + if (NodemapperOperator.isLeaf(node)) { + ////info("LEAF: "+node.category.getActivationCnt()+". "+partialPatternThatTopic); + if (node.category.getActivationCnt() > MagicNumbers.node_activation_cnt) { + ////info("LEAF: "+node.category.getActivationCnt()+". "+partialPatternThatTopic+" "+node.shortCut); //Start writing to the output stream + leafPatternCnt ++; + try { + String categoryPatternThatTopic = ""; + if (node.shortCut) { + ////info("Partial patternThatTopic = "+partialPatternThatTopic); + categoryPatternThatTopic = partialPatternThatTopic + " * *"; + } + else categoryPatternThatTopic = partialPatternThatTopic; + Category c = new Category(0, categoryPatternThatTopic, MagicStrings.blank_template, MagicStrings.unknown_aiml_file); + //if (brain.existsCategory(c)) //info(c.inputThatTopic()+" Exists"); + //if (deleted.existsCategory(c)) //info(c.inputThatTopic()+ " Deleted"); + if (!brain.existsCategory(c) && !deletedGraph.existsCategory(c) && !unfinishedGraph.existsCategory(c)) { + patternGraph.addCategory(c); + suggestedCategories.add(c); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + if(NodemapperOperator.size(node) > MagicNumbers.node_size) { + ////info("STAR: "+NodemapperOperator.size(node)+". "+partialPatternThatTopic+" * * *"); + starPatternCnt ++; + try { + Category c = new Category(0, partialPatternThatTopic+" * * *", MagicStrings.blank_template, MagicStrings.unknown_aiml_file); + //if (brain.existsCategory(c)) //info(c.inputThatTopic()+" Exists"); + //if (deleted.existsCategory(c)) //info(c.inputThatTopic()+ " Deleted"); + if (!brain.existsCategory(c) && !deletedGraph.existsCategory(c) && !unfinishedGraph.existsCategory(c)) { + patternGraph.addCategory(c); + suggestedCategories.add(c); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + for (String key : NodemapperOperator.keySet(node)) { + Nodemapper value = NodemapperOperator.get(node, key); + findPatterns(value, partialPatternThatTopic + " " + key); + } + + } + + /** classify inputs into matching categories + * + * @param filename file containing sample normalized inputs + */ + public void classifyInputs (String filename) { + try{ + FileInputStream fstream = new FileInputStream(filename); + // Get the object + BufferedReader br = new BufferedReader(new InputStreamReader(fstream)); + String strLine; + //Read File Line By Line + int count = 0; + while ((strLine = br.readLine())!= null) { + // Print the content on the console + ////info("Classifying "+strLine); + if (strLine.startsWith("Human: ")) strLine = strLine.substring("Human: ".length(), strLine.length()); + Nodemapper match = patternGraph.match(strLine, "unknown", "unknown"); + match.category.incrementActivationCnt(); + count += 1; + } + //Close the input stream + br.close(); + } catch (Exception e){//Catch exception if any + //error("Cannot classify inputs from '" + filename + "': " + e, e); + } + } + + /** read sample inputs from filename, turn them into Paths, and + * add them to the graph. + * + * @param filename file containing sample inputs + */ + public void graphInputs (String filename) { + try{ + // Open the file that is the first + // command line parameter + FileInputStream fstream = new FileInputStream(filename); + // Get the object + BufferedReader br = new BufferedReader(new InputStreamReader(fstream)); + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + //strLine = preProcessor.normalize(strLine); + Category c = new Category(0, strLine, "*", "*", "nothing", MagicStrings.unknown_aiml_file); + Nodemapper node = inputGraph.findNode(c); + if (node == null) { + inputGraph.addCategory(c); + c.incrementActivationCnt(); + } + else node.category.incrementActivationCnt(); + ////info("Root branches="+g.root.size()); + } + //Close the input stream + br.close(); + }catch (Exception e){//Catch exception if any + //error("Cannot graph inputs from '" + filename + "': " + e, e); + } + } + + + + /** + * read AIMLIF categories from a file into bot brain + * + * @param filename name of AIMLIF file + * @return array list of categories read + */ + public ArrayList readIFCategories (String filename) { + ArrayList categories = new ArrayList(); + try{ + // Open the file that is the first + // command line parameter + FileInputStream fstream = new FileInputStream(filename); + // Get the object + BufferedReader br = new BufferedReader(new InputStreamReader(fstream)); + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + try { + Category c = Category.IFToCategory(strLine); + categories.add(c); + } catch (Exception ex) { + //warn("Invalid AIMLIF in "+filename+" line "+strLine, ex); + } + } + //Close the input stream + br.close(); + }catch (Exception e){//Catch exception if any + //error("Cannot read IF Categories from '" + filename + "': " + e, e); + } + return categories; + } + + /** + * check Graphmaster for shadowed categories + */ + public void shadowChecker () { + shadowChecker(brain.root) ; + } + + /** traverse graph and test all categories found in leaf nodes for shadows + * + * @param node + */ + void shadowChecker(Nodemapper node) { + if (NodemapperOperator.isLeaf(node)) { + String input = node.category.getPattern().replace("*", "XXX").replace("_", "XXX"); + String that = node.category.getThat().replace("*", "XXX").replace("_", "XXX"); + String topic = node.category.getTopic().replace("*", "XXX").replace("_", "XXX"); + Nodemapper match = brain.match(input, that, topic); + if (match != node) { + //info(Graphmaster.inputThatTopic(input, that, topic)); + //info("MATCHED: {}", match.category.inputThatTopic()); + //info("SHOULD MATCH: {}", node.category.inputThatTopic()); + } + } + else { + for (String key : NodemapperOperator.keySet(node)) { + shadowChecker(NodemapperOperator.get(node, key)); + } + } + } + + /** + * Load all AIML Sets + */ + void addAIMLSets() { + Timer timer = new Timer(); + timer.start(); + try { + // Directory path here + String file; + File folder = new File(MagicStrings.sets_path); + if (folder.exists()) { + File[] listOfFiles = folder.listFiles(); + //info("Loading AIML Sets files from '{}'", MagicStrings.sets_path); + for (File listOfFile : listOfFiles) { + if (listOfFile.isFile()) { + file = listOfFile.getName(); + if (file.endsWith(".txt") || file.endsWith(".TXT")) { + //info(file); + String setName = file.substring(0, file.length()-".txt".length()); + //info("Read AIML Set {}", setName); + AIMLSet aimlSet = new AIMLSet(setName); + aimlSet.readAIMLSet(this); + setMap.put(setName, aimlSet); + } + } + } + } + //info("addAIMLSets: {} does not exist.", MagicStrings.sets_path); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + /** + * Load all AIML Maps + */ + void addAIMLMaps() { + Timer timer = new Timer(); + timer.start(); + try { + // Directory path here + String file; + File folder = new File(MagicStrings.maps_path); + if (folder.exists()) { + File[] listOfFiles = folder.listFiles(); + //info("Loading AIML Map files from '{}'", MagicStrings.maps_path); + for (File listOfFile : listOfFiles) { + if (listOfFile.isFile()) { + file = listOfFile.getName(); + if (file.endsWith(".txt") || file.endsWith(".TXT")) { + //info(file); + String mapName = file.substring(0, file.length()-".txt".length()); + //info("Read AIML Map "+mapName); + AIMLMap aimlMap = new AIMLMap(mapName); + aimlMap.readAIMLMap(this); + mapMap.put(mapName, aimlMap); + } + } + } + } + //info("addCategories: '{}' does not exist.", MagicStrings.aiml_path); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/org/alicebot/ab/Category.java b/app/src/main/java/org/alicebot/ab/Category.java new file mode 100644 index 0000000..3adacfa --- /dev/null +++ b/app/src/main/java/org/alicebot/ab/Category.java @@ -0,0 +1,392 @@ +package org.alicebot.ab; +/* Program AB Reference AIML 2.0 implementation + Copyright (C) 2013 ALICE A.I. Foundation + Contact: info@alicebot.org + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +import java.util.Comparator; + +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; + +/** + * structure representing an AIML category and operations on Category + */ +public class Category { + //private static final Logger log = LoggerFactory.getLogger(Category.class); + private String pattern; + private String that; + private String topic; + private String template; + private String filename; + private int activationCnt; + private final int categoryNumber; // for loading order + public static int categoryCnt = 0; + private AIMLSet matches; + + /** + * Return a set of inputs matching the category + * + * @return and AIML Set of elements matching this category + */ + public AIMLSet getMatches() { + if (matches != null) + return matches; + else return new AIMLSet("No Matches"); + } + + /** + * number of times a category was activated by inputs + * + * @return integer number of activations + */ + public int getActivationCnt () { + return activationCnt; + } + + /** + * get the index number of this category + * + * @return unique integer identifying this category + */ + public int getCategoryNumber () { + return categoryNumber; + } + + /** + * get category pattern + * + * @return pattern + */ + public String getPattern () { + if (pattern == null) return "*"; + else return pattern; + } + /** + * get category that pattern + * + * @return that pattern + */ + public String getThat () { + if (that == null) return "*"; + else return that; + } + /** + * get category topic pattern + * + * @return topic pattern + */ + public String getTopic () { + if (topic == null) return "*"; + else return topic; + } + /** + * get category template + * + * @return template + */ + public String getTemplate () { + if (template==null) return ""; + else + return template; + } + /** + * get name of AIML file for this category + * + * @return file name + */ + public String getFilename () { + if (filename==null) return MagicStrings.unknown_aiml_file; + else + return filename; + } + + /** + * increment the category activation count + */ + public void incrementActivationCnt() { + activationCnt++; + } + + /** set category activation count + * + * @param cnt activation count + */ + public void setActivationCnt(int cnt) { + activationCnt = cnt; + } + + /** + * set category filename + * @param filename name of AIML file + */ + public void setFilename(String filename) { + this.filename = filename; + } + /** + * set category template + * @param template AIML template + */ + public void setTemplate(String template) { + this.template = template; + } + + /** + * set category pattern + * + * @param pattern AIML pattern + */ + public void setPattern(String pattern) { + this.pattern = pattern; + } + + /** + * set category that pattern + * + * @param that AIML that pattern + */ + public void setThat(String that) { + this.that = that; + } + + /** + * set category topic + * + * @param topic AIML topic pattern + */ + public void setTopic(String topic) { + this.topic = topic; + } + + /** + * return a string represeting the full pattern path as "{@code input pattern that pattern topic pattern}" + * @return + */ + public String inputThatTopic() { + return Graphmaster.inputThatTopic(pattern, that, topic); + } + + /** + * add a matching input to the matching input set + * + * @param input matching input + */ + public void addMatch (String input) { + if (matches == null) { + String setName = this.inputThatTopic().replace("*", "STAR").replace("_", "UNDERSCORE").replace(" ","-").replace("","THAT").replace("","TOPIC"); + // log.info("Created match set "+setName); + matches = new AIMLSet(setName); + } + matches.add(input); + } + + /** + * convert a template to a single-line representation by replacing "," with #Comma and newline with #Newline + * @param template original template + * @return template on a single line of text + */ + public static String templateToLine (String template) { + String result = template; + result = result.replaceAll("(\r\n|\n\r|\r|\n)", "\\#Newline"); + result = result.replaceAll(MagicStrings.aimlif_split_char, MagicStrings.aimlif_split_char_name); + return result; + } + + /** + * restore a template to its original form by replacing #Comma with "," and #Newline with newline. + * @param line template on a single line of text + * @return original multi-line template + */ + private static String lineToTemplate(String line) { + String result = line.replaceAll("\\#Newline","\n"); + result = result.replaceAll(MagicStrings.aimlif_split_char_name, MagicStrings.aimlif_split_char); + return result; + } + + /** + * convert a category from AIMLIF format to a Category object + * + * @param IF Category in AIMLIF format + * @return Category object + */ + public static Category IFToCategory(String IF) { + String[] split = IF.split(MagicStrings.aimlif_split_char); + //log.info("Read: "+split); + return new Category(Integer.parseInt(split[0]), split[1], split[2], split[3], lineToTemplate(split[4]), split[5]); + } + + /** + * convert a Category object to AIMLIF format + * @param category Category object + * @return category in AIML format + */ + public static String categoryToIF(Category category) { + //log.info("categoryToIF: template="+templateToLine(category.getTemplate())); + String c = MagicStrings.aimlif_split_char; + return category.getActivationCnt()+c+category.getPattern()+c+category.getThat()+c+category.getTopic()+c+templateToLine(category.getTemplate())+c+category.getFilename(); + } + + /** + * convert a Category object to AIML syntax + * + * @param category Category object + * @return AIML Category + */ + public static String categoryToAIML(Category category) { + String topicStart = ""; String topicEnd = ""; + String thatStatement = ""; + String result = ""; + String pattern = category.getPattern(); + String[] splitPattern = pattern.split(" "); + for (String w : splitPattern) { + if (w.startsWith("")) w = w.toLowerCase(); + pattern = pattern+" "+w; + } + pattern = pattern.trim(); + // if (pattern.contains("type")) log.info("Rebuilt pattern "+pattern); + + String NL = System.getProperty("line.separator"); + NL = "\n"; + try { + if (!category.getTopic().equals("*")) { topicStart = ""+NL; topicEnd = ""+NL;} + if (!category.getThat().equals("*")) { thatStatement = ""+category.getThat()+"";} + result = topicStart+""+category.getPattern()+""+thatStatement+NL+ + ""+NL+ + ""+topicEnd; + } catch (Exception ex) { + ex.printStackTrace(); + } + return result; + } + + /** + * check to see if a pattern expression is valid in AIML 2.0 + * + * @param pattern pattern expression + * @return true or false + */ + public boolean validPatternForm(String pattern) { + if (pattern.length() < 1) {validationMessage += "Zero length. "; return false; } + String[] words = pattern.split(" "); + for (int i = 0; i < words.length; i++) { + //String word = words[i]; + /*if (!(word.matches("[\\p{Hiragana}\\p{Katakana}\\p{Han}\\p{Latin}]*+") || word.equals("*") || word.equals("_"))) { + log.info("Invalid pattern word "+word); + return false; + }*/ + } + return true; + } + public String validationMessage=""; + + /** + * check for valid Category format + * + * @return true or false + */ + public boolean validate () { + validationMessage = ""; + if (!validPatternForm(pattern)) {validationMessage += "Badly formatted "; return false;} + if (!validPatternForm(that)) {validationMessage += "Badly formatted "; return false;} + if (!validPatternForm(topic)) {validationMessage += "Badly formatted "; return false;} + if (!AIMLProcessor.validTemplate(template)) {validationMessage += "Badly formatted