diff --git a/texla/Parser/Blocks/Block.py b/texla/Parser/Blocks/Block.py index 61878fa..4a1fbb7 100644 --- a/texla/Parser/Blocks/Block.py +++ b/texla/Parser/Blocks/Block.py @@ -1,141 +1,160 @@ from .Utilities import * """Base Block definition""" class Block: """ Block general attributes: -block_name: the new of the "type" of the block -id: unique id for the block in the tree -parent_block: parent in the tree -attributes: a dictionary for description of the block. All useful parser data go into attributes -ch_blocks: a list of children_blocks -section_level: the position of the block compared to sectioning levels defined in utility.py Derived Block could add more attributes. """ @staticmethod def parse(parser, tex, parent_block, params): """ The method must return a tuple with the created Block and the last used index of tex string.""" pass def __init__(self, block_name, content, parent_block): """ Base constructor for Block. It saves the parent_block and block name and create the new id for the new block. It creates data structures like the attributed dictionary and children nodes list. It always saves a content variable. By default, it sets the section_level of the block to that of the parend_block. """ self.block_name = block_name self.content = content if not parent_block is None: self.parent_block = parent_block self.id = parent_block.id + '-' + utility.get_random_string(3) #Section level: #by default the level is the same of parent block self.section_level = self.parent_block.section_level #depth in the tree self.tree_depth = self.parent_block.tree_depth+1 else: #if this is the root block self.parent_block = None self.id = '@' self.section_level = -1 self.tree_depth = 0 #dictionary for attributes self.attributes = {} #list for childrend blocks self.ch_blocks = [] self.N_chblocks = 0 def add_child_block(self, block): """ IMPORTANT: this function is called by the self.parse fuction. It MUST NOT be called from outside, expecially the parser """ self.ch_blocks.append(block) self.N_chblocks +=1 def add_children_blocks(self, blocks): """ IMPORTANT: this function is called by the self.parse fuction. It MUST NOT be called from outside, expecially the parser """ self.ch_blocks += blocks self.N_chblocks +=len(blocks) def change_parent_block(self, new_parent): - """s function changes the parent of the + """This function changes the parent of the block. It changes parent object, id, and tree_depth. The section level is not changes for consistency. All children are updated. """ self.parent_block = new_parent #rebuiding id self.id = new_parent.id + '-' + utility.get_random_string(3) #the section level is not changed, #but tree_depth is updated self.tree_depth = new_parent. tree_depth + 1 #now childrens are updated for ch in self.ch_blocks: ch.change_parent_block(self) def get_children(self, bl_name): """ This function return a list of children blocks corresponding to the requested type. If there are not children blocks of that type it returns a void list.""" result = [] for bl in self.ch_blocks: if bl.block_name == bl_name: result.append(bl) return result def __str__(self): return ''.format( self.block_name, self.id) def to_json(self, level=0): """ This functions create a json ouput that represents the tree of subblocks of the called block. """ json = '' levelb = level+3 json += (' '*level + '{\n') json += (' '*levelb + '"ID":"'+ self.id+'",\n') json += (' '*levelb + '"block_name":"'+ self.block_name+'",\n') json += (' '*levelb + '"N. ch_blocks":"'+ str(self.N_chblocks)+'",\n') json += (' '*levelb + '"tree_depth":"'+ str(self.tree_depth)+'",\n') for k,v in self.attributes.items(): json += (' '*levelb + '"'+k+ '":"'+str(v)+ '",\n' ) json += (' '*levelb + '"children_blocks":[\n') for b in self.ch_blocks: json+= b.to_json(levelb+3) json += (' '*levelb+'],\n') json += (' '*level + '}\n') return json def n_children_blocks_total(self): """This function returns the number of all children blocks recursively.""" n = len(self.ch_blocks) for c in self.ch_blocks: n+= c.n_children_blocks_total() return n def get_content(self): """ This function can be overrided by a specific block in order to provide a personalized representation of the content of the block for logging/reporting. """ return self.content + + def query_children_blocks(self, block_name, depth_first=False): + """ + This function looks for a block with a specific block_name + in the children of the block. If depth_first=True it recursiverly + check in every children before continuing. + """ + results = [] + for block in self.ch_blocks: + if block.block_name == block_name: + results.append(block) + if depth_first: + results += block.query_children_blocks(block_name, depth_first) + if not depth_first: + for bl in self.ch_blocks: + results += bl.query_children_blocks(block_name, depth_first) + return results + + diff --git a/texla/Parser/TreeExplorer.py b/texla/Parser/TreeExplorer.py index 53a80cd..092b3cc 100644 --- a/texla/Parser/TreeExplorer.py +++ b/texla/Parser/TreeExplorer.py @@ -1,125 +1,150 @@ import logging class TreeExplorer: """ The TreeExplorer class is an utility to navigate and extract information from the tree of parsed blocks. For example it is useful to extract the tree of the parents of a block for debugging reasons. It is useful also in rendering to localize blocks inside the document. + It is the default container of the result of the parsing process + because it handles the chain of block with several utilities for + quering. """ def __init__(self, root_block): """ The constructor needs a root_block to begin the tree""" self.root_block = root_block self.blocks = {'@': root_block} self.block_names = {"default":[]} #registering blocks by id self.register_blocks(root_block.ch_blocks) self.register_block_names() @staticmethod def create_tree_from_children(block): #first of all we need the root_block current = block while True: if current.parent_block is None: root_block = current break + #populating ch_blocks of parent block if not present + #in order to have a fully functional TreeExplorer. + #This is necessary if the TreeExplorer is used before + #the end of Parsing process. + if current not in current.parent_block.ch_blocks: + current.parent_block.ch_blocks.append(current) current = current.parent_block + #now we can return a new TreeExplorer #constructed from the found root. return TreeExplorer(root_block) def register_blocks(self, blocks): """This methods reads all the blocks tree from the root_block and created a dictionary with id:block""" for block in blocks: if not block.id in self.blocks: self.blocks[block.id] = block if block.N_chblocks > 0: self.register_blocks(block.ch_blocks) def register_block_names(self): """This function registers the block_names, creating a dictionary with blocks groups by type""" for bl in self.blocks.values(): if not bl in self.block_names: self.block_names[bl.block_name] = [] if not bl in self.block_names[bl.block_name]: self.block_names[bl.block_name].append(bl) def update_blocks_register(self): """This methods update the blocks' ids register recalling register_blocks with the root_block""" self.register_blocks(self.root_block.ch_blocks) self.register_block_names() def get_parents_list(self, block): """This method returns the list of the parent blocks of the requested block """ if isinstance(block, str): block = self.blocks[block] parents = [] current = block while True: if current == self.root_block: break parents.append(current.parent_block) current = current.parent_block parents.reverse() return parents def get_parents_list_ids(self, block): parents = self.get_parents_list(block) return [x.id for x in parents] def get_block(self, blockid): return self.blocks.get(blockid) + ############################################################# + # Quering functions + + def query_block_by_name(self, block_name, depth_first=False): + """ + This methods queries recursively the tree of blocks + and returns a list of blocks with the requested block_name. + depth_first controls the type of query. + """ + return self.root_block.query_children_blocks(block_name, depth_first) + + + ############################################################# + # Printing functions + def print_tree(self, block, filter_list=None): """This methods prints a beautified tree starting from block parameter and his children. If filter_list is present only the block with the id in the list are printed. It returns a single string. """ output = [] if filter_list is None or block.id in filter_list: lstr = ". "* (block.tree_depth+1) output.append(lstr+ ". "+ " "+"_"*40 ) output.append(lstr+ "#"+"---"+ ">|ID : {}".format(block.id)) output.append(lstr+ ". "+ " |block_name : {}". format(block.block_name)) output.append(lstr+ ". "+ " |attributes: ") for at,attr in block.attributes.items(): output.append(lstr+ ". " + " | - "+ "{} : {}". format(at, attr)) output.append(lstr+ ". "+ " |content : {}". format(block.get_content())) output.append(lstr+ ". ."+"\u203E"*40+"\n") output = "\n".join(output) #iterating on the block children for bl in block.ch_blocks: output += self.print_tree(bl, filter_list) return output def print_tree_to_blocks(self, blocks): """This methods print the tree of parents of the list of blocks passed as parameter. First of all it gets all the parents ids and then prints the tree using the list as filter.""" fl = [] for bl in blocks: fl+= self.get_parents_list_ids(bl) if isinstance(bl, str): fl.append(bl) else: fl.append(bl.id) return self.print_tree(self.root_block, filter_list=fl) def print_tree_to_block(self, block): return self.print_tree_to_blocks([block]) def print_all_tree(self): return self.print_tree(self.root_block)