08 February 2011

Export all Tomboy notes into HTML/XHTML

Just wrote Python script to export all the Tomboy notes into an HTML file. The following script connects Tomboy via DBus Interface, creates XHTML file and converts it to HTML by means of an XSL. Conversion powered by xsltproc utility.

tomboy2html.py

#!/usr/bin/python
# Copyright (C) 2011 - Ruslan Osmanov <rrosmanov@gmail.com>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# See <http://www.gnu.org/licenses/>
# 
##
# @file tomboy2html.py
# @brief Export all Tomboy notes in XHTML/HTML file(s)
# @author Ruslan Osmanov
# @version 1.0
# @date 08.02.2011
# @details xsltproc utility required
# @copyright Copyright (C) 2011 - Ruslan Osmanov

import dbus, dbus.glib
try:
  import gobject
except ImportError:
  # gobject functions are moved to dbus.glib in this Python version
  pass
import os, sys, getopt
import re

def usage():
  print __file__ + " [OPTIONS]"
  print """Options:
-h, --help    Display help
-p, --prefix    Optional. Output filename prefix. Default: notes
-x, --xhtml   Optional. Generate XHTML file also. Default: off 
-s, --xsl   Optional. XSL for XML-HTML conversion. Default: tomboy-notes.xsl 
-d, --debug   Optional. Debug mode. Default: off 
"""

def main(argv):
  debug_mode = False
  prefix = 'notes'
  xhtml = False
  xsl_filename = os.path.dirname(__file__) + '/tomboy-notes.xsl'

  # Get CLI options
  try:
    opts, args = getopt.getopt(argv[1:], "s:p:xhd", 
        ("xsl=", "prefix=", "debug", "xhtml", "help"))

  except getopt.GetoptError:
    usage()
    sys.exit(2)

  # Save CLI options
  for o, a in opts:
    if o in ('-h', '--help'):
      usage();
      sys.exit();

    elif o in ('-x', '--xhtml'):
      xhtml = True

    elif o in ('-d', '--debug'):
      debug_mode = True

    elif o in ("-p", "--prefix"):
      prefix = os.path.expanduser(a)

    elif o in ("-s", "--xsl"):
      xsl_filename = os.path.expanduser(a)

  xhtml_filename = prefix + '.xhtml'
  html_filename = prefix + '.html'
  if False == os.path.exists(xsl_filename):
    print "XSL file '"+xsl_filename+"' doesn't exist"
    sys.exit(2)

  if debug_mode:
    print "xhtml =", xhtml_filename, "\nhtml =", html_filename
    print "\nxsl =", xsl_filename

  # Access the Tomboy remote control interface
  bus = dbus.SessionBus()
  obj = bus.get_object("org.gnome.Tomboy", "/org/gnome/Tomboy/RemoteControl")
  tomboy = dbus.Interface(obj, "org.gnome.Tomboy.RemoteControl")

  # Get note URIs
  all_notes = tomboy.ListAllNotes()

  # Write each note XML to the XHTML file
  f = open(xhtml_filename, "w")
  f.write('<?xml version="1.0" encoding="utf-8"?>' + 
      '<?xml-stylesheet type="text/xsl" href="' +xsl_filename+'"?>'
      '<notes>')
  for n in all_notes:
    if (debug_mode):
      print "NOTE '"+n+"'"
    xml = re.sub(r'<\?xml[^<]*\?>', '', tomboy.GetNoteCompleteXml(n))
    xml = re.sub(r'\&\#x?.*\;', '', xml)
    f.write(unicode(xml).encode("utf-8"))

  f.write('</notes>')
  f.close()

  # Generate HTML
  cmd = "xsltproc -o '%(html)s' '%(xsl)s' '%(xhtml)s'" % \
      {'html': html_filename.replace("'", "\\'"), 
          'xsl': xsl_filename, 
          'xhtml': xhtml_filename}
      if debug_mode:
        print cmd
  os.system(cmd)

  if xhtml == False:
    os.unlink(xhtml_filename)

if __name__ == "__main__":
    main(sys.argv)

tomboy-notes.xsl

<?xml version='1.0' encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tomboy="http://beatniksoftware.com/tomboy"
xmlns:notes="http://beatniksoftware.com/tomboy/notes"
xmlns:size="http://beatniksoftware.com/tomboy/size"
xmlns:link="http://beatniksoftware.com/tomboy/link"
version='1.0'>

<xsl:output method="html" indent="no" />
<xsl:preserve-space elements="*" />

<xsl:param name="font" />
<xsl:param name="export-linked" />
<xsl:param name="export-linked-all" />
<xsl:param name="root-note" />

<xsl:param name="newline" select="'&#xA;'" />

