1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2004-2007  Donald N. Allingham
5# Copyright (C) 2008,2011  Gary Burton
6# Copyright (C) 2010       Jakim Friant
7# Copyright (C) 2011-2012  Paul Franklin
8# Copyright (C) 2012       Brian G. Matherly
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24
25# Written by Alex Roitman
26
27"""
28Report option handling, including saving and parsing.
29"""
30
31#-------------------------------------------------------------------------
32#
33# Standard Python modules
34#
35#-------------------------------------------------------------------------
36import os
37import copy
38
39#-------------------------------------------------------------------------
40#
41# SAX interface
42#
43#-------------------------------------------------------------------------
44from xml.sax import make_parser, SAXParseException
45from xml.sax.saxutils import escape
46
47#------------------------------------------------------------------------
48#
49# Set up logging
50#
51#------------------------------------------------------------------------
52import logging
53LOG = logging.getLogger(".plug.report.options")
54
55#-------------------------------------------------------------------------
56#
57# gramps modules
58#
59# (do not import anything from 'gui' as this is in 'gen')
60#
61#-------------------------------------------------------------------------
62from ...const import HOME_DIR, REPORT_OPTIONS
63from ...config import config
64from ..docgen import PAPER_PORTRAIT
65from .. import _options
66from .. import MenuOptions
67from ...utils.cast import get_type_converter
68
69def escxml(word):
70    return escape(word, {'"' : '"'})
71
72#-------------------------------------------------------------------------
73#
74# List of options for a single report
75#
76#-------------------------------------------------------------------------
77class OptionList(_options.OptionList):
78    """
79    Implements a set of options to parse and store for a given report.
80    """
81
82    def __init__(self):
83        _options.OptionList.__init__(self)
84        self.style_name = None
85        self.paper_metric = None
86        self.paper_name = None
87        self.orientation = None
88        self.custom_paper_size = [29.7, 21.0]
89        self.margins = [2.54, 2.54, 2.54, 2.54]
90        self.format_name = None
91        self.css_filename = None
92        self.output = None
93
94    def set_style_name(self, style_name):
95        """
96        Set the style name for the OptionList.
97
98        :param style_name: name of the style to set.
99        :type style_name: str
100        """
101        self.style_name = style_name
102
103    def get_style_name(self):
104        """
105        Return the style name of the OptionList.
106
107        :returns: string representing the style name
108        :rtype: str
109        """
110        return self.style_name
111
112    def set_paper_metric(self, paper_metric):
113        """
114        Set the paper metric for the OptionList.
115
116        :param paper_metric: whether to use metric.
117        :type paper_name: boolean
118        """
119        self.paper_metric = paper_metric
120
121    def get_paper_metric(self):
122        """
123        Return the paper metric of the OptionList.
124
125        :returns: returns whether to use metric
126        :rtype: boolean
127        """
128        return self.paper_metric
129
130    def set_paper_name(self, paper_name):
131        """
132        Set the paper name for the OptionList.
133
134        :param paper_name: name of the paper to set.
135        :type paper_name: str
136        """
137        self.paper_name = paper_name
138
139    def get_paper_name(self):
140        """
141        Return the paper name of the OptionList.
142
143        :returns: returns the paper name
144        :rtype: str
145        """
146        return self.paper_name
147
148    def set_orientation(self, orientation):
149        """
150        Set the orientation for the OptionList.
151
152        :param orientation: orientation to set. Possible values are
153                            PAPER_LANDSCAPE or PAPER_PORTRAIT
154        :type orientation: int
155        """
156        self.orientation = orientation
157
158    def get_orientation(self):
159        """
160        Return the orientation for the OptionList.
161
162        :returns: returns the selected orientation. Valid values are
163                  PAPER_LANDSCAPE or PAPER_PORTRAIT
164        :rtype: int
165        """
166        return self.orientation
167
168    def set_custom_paper_size(self, paper_size):
169        """
170        Set the custom paper size for the OptionList.
171
172        :param paper_size: paper size to set in cm.
173        :type paper_size: [float, float]
174        """
175        self.custom_paper_size = paper_size
176
177    def get_custom_paper_size(self):
178        """
179        Return the custom paper size for the OptionList.
180
181        :returns: returns the custom paper size in cm
182        :rtype: [float, float]
183        """
184        return self.custom_paper_size
185
186    def set_margins(self, margins):
187        """
188        Set the margins for the OptionList.
189
190        :param margins: margins to set. Possible values are floats in cm
191        :type margins: [float, float, float, float]
192        """
193        self.margins = copy.copy(margins)
194
195    def get_margins(self):
196        """
197        Return the margins for the OptionList.
198
199        :returns margins: returns the margins, floats in cm
200        :rtype margins: [float, float, float, float]
201        """
202        return copy.copy(self.margins)
203
204    def set_margin(self, pos, value):
205        """
206        Set a margin for the OptionList.
207
208        :param pos: Position of margin [left, right, top, bottom]
209        :type pos: int
210        :param value: floating point in cm
211        :type value: float
212        """
213        self.margins[pos] = value
214
215    def get_margin(self, pos):
216        """
217        Return a margin for the OptionList.
218
219        :param pos: Position of margin [left, right, top, bottom]
220        :type pos: int
221        :returns: float cm of margin
222        :rtype: float
223        """
224        return self.margins[pos]
225
226    def set_css_filename(self, css_filename):
227        """
228        Set the template name for the OptionList.
229
230        :param template_name: name of the template to set.
231        :type template_name: str
232        """
233        self.css_filename = css_filename
234
235    def get_css_filename(self):
236        """
237        Return the template name of the OptionList.
238
239        :returns: template name
240        :rtype: str
241        """
242        return self.css_filename
243
244    def set_format_name(self, format_name):
245        """
246        Set the format name for the OptionList.
247
248        :param format_name: name of the format to set.
249        :type format_name: str
250        """
251        self.format_name = format_name
252
253    def get_format_name(self):
254        """
255        Return the format name of the OptionList.
256
257        :returns: returns the format name
258        :rtype: str
259        """
260        return self.format_name
261
262    def set_output(self, output):
263        """
264        Set the output for the OptionList.
265
266        :param output: name of the output to set.
267        :type output: str
268        """
269        self.output = output
270
271    def get_output(self):
272        """
273        Return the output of the OptionList.
274
275        :returns: returns the output name
276        :rtype: str
277        """
278        return self.output
279
280#-------------------------------------------------------------------------
281#
282# Collection of option lists
283#
284#-------------------------------------------------------------------------
285class OptionListCollection(_options.OptionListCollection):
286    """
287    Implements a collection of option lists.
288    """
289    def __init__(self, filename):
290        _options.OptionListCollection.__init__(self, filename)
291
292    def init_common(self):
293        # Default values for common options
294        self.default_style_name = "default"
295        self.default_paper_metric = config.get('preferences.paper-metric')
296        self.default_paper_name = config.get('preferences.paper-preference')
297        self.default_orientation = PAPER_PORTRAIT
298        self.default_css_filename = ""
299        self.default_custom_paper_size = [29.7, 21.0]
300        self.default_margins = [2.54, 2.54, 2.54, 2.54]
301        self.default_format_name = 'print'
302
303        self.last_paper_metric = self.default_paper_metric
304        self.last_paper_name = self.default_paper_name
305        self.last_orientation = self.default_orientation
306        self.last_custom_paper_size = copy.copy(self.default_custom_paper_size)
307        self.last_margins = copy.copy(self.default_margins)
308        self.last_css_filename = self.default_css_filename
309        self.last_format_name = self.default_format_name
310        self.option_list_map = {}
311
312    def set_last_paper_metric(self, paper_metric):
313        """
314        Set the last paper metric used for any report in this collection.
315
316        :param paper_metric: whether to use metric.
317        :type paper_name: boolean
318        """
319        self.last_paper_metric = paper_metric
320
321    def get_last_paper_metric(self):
322        """
323        Return the last paper metric used for any report in this collection.
324
325        :returns: returns whether or not to use metric
326        :rtype: boolean
327        """
328        return self.last_paper_metric
329
330    def set_last_paper_name(self, paper_name):
331        """
332        Set the last paper name used for any report in this collection.
333
334        :param paper_name: name of the paper to set.
335        :type paper_name: str
336        """
337        self.last_paper_name = paper_name
338
339    def get_last_paper_name(self):
340        """
341        Return the last paper name used for any report in this collection.
342
343        :returns: returns the name of the paper
344        :rtype: str
345        """
346        return self.last_paper_name
347
348    def set_last_orientation(self, orientation):
349        """
350        Set the last orientation used for any report in this collection.
351
352        :param orientation: orientation to set.
353        :type orientation: int
354        """
355        self.last_orientation = orientation
356
357    def get_last_orientation(self):
358        """
359        Return the last orientation used for any report in this collection.
360
361        :returns: last orientation used
362        :rtype: int
363        """
364        return self.last_orientation
365
366    def set_last_custom_paper_size(self, custom_paper_size):
367        """
368        Set the last custom paper size used for any report in this collection.
369
370        :param custom_paper_size: size to set in cm (width, height)
371        :type custom_paper_size: [float, float]
372        """
373        self.last_custom_paper_size = copy.copy(custom_paper_size)
374
375    def get_last_custom_paper_size(self):
376        """
377        Return the last custom paper size used for any report in this
378        collection.
379
380        :returns: list of last custom paper size used in cm (width, height)
381        :rtype: [float, float]
382        """
383        return copy.copy(self.last_custom_paper_size)
384
385    def set_last_margins(self, margins):
386        """
387        Set the last margins used for any report in this collection.
388
389        :param margins: margins to set in cm (left, right, top, bottom)
390        :type margins: [float, float, float, float]
391        """
392        self.last_margins = copy.copy(margins)
393
394    def get_last_margins(self):
395        """
396        Return the last margins used for any report in this
397        collection.
398
399        :returns: list of last margins used in cm (left, right, top, bottom)
400        :rtype: [float, float, float, float]
401        """
402        return copy.copy(self.last_margins)
403
404    def set_last_margin(self, pos, value):
405        """
406        Set the last margin used for any report in this collection.
407
408        :param pos: pos to set (0-4) (left, right, top, bottom)
409        :type pos: int
410        :param value: value to set the margin to in cm
411        :type value: float
412        """
413        self.last_margins[pos] = value
414
415    def get_last_margin(self, pos):
416        """
417        Return the last margins used for any report in this collection.
418
419        :param pos: position in margins list
420        :type pos: int
421        :returns: last margin used in pos
422        :rtype: float
423        """
424        return self.last_margins[pos]
425
426    def set_last_css_filename(self, css_filename):
427        """
428        Set the last css used for any report in this collection.
429
430        :param css_filename: name of the style to set.
431        """
432        self.last_css_name = css_filename
433
434    def get_last_css_filename(self):
435        """
436        Return the last template used for any report in this collection.
437        """
438        return self.last_css_filename
439
440    def set_last_format_name(self, format_name):
441        """
442        Set the last format used for any report in this collection.
443
444        :param format_name: name of the format to set.
445        """
446        self.last_format_name = format_name
447
448    def get_last_format_name(self):
449        """
450        Return the last format used for any report in this collection.
451        """
452        return self.last_format_name
453
454    def write_common(self, file):
455        file.write('<last-common>\n')
456        if self.get_last_paper_metric() != self.default_paper_metric:
457            file.write('  <metric value="%d"/>\n'
458                       % self.get_last_paper_metric())
459        if self.get_last_custom_paper_size() != self.default_custom_paper_size:
460            size = self.get_last_custom_paper_size()
461            file.write('  <size value="%f %f"/>\n' % (size[0], size[1]))
462        if self.get_last_paper_name() != self.default_paper_name:
463            file.write('  <paper name="%s"/>\n'
464                       % escxml(self.get_last_paper_name()))
465        if self.get_last_css_filename() != self.default_css_filename:
466            file.write('  <css name="%s"/>\n'
467                       % escxml(self.get_last_css_filename()))
468        if self.get_last_format_name() != self.default_format_name:
469            file.write('  <format name="%s"/>\n'
470                       % escxml(self.get_last_format_name()))
471        if self.get_last_orientation() != self.default_orientation:
472            file.write('  <orientation value="%d"/>\n'
473                       % self.get_last_orientation())
474        file.write('</last-common>\n')
475
476    def write_module_common(self, file, option_list):
477        if option_list.get_format_name():
478            file.write('  <format name="%s"/>\n'
479                       % escxml(option_list.get_format_name()))
480            if option_list.get_format_name() == 'html':
481                if option_list.get_css_filename():
482                    file.write('  <css name="%s"/>\n'
483                               % escxml(option_list.get_css_filename()))
484            else: # not HTML format, therefore it's paper
485                if option_list.get_paper_name():
486                    file.write('  <paper name="%s"/>\n'
487                               % escxml(option_list.get_paper_name()))
488                if option_list.get_orientation() is not None: # 0 is legal
489                    file.write('  <orientation value="%d"/>\n'
490                               % option_list.get_orientation())
491                if option_list.get_paper_metric() is not None: # 0 is legal
492                    file.write('  <metric value="%d"/>\n'
493                               % option_list.get_paper_metric())
494                if option_list.get_custom_paper_size():
495                    size = option_list.get_custom_paper_size()
496                    file.write('  <size value="%f %f"/>\n'
497                               % (size[0], size[1]))
498                if option_list.get_margins():
499                    margins = option_list.get_margins()
500                    for pos in range(len(margins)):
501                        file.write('  <margin number="%s" value="%f"/>\n'
502                                   % (pos, margins[pos]))
503
504        if option_list.get_style_name():
505            file.write('  <style name="%s"/>\n'
506                       % escxml(option_list.get_style_name()))
507        if option_list.get_output():
508            file.write('  <output name="%s"/>\n'
509                       % escxml(os.path.basename(option_list.get_output())))
510
511    def parse(self):
512        """
513        Loads the :class:`OptionList` from the associated file, if it exists.
514        """
515        try:
516            if os.path.isfile(self.filename):
517                parser = make_parser()
518                parser.setContentHandler(OptionParser(self))
519                with open(self.filename, encoding="utf-8") as the_file:
520                    parser.parse(the_file)
521        except (IOError, OSError, SAXParseException):
522            pass
523
524#-------------------------------------------------------------------------
525#
526# OptionParser
527#
528#-------------------------------------------------------------------------
529class OptionParser(_options.OptionParser):
530    """
531    SAX parsing class for the OptionListCollection XML file.
532    """
533
534    def __init__(self, collection):
535        """
536        Create a OptionParser class that populates the passed collection.
537
538        collection: OptionListCollection to be loaded from the file.
539        """
540        _options.OptionParser.__init__(self, collection)
541        self.common = False
542        self.list_class = OptionList
543
544    def startElement(self, tag, attrs):
545        """
546        Overridden class that handles the start of a XML element
547        """
548        # First we try report-specific tags
549        if tag == "last-common":
550            self.common = True
551        elif tag == 'docgen-option':
552            if not self.common:
553                self.oname = attrs['name']
554                self.an_o = [attrs['docgen'], attrs['value']]
555        elif tag == "style":
556            self.option_list.set_style_name(attrs['name'])
557        elif tag == "paper":
558            if self.common:
559                self.collection.set_last_paper_name(attrs['name'])
560            else:
561                self.option_list.set_paper_name(attrs['name'])
562        elif tag == "css":
563            if self.common:
564                self.collection.set_last_css_filename(attrs['name'])
565            else:
566                self.option_list.set_css_filename(attrs['name'])
567        elif tag == "format":
568            if self.common:
569                self.collection.set_last_format_name(attrs['name'])
570            else:
571                self.option_list.set_format_name(attrs['name'])
572        elif tag == "orientation":
573            if self.common:
574                self.collection.set_last_orientation(int(attrs['value']))
575            else:
576                self.option_list.set_orientation(int(attrs['value']))
577        elif tag == "metric":
578            if self.common:
579                self.collection.set_last_paper_metric(int(attrs['value']))
580            else:
581                self.option_list.set_paper_metric(int(attrs['value']))
582        elif tag == "size":
583            width, height = attrs['value'].split()
584            width = float(width)
585            height = float(height)
586            if self.common:
587                self.collection.set_last_custom_paper_size([width, height])
588            else:
589                self.option_list.set_custom_paper_size([width, height])
590
591        elif tag == "margin":
592            pos, value = int(attrs['number']), float(attrs['value'])
593            if self.common:
594                self.collection.set_last_margin(pos, value)
595            else:
596                self.option_list.set_margin(pos, value)
597        elif tag == 'output':
598            if not self.common:
599                self.option_list.set_output(attrs['name'])
600        else:
601            # Tag is not report-specific, so we let the base class handle it.
602            _options.OptionParser.startElement(self, tag, attrs)
603
604    def endElement(self, tag):
605        "Overridden class that handles the end of a XML element"
606        # First we try report-specific tags
607        if tag == "last-common":
608            self.common = False
609        elif tag == 'docgen-option':
610            self.odict[self.oname] = self.an_o
611        else:
612            # Tag is not report-specific, so we let the base class handle it.
613            _options.OptionParser.endElement(self, tag)
614
615#------------------------------------------------------------------------
616#
617# Empty class to keep the BaseDoc-targeted format happy
618# Yes, this is a hack. Find some other way to pass around documents so that
619# we don't have to handle them for reports that don't use documents (web)
620#
621#------------------------------------------------------------------------
622class EmptyDoc:
623    def init(self):
624        pass
625
626    def set_creator(self, creator):
627        pass
628
629    def set_rtl_doc(self, value): # crock!
630        pass
631
632    def open(self, filename):
633        pass
634
635    def close(self):
636        pass
637
638#-------------------------------------------------------------------------
639#
640# Class handling options for plugins
641#
642#-------------------------------------------------------------------------
643class OptionHandler(_options.OptionHandler):
644    """
645    Implements handling of the options for the plugins.
646    """
647    def __init__(self, module_name, options_dict):
648        _options.OptionHandler.__init__(self, module_name, options_dict, None)
649
650    def init_subclass(self):
651        self.collection_class = OptionListCollection
652        self.list_class = OptionList
653        self.filename = REPORT_OPTIONS
654
655    def init_common(self):
656        """
657        Specific initialization for reports.
658        """
659        # These are needed for running reports.
660        # We will not need to save/retrieve them, just keep around.
661        self.doc = EmptyDoc() # Nasty hack. Text reports replace this
662        self.output = None
663
664        # Retrieve our options from whole collection
665        self.style_name = self.option_list_collection.default_style_name
666        self.paper_metric = self.option_list_collection.get_last_paper_metric()
667        self.paper_name = self.option_list_collection.get_last_paper_name()
668        self.orientation = self.option_list_collection.get_last_orientation()
669        self.custom_paper_size = \
670            self.option_list_collection.get_last_custom_paper_size()
671        self.css_filename = self.option_list_collection.get_last_css_filename()
672        self.margins = self.option_list_collection.get_last_margins()
673        self.format_name = self.option_list_collection.get_last_format_name()
674
675    def set_common_options(self):
676        if self.saved_option_list.get_style_name():
677            self.style_name = self.saved_option_list.get_style_name()
678        if self.saved_option_list.get_orientation() is not None: # 0 is legal
679            self.orientation = self.saved_option_list.get_orientation()
680        if self.saved_option_list.get_custom_paper_size():
681            self.custom_paper_size = \
682                self.saved_option_list.get_custom_paper_size()
683        if self.saved_option_list.get_margins():
684            self.margins = self.saved_option_list.get_margins()
685        if self.saved_option_list.get_css_filename():
686            self.css_filename = self.saved_option_list.get_css_filename()
687        if self.saved_option_list.get_paper_metric() is not None: # 0 is legal
688            self.paper_metric = self.saved_option_list.get_paper_metric()
689        if self.saved_option_list.get_paper_name():
690            self.paper_name = self.saved_option_list.get_paper_name()
691        if self.saved_option_list.get_format_name():
692            self.format_name = self.saved_option_list.get_format_name()
693        if self.saved_option_list.get_output():
694            self.output = self.saved_option_list.get_output()
695
696    def save_options(self):
697        """
698        Saves options to file.
699
700        """
701
702        # First we save options from options_dict
703        for option_name, option_data in self.options_dict.items():
704            self.saved_option_list.set_option(option_name,
705                                              self.options_dict[option_name])
706
707        # Handle common options
708        self.save_common_options()
709
710        # Finally, save the whole collection into file
711        self.option_list_collection.save()
712
713    def save_common_options(self):
714        # First we save common options
715        self.saved_option_list.set_style_name(self.style_name)
716        self.saved_option_list.set_output(self.output)
717        self.saved_option_list.set_orientation(self.orientation)
718        self.saved_option_list.set_custom_paper_size(self.custom_paper_size)
719        self.saved_option_list.set_margins(self.margins)
720        self.saved_option_list.set_paper_metric(self.paper_metric)
721        self.saved_option_list.set_paper_name(self.paper_name)
722        self.saved_option_list.set_css_filename(self.css_filename)
723        self.saved_option_list.set_format_name(self.format_name)
724        self.option_list_collection.set_option_list(self.module_name,
725                                                    self.saved_option_list)
726
727        # Then save last-common options from the current selection
728        self.option_list_collection.set_last_orientation(self.orientation)
729        self.option_list_collection.set_last_custom_paper_size(
730            self.custom_paper_size)
731        self.option_list_collection.set_last_margins(self.margins)
732        self.option_list_collection.set_last_paper_metric(self.paper_metric)
733        self.option_list_collection.set_last_paper_name(self.paper_name)
734        self.option_list_collection.set_last_css_filename(self.css_filename)
735        self.option_list_collection.set_last_format_name(self.format_name)
736
737    def get_stylesheet_savefile(self):
738        """Where to save user defined styles for this report."""
739        # Get the first part of name, if it contains a comma:
740        # (will just be module_name, if no comma)
741        filename = "%s.xml" % self.module_name.split(",")[0]
742        return os.path.join(HOME_DIR, filename)
743
744    def get_default_stylesheet_name(self):
745        """ get the default stylesheet name """
746        return self.style_name
747
748    def set_default_stylesheet_name(self, style_name):
749        """ set the default stylesheet name """
750        self.style_name = style_name
751
752    def get_format_name(self):
753        """ get the format name """
754        return self.format_name
755
756    def set_format_name(self, format_name):
757        """ set the format name """
758        self.format_name = format_name
759
760    def get_paper_metric(self):
761        """ get the paper metric """
762        return self.paper_metric
763
764    def set_paper_metric(self, paper_metric):
765        """ set the paper metric """
766        self.paper_metric = paper_metric
767
768    def get_paper_name(self):
769        """ get the paper name """
770        return self.paper_name
771
772    def set_paper_name(self, paper_name):
773        """ set the paper name """
774        self.paper_name = paper_name
775
776    def get_paper(self):
777        """
778        This method is for temporary storage, not for saving/restoring.
779        """
780        return self.paper
781
782    def set_paper(self, paper):
783        """
784        This method is for temporary storage, not for saving/restoring.
785        """
786        self.paper = paper
787
788    def get_css_filename(self):
789        """ get the CSS filename """
790        return self.css_filename
791
792    def set_css_filename(self, css_filename):
793        """ set the CSS filename """
794        self.css_filename = css_filename
795
796    def get_orientation(self):
797        """ get the paper's orientation """
798        return self.orientation
799
800    def set_orientation(self, orientation):
801        """ set the paper's orientation """
802        self.orientation = orientation
803
804    def get_custom_paper_size(self):
805        """ get the paper's custom paper size, if any """
806        return copy.copy(self.custom_paper_size)
807
808    def set_custom_paper_size(self, custom_paper_size):
809        """ set the paper's custom paper size """
810        self.custom_paper_size = copy.copy(custom_paper_size)
811
812    def get_margins(self):
813        """ get the paper's margin sizes """
814        return copy.copy(self.margins)
815
816    def set_margins(self, margins):
817        """ set the paper's margin sizes """
818        self.margins = copy.copy(margins)
819
820#------------------------------------------------------------------------
821#
822# Base Options class
823#
824#------------------------------------------------------------------------
825class ReportOptions(_options.Options):
826
827    """
828    Defines options and provides handling interface.
829
830    This is a base Options class for the reports. All reports, options
831    classes should derive from it.
832    """
833    def __init__(self, name, dbase):
834        """
835        Initialize the class, performing usual house-keeping tasks.
836        Subclasses MUST call this in their :meth:`__init__` method.
837        """
838        self.name = name
839        self.options_dict = {}
840        self.options_help = {}
841        self.handler = None
842
843    def load_previous_values(self):
844        self.handler = OptionHandler(self.name, self.options_dict)
845
846    def make_default_style(self, default_style):
847        """
848        Defines default style for this report.
849
850        This method MUST be overridden by reports that use the
851        user-adjustable paragraph styles.
852
853        .. note:: Unique names MUST be used for all style names, otherwise the
854                  styles will collide when making a book with duplicate style
855                  names. A rule of safety is to prepend style name with the
856                  acronym based on report name.
857
858        The following acronyms are already taken:
859
860        ====    ================================
861        Code    Report
862        ====    ================================
863        AC      Ancestor Chart
864        AC2     Ancestor Chart 2 (Wall Chart)
865        AHN     Ahnentafel Report
866        AR      Comprehensive Ancestors report
867        CBT     Custom Book Text
868        DG      Descendant Graph
869        DR      Descendant Report
870        DAR     Detailed Ancestral Report
871        DDR     Detailed Descendant Report
872        FGR     Family Group Report
873        FC      Fan Chart
874        FTA     FTM Style Ancestral report
875        FTD     FTM Style Descendant report
876        IDS     Individual Complete Report
877        IDX     Alphabetical Index
878        IVS     Individual Summary Report
879        PLC     Place Report
880        SBT     Simple Book Title
881        TLG     Timeline Graph
882        TOC     Table Of Contents
883        ====    ================================
884        """
885        pass
886
887    def get_document(self):
888        """
889        Return document instance.
890
891        .. warning:: This method MUST NOT be overridden by subclasses.
892        """
893        return self.handler.doc
894
895    def set_document(self, val):
896        """
897        Set document to a given instance.
898
899        .. warning:: This method MUST NOT be overridden by subclasses.
900        """
901        self.handler.doc = val
902
903    def get_output(self):
904        """
905        Return document output destination.
906
907        .. warning:: This method MUST NOT be overridden by subclasses.
908        """
909        return self.handler.output
910
911    def set_output(self, val):
912        """
913        Set output destination to a given string.
914
915        .. warning:: This method MUST NOT be overridden by subclasses.
916        """
917        self.handler.output = val
918
919#-------------------------------------------------------------------------
920#
921# MenuReportOptions
922#
923#-------------------------------------------------------------------------
924class MenuReportOptions(MenuOptions, ReportOptions):
925    """
926
927    The MenuReportOptions class implements the :class:`ReportOptions`
928    functionality in a generic way so that the user does not need to
929    be concerned with the actual representation of the options.
930
931    The user should inherit the MenuReportOptions class and override the
932    add_menu_options function. The user can add options to the menu and the
933    MenuReportOptions class will worry about setting up the UI.
934
935    """
936    def __init__(self, name, dbase):
937        ReportOptions.__init__(self, name, dbase)
938        MenuOptions.__init__(self)
939
940    def load_previous_values(self):
941        ReportOptions.load_previous_values(self)
942        # Pass the loaded values to the menu options so they will be displayed
943        # properly.
944        for optname in self.options_dict:
945            menu_option = self.menu.get_option_by_name(optname)
946            if menu_option:
947                menu_option.set_value(self.options_dict[optname])
948
949    def get_subject(self):
950        """
951        Return a string that describes the subject of the report.
952
953        This method MUST be overridden by subclasses.
954        """
955        LOG.warning("get_subject not implemented for %s" % self.name)
956        return ""
957
958#-------------------------------------------------------------------------
959#
960# DocOptionHandler class
961#
962#-------------------------------------------------------------------------
963class DocOptionHandler(_options.OptionHandler):
964    """
965    Implements handling of the docgen options for the plugins.
966    """
967
968    def __init__(self, module_name, options_dict):
969        _options.OptionHandler.__init__(self, module_name, options_dict)
970
971    def init_subclass(self):
972        self.collection_class = DocOptionListCollection
973        self.list_class = OptionList
974        self.filename = REPORT_OPTIONS
975
976    def set_options(self):
977        """
978        Set options to be used in this plugin according to the passed
979        options dictionary.
980
981        Dictionary values are all strings, since they were read from XML.
982        Here we need to convert them to the needed types. We use default
983        values to determine the type.
984        """
985        # First we set options_dict values based on the saved options
986        options = self.saved_option_list.get_options()
987        docgen_names = self.option_list_collection.docgen_names
988        for option_name, option_data in options.items():
989            if (option_name in self.options_dict
990                    and isinstance(option_data, list)
991                    and option_data
992                    and option_data[0] in docgen_names):
993                try:
994                    converter = get_type_converter(
995                        self.options_dict[option_name])
996                    self.options_dict[option_name] = converter(option_data[1])
997                except (TypeError, ValueError):
998                    pass
999
1000#------------------------------------------------------------------------
1001#
1002# DocOptions class
1003#
1004#------------------------------------------------------------------------
1005class DocOptions(MenuOptions):
1006    """
1007    Defines options and provides handling interface.
1008    """
1009
1010    def __init__(self, name):
1011        """
1012        Initialize the class, performing usual house-keeping tasks.
1013        Subclasses MUST call this in their :meth:`__init__` method.
1014        """
1015        self.name = name
1016        MenuOptions.__init__(self)
1017
1018    def load_previous_values(self):
1019        self.handler = DocOptionHandler(self.name, self.options_dict)
1020
1021#-------------------------------------------------------------------------
1022#
1023# DocOptionListCollection class
1024#
1025#-------------------------------------------------------------------------
1026class DocOptionListCollection(_options.OptionListCollection):
1027    """
1028    Implements a collection of option lists.
1029    """
1030
1031    def __init__(self, filename):
1032        _options.OptionListCollection.__init__(self, filename)
1033
1034    def init_common(self):
1035        pass
1036
1037    def parse(self):
1038        """
1039        Loads the :class:`OptionList` from the associated file, if it exists.
1040        """
1041        try:
1042            if os.path.isfile(self.filename):
1043                parser = make_parser()
1044                parser.setContentHandler(DocOptionParser(self))
1045                with open(self.filename, encoding="utf-8") as the_file:
1046                    parser.parse(the_file)
1047        except (IOError, OSError, SAXParseException):
1048            pass
1049
1050#------------------------------------------------------------------------
1051#
1052# DocOptionParser class
1053#
1054#------------------------------------------------------------------------
1055class DocOptionParser(_options.OptionParser):
1056    """
1057    SAX parsing class for the DocOptionListCollection XML file.
1058    """
1059
1060    def __init__(self, collection):
1061        """
1062        Create a DocOptionParser class that populates the passed collection.
1063
1064        collection:   DocOptionListCollection to be loaded from the file.
1065        """
1066        _options.OptionParser.__init__(self, collection)
1067        self.list_class = OptionList
1068
1069    def startElement(self, tag, attrs):
1070        "Overridden class that handles the start of a XML element"
1071        if tag == 'docgen-option':
1072            self.oname = attrs['name']
1073            self.an_o = [attrs['docgen'], attrs['value']]
1074        else:
1075            _options.OptionParser.startElement(self, tag, attrs)
1076
1077    def endElement(self, tag):
1078        "Overridden class that handles the end of a XML element"
1079        if tag == 'docgen-option':
1080            self.odict[self.oname] = self.an_o
1081        else:
1082            _options.OptionParser.endElement(self, tag)
1083