├── .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 ( ).
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 [](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 | My name is
11 |
12 |
13 | do you like *
14 | ? Maybe.
15 |
16 |
17 | you age
18 |
19 |
20 |
21 | how old are you
22 | you age
23 |
24 |
25 | lets chainge topic to Dev
26 | Developmentok
27 |
28 |
29 | lets talk about *
30 | stuffok
31 |
32 |
33 | what the subject
34 | Subject is
35 |
36 |
37 |
38 | , how are yoy
39 | awesome
40 |
41 |
42 | , what is your preffered programming language
43 |
44 |
45 | My name is and i prefer F#.
46 | Only Haskell,
47 | Maybe OCaml
48 |
49 |
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 | 88888
12 |
13 |
14 |
15 | , what is your preffered programming language
16 | My name is and i prefer F#.
17 |
18 |
19 | , what is best programming language
20 |
21 |
22 |
23 |
24 | Only
25 | Maybe
26 | Not
27 |
28 |
29 | C#
30 | F#
31 | js
32 |
33 |
34 |
35 |
36 |
37 |
38 | do you like *
39 | ? Maybe.
40 |
41 |
42 | you age
43 |
44 |
45 |
46 | how old are you
47 | you age
48 |
49 |
50 | lets chainge topic to Dev
51 | Developmentok
52 |
53 |
54 | lets talk about *
55 | stuffok
56 |
57 |
58 | what the subject
59 | Subject is
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 |
--------------------------------------------------------------------------------