<xsl:template match="/">
<html>
<head>
    <title>Tomboy Notes Export</title>
    <style type="text/css">/*<![CDATA[*/
        body { <xsl:value-of select="$font" /> }
        h1 { 
            font-size: xx-large;
            font-weight: bold;
            border-bottom: 1px solid black; 
        }
        div.note {
            position: relative;
            display: block;
            padding: 5pt;
            margin: 5pt;
            white-space: -moz-pre-wrap; /* Mozilla */
            white-space: -pre-wrap; /* Opera 4 - 6 */
            white-space: -o-pre-wrap; /* Opera 7 */
            white-space: pre-wrap; /* CSS3 */
            word-wrap: break-word; /* IE 5.5+ */ 
        }
        table.note-info{
            margin: 20px 0 5px;
            font-size:xx-small;
            border-width: 0 1px 1px 0;
            border-style: solid;
        }
        table.note-info td{
            padding: 2px 5px;
            border-width: 1px 0 0 1px;
            border-style: solid;
        }
        /*//]]>*/
    </style>
</head>
<body>
    <xsl:for-each select="notes/tomboy:note">
    <div class="note">
        <xsl:apply-templates select="tomboy:text | tomboy:title"/>

        <table class="note-info" boder="0" cellpadding="0" cellspacing="0"
            summary="Note Info">
            <tr>
                <td>Last updated:</td>    
                <td><xsl:value-of select="tomboy:last-change-date" /></td>
            </tr>    
            <tr>
                <td>Tags:</td>    
                <td>
                    <xsl:for-each select="tomboy:tags">
                    <div><xsl:value-of select="tomboy:tag"/></div>    
                    </xsl:for-each>
                </td>
            </tr>    
        </table>
    </div>
    </xsl:for-each>
</body>
</html>
</xsl:template>



<xsl:template match="text()">
    
    <xsl:value-of select="."/>
</xsl:template>

<xsl:template match="child::tomboy:text">
    <xsl:apply-templates select="node()"/>
</xsl:template>

<xsl:template match="tomboy:title">
    <h1><xsl:value-of select="text()"/></h1>
</xsl:template>
<xsl:template match="tomboy:bold">
    <strong><xsl:apply-templates select="node()"/></strong>
</xsl:template>
<xsl:template match="tomboy:italic">
    <i><xsl:apply-templates select="node()"/></i>
</xsl:template>
<xsl:template match="tomboy:monospace">
    <code><xsl:apply-templates select="node()"/></code>
</xsl:template>

<xsl:template match="tomboy:strikethrough">
<strike><xsl:apply-templates select="node()"/></strike>
</xsl:template>

<xsl:template match="tomboy:highlight">
<span style="background:yellow"><xsl:apply-templates select="node()"/></span>
</xsl:template>

<xsl:template match="tomboy:datetime">
<span style="font-style:italic;font-size:small;color:#888A85">
<xsl:apply-templates select="node()"/>
</span>
</xsl:template>

<xsl:template match="size:small">
<span style="font-size:small"><xsl:apply-templates select="node()"/></span>
</xsl:template>

<xsl:template match="size:large">
<span style="font-size:large"><xsl:apply-templates select="node()"/></span>
</xsl:template>

<xsl:template match="size:huge">
<span style="font-size:xx-large"><xsl:apply-templates select="node()"/></span>
</xsl:template>

<xsl:template match="link:broken">
<span style="color:#555753;text-decoration:underline">
<xsl:value-of select="node()"/>
</span>
</xsl:template>
<xsl:template match="link:internal">
<a style="color:#204A87" href="#{node()}">
<xsl:value-of select="node()"/>
</a>
</xsl:template>

<xsl:template match="link:url">
<a style="color:#3465A4" href="{node()}"><xsl:value-of select="node()"/></a>
</xsl:template>

<xsl:template match="tomboy:list">
<ul>
<xsl:apply-templates select="tomboy:list-item" />
</ul>
</xsl:template>

<xsl:template match="tomboy:list-item">
<li>
<xsl:if test="normalize-space(text()) = ''">
<xsl:attribute name="style">list-style-type: none</xsl:attribute>
</xsl:if>
<xsl:attribute name="dir">
<xsl:value-of select="@dir"/>
</xsl:attribute>
<xsl:apply-templates select="node()" />
</li>
</xsl:template>

<xsl:template match="//tomboy:x | //tomboy:y | //tomboy:width | //tomboy:height | //tomboy:cursor-position | //tomboy:create-date | tomboy:last-change-date | tomboy:open-on-startup | tomboy:last-metadata-change-date | tomboy:tags">
<xsl:comment>literal</xsl:comment>
</xsl:template>

</xsl:stylesheet>

Usage

I saved the files here in ~/scripts/python/tomboy2html/. Thus, to save all the Tomboy notes in all_notes.xhtml and all_notes.html, I can issue the following:
$ chmod +x ~/scripts/python/tomboy2html/tomboy2html.py
$ ~/scripts/python/tomboy2html/tomboy2html.py -p "all_notes" -x -s \
~/scripts/python/tomboy2html/tomboy-notes.xsl
It creates all_notes.xhtml and all_notes.html files in current directory.

Update 09 February 2011

Clone the Git repo:
$ git clone git://github.com/rosmanov/Tomboy2HTML.git

22 comments :

  1. Hey,
    Thanks for the script, but when I tried, it just exported title's note and urls indide the notes, not the text.
    Can I provide you anything to fix it? using Ubuntu 10.04.2 LTS.

    Thanks in advance.

    ReplyDelete
  2. I suspect it is some kind of indentation issue, if you copied the code directly. Try this:

    $ git clone git://github.com/rosmanov/Tomboy2HTML.git
    $ cd Tomboy2HTML/
    $ python tomboy2html.py -p mynotes -d -s tomboy-notes.xsl
    $ firefox mynotes.html

    If it fails, please, send me the output.

    Thank you for the report.

    ReplyDelete
  3. Hey,
    thanks for the quick answer. From git repo, the script is exporting great! Totally was an indentation issue, my bad.

    Best regards osmanov!

    ReplyDelete
  4. Thanks for writing this .. really really handy. Worked like a charm on my ~300 notes.

    ReplyDelete
  5. Thank you very much!

    ReplyDelete
  6. Thanks for this script!

    (There is a "Tomboy" label missing for this post :P)

    ReplyDelete
  7. Ruslan - thanks for sharing the script with us. Running Lucid, had same problem (indentation?) as above, but git worked great. Thanks!

    ReplyDelete
  8. Thank you for the script! It *almost* works in my case. But something a little bit strange occurs: The "F" "1" "x" disappear in the .html file but not in the .xhtml file. Do you know what I should do?

    ReplyDelete
  9. I just tried this and got the following message:


    dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.


    The output file was not created.

    ReplyDelete
  10. Kupp, is Tomboy really running? Try to checkout the latest version please:

    git clone git://github.com/rosmanov/Tomboy2HTML.git
    cd Tomboy2HTML/
    ./tomboy2html.py -x

    If the problem still persists, post the output of the following command:
    tomboy --debug

    Also make sure dbus is running:
    ps aux | grep dbus

    ReplyDelete
  11. Great utility, you should propose it to become an official Plugin of Tomboy:
    http://live.gnome.org/Tomboy/PluginList

    or you could add an install script (I am not a pro python dev, but I may help if you need).
    Thank you.

    Cristian

    ReplyDelete
  12. Great tool ! Exactly what I was looking for !! Thank you very much !
    I have one question: In the html/xhtml document the links are highlighted, but when I click on them, the focus does not turn to the linked note. Is this supposed to work and hence a problem of my settings ? Sorry, if this is a stupid question, I don't konw much about html. External links do work (like opening my jabref database when clicking on a .bib - file in the note).
    Luuuzi

    ReplyDelete
    Replies
    1. Luuuzi, indeed. It's a bug. I'll make anchors for that.

      Delete
    2. E-mail links didn't work either. Fixed it in v1.3.
      Checkout the git repo please.

      I'm glad you found it useful:)

      Thanks.

      Delete
    3. Thanks a lot !! Works great !!!

      Delete
  13. great! you saved me a lot of work! thanks!

    ReplyDelete
  14. I am having a problem having this script run with Cron.

    I get the following Error:

    ###

    Subject: Cron
    X-Cron-Env:
    X-Cron-Env:
    X-Cron-Env:

    Traceback (most recent call last):
    File "/home/ld50/Sync_Folder/tomboy2html.py", line 126, in
    main(sys.argv)
    File "/home/ld50/Sync_Folder/tomboy2html.py", line 91, in main
    bus = dbus.SessionBus()
    File "/usr/lib/pymodules/python2.6/dbus/_dbus.py", line 219, in __new__
    mainloop=mainloop)
    File "/usr/lib/pymodules/python2.6/dbus/_dbus.py", line 108, in __new__
    bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
    File "/usr/lib/pymodules/python2.6/dbus/bus.py", line 125, in __new__
    bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
    dbus.exceptions.DBusException: org.freedesktop.DBus.Error.Spawn.ExecFailed: /usr/bin/dbus-launch terminated abnormally with the following error: Autolaunch error: X11 initialization failed.
    :


    ###

    My current cron job looks like this.

    # m h dom mon dow command
    * */1 * * * /home/ld50/Sync_Folder/tomboy2html.py

    ReplyDelete
    Replies
    1. Something prevents DBus connection via crond. Here is a workaround:

      #!/bin/bash
      #
      # tomboy2html.sh
      #
      sessionfile=`find "${HOME}/.dbus/session-bus/" -type f`
      export `grep "DBUS_SESSION_BUS_ADDRESS" "${sessionfile}" | sed '/^#/d'`

      cd /home/ld50/Sync_Folder/
      ./tomboy2html.py


      #
      # crontab
      #
      * */1 * * * /bin/bash /home/ld50/Sync_Folder/tomboy2html.sh

      Delete
  15. thanks! great script.

    ReplyDelete
    Replies
    1. What he said. Worked instantly. Very nice output!

      Delete