Source code for gramps.gen.plug.docgen.stylesheet

#
# 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
# Copyright (C) 2014       Nick Hall
# Copyright (C) 2017       Paul Franklin
#
# 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 #------------------------------------------------------------------------- # # set up logging # #------------------------------------------------------------------------- import logging log = logging.getLogger(".stylesheet") #------------------------------------------------------------------------- # # SAX interface # #------------------------------------------------------------------------- from xml.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: """ 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. """ with open(self.__file, "w") as xml_file: 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 ones if name == "default": continue sheet = self.map[name] xml_file.write(' <sheet name="%s">\n' % escxml(name)) for p_name in sorted(sheet.get_paragraph_style_names()): self.write_paragraph_style(xml_file, sheet, p_name) for t_name in sorted(sheet.get_table_style_names()): self.write_table_style(xml_file, sheet, t_name) for c_name in sorted(sheet.get_cell_style_names()): self.write_cell_style(xml_file, sheet, c_name) for g_name in sorted(sheet.get_draw_style_names()): self.write_graphics_style(xml_file, sheet, g_name) xml_file.write(' </sheet>\n') xml_file.write('</stylelist>\n')
[docs] def write_paragraph_style(self, xml_file, sheet, p_name): para = sheet.get_paragraph_style(p_name) # Get variables for substitutions 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' )
[docs] def write_table_style(self, xml_file, sheet, t_name): t_style = sheet.get_table_style(t_name) # Write out style definition xml_file.write( ' <style name="%s">\n' % escxml(t_name) + ' <table ' + 'description="%s" ' % escxml(t_style.get_description()) + 'width="%d" ' % t_style.get_width() + 'columns="%d"' % t_style.get_columns() + '>\n') for col in range(t_style.get_columns()): column_width = t_style.get_column_width(col) xml_file.write(' <column width="%d" />\n' % column_width) xml_file.write(' </table>\n') xml_file.write(' </style>\n')
[docs] def write_cell_style(self, xml_file, sheet, c_name): cell = sheet.get_cell_style(c_name) # Write out style definition xml_file.write( ' <style name="%s">\n' % escxml(c_name) + ' <cell ' + 'description="%s" ' % escxml(cell.get_description()) + 'lborder="%d" ' % cell.get_left_border() + 'rborder="%d" ' % cell.get_right_border() + 'tborder="%d" ' % cell.get_top_border() + 'bborder="%d" ' % cell.get_bottom_border() + 'pad="%.3f" ' % cell.get_padding() + '/>\n' + ' </style>\n' )
[docs] def write_graphics_style(self, xml_file, sheet, g_name): draw = sheet.get_draw_style(g_name) # Write out style definition xml_file.write( ' <style name="%s">\n' % escxml(g_name) + ' <draw ' + 'description="%s" ' % escxml(draw.get_description()) + 'para="%s" ' % draw.get_paragraph_style() + 'width="%.3f" ' % draw.get_line_width() + 'style="%d" ' % draw.get_line_style() + 'color="#%02x%02x%02x" ' % draw.get_color() + 'fillcolor="#%02x%02x%02x" ' % draw.get_fill_color() + 'shadow="%d" ' % draw.get_shadow() + 'space="%.3f" ' % draw.get_shadow_space() + '/>\n' + ' </style>\n' )
[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)) with open(self.__file) as the_file: parser.parse(the_file) except (IOError, OSError, SAXParseException): pass
#------------------------------------------------------------------------ # # StyleSheet # #------------------------------------------------------------------------
[docs] class StyleSheet: """ 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.t = None self.columns_widths = [] self.sheet_name = None self.style_name = 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.sheet_name = 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": self.p = ParagraphStyle() if 'description' in attrs: self.p.set_description(attrs['description']) self.p.set_right_margin(float(attrs['rmargin'])) self.p.set_left_margin(float(attrs['lmargin'])) self.p.set_first_indent(float(attrs['first'])) try: # This is needed to read older style files # lacking tmargin and bmargin self.p.set_top_margin(float(attrs['tmargin'])) self.p.set_bottom_margin(float(attrs['bmargin'])) except KeyError: pass self.p.set_padding(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'])) self.p.set_font(self.f) elif tag == "style": self.style_name = attrs['name'] elif tag == "table": self.t = TableStyle() if 'description' in attrs: self.t.set_description(attrs['description']) self.t.set_width(int(attrs['width'])) self.t.set_columns(int(attrs['columns'])) self.column_widths = [] elif tag == "column": self.column_widths.append(int(attrs['width'])) elif tag == "cell": self.c = TableCellStyle() if 'description' in attrs: self.c.set_description(attrs['description']) self.c.set_left_border(int(attrs['lborder'])) self.c.set_right_border(int(attrs['rborder'])) self.c.set_top_border(int(attrs['tborder'])) self.c.set_bottom_border(int(attrs['bborder'])) self.c.set_padding(float(attrs['pad'])) elif tag == "draw": self.g = GraphicsStyle() if 'description' in attrs: self.g.set_description(attrs['description']) self.g.set_paragraph_style(attrs['para']) self.g.set_line_width(float(attrs['width'])) self.g.set_line_style(int(attrs['style'])) self.g.set_color(cnv2color(attrs['color'])) self.g.set_fill_color(cnv2color(attrs['fillcolor'])) self.g.set_shadow(int(attrs['shadow']), float(attrs['space']))
[docs] def endElement(self, tag): "Overridden class that handles the end of a XML element" if tag == "sheet": self.sheetlist.set_style_sheet(self.sheet_name, self.s) elif tag == "para": self.s.add_paragraph_style(self.style_name, self.p) elif tag == "table": self.t.set_column_widths(self.column_widths) self.s.add_table_style(self.style_name, self.t) elif tag == "cell": self.s.add_cell_style(self.style_name, self.c) elif tag == "draw": self.s.add_draw_style(self.style_name, self.g)