Source code for alex.components.nlg.tectotpl.core.node

#!/usr/bin/env python
# coding=utf-8
#
# Classes related to Treex trees & nodes
#

from __future__ import unicode_literals
from alex.components.nlg.tectotpl.core.exception import RuntimeException
from alex.components.nlg.tectotpl.core.log import log_warn
from collections import deque
import types
import re
import sys
import inspect
from alex.components.nlg.tectotpl.core.util import as_list


__author__ = "Ondřej Dušek"
__date__ = "2012"


[docs]class Node(object): "Representing a node in a tree (recursively)" __lastId = 0 # this holds attributes used for all nodes # (overridden in derived classes and used from get_attr_list) attrib = [('alignment', types.ListType), ('wild', types.DictType)] # this similarly holds a list of attributes that contain references # (to be overridden by derived classes) ref_attrib = [] def __init__(self, data=None, parent=None, zone=None): "Constructor, can create a tree recursively" # create a dummy data dictionary if None is passed data = data or {} # upper level links self.__zone = zone or (parent and parent.zone) or None self.__document = self.zone and self.zone.document or None self.__parent = None self.parent = parent # set all attributes belonging to the current node class # (replace '.' with '_') for attr_type, safe_attr in zip(self.get_attr_list(include_types=True), self.get_attr_list(safe=True)): attr, att_type = attr_type # initialize lists and dicts, perform simple type coercion on other if att_type == types.DictType: setattr(self, safe_attr, data.get(attr) is not None and dict(data[attr]) or {}) elif att_type == types.ListType: setattr(self, safe_attr, data.get(attr) is not None and list(data[attr]) or []) elif att_type == types.BooleanType: # booleans need to be prepared for values such as '1' and '0' setattr(self, safe_attr, data.get(attr) is not None and bool(int(data[attr])) or False) else: # other types (int,str): be prepared for values that evaluate # to false -- cannot use the and-or trick if data.get(attr) is not None: val = att_type(data[attr]) else: val = None setattr(self, safe_attr, val) # set or generate id (will be indexed automatically; must be called # after attributes have been set due to references) self.id = data.get('id') or self.__generate_id() # create children (will add themselves to the list automatically) self.__children = [] if ('children' in data): # call the right constructor for each child from data [self.create_child(data=child_data) for child_data in data['children']] def __generate_id(self): "Generate successive IDs for all nodes" Node.__lastId += 1 ret = re.sub(r'^.*\.', '', self.__class__.__name__.lower()) + '-node-' if self.zone: ret += self.zone.language_and_selector + '-' if self.zone.bundle: ret += 's' + str(self.zone.bundle.ord) + '-' ret += 'n' + str(Node.__lastId) return ret @staticmethod def __safe_name(attr): """Return a safe version of an attribute's name (mangle referencing attributes).""" if attr.endswith('.rf'): return '__' + re.sub(r'\.', '_', attr) return attr def __track_backref(self, name, value): """Track reverse references if the given attribute contains references (used by set_attr)""" # handle alignment as a special case if name == 'alignment': old_alignment = self.get_attr('alignment') if old_alignment: for reference in old_alignment: self.document.remove_backref('alignment', self.id, reference['counterpart.rf']) if value: for reference in value: self.document.index_backref('alignment', self.id, reference['counterpart.rf']) return # test if the attribute contains references reference = self.get_ref_attr_list(split_nested=True).get(name) if not reference: return # normal case: value itself is a reference ref_keys = [name] ref_values = [value] # special case: value is a dict containing references if isinstance(reference, dict): ref_keys = [name + '/' + key for key in value if key in reference] ref_values = [value[key] for key in value if key in reference] # track all the references for ref_name, ref_value in zip(ref_keys, ref_values): old_value = self.get_attr(ref_name) self.document.remove_backref(ref_name, self.id, old_value) self.document.index_backref(ref_name, self.id, ref_value)
[docs] def get_attr_list(self, include_types=False, safe=False): """Get attributes of the current class (gathering all attributes of base classes)""" # Caching for classes # (since the output is always the same for the same class) myclass = self.__class__ if not hasattr(myclass, '__attr_list_cache'): myclass.__attr_list_cache = {} # Not in cache -- must compute if not (include_types, safe) in myclass.__attr_list_cache: mybases = inspect.getmro(myclass) attrs = [attr for cls in mybases if hasattr(cls, 'attrib') for attr in cls.attrib] if safe: attrs = [(Node.__safe_name(attr), atype) for attr, atype in attrs] if not include_types: attrs = [attr for attr, atype in attrs] myclass.__attr_list_cache[(include_types, safe)] = attrs # Return the result from cache return myclass.__attr_list_cache[(include_types, safe)]
[docs] def get_ref_attr_list(self, split_nested=False): """Return a list of the attributes of the current class that contain references (splitting nested ones, if needed)""" # Caching for classes # (since the output is always the same for the same class) myclass = self.__class__ if not hasattr(myclass, '__ref_attr_cache'): myclass.__ref_attr_cache = {} # Not in cache -- must compute if not split_nested in self.__class__.__ref_attr_cache: mybases = inspect.getmro(myclass) attrs = [attr for cls in mybases if hasattr(cls, 'ref_attrib') for attr in cls.ref_attrib] if not split_nested: myclass.__ref_attr_cache[split_nested] = attrs else: # unwind the attributes to a dictionary attr_dict = {} for attr in attrs: # always put True value for the whole path attr_dict[attr] = True # for nested values, put a nested dictionary in addition if '/' in attr: key, val = attr.split('/', 1) if not isinstance(attr_dict.get(key), dict): attr_dict[key] = {} attr_dict[key][val] = True myclass.__ref_attr_cache[split_nested] = attr_dict # Return the result from cache return myclass.__ref_attr_cache[split_nested]
[docs] def get_attr(self, name): """Return the value of the given attribute. Allows for dictionary nesting, e.g. 'morphcat/gender'""" if '/' in name: attr, path = name.split('/', 1) path = path.split('/') obj = getattr(self, Node.__safe_name(attr)) for step in path: if type(obj) != dict: return None obj = obj.get(step) return obj else: return getattr(self, Node.__safe_name(name))
[docs] def set_attr(self, name, value): """Set the value of the given attribute. Allows for dictionary nesting, e.g. 'morphcat/gender'""" # handle referring attributes (keep track of backwards references) self.__track_backref(name, value) # any nested attributes if '/' in name: #prepare the attribute as a dict attr, path = name.split('/', 1) path = path.split('/') obj = getattr(self, Node.__safe_name(attr)) if type(obj) != dict: obj = {} setattr(self, Node.__safe_name(attr), obj) # build dict path up to the last level for step in path[:-1]: if not step in obj: obj[step] = {} obj = obj[step] #set the value obj[path[-1]] = value # plain attributes else: setattr(self, Node.__safe_name(name), value)
[docs] def set_deref_attr(self, name, value): """This assumes the value is a node/list of nodes and sets its id/their ids as the value of the given attribute.""" if type(value) == list: self.set_attr(name, [node.id for node in value]) else: self.set_attr(name, value.id)
[docs] def get_deref_attr(self, name): """This assumes the given attribute holds node id(s) and returns the corresponding node(s)""" value = self.get_attr(name) if type(value) == list: return [self.document.get_node_by_id(node_id) for node_id in value] elif value is not None: return self.document.get_node_by_id(value) return None
[docs] def get_referenced_ids(self): """Return all ids referenced by this node, keyed under their reference types in a hash.""" ret = {'alignment': []} for align in self.alignment: ret['alignment'].add(align['counterpart.rf']) for attr in self.get_ref_attr_list(): value = self.get_attr(attr) if not value: continue ret[attr] = as_list(value) return ret
[docs] def remove_reference(self, ref_type, refd_id): "Remove the reference of the given type to the given node." # handle alignment separately if ref_type == 'alignment': refs = self.get_attr('alignment') self.set_attr('alignment', [ref for ref in refs if ref['counterpart.rf'] != refd_id]) # handle plain attributes and lists refs = self.get_attr(ref_type) if isinstance(refs, list): self.set_attr(ref_type, [ref for ref in refs if ref != refd_id]) else: self.set_attr(ref_type, None)
[docs] def get_descendants(self, add_self=False, ordered=False, preceding_only=False, following_only=False): "Return all topological descendants of this node." return self._process_switches([desc for child in self.__children for desc in child.__descs_and_self_unsorted()], add_self, ordered, preceding_only, following_only)
[docs] def get_children(self, add_self=False, ordered=False, preceding_only=False, following_only=False): "Return all children of the node" return self._process_switches(list(self.__children), add_self, ordered, preceding_only, following_only)
def __descs_and_self_unsorted(self): "Recursive function to return all descendants + self, in any order." return [self] + [desc for child in self.__children for desc in child.__descs_and_self_unsorted()] def _process_switches(self, nodes, add_self, ordered, preceding_only, following_only): """Process all variants on a node list: add self, order, filter out only preceding or only following ones.""" if preceding_only and following_only: raise RuntimeException('Cannot return preceding_only ' + 'and following_only nodes') if preceding_only or following_only: ordered = True if add_self: nodes.append(self) # filtering if preceding_only: nodes = filter(lambda node: node < self, nodes) elif following_only: nodes = filter(lambda node: node > self, nodes) # sorting if ordered: nodes.sort() return nodes
[docs] def create_child(self, id=None, data=None): "Create a child of the current node" if id: data = data and data or {} data['id'] = id return getattr(sys.modules[__name__], self.__class__.__name__)(data=data, parent=self)
[docs] def remove(self): "Remove the node from the tree." if self.get_children(): raise RuntimeException('Cannot remove a node with children:' + self.id) self.parent = None self.document.remove_node(self.id)
@property def root(self): "The root of the tree this node is in." return self.__root @property def document(self): "The document this node is a member of." return self.__document @property def parent(self): "The parent of the current node. None for roots." return self.__parent @parent.setter def parent(self, value): "Change the parent of the current node." # TODO possibly implement moving across documents # (would require new ID indexing) if value is not None and self.__document != value.__document: raise RuntimeException('Cannot move nodes across documents.') # filter original parent's children if self.__parent: self.__parent.__children = [child for child in self.__parent.__children if child != self] # set new parent and update its children, set new root self.__parent = value if self.__parent: self.__parent.__children.append(self) self.__root = self.__parent.__root else: self.__root = self
[docs] def get_depth(self): "Return the depth, i.e. the distance to the root." node = self depth = 0 while not node.is_root: node = node.parent depth += 1 return depth
@property def id(self): "The unique id of the node within the document." return self.__id @id.setter def id(self, value): self.__id = value if self.__document: self.__document.index_node(self) @property def zone(self): "The zone this node belongs to." return self.__zone @property def is_root(self): """Return true if this node is a root""" return self.parent is None def __eq__(self, other): "Node comparison by id" return self.id == other.id def __ne__(self, other): "Node comparison by id" return self.id != other.id def __lt__(self, other): "Node ordering is only implemented in Ordered" if not isinstance(self, Ordered) or not isinstance(other, Ordered): return NotImplemented return Ordered.__lt__(self, other) def __gt__(self, other): "Node ordering is only implemented in Ordered" if not isinstance(self, Ordered) or not isinstance(other, Ordered): return NotImplemented return Ordered.__gt__(self, other) def __le__(self, other): "Node ordering is only implemented in Ordered" if not isinstance(self, Ordered) or not isinstance(other, Ordered): return NotImplemented return Ordered.__le__(self, other) def __ge__(self, other): "Node ordering is only implemented in Ordered" if not isinstance(self, Ordered) or not isinstance(other, Ordered): return NotImplemented return Ordered.__ge__(self, other)
[docs]class Ordered(object): """\ Representing an ordered node (has an attribute called ord), defines sorting. """ attrib = [('ord', types.IntType)] ref_attrib = [] def __lt__(self, other): return self.ord < other.ord def __gt__(self, other): return self.ord > other.ord def __le__(self, other): return self.ord <= other.ord def __ge__(self, other): return self.ord >= other.ord
[docs] def shift_after_node(self, other, without_children=False): "Shift one node after another in the ordering." self.__shift_to_node(other, after=True)
[docs] def shift_before_node(self, other, without_children=False): "Shift one node before another in the ordering." self.__shift_to_node(other, after=False)
[docs] def shift_before_subtree(self, other, without_children=False): """\ Shift one node before the whole subtree of another node in the ordering. """ subtree = other.get_descendants(ordered=True, add_self=True) if len(subtree) <= 1 and self == other: return # no point if self==other and there are no children self.__shift_to_node(subtree[0] == self and subtree[1] or subtree[0], after=False)
[docs] def shift_after_subtree(self, other, without_children=False): """\ Shift one node after the whole subtree of another node in the ordering. """ subtree = other.get_descendants(ordered=True, add_self=True) if len(subtree) <= 1 and self == other: return # no point if self==other and there are no children self.__shift_to_node(subtree[-1] == self and subtree[-2] or subtree[-1], after=True)
def __shift_to_node(self, other, after, without_children=False): "Shift a node before or after another node in the ordering" all_nodes = self.root.get_descendants(ordered=True, add_self=True) # determine what's being moved to_move = [self] if without_children else self.get_descendants(ordered=True, add_self=True) moving = set(to_move) # do the moving cur_ord = 0 for node in all_nodes: # skip nodes moved, handle them when we're at the reference node if node in moving: continue if after: node.ord = cur_ord cur_ord += 1 # we're at the target node, move all needed if node == other: for moving_node in to_move: moving_node.ord = cur_ord cur_ord += 1 if not after: node.ord = cur_ord cur_ord += 1
[docs] def get_next_node(self): "Get the following node in the ordering." my_ord = self.ord next_ord, next_node = (None, None) for node in self.root.get_descendants(): cur_ord = node.ord if cur_ord <= my_ord: continue if next_ord is not None and cur_ord > next_ord: continue next_ord, next_node = (cur_ord, node) return next_node
[docs] def get_prev_node(self): "Get the preceding node in the ordering." my_ord = self.ord prev_ord, prev_node = (None, None) for node in self.root.get_descendants(): cur_ord = node.ord if cur_ord >= my_ord: continue if prev_ord is not None and cur_ord < prev_ord: continue prev_ord, prev_node = (cur_ord, node) return prev_node
[docs] def is_first_node(self): """\ Return True if this node is the first node in the tree, i.e. has no previous nodes. """ prev_node = self.get_prev_node() return prev_node is None
[docs] def is_last_node(self): """\ Return True if this node is the last node in the tree, i.e. has no following nodes. """ next_node = self.get_next_node() return next_node is None
@property def is_right_child(self): """Return True if this node has a greater ord than its parent. Returns None for a root.""" if self.parent is None: return None return self.parent.ord < self.ord
[docs]class EffectiveRelations(object): "Representing a node with effective relations" attrib = [('is_member', types.BooleanType)] ref_attrib = []
[docs] def is_coap_root(self): """\ Testing whether the node is a coordination/apposition root. Must be implemented in descendants. """ raise NotImplementedError
# TODO: dive ~ subroutine / auxcp
[docs] def get_echildren(self, or_topological=False, add_self=False, ordered=False, preceding_only=False, following_only=False): "Return the effective children of the current node." # test if we can get e-children if not self.__can_apply_eff(or_topological): return self.get_children(add_self, ordered, preceding_only, following_only) # my own effective children # (I am their only parent) & shared effective children echildren = self.__get_my_own_echildren() + \ self.__get_shared_echildren() # final filtering return self._process_switches(echildren, add_self, ordered, preceding_only, following_only)
def __can_apply_eff(self, or_topological): """Return true if the given node is OK for effective relations to be applied, false otherwise.""" if self.is_coap_root(): caller_name = inspect.stack()[1][3] message = caller_name + ' called on coap_root (' + self.id + ').' if or_topological: return False else: # this should not happen, so warn about it log_warn(message + ' Fallback to topological.') return False return True def __get_my_own_echildren(self): "Return the e-children of which this node is the only parent." echildren = [] for node in self.get_children(): if node.is_coap_root(): echildren.extend(node.get_coap_members()) else: echildren.append(node) return echildren def __get_shared_echildren(self): "Return e-children this node shares with other node(s)" coap_root = self.__get_direct_coap_root() if not coap_root: return [] echildren = [] while coap_root: # add all shared children and go upwards echildren += [coap_member for node in coap_root.get_children() if not node.is_member for coap_member in node.get_coap_members()] coap_root = coap_root.__get_direct_coap_root() return echildren def __get_direct_coap_root(self): "Return the direct coap root." if self.is_member: return self.parent return None
[docs] def get_coap_members(self): """Return the members of the coordination, if the node is a coap root. Otherwise return the node itself.""" if not self.is_coap_root(): return [self] queue = deque(filter(lambda node: node.is_member, self.get_children())) members = [] while queue: node = queue.popleft() if node.is_coap_root(): queue.extend(filter(lambda node: node.is_member, node.get_children())) else: members.append(node) return members
[docs] def get_eparents(self, or_topological=False, add_self=False, ordered=False, preceding_only=False, following_only=False): "Return the effective parents of the current node." # test if we can get e-parents if not self.__can_apply_eff(or_topological): return [self.parent] return self._process_switches(self.__get_eparents(), add_self, ordered, preceding_only, following_only)
def __get_eparents(self): if not self.parent: log_warn("Cannot find parents, using the root: " + self.id) return [self.root] # try getting coap root, if applicable node = self.__get_transitive_coap_root() or self # continue to parent node = node.parent if not node: return [self.__fallback_parent()] # if we are not in coap, return just the one node if not node.is_coap_root: return [node] # we are in a coap -> return members eff = node.get_coap_members() if eff: return eff return [self.__fallback_parent()] def __get_transitive_coap_root(self): "Climb up a nested coap structure and return its root." root = self.__get_direct_coap_root() if not root: return None while root.is_member: root = root.__get_direct_coap_root() if not root: return None return root def __fallback_parent(self): "Issue a warning and return the topological parent." log_warn("No effective parent, using topological: " + self.id) return self.parent
[docs]class InClause(object): "Represents nodes that are organized in clauses" attrib = [('clause_number', types.IntType), ('is_clause_head', types.BooleanType)] ref_attrib = []
[docs] def get_clause_root(self): "Return the root of the clause the current node resides in." # default to self if clause number is not defined if self.clause_number is None: log_warn('Clause number undefined in: ' + self.id) return self highest = self parent = self.parent # move as high as possible within the clause while parent and parent.clause_number == self.clause_number: highest = parent parent = parent.parent # handle coordinations - shared attributes if parent and parent.is_coap_root() and not highest.is_member: try: eff_parent = next(child for child in parent.get_children() if child.is_member and child.clause_number == self.clause_number) return eff_parent except StopIteration: # no eff_parent found pass return highest
[docs]class T(Node, Ordered, EffectiveRelations, InClause): "Representing a t-node" attrib = [('functor', types.UnicodeType), ('formeme', types.UnicodeType), ('t_lemma', types.UnicodeType), ('nodetype', types.UnicodeType), ('subfunctor', types.UnicodeType), ('tfa', types.UnicodeType), ('is_dsp_root', types.BooleanType), ('gram', types.DictType), ('a', types.DictType), ('compl.rf', types.ListType), ('coref_gram.rf', types.ListType), ('coref_text.rf', types.ListType), ('sentmod', types.UnicodeType), ('is_parenthesis', types.BooleanType), ('is_passive', types.BooleanType), ('is_generated', types.BooleanType), ('is_relclause_head', types.BooleanType), ('is_name_of_person', types.BooleanType), ('voice', types.UnicodeType), ('mlayer_pos', types.UnicodeType), ('t_lemma_origin', types.UnicodeType), ('formeme_origin', types.UnicodeType), ('is_infin', types.BooleanType), ('is_reflexive', types.BooleanType)] ref_attrib = ['a/lex.rf', 'a/aux.rf', 'compl.rf', 'coref_gram.rf', 'coref_text.rf'] def __init__(self, data=None, parent=None, zone=None): "Constructor" Node.__init__(self, data, parent, zone)
[docs] def is_coap_root(self): functor = self.functor or None return functor in ['CONJ', 'CONFR', 'DISJ', 'GRAD', 'ADVS', 'CSQ', 'REAS', 'CONTRA', 'APPS', 'OPER']
@property def lex_anode(self): return self.get_deref_attr('a/lex.rf') @lex_anode.setter def lex_anode(self, value): self.set_deref_attr('a/lex.rf', value) @property # TODO think of a better way (make node.aux_anodes.append(node2) possible?) def aux_anodes(self): return self.get_deref_attr('a/aux.rf') @aux_anodes.setter def aux_anodes(self, value): self.set_deref_attr('a/aux.rf', value) @property def anodes(self): "Return all anodes of a t-node" return (self.lex_anode and [self.lex_anode] or []) + \ (self.aux_anodes or [])
[docs] def add_aux_anodes(self, new_anodes): "Add an auxiliary a-node/a-nodes to the list." # get the original anodes and set the union if self.aux_anodes: self.aux_anodes = self.aux_anodes + as_list(new_anodes) else: self.aux_anodes = as_list(new_anodes)
[docs] def remove_aux_anodes(self, to_remove): "Remove an auxiliary a-node from the list" self.aux_anodes = [anode for anode in self.aux_anodes if not anode in to_remove] if not self.aux_anodes: self.aux_anodes = None
@property def coref_gram_nodes(self): return self.get_deref_attr('coref_gram.rf') @coref_gram_nodes.setter def coref_gram_nodes(self, new_coref): self.set_deref_attr('coref_gram.rf', new_coref) @property def coref_text_nodes(self): return self.get_deref_attr('coref_text.rf') @coref_text_nodes.setter def coref_text_nodes(self, new_coref): self.set_deref_attr('coref_text.rf', new_coref) @property def compl_nodes(self): return self.get_deref_attr('compl.rf') @compl_nodes.setter def compl_nodes(self, new_coref): self.set_deref_attr('compl.rf', new_coref) @property def gram_number(self): return self.get_attr('gram/number') @gram_number.setter def gram_number(self, value): self.set_attr('gram/number', value) @property def gram_gender(self): return self.get_attr('gram/gender') @gram_gender.setter def gram_gender(self, value): self.set_attr('gram/gender', value) @property def gram_tense(self): return self.get_attr('gram/tense') @gram_tense.setter def gram_tense(self, value): self.set_attr('gram/tense', value) @property def gram_negation(self): return self.get_attr('gram/negation') @gram_negation.setter def gram_negation(self, value): self.set_attr('gram/negation', value) @property def gram_aspect(self): return self.get_attr('gram/aspect') @gram_aspect.setter def gram_aspect(self, value): self.set_attr('gram/aspect', value) @property def gram_degcmp(self): return self.get_attr('gram/degcmp') @gram_degcmp.setter def gram_degcmp(self, value): self.set_attr('gram/degcmp', value) @property def gram_deontmod(self): return self.get_attr('gram/deontmod') @gram_deontmod.setter def gram_deontmod(self, value): self.set_attr('gram/deontmod', value) @property def gram_dispmod(self): return self.get_attr('gram/dispmod') @gram_dispmod.setter def gram_dispmod(self, value): self.set_attr('gram/dispmod', value) @property def gram_indeftype(self): return self.get_attr('gram/indeftype') @gram_indeftype.setter def gram_indeftype(self, value): self.set_attr('gram/indeftype', value) @property def gram_iterativeness(self): return self.get_attr('gram/iterativeness') @gram_iterativeness.setter def gram_iterativeness(self, value): self.set_attr('gram/iterativeness', value) @property def gram_numertype(self): return self.get_attr('gram/numertype') @gram_numertype.setter def gram_numertype(self, value): self.set_attr('gram/numertype', value) @property def gram_person(self): return self.get_attr('gram/person') @gram_person.setter def gram_person(self, value): self.set_attr('gram/person', value) @property def gram_politeness(self): return self.get_attr('gram/politeness') @gram_politeness.setter def gram_politeness(self, value): self.set_attr('gram/politeness', value) @property def gram_resultative(self): return self.get_attr('gram/resultative') @gram_resultative.setter def gram_resultative(self, value): self.set_attr('gram/resultative', value) @property def gram_verbmod(self): return self.get_attr('gram/verbmod') @gram_verbmod.setter def gram_verbmod(self, value): self.set_attr('gram/verbmod', value) @property def gram_sempos(self): return self.get_attr('gram/sempos') @gram_sempos.setter def gram_sempos(self, value): self.set_attr('gram/sempos', value) @property def gram_diathesis(self): return self.get_attr('gram/diathesis') @gram_diathesis.setter def gram_diathesis(self, value): self.set_attr('gram/diathesis', value) def __eq__(self, other): """Equality based on memory reference, IDs, and finally hashes. TODO evaluate thoroughly""" if self is other: # same object (address) return True if self.id and other.id and self.id == other.id: # same IDs return True return hash(self) == hash(other) # same tree under different/no ID def __ne__(self, other): return not self == other def __hash__(self): """Return hash of the tree that is composed of t-lemmas, formemes, and parent orders of all nodes in the tree (ordered).""" return hash(unicode(self)) def __unicode__(self): desc = self.get_descendants(add_self=1, ordered=1) return ' '.join(['%d|%d|%s|%s' % (n.ord if n.ord is not None else -1, n.parent.ord if n.parent else -1, n.t_lemma, n.formeme) for n in desc]) def __str__(self): return unicode(self).encode('UTF-8', 'replace')
[docs]class A(Node, Ordered, EffectiveRelations, InClause): "Representing an a-node" attrib = [('form', types.UnicodeType), ('lemma', types.UnicodeType), ('tag', types.UnicodeType), ('afun', types.UnicodeType), ('no_space_after', types.BooleanType), ('morphcat', types.DictType), ('is_parenthesis_root', types.BooleanType), ('edge_to_collapse', types.BooleanType), ('is_auxiliary', types.BooleanType), ('p_terminal.rf', types.UnicodeType), ] ref_attrib = ['p_terminal.rf'] morphcat_members = ['pos', 'subpos', 'gender', 'number', 'case', 'person', 'tense', 'negation', 'voice', 'grade', 'mood', 'possnumber', 'possgender'] def __init__(self, data=None, parent=None, zone=None): "Constructor" Node.__init__(self, data, parent, zone)
[docs] def is_coap_root(self): afun = self.afun or None return afun in ['Coord', 'Apos']
[docs] def reset_morphcat(self): "Reset the morphcat structure members to '.'" for category in A.morphcat_members: self.set_attr('morphcat/' + category, '.')
@property def morphcat_pos(self): return self.get_attr('morphcat/pos') @morphcat_pos.setter def morphcat_pos(self, value): self.set_attr('morphcat/pos', value) @property def morphcat_subpos(self): return self.get_attr('morphcat/subpos') @morphcat_subpos.setter def morphcat_subpos(self, value): self.set_attr('morphcat/subpos', value) @property def morphcat_gender(self): return self.get_attr('morphcat/gender') @morphcat_gender.setter def morphcat_gender(self, value): self.set_attr('morphcat/gender', value) @property def morphcat_number(self): return self.get_attr('morphcat/number') @morphcat_number.setter def morphcat_number(self, value): self.set_attr('morphcat/number', value) @property def morphcat_case(self): return self.get_attr('morphcat/case') @morphcat_case.setter def morphcat_case(self, value): self.set_attr('morphcat/case', value) @property def morphcat_person(self): return self.get_attr('morphcat/person') @morphcat_person.setter def morphcat_person(self, value): self.set_attr('morphcat/person', value) @property def morphcat_tense(self): return self.get_attr('morphcat/tense') @morphcat_tense.setter def morphcat_tense(self, value): self.set_attr('morphcat/tense', value) @property def morphcat_negation(self): return self.get_attr('morphcat/negation') @morphcat_negation.setter def morphcat_negation(self, value): self.set_attr('morphcat/negation', value) @property def morphcat_voice(self): return self.get_attr('morphcat/voice') @morphcat_voice.setter def morphcat_voice(self, value): self.set_attr('morphcat/voice', value) @property def morphcat_grade(self): return self.get_attr('morphcat/grade') @morphcat_grade.setter def morphcat_grade(self, value): self.set_attr('morphcat/grade', value) @property def morphcat_mood(self): return self.get_attr('morphcat/mood') @morphcat_mood.setter def morphcat_mood(self, value): self.set_attr('morphcat/mood', value) @property def morphcat_possnumber(self): return self.get_attr('morphcat/possnumber') @morphcat_possnumber.setter def morphcat_possnumber(self, value): self.set_attr('morphcat/possnumber', value) @property def morphcat_possgender(self): return self.get_attr('morphcat/possgender') @morphcat_possgender.setter def morphcat_possgender(self, value): self.set_attr('morphcat/possgender', value)
[docs]class N(Node): "Representing an n-node" attrib = [('ne_type', types.UnicodeType), ('normalized_name', types.UnicodeType), ('a.rf', types.ListType), ] ref_attrib = ['a.rf'] def __init__(self, data=None, parent=None, zone=None): "Constructor" Node.__init__(self, data, parent, zone)
[docs]class P(Node): "Representing a p-node" attrib = [('is_head', types.BooleanType), ('index', types.UnicodeType), ('coindex', types.UnicodeType), ('edgelabel', types.UnicodeType), ('form', types.UnicodeType), ('lemma', types.UnicodeType), ('tag', types.UnicodeType), ('phrase', types.UnicodeType), ('functions', types.UnicodeType), ] ref_attrib = [] def __init__(self, data=None, parent=None, zone=None): "Constructor" Node.__init__(self, data, parent, zone)