├── .gitignore ├── README.md ├── TalkBot.py ├── __init__.py ├── aiml ├── AimlParser.py ├── DefaultSubs.py ├── Kernel.py ├── LangSupport.py ├── PatternMgr.py ├── Utils.py ├── WordSub.py ├── __init__.py └── build │ └── pip-delete-this-directory.txt ├── aiml_set ├── general.aiml ├── learn.aiml ├── maimeng.aiml ├── property.aiml ├── rude.aiml └── sex.aiml ├── config.py └── xdtuxbot.brn /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.pyc 4 | *.pyo 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TalkBot 2 | === 3 | TalkBot 是基于AIML的对话机器人,目前用于 @xdtuxbot 的对话,支持中文,暂未加入学习功能 4 | -------------------------------------------------------------------------------- /TalkBot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | import aiml 4 | import os 5 | import config as cfg 6 | 7 | class TalkBot(aiml.Kernel): 8 | def __init__(self,properties=cfg.BOT_PROPERTIES): 9 | aiml.Kernel.__init__(self) 10 | self.verbose(cfg.DEBUG) 11 | if os.path.isfile("xdtuxbot.brn"): 12 | self.bootstrap(brainFile = "xdtuxbot.brn") 13 | else: 14 | self.init_bot() 15 | self.saveBrain("xdtuxbot.brn") 16 | for p in properties: 17 | self.setBotPredicate( p, properties[p] ) 18 | 19 | def init_bot(self): 20 | for file in os.listdir(cfg.AIML_SET): 21 | if file[-4::]=="aiml": 22 | self.learn(os.path.join(cfg.AIML_SET,file) ) 23 | 24 | 25 | if __name__=="__main__": 26 | bot = TalkBot() 27 | while True: print bot.respond(raw_input("> ")) 28 | 29 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [] 2 | from TalkBot import TalkBot 3 | -------------------------------------------------------------------------------- /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