From 4f7ce160205c04b2b229809cf57a9d12dfd08790 Mon Sep 17 00:00:00 2001 From: dkuhlman Date: Tue, 29 Dec 2009 19:45:17 +0000 Subject: [PATCH] Added custom headers/footers with page numbers, date, time, etc. Plus, doc fix (containers). git-svn-id: https://docutils.svn.sourceforge.net/svnroot/docutils/trunk/docutils@6214 929543f6-e4f2-0310-98a6-ba3bd3dd1d04 --- docutils/writers/odf_odt/__init__.py | 325 ++++++++++++++++++++++- docutils/writers/odf_odt/styles.odt | Bin 10689 -> 11051 bytes test/functional/expected/odt_custom_headfoot.odt | Bin 0 -> 8879 bytes test/functional/input/odt_custom_headfoot.txt | 9 + test/test_writers/test_odt.py | 16 +- 5 files changed, 344 insertions(+), 6 deletions(-) rewrite docutils/writers/odf_odt/styles.odt (92%) create mode 100644 test/functional/expected/odt_custom_headfoot.odt create mode 100644 test/functional/input/odt_custom_headfoot.txt diff --git a/docutils/writers/odf_odt/__init__.py b/docutils/writers/odf_odt/__init__.py index 684ed0065..4a794d23e 100644 --- a/docutils/writers/odf_odt/__init__.py +++ b/docutils/writers/odf_odt/__init__.py @@ -481,6 +481,20 @@ class Writer(writers.Writer): 'action': 'store_true', 'dest': 'generate_oowriter_toc', 'validator': frontend.validate_boolean}), + ('Specify the contents of an custom header line. ' + 'See odf_odt writer documentation for details ' + 'about special field character sequences.', + ['--custom-odt-header'], + { 'default': '', + 'dest': 'custom_header', + }), + ('Specify the contents of an custom footer line. ' + 'See odf_odt writer documentation for details ' + 'about special field character sequences.', + ['--custom-odt-footer'], + { 'default': '', + 'dest': 'custom_footer', + }), ) ) @@ -806,6 +820,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.line_block_level = 0 self.line_indent_level = 0 self.citation_id = None + self.style_index = 0 # use to form unique style names def add_doc_title(self): text = self.settings.title @@ -835,7 +850,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): def setup_page(self, content): root_el = etree.fromstring(content) self.setup_paper(root_el) - if len(self.header_content) > 0 or len(self.footer_content) > 0: + if (len(self.header_content) > 0 or len(self.footer_content) > 0 or + self.settings.custom_header or self.settings.custom_footer): self.add_header_footer(root_el) new_content = etree.tostring(root_el) return new_content @@ -863,6 +879,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): walk(root_el) def add_header_footer(self, root_el): + automatic_styles = root_el.find( + '{%s}automatic-styles' % SNSD['office']) path = '{%s}master-styles' % (NAME_SPACE_1, ) master_el = root_el.find(path) if master_el is None: @@ -872,7 +890,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): if master_el is None: return el1 = master_el - if len(self.header_content) > 0: + if self.header_content or self.settings.custom_header: if WhichElementTree == 'lxml': el2 = SubElement(el1, 'style:header', nsdict=SNSD) else: @@ -884,7 +902,10 @@ class ODFTranslator(nodes.GenericNodeVisitor): attrkey = add_ns('text:style-name', nsdict=SNSD) el.attrib[attrkey] = self.rststyle('header') el2.append(el) - if len(self.footer_content) > 0: + if self.settings.custom_header: + elcustom = self.create_custom_headfoot(el2, + self.settings.custom_header, 'header', automatic_styles) + if self.footer_content or self.settings.custom_footer: if WhichElementTree == 'lxml': el2 = SubElement(el1, 'style:footer', nsdict=SNSD) else: @@ -896,6 +917,304 @@ class ODFTranslator(nodes.GenericNodeVisitor): attrkey = add_ns('text:style-name', nsdict=SNSD) el.attrib[attrkey] = self.rststyle('footer') el2.append(el) + if self.settings.custom_footer: + elcustom = self.create_custom_headfoot(el2, + self.settings.custom_footer, 'footer', automatic_styles) + + code_none, code_field, code_text = range(3) + field_pat = re.compile(r'%(..?)%') + + def create_custom_headfoot(self, parent, text, style_name, automatic_styles): + current_element = None + field_iter = self.split_field_specifiers_iter(text) + for item in field_iter: + if item[0] == ODFTranslator.code_field: + if item[1] not in ('p', 'P', + 't1', 't2', 't3', 't4', + 'd1', 'd2', 'd3', 'd4', 'd5', + 's', 't', 'a'): + msg = 'bad field spec: %%%s%%' % (item[1], ) + raise RuntimeError, msg + if current_element is None: + parent = SubElement(parent, 'text:p', attrib={ + 'text:style-name': self.rststyle(style_name), + }) + el1 = self.make_field_element(parent, + item[1], style_name, automatic_styles) + if el1 is None: + msg = 'bad field spec: %%%s%%' % (item[1], ) + raise RuntimeError, msg + else: + current_element = el1 + else: + if current_element is None: + parent = SubElement(parent, 'text:p', attrib={ + 'text:style-name': self.rststyle(style_name), + }) + parent.text = item[1] + else: + current_element.tail = item[1] + + def make_field_element(self, parent, text, style_name, automatic_styles): + if text == 'p': + el1 = SubElement(parent, 'text:page-number', attrib={ + 'text:style-name': self.rststyle(style_name), + 'text:select-page': 'current', + }) + elif text == 'P': + el1 = SubElement(parent, 'text:page-count', attrib={ + 'text:style-name': self.rststyle(style_name), + }) + elif text == 't1': + self.style_index += 1 + el1 = SubElement(parent, 'text:time', attrib={ + 'text:style-name': self.rststyle(style_name), + 'text:fixed': 'true', + 'style:data-style-name': 'rst-time-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:time-style', attrib={ + 'style:name': 'rst-time-style-%d' % self.style_index, + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:hours', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:minutes', attrib={ + 'number:style': 'long', + }) + elif text == 't2': + self.style_index += 1 + el1 = SubElement(parent, 'text:time', attrib={ + 'text:style-name': self.rststyle(style_name), + 'text:fixed': 'true', + 'style:data-style-name': 'rst-time-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:time-style', attrib={ + 'style:name': 'rst-time-style-%d' % self.style_index, + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:hours', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:minutes', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:seconds', attrib={ + 'number:style': 'long', + }) + elif text == 't3': + self.style_index += 1 + el1 = SubElement(parent, 'text:time', attrib={ + 'text:style-name': self.rststyle(style_name), + 'text:fixed': 'true', + 'style:data-style-name': 'rst-time-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:time-style', attrib={ + 'style:name': 'rst-time-style-%d' % self.style_index, + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:hours', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:minutes', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ' ' + el3 = SubElement(el2, 'number:am-pm') + elif text == 't4': + self.style_index += 1 + el1 = SubElement(parent, 'text:time', attrib={ + 'text:style-name': self.rststyle(style_name), + 'text:fixed': 'true', + 'style:data-style-name': 'rst-time-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:time-style', attrib={ + 'style:name': 'rst-time-style-%d' % self.style_index, + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:hours', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:minutes', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ':' + el3 = SubElement(el2, 'number:seconds', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ' ' + el3 = SubElement(el2, 'number:am-pm') + elif text == 'd1': + self.style_index += 1 + el1 = SubElement(parent, 'text:date', attrib={ + 'text:style-name': self.rststyle(style_name), + 'style:data-style-name': 'rst-date-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:date-style', attrib={ + 'style:name': 'rst-date-style-%d' % self.style_index, + 'number:automatic-order': 'true', + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:month', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '/' + el3 = SubElement(el2, 'number:day', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '/' + el3 = SubElement(el2, 'number:year') + elif text == 'd2': + self.style_index += 1 + el1 = SubElement(parent, 'text:date', attrib={ + 'text:style-name': self.rststyle(style_name), + 'style:data-style-name': 'rst-date-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:date-style', attrib={ + 'style:name': 'rst-date-style-%d' % self.style_index, + 'number:automatic-order': 'true', + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:month', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '/' + el3 = SubElement(el2, 'number:day', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '/' + el3 = SubElement(el2, 'number:year', attrib={ + 'number:style': 'long', + }) + elif text == 'd3': + self.style_index += 1 + el1 = SubElement(parent, 'text:date', attrib={ + 'text:style-name': self.rststyle(style_name), + 'style:data-style-name': 'rst-date-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:date-style', attrib={ + 'style:name': 'rst-date-style-%d' % self.style_index, + 'number:automatic-order': 'true', + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:month', attrib={ + 'number:textual': 'true', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ' ' + el3 = SubElement(el2, 'number:day', attrib={ + }) + el3 = SubElement(el2, 'number:text') + el3.text = ', ' + el3 = SubElement(el2, 'number:year', attrib={ + 'number:style': 'long', + }) + elif text == 'd4': + self.style_index += 1 + el1 = SubElement(parent, 'text:date', attrib={ + 'text:style-name': self.rststyle(style_name), + 'style:data-style-name': 'rst-date-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:date-style', attrib={ + 'style:name': 'rst-date-style-%d' % self.style_index, + 'number:automatic-order': 'true', + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:month', attrib={ + 'number:textual': 'true', + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = ' ' + el3 = SubElement(el2, 'number:day', attrib={ + }) + el3 = SubElement(el2, 'number:text') + el3.text = ', ' + el3 = SubElement(el2, 'number:year', attrib={ + 'number:style': 'long', + }) + elif text == 'd5': + self.style_index += 1 + el1 = SubElement(parent, 'text:date', attrib={ + 'text:style-name': self.rststyle(style_name), + 'style:data-style-name': 'rst-date-style-%d' % self.style_index, + }) + el2 = SubElement(automatic_styles, 'number:date-style', attrib={ + 'style:name': 'rst-date-style-%d' % self.style_index, + 'xmlns:number': SNSD['number'], + 'xmlns:style': SNSD['style'], + }) + el3 = SubElement(el2, 'number:year', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '-' + el3 = SubElement(el2, 'number:month', attrib={ + 'number:style': 'long', + }) + el3 = SubElement(el2, 'number:text') + el3.text = '-' + el3 = SubElement(el2, 'number:day', attrib={ + 'number:style': 'long', + }) + elif text == 's': + el1 = SubElement(parent, 'text:subject', attrib={ + 'text:style-name': self.rststyle(style_name), + }) + elif text == 't': + el1 = SubElement(parent, 'text:title', attrib={ + 'text:style-name': self.rststyle(style_name), + }) + elif text == 'a': + el1 = SubElement(parent, 'text:author-name', attrib={ + 'text:fixed': 'false', + }) + else: + el1 = None + return el1 + + def split_field_specifiers_iter(self, text): + pos1 = 0 + pos_end = len(text) + while True: + mo = ODFTranslator.field_pat.search(text, pos1) + if mo: + pos2 = mo.start() + if pos2 > pos1: + yield (ODFTranslator.code_text, text[pos1:pos2]) + yield (ODFTranslator.code_field, mo.group(1)) + pos1 = mo.end() + else: + break + trailing = text[pos1:] + if trailing: + yield (ODFTranslator.code_text, trailing) + def astext(self): root = self.content_tree.getroot() diff --git a/docutils/writers/odf_odt/styles.odt b/docutils/writers/odf_odt/styles.odt dissimilarity index 92% index 25e0f36a3cdbc69e749ce5edbe9eb7c6177ddbac..9a50a805944e7facfe0385f209e90b9d4de57bed 100644 GIT binary patch delta 7955 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 literal 0 HcwPel00001 diff --git a/test/functional/input/odt_custom_headfoot.txt b/test/functional/input/odt_custom_headfoot.txt new file mode 100644 index 000000000..e34a08ee4 --- /dev/null +++ b/test/functional/input/odt_custom_headfoot.txt @@ -0,0 +1,9 @@ +============================ +Custom Headers and Footers +============================ + +Test for custom headers and footers and for page numbers, date, +time, etc. + + + diff --git a/test/test_writers/test_odt.py b/test/test_writers/test_odt.py index af55f5542..d1ee95277 100755 --- a/test/test_writers/test_odt.py +++ b/test/test_writers/test_odt.py @@ -84,7 +84,7 @@ class DocutilsOdtTestCase(DocutilsTestSupport.StandardTestCase): return WhichElementTree def process_test(self, input_filename, expected_filename, - save_output_name=None): + save_output_name=None, settings_overrides=None): if not self.check_import(): return # Test that xmlcharrefreplace is the default output encoding @@ -95,8 +95,8 @@ class DocutilsOdtTestCase(DocutilsTestSupport.StandardTestCase): expected = expected_file.read() input_file.close() expected_file.close() - settings_overrides={ - } + if settings_overrides is None: + settings_overrides={ } result = docutils.core.publish_string( source=input, reader_name='standalone', @@ -157,6 +157,16 @@ class DocutilsOdtTestCase(DocutilsTestSupport.StandardTestCase): #save_output_name='odt_tables1.odt' ) + def test_odt_custom_headfoot(self): + settings_overrides = { + 'custom_header': 'Page %p% of %P%', + 'custom_footer': 'Title: %t% Date: %d3% Time: %t4%', + } + self.process_test('odt_custom_headfoot.txt', 'odt_custom_headfoot.odt', + settings_overrides=settings_overrides, + #save_output_name='odt_tables1.odt' + ) + # # Template for new tests. # Also add functional/input/odt_xxxx.txt and -- 2.11.4.GIT