Source code for leo.plugins.leo_to_html

#@+leo-ver=5-thin
#@+node:danr7.20060902215215.1: * @file leo_to_html.py
#@@language python
#@@tabwidth -4

#@+<< docstring >>
#@+node:danr7.20060902215215.2: ** << docstring >>
r''' Converts a leo outline to an html web page.

This plugin takes an outline stored in Leo and converts it to html which is then
either saved in a file or shown in a browser. It is based on the original
leoToHTML 1.0 plugin by Dan Rahmel which had bullet list code by Mike Crowe.

The outline can be represented as a bullet list, a numbered list or using html
<h?> type headings. Optionally, the body text may be included in the output.

If desired, only the current node will be included in the output rather than
the entire outline.

An xhtml header may be included in the output, in which case the code will be
valid XHTML 1.0 Strict.

The plugin is fully scriptable as all its functionality is available through a
Leo_to_HTML object which can be imported and used in scripts.

**Menu items and @settings**

If this plugin loads properly, the following menu items should appear in
your File > Export... menu in Leo::

    Save Outline as HTML  (equivalent to export-html)
    Save Node as HTML     (equivalent to export-html-node)
    Show Outline as HTML  (equivalent to show-html)
    Show Node as HTML     (equivalent to show-html-node)

*Unless* the following appears in an @setting tree::

    @bool leo_to_html_no_menus = True

in which case the menus will **not** be created. This is so that the user can
use @menu and @item to decide which commands will appear in the menu and where.

**Commands**

Several commands will also be made available

export-html
  will export to a file according to current settings.
export-html-*
  will export to a file using bullet type '*' which can be
  **number**, **bullet** or **head**.

The following commands will start a browser showing the html.

show-html
  will show the outline according to current settings.

show-html-*
  will show the outline using bullet type '*' which can be
  **number**, **bullet** or **head**.

The following commands are the same as above except only the current node is converted::

    export-html-node
    export-html-node-*
    show-html-node
    show-html-node-*

**Properties**

.. note::

    As of Mar. 2014 regular Leo @string settings starting with
    `leo_to_html_` are checked first, before the ``.ini`` file.
    E.g. ``@string leo_to_html_flagjustheadlines = No`` has the
    same effect as ``flagjustheadlines = No`` in the ``.ini``, and
    takes precedence.

There are several settings that can appear in the leo_to_html.ini properties
file in leo's plugins folder or be set via the Plugins > leo_to_html >
Properties... menu. These are:

exportpath:
    The path to the folder where you want to store the generated html file.
    Default: c:\\

flagjustheadlines:
    Default: 'Yes' to include only headlines in the output.

flagignorefiles:
    Default: 'Yes' to ignore @file nodes.

use_xhtml:
    Yes to include xhtml doctype declarations and make the file valid XHTML 1.0 Strict.
    Otherwise only a simple <html> tag is used although the output will be xhtml
    compliant otherwise. Default: Yes

bullet_type:
    If this is 'bullet' then the output will be in the form of a bulleted list.
    If this is 'number' then the output will be in the form of a numbered list.
    If this is 'heading' then the output will use <h?> style headers.

    Anything else will result in <h?> type tags being used where '?' will be a
    digit starting at 1 and increasing up to a maximum of six depending on depth
    of nesting. Default: number

browser_command:
    Set this to the command needed to launch a browser on your system or leave it blank
    to use your systems default browser.

    If this is an empty string or the browser can not be launched using this command then
    python's `webbrowser` module will be tried. Using a bad command here will slow down the
    launch of the default browser, better to leave it blank.
    Default: empty string

**Configuration**

At present, the file leo/plugins/leo_to_html.ini contains configuration
settings. In particular, the default export path, "c:\" must be changed for \*nix
systems.

'''
#@-<< docstring >>
#@+<< version history >>
#@+node:danr7.20060902215215.3: ** << version history >>
#@@killcolor
#@+at
# 
# 1.00 - Finished testing with 4 different options & outlines
# 0.91 - Got initial headline export code working. Resolved bug in INI file checking
# 0.90 - Created initial plug-in framework
# 1.1 ekr: Added init method.
# 2.0 plumloco:
#     - made gui independent
#     - made output xhtml compliant
#     - added ini options
#         - use_xhtml: 'Yes' to included xhtml headers in output.
#         - bullet_type: 'number', 'bullet', or 'head'.
#         - browser_command: the command needed to launch a browser.
#     - removed bullet/headlines dialog in favour of bullet_type ini option.
#     - added option to show output in a browser instead of saving to a file
#     - added extra menu items to save/show current node only
#     - added export-html-*-* commands
#     - added show-html-*-* commands
#     - added Leo_to_HTML object so all the plugins functionality can be scripted.
# 2.1 plumloco:
#     - fixed bug in export of single nodes
#     - fixed to use tempdir to get a temp dir
#     - improved (and spellchecked :) docstring.
#     - added abspath module level method
# 2.2 bobjack:
#     - fixed tempdir bug
#     - converted docstring to rst
#     - removed trace
# 2.3 bobjack:
#     - adopt 'every method must have a docstring' ( however inane :) ) rule
#     - added support for @string leo_to_html_no_menus setting.
#     - changed browser_command property default to empty string
#     - use webbrowser module if browser_command property is empty or does not work.
# 
# 
# 
# 
#@-<< version history >>
#@+<< imports >>
#@+node:danr7.20060902215215.4: ** << imports >>
import leo.core.leoGlobals as g

