gramps/src/plugins/docgen/HtmlDoc.py

548 lines
19 KiB
Python
Raw Normal View History

2002-10-20 19:55:16 +05:30
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2009 Benny Malengier <benny.malengier@gramps-project.org>
2002-10-20 19:55:16 +05:30
#
# 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
#
# $Id:HtmlDoc.py 9912 2008-01-22 09:17:46Z acraphae $
"""
Report output generator for html documents, based on Html and HtmlBackend
"""
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
2002-10-20 19:55:16 +05:30
import os
import shutil
2002-10-20 19:55:16 +05:30
import time
from gettext import gettext as _
2002-10-20 19:55:16 +05:30
2005-12-06 12:08:09 +05:30
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gen.plug import PluginManager, DocGenPlugin
2002-10-20 19:55:16 +05:30
import ImgManip
import const
from gen.plug.docgen import BaseDoc, TextDoc, FONT_SANS_SERIF
from libhtmlbackend import HtmlBackend
from libhtml import Html
from QuestionDialog import WarningDialog
2002-10-20 19:55:16 +05:30
#------------------------------------------------------------------------
#
# Set up logging
2002-10-20 19:55:16 +05:30
#
#------------------------------------------------------------------------
import logging
LOG = logging.getLogger(".htmldoc")
2002-10-20 19:55:16 +05:30
_HTMLSCREEN = 'grampshtml.css'
2002-10-20 19:55:16 +05:30
#------------------------------------------------------------------------
#
# HtmlDoc
#
#------------------------------------------------------------------------
class HtmlDoc(BaseDoc, TextDoc):
"""Implementation of the BaseDoc and TextDoc gen.plug.docgen api for the
creation of Html files. This is achieved by writing on a HtmlBackend
object
div id's defined here:
id="grampstextdoc" : the entire text report
id="grampsheading" : a small defined heading, but not h1 to h6 !
id="grampsstylednote" : start of part with a styled note, divided in
paragraphs
id="grampsnote" : start of part with a note. This id is normally not
used
"""
2002-10-20 19:55:16 +05:30
def __init__(self, styles, paper_style):
BaseDoc.__init__(self, styles, None)
self.style_declaration = ''
self.htmllist = []
self._backend = None
self.css_filename = None
self.warn_dir = True
self._col = 0
self._tbl = None
self._empty = 1
self.title = ''
self.__title_written = -1 # -1 = not written, 0 = writing, 1 = written
2002-10-20 19:55:16 +05:30
def set_css_filename(self, css_filename):
"""
Set the css file to use. The path must be included.
Note: DocReportDialog sets this for html doc
"""
self.css_filename = css_filename
def open(self, filename):
"""
Overwrite base method
"""
self._backend = HtmlBackend(filename)
self._backend.open()
self.htmllist += [self._backend.html_body]
#start a gramps report
self.htmllist += [Html('div', id="grampstextdoc")]
2002-10-20 19:55:16 +05:30
self.build_header()
2002-10-20 19:55:16 +05:30
def build_header(self):
"""
Build up the header of the html file over the defaults of Html()
"""
# add additional meta tags and stylesheet links to head section
# create additional meta tags
_meta1 = 'name="generator" content="%s %s %s"' % (const.PROGRAM_NAME,
const.VERSION, const.URL_HOMEPAGE)
meta = Html('meta', attr = _meta1)
#set styles of the report as inline css
self.build_style_declaration()
self._backend.html_header += self.style_declaration
# GRAMPS favicon en css
fname1 = '/'.join([self._backend.datadir(), 'favicon.ico'])
fname2 = '/'.join([self._backend.datadir(), _HTMLSCREEN])
# links for GRAMPS favicon and stylesheets
links = Html('link', rel='shortcut icon', href=fname1,
type='image/x-icon') + (
Html('link', rel='stylesheet', href=fname2, type='text/css',
media='screen', indent=False)
)
self._backend.html_header += (meta, links)
2002-10-20 19:55:16 +05:30
def build_style_declaration(self):
"""
Convert the styles of the report into inline css for the html doc
"""
styles = self.get_style_sheet()
2002-10-20 19:55:16 +05:30
text = ['<style type="text/css">\n<!--']
for sname in styles.get_cell_style_names():
style = styles.get_cell_style(sname)
2002-10-20 19:55:16 +05:30
pad = "%.3fcm" % style.get_padding()
top = bottom = left = right = 'none'
if style.get_top_border():
top = 'thin solid #000000'
if style.get_bottom_border():
bottom = 'thin solid #000000'
if style.get_left_border():
left = 'thin solid #000000'
2002-10-20 19:55:16 +05:30
if style.get_right_border():
right = 'thin solid #000000'
text.append('#grampstextdoc .%s {\n'
2002-10-20 19:55:16 +05:30
'\tpadding: %s %s %s %s;\n'
'\tborder-top:%s; border-bottom:%s;\n'
'\tborder-left:%s; border-right:%s;\n}'
% (sname, pad, pad, pad, pad, top, bottom, left, right))
2002-10-20 19:55:16 +05:30
for style_name in styles.get_paragraph_style_names():
style = styles.get_paragraph_style(style_name)
2002-10-20 19:55:16 +05:30
font = style.get_font()
font_size = font.get_size()
#font_color = '#%02x%02x%02x' % font.get_color()
2002-10-20 19:55:16 +05:30
align = style.get_alignment_text()
text_indent = "%.2f" % style.get_first_indent()
right_margin = "%.2f" % style.get_right_margin()
left_margin = "%.2f" % style.get_left_margin()
2005-12-06 12:08:09 +05:30
top_margin = "%.2f" % style.get_top_margin()
bottom_margin = "%.2f" % style.get_bottom_margin()
2002-10-20 19:55:16 +05:30
top = bottom = left = right = 'none'
if style.get_top_border():
top = 'thin solid #000000'
if style.get_bottom_border():
bottom = 'thin solid #000000'
if style.get_left_border():
left = 'thin solid #000000'
2002-10-20 19:55:16 +05:30
if style.get_right_border():
right = 'thin solid #000000'
italic = bold = ''
if font.get_italic():
italic = 'font-style:italic; '
if font.get_bold():
bold = 'font-weight:bold; '
#if font.get_type_face() == FONT_SANS_SERIF:
# family = '"Helvetica","Arial","sans-serif"'
#else:
# family = '"Times New Roman","Times","serif"'
# do not allow color, set in base css !
# so no : 'color: %s' % font_color
# so no : 'font-family:%s;' % family
text.append('#grampstextdoc .%s {\n'
'\tfont-size: %dpt;\n'
2002-10-20 19:55:16 +05:30
'\ttext-align: %s; text-indent: %scm;\n'
'\tmargin-right: %scm; margin-left: %scm;\n'
2005-12-06 12:08:09 +05:30
'\tmargin-top: %scm; margin-bottom: %scm;\n'
2002-10-20 19:55:16 +05:30
'\tborder-top:%s; border-bottom:%s;\n'
'\tborder-left:%s; border-right:%s;\n'
'\t%s%s\n}'
% (style_name, font_size,
2002-10-20 19:55:16 +05:30
align, text_indent,
right_margin, left_margin,
2005-12-06 12:08:09 +05:30
top_margin, bottom_margin,
2002-10-20 19:55:16 +05:30
top, bottom, left, right,
italic, bold))
2002-10-20 19:55:16 +05:30
text.append('-->\n</style>')
2005-12-06 12:08:09 +05:30
self.style_declaration = '\n'.join(text)
2002-10-20 19:55:16 +05:30
def close(self):
"""
Overwrite base method
"""
while len(self.htmllist)>1 :
self.__reduce_list()
#now write the actual file
self._backend.close()
self.write_support_files()
2002-10-20 19:55:16 +05:30
if self.open_req:
import Utils
Utils.open_file_with_default_application(self._backend.getf())
2002-10-20 19:55:16 +05:30
def copy_file(self, from_fname, to_fname, to_dir=''):
"""
Copy a file from a source to a (report) destination.
If to_dir is not present, then the destination directory will be created.
2002-10-20 19:55:16 +05:30
Normally 'to_fname' will be just a filename, without directory path.
2002-10-20 19:55:16 +05:30
'to_dir' is the relative path name in the destination root. It will
be prepended before 'to_fname'.
"""
dest = os.path.join(self._backend.datadir(), to_dir, to_fname)
destdir = os.path.dirname(dest)
if not os.path.isdir(destdir):
os.makedirs(destdir)
if from_fname != dest:
shutil.copyfile(from_fname, dest)
elif self.warn_dir:
WarningDialog(
_("Possible destination error") + "\n" +
_("You appear to have set your target directory "
"to a directory used for data storage. This "
"could create problems with file management. "
"It is recommended that you consider using "
"a different directory to store your generated "
"web pages."))
self.warn_dir = False
def write_support_files(self):
"""
Copy support files to the datadir that needs to hold them
"""
#css file
self.copy_file(os.path.join(const.DATA_DIR, self.css_filename),
_HTMLSCREEN)
#favicon
self.copy_file(os.path.join(const.IMAGE_DIR, 'favicon.ico'),
'favicon.ico')
def __reduce_list(self):
"""
Takes the internal list of html objects, and adds the last to the
previous. This closes the upper tag
"""
self.htmllist[-2] += self.htmllist[-1]
self.htmllist.pop()
def __write_text(self, text, mark=None, markup=False):
"""
@param text: text to write.
@param mark: IndexMark to use for indexing (if supported)
@param markup: True if text already contains markup info.
Then text will no longer be escaped
"""
if not markup:
text = self._backend.ESCAPE_FUNC()(text)
if self.__title_written == 0 :
self.title += text
self.htmllist[-1] += text
def __empty_char(self):
"""
Output a non breaking whitespace so as to have browser behave ok on
empty content
"""
self.__write_text('&nbsp;', markup=True)
def write_text(self, text, mark=None):
"""
Overwrite base method
"""
if text != "":
self._empty = 0
self.__write_text(text, mark)
def write_title(self):
"""
Add title field to header
"""
self._backend.html_header += Html('title', self.title,
inline=True, indent=True)
def start_table(self, name, style):
"""
Overwrite base method
"""
styles = self.get_style_sheet()
self._tbl = styles.get_table_style(style)
self.htmllist += [Html('table', width=str(self._tbl.get_width())+'%%',
cellspacing='0')]
2002-10-20 19:55:16 +05:30
def end_table(self):
"""
Overwrite base method
"""
self.write_text('\n')
self.__reduce_list()
2002-10-20 19:55:16 +05:30
def start_row(self):
"""
Overwrite base method
"""
self.htmllist += [Html('tr')]
self._col = 0
2002-10-20 19:55:16 +05:30
def end_row(self):
"""
Overwrite base method
"""
self.write_text('\n')
self.__reduce_list()
2002-10-20 19:55:16 +05:30
def start_cell(self, style_name, span=1):
"""
Overwrite base method
"""
self._empty = 1
#self.f.write('<td valign="top"')
2002-10-20 19:55:16 +05:30
if span > 1:
self.htmllist += [Html('td', colspan=str(span),
class_=style_name)]
self._col += span
2002-10-20 19:55:16 +05:30
else:
self.htmllist += [Html('td', colspan=str(span),
width=str(self._tbl.get_column_width(
self._col))+ '%%',
class_=style_name)]
self._col += 1
2002-10-20 19:55:16 +05:30
def end_cell(self):
"""
Overwrite base method
"""
self.write_text('\n')
self.__reduce_list()
2002-10-20 19:55:16 +05:30
def start_paragraph(self, style_name, leader=None):
"""
Overwrite base method
"""
style_sheet = self.get_style_sheet()
style = style_sheet.get_paragraph_style(style_name)
level = style.get_header_level()
if level == 0:
#a normal paragraph
self.htmllist += [Html('p', class_=style_name)]
elif level == 1:
if self.__title_written == -1 and \
style_name.upper().find('TITLE') != -1:
self.__title_written == 0
self.htmllist += [Html('div', id="header")]
self.htmllist += [Html('h1', id='SiteTitle')]
else:
self.htmllist += [Html('h1', class_=style_name)]
elif 2<= level <= 5:
tag = 'h'+str(level+1)
self.htmllist += [Html(tag, class_=style_name)]
else:
# a low level header
self.htmllist += [Html('div', id='grampsheading',
class_=style_name)]
if leader is not None:
self.write_text(leader+' ')
2002-10-20 19:55:16 +05:30
def end_paragraph(self):
"""
Overwrite base method
"""
self.write_text('\n')
if self._empty == 1:
self.__empty_char()
self._empty = 0
self.__reduce_list()
if self.__title_written == 0:
self.__title_written = 1
#close div statement
self.__reduce_list()
self.write_title()
2002-10-20 19:55:16 +05:30
def start_bold(self):
"""
Overwrite base method
"""
self.htmllist += [Html('strong')]
def end_bold(self):
"""
Overwrite base method
"""
self.__reduce_list()
def start_superscript(self):
"""
Overwrite base method
"""
self.htmllist += [Html('sup')]
def end_superscript(self):
"""
Overwrite base method
"""
self.__reduce_list()
def write_note(self, text, format, style_name):
"""
Overwrite base method
"""
self.htmllist += [Html('div', id='grampsstylednote')]
if format == 1:
#preformatted, retain whitespace.
# User should use write_styled_note for correct behavior, in this
# more basic method we convert all to a monospace character
self.htmllist += [Html('pre', class_=style_name,
style = 'font-family: courier, monospace')]
self.write_text(text)
#end pre element
self.__reduce_list()
elif format == 0:
for line in text.split('\n\n'):
self.start_paragraph(style_name)
self.write_text(line)
self.end_paragraph()
else:
raise NotImplementedError
#end div element
self.__reduce_list()
def write_styled_note(self, styledtext, format, style_name):
"""
Convenience function to write a styledtext to the html doc.
styledtext : assumed a StyledText object to write
format : = 0 : Flowed, = 1 : Preformatted
style_name : name of the style to use for default presentation
"""
text = str(styledtext)
s_tags = styledtext.get_tags()
#FIXME: following split should be regex to match \n\s*\n instead?
markuptext = self._backend.add_markup_from_styled(text, s_tags,
split='\n\n')
self.htmllist += [Html('div', id='grampsstylednote')]
if format == 1:
#preformatted, retain whitespace.
#so use \n\n for paragraph detection
#FIXME: following split should be regex to match \n\s*\n instead?
self.htmllist += [Html('pre')]
for line in markuptext.split('\n\n'):
self.start_paragraph(style_name)
for realline in line.split('\n'):
self.__write_text(realline, markup=True)
self.htmllist[-1] += Html('br')
self.end_paragraph()
#end pre element
self.__reduce_list()
elif format == 0:
#flowed
#FIXME: following split should be regex to match \n\s*\n instead?
for line in markuptext.split('\n\n'):
self.start_paragraph(style_name)
self.__write_text(line, markup=True)
self.end_paragraph()
#end div element
self.__reduce_list()
def add_media_object(self, name, pos, w_cm, h_cm, alt=''):
"""
Overwrite base method
"""
self._empty = 0
size = int(max(w_cm, h_cm) * float(150.0/2.54))
refname = "is%s" % os.path.basename(name)
imdir = self._backend.datadir()
try:
ImgManip.resize_to_jpeg(name, imdir + os.sep + refname, size, size)
except:
LOG.warn(_("Could not create jpeg version of image %(name)s") %
{'name' : name})
return
if pos not in ["right", "left"] :
self.htmllist[-1] += Html('img', src= imdir + os.sep + refname,
border = '0', alt=alt)
else:
self.htmllist[-1] += Html('img', src= imdir + os.sep + refname,
border = '0', alt=alt, align=pos)
2002-10-20 19:55:16 +05:30
def page_break(self):
"""
overwrite base method so page break has no effect
"""
2008-03-05 01:17:52 +05:30
pass
#------------------------------------------------------------------------
#
# register_plugin
#
#------------------------------------------------------------------------
def register_plugin():
"""
Register the document generator with the GRAMPS plugin system.
"""
pmgr = PluginManager.get_instance()
plugin = DocGenPlugin(name = _('HTML'),
description = _("Generates documents in HTML "
"format."),
basedoc = HtmlDoc,
paper = False,
style = True,
extension = "html" )
pmgr.register_plugin(plugin)
register_plugin()