├── .gitignore ├── CHANGES.txt ├── COPYING.txt ├── MANIFEST.in ├── README.txt ├── SUPPORTED_TAGS.txt ├── TODO.txt ├── aiml ├── AimlParser.py ├── DefaultSubs.py ├── Kernel.py ├── PatternMgr.py ├── Utils.py ├── WordSub.py ├── __init__.py └── self-test.aiml ├── aimlvalidate.py ├── setup.cfg ├── setup.py ├── standard ├── dev-calendar.aiml ├── dev-examples.aiml ├── dev-scripts.aiml ├── dev-testcases.aiml ├── dev-translation.aiml ├── dev-webhelper.aiml ├── per-drWallace.aiml ├── std-65percent.aiml ├── std-atomic.aiml ├── std-botmaster.aiml ├── std-brain.aiml ├── std-connect.aiml ├── std-dictionary.aiml ├── std-disconnect.aiml ├── std-dont.aiml ├── std-errors.aiml ├── std-gender.aiml ├── std-geography.aiml ├── std-german.aiml ├── std-gossip.aiml ├── std-hello.aiml ├── std-inactivity.aiml ├── std-inventions.aiml ├── std-knowledge.aiml ├── std-lizards.aiml ├── std-login.aiml ├── std-numbers.aiml ├── std-personality.aiml ├── std-pickup.aiml ├── std-politics.aiml ├── std-profile.aiml ├── std-religion.aiml ├── std-robot.aiml ├── std-sales.aiml ├── std-sextalk.aiml ├── std-sports.aiml ├── std-srai.aiml ├── std-suffixes.aiml ├── std-that.aiml ├── std-turing.aiml └── std-yesno.aiml ├── std-startup.xml ├── stress.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | version 0.8.6 2 | - Fixed WorbSub module to work with words that consist entirely of punctuation :-). 3 | - Fixed PatternMgr module to replace non-alphanumerics with whitespace instead of simply 4 | consuming them (thanks to Hugh Sasse for the patch). 5 | - Added workaround to avoid a rare IOError exception when processing tags 6 | (thanks to Duncan Gough for the patch). 7 | - PyAIML is now released under the FreeBSD license. 8 | 9 | version 0.8.5 10 | - Fixed a couple lingering Unicode bugs, one of which was breaking things 11 | pretty thoroughly under Python 2.2.3. 12 | - Improved whitespace handling, to the point where it should be compliant 13 | with the AIML 1.0.1 standard. Basically, whitespace will be stripped 14 | down to readable levels, unless the xml:space attribute is used to 15 | indicate otherwise. 16 | - Fixed bug in PatternMgr where the character '1' was mistaken identified 17 | as punctuation to be stripped, instead of the intended '!'. 18 | 19 | version 0.8.4: 20 | - Fixed bug in AimlParser creation, if the aiml module was not in the same 21 | directory as the script that imported it. 22 | - Improved Unicode support (again) (thanks to Daniel Kottow for his help). 23 | - 2nd pass at documentation. All functions now have useful, properly-formatted 24 | docstrings. 25 | 26 | version 0.8.3: 27 | - Reworked the way AimlParser objects are created, to allow support for 28 | XML namespaces in a future version. 29 | - First pass at Unicode-proofing the whole system, so that multi-lingual 30 | AIML files can be processed. 31 | - Added support for "index" attribute in "star", "thatstar" and "topicstar" 32 | elements (thanks to Robert D. Cotey for the patch!). Also added type-check 33 | to ensure that the value of the "index" attribute is an integer. 34 | 35 | version 0.8.2: 36 | - Added protection against infinite AIML recursion. 37 | - Removed the deprecated Kernel.getBotName and Kernel.setBotName functions. 38 | - Added workaround for Unicode input crash. Feeding the Kernel Unicode 39 | input no longer crashes the interpreter, but some internal input-formatting 40 | operations must be skipped, which could cause some nonsensical replies 41 | (like you're not already used to nonsensical replies, though...) 42 | 43 | version 0.8.1: 44 | - "system" elements now attempt to intelligently convert between UNIX-style 45 | and Windows-style pathnames as appropriate. For cross-platform compatibility, 46 | all AIML files should use UNIX-style paths inside their "system" elements. 47 | - Fixed crashes under Windows9X when a "system" element is executed when 48 | w9xpopen.exe is unavailable. 49 | - Re-piped most errors and warnings to go to stderr instead of stdout. 50 | - Fixed bug that would cause the the bot to stop responding to any input in a session 51 | if the "that" or "topic" strings were 100% whitespace (but still not empty). 52 | - Fixed bug that would cause multi-line "that" strings to become corrupted. For 53 | example, "Hello there.\nNice to meet you" would become "HELLO THERENICE TO MEET 54 | YOU" when queried for that-matching. 55 | 56 | version 0.8: 57 | - Removed support for non-standard "mode" attribute in "system" elements. 58 | It just wasn't worth breaking the AIML standard for. 59 | - Reworked persistent session support (I warned you!). The old method involving 60 | Kernel.persistentSessions() has been removed. Instead, there is a new 61 | Kernel.getSessionData() method which gives the bot author access to the session 62 | data. You can store this data however you wish. Session data can be restored 63 | later by repeated calls to Kernel.setPredicate(). 64 | - Improved AIML parser error handling; instead of aborting the entire file, a 65 | parse error now only ignores the current category and forges ahead. To check for 66 | parse errors, check the return value of AimlParser.getNumErrors() after parsing. 67 | 68 | version 0.7: 69 | - Further improved AIML parsing; missing or invalid XML attributes are now 70 | detected at parse-time. There's also a first stab at support for Forward 71 | Compatibility Mode: if the "version" attribute of an AIML tag is not 1.0.1, 72 | the parser is much more forgiving of unknown or missing XML tags and attributes. 73 | - Added "support" for the 'gossip' tag. The specific behavior of this tag is 74 | totally undefined; currently, the PyAIML implementation treats it as a big 75 | no-op. Its contents are ignored, and it returns the empty string (but at least 76 | it isn't flagged as a syntax error!) 77 | - Oops; I've implemented 'person' as 'person2' and vice versa this whole time! 78 | Swapped them back; they should each behave correctly now. 79 | - A bit of code cleanup, with an emphasis on using proper XML/AIML terminology 80 | whenever possible. 81 | - Fixed some exception-masking in the 'condition' element processor, which could 82 | prevent AIML parse errors from being reported. 83 | - Added full support for the "bot" tag (previously only the 'bot name="name"' 84 | form was supported). Bot predicates are set using the Kernel.setBotPredicate() 85 | function. Note that Kernel.getBotName() and Kernel.setBotName() have been 86 | deprecated, and will disappear in a future release. 87 | 88 | version 0.6.1: 89 | - Fixed undefined variable exception that was thrown whenever there was no 90 | match for the user's input. 91 | - Fixed an infinite recursion bug caused by processing two or more nested 92 | 'srai' or 'sr' elements. 93 | 94 | version 0.6 95 | - Completely rewrote the LearnHandler class (which handles all AIML parsing) 96 | to be much less forgiving of incorrect AIML. Currently, any AIML errors 97 | outside of "template" elements should be caught and reported at load-time. 98 | AIML errors inside templates are still not detected until run-time. 99 | - Added support for atomic 'person' and 'person2' tags (they implicitly use 100 | 'star' as their contents if none are provided). 101 | - Oops; Kernel.respond() was DEFINITELY not thread-safe. Now it is, provided 102 | you're using Python's standard 'threading' module. If not, you'll have to 103 | provide your own mutual-exclusion system to ensure that only one thread 104 | is calling Kernel.respond() at a time. 105 | - Recursive tags ('srai' and 'sr') no longer add their intermediate results 106 | to the input and output history. 107 | - EXPERIMENTAL FEATURE: persistent sessions. If enabled using 108 | Kernel.persistentSessions(True), session data will be written to disk 109 | after every response. This lets session data be preserved when the 110 | Kernel is destroyed, at the expense of a small performance hit (all 111 | that extra disk I/O). The semantics of this feature may change in future 112 | releases! 113 | - Added support for the "id" tag (it returns the session ID). 114 | - Added support for the "topic", "topicstar" and "thatstar" tags. 115 | - Commented up the test.py file. 116 | - Added a summary to the end of the Kernel.py self-test (since there are now 117 | too many tests to fit on screen). 118 | - Added support for multi-sentence input. The user's input is split into 119 | individual sentences, each of which is treated as a completely separate 120 | piece of input. I'm *pretty* sure this is the Right Way(tm). 121 | 122 | version 0.5 123 | - The filename inside Learn tags can now contain wildcards. Besides 124 | simplifying the loading of multiple files, this allows the possibility 125 | of loading AIML files that may not exist without crashing the 126 | interpreter. 127 | - Attempting to set the value of a predicate in a nonexistent session 128 | now automatically creates the session, instead of silently doing nothing. 129 | - "set" tags now output the new value of the variable, as well as assigning 130 | it. 131 | 132 | version 0.4.1 133 | - Unknown AIML tags are handled in a more forgiving fashion. If an 134 | unknown tag is encountered, instead of ignoring its contents entirely, 135 | the contents are processed and returned unaltered. 136 | - Added support for "bot name='name'" tags, both in patterns and in 137 | templates. This also fixes a nasty infinite loop crash when a user 138 | says Hello to a bot. 139 | 140 | version 0.4 141 | - Added support for "star" and "sr" tags. 142 | - Considered adding support for the "secure" tag, but decided against it. 143 | see SUPPORTED_TAGS.txt for more information. 144 | 145 | version 0.3 146 | - Cleaned up AIML processing code to remove some unnecessary 147 | special-cases in the interpretation algorithm. 148 | - Added support for "input" and "that" tags. 149 | 150 | version 0.2 151 | - 50% speedup in Kernel.loadBrain() and Kernel.saveBrain(). 152 | - The optional 'learnFiles' and 'commands' arguments to 153 | Kernel.bootstrap() can now be either strings or lists. This 154 | allows multiple files/commands to be loaded during 155 | initialization. 156 | - Added support for "person" and "person2" tags. 157 | - Added general word-substitution functionality. This opens up 158 | the potential for localizing PyAIML into non-English languages. 159 | See DefaultSubs.py for more info. 160 | 161 | version 0.1: 162 | - Initial release. 163 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright 2003-2010 Cort Stratton. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the 11 | distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 14 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR 17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include aiml/*.aiml 3 | prune standard 4 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | PyAIML -- The Python AIML Interpreter 2 | author: Cort Stratton 3 | web: https://github.com/cdwfs/pyaiml 4 | 5 | PyAIML is an interpreter for AIML (the Artificial Intelligence Markup 6 | Language), implemented entirely in standard Python. It strives for 7 | simple, austere, 100% compliance with the AIML 1.0.1 standard, no less 8 | and no more. 9 | 10 | This is currently pre-alpha software. Use at your 11 | own risk! 12 | 13 | For information on what's new in this version, see the 14 | CHANGES.txt file. 15 | 16 | For information on the state of development, including 17 | the current level of AIML 1.0.1 compliance, see the 18 | SUPPORTED_TAGS.txt file. 19 | 20 | Quick & dirty example (assuming you've downloaded the 21 | "standard" AIML set): 22 | 23 | import aiml 24 | 25 | # The Kernel object is the public interface to 26 | # the AIML interpreter. 27 | k = aiml.Kernel() 28 | 29 | # Use the 'learn' method to load the contents 30 | # of an AIML file into the Kernel. 31 | k.learn("std-startup.xml") 32 | 33 | # Use the 'respond' method to compute the response 34 | # to a user's input string. respond() returns 35 | # the interpreter's response, which in this case 36 | # we ignore. 37 | k.respond("load aiml b") 38 | 39 | # Loop forever, reading user input from the command 40 | # line and printing responses. 41 | while True: print k.respond(raw_input("> ")) 42 | -------------------------------------------------------------------------------- /SUPPORTED_TAGS.txt: -------------------------------------------------------------------------------- 1 | This document describes the current state of PyAIML's compliance 2 | to the AIML 1.0.1 standard. The full AIML reference manual can be 3 | found online at http://alicebot.org/TR/2001/WD-aiml. 4 | 5 | The following tags are currently supported: 6 | 7 | (see notes) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
  • 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Support for the following tags should be implemented in the next version: 37 | 38 | None 39 | 40 | The following tags are not supported: 41 | 42 | (see notes) 43 | / (see notes) 44 | (see notes) 45 | (see notes) 46 | 47 | ------------------------------------------------------------------ 48 | 49 | NOTES ON SPECIFIC TAGS: 50 | 51 | 52 | To set the bot's name, use Kernel.setBotName("NewName"). Note that the 53 | name *MUST* be a single word! Use Kernel.getBotName() to query the bot's 54 | name in your code. 55 | 56 | 57 | The AIML 1.0.1 specification lets engine authors implement the the behavior 58 | of the tag however they wish. I haven't yet decided what I'd like 59 | to do with it, so right now it doesn't do anything at all. 60 | 61 | / 62 | These elements appear to have been dropped between AIML 1.0 and AIML 1.0.1. 63 | They may someday be added as a part of an AIML 1.0 backwards-compatibility 64 | mode, but in the meantime, use instead. 65 | 66 | 67 | Support for the JavaScript tag is not anticipated; one of the design 68 | goals of PyAIML is to remain 100% pure standard Python. So until 69 | somebody writes a JavaScript interpreter in Python, PyAIML won't 70 | support the tag. On the bright side, it is possible 71 | to simulate the effects of the tag (i.e. dynamically- 72 | generated tag contents) using the tag. This 73 | solution has the added advantage of allowing *any* programming 74 | language to be used, not just JavaScript. 75 | UPDATE: The python-spidermonkey project provides a bridge between Python 76 | and the open-source SpiderMonkey JavaScript library. I am currently 77 | investigating the possibility of adding support for the 78 | tag ON A PURELY OPTIONAL BASIS. 79 | 80 | 81 | Some AIML implementations support a non-standard tag, intended to 82 | wrap parts of a template which should only be processed if the user is 83 | "secure", or trusted. After implementing support for this tag, I realized 84 | that it wasn't doing anything that you can't do with the tag. 85 | Therefore, I've decided to drop support for the tag. You can 86 | easily duplicate its effects; simply replace this: 87 | you are allowed 88 | with this: 89 | 90 |
  • you are allowed
  • 91 |
  • you are not allowed
  • 92 |
    93 | Then, use the Kernel.setPredicate() call to set the "secure" predicate to 94 | "yes" for any session that you wish to be secure. 95 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Laundry list of future tasks, in no particular order: 2 | 3 | - AIML 1.0.1 compliance (highest priority): 4 | - Unknown yet well-defined elements (e.g. HTML) inside templates 5 | (see sections 3.2, 3.6). 6 | - AimlParser._validateElemStart() needs to test the well-formedness of 7 | attribute values (for example, making sure that the "index" attribute 8 | has an integer value, and not a string). UPDATE: this works for , 9 | and . Still needs to be written for and , 10 | which take either an integer or an integer pair. 11 | - Support the Program D startup file syntax, or something similar? It 12 | seems to be a good way to initialize bot settings and substitutions. 13 | - Documentation/tutorials. 14 | -------------------------------------------------------------------------------- /aiml/AimlParser.py: -------------------------------------------------------------------------------- 1 | from xml.sax.handler import ContentHandler 2 | from xml.sax.xmlreader import Locator 3 | import sys 4 | import xml.sax 5 | import xml.sax.handler 6 | 7 | class AimlParserError(Exception): pass 8 | 9 | class AimlHandler(ContentHandler): 10 | # The legal states of the AIML parser 11 | _STATE_OutsideAiml = 0 12 | _STATE_InsideAiml = 1 13 | _STATE_InsideCategory = 2 14 | _STATE_InsidePattern = 3 15 | _STATE_AfterPattern = 4 16 | _STATE_InsideThat = 5 17 | _STATE_AfterThat = 6 18 | _STATE_InsideTemplate = 7 19 | _STATE_AfterTemplate = 8 20 | 21 | def __init__(self, encoding = "UTF-8"): 22 | self.categories = {} 23 | self._encoding = encoding 24 | self._state = self._STATE_OutsideAiml 25 | self._version = "" 26 | self._namespace = "" 27 | self._forwardCompatibleMode = False 28 | self._currentPattern = "" 29 | self._currentThat = "" 30 | self._currentTopic = "" 31 | self._insideTopic = False 32 | self._currentUnknown = "" # the name of the current unknown element 33 | 34 | # This is set to true when a parse error occurs in a category. 35 | self._skipCurrentCategory = False 36 | 37 | # Counts the number of parse errors in a particular AIML document. 38 | # query with getNumErrors(). If 0, the document is AIML-compliant. 39 | self._numParseErrors = 0 40 | 41 | # TODO: select the proper validInfo table based on the version number. 42 | self._validInfo = self._validationInfo101 43 | 44 | # This stack of bools is used when parsing
  • elements inside 45 | # elements, to keep track of whether or not an 46 | # attribute-less "default"
  • element has been found yet. Only 47 | # one default
  • is allowed in each element. We need 48 | # a stack in order to correctly handle nested tags. 49 | self._foundDefaultLiStack = [] 50 | 51 | # This stack of strings indicates what the current whitespace-handling 52 | # behavior should be. Each string in the stack is either "default" or 53 | # "preserve". When a new AIML element is encountered, a new string is 54 | # pushed onto the stack, based on the value of the element's "xml:space" 55 | # attribute (if absent, the top of the stack is pushed again). When 56 | # ending an element, pop an object off the stack. 57 | self._whitespaceBehaviorStack = ["default"] 58 | 59 | self._elemStack = [] 60 | self._locator = Locator() 61 | self.setDocumentLocator(self._locator) 62 | 63 | def getNumErrors(self): 64 | "Return the number of errors found while parsing the current document." 65 | return self._numParseErrors 66 | 67 | def setEncoding(self, encoding): 68 | """Set the text encoding to use when encoding strings read from XML. 69 | 70 | Defaults to 'UTF-8'. 71 | 72 | """ 73 | self._encoding = encoding 74 | 75 | def _location(self): 76 | "Return a string describing the current location in the source file." 77 | line = self._locator.getLineNumber() 78 | column = self._locator.getColumnNumber() 79 | return "(line %d, column %d)" % (line, column) 80 | 81 | def _pushWhitespaceBehavior(self, attr): 82 | """Push a new string onto the whitespaceBehaviorStack. 83 | 84 | The string's value is taken from the "xml:space" attribute, if it exists 85 | and has a legal value ("default" or "preserve"). Otherwise, the previous 86 | stack element is duplicated. 87 | 88 | """ 89 | assert len(self._whitespaceBehaviorStack) > 0, "Whitespace behavior stack should never be empty!" 90 | try: 91 | if attr["xml:space"] == "default" or attr["xml:space"] == "preserve": 92 | self._whitespaceBehaviorStack.append(attr["xml:space"]) 93 | else: 94 | raise AimlParserError, "Invalid value for xml:space attribute "+self._location() 95 | except KeyError: 96 | self._whitespaceBehaviorStack.append(self._whitespaceBehaviorStack[-1]) 97 | 98 | def startElementNS(self, name, qname, attr): 99 | print "QNAME:", qname 100 | print "NAME:", name 101 | uri,elem = name 102 | if (elem == "bot"): print "name:", attr.getValueByQName("name"), "a'ite?" 103 | self.startElement(elem, attr) 104 | pass 105 | 106 | def startElement(self, name, attr): 107 | # Wrapper around _startElement, which catches errors in _startElement() 108 | # and keeps going. 109 | 110 | # If we're inside an unknown element, ignore everything until we're 111 | # out again. 112 | if self._currentUnknown != "": 113 | return 114 | # If we're skipping the current category, ignore everything until 115 | # it's finished. 116 | if self._skipCurrentCategory: 117 | return 118 | 119 | # process this start-element. 120 | try: self._startElement(name, attr) 121 | except AimlParserError, msg: 122 | # Print the error message 123 | sys.stderr.write("PARSE ERROR: %s\n" % msg) 124 | 125 | self._numParseErrors += 1 # increment error count 126 | # In case of a parse error, if we're inside a category, skip it. 127 | if self._state >= self._STATE_InsideCategory: 128 | self._skipCurrentCategory = True 129 | 130 | def _startElement(self, name, attr): 131 | if name == "aiml": 132 | # tags are only legal in the OutsideAiml state 133 | if self._state != self._STATE_OutsideAiml: 134 | raise AimlParserError, "Unexpected tag "+self._location() 135 | self._state = self._STATE_InsideAiml 136 | self._insideTopic = False 137 | self._currentTopic = u"" 138 | try: self._version = attr["version"] 139 | except KeyError: 140 | # This SHOULD be a syntax error, but so many AIML sets out there are missing 141 | # "version" attributes that it just seems nicer to let it slide. 142 | #raise AimlParserError, "Missing 'version' attribute in tag "+self._location() 143 | #print "WARNING: Missing 'version' attribute in tag "+self._location() 144 | #print " Defaulting to version 1.0" 145 | self._version = "1.0" 146 | self._forwardCompatibleMode = (self._version != "1.0.1") 147 | self._pushWhitespaceBehavior(attr) 148 | # Not sure about this namespace business yet... 149 | #try: 150 | # self._namespace = attr["xmlns"] 151 | # if self._version == "1.0.1" and self._namespace != "http://alicebot.org/2001/AIML-1.0.1": 152 | # raise AimlParserError, "Incorrect namespace for AIML v1.0.1 "+self._location() 153 | #except KeyError: 154 | # if self._version != "1.0": 155 | # raise AimlParserError, "Missing 'version' attribute(s) in tag "+self._location() 156 | elif self._state == self._STATE_OutsideAiml: 157 | # If we're outside of an AIML element, we ignore all tags. 158 | return 159 | elif name == "topic": 160 | # tags are only legal in the InsideAiml state, and only 161 | # if we're not already inside a topic. 162 | if (self._state != self._STATE_InsideAiml) or self._insideTopic: 163 | raise AimlParserError, "Unexpected tag", self._location() 164 | try: self._currentTopic = unicode(attr['name']) 165 | except KeyError: 166 | raise AimlParserError, "Required \"name\" attribute missing in element "+self._location() 167 | self._insideTopic = True 168 | elif name == "category": 169 | # tags are only legal in the InsideAiml state 170 | if self._state != self._STATE_InsideAiml: 171 | raise AimlParserError, "Unexpected tag "+self._location() 172 | self._state = self._STATE_InsideCategory 173 | self._currentPattern = u"" 174 | self._currentThat = u"" 175 | # If we're not inside a topic, the topic is implicitly set to * 176 | if not self._insideTopic: self._currentTopic = u"*" 177 | self._elemStack = [] 178 | self._pushWhitespaceBehavior(attr) 179 | elif name == "pattern": 180 | # tags are only legal in the InsideCategory state 181 | if self._state != self._STATE_InsideCategory: 182 | raise AimlParserError, "Unexpected tag "+self._location() 183 | self._state = self._STATE_InsidePattern 184 | elif name == "that" and self._state == self._STATE_AfterPattern: 185 | # are legal either inside a