pythontreeanytree

Generate tree from file with indents using anytree


I'm using anytree to generate a tree from a file, Tree.txt. Each indent is 3 spaces ( ). Here's what my file looks like:

ROOT
   Node1
      Node2
         Node3
            Node4
   Node5
   Node6

And here's what I have so far for the generating code:

from anytree import Node, RenderTree, find_by_attr

def parse_tree():
    with open('Tree.txt', 'r') as file:
        file_contents = file.readlines()
        root = Node("ROOT")

        current_indent = -1 # store index for comparing later
        for (index, line) in enumerate(file_contents):

            leading_spaces = len(line) - len(line.lstrip(' '))
            indent = int(leading_spaces / 3) # indent = 3 spaces
            
            if indent > current_indent: # node must be 1 level down
                
                # get the previous Node (1 level up)
                previous_line = file_contents[index - 1].strip()
                current_line = line.strip()
                parent = find_by_attr(root, previous_line)
                Node(current_line, parent=parent) # start searching from top node (root) for `previous_line`
                current_indent = indent
            else: # node is on same level or higher
                print(f"What to do for {line.strip()}?") 
                # what should I do here?

        print(RenderTree(root)) # print the output

parse_tree()

However, this prints out:

What to do for Node5?
What to do for Node6?
Node('/ROOT')
└── Node('/ROOT/Node1')
    └── Node('/ROOT/Node1/Node2')
        └── Node('/ROOT/Node1/Node2/Node3')
            └── Node('/ROOT/Node1/Node2/Node3/Node4')

The generated tree is fine, but only up until Node4 - Node5 and Node6 are missing. This is expected, because I didn't handle the case when the current indent is smaller than the previous indent (see # what should I do here?).

What should I do when the current indent is smaller than the previous indent? I know I need to go up n level higher, but how do I find out which level that is?


Solution

  • You'll need a kind of stack to keep track of the depth of the nodes, then you can use the number of initial spaces to determine which parent node to append each leaf to. E.g., three spaces means one level down, so you need to append to the root node at index 0 in the stack. (You could also say that by using a stack we are taking the absolute depth of the leaves, while you tried to solve it relatively if I'm making any sense). I suspect if you use find_by_attr, that might not work in case there are leaves with the same name.

    treestring = '''
    ROOT
       Node1
          Node2
             Node3
                Node4
       Node5
       Node6'''.strip()
    
    leaves = treestring.splitlines()
    
    # initialize stack with the root node
    stack = {0: Node(leaves.pop(0))}
    
    for leaf in leaves:
        
        # determine the node's depth
        leading_spaces = len(leaf)-len(leaf.lstrip(' '))
        level = int(leading_spaces/3)
        
        # add the node to the stack, set as parent the node that's one level up
        stack[level] = Node(leaf.strip(), parent=stack[level-1])
    
    tree = stack[0]
    
    for pre, _, node in RenderTree(tree):
        print(f"{pre}{node.name}")
    

    Result:

    ROOT
    ├── Node1
    │   └── Node2
    │       └── Node3
    │           └── Node4
    ├── Node5
    └── Node6