class Element(object): """A base class representing an NLG element. Aside for providing a base class for other kinds of NLG elements, the class also implements basic functionality for elements. """ category = category.ELEMENT def __init__(self, features=None, parent=None, id=None): self.features = FeatureSet() self.features.update(features) self.parent = parent self.id = id self.hash = -1 def __copy__(self): rv = self.__class__(features=self.features, parent=self.parent, id=self.id) return rv # noinspection PyArgumentList def __deepcopy__(self, memo): rv = self.__class__(features=None, parent=None, id=self.id) memo[id(self)] = rv rv.features = deepcopy(self.features, memo=memo) rv.parent = memo.get(id(self.parent), None) return rv def __bool__(self): """Because Element is abstract, it will evaluate to false. """ return False def __eq__(self, other): return (isinstance(other, Element) and self.id == other.id and self.category == other.category and comparable_features( self.features) == comparable_features(other.features)) def __hash__(self): if self.hash == -1: self.hash = hash(str(self)) return self.hash def __repr__(self): from . import visitors v = visitors.ReprVisitor() self.accept(v) return str(v) def __str__(self): from . import visitors v = visitors.SimpleStrVisitor() self.accept(v) return str(v) def __contains__(self, feature_name): """Check if the argument feature name is contained in the element.""" return feature_name in self.features def __setitem__(self, feature_name, feature_value): """Set the feature name/value in the element feature set.""" self.features[feature_name] = feature_value def __getitem__(self, feature_name): """Return the value associated with the feature name, from the element feature set. If the feature name is not found in the feature dict, return None. """ return self.features.get(feature_name) def __delitem__(self, feature_name): """Remove the argument feature name and its associated value from the element feature set. """ self.features.discard(feature_name) def __add__(self, other): """Add two elements resulting in a coordination if both elements are not "False" else return the "True" element. """ if not self: return other if not other: return self return Coordination(self, other) @classmethod def from_dict(cls, dct): o = cls(None, None, None) o.__dict__.update(dct) return o @classmethod def from_json(cls, s): return json.loads(s, cls=ElementDecoder) def to_json(self): return json.dumps(self, cls=ElementEncoder) def to_xml(self, depth=0, headers=False): from . import visitors visitor = visitors.XmlVisitor(depth=depth) self.accept(visitor) if headers: return str(visitor) else: return str(visitor.xml) def accept(self, visitor, **kwargs): """Implementation of the Visitor pattern.""" visitor_method_name = self.category.lower() # get the appropriate method of the visitor instance m = getattr(visitor, visitor_method_name) # ensure that the method is callable if not hasattr(m, '__call__'): msg = 'Error: cannot call undefined method: %s on visitor' raise ValueError(msg % visitor_method_name) # and finally call the callback return m(self, **kwargs) def elements(self, recursive=False, itself=None): """Return a generator yielding elements contained in the element :param bool recursive: also include sub-elements of the contained elements :param str itself: yield `self` as one of the elements; values in (None, 'first', 'last') """ if itself or recursive and self.category != category.ELEMENT: yield self def arguments(self): """Return any arguments (vars) from the element as a list. """ return [ x for x in self.elements(recursive=True) if x.category == category.VAR ] def replace(self, one, another, key=lambda x: x): """Replace the first occurrence of `one` by `another`. :param one: a constituent to replace; will be raised to element :param another: a replacement element; will be raised to element :param key: a key function for comparison; default is identity :returns: True if replacement occurred; False otherwise """ return False # basic implementation does nothing def replace_argument(self, id, replacement): """Replace an argument with given `id` by `replacement` if such argument exists. """ for a in self.arguments(): if a.id == id: return self.replace(a, replacement) return False def replace_arguments(self, **kwargs): """Replace arguments with ids in the kwargs by the corresponding values. """ for k, v in kwargs.items(): self.replace_argument(k, v) @property def string(self): """Return the string inside the value. """ return '' def update_parents(self, parent=_sentinel): """Re-set the `parent` attribute of nested elements.""" if parent is not _sentinel: self.parent = parent
def test_get(self): fs = FeatureSet([self.number.singular, self.person.first]) self.assertEqual(self.number.singular, fs.get(self.number)) self.assertEqual(None, fs.get(self.tense)) self.assertEqual(self.tense.future, fs.get(self.tense, self.tense.future))