leo_importer.py:

import cgi, os

import xml.etree.ElementTree as ET

class LeoImporter:
    def to_leo(self, path_to_foreign_data):
        # Leo nodes require an id
        self.t_id = "leo_importer.20090305190728."

        leo_path = self.get_path_to_resulting_leo_file(path_to_foreign_data)

        self.read_foreign_data(path_to_foreign_data)

        self.out_stream = open(leo_path, 'w')

        self.write_opening_of_leo_file()

        self.write_v_node(index=0, foreign_node="@chapters")
        self.write_v_node(index=1, foreign_node=self.get_root())

        self.out_stream.write("""</vnodes>
        <tnodes>
        """)

        # The tnode for the chapters node
        self.write_t_node(index=0, text="")
        i = 1
        for node in self.each_node():
            if i % 100 == 0:
                print "adding t node: ", i
            self.write_t_node(i, self.get_body(node))
            i += 1
        self.out_stream.write("""</tnodes>
        </leo_file>
        """)

        self.out_stream.close()

    def get_path_to_resulting_leo_file(self, filename):
        return filename[:filename.rfind('.')] + ".leo"

    def write_opening_of_leo_file(self):
        """ This stuff goes at the begining of any Leo file. """

        self.out_stream.write("""<?xml version="1.0" encoding="utf-8"?>
        <?xml-stylesheet ekr_test?>
        <leo_file>
        <leo_header file_format="2" tnodes="0" max_tnode_index="0" clone_windows="0"/>
        <globals body_outline_ratio="0.5">
          <global_window_position top="71" left="10" height="600" width="800"/>
          <global_log_window_position top="0" left="0" height="0" width="0"/>
        </globals>
        <preferences/>
        <find_panel_settings/>
        <vnodes>
        """)

    def write_v_node(self, index, foreign_node):
        """
        Take a foreign_node object, extract the necessary information via
        various abstract methods and write the resulting vnode to the output
        stream.
        """

        if index % 100 == 0 and index > 0:
            print "adding v_node: ", index

        if foreign_node == "@chapters":
            headline = foreign_node
        else:
            headline = cgi.escape(self.get_headline(foreign_node))

        self.out_stream.write(('<v t="' + self.t_id + str(index) + '"><vh>' +
                       headline + '</vh>').encode("ascii", "ignore"))

        if foreign_node != "@chapters":
            for child in self.each_child(foreign_node):
                index = self.write_v_node(index + 1, child)

        self.out_stream.write('</v>\n')

        return index

    def write_t_node(self, index, text):
        """
        Write a tnode to the file stream with the passed id and body text.
        """
        self.out_stream.write('<t tx="' + self.t_id + str(index) + '">')
        self.out_stream.write(cgi.escape(text).encode("ascii", "ignore"))
        self.out_stream.write('</t>\n')

    def each_node(self):
        root = self.get_root()
        yield root
        for child in self.each_descendant(root):
            yield child

    def each_descendant(self, node):
        for child in self.each_child(node):
            yield child
            for grandchild in self.each_descendant(child):
                yield grandchild

class ImportFreemind(LeoImporter):
    def read_foreign_data(self, filename):
        self.doc = ET.parse(filename)
        self.root = self.doc.find('node')

    def get_root(self):  return self.root

    def get_headline(self, node):
        return node.attrib['TEXT'].replace('\n', '  ')

    def get_body(self, node):  return ""

    def each_child(self, node):
        for child in node.findall('node'):
            yield child

class ImportTomboy(LeoImporter):
    def read_foreign_data(self, filename):
        dir_to_check = filename
        self.note_filenames = [os.path.join(dir_to_check, name)
                               for name in os.listdir(dir_to_check)
                               if name.endswith(".note")]

    def get_root(self):  return "dummy root"

    def get_path_to_resulting_leo_file(self, filename):
        return "tomboy_notes.leo"

    def get_headline(self, node):
        if node == "dummy root":
            return "Tomboy notes"

        doc = ET.parse(node)

        return doc.getroot()[0].text

    def get_body(self, node):
        if node == "dummy root":
            return ""

        doc = ET.parse(node)

        content = doc.getroot()[1][0]

        def to_string(possible_text):
            if possible_text is None:
                return ""
            return possible_text

        text = to_string(content.text)
        for e in content:
            text += to_string(e.text)
        if len(content) > 0:
            text += to_string(content[-1].tail)

        return text

    def each_child(self, node):
        if node == "dummy root":
            for note_filename in self.note_filenames:
                yield note_filename

if __name__ == '__main__':
  mm_path = "foo.mm"
  ImportFreemind().to_leo(mm_path)