├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── AIML.xsd ├── LICENSE ├── README.md ├── index.js ├── lib ├── engine.coffee └── parser.coffee ├── package.json └── test ├── engine-tests.coffee └── parser-tests.coffee /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | node_modules 14 | 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | branches: 5 | only: 6 | - master 7 | -------------------------------------------------------------------------------- /AIML.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | An AIML object is represented by an aiml element in an XML document. 10 | 11 | Schematron validation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | A topic is an optional top-level element that contains 21 | category elements. A topic element has a required name attribute 22 | that must contain a simple pattern expression. A topic element may 23 | contain one or more category elements. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | A category is a top-level (or second-level, if contained within a 46 | topic) element that contains exactly one pattern and exactly one template. A 47 | category does not have any attributes. All category elements that do not occur as 48 | children of an explicit topic element must be assumed by the AIML interpreter to 49 | occur as children of an "implied" topic whose name attribute has the value * (single 50 | asterisk wildcard). 51 | 52 | 53 | 54 | 55 | A pattern is an element whose content is a mixed pattern 56 | expression. Exactly one pattern must appear in each category. The pattern 57 | must always be the first child element of the category. A pattern does not 58 | have any attributes. The contents of the pattern are appended to the full 59 | match path that is constructed by the AIML interpreter at load 60 | time. 61 | 62 | 63 | 64 | 65 | The pattern-side that element is a special type of pattern 66 | element used for context matching. The pattern-side that is optional in a 67 | category, but if it occurs it must occur no more than once, and must 68 | immediately follow the pattern and immediately precede the template. A 69 | pattern-side that element contains a simple pattern expression. The contents 70 | of the pattern-side that are appended to the full match path that is 71 | constructed by the AIML interpreter at load time. If a category does not 72 | contain a pattern-side that, the AIML interpreter must assume an "implied" 73 | pattern-side that containing the pattern expression * (single asterisk 74 | wildcard). 75 | 76 | 77 | 78 | 79 | The majority of AIML content is within the template. The 80 | template may contain zero or more AIML template elements mixed with 81 | character data. 82 | 83 | 84 | 85 | 86 | 87 | 88 | A mixed pattern expression is composed from one or more mixed pattern 89 | expression constituents, separated by XML spaces (&#x20). 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | An atomic template element in AIML indicates to an AIML interpreter 111 | that it must return a value according to the functional meaning of the element. 112 | Atomic elements do not have any content. 113 | 114 | 115 | 116 | 117 | The star element indicates that an AIML interpreter should 118 | substitute the value "captured" by a particular wildcard from the 119 | pattern-specified portion of the match path when returning the template. The 120 | star element has an optional integer index attribute that indicates which 121 | wildcard to use. The minimum acceptable value for the index is "1" (the 122 | first wildcard), and the maximum acceptable value is equal to the number of 123 | wildcards in the pattern. 124 | 125 | 126 | 127 | 128 | The pattern-side that element is a special type of pattern 129 | element used for context matching. The pattern-side that is optional in a 130 | category, but if it occurs it must occur no more than once, and must 131 | immediately follow the pattern and immediately precede the template. A 132 | pattern-side that element contains a simple pattern expression. The contents 133 | of the pattern-side that are appended to the full match path that is 134 | constructed by the AIML interpreter at load time. If a category does not 135 | contain a pattern-side that, the AIML interpreter must assume an "implied" 136 | pattern-side that containing the pattern expression * (single asterisk 137 | wildcard). 138 | 139 | 140 | 141 | 142 | The input element tells the AIML interpreter that it should 143 | substitute the contents of a previous user input. The template-side input 144 | has an optional index attribute that may contain either a single integer or 145 | a comma-separated pair of integers. The minimum value for either of the 146 | integers in the index is "1". The index tells the AIML interpreter which 147 | previous user input should be returned (first dimension), and optionally 148 | which "sentence" of the previous user input. The AIML interpreter should 149 | raise an error if either of the specified index dimensions is invalid at 150 | run-time. An unspecified index is the equivalent of "1,1". An unspecified 151 | second dimension of the index is the equivalent of specifying a "1" for the 152 | second dimension. 153 | 154 | 155 | 156 | 157 | The thatstar element tells the AIML interpreter that it should 158 | substitute the contents of a wildcard from a pattern-side that element. The 159 | thatstar element has an optional integer index attribute that indicates 160 | which wildcard to use; the minimum acceptable value for the index is "1" 161 | (the first wildcard). An AIML interpreter should raise an error if the index 162 | attribute of a star specifies a wildcard that does not exist in the that 163 | element's pattern content. Not specifying the index is the same as 164 | specifying an index of "1". 165 | 166 | 167 | 168 | 169 | The topicstar element tells the AIML interpreter that it 170 | should substitute the contents of a wildcard from the current topic (if the 171 | topic contains any wildcards). The topicstar element has an optional integer 172 | index attribute that indicates which wildcard to use; the minimum acceptable 173 | value for the index is "1" (the first wildcard). Not specifying the index is 174 | the same as specifying an index of "1". 175 | 176 | 177 | 178 | 179 | The get element tells the AIML interpreter that it should 180 | substitute the contents of a predicate, if that predicate has a value 181 | defined. If the predicate has no value defined, the AIML interpreter should 182 | substitute the empty string "". The AIML interpreter implementation may 183 | optionally provide a mechanism that allows the AIML author to designate 184 | default values for certain predicates. 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | An element called bot, which may be considered a restricted 198 | version of get, is used to tell the AIML interpreter that it should 199 | substitute the contents of a "bot predicate". The value of a bot predicate 200 | is set at load-time, and cannot be changed at run-time. The AIML interpreter 201 | may decide how to set the values of bot predicate at load-time. If the bot 202 | predicate has no value defined, the AIML interpreter should substitute an 203 | empty string. 204 | 205 | 206 | 207 | 208 | 209 | Several atomic AIML elements are "short-cuts" for combinations of 210 | other AIML elements. 211 | 212 | 213 | 214 | 215 | The sr element is a shortcut for: 216 | <srai><star/></srai> The atomic sr 217 | does not have any content. 218 | 219 | 220 | 221 | 222 | 223 | 224 | Several atomic AIML elements require the AIML interpreter to 225 | substitute a value that is determined from the system, independently of the AIML 226 | content. 227 | 228 | 229 | 230 | 231 | The date element tells the AIML interpreter that it should 232 | substitute the system local date and time. No formatting constraints on the 233 | output are specified. 234 | 235 | 236 | 237 | 238 | The id element tells the AIML interpreter that it should 239 | substitute the user ID. The determination of the user ID is not specified, 240 | since it will vary by application. A suggested default return value is 241 | "localhost". 242 | 243 | 244 | 245 | 246 | The size element tells the AIML interpreter that it should 247 | substitute the number of categories currently loaded. 248 | 249 | 250 | 251 | 252 | The version element tells the AIML interpreter that it should 253 | substitute the version number of the AIML interpreter. 254 | 255 | 256 | 257 | 258 | 259 | 260 | Text-formatting elements instruct an AIML interpreter to perform 261 | locale-specific post-processing of the textual results of the processing of their 262 | contents. 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | The condition element instructs the AIML interpreter to return 276 | specified contents depending upon the results of matching a predicate 277 | against a pattern. NOTE: The definition in this Schema is currently far too 278 | permissive. AIML conditions have several forms and constraints that can't be 279 | expressed using W3C Schema alone. For this reason, AIML objects that 280 | validate using this Schema alone may not actually be valid AIML. 281 | 282 | 283 | 284 | A condition must be a block condition, a single-predicate 287 | condition, or a multi-predicate condition. 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | The random element instructs the AIML interpreter to return 303 | exactly one of its contained li elements randomly. 304 | 305 | 306 | 307 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | AIML defines two content-capturing elements, which tell the AIML 317 | interpreter to capture their processed contents and perform some storage operation 318 | with them. 319 | 320 | 321 | 322 | 323 | The set element instructs the AIML interpreter to set the 324 | value of a predicate to the result of processing the contents of the set 325 | element. The set element has a required attribute name, which must be a 326 | valid AIML predicate name. If the predicate has not yet been defined, the 327 | AIML interpreter should define it in memory. 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | The gossip element instructs the AIML interpreter to capture 340 | the result of processing the contents of the gossip elements and to store 341 | these contents in a manner left up to the implementation. Most common uses 342 | of gossip have been to store captured contents in a separate 343 | file. 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | The srai element instructs the AIML interpreter to pass the 353 | result of processing the contents of the srai element to the AIML matching 354 | loop, as if the input had been produced by the user (this includes stepping 355 | through the entire input normalization process). 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | The person element instructs the AIML interpreter to: 1. 365 | replace words with first-person aspect in the result of processing the 366 | contents of the person element with words with the 367 | grammatically-corresponding third-person aspect; and 2. replace words with 368 | third-person aspect in the result of processing the contents of the person 369 | element with words with the grammatically-corresponding first-person aspect. 370 | The definition of "grammatically-corresponding" is left up to the 371 | implementation. 372 | 373 | 374 | 375 | 376 | The person2 element instructs the AIML interpreter to: 1. 377 | replace words with first-person aspect in the result of processing the 378 | contents of the person2 element with words with the 379 | grammatically-corresponding second-person aspect; and 2. replace words with 380 | second-person aspect in the result of processing the contents of the person2 381 | element with words with the grammatically-corresponding first-person aspect. 382 | The definition of "grammatically-corresponding" is left up to the 383 | implementation. 384 | 385 | 386 | 387 | 388 | The gender element instructs the AIML interpreter to: 1. 389 | replace male-gendered words in the result of processing the contents of the 390 | gender element with the grammatically-corresponding female-gendered words; 391 | and 2. replace female-gendered words in the result of processing the 392 | contents of the gender element with the grammatically-corresponding 393 | male-gendered words. The definition of "grammatically-corresponding" is left 394 | up to the implementation. Historically, implementations of gender have 395 | exclusively dealt with pronouns, likely due to the fact that most AIML has 396 | been written in English. However, the decision about whether to transform 397 | gender of other words is left up to the implementation. 398 | 399 | 400 | 401 | 402 | 403 | 404 | AIML defines two "covert" elements that instruct the AIML interpreter 405 | to perform some processing on their contents, but to not return any 406 | value. 407 | 408 | 409 | 410 | 411 | The think element instructs the AIML interpreter to perform 412 | all usual processing of its contents, but to not return any value, 413 | regardless of whether the contents produce output. 414 | 415 | 416 | 417 | 418 | 419 | The learn element instructs the AIML interpreter to 420 | retrieve a resource specified by a URI, and to process its AIML object 421 | contents. 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | AIML defines two external processor elements, which instruct the AIML 431 | interpreter to pass the contents of the elements to an external processor. External 432 | processor elements may return a value, but are not required to do so. Contents of 433 | external processor elements may consist of character data as well as AIML template 434 | elements. If AIML template elements in the contents of an external processor element 435 | are not enclosed as CDATA, then the AIML interpreter is required to substitute the 436 | results of processing those elements before passing the contents to the external 437 | processor. AIML does not require that any contents of an external processor element 438 | are enclosed as CDATA. An AIML interpreter should assume that any unrecognized 439 | content is character data, and simply pass it to the appropriate external processor 440 | as-is, following any processing of AIML template elements not enclosed as CDATA. If 441 | an external processor is not available to process the contents of an external 442 | processor element, the AIML interpreter may return an error, but this is not 443 | required. 444 | 445 | 446 | 447 | 448 | The system element instructs the AIML interpreter to pass its 449 | content (with any appropriate preprocessing) to the system command 450 | interpreter of the local machine on which the AIML interpreter is 451 | running. 452 | 453 | 454 | 455 | 456 | The javascript element instructs the AIML interpreter to pass 457 | its content (with any appropriate preprocessing, as noted above) to a 458 | server-side JavaScript interpreter on the local machine on which the AIML 459 | interpreter is running. The javascript element does not have any attributes. 460 | AIML does not require that an AIML interpreter include a server-side 461 | JavaScript interpreter, and does not require any particular behavior from 462 | the server-side JavaScript interpreter if it exists. The javascript element 463 | may return a value. 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2011-2013 Vojta Jína and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AIML [![Build Status](https://secure.travis-ci.org/dotCypress/aiml.png?branch=master)](https://travis-ci.org/dotCypress/aiml) 2 | ===== 3 | 4 | [Artificial Intelligence Markup Language](http://en.wikipedia.org/wiki/AIML "Artificial Intelligence Markup Language") lib for Node.js 5 | 6 | ## Usage 7 | 8 | ### Installation 9 | 10 | `npm install aiml` 11 | 12 | ### Parser 13 | 14 | * `aiml.parse(xml, callback)` - parse string with AIML Xml. 15 | * `aiml.parseFiles(files, callback)` - parse file or files. 16 | * `aiml.parseDir(dir, callback)` - parse all files in specified directory. 17 | 18 | ### Engine 19 | 20 | Engine constructor: `var engine = new aiml.AiEngine(roomName, topics, botData)` 21 | 22 | #### Parameters 23 | 24 | * `roomName` - (required) name of chat room. 25 | * `topics` - (required) array of topics(parser results). 26 | * `botData` - (optional) bot metadata (name, version, gender, etc.). 27 | 28 | Main awesome function: `engine.reply(authorData, message, callback)` 29 | 30 | #### Parameters 31 | 32 | * `authorData` - (required) message author metadata (name, age, etc.). 33 | * `message` - (required) just message. 34 | * `callback` - (required) classic js callback, nothing special: ). 35 | 36 | #### Sample 37 | 38 | ```js 39 | 40 | var aiml = require('aiml') 41 | 42 | aiml.parseFile('sample.aiml', function(err, topics){ 43 | var engine = new aiml.AiEngine('Default', topics, {name: 'Jonny'}); 44 | var responce = engine.reply({name: 'Billy'}, "Hi, dude", function(err, responce){ 45 | console.log(responce); 46 | }); 47 | }); 48 | ``` 49 | 50 | ## Supported features in current release 51 | 52 | * Category patterns 53 | * `` 54 | * `*` 55 | * Ctegory templates 56 | * `` 57 | * `*` 58 | * `link` 59 | * `` 60 | * `value` 61 | 62 | ## Contribute 63 | 64 | You are welcome ;) 65 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('coffee-script'); 2 | module.exports.AiEngine = require('./lib/engine'); 3 | module.exports.parse = require('./lib/parser').parse; 4 | module.exports.parseFiles = require('./lib/parser').parseFiles; 5 | module.exports.parseDir = require('./lib/parser').parseDir; 6 | -------------------------------------------------------------------------------- /lib/engine.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'underscore' 2 | async = require 'async' 3 | mustache = require 'mustache' 4 | 5 | class AiEngine 6 | 7 | constructor: (@roomName, @topics, botData) -> 8 | throw "Topics not found" unless @topics 9 | throw "Room name is undefined not found" unless @roomName 10 | @view = 11 | topic: null 12 | bot: botData 13 | set: (name, value) => 14 | console.log 'dfdfdfdf' 15 | @view[name] = value 16 | get: (name) => @view[name] or '' 17 | 18 | _.each @topics, (topic) => 19 | _.each topic.categories, (category) => 20 | category["room:#{@roomName}"] = new RegExp (category.pattern.replace '*', '([^/?!.;:$]*)'), "i" 21 | 22 | getCurrentTopic: () -> 23 | _.find @topics, (topic) => topic.name is @view.topic 24 | 25 | findCategory: (message) -> 26 | topic = @getCurrentTopic() 27 | return @view.topic = null unless topic 28 | _.find topic.categories, (category) => category["room:#{@roomName}"].test message 29 | 30 | reply: (authorData, message, cb) -> 31 | category = @findCategory message 32 | return cb null unless category 33 | return @reply authorData, category.template.link, cb if category.template?.link 34 | match = category["room:#{@roomName}"].exec message 35 | @view.star = match[1] if match and match.length > 0 36 | category.template.do @view, @view.star if category.template.do 37 | responce = mustache.render category.template.text, @view 38 | cb null, responce 39 | 40 | module.exports = AiEngine 41 | -------------------------------------------------------------------------------- /lib/parser.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | _ = require 'underscore' 4 | async = require 'async' 5 | DomJS = require("dom-js").DomJS 6 | mustache = require 'mustache' 7 | engine = require './engine' 8 | 9 | parse = (xml, cb) -> 10 | return cb 'Xml is not defined' unless xml 11 | domjs = new DomJS() 12 | domjs.parse xml, (err, dom) -> 13 | return cb err if err 14 | return cb 'Unsupported file' if dom.name is not 'aiml' 15 | topics = parseTopics dom 16 | topCategories = parseCategories dom 17 | topics.unshift { name: null, categories: topCategories } if topCategories.length > 0 18 | cb null, topics 19 | 20 | parseFiles = (files, cb) -> 21 | files = [files] unless _.isArray files 22 | 23 | parseTasks = _.map files, (file) -> 24 | (cb) -> 25 | fs.readFile file, 'utf8', (err, data) -> 26 | return cb err if err 27 | parse data, cb 28 | 29 | async.parallel parseTasks, (err, results) -> 30 | return cb err if err 31 | all = _.flatten results, true 32 | merged = _.groupBy all, 'name' 33 | result = _.map merged, (arr) -> 34 | name: arr[0].name 35 | categories: _.reduce arr, ((acc, next) -> acc.concat next.categories), [] 36 | cb null, result 37 | 38 | parseDir = (dir, cb) -> 39 | fs.readdir dir, (err, files) -> 40 | return cb err if err 41 | files = _.map files, (file) -> path.join dir, file 42 | parseFiles files, cb 43 | 44 | parseTopics = (node) -> 45 | topics = _.filter node.children, (child) -> child.name is 'topic' 46 | _.map topics, parseTopic 47 | 48 | parseTopic = (node) -> 49 | name: node.attributes.name 50 | categories: parseCategories node 51 | 52 | parseCategories = (node) -> 53 | categories = _.filter node.children, (child) -> child.name is 'category' 54 | _.map categories, parseCategory 55 | 56 | parseCategory = (node) -> 57 | pattern = _.find node.children, (child) -> child.name is 'pattern' 58 | that = _.find node.children, (child) -> child.name is 'that' 59 | template = _.find node.children, (child) -> child.name is 'template' 60 | pattern: parseMixedPatternExpression pattern 61 | that: parseMixedPatternExpression that if that 62 | template: parseMixedTemplateContentContainer template 63 | 64 | parseMixedPatternExpression = (node) -> 65 | return undefined unless node 66 | _.reduce node.children, ((acc, next) -> "#{acc}#{parsePatternExpression next}"), '' 67 | 68 | parsePatternExpression = (node) -> 69 | return "{{bot.#{node.attributes.name}}}" if node.name is 'bot' 70 | node.text 71 | 72 | parseMixedTemplateContentContainer = (node) -> 73 | return undefined unless node 74 | linkNode = _.find node.children, (subNode) -> subNode.name is 'srai' 75 | return link: linkNode.children[0].text if linkNode 76 | setterNode = _.find node.children, (subNode) -> subNode.name is 'set' 77 | simpleNodes = _.filter node.children, (subNode) -> 78 | subNode.name is 'bot' or subNode.name is 'star' or subNode.text 79 | text: trim _.reduce simpleNodes, ((acc, next) -> "#{acc}#{parseTemplateExpression next}"), '' 80 | do: processSetter setterNode if setterNode 81 | 82 | parseTemplateExpression = (node) -> 83 | if node.name 84 | return "{{bot.#{node.attributes.name}}}" if node.name is 'bot' 85 | return "{{star}}" if node.name is 'star' 86 | return "{{#{node.attributes.name}}}" if node.name is 'get' 87 | return '' 88 | node.text 89 | 90 | processSetter = (node) -> 91 | value = parseMixedTemplateContentContainer node 92 | content = value.text 93 | if (content.indexOf '{{star}}') != -1 94 | return (state, star) -> state[node.attributes.name] = mustache.render content, {star: star} 95 | (state) -> state[node.attributes.name] = content 96 | 97 | trim = (string) -> string.replace /^\s+|\s+$/g, '' 98 | 99 | module.exports.parse = parse 100 | module.exports.parseFiles = parseFiles 101 | module.exports.parseDir = parseDir 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aiml", 3 | "version": "0.0.2", 4 | "description": "Artificial Intelligence Markup Language lib for Node.js", 5 | "keywords": [ 6 | "aiml", 7 | "ai", 8 | "parser" 9 | ], 10 | "homepage": "https://github.com/dotCypress/aiml", 11 | "bugs": "https://github.com/dotCypress/aiml/issues", 12 | "author": "Vitaly Domnikov ", 13 | "main": "index.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/dotCypress/aiml.git" 17 | }, 18 | "scripts": { 19 | "test": "mocha --reporter spec --compilers coffee:coffee-script -t 10000" 20 | }, 21 | "dependencies": { 22 | "coffee-script": "~1.6.1", 23 | "underscore": "~1.4.4", 24 | "async": "~0.2.6", 25 | "dom-js": "~0.0.9", 26 | "mustache": "~0.7.2" 27 | }, 28 | "devDependencies": { 29 | "mocha": "~1.8.1", 30 | "chai": "~1.5.0" 31 | }, 32 | "engines": { 33 | "node": ">=0.8.0" 34 | }, 35 | "licenses": [ 36 | { 37 | "type": "MIT" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /test/engine-tests.coffee: -------------------------------------------------------------------------------- 1 | should = require('chai').should() 2 | AiEngine = require('./../index').AiEngine 3 | parse = require('./../index').parse 4 | 5 | xml = " 6 | 7 | 8 | what is your Name 9 | bot 10 | 11 | 12 | 13 | do you like * 14 | 15 | 16 | 17 | you age 18 | 19 | 20 | 21 | how old are you 22 | 23 | 24 | 25 | lets chainge topic to Dev 26 | 27 | 28 | 29 | lets talk about * 30 | 31 | 32 | 33 | what the subject 34 | 35 | 36 | 37 | 38 | , how are yoy 39 | 40 | 41 | 42 | , what is your preffered programming language 43 | 50 | 51 | 52 | " 53 | 54 | describe 'AIML engine', () -> 55 | 56 | it 'should throw error without room name', (done) -> 57 | should.throw () -> new AiEngine() 58 | done() 59 | 60 | it 'should throw error without topics', (done) -> 61 | should.throw () -> new AiEngine 'Default' 62 | done() 63 | 64 | describe '#reply', () -> 65 | 66 | engine = null 67 | 68 | beforeEach (done) -> 69 | parse xml, (err, topics) -> 70 | engine = new AiEngine 'Default', topics, {name: 'Jonny', age: 21} 71 | done() 72 | 73 | it 'should not responce for unknown message', (done) -> 74 | engine.reply {name: 'Lisa'}, 'LOL', (err, reply) -> 75 | should.not.exist err 76 | should.not.exist reply 77 | done() 78 | 79 | it 'should responce to exact message', (done) -> 80 | engine.reply {name: 'Lisa'}, 'what is your name', (err, reply) -> 81 | should.exist reply 82 | reply.should.be.equal 'My name is Jonny' 83 | done() 84 | 85 | it 'should responce to not exact message', (done) -> 86 | engine.reply {name: 'Lisa'}, 'Hey, what is your name?', (err, reply) -> 87 | should.exist reply 88 | reply.should.be.equal 'My name is Jonny' 89 | done() 90 | 91 | it 'should responce with context', (done) -> 92 | engine.reply {name: 'Lisa'}, 'Dude, do you like bananas', (err, reply) -> 93 | should.exist reply 94 | reply.should.be.equal 'bananas? Maybe.' 95 | done() 96 | 97 | it 'should work with references', (done) -> 98 | engine.reply {name: 'Lisa'}, 'how old are you?', (err, reply) -> 99 | should.exist reply 100 | reply.should.be.equal '21' 101 | done() 102 | 103 | it 'should work with references', (done) -> 104 | engine.reply {name: 'Lisa'}, 'how old are you?', (err, reply) -> 105 | should.exist reply 106 | reply.should.be.equal '21' 107 | done() 108 | 109 | it 'should work with setters ang getters', (done) -> 110 | engine.reply {name: 'Lisa'}, 'lets chainge topic to Dev?', (err, reply) -> 111 | should.exist reply 112 | reply.should.be.equal 'ok' 113 | should.exist engine.view.topic 114 | engine.view.topic.should.be.equal 'Development' 115 | done() 116 | 117 | it 'should work with setters ang getters (with star)', (done) -> 118 | engine.reply {name: 'Lisa'}, 'lets talk about js?', (err, reply) -> 119 | should.exist reply 120 | reply.should.be.equal 'ok' 121 | engine.reply {name: 'Lisa'}, 'what the subject?', (err, reply) -> 122 | should.exist reply 123 | reply.should.be.equal 'Subject is js stuff' 124 | done() 125 | -------------------------------------------------------------------------------- /test/parser-tests.coffee: -------------------------------------------------------------------------------- 1 | should = require('chai').should() 2 | parse = require('./../index').parse 3 | parseFiles = require('./../index').parseFiles 4 | parseDir = require('./../index').parseDir 5 | 6 | xml = " 7 | 8 | 9 | what is your name 10 | bot 11 | 12 | 13 | 14 | 15 | , what is your preffered programming language 16 | 17 | 18 | 19 | , what is best programming language 20 | 35 | 36 | 37 | 38 | do you like * 39 | 40 | 41 | 42 | you age 43 | 44 | 45 | 46 | how old are you 47 | 48 | 49 | 50 | lets chainge topic to Dev 51 | 52 | 53 | 54 | lets talk about * 55 | 56 | 57 | 58 | what the subject 59 | 60 | 61 | " 62 | 63 | describe 'AIML parser', () -> 64 | 65 | it 'should not parse empty string', (done) -> 66 | parse '', (err, topics) -> 67 | should.exist err 68 | done() 69 | 70 | it 'should not parse non aiml xmls', (done) -> 71 | parse '/', (err, topics) -> 72 | should.exist err 73 | done() 74 | 75 | it 'should parse topics', (done) -> 76 | parse xml, (err, topics) -> 77 | should.not.exist err 78 | topics.should.have.length 2 79 | topics[0].categories.should.have.length 7 80 | should.not.exist topics[0].name 81 | topics[1].name.should.equal 'Development' 82 | done() 83 | 84 | it 'should parse simple category', (done) -> 85 | parse xml, (err, topics) -> 86 | topic = topics[0] 87 | category = topic.categories[0] 88 | category.pattern.should.equal 'what is your name' 89 | category.that.should.equal 'bot' 90 | category.template.text.should.equal 'My name is Jonny.' 91 | done() 92 | 93 | it 'should parse bot predicate in pattern', (done) -> 94 | parse xml, (err, topics) -> 95 | category = topics[1].categories[0] 96 | category.pattern.should.equal '{{bot.name}}, what is your preffered programming language' 97 | done() 98 | 99 | it 'should parse bot predicate in templates', (done) -> 100 | parse xml, (err, topics) -> 101 | category = topics[1].categories[0] 102 | category.template.text.should.equal 'My name is {{bot.name}} and i prefer F#.' 103 | done() 104 | 105 | it 'should parse stars', (done) -> 106 | parse xml, (err, topics) -> 107 | category = topics[0].categories[1] 108 | category.pattern.should.equal 'do you like *' 109 | category.template.text.should.equal '{{star}}? Maybe.' 110 | done() 111 | 112 | it 'should parse category reference', (done) -> 113 | parse xml, (err, topics) -> 114 | category = topics[0].categories[3] 115 | category.pattern.should.equal 'how old are you' 116 | category.template.link.should.equal 'you age' 117 | done() 118 | 119 | it 'should parse setters', (done) -> 120 | parse xml, (err, topics) -> 121 | category = topics[0].categories[4] 122 | category.pattern.should.equal 'lets chainge topic to Dev' 123 | category.template.text.should.equal 'ok' 124 | should.exist category.template.do 125 | category.template.do.should.be.a 'function' 126 | done() 127 | 128 | it 'should parse setters with star', (done) -> 129 | parse xml, (err, topics) -> 130 | category = topics[0].categories[5] 131 | category.pattern.should.equal 'lets talk about *' 132 | category.template.text.should.equal 'ok' 133 | should.exist category.template.do 134 | category.template.do.should.be.a 'function' 135 | done() 136 | 137 | it 'should parse getters', (done) -> 138 | parse xml, (err, topics) -> 139 | category = topics[0].categories[6] 140 | category.pattern.should.equal 'what the subject' 141 | category.template.text.should.equal 'Subject is {{subject}}' 142 | done() 143 | --------------------------------------------------------------------------------