#@+leo-ver=4-thin
#@+node:danr7.20060902215215.1:@thin leo_to_html.py
#@@language python
#@@tabwidth -4
#@<< docstring >>
#@+node:danr7.20060902215215.2:<< docstring >>
'''
leo_to_html
===========
**Converts a leo outline to an html web page.**
.. contents::
Introduction
~~~~~~~~~~~~
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
~~~~~~~~~~
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.
'''
#@-node:danr7.20060902215215.2:<< docstring >>
#@nl
#@<< 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.
#
#
#
#
#@-at
#@-node:danr7.20060902215215.3:<< version history >>
#@nl
#@<< imports >>
#@+node:danr7.20060902215215.4:<< imports >>
import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins
if g.isPython3:
import configparser as ConfigParser
else:
import ConfigParser
import webbrowser
import tempfile
import os
#@-node:danr7.20060902215215.4:<< imports >>
#@nl
__version__ = '2.3'
#@+others
#@+node:bob.20080107154936:module level functions
#@+node:bob.20080107154936.1:init
def init ():
"""Initialize and register plugin.
Hooks create-optional-menus and after-create-leo-frame.
"""
leoPlugins.registerHandler("create-optional-menus",createExportMenus)
leoPlugins.registerHandler('after-create-leo-frame', onCreate)
g.plugin_signon(__name__)
# I think this should be ok for unit testing.
return True
#@-node:bob.20080107154936.1:init
#@+node:bob.20080107154936.2:safe
def safe(s):
"""Convert special characters to html entities."""
return s.replace('&', '&').replace('<', '<').replace('>', '>')
#@-node:bob.20080107154936.2:safe
#@+node:bob.20080110210953:abspath
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.20080110210953:abspath
#@+node:bob.20080107154936.3:onCreate
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 not c: return
thePluginController = pluginController(c)
#@-node:bob.20080107154936.3:onCreate
#@+node:bob.20080107154936.4:createExportMenus
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.
"""
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.20080107154936.4:createExportMenus
#@-node:bob.20080107154936:module level functions
#@+node:bob.20080107154757:class pluginController
class pluginController:
"""A per commander plugin controller to create and handle
minibuffer commands that control the plugins functions.
"""
#@ @+others
#@+node:bob.20080107154757.1:__init__
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, shortcut=None, func=method)
#@-node:bob.20080107154757.1:__init__
#@+node:bob.20080107154757.3:export_html
# EXPORT ALL
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)
def export_html_bullet(self, event=None):
"""Command handler for leo_to_html. See modules docstring for details."""
self.export_html(bullet='bullet')
def export_html_number(self, event=None):
"""Command handler for leo_to_html. See modules docstring for details."""
self.export_html(bullet='number')
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
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)
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')
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')
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
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)
def show_html_bullet(self, event=None):
"""Command handler for leo_to_html. See modules docstring for details."""
self.show_html(bullet='bullet')
def show_html_number(self, event=None):
"""Command handler for leo_to_html. See modules docstring for details."""
self.show_html(bullet='number')
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
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)
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')
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')
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')
#@-node:bob.20080107154757.3:export_html
#@-others
#@nonl
#@-node:bob.20080107154757:class pluginController
#@+node:bob.20080107154746:class Leo_to_HTML
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:__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.1:__init__
#@+node:bob.20080107154746.2:do_xhtml
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 node:
if self.bullet_type == 'head':
self.doItemHeadlineTags(root)
else:
self.doItemBulletList(root)
else:
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:doItemHeadlineTags
def doItemHeadlineTags(self, p, level=1):
"""" Recursivley proccess an outline node into an xhtml list."""
xhtml = self.xhtml
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.20080107160008:doItemHeadlineTags
#@+node:bob.20080107165629:doItemBulletList
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.20080107165629:doItemBulletList
#@+node:bob.20080107154746.5:doHeadline
def doHeadline(self, p, level=None):
"""Append wrapped headstring to output stream."""
headline = safe(p.h).replace(' ', ' ')
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.5:doHeadline
#@+node:bob.20080107154746.6:doBodyElement
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.20080107154746.6:doBodyElement
#@+node:bob.20080107175336:showSubtree
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.20080107175336:showSubtree
#@-node:bob.20080107154746.2:do_xhtml
#@+node:bob.20080107154746.9:main
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.20080107154746.9:main
#@+node:bob.20080109063110.7:announce
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)
def announce_start(self, msg='running ...', prefix=None, color=None):
self.announce(msg, prefix, color)
def announce_end(self, msg='done', prefix=None, color=None):
self.announce(msg, prefix, color)
def announce_fail(self, msg='failed', prefix=None, color=None):
self.announce(msg, prefix, color= color or self.errorColor, silent=False)
#@-node:bob.20080109063110.7:announce
#@+node:bob.20080107154746.11:loadConfig
def loadConfig(self):
"""Load configuration from a .ini file."""
def config(s):
s = configParser.get("Main", s)
if not s: s = ''
return s.strip()
def flag(s):
ss = config(s)
if ss: return ss.lower()[0] in ('y', 't', '1')
#g.trace(g.app.loadDir,"..","plugins","leo_to_html.ini")
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.20080107154746.11:loadConfig
#@+node:bob.20080109063110.8:setup
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.20080109063110.8:setup
#@+node:bob.20080107154746.10:applyTemplate
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.20080107154746.10:applyTemplate
#@+node:bob.20080109063110.9:show
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:
import subprocess
except:
msg = 'cant import subprocess'
if subprocess:
try:
subprocess.Popen([self.browser_command, url])
return True
except:
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.20080109063110.9:show
#@+node:bob.20080107171331:writeall
def writeall(self):
"""Write all the files"""
self.write(self.myFileName, self.xhtml)
#@-node:bob.20080107171331:writeall
#@+node:bob.20080107154746.13:write
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)
# g.trace('basedir',basedir,'path',path,'name',name)
try:
f = open(filepath, 'wb')
ok = True
except IOError:
ok = False
if ok:
try:
try:
f.write(data.encode('utf-8'))
finally:
f.close()
except IOError:
ok = False
if ok:
self.announce('output file: %s' % filepath, color=self.fileColor)
return True
self.announce_fail('failed writing to %s' % filepath)
return False
#@-node:bob.20080107154746.13:write
#@+node:bob.20080107175154:getXHTMLTemplate
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.20080107175154:getXHTMLTemplate
#@+node:bob.20080107175336.1:getPlainTemplate
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>
"""
#@-node:bob.20080107175336.1:getPlainTemplate
#@-others
#@-node:bob.20080107154746:class Leo_to_HTML
#@-others
#@nonl
#@-node:danr7.20060902215215.1:@thin leo_to_html.py
#@-leo
|