from collections import deque import logging as log from . import skip_whitespace, ParserWarning from .property import Property, GAME_INFO class Node: def __init__(self): self.properties = dict() self.parent = None self.children = [] @staticmethod def fits(s, i): return i < len(s) and s[i] == ";" @staticmethod def create(s, start): assert Node.fits(s, start) res = Node() i = skip_whitespace(s, start + 1) while Property.fits(s, i): (i, x) = Property.create(s, i) if x.name in res.properties: log.warning(ParserWarning('duplicate "{0}" property in a node. second value ignored'.format(x.name), s, i)) else: res.properties[x.name] = x i = skip_whitespace(s, i) return (i, res) def list_gi_nodes(self): if self.is_gi_node(): yield self empty = not self.is_gi_node() node = self while node.parent: node = node.parent if node.is_gi_node(): empty = False yield node queue = deque(self.children) while len(queue) > 0: node = queue.popleft() if node.is_gi_node(): empty = False yield node queue.extend(node.children) if empty: yield self # always yield at least self, can work as GINode as well as any other def is_gi_node(self): return any(prop.type == GAME_INFO for prop in self.properties.values()) def set_prop(self, name, value): self.properties[name] = value # check value type def set_children(self, children): self.children = children for child in children: child.parent = self def add_child(self, node): if node in self.children: return node node.parent = self self.children.append(node) return node def remove_child(self, node): if node not in self.children: return None del self.children[self.children.index(node)] node.parent = None return node def remove_child_at(self, i): if -len(self.children) < i < len(self.children): res = self.children[i] del self.children[i] res.parent = None return res return None def copy(self): """Create a copy of the Node, with the same parent and deep copied properties, no copied children.""" res = Node() res.properties = {k: v.copy() for (k, v) in self.properties.items()} res.parent = self.parent return res def get_prop(self, name, default=None): if name in self.properties: return self.properties[name].value else: return default def __str__(self): """Returns textual representation of the Node itself, but disregards its children.""" return ";" + "".join(str(p) for p in self.properties.values()) def export(self): # there is a beautiful recursive solution, which this stack is too narrow to contain stack = [(self, 1, 1)] output = [] while len(stack) > 0: (node, left, right) = stack.pop() if left > 0: output.append("("*left) output.append(str(node)+"\n") child_count = len(node.children) if child_count == 0: # a leaf output.append(")"*right+"\n") elif child_count == 1: # a line stack.append((node.children[0], 0, right)) else: # a branching node # first child pops first, last child closes parent's parentheses children = zip(node.children, [1]*child_count, [1]*(child_count-1)+[1+right]) stack.extend(reversed(list(children))) return "".join(output)