From bb177fc777e7e57b829534a8e13205946633c7ee Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Thu, 6 Jun 2024 10:55:51 +0200 Subject: [PATCH 1/7] Change lpod require to odfdo odfdo (https://github.com/jdum/odfdo) is a Python 3 library for handling ODF documents. It is based on lpod-python which is no longer actively maintained and still requires Python 2 to run. Since Python 2 is no longer supported by upstream and increasingly becoming unavailable on contemporary operating systems, it makes sense to switch to a Python 3 library. --- odpdown.py | 24 ++++++++++++------------ setup.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/odpdown.py b/odpdown.py index 7dda926..197dadf 100755 --- a/odpdown.py +++ b/odpdown.py @@ -35,7 +35,7 @@ # POSSIBILITY OF SUCH DAMAGE. # -# use of lpod inspired by Bart Hanssens' odflinkchecker.py, +# use of odfdo (successor of lpod) inspired by Bart Hanssens' odflinkchecker.py, # https://lists.oasis-open.org/archives/opendocument-users/201008/msg00004.html # and lpod-python-recipes # @@ -50,17 +50,17 @@ from mimetypes import guess_type from uuid import uuid4 -from lpod import ODF_MANIFEST, ODF_STYLES -from lpod.document import odf_get_document -from lpod.frame import odf_create_text_frame, odf_create_image_frame, odf_frame -from lpod.draw_page import odf_create_draw_page, odf_draw_page -from lpod.list import odf_create_list_item, odf_create_list -from lpod.style import odf_create_style -from lpod.paragraph import odf_create_paragraph, odf_span -from lpod.paragraph import odf_create_line_break, odf_create_spaces -from lpod.paragraph import odf_create_tabulation -from lpod.element import odf_create_element -from lpod.link import odf_create_link, odf_link +from odfdo import ODF_MANIFEST, ODF_STYLES +from odfdo.document import odf_get_document +from odfdo.frame import odf_create_text_frame, odf_create_image_frame, odf_frame +from odfdo.draw_page import odf_create_draw_page, odf_draw_page +from odfdo.list import odf_create_list_item, odf_create_list +from odfdo.style import odf_create_style +from odfdo.paragraph import odf_create_paragraph, odf_span +from odfdo.paragraph import odf_create_line_break, odf_create_spaces +from odfdo.paragraph import odf_create_tabulation +from odfdo.element import odf_create_element +from odfdo.link import odf_create_link, odf_link from pygments.lexers import get_lexer_by_name from pygments.formatter import Formatter diff --git a/setup.py b/setup.py index 9305d04..3fc3052 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ class install(setuptools.command.install.install): license='BSD License', install_requires=[ 'mistune>=0.7.1', - 'lpod-python>=1.1.6', + 'odfdo>=3.3.7', 'pygments>=1.6', 'pillow>=2.0', 'lxml>=3.4.4', From c24edfcb6a61003f12df221b4436c30f49a4994e Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Thu, 6 Jun 2024 11:04:12 +0200 Subject: [PATCH 2/7] 2to3 automatic conversion These changes are the result of a simple 2to3-2.7 -w run on Ubuntu 20.04 and have not been vetted for correctness, yet. Any required fixes will follow in subsequent commits. --- odpdown.py | 220 ++++++++++++++++++++++++++--------------------------- test.py | 4 +- 2 files changed, 112 insertions(+), 112 deletions(-) diff --git a/odpdown.py b/odpdown.py index 197dadf..a978ed9 100755 --- a/odpdown.py +++ b/odpdown.py @@ -41,12 +41,12 @@ # import mistune import argparse -import urlparse +import urllib.parse import codecs import sys import re -from urllib import urlopen +from urllib.request import urlopen from mimetypes import guess_type from uuid import uuid4 @@ -123,7 +123,7 @@ def wrap_spans(odf_elements): return res # buffer regex for tab/space splitting for block code -_whitespace_re = re.compile(u'( {2,}|\t)', re.UNICODE) +_whitespace_re = re.compile('( {2,}|\t)', re.UNICODE) def handle_whitespace(text): @@ -146,7 +146,7 @@ def handle_whitespace(text): odf_create_tabulation()) else: result.append( - unicode(part)) + str(part)) # for all but the last line: add linebreak if index < len(lines)-1: @@ -187,7 +187,7 @@ def add_child_elems(self, elems): # stick additional frame content into last existing one for child in self._elements[-1].get_elements( 'descendant::draw:frame'): - if child.get_presentation_class() == u'outline': + if child.get_presentation_class() == 'outline': text_box = child.get_children()[0] for elem in wrap_spans(elems): text_box.append(elem) @@ -203,19 +203,19 @@ def add_child_elems(self, elems): self._elements[-1].append( odf_create_text_frame( elems, - presentation_style=u'md2odp-OutlineText', - size=(u'%s' % self.outline_size[0], - u'%s' % self.outline_size[1]), - position=(u'%s' % self.outline_position[0], - u'%s' % self.outline_position[1]), - presentation_class=u'outline')) + presentation_style='md2odp-OutlineText', + size=('%s' % self.outline_size[0], + '%s' % self.outline_size[1]), + position=('%s' % self.outline_position[0], + '%s' % self.outline_position[1]), + presentation_class='outline')) else: self._elements += elems def add_text(self, text): """Helper to ctext to self""" span = odf_create_element('text:span') - span.set_text(unicode(text)) + span.set_text(str(text)) self._elements.append(span) def __add__(self, other): @@ -223,7 +223,7 @@ def __add__(self, other): tmp = ODFPartialTree(list(self._elements), self.outline_size, self.outline_position) - if isinstance(other, basestring): + if isinstance(other, str): tmp.add_text(other) else: tmp.add_child_elems(other.get()) @@ -231,7 +231,7 @@ def __add__(self, other): def __iadd__(self, other): """Override of +=""" - if isinstance(other, basestring): + if isinstance(other, str): self.add_text(other) else: self.add_child_elems(other.get()) @@ -256,7 +256,7 @@ def __init__(self, **options): Formatter.__init__(self, **options) # buffer regex for tab/space splitting for block code - self.whitespace_re = re.compile(u'( {2,}|\t)', re.UNICODE) + self.whitespace_re = re.compile('( {2,}|\t)', re.UNICODE) # create a dict of (start, end) tuples that wrap the # value of a token so that we can use it in the format @@ -316,19 +316,19 @@ def add_style_defs(self, document): # colors are readily specified in hex: 'RRGGBB' if style['color']: add_style(document, 'text', - u'md2odp-TColor%s' % style['color'], - [('text', {'color': u'#'+style['color']})]) + 'md2odp-TColor%s' % style['color'], + [('text', {'color': '#'+style['color']})]) if style['bold']: - add_style(document, 'text', u'md2odp-TBold', - [('text', {'font_weight': u'bold'})]) + add_style(document, 'text', 'md2odp-TBold', + [('text', {'font_weight': 'bold'})]) if style['italic']: - add_style(document, 'text', u'md2odp-TItalic', - [('text', {'font_style': u'italic'})]) + add_style(document, 'text', 'md2odp-TItalic', + [('text', {'font_style': 'italic'})]) if style['underline']: - add_style(document, 'text', u'md2odp-TUnderline', - [('text', {'text_underline_style': u'solid', - 'text_underline_width': u'auto', - 'text_underline_color': u'font-color'})]) + add_style(document, 'text', 'md2odp-TUnderline', + [('text', {'text_underline_style': 'solid', + 'text_underline_width': 'auto', + 'text_underline_color': 'font-color'})]) def format(self, tokensource): result = [] @@ -361,7 +361,7 @@ def format(self, tokensource): # white space and linefeeds: special handling # needed in ODF for elem in handle_whitespace(lastval): - if isinstance(elem, basestring): + if isinstance(elem, str): if root_span is None: span = odf_create_element('text:span') span.set_text(elem) @@ -384,12 +384,12 @@ def format(self, tokensource): root_span, leaf_span = self.styles[lasttype] if root_span is None: span = odf_create_element('text:span') - span.set_text(unicode(lastval)) + span.set_text(str(lastval)) result.append(span) else: # this is nasty - set text in shared style instance, # clone afterwards - leaf_span.set_text(unicode(lastval)) + leaf_span.set_text(str(lastval)) result.append(root_span.clone()) return result @@ -416,20 +416,20 @@ def __init__(self, self.document = document self.doc_manifest = document.get_part(ODF_MANIFEST) self.break_master = 'Default' if break_master is None else break_master - self.breakheader_size = ((u'20cm', u'3cm') if breakheader_size is None + self.breakheader_size = (('20cm', '3cm') if breakheader_size is None else breakheader_size) self.breakheader_position = ( - (u'2cm', u'8cm') if breakheader_position is None + ('2cm', '8cm') if breakheader_position is None else breakheader_position) self.content_master = ('Default' if content_master is None else content_master) - self.header_size = ((u'20cm', u'3cm') if header_size is None + self.header_size = (('20cm', '3cm') if header_size is None else header_size) - self.header_position = ((u'2cm', u'0.5cm') if header_position is None + self.header_position = (('2cm', '0.5cm') if header_position is None else header_position) - self.outline_size = ((u'22cm', u'12cm') if outline_size is None + self.outline_size = (('22cm', '12cm') if outline_size is None else outline_size) - self.outline_position = ((u'2cm', u'4cm') if outline_position is None + self.outline_position = (('2cm', '4cm') if outline_position is None else outline_position) # font/char styles @@ -439,14 +439,14 @@ def __init__(self, name=code_font_name, font_name=code_font_name, font_family=code_font_name, - font_family_generic=u'modern', - font_pitch=u'fixed'), + font_family_generic='modern', + font_pitch='fixed'), automatic=True) - add_style(document, 'text', u'md2odp-TextEmphasisStyle', - [('text', {'font_style': u'italic'})]) - add_style(document, 'text', u'md2odp-TextDoubleEmphasisStyle', - [('text', {'font_weight': u'bold'})]) - add_style(document, 'text', u'md2odp-TextQuoteStyle', + add_style(document, 'text', 'md2odp-TextEmphasisStyle', + [('text', {'font_style': 'italic'})]) + add_style(document, 'text', 'md2odp-TextDoubleEmphasisStyle', + [('text', {'font_weight': 'bold'})]) + add_style(document, 'text', 'md2odp-TextQuoteStyle', # TODO: font size increase does not work currently # Bug in Impress: # schema has his - for _all_ occurences @@ -456,45 +456,45 @@ def __init__(self, # # # - [('text', {'size': u'200%', - 'color': u'#ccf4c6'})]) - add_style(document, 'text', u'md2odp-TextCodeStyle', + [('text', {'size': '200%', + 'color': '#ccf4c6'})]) + add_style(document, 'text', 'md2odp-TextCodeStyle', [('text', {'style:font_name': code_font_name})]) # paragraph styles - add_style(document, 'paragraph', u'md2odp-ParagraphQuoteStyle', - [('text', {'color': u'#18a303'}), - ('paragraph', {'margin_left': u'0.5cm', - 'margin_right': u'0.5cm', - 'margin_top': u'0.6cm', - 'margin_bottom': u'0.5cm', - 'text_indent': u'-0.6cm'})]) - add_style(document, 'paragraph', u'md2odp-ParagraphCodeStyle', + add_style(document, 'paragraph', 'md2odp-ParagraphQuoteStyle', + [('text', {'color': '#18a303'}), + ('paragraph', {'margin_left': '0.5cm', + 'margin_right': '0.5cm', + 'margin_top': '0.6cm', + 'margin_bottom': '0.5cm', + 'text_indent': '-0.6cm'})]) + add_style(document, 'paragraph', 'md2odp-ParagraphCodeStyle', [('text', {'style:font_name': code_font_name}), - ('paragraph', {'margin_left': u'0.5cm', - 'margin_right': u'0.5cm', - 'margin_top': u'0.6cm', - 'margin_bottom': u'0.6cm', - 'text_indent': u'0cm'})]) + ('paragraph', {'margin_left': '0.5cm', + 'margin_right': '0.5cm', + 'margin_top': '0.6cm', + 'margin_bottom': '0.6cm', + 'text_indent': '0cm'})]) # graphic styles - add_style(document, 'graphic', u'md2odp-ImageStyle', - [('graphic', {'stroke': u'none', - 'fille': u'none', - 'draw:textarea_horizontal_align': u'right', - 'draw:textarea-vertical-align': u'bottom'})]) + add_style(document, 'graphic', 'md2odp-ImageStyle', + [('graphic', {'stroke': 'none', + 'fille': 'none', + 'draw:textarea_horizontal_align': 'right', + 'draw:textarea-vertical-align': 'bottom'})]) # presentation styles - add_style(document, 'presentation', u'md2odp-OutlineText', + add_style(document, 'presentation', 'md2odp-OutlineText', ([('graphic', - {'draw:fit_to_size': u'shrink-to-fit'})] if autofit_text + {'draw:fit_to_size': 'shrink-to-fit'})] if autofit_text else [('graphic', - {'draw:auto_grow_height': u'true'})]), + {'draw:auto_grow_height': 'true'})]), self.content_master + '-outline1') - add_style(document, 'presentation', u'md2odp-TitleText', - [('graphic', {'draw:auto_grow_height': u'true'})], + add_style(document, 'presentation', 'md2odp-TitleText', + [('graphic', {'draw:auto_grow_height': 'true'})], self.content_master + '-title') - add_style(document, 'presentation', u'md2odp-BreakTitleText', - [('graphic', {'draw:auto_grow_height': u'true'})], + add_style(document, 'presentation', 'md2odp-BreakTitleText', + [('graphic', {'draw:auto_grow_height': 'true'})], self.break_master + '-title') # clone list style out of content master page (an abomination @@ -508,11 +508,11 @@ def __init__(self, # now stick that under custom name into automatic style section list_style = content_master_styles[0].get_elements( 'style:graphic-properties/text:list-style[1]')[0].clone() - list_style.set_attribute('style:name', u'OutlineListStyle') + list_style.set_attribute('style:name', 'OutlineListStyle') document.insert_style(list_style, automatic=True) else: - print 'WARNING: no outline list style found for ' \ - 'master page "%s"!' % self.content_master + print('WARNING: no outline list style found for ' \ + 'master page "%s"!' % self.content_master) # delegate to pygments formatter for their styles self.formatter.add_style_defs(document) @@ -521,7 +521,7 @@ def placeholder(self): return ODFPartialTree.from_metrics_provider([], self) def block_code(self, code, language=None): - para = odf_create_paragraph(style=u'md2odp-ParagraphCodeStyle') + para = odf_create_paragraph(style='md2odp-ParagraphCodeStyle') if language is not None: # explicit lang given, use syntax highlighting @@ -532,7 +532,7 @@ def block_code(self, code, language=None): else: # no lang given, use plain monospace formatting for elem in handle_whitespace(code): - if isinstance(elem, basestring): + if isinstance(elem, str): span = odf_create_element('text:span') span.set_text(elem) para.append(span) @@ -548,40 +548,40 @@ def header(self, text, level, raw=None): 'page1', name=hasher(), master_page=self.break_master, - presentation_page_layout=u'AL3T19') + presentation_page_layout='AL3T19') page.append( odf_create_text_frame( wrap_spans(text.get()), - presentation_style=u'md2odp-BreakTitleText', - size=(u'%s' % self.breakheader_size[0], - u'%s' % self.breakheader_size[1]), - position=(u'%s' % self.breakheader_position[0], - u'%s' % self.breakheader_position[1]), - presentation_class=u'title')) + presentation_style='md2odp-BreakTitleText', + size=('%s' % self.breakheader_size[0], + '%s' % self.breakheader_size[1]), + position=('%s' % self.breakheader_position[0], + '%s' % self.breakheader_position[1]), + presentation_class='title')) elif level == 2: page = odf_create_draw_page( 'page1', name=hasher(), master_page=self.content_master, - presentation_page_layout=u'AL3T1') + presentation_page_layout='AL3T1') page.append( odf_create_text_frame( wrap_spans(text.get()), - presentation_style=u'md2odp-TitleText', - size=(u'%s' % self.header_size[0], - u'%s' % self.header_size[1]), - position=(u'%s' % self.header_position[0], - u'%s' % self.header_position[1]), - presentation_class=u'title')) + presentation_style='md2odp-TitleText', + size=('%s' % self.header_size[0], + '%s' % self.header_size[1]), + position=('%s' % self.header_position[0], + '%s' % self.header_position[1]), + presentation_class='title')) else: raise RuntimeError('Unsupported heading level: %d' % level) return ODFPartialTree.from_metrics_provider([page], self) def block_quote(self, text): - para = odf_create_paragraph(style=u'md2odp-ParagraphQuoteStyle') + para = odf_create_paragraph(style='md2odp-ParagraphQuoteStyle') span = odf_create_element('text:span') - span.set_text(u'“') + span.set_text('“') para.append(span) span = odf_create_element('text:span') @@ -590,12 +590,12 @@ def block_quote(self, text): para.append(span) span = odf_create_element('text:span') - span.set_text(u'”') + span.set_text('”') para.append(span) # pylint: disable=maybe-no-member - para.set_span(u'md2odp-TextQuoteStyle', regex=u'“') - para.set_span(u'md2odp-TextQuoteStyle', regex=u'”') + para.set_span('md2odp-TextQuoteStyle', regex='“') + para.set_span('md2odp-TextQuoteStyle', regex='”') return ODFPartialTree.from_metrics_provider([para], self) def list_item(self, text): @@ -607,7 +607,7 @@ def list_item(self, text): def list(self, body, ordered=True): # TODO: reverse-engineer magic to convert outline style to # numbering style - lst = odf_create_list(style=u'L1' if ordered else u'OutlineListStyle') + lst = odf_create_list(style='L1' if ordered else 'OutlineListStyle') for elem in body.get(): lst.append(elem) return ODFPartialTree.from_metrics_provider([lst], self) @@ -638,21 +638,21 @@ def autolink(self, link, is_email=False): text = link if is_email: link = 'mailto:%s' % link - lnk = odf_create_link(link, text=unicode(text)) + lnk = odf_create_link(link, text=str(text)) return ODFPartialTree.from_metrics_provider([lnk], self) def link(self, link, title, content): lnk = odf_create_link(link, text=content.get()[0].get_text(), - title=unicode(title)) + title=str(title)) return ODFPartialTree.from_metrics_provider([lnk], self) def codespan(self, text): span = odf_create_element('text:span') # pylint: disable=maybe-no-member span.set_style('md2odp-TextCodeStyle') - if isinstance(text, basestring): - span.set_text(unicode(text)) + if isinstance(text, str): + span.set_text(str(text)) else: for elem in text.get(): span.append(elem) @@ -677,7 +677,7 @@ def emphasis(self, text): def image(self, src, title, alt_text): # embed picture - TODO: optionally just link it media_type = guess_type(src) - parse = urlparse.urlparse(src) + parse = urllib.parse.urlparse(src) fragment_ext = parse[2].split('.')[-1] self.image_entry_id = hasher() fragment_name = 'Pictures/%s.%s' % (self.image_entry_id, @@ -691,9 +691,9 @@ def image(self, src, title, alt_text): if not fragment_ext.endswith('svg'): # delay our PIL dependency until really needed from PIL import Image - import cStringIO + import io - imagefile = cStringIO.StringIO(imagedata) + imagefile = io.StringIO(imagedata) # obtain image aspect ratio image_w, image_h = Image.open(imagefile).size @@ -724,16 +724,16 @@ def image(self, src, title, alt_text): image_h = frame_h frame_x += (frame_w - image_w) / 2 - args = {'style': u'md2odp-ImageStyle', - 'size': (u'%dcm' % image_w, u'%dcm' % image_h), - 'position': (u'%dcm' % frame_x, u'%dcm' % frame_y), - 'presentation_class': u'graphic'} + args = {'style': 'md2odp-ImageStyle', + 'size': ('%dcm' % image_w, '%dcm' % image_h), + 'position': ('%dcm' % frame_x, '%dcm' % frame_y), + 'presentation_class': 'graphic'} if title is not None: - args['text'] = unicode(title) + args['text'] = str(title) frame = odf_create_image_frame(fragment_name, **args) if alt_text is not None: - frame.set_svg_description(unicode(alt_text)) + frame.set_svg_description(str(alt_text)) self.doc_manifest.add_full_path(fragment_name, media_type[0]) @@ -810,9 +810,9 @@ def main(): (args.content_master is not None and args.content_master not in master_names)): - print _master_page_spew + '\n' + print(_master_page_spew + '\n') for i in master_names: - print ' - ' + i + print(' - ' + i) return breakheader_size = None @@ -850,7 +850,7 @@ def main(): frame.get_attribute('svg:y')) odf_renderer = ODFRenderer(presentation, - code_font_name=unicode(args.code_font_name), + code_font_name=str(args.code_font_name), break_master=args.break_master, breakheader_size=breakheader_size, breakheader_position=breakheader_position, diff --git a/test.py b/test.py index c101138..1e4a391 100644 --- a/test.py +++ b/test.py @@ -49,7 +49,7 @@ def setup(): global testdoc, odf_renderer, mkdown testdoc = odf_new_document('presentation') - odf_renderer = odpdown.ODFRenderer(testdoc,u'Nimbus Mono L') + odf_renderer = odpdown.ODFRenderer(testdoc,'Nimbus Mono L') mkdown = mistune.Markdown(renderer=odf_renderer) @@ -241,7 +241,7 @@ def test_weird_uris(): '''.strip() mkdown.render(markdown) - assert u'.svg' in [x[-4:] for x in testdoc.get_part( + assert '.svg' in [x[-4:] for x in testdoc.get_part( ODF_MANIFEST).get_paths()] From c1804ba3c2150b65a76df091f25f4f54618457bf Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Thu, 6 Jun 2024 13:32:58 +0200 Subject: [PATCH 3/7] Adjust various requirements The following requirements needed adjusting: * odfdo requires a minimum lxml version of 4.8.0 on python3.9 so it does not make sense to require a lower version here. * In a Python 3 environment one needs to use beautifulsoup4 * We need to ensure we get mistune < 2.0.0 because later versions introduced breaking API changes. --- requirements.txt | 10 +++++----- setup.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58d8fbc..a0827b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ --e git+https://github.com/lepture/mistune.git@v0.7.1#egg=mistune --e git+https://github.com/thorstenb/lpod-python.git#egg=lpod-python +beautifulsoup4 >= 4.8.2 cram>=0.6 -pygments>=1.6 +lxml >=3.4.4, <5.0.0 +mistune >0.7.1, <2.0.0 +odfdo>=3.7.7 pillow>=2.0 -BeautifulSoup -lxml>=3.4.4 +pygments>=1.6 diff --git a/setup.py b/setup.py index 3fc3052..5cd64bb 100644 --- a/setup.py +++ b/setup.py @@ -42,11 +42,12 @@ class install(setuptools.command.install.install): scripts=['odpdown'], license='BSD License', install_requires=[ - 'mistune>=0.7.1', - 'odfdo>=3.3.7', + 'beautifulsoup4 >= 4.8.2', + 'mistune>=0.7.1, <2.0.0', + 'odfdo>=3.7.7', 'pygments>=1.6', 'pillow>=2.0', - 'lxml>=3.4.4', + 'lxml>=3.4.4, <5.0.0', ], classifiers=[ 'Development Status :: 4 - Beta', From 745d760a01baea4355ddac28d210249e2cc387f3 Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Thu, 6 Jun 2024 14:48:18 +0200 Subject: [PATCH 4/7] Adapt to odfdo API The odfdo API has been refactored to be more object oriented so all code using lpod functions needed to be adapted to work with odfdo classes. --- odpdown.py | 166 ++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 85 deletions(-) diff --git a/odpdown.py b/odpdown.py index a978ed9..b193df6 100755 --- a/odpdown.py +++ b/odpdown.py @@ -50,17 +50,16 @@ from mimetypes import guess_type from uuid import uuid4 -from odfdo import ODF_MANIFEST, ODF_STYLES -from odfdo.document import odf_get_document -from odfdo.frame import odf_create_text_frame, odf_create_image_frame, odf_frame -from odfdo.draw_page import odf_create_draw_page, odf_draw_page -from odfdo.list import odf_create_list_item, odf_create_list -from odfdo.style import odf_create_style -from odfdo.paragraph import odf_create_paragraph, odf_span -from odfdo.paragraph import odf_create_line_break, odf_create_spaces -from odfdo.paragraph import odf_create_tabulation -from odfdo.element import odf_create_element -from odfdo.link import odf_create_link, odf_link +from odfdo.const import ODF_MANIFEST, ODF_STYLES +from odfdo.document import Document +from odfdo.frame import Frame +from odfdo.draw_page import DrawPage +from odfdo.list import List, ListItem +from odfdo.style import Style +from odfdo.paragraph import Paragraph, Span +from odfdo.paragraph import LineBreak, Spacer +from odfdo.paragraph import Tab +from odfdo.link import Link from pygments.lexers import get_lexer_by_name from pygments.formatter import Formatter @@ -88,15 +87,15 @@ # helper for unique hashes def hasher(): - return uuid4().get_hex() + return uuid4() # helper for ODFFormatter and ODFRenderer def add_style(document, style_family, style_name, properties, parent=None): """Insert global style into given document""" - style = odf_create_style(style_family, style_name, - style_name, parent) + style = Style(family=style_family, name=style_name, + display_name=style_name, parent_style=parent) for elem in properties: # pylint: disable=maybe-no-member style.set_properties(properties=elem[1], area=elem[0]) @@ -109,9 +108,9 @@ def wrap_spans(odf_elements): res = [] para = None for elem in odf_elements: - if isinstance(elem, odf_span) or isinstance(elem, odf_link): + if isinstance(elem, Span) or isinstance(elem, Link): if para is None: - para = odf_create_paragraph() + para = Paragraph() para.append(elem) else: if para is not None: @@ -139,18 +138,18 @@ def handle_whitespace(text): if part[:2] == ' ': # multiple spaces in ODF need markup result.append( - odf_create_spaces(len(part))) + Spacer(len(part))) elif part[:1] == '\t': # insert an actual tab result.append( - odf_create_tabulation()) + Tab()) else: result.append( str(part)) # for all but the last line: add linebreak if index < len(lines)-1: - result.append(odf_create_line_break()) + result.append(LineBreak()) return result @@ -180,28 +179,28 @@ def add_child_elems(self, elems): # TODO: kill this ugly typeswitching if (len(self._elements) and isinstance( - self._elements[-1], odf_draw_page) and not + self._elements[-1], DrawPage) and not isinstance( - elems[0], odf_draw_page)): + elems[0], DrawPage)): # stick additional frame content into last existing one for child in self._elements[-1].get_elements( 'descendant::draw:frame'): - if child.get_presentation_class() == 'outline': - text_box = child.get_children()[0] + if child.presentation_class == 'outline': + text_box = child.children[0] for elem in wrap_spans(elems): text_box.append(elem) return # special-case image frames - append to pages literally! - if isinstance(elems[0], odf_frame): + if isinstance(elems[0], Frame): for child in elems: self._elements[-1].append(child) else: # no outline frame found, create new one with elems content elems = wrap_spans(elems) self._elements[-1].append( - odf_create_text_frame( + Frame.text_frame( elems, presentation_style='md2odp-OutlineText', size=('%s' % self.outline_size[0], @@ -214,8 +213,8 @@ def add_child_elems(self, elems): def add_text(self, text): """Helper to ctext to self""" - span = odf_create_element('text:span') - span.set_text(str(text)) + span = Span() + span.text = str(text) self._elements.append(span) def __add__(self, other): @@ -271,14 +270,14 @@ def __init__(self, **options): # a style item is a tuple in the following form: # colors are readily specified in hex: 'RRGGBB' if style['color']: - root_elem = curr_elem = odf_create_element('text:span') + root_elem = curr_elem = Span() # pylint: disable=maybe-no-member - curr_elem.set_style('md2odp-TColor%s' % style['color']) + curr_elem.set_attribute(name='style', value='md2odp-TColor%s' % style['color']) if style['bold']: - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TBold') + span.set_attribute(name='style', value='md2odp-TBold') if root_elem is None: root_elem = curr_elem = span else: @@ -286,9 +285,9 @@ def __init__(self, **options): curr_elem = span if style['italic']: - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TItalic') + span.set_attribute(name='style', value='md2odp-TItalic') if root_elem is None: root_elem = curr_elem = span else: @@ -296,9 +295,9 @@ def __init__(self, **options): curr_elem = span if style['underline']: - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TUnderline') + span.set_attribute(name='style', value='md2odp-TUnderline') if root_elem is None: root_elem = curr_elem = span else: @@ -363,15 +362,15 @@ def format(self, tokensource): for elem in handle_whitespace(lastval): if isinstance(elem, str): if root_span is None: - span = odf_create_element('text:span') - span.set_text(elem) + span = Span() + span.text = elem result.append(span) else: # this is nasty - set text in # shared style instance, clone # afterwards - leaf_span.set_text(elem) - result.append(root_span.clone()) + leaf_span.text = elem + result.append(root_span.clone) else: result.append(elem) @@ -383,14 +382,14 @@ def format(self, tokensource): if lastval: root_span, leaf_span = self.styles[lasttype] if root_span is None: - span = odf_create_element('text:span') - span.set_text(str(lastval)) + span = Span() + span.text = str(lastval) result.append(span) else: # this is nasty - set text in shared style instance, # clone afterwards - leaf_span.set_text(str(lastval)) - result.append(root_span.clone()) + leaf_span.text = str(lastval) + result.append(root_span.clone) return result @@ -434,8 +433,8 @@ def __init__(self, # font/char styles self.document.insert_style( - odf_create_style( - 'font-face', + Style( + family='font-face', name=code_font_name, font_name=code_font_name, font_family=code_font_name, @@ -507,9 +506,10 @@ def __init__(self, if len(content_master_styles): # now stick that under custom name into automatic style section list_style = content_master_styles[0].get_elements( - 'style:graphic-properties/text:list-style[1]')[0].clone() + 'style:graphic-properties/text:list-style[1]')[0].clone list_style.set_attribute('style:name', 'OutlineListStyle') - document.insert_style(list_style, automatic=True) + list_style.family=('presentation') + document.insert_style(style=list_style, automatic=True) else: print('WARNING: no outline list style found for ' \ 'master page "%s"!' % self.content_master) @@ -521,7 +521,7 @@ def placeholder(self): return ODFPartialTree.from_metrics_provider([], self) def block_code(self, code, language=None): - para = odf_create_paragraph(style='md2odp-ParagraphCodeStyle') + para = Paragraph(style='md2odp-ParagraphCodeStyle') if language is not None: # explicit lang given, use syntax highlighting @@ -533,8 +533,8 @@ def block_code(self, code, language=None): # no lang given, use plain monospace formatting for elem in handle_whitespace(code): if isinstance(elem, str): - span = odf_create_element('text:span') - span.set_text(elem) + span = Span() + span.text = elem para.append(span) else: para.append(elem) @@ -544,13 +544,13 @@ def block_code(self, code, language=None): def header(self, text, level, raw=None): page = None if level == 1: - page = odf_create_draw_page( + page = DrawPage( 'page1', name=hasher(), master_page=self.break_master, presentation_page_layout='AL3T19') page.append( - odf_create_text_frame( + Frame.text_frame( wrap_spans(text.get()), presentation_style='md2odp-BreakTitleText', size=('%s' % self.breakheader_size[0], @@ -559,13 +559,13 @@ def header(self, text, level, raw=None): '%s' % self.breakheader_position[1]), presentation_class='title')) elif level == 2: - page = odf_create_draw_page( - 'page1', + page = DrawPage( + draw_id='page1', name=hasher(), master_page=self.content_master, presentation_page_layout='AL3T1') page.append( - odf_create_text_frame( + Frame.text_frame( wrap_spans(text.get()), presentation_style='md2odp-TitleText', size=('%s' % self.header_size[0], @@ -579,18 +579,18 @@ def header(self, text, level, raw=None): return ODFPartialTree.from_metrics_provider([page], self) def block_quote(self, text): - para = odf_create_paragraph(style='md2odp-ParagraphQuoteStyle') - span = odf_create_element('text:span') - span.set_text('“') + para = Paragraph(style='md2odp-ParagraphQuoteStyle') + span = Span() + span.text = '“' para.append(span) - span = odf_create_element('text:span') + span = Span() for elem in text.get(): span.append(elem) para.append(span) - span = odf_create_element('text:span') - span.set_text('”') + span = Span() + span.text = '”' para.append(span) # pylint: disable=maybe-no-member @@ -599,7 +599,7 @@ def block_quote(self, text): return ODFPartialTree.from_metrics_provider([para], self) def list_item(self, text): - item = odf_create_list_item() + item = ListItem() for elem in wrap_spans(text.get()): item.append(elem) return ODFPartialTree.from_metrics_provider([item], self) @@ -607,20 +607,20 @@ def list_item(self, text): def list(self, body, ordered=True): # TODO: reverse-engineer magic to convert outline style to # numbering style - lst = odf_create_list(style='L1' if ordered else 'OutlineListStyle') + lst = List(style='L1' if ordered else 'OutlineListStyle') for elem in body.get(): lst.append(elem) return ODFPartialTree.from_metrics_provider([lst], self) def paragraph(self, text): # images? insert as standalone frame, no inline img - if isinstance(text.get()[0], odf_frame): + if isinstance(text.get()[0], Frame): return text else: # yes, this seem broadly illogical. but most 'paragraphs' # actually end up being parts of tables, quotes, list items # etc, which might not always permit text:p - span = odf_create_element('text:span') + span = Span() for elem in text.get(): span.append(elem) return ODFPartialTree.from_metrics_provider([span], self) @@ -638,38 +638,38 @@ def autolink(self, link, is_email=False): text = link if is_email: link = 'mailto:%s' % link - lnk = odf_create_link(link, text=str(text)) + lnk = Link(url=link, text=str(text)) return ODFPartialTree.from_metrics_provider([lnk], self) def link(self, link, title, content): - lnk = odf_create_link(link, - text=content.get()[0].get_text(), - title=str(title)) + lnk = Link(url=link, + text=content.get()[0].get_text(), + title=str(title)) return ODFPartialTree.from_metrics_provider([lnk], self) def codespan(self, text): - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TextCodeStyle') + span.set_attribute(name='style', value='md2odp-TextCodeStyle') if isinstance(text, str): - span.set_text(str(text)) + span.text = str(text) else: for elem in text.get(): span.append(elem) return ODFPartialTree.from_metrics_provider([span], self) def double_emphasis(self, text): - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TextDoubleEmphasisStyle') + span.set_attribute(name='style', value='md2odp-TextDoubleEmphasisStyle') for elem in text.get(): span.append(elem) return ODFPartialTree.from_metrics_provider([span], self) def emphasis(self, text): - span = odf_create_element('text:span') + span = Span() # pylint: disable=maybe-no-member - span.set_style('md2odp-TextEmphasisStyle') + span.set_attribute(name='style', value='md2odp-TextEmphasisStyle') for elem in text.get(): span.append(elem) return ODFPartialTree.from_metrics_provider([span], self) @@ -730,7 +730,7 @@ def image(self, src, title, alt_text): 'presentation_class': 'graphic'} if title is not None: args['text'] = str(title) - frame = odf_create_image_frame(fragment_name, **args) + frame = Frame.image_frame(fragment_name, **args) if alt_text is not None: frame.set_svg_description(str(alt_text)) @@ -742,7 +742,7 @@ def image(self, src, title, alt_text): return ODFPartialTree.from_metrics_provider([frame], self) def linebreak(self): - return ODFPartialTree.from_metrics_provider([odf_create_line_break()], + return ODFPartialTree.from_metrics_provider([LineBreak()], self) def tag(self, html): @@ -764,10 +764,8 @@ def main(): parser.add_argument('input_md', help='Input markdown file') parser.add_argument('template_odp', - type=argparse.FileType('r'), help='Input ODP template file') parser.add_argument('output_odp', - type=argparse.FileType('w'), help='Output ODP file') parser.add_argument('-p', '--page', default=-1, type=int, help='Append markdown after given page. Negative ' @@ -798,9 +796,7 @@ def main(): markdown = (codecs.getreader("utf-8")(sys.stdin) if args.input_md == '-' else codecs.open(args.input_md, 'r', encoding='utf-8')) - odf_in = args.template_odp - odf_out = args.output_odp - presentation = odf_get_document(odf_in) + presentation = Document(args.template_odp) master_pages = presentation.get_part(ODF_STYLES).get_elements( 'descendant::style:master-page') @@ -863,7 +859,7 @@ def main(): highlight_style=args.highlight_style) mkdown = mistune.Markdown(renderer=odf_renderer) - doc_elems = presentation.get_body() + doc_elems = presentation.body if args.page < 0: args.page = len(doc_elems.get_children()) + args.page @@ -873,7 +869,7 @@ def main(): for index, page in enumerate(pages.get()): doc_elems.insert(page, position=args.page + index) - presentation.save(target=odf_out, pretty=False) + presentation.save(target=args.output_odp, pretty=False) if __name__ == "__main__": main() From df0c93940c5fa0c7c3c028ce953c85022c36afdc Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Thu, 6 Jun 2024 23:50:08 +0200 Subject: [PATCH 5/7] Fixed tests and regressions. --- odpdown | 1 + odpdown.py | 44 ++++++++++++++++++++++++++------------------ test.py | 53 +++++++++++++++++++++++++++-------------------------- tox.ini | 2 +- 4 files changed, 55 insertions(+), 45 deletions(-) diff --git a/odpdown b/odpdown index bacd316..6799391 100755 --- a/odpdown +++ b/odpdown @@ -5,5 +5,6 @@ def main(): import odpdown odpdown.main() + if __name__ == "__main__": main() diff --git a/odpdown.py b/odpdown.py index b193df6..c5c37c1 100755 --- a/odpdown.py +++ b/odpdown.py @@ -35,16 +35,18 @@ # POSSIBILITY OF SUCH DAMAGE. # -# use of odfdo (successor of lpod) inspired by Bart Hanssens' odflinkchecker.py, +# use of odfdo (successor of lpod) inspired by Bart Hanssens' +# odflinkchecker.py, # https://lists.oasis-open.org/archives/opendocument-users/201008/msg00004.html # and lpod-python-recipes # -import mistune import argparse -import urllib.parse import codecs -import sys +import io +import mistune import re +import sys +import urllib.parse from urllib.request import urlopen from mimetypes import guess_type @@ -95,7 +97,7 @@ def add_style(document, style_family, style_name, properties, parent=None): """Insert global style into given document""" style = Style(family=style_family, name=style_name, - display_name=style_name, parent_style=parent) + display_name=style_name, parent_style=parent) for elem in properties: # pylint: disable=maybe-no-member style.set_properties(properties=elem[1], area=elem[0]) @@ -121,6 +123,7 @@ def wrap_spans(odf_elements): res.append(para) return res + # buffer regex for tab/space splitting for block code _whitespace_re = re.compile('( {2,}|\t)', re.UNICODE) @@ -272,7 +275,9 @@ def __init__(self, **options): if style['color']: root_elem = curr_elem = Span() # pylint: disable=maybe-no-member - curr_elem.set_attribute(name='style', value='md2odp-TColor%s' % style['color']) + curr_elem.set_attribute( + name='style', + value='md2odp-TColor%s' % style['color']) if style['bold']: span = Span() @@ -508,10 +513,10 @@ def __init__(self, list_style = content_master_styles[0].get_elements( 'style:graphic-properties/text:list-style[1]')[0].clone list_style.set_attribute('style:name', 'OutlineListStyle') - list_style.family=('presentation') + list_style.family = 'presentation' document.insert_style(style=list_style, automatic=True) else: - print('WARNING: no outline list style found for ' \ + print('WARNING: no outline list style found for ' 'master page "%s"!' % self.content_master) # delegate to pygments formatter for their styles @@ -643,7 +648,7 @@ def autolink(self, link, is_email=False): def link(self, link, title, content): lnk = Link(url=link, - text=content.get()[0].get_text(), + text=content.get()[0].text, title=str(title)) return ODFPartialTree.from_metrics_provider([lnk], self) @@ -661,7 +666,8 @@ def codespan(self, text): def double_emphasis(self, text): span = Span() # pylint: disable=maybe-no-member - span.set_attribute(name='style', value='md2odp-TextDoubleEmphasisStyle') + span.set_attribute(name='style', + value='md2odp-TextDoubleEmphasisStyle') for elem in text.get(): span.append(elem) return ODFPartialTree.from_metrics_provider([span], self) @@ -700,13 +706,12 @@ def image(self, src, title, alt_text): else: # PIL does not really support svg, so let's try heuristics # & find the aspect ratio ourselves - from BeautifulSoup import BeautifulSoup + from bs4 import BeautifulSoup - imagefile = BeautifulSoup(imagedata) + imagefile = BeautifulSoup(imagedata, features='xml') image_w = float(imagefile.svg['width']) image_h = float(imagefile.svg['height']) - except: - # unable to extract aspect ratio + except Exception: image_w, image_h = (100, 100) image_ratio = image_w / float(image_h) @@ -733,7 +738,7 @@ def image(self, src, title, alt_text): frame = Frame.image_frame(fragment_name, **args) if alt_text is not None: - frame.set_svg_description(str(alt_text)) + frame.svg_description = str(alt_text) self.doc_manifest.add_full_path(fragment_name, media_type[0]) @@ -794,8 +799,10 @@ def main(): ' empty or unknown name') args = parser.parse_args() - markdown = (codecs.getreader("utf-8")(sys.stdin) if args.input_md == '-' - else codecs.open(args.input_md, 'r', encoding='utf-8')) + if args.input_md == '-': + markdown = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') + else: + markdown = codecs.open(args.input_md, 'r', encoding='utf-8') presentation = Document(args.template_odp) master_pages = presentation.get_part(ODF_STYLES).get_elements( @@ -861,7 +868,7 @@ def main(): doc_elems = presentation.body if args.page < 0: - args.page = len(doc_elems.get_children()) + args.page + args.page = len(doc_elems.children) + args.page pages = mkdown.render(markdown.read()) if isinstance(pages, ODFPartialTree): @@ -871,5 +878,6 @@ def main(): presentation.save(target=args.output_odp, pretty=False) + if __name__ == "__main__": main() diff --git a/test.py b/test.py index 1e4a391..952c91d 100644 --- a/test.py +++ b/test.py @@ -36,9 +36,9 @@ import odpdown import mistune import codecs -from lpod import ODF_MANIFEST -from lpod.document import odf_new_document -from lpod.draw_page import odf_draw_page +from odfdo.const import ODF_MANIFEST +from odfdo.document import Document +from odfdo.draw_page import DrawPage from nose.tools import with_setup, raises testdoc = None @@ -48,7 +48,7 @@ def setup(): global testdoc, odf_renderer, mkdown - testdoc = odf_new_document('presentation') + testdoc = Document('presentation') odf_renderer = odpdown.ODFRenderer(testdoc,'Nimbus Mono L') mkdown = mistune.Markdown(renderer=odf_renderer) @@ -58,10 +58,10 @@ def test_heading1(): markdown = '# Heading 1' odf = mkdown.render(markdown) assert len(odf.get()) == 1 - assert isinstance(odf.get()[0], odf_draw_page) + assert isinstance(odf.get()[0], DrawPage) assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 1 assert odf.get()[0].get_elements( - 'descendant::text:span')[0].get_text() == 'Heading 1' + 'descendant::text:span')[0].text == 'Heading 1' @with_setup(setup) @@ -69,10 +69,10 @@ def test_heading2(): markdown = '## Heading 2' odf = mkdown.render(markdown) assert len(odf.get()) == 1 - assert isinstance(odf.get()[0], odf_draw_page) + assert isinstance(odf.get()[0], DrawPage) assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 1 assert odf.get()[0].get_elements( - 'descendant::text:span')[0].get_text() == 'Heading 2' + 'descendant::text:span')[0].text == 'Heading 2' @raises(RuntimeError) @@ -93,9 +93,9 @@ def test_simple_page(): odf = mkdown.render(markdown) assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 - assert (odf.get()[0].get_elements('descendant::text:span')[0].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[0].text == 'Heading') - assert (odf.get()[0].get_elements('descendant::text:span')[2].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[2].text == 'This is a sample paragraph.') @@ -111,18 +111,18 @@ def test_items_page(): odf = mkdown.render(markdown) assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 - assert (odf.get()[0].get_elements('descendant::text:span')[0].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[0].text == 'Heading') items = odf.get()[0].get_elements('descendant::text:list-item') assert len(items) == 3 assert items[0].get_elements( - 'descendant::text:span')[0].get_text() == 'this is item one' + 'descendant::text:span')[0].text == 'this is item one' assert items[1].get_elements( - 'descendant::text:span')[0].get_text() == 'this is item two' + 'descendant::text:span')[0].text == 'this is item two' subitems = items[1].get_elements('descendant::text:list-item') assert len(subitems) == 1 assert subitems[0].get_elements( - 'descendant::text:span')[0].get_text() == 'and a subitem' + 'descendant::text:span')[0].text == 'and a subitem' @with_setup(setup) @@ -140,11 +140,11 @@ def test_empty_list_items_page(): items = odf.get()[0].get_elements('descendant::text:list-item') assert len(items) == 3 assert items[0].get_elements( - 'descendant::text:span')[0].get_text() == 'a' + 'descendant::text:span')[0].text == 'a' assert len(items[1].get_elements( 'descendant::text:span')) == 0 assert items[2].get_elements( - 'descendant::text:span')[0].get_text() == 'c' + 'descendant::text:span')[0].text == 'c' @with_setup(setup) @@ -158,11 +158,11 @@ def test_xml_entity_escaping(): assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 # mistune splits text at '<', so we get two spans here - assert (odf.get()[0].get_elements('descendant::text:span')[2].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[2].text == 'There "is" ') - assert (odf.get()[0].get_elements('descendant::text:span')[3].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[3].text == '') - assert (odf.get()[0].get_elements('descendant::text:span')[4].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[4].text == ' & \'the\' other to escape') @@ -177,10 +177,10 @@ def test_nested_emphasis(): assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 assert (odf.get()[0].get_elements('descendant::text:span')[2].get_attribute( - 'text:style-name') == 'md2odp-TextDoubleEmphasisStyle') + 'style') == 'md2odp-TextDoubleEmphasisStyle') assert (odf.get()[0].get_elements('descendant::text:span')[3].get_attribute( - 'text:style-name') == 'md2odp-TextEmphasisStyle') - assert (odf.get()[0].get_elements('descendant::text:span')[4].get_text() == + 'style') == 'md2odp-TextEmphasisStyle') + assert (odf.get()[0].get_elements('descendant::text:span')[4].text == 'triple emphasis') @@ -199,7 +199,7 @@ def test_code_block(): odf = mkdown.render(markdown) assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 - assert (odf.get()[0].get_elements('descendant::text:span')[0].get_text() == + assert (odf.get()[0].get_elements('descendant::text:span')[0].text == 'Heading') spaces = odf.get()[0].get_elements('descendant::text:s') assert len(spaces) == 1 @@ -225,6 +225,7 @@ def test_svg1(): odf = mkdown.render(markdown) assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 + print(odf.get()[0].get_elements('descendant::draw:frame')[1].serialize()) assert odf.get()[0].get_elements('descendant::draw:frame')[1].get_attribute( 'svg:width') == '22cm' assert odf.get()[0].get_elements('descendant::draw:frame')[1].get_attribute( @@ -256,7 +257,7 @@ def test_single_space_bug(): ~~~ '''.strip() odf = mkdown.render(markdown) - assert odf.get()[0].get_elements('descendant::text:span')[7].get_text() == ' -eq ' + assert odf.get()[0].get_elements('descendant::text:span')[8].text == '-eq' @with_setup(setup) @@ -271,6 +272,6 @@ def test_multiline_bold(): assert len(odf.get()) == 1 assert len(odf.get()[0].get_elements('descendant::draw:frame')) == 2 assert (odf.get()[0].get_elements('descendant::text:span')[2].get_attribute( - 'text:style-name') == 'md2odp-TextDoubleEmphasisStyle') - assert (odf.get()[0].get_elements('descendant::text:span')[3].get_text() == + 'style') == 'md2odp-TextDoubleEmphasisStyle') + assert (odf.get()[0].get_elements('descendant::text:span')[3].text == 'bold text\nnext line bold') diff --git a/tox.ini b/tox.ini index 840ddb9..27b21ed 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,flake8 +envlist = py39,flake8 [testenv] whitelist_externals = cram From 569ec7fde153510d479e7bd7d312e065f00bf768 Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Fri, 7 Jun 2024 08:09:17 +0000 Subject: [PATCH 6/7] Switch to pynose for tests With Python versions later than 3.9, nose tests will break due to nose AttributeError: module 'collections' has no attribute 'Callable' Switching to pynose which accounts for later Python versions fixes the problem. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 27b21ed..c31f271 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,10 @@ [tox] -envlist = py39,flake8 +envlist = py3,flake8 [testenv] whitelist_externals = cram deps = -rrequirements.txt - nose + pynose commands = nosetests cram cramtest From 97df48833e621546e7092ea381ca886605ad02ad Mon Sep 17 00:00:00 2001 From: Johannes Grassler Date: Fri, 7 Jun 2024 08:17:28 +0000 Subject: [PATCH 7/7] Added python_requires constraint. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5cd64bb..71cb195 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ class install(setuptools.command.install.install): author_email='tbehrens@acm.org', url='https://github.com/thorstenb/odpdown.git', long_description=open('README.md').read(), + python_requires = '>=3.9,<4', py_modules=['odpdown'], scripts=['odpdown'], license='BSD License',