#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2002 Gary Shao
# Copyright (C) 2007 Brian G. Matherly
# Copyright (C) 2009 Benny Malengier
# Copyright (C) 2009 Gary Burton
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#-------------------------------------------------------------------------
#
# standard python modules
#
#-------------------------------------------------------------------------
import os
from xml.sax.saxutils import escape
[docs]def escxml(string):
"""
Escapes XML special characters.
"""
return escape(string, { '"' : '"' } )
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from .paragraphstyle import ParagraphStyle
from .fontstyle import FontStyle
from .tablestyle import TableStyle, TableCellStyle
from .graphicstyle import GraphicsStyle
from gramps.gen.const import GRAMPS_LOCALE as glocale
#-------------------------------------------------------------------------
#
# set up logging
#
#-------------------------------------------------------------------------
import logging
log = logging.getLogger(".stylesheet")
#-------------------------------------------------------------------------
#
# SAX interface
#
#-------------------------------------------------------------------------
try:
from xml.sax import make_parser, handler, SAXParseException
except ImportError:
from _xmlplus.sax import make_parser, handler, SAXParseException
#------------------------------------------------------------------------
#
# cnv2color
#
#------------------------------------------------------------------------
[docs]def cnv2color(text):
"""
converts a hex value in the form of #XXXXXX into a tuple of integers
representing the RGB values
"""
return (int(text[1:3], 16), int(text[3:5], 16), int(text[5:7], 16))
#------------------------------------------------------------------------
#
# StyleSheetList
#
#------------------------------------------------------------------------
[docs]class StyleSheetList(object):
"""
Interface into the user's defined style sheets. Each StyleSheetList
has a predefined default style specified by the report. Additional
styles are loaded from a specified XML file if it exists.
"""
def __init__(self, filename, defstyle):
"""
Create a new StyleSheetList from the specified default style and
any other styles that may be defined in the specified file.
file - XML file that contains style definitions
defstyle - default style
"""
defstyle.set_name('default')
self.map = { "default" : defstyle }
self.__file = filename
self.parse()
[docs] def delete_style_sheet(self, name):
"""
Remove a style from the list. Since each style must have a
unique name, the name is used to delete the stylesheet.
name - Name of the style to delete
"""
del self.map[name]
[docs] def get_style_sheet_map(self):
"""
Return the map of names to styles.
"""
return self.map
[docs] def get_style_sheet(self, name):
"""
Return the StyleSheet associated with the name
name - name associated with the desired StyleSheet.
"""
return self.map[name]
[docs] def get_style_names(self):
"Return a list of all the style names in the StyleSheetList"
return list(self.map.keys())
[docs] def set_style_sheet(self, name, style):
"""
Add or replaces a StyleSheet in the StyleSheetList. The
default style may not be replaced.
name - name associated with the StyleSheet to add or replace.
style - definition of the StyleSheet
"""
style.set_name(name)
if name != "default":
self.map[name] = style
[docs] def save(self):
"""
Saves the current StyleSheet definitions to the associated file.
"""
xml_file = open(self.__file, "w")
xml_file.write('<?xml version="1.0" encoding="utf-8"?>\n')
xml_file.write('<stylelist>\n')
for name in sorted(self.map.keys()): # enable diff of archived copies
if name == "default":
continue
sheet = self.map[name]
xml_file.write('<sheet name="%s">\n' % escxml(name))
for p_name in sheet.get_paragraph_style_names():
# Get variables for substitutions
para = sheet.get_paragraph_style(p_name)
font = para.get_font()
rmargin = float(para.get_right_margin())
lmargin = float(para.get_left_margin())
findent = float(para.get_first_indent())
tmargin = float(para.get_top_margin())
bmargin = float(para.get_bottom_margin())
padding = float(para.get_padding())
bg_color = para.get_background_color()
# Write out style definition
xml_file.write(
'<style name="%s">\n' % escxml(p_name) +
'<font face="%d" ' % font.get_type_face() +
'size="%d" ' % font.get_size() +
'italic="%d" ' % font.get_italic() +
'bold="%d" ' % font.get_bold() +
'underline="%d" ' % font.get_underline() +
'color="#%02x%02x%02x" ' % font.get_color() +
'/>\n' +
'<para ' +
'description="%s" ' %
escxml(para.get_description()) +
'rmargin="%.3f" ' % rmargin +
'lmargin="%.3f" ' % lmargin +
'first="%.3f" ' % findent +
'tmargin="%.3f" ' % tmargin +
'bmargin="%.3f" ' % bmargin +
'pad="%.3f" ' % padding +
'bgcolor="#%02x%02x%02x" ' % bg_color +
'level="%d" ' % para.get_header_level() +
'align="%d" ' % para.get_alignment() +
'tborder="%d" ' % para.get_top_border() +
'lborder="%d" ' % para.get_left_border() +
'rborder="%d" ' % para.get_right_border() +
'bborder="%d" ' % para.get_bottom_border() +
'/>\n' +
'</style>\n'
)
xml_file.write('</sheet>\n')
xml_file.write('</stylelist>\n')
xml_file.close()
[docs] def parse(self):
"""
Loads the StyleSheets from the associated file, if it exists.
"""
try:
if os.path.isfile(self.__file):
parser = make_parser()
parser.setContentHandler(SheetParser(self))
the_file = open(self.__file)
parser.parse(the_file)
the_file.close()
except (IOError, OSError, SAXParseException):
pass
#------------------------------------------------------------------------
#
# StyleSheet
#
#------------------------------------------------------------------------
[docs]class StyleSheet(object):
"""
A collection of named paragraph styles.
"""
def __init__(self, obj=None):
"""
Create a new empty StyleSheet.
:param obj: if not None, creates the StyleSheet from the values in
obj, instead of creating an empty StyleSheet
"""
self.para_styles = {}
self.draw_styles = {}
self.table_styles = {}
self.cell_styles = {}
self.name = ""
if obj is not None:
for style_name, style in obj.para_styles.items():
self.para_styles[style_name] = ParagraphStyle(style)
for style_name, style in obj.draw_styles.items():
self.draw_styles[style_name] = GraphicsStyle(style)
for style_name, style in obj.table_styles.items():
self.table_styles[style_name] = TableStyle(style)
for style_name, style in obj.cell_styles.items():
self.cell_styles[style_name] = TableCellStyle(style)
[docs] def set_name(self, name):
"""
Set the name of the StyleSheet
:param name: The name to be given to the StyleSheet
"""
self.name = name
[docs] def get_name(self):
"""
Return the name of the StyleSheet
"""
return self.name
[docs] def clear(self):
"Remove all styles from the StyleSheet"
self.para_styles = {}
self.draw_styles = {}
self.table_styles = {}
self.cell_styles = {}
[docs] def is_empty(self):
"Checks if any styles are defined"
style_count = len(self.para_styles) + \
len(self.draw_styles) + \
len(self.table_styles) + \
len(self.cell_styles)
if style_count > 0:
return False
else:
return True
[docs] def add_paragraph_style(self, name, style):
"""
Add a paragraph style to the style sheet.
:param name: The name of the :class:`.ParagraphStyle`
:param style: :class:`.ParagraphStyle` instance to be added.
"""
self.para_styles[name] = ParagraphStyle(style)
[docs] def get_paragraph_style(self, name):
"""
Return the :class:`.ParagraphStyle` associated with the name
:param name: name of the :class:`.ParagraphStyle` that is wanted
"""
return ParagraphStyle(self.para_styles[name])
[docs] def get_paragraph_style_names(self):
"Return the list of paragraph names in the StyleSheet"
return list(self.para_styles.keys())
[docs] def add_draw_style(self, name, style):
"""
Add a draw style to the style sheet.
:param name: The name of the :class:`.GraphicsStyle`
:param style: :class:`.GraphicsStyle` instance to be added.
"""
self.draw_styles[name] = GraphicsStyle(style)
[docs] def get_draw_style(self, name):
"""
Return the :class:`.GraphicsStyle` associated with the name
:param name: name of the :class:`.GraphicsStyle` that is wanted
"""
return GraphicsStyle(self.draw_styles[name])
[docs] def get_draw_style_names(self):
"Return the list of draw style names in the StyleSheet"
return list(self.draw_styles.keys())
[docs] def add_table_style(self, name, style):
"""
Add a table style to the style sheet.
:param name: The name of the :class:`.TableStyle`
:param style: :class:`.TableStyle` instance to be added.
"""
self.table_styles[name] = TableStyle(style)
[docs] def get_table_style(self, name):
"""
Return the :class:`.TableStyle` associated with the name
:param name: name of the :class:`.TableStyle` that is wanted
"""
return TableStyle(self.table_styles[name])
[docs] def get_table_style_names(self):
"Return the list of table style names in the StyleSheet"
return list(self.table_styles.keys())
[docs] def add_cell_style(self, name, style):
"""
Add a cell style to the style sheet.
:param name: The name of the :class:`.TableCellStyle`
:param style: :class:`.TableCellStyle` instance to be added.
"""
self.cell_styles[name] = TableCellStyle(style)
[docs] def get_cell_style(self, name):
"""
Return the :class:`.TableCellStyle` associated with the name
:param name: name of the :class:`.TableCellStyle` that is wanted
"""
return TableCellStyle(self.cell_styles[name])
[docs] def get_cell_style_names(self):
"Return the list of cell style names in the StyleSheet"
return list(self.cell_styles.keys())
#-------------------------------------------------------------------------
#
# SheetParser
#
#-------------------------------------------------------------------------
[docs]class SheetParser(handler.ContentHandler):
"""
SAX parsing class for the StyleSheetList XML file.
"""
def __init__(self, sheetlist):
"""
Create a SheetParser class that populates the passed StyleSheetList
class.
sheetlist - :class:`StyleSheetList` instance to be loaded from the file.
"""
handler.ContentHandler.__init__(self)
self.sheetlist = sheetlist
self.f = None
self.p = None
self.s = None
self.sname = None
self.pname = None
[docs] def startElement(self, tag, attrs):
"""
Overridden class that handles the start of a XML element
"""
if tag == "sheet":
self.s = StyleSheet(self.sheetlist.map["default"])
self.sname = attrs['name']
elif tag == "font":
self.f = FontStyle()
self.f.set_type_face(int(attrs['face']))
self.f.set_size(int(attrs['size']))
self.f.set_italic(int(attrs['italic']))
self.f.set_bold(int(attrs['bold']))
self.f.set_underline(int(attrs['underline']))
self.f.set_color(cnv2color(attrs['color']))
elif tag == "para":
if 'description' in attrs:
self.p.set_description(attrs['description'])
self.p.set_right_margin(glocale.float(attrs['rmargin']))
self.p.set_left_margin(glocale.float(attrs['lmargin']))
self.p.set_first_indent(glocale.float(attrs['first']))
try:
# This is needed to read older style files
# lacking tmargin and bmargin
self.p.set_top_margin(glocale.float(attrs['tmargin']))
self.p.set_bottom_margin(glocale.float(attrs['bmargin']))
except KeyError:
pass
self.p.set_padding(glocale.float(attrs['pad']))
self.p.set_alignment(int(attrs['align']))
self.p.set_right_border(int(attrs['rborder']))
self.p.set_header_level(int(attrs['level']))
self.p.set_left_border(int(attrs['lborder']))
self.p.set_top_border(int(attrs['tborder']))
self.p.set_bottom_border(int(attrs['bborder']))
self.p.set_background_color(cnv2color(attrs['bgcolor']))
elif tag == "style":
self.p = ParagraphStyle()
self.pname = attrs['name']
[docs] def endElement(self, tag):
"Overridden class that handles the start of a XML element"
if tag == "style":
self.p.set_font(self.f)
self.s.add_paragraph_style(self.pname, self.p)
elif tag == "sheet":
self.sheetlist.set_style_sheet(self.sname, self.s)