├── .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 element, or
186 | # inside a element, between the and the
187 | # elements. This clause handles the latter case.
188 | self._state = self._STATE_InsideThat
189 | elif name == "template":
190 | # tags are only legal in the AfterPattern and AfterThat
191 | # states
192 | if self._state not in [self._STATE_AfterPattern, self._STATE_AfterThat]:
193 | raise AimlParserError, "Unexpected tag "+self._location()
194 | # if no element was specified, it is implicitly set to *
195 | if self._state == self._STATE_AfterPattern:
196 | self._currentThat = u"*"
197 | self._state = self._STATE_InsideTemplate
198 | self._elemStack.append(['template',{}])
199 | self._pushWhitespaceBehavior(attr)
200 | elif self._state == self._STATE_InsidePattern:
201 | # Certain tags are allowed inside elements.
202 | if name == "bot" and attr.has_key("name") and attr["name"] == u"name":
203 | # Insert a special character string that the PatternMgr will
204 | # replace with the bot's name.
205 | self._currentPattern += u" BOT_NAME "
206 | else:
207 | raise AimlParserError, ("Unexpected <%s> tag " % name)+self._location()
208 | elif self._state == self._STATE_InsideThat:
209 | # Certain tags are allowed inside elements.
210 | if name == "bot" and attr.has_key("name") and attr["name"] == u"name":
211 | # Insert a special character string that the PatternMgr will
212 | # replace with the bot's name.
213 | self._currentThat += u" BOT_NAME "
214 | else:
215 | raise AimlParserError, ("Unexpected <%s> tag " % name)+self._location()
216 | elif self._state == self._STATE_InsideTemplate and self._validInfo.has_key(name):
217 | # Starting a new element inside the current pattern. First
218 | # we need to convert 'attr' into a native Python dictionary,
219 | # so it can later be marshaled.
220 | attrDict = {}
221 | for k,v in attr.items():
222 | #attrDict[k[1].encode(self._encoding)] = v.encode(self._encoding)
223 | attrDict[k.encode(self._encoding)] = unicode(v)
224 | self._validateElemStart(name, attrDict, self._version)
225 | # Push the current element onto the element stack.
226 | self._elemStack.append([name.encode(self._encoding),attrDict])
227 | self._pushWhitespaceBehavior(attr)
228 | # If this is a condition element, push a new entry onto the
229 | # foundDefaultLiStack
230 | if name == "condition":
231 | self._foundDefaultLiStack.append(False)
232 | else:
233 | # we're now inside an unknown element.
234 | if self._forwardCompatibleMode:
235 | # In Forward Compatibility Mode, we ignore the element and its
236 | # contents.
237 | self._currentUnknown = name
238 | else:
239 | # Otherwise, unknown elements are grounds for error!
240 | raise AimlParserError, ("Unexpected <%s> tag " % name)+self._location()
241 |
242 | def characters(self, ch):
243 | # Wrapper around _characters which catches errors in _characters()
244 | # and keeps going.
245 | if self._state == self._STATE_OutsideAiml:
246 | # If we're outside of an AIML element, we ignore all text
247 | return
248 | if self._currentUnknown != "":
249 | # If we're inside an unknown element, ignore all text
250 | return
251 | if self._skipCurrentCategory:
252 | # If we're skipping the current category, ignore all text.
253 | return
254 | try: self._characters(ch)
255 | except AimlParserError, msg:
256 | # Print the message
257 | sys.stderr.write("PARSE ERROR: %s\n" % msg)
258 | self._numParseErrors += 1 # increment error count
259 | # In case of a parse error, if we're inside a category, skip it.
260 | if self._state >= self._STATE_InsideCategory:
261 | self._skipCurrentCategory = True
262 |
263 | def _characters(self, ch):
264 | text = unicode(ch)
265 | if self._state == self._STATE_InsidePattern:
266 | self._currentPattern += text
267 | elif self._state == self._STATE_InsideThat:
268 | self._currentThat += text
269 | elif self._state == self._STATE_InsideTemplate:
270 | # First, see whether the element at the top of the element stack
271 | # is permitted to contain text.
272 | try:
273 | parent = self._elemStack[-1][0]
274 | parentAttr = self._elemStack[-1][1]
275 | required, optional, canBeParent = self._validInfo[parent]
276 | nonBlockStyleCondition = (parent == "condition" and not (parentAttr.has_key("name") and parentAttr.has_key("value")))
277 | if not canBeParent:
278 | raise AimlParserError, ("Unexpected text inside <%s> element "%parent)+self._location()
279 | elif parent == "random" or nonBlockStyleCondition:
280 | # elements can only contain
subelements. However,
281 | # there's invariably some whitespace around the
that we need
282 | # to ignore. Same for non-block-style elements (i.e.
283 | # those which don't have both a "name" and a "value" attribute).
284 | if len(text.strip()) == 0:
285 | # ignore whitespace inside these elements.
286 | return
287 | else:
288 | # non-whitespace text inside these elements is a syntax error.
289 | raise AimlParserError, ("Unexpected text inside <%s> element "%parent)+self._location()
290 | except IndexError:
291 | # the element stack is empty. This should never happen.
292 | raise AimlParserError, "Element stack is empty while validating text "+self._location()
293 |
294 | # Add a new text element to the element at the top of the element
295 | # stack. If there's already a text element there, simply append the
296 | # new characters to its contents.
297 | try: textElemOnStack = (self._elemStack[-1][-1][0] == "text")
298 | except IndexError: textElemOnStack = False
299 | except KeyError: textElemOnStack = False
300 | if textElemOnStack:
301 | self._elemStack[-1][-1][2] += text
302 | else:
303 | self._elemStack[-1].append(["text", {"xml:space": self._whitespaceBehaviorStack[-1]}, text])
304 | else:
305 | # all other text is ignored
306 | pass
307 |
308 | def endElementNS(self, name, qname):
309 | uri, elem = name
310 | self.endElement(elem)
311 |
312 | def endElement(self, name):
313 | """Wrapper around _endElement which catches errors in _characters()
314 | and keeps going.
315 |
316 | """
317 | if self._state == self._STATE_OutsideAiml:
318 | # If we're outside of an AIML element, ignore all tags
319 | return
320 | if self._currentUnknown != "":
321 | # see if we're at the end of an unknown element. If so, we can
322 | # stop ignoring everything.
323 | if name == self._currentUnknown:
324 | self._currentUnknown = ""
325 | return
326 | if self._skipCurrentCategory:
327 | # If we're skipping the current category, see if it's ending. We
328 | # stop on ANY
tag, since we're not keeping track of
329 | # state in ignore-mode.
330 | if name == "category":
331 | self._skipCurrentCategory = False
332 | self._state = self._STATE_InsideAiml
333 | return
334 | try: self._endElement(name)
335 | except AimlParserError, msg:
336 | # Print the message
337 | sys.stderr.write("PARSE ERROR: %s\n" % msg)
338 | self._numParseErrors += 1 # increment error count
339 | # In case of a parse error, if we're inside a category, skip it.
340 | if self._state >= self._STATE_InsideCategory:
341 | self._skipCurrentCategory = True
342 |
343 | def _endElement(self, name):
344 | """Verify that an AIML end element is valid in the current
345 | context.
346 |
347 | Raises an AimlParserError if an illegal end element is encountered.
348 |
349 | """
350 | if name == "aiml":
351 | # tags are only legal in the InsideAiml state
352 | if self._state != self._STATE_InsideAiml:
353 | raise AimlParserError, "Unexpected tag "+self._location()
354 | self._state = self._STATE_OutsideAiml
355 | self._whitespaceBehaviorStack.pop()
356 | elif name == "topic":
357 | # tags are only legal in the InsideAiml state, and
358 | # only if _insideTopic is true.
359 | if self._state != self._STATE_InsideAiml or not self._insideTopic:
360 | raise AimlParserError, "Unexpected tag "+self._location()
361 | self._insideTopic = False
362 | self._currentTopic = u""
363 | elif name == "category":
364 | # tags are only legal in the AfterTemplate state
365 | if self._state != self._STATE_AfterTemplate:
366 | raise AimlParserError, "Unexpected tag "+self._location()
367 | self._state = self._STATE_InsideAiml
368 | # End the current category. Store the current pattern/that/topic and
369 | # element in the categories dictionary.
370 | key = (self._currentPattern.strip(), self._currentThat.strip(),self._currentTopic.strip())
371 | self.categories[key] = self._elemStack[-1]
372 | self._whitespaceBehaviorStack.pop()
373 | elif name == "pattern":
374 | # tags are only legal in the InsidePattern state
375 | if self._state != self._STATE_InsidePattern:
376 | raise AimlParserError, "Unexpected tag "+self._location()
377 | self._state = self._STATE_AfterPattern
378 | elif name == "that" and self._state == self._STATE_InsideThat:
379 | # tags are only allowed inside elements or in
380 | # the InsideThat state. This clause handles the latter case.
381 | self._state = self._STATE_AfterThat
382 | elif name == "template":
383 | # tags are only allowed in the InsideTemplate state.
384 | if self._state != self._STATE_InsideTemplate:
385 | raise AimlParserError, "Unexpected tag "+self._location()
386 | self._state = self._STATE_AfterTemplate
387 | self._whitespaceBehaviorStack.pop()
388 | elif self._state == self._STATE_InsidePattern:
389 | # Certain tags are allowed inside elements.
390 | if name not in ["bot"]:
391 | raise AimlParserError, ("Unexpected %s> tag " % name)+self._location()
392 | elif self._state == self._STATE_InsideThat:
393 | # Certain tags are allowed inside elements.
394 | if name not in ["bot"]:
395 | raise AimlParserError, ("Unexpected %s> tag " % name)+self._location()
396 | elif self._state == self._STATE_InsideTemplate:
397 | # End of an element inside the current template. Append the
398 | # element at the top of the stack onto the one beneath it.
399 | elem = self._elemStack.pop()
400 | self._elemStack[-1].append(elem)
401 | self._whitespaceBehaviorStack.pop()
402 | # If the element was a condition, pop an item off the
403 | # foundDefaultLiStack as well.
404 | if elem[0] == "condition": self._foundDefaultLiStack.pop()
405 | else:
406 | # Unexpected closing tag
407 | raise AimlParserError, ("Unexpected %s> tag " % name)+self._location()
408 |
409 | # A dictionary containing a validation information for each AIML
410 | # element. The keys are the names of the elements. The values are a
411 | # tuple of three items. The first is a list containing the names of
412 | # REQUIRED attributes, the second is a list of OPTIONAL attributes,
413 | # and the third is a boolean value indicating whether or not the
414 | # element can contain other elements and/or text (if False, the
415 | # element can only appear in an atomic context, such as ).
416 | _validationInfo101 = {
417 | "bot": ( ["name"], [], False ),
418 | "condition": ( [], ["name", "value"], True ), # can only contain
elements
433 | "sentence": ( [], [], True ),
434 | "set": ( ["name"], [], True),
435 | "size": ( [], [], False ),
436 | "sr": ( [], [], False ),
437 | "srai": ( [], [], True ),
438 | "star": ( [], ["index"], False ),
439 | "system": ( [], [], True ),
440 | "template": ( [], [], True ), # needs to be in the list because it can be a parent.
441 | "that": ( [], ["index"], False ),
442 | "thatstar": ( [], ["index"], False ),
443 | "think": ( [], [], True ),
444 | "topicstar": ( [], ["index"], False ),
445 | "uppercase": ( [], [], True ),
446 | "version": ( [], [], False ),
447 | }
448 |
449 | def _validateElemStart(self, name, attr, version):
450 | """Test the validity of an element starting inside a
451 | element.
452 |
453 | This function raises an AimlParserError exception if it the tag is
454 | invalid. Otherwise, no news is good news.
455 |
456 | """
457 | # Check the element's attributes. Make sure that all required
458 | # attributes are present, and that any remaining attributes are
459 | # valid options.
460 | required, optional, canBeParent = self._validInfo[name]
461 | for a in required:
462 | if a not in attr and not self._forwardCompatibleMode:
463 | raise AimlParserError, ("Required \"%s\" attribute missing in <%s> element " % (a,name))+self._location()
464 | for a in attr:
465 | if a in required: continue
466 | if a[0:4] == "xml:": continue # attributes in the "xml" namespace can appear anywhere
467 | if a not in optional and not self._forwardCompatibleMode:
468 | raise AimlParserError, ("Unexpected \"%s\" attribute in <%s> element " % (a,name))+self._location()
469 |
470 | # special-case: several tags contain an optional "index" attribute.
471 | # This attribute's value must be a positive integer.
472 | if name in ["star", "thatstar", "topicstar"]:
473 | for k,v in attr.items():
474 | if k == "index":
475 | temp = 0
476 | try: temp = int(v)
477 | except:
478 | raise AimlParserError, ("Bad type for \"%s\" attribute (expected integer, found \"%s\") " % (k,v))+self._location()
479 | if temp < 1:
480 | raise AimlParserError, ("\"%s\" attribute must have non-negative value " % (k))+self._location()
481 |
482 | # See whether the containing element is permitted to contain
483 | # subelements. If not, this element is invalid no matter what it is.
484 | try:
485 | parent = self._elemStack[-1][0]
486 | parentAttr = self._elemStack[-1][1]
487 | except IndexError:
488 | # If the stack is empty, no parent is present. This should never
489 | # happen.
490 | raise AimlParserError, ("Element stack is empty while validating <%s> " % name)+self._location()
491 | required, optional, canBeParent = self._validInfo[parent]
492 | nonBlockStyleCondition = (parent == "condition" and not (parentAttr.has_key("name") and parentAttr.has_key("value")))
493 | if not canBeParent:
494 | raise AimlParserError, ("<%s> elements cannot have any contents "%parent)+self._location()
495 | # Special-case test if the parent element is (the
496 | # non-block-style variant) or : these elements can only
497 | # contain
subelements.
498 | elif (parent == "random" or nonBlockStyleCondition) and name!="li":
499 | raise AimlParserError, ("<%s> elements can only contain
subelements "%parent)+self._location()
500 | # Special-case test for
elements, which can only be contained
501 | # by non-block-style and elements, and whose
502 | # required attributes are dependent upon which attributes are
503 | # present in the parent.
504 | elif name=="li":
505 | if not (parent=="random" or nonBlockStyleCondition):
506 | raise AimlParserError, ("Unexpected
element contained by <%s> element "%parent)+self._location()
507 | if nonBlockStyleCondition:
508 | if parentAttr.has_key("name"):
509 | # Single-predicate condition. Each
element except the
510 | # last must have a "value" attribute.
511 | if len(attr) == 0:
512 | # This could be the default
element for this ,
513 | # unless we've already found one.
514 | if self._foundDefaultLiStack[-1]:
515 | raise AimlParserError, "Unexpected default
element inside "+self._location()
516 | else:
517 | self._foundDefaultLiStack[-1] = True
518 | elif len(attr) == 1 and attr.has_key("value"):
519 | pass # this is the valid case
520 | else:
521 | raise AimlParserError, "Invalid