import itertools
from xml.dom.minidom import Document,Element,Node,NodeList,parseString
[docs]class Action(dict):
"""
:cvar special: a list of keys that are reserved for system use
:type special: list(str)
"""
special = ['subject','verb','object']
def __init__(self,arg={},description=None):
if isinstance(arg,Node):
dict.__init__(self)
self.parse(arg)
if isinstance(arg,str):
values = arg.split('-')
dict.__init__(self,{self.special[i]: values[i] for i in range(len(values))})
else:
dict.__init__(self,arg)
self._string = None
self.description = description
[docs] def match(self,pattern):
for key,value in pattern.items():
if not key in self or self[key] != value:
# Mismatch
return False
else:
# Match
return True
[docs] def agentLess(self):
"""
Utility method that returns a subject-independent version of this action
:rtype: Action
"""
args = dict(self)
try:
del args['subject']
return self.__class__(args)
except KeyError:
return self.__class__(self)
[docs] def getParameters(self):
"""
:return: list of special parameters for this action
:rtype: list(str)
"""
return filter(lambda k: not k in self.special,self.keys())
def __setitem__(self,key,value):
self._string = None
dict.__setitem__(self,key,value)
[docs] def clear(self):
self._string = None
dict.clear(self)
[docs] def root(self):
"""
:return: the base action table, with only special keys "subject", "verb", and "object"
:rtype: Action
"""
root = {}
for key in self.special:
if key in self:
root[key] = self[key]
return Action(root)
def __str__(self):
if self._string is None:
elements = []
keys = list(self.keys())
for special in self.special:
if special in self:
elements.append(self[special])
keys.remove(special)
keys.sort()
elements += map(lambda k: self[k],keys)
self._string = '-'.join(map(str,elements))
return self._string
def __hash__(self):
return hash(str(self))
def __xml__(self):
doc = Document()
root = doc.createElement('action')
doc.appendChild(root)
for key,value in self.items():
node = doc.createElement('entry')
node.setAttribute('key',key)
node.appendChild(doc.createTextNode(str(value)))
root.appendChild(node)
node = doc.createElement('description')
if self.description:
node.appendChild(doc.createTextNode(self.description))
root.appendChild(node)
return doc
[docs] def parse(self,element):
assert element.tagName == 'action'
self.clear()
child = element.firstChild
while child:
if child.nodeType == child.ELEMENT_NODE:
if child.tagName == 'entry':
key = str(child.getAttribute('key'))
subchild = child.firstChild
while subchild.nodeType != subchild.TEXT_NODE:
subchild = subchild.nextSibling
value = str(subchild.data).strip()
if not key in self.special:
if '.' in value:
value = float(value)
else:
value = int(value)
self[key] = value
elif child.tagName == 'description':
while subchild.nodeType != subchild.TEXT_NODE:
subchild = subchild.nextSibling
self.description = str(subchild.data).strip()
child = child.nextSibling
[docs]class ActionSet(frozenset):
def __new__(cls,elements=[]):
if isinstance(elements,Element):
iterable = []
node = elements.firstChild
while node:
if node.nodeType == node.ELEMENT_NODE and node.tagName == 'action':
assert node.tagName == 'action','Element has tag %s instead of action' % (node.tagName)
atom = Action(node)
iterable.append(atom)
node = node.nextSibling
elif isinstance(elements,NodeList):
iterable = []
for node in elements:
if node.nodeType == node.ELEMENT_NODE and node.tagName == 'action':
assert node.tagName == 'action','Element has tag %s instead of action' % (node.tagName)
atom = Action(node)
iterable.append(atom)
elif isinstance(elements,Action):
iterable = [elements]
elif isinstance(elements,dict):
iterable = set()
for subset in elements.values():
iterable |= subset
# iterable = reduce(ActionSet.union,elements.values(),ActionSet())
else:
iterable = elements
return frozenset.__new__(cls,iterable)
[docs] def match(self,pattern):
"""
:param pattern: a table of key-value patterns that the action must match
:type pattern: dict
:return: the first action that matches the given pattern, or C{None} if none
:rtype: Action
"""
for action in self:
if action.match(pattern):
return action
else:
# No matching actions
return None
def __getitem__(self,key):
elements = list(self)
result = elements[0].get(key,None)
for atom in elements[1:]:
if key in atom and atom[key] != result:
raise ValueError('Conflicting values for key: %s' % (key))
return result
[docs] def get(self,key,default=None):
try:
return self.__getitem__(key)
except KeyError:
return default
[docs] def items(self):
history = set()
for a in self:
for key, value in a.items():
if key not in history:
yield key, self[key]
history.add(key)
def __str__(self):
return ','.join(map(str,self))
def __hash__(self):
return hash(str(self))
def __lt__(self,other):
return str(self) < str(other)
[docs] def agentLess(self):
"""
Utility method that returns a subject-independent version of this action set
:rtype: ActionSet
"""
return self.__class__([a.agentLess() for a in self])
def __xml__(self):
doc = Document()
root = doc.createElement('option')
doc.appendChild(root)
for atom in self:
root.appendChild(atom.__xml__().documentElement)
return doc
[docs]def filterActions(pattern,actions):
"""
:type pattern: dict
:return: the subset of given actions that match the given pattern
"""
return filter(lambda a: a.match(pattern),actions)
[docs]def powerset(iterable):
"""
Utility function, taken from Python doc recipes
powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
"""
s = list(iterable)
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s)+1))
[docs]def makeActionSet(subject,verb,obj=None):
if obj is None:
return ActionSet([Action({'subject': subject,'verb': verb})])
else:
return ActionSet([Action({'subject': subject,'verb': verb,'object': obj})])
[docs]def act2dict(actions):
"""
:return: a dictionary (indexed by actor) of actions equivalent to the Action, ActionSet, or dictionary passed in
"""
if isinstance(actions,Action):
actions = {actions['subject']: ActionSet({actions})}
elif isinstance(actions,ActionSet) or isinstance(actions,set):
actionDict = {}
for action in actions:
actionDict[action['subject']] = actionDict.get(action['subject'],[])+[action]
actions = {name: ActionSet(policy) for name,policy in actionDict.items()}
elif actions is None:
actions = {}
else:
assert isinstance(actions,dict),'Unable to handle actions of type %s' % (actions.__class__.__name__)
return actions
if __name__ == '__main__':
act1 = Action({'subject': 'I','verb': 'help','object': 'you'})
act2 = Action({'subject': 'you','verb': 'help','object': 'I'})
old = ActionSet([act1,act2])
print(old)
doc = parseString(old.__xml__().toprettyxml())
new = ActionSet(doc.documentElement.childNodes)
print(new)
print(old == new)