In the previous part I wrote about implementation of LeoTreeModel class and its fields. First instances of LeoTreeModel were made from Leo’s VNode instances. Now let’s enable building LeoTreeModel directly from .leo xml files.

Loading from Leo xml file

We already have function that builds LeoTreeModel based on a sequence of tuples that define each node in outline see (nodes2treemodel) in previous part. This function expects as its only input argument array of tuples in the following form:

This values can be retrieved from Leo xml file. Iterating over <vnodes> child elements we can build required array of tuples. Values for each tuple element can be deduced from <vnodes> elements all except b-value which is the text of <t> element with the same gnx. All <t> elements are children of one unique <tnodes> element.

For parsing xml I have used xml.etree.ElementTree module which I think is part of standard Python library.

Leo xml file format is not fully symmetric which requires that we have two different iterators: one for every <v> element, and the second one for iterating top level nodes which are children of <vnodes> element.

And as a child nodes we have two different iterators:

This iterator will be used for every <v> element. However, for top-level elements that are not children of <v> element but of <vnodes> element, we have to make different iterator r(oot)iterator.

This iterator invokes the first one for each top level vnode, and finally gives us tuple of node tuples that we can pass to nodes2treemodel function.

Reading external files

After this first pass we will have outline only. All children of @file nodes are still missing. In July 2017, I wrote two functions for reading and writing external files in Leo. They relied on VNode and Position methods so they need to be adjusted for use in new LeoTreeModel. However, we can keep their overall structure.

load_derived_file(lines) takes as input lines of text from derived file and returns generator of tuples (gnx, h, b, level). It has five distinct phases:

  1. handling first lines and header of derived file
  2. creating necessary regexes
  3. init topnode
  4. iterate input lines
  5. yield collected nodes

Phase 1: handling first lines and header

Nothing too special about this phase. We simply read lines and collect them in the first_lines list until we encounter header line. When we have header line we deduce start and end delimiters.

Phase 2: creating regexes

Once we know start and end delimiters we can make some patterns that can be used for parsing remaining lines.

Phase 3: start top node

First we need a place to collect all data.

where set_node is like this:

Phase 4: iterating lines

All those handle ... parts are subnodes of this for loop. There is nothing special about them. They start with some check if they should be applied to current line or not. If so, they do what they need to do and end with continue statement. If current line is not handled by any of handle... nodes, then it is simply appended to the current body.

When we encounter line with closing leo header (“@-leo”), we break out of the loop, and remaining lines (if any) are appended to the top-level node as @last lines.

Phase 5: yielding results

Finally we can just dump all collected data in the outline order.

Extending load_derived_file to get the sequence of node tuples suitable for building LeoTreeModel is straightforward. We just need to collect data about parents/children relations and calculate subtree size for each node.

To be continued

In the next part I will write about adding some methods to data model to implement outline commands.