if g.isPython3:
    import configparser as ConfigParser
else:
    import ConfigParser

import subprocess
import webbrowser
import tempfile
import os

#@-<< imports >>

__version__ = '2.3'

#@+others
#@+node:bob.20080107154936: ** module level functions
#@+node:bob.20080107154936.1: *3* init
[docs]def init (): '''Return True if the plugin has loaded successfully.''' g.registerHandler("create-optional-menus",createExportMenus) g.registerHandler('after-create-leo-frame', onCreate) g.plugin_signon(__name__) # I think this should be ok for unit testing. return True
#@+node:bob.20080107154936.2: *3* safe
[docs]def safe(s): """Convert special characters to html entities.""" return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
#@+node:bob.20080110210953: *3* abspath
[docs]def abspath(*args): """Join the arguments and convert to an absolute file path.""" # return g.os_path_abspath(g.os_path_join(*args)) return g.os_path_finalize_join(*args)
#@+node:bob.20080107154936.3: *3* onCreate
[docs]def onCreate (tag, keys): """ Handle 'after-create-leo-frame' hooks by creating a plugin controller for the commander issuing the hook. """ c = keys.get('c') if c: pluginController(c)
#@+node:bob.20080107154936.4: *3* createExportMenus
[docs]def createExportMenus (tag,keywords): """Create menu items in File -> Export menu. Menu's will not be created if the following appears in an @setting tree:: @bool leo_to_html_no_menus = True This is so that the user can use @menu to decide which commands will appear in the menu and where. """ # pylint: disable=undefined-variable # c *is* defined. c = keywords.get("c") if c.config.getBool('leo_to_html_no_menus'): return for item, cmd in ( ('Show Node as HTML', 'show-html-node'), ('Show Outline as HTML', 'show-html'), ('Save Node as HTML', 'export-html-node'), ('Save Outline as HTML', 'export-html'), ): c.frame.menu.insert('Export...', 3, label = item, command = lambda c = c, cmd=cmd: c.k.simulateCommand(cmd) )
#@+node:bob.20080107154757: ** class pluginController
[docs]class pluginController(object): """A per commander plugin controller to create and handle minibuffer commands that control the plugins functions. """ #@+others #@+node:bob.20080107154757.1: *3* __init__(pluginController, leo_to_html.py) def __init__ (self,c): """ Initialze pluginController by registering minibuffer commands. """ self.c = c # Warning: hook handlers must use keywords.get('c'), NOT self.c. for command in ( 'export-html', 'export-html-bullet', 'export-html-number', 'export-html-head', 'export-html-node', 'export-html-node-bullet', 'export-html-node-number', 'export-html-node-head', 'show-html', 'show-html-bullet', 'show-html-number', 'show-html-head', 'show-html-node', 'show-html-node-bullet', 'show-html-node-number', 'show-html-node-head' ): method = getattr(self, command.replace('-','_')) c.k.registerCommand(command, method) #@+node:bob.20080107154757.3: *3* export_html # EXPORT ALL
[docs] def export_html(self, event=None, bullet=None, show=False, node=False): """Command handler for leo_to_html. See modules docstring for details.""" html = Leo_to_HTML(self.c) html.main(bullet=bullet, show=show, node=node)
[docs] def export_html_bullet(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet='bullet')
[docs] def export_html_number(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet='number')
[docs] def export_html_head(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet='head')
# EXPORT NODE
[docs] def export_html_node(self,event=None, bullet=None,): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet=bullet, node=True)
[docs] def export_html_node_bullet(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html_node(bullet='bullet')
[docs] def export_html_node_number(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html_node(bullet='number')
[docs] def export_html_node_head(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html_node(bullet='head')
# SHOW ALL
[docs] def show_html(self, event=None, bullet=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet=bullet, show=True)
[docs] def show_html_bullet(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html(bullet='bullet')
[docs] def show_html_number(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html(bullet='number')
[docs] def show_html_head(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html(bullet='head')
## SHOW NODE
[docs] def show_html_node(self, event=None, bullet=None): """Command handler for leo_to_html. See modules docstring for details.""" self.export_html(bullet=bullet, show=True, node=True)
[docs] def show_html_node_bullet(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html_node(bullet='bullet')
[docs] def show_html_node_number(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html_node(bullet='number')
[docs] def show_html_node_head(self, event=None): """Command handler for leo_to_html. See modules docstring for details.""" self.show_html_node(bullet='head')
#@-others #@+node:bob.20080107154746: ** class Leo_to_HTML
[docs]class Leo_to_HTML(object): """ This class provides all the functionality of the leo_to_html plugin. See the docstring for the leo_to_html module for details. """ #@+others #@+node:bob.20080107154746.1: *3* __init__ def __init__(self, c=None): """Constructor.""" self.c = c self.basedir = '' self.path = '' self.reportColor = 'turquoise4' self.errorColor = 'red' self.fileColor = 'turquoise4' self.msgPrefix = 'leo_to_html: ' #@+node:bob.20080107154746.2: *3* do_xhtml
[docs] def do_xhtml(self, node=False): """Convert the tree to xhtml. Return the result as a string in self.xhtml. Only the code to represent the tree is generated, not the wraper code to turn it into a file. """ self.xhtml = xhtml = [] if node: root = self.c.p else: root = self.c.rootPosition() if self.bullet_type != 'head': xhtml.append(self.openLevelString) if self.bullet_type == 'head': self.doItemHeadlineTags(root) else: self.doItemBulletList(root) if not node: for pp in root.following_siblings(): if self.bullet_type == 'head': self.doItemHeadlineTags(pp) else: self.doItemBulletList(pp) if self.bullet_type != 'head': xhtml.append(self.closeLevelString) self.xhtml = '\n'.join(xhtml)
#@+node:bob.20080107160008: *4* doItemHeadlineTags
[docs] def doItemHeadlineTags(self, p, level=1): """" Recursivley proccess an outline node into an xhtml list.""" self.doHeadline(p, level) self.doBodyElement(p, level) if p.hasChildren() and self.showSubtree(p): for item in p.children(): self.doItemHeadlineTags(item, level+1)
#@+node:bob.20080107165629: *4* doItemBulletList
[docs] def doItemBulletList(self, p): """" Recursivley proccess an outline node into an xhtml list.""" xhtml = self.xhtml xhtml.append(self.openItemString) self.doHeadline(p) self.doBodyElement(p) if p.hasChildren(): xhtml.append(self.openLevelString) for item in p.children(): self.doItemBulletList(item) xhtml.append(self.closeLevelString) xhtml.append(self.closeItemString)
#@+node:bob.20080107154746.5: *4* doHeadline
[docs] def doHeadline(self, p, level=None): """Append wrapped headstring to output stream.""" headline = safe(p.h).replace(' ', '&nbsp;') if level is None: self.xhtml.append(headline) return h = '%s' % min(level, 6) self.xhtml.append( self.openHeadlineString % h + headline + self.closeHeadlineString % h)
#@+node:bob.20080107154746.6: *4* doBodyElement
[docs] def doBodyElement(self, pp, level=None): """Append wrapped body string to output stream.""" if not self.include_body: return self.xhtml.append( self.openBodyString + '<pre>' + safe(pp.b) + '</pre>' + self.closeBodyString )
#@+node:bob.20080107175336: *4* showSubtree
[docs] def showSubtree(self, p): """Return True if subtree should be shown. subtree should be shown if it is not an @file node or if it is an @file node and flags say it should be shown. """ s = p.h if not self.flagIgnoreFiles or s[:len('@file')] != '@file': return True
#@+node:bob.20080107154746.9: *3* main
[docs] def main(self, bullet=None, show=False, node=False): """Generate the html and write the files. If 'bullet' is not recognized then the value of bullet_type from the the properties file will be used. If 'show' is True then the file will be saved to a temp dir and shown in a browser. """ self.silent = show self.announce_start() self.loadConfig() if bullet in ('bullet', 'number', 'head'): self.bullet_type = bullet self.setup() self.do_xhtml(node) self.applyTemplate() if show: self.show() else: self.writeall() self.announce_end()
#@+node:bob.20080109063110.7: *3* announce
[docs] def announce(self, msg, prefix=None, color=None, silent=None): """Print a message if flags allow.""" if silent is None: silent = self.silent if silent: return g.es('%s%s' % (prefix or self.msgPrefix, msg), color=color or self.reportColor)
[docs] def announce_start(self, msg='running ...', prefix=None, color=None): self.announce(msg, prefix, color)
[docs] def announce_end(self, msg='done', prefix=None, color=None): self.announce(msg, prefix, color)
[docs] def announce_fail(self, msg='failed', prefix=None, color=None): self.announce(msg, prefix, color= color or self.errorColor, silent=False)
#@+node:bob.20080107154746.11: *3* loadConfig
[docs] def loadConfig(self): """Load configuration from a .ini file.""" def config(s): ss = self.c.config.getString("leo_to_html_%s"%s) if ss is None: s = configParser.get("Main", s) else: s = ss if not s: s = '' return s.strip() def flag(s): ss = config(s) if ss: return ss.lower()[0] in ('y', 't', '1') fileName = abspath(g.app.loadDir,"..","plugins","leo_to_html.ini") configParser = ConfigParser.ConfigParser() configParser.read(fileName) self.flagIgnoreFiles = flag("flagIgnoreFiles") self.include_body = not flag("flagJustHeadlines") self.basedir = config("exportPath") # "/" self.browser_command = config("browser_command").strip() self.use_xhtml = flag("use_xhtml") if self.use_xhtml: self.template = self.getXHTMLTemplate() else: self.template = self.getPlainTemplate() self.bullet_type = config( "bullet_type").lower() if self.bullet_type not in ('bullet', 'number', 'head'): self.bulletType = 'number'
#@+node:bob.20080109063110.8: *3* setup
[docs] def setup(self): """Set various parameters.""" self.openItemString = '<li>' self.closeItemString = '</li>' self.openBodyString = '<div>' self.closeBodyString = '</div>' self.openHeadlineString = '' self.closeHeadlineString = '' if self.bullet_type == 'head': self.openHeadlineString = '<h%s>' self.closeHeadlineString = '</h%s>' self.openBodyString = '<blockquote>' self.closeBodyString = '</blockquote>' else: if self.bullet_type == 'number': self.openLevelString = '<ol>' self.closeLevelString = '</ol>' else: self.openLevelString = '<ul>' self.closeLevelString = '</ul>' self.openBlockquoteString = '<div>' self.closeBlockquoteString = '</div>' myFileName = self.c.frame.shortFileName() # Get current outline filename if not myFileName: myFileName = 'untitiled' self.title = myFileName if myFileName[-4:].lower() == '.leo': myFileName = myFileName[:-4] # Remove .leo suffix self.myFileName = myFileName + '.html'
#@+node:bob.20080107154746.10: *3* applyTemplate
[docs] def applyTemplate(self, template=None): """ Fit self.xhtml and self.title into an (x)html template. Plaace the result in self.xhtml. The template string in self.template should have too %s place holders. The first for the title the second for the body. """ xhtml = self.xhtml if template is None: template = self.template self.xhtml = template%( self.title, xhtml )
#@+node:bob.20080109063110.9: *3* show
[docs] def show(self): """ Convert the outline to xhtml and display the results in a browser. If browser_command is set, this command will be used to launch the browser. If it is not set, or if the command fails, the default browser will be used. Setting browser_command to a bad command will slow down browser launch. """ tempdir = g.os_path_finalize_join(tempfile.gettempdir(),'leo_show') if not g.os_path_exists(tempdir): os.mkdir(tempdir) filename = g.sanitize_filename(self.myFileName) filepath = g.os_path_finalize_join(tempdir, filename + '.html') self.write(filepath, self.xhtml, basedir='', path='') url = "file://%s" % filepath msg = '' if self.browser_command: g.trace(self.browser_command) try: subprocess.Popen([self.browser_command, url]) return True except Exception: msg = 'can\'t open browser using \n %s\n'%self.browser_command + \ 'Using default browser instead.' if msg: self.announce_fail(msg) webbrowser.open(url)
#@+node:bob.20080107171331: *3* writeall
[docs] def writeall(self): """Write all the files""" self.write(self.myFileName, self.xhtml)
#@+node:bob.20080107154746.13: *3* write
[docs] def write(self, name, data, basedir=None, path=None): """Write a single file. The `name` can be a file name or a ralative path which will be added to basedir and path to create a full path for the file to be written. If basedir is None self.basedir will be used and if path is none self.path will be used. """ if basedir is None: basedir = self.basedir if path is None: path = self.path filepath = abspath(basedir,path,name) try: f = open(filepath, 'wb') except Exception: g.error('can not open: %s' % (filepath)) g.es_exception(full=False,c=self.c) return False try: try: f.write(data.encode('utf-8')) self.announce('output file: %s' % (filepath), color=self.fileColor) ok = True except Exception: g.error('write failed: %s' % (filepath)) g.es_exception(full=False,c=self.c) ok = False finally: f.close() return ok
#@+node:bob.20080107175154: *3* getXHTMLTemplate
[docs] def getXHTMLTemplate(self): """Returns a string containing a template for the outline page. The string should have positions in order, for: title and body text. """ return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title> %s </title> </head> <body> %s </body></html> """
#@+node:bob.20080107175336.1: *3* getPlainTemplate
[docs] def getPlainTemplate(self): """Returns a string containing a template for the outline page. The string should have positions in order, for: title and body text. """ return """<html> <head> <title> %s </title> </head> <body> %s </body></html> """
#@-others #@-others #@-leo