Walk the table of contents, calling visitor_fn() at each node

    The /Outlines data structure is a messy data structure, but rather than
    navigating hierarchically we just track unique nodes.  Enqueue nodes when
    we find them, and never visit them again.  set() is awesome.  We look for
    the two types of object in the table of contents that can be page bookmarks
    and update the page entry.

def _traverse_toc(pdf_base, visitor_fn, log):
    """
    Walk the table of contents, calling visitor_fn() at each node

    The /Outlines data structure is a messy data structure, but rather than
    navigating hierarchically we just track unique nodes.  Enqueue nodes when
    we find them, and never visit them again.  set() is awesome.  We look for
    the two types of object in the table of contents that can be page bookmarks
    and update the page entry.

    """

    visited = set()
    queue = set()
    link_keys = ('/Parent', '/First', '/Last', '/Prev', '/Next')

    if not '/Outlines' in pdf_base.root:
        return

    queue.add(pdf_base.root.Outlines.objgen)
    while queue:
        objgen = queue.pop()
        visited.add(objgen)
        node = pdf_base.get_object(objgen)
        log.debug('fix toc: exploring outline entries at %r', objgen)

        # Enumerate other nodes we could visit from here
        for key in link_keys:
            if key not in node:
                continue
            item = node[key]
            if not item.is_indirect:
                # Direct references are not allowed here, but it's not clear
                # what we should do if we find any. Removing them is an option:
                # node[key] = pdf_base.make_indirect(None)
                continue
            objgen = item.objgen
            if objgen not in visited:
                queue.add(objgen)

        if visitor_fn:
            visitor_fn(pdf_base, node, log)