diff --git a/HISTORY.md b/HISTORY.md index c9425802..13640879 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # History +## Unreleased + +### Changes + +- The html export format does not depend on MarkupPy any longer, therefore the + tablib[html] install target was removed also. + ## 3.5.0 (2023-06-11) ### Improvements diff --git a/docs/formats.rst b/docs/formats.rst index 268c3c83..919394b7 100644 --- a/docs/formats.rst +++ b/docs/formats.rst @@ -101,9 +101,6 @@ The ``html`` format is currently export-only. The exports produce an HTML page with the data in a ````. If headers have been set, they will be used as table headers. -This format is optional, install Tablib with ``pip install "tablib[html]"`` to -make the format available. - jira ==== diff --git a/pyproject.toml b/pyproject.toml index ab89ab45..69e34ebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ dynamic = ["version"] [project.optional-dependencies] all = [ - "markuppy", "odfpy", "openpyxl>=2.6.0", "pandas", @@ -44,7 +43,6 @@ all = [ "xlwt", ] cli = ["tabulate"] -html = ["markuppy"] ods = ["odfpy"] pandas = ["pandas"] xls = ["xlrd", "xlwt"] diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py index 0756ab1e..1ce25142 100644 --- a/src/tablib/formats/__init__.py +++ b/src/tablib/formats/__init__.py @@ -15,7 +15,6 @@ uninstalled_format_messages = { "cli": {"package_name": "tabulate package", "extras_name": "cli"}, "df": {"package_name": "pandas package", "extras_name": "pandas"}, - "html": {"package_name": "MarkupPy package", "extras_name": "html"}, "ods": {"package_name": "odfpy package", "extras_name": "ods"}, "xls": {"package_name": "xlrd and xlwt packages", "extras_name": "xls"}, "xlsx": {"package_name": "openpyxl package", "extras_name": "xlsx"}, @@ -101,8 +100,7 @@ def register_builtins(self): if find_spec('odf'): self.register('ods', 'tablib.formats._ods.ODSFormat') self.register('dbf', 'tablib.formats._dbf.DBFFormat') - if find_spec('MarkupPy'): - self.register('html', 'tablib.formats._html.HTMLFormat') + self.register('html', 'tablib.formats._html.HTMLFormat') self.register('jira', 'tablib.formats._jira.JIRAFormat') self.register('latex', 'tablib.formats._latex.LATEXFormat') if find_spec('pandas'): diff --git a/src/tablib/formats/_html.py b/src/tablib/formats/_html.py index b362eae0..373620d2 100644 --- a/src/tablib/formats/_html.py +++ b/src/tablib/formats/_html.py @@ -1,10 +1,6 @@ """ Tablib - HTML export support. """ - -import codecs -from io import BytesIO - -from MarkupPy import markup +from xml.etree import ElementTree as ET class HTMLFormat: @@ -17,48 +13,38 @@ class HTMLFormat: def export_set(cls, dataset): """HTML representation of a Dataset.""" - stream = BytesIO() - - page = markup.page() - page.table.open() - + table = ET.Element('table') if dataset.headers is not None: - new_header = [item if item is not None else '' for item in dataset.headers] - - page.thead.open() - headers = markup.oneliner.th(new_header) - page.tr(headers) - page.thead.close() - - page.tbody.open() + head = ET.Element('thead') + tr = ET.Element('tr') + for header in dataset.headers: + th = ET.Element('th') + th.text = str(header) if header is not None else '' + tr.append(th) + head.append(tr) + table.append(head) + + body = ET.Element('tbody') for row in dataset: - new_row = [item if item is not None else '' for item in row] + tr = ET.Element('tr') + for item in row: + td = ET.Element('td') + td.text = str(item) if item is not None else '' + tr.append(td) + body.append(tr) + table.append(body) - html_row = markup.oneliner.td(new_row) - page.tr(html_row) - page.tbody.close() - - page.table.close() - - # Allow unicode characters in output - wrapper = codecs.getwriter("utf8")(stream) - wrapper.writelines(str(page)) - - return stream.getvalue().decode('utf-8') + return ET.tostring(table, method='html', encoding='unicode') @classmethod def export_book(cls, databook): """HTML representation of a Databook.""" - stream = BytesIO() - - # Allow unicode characters in output - wrapper = codecs.getwriter("utf8")(stream) - + result = '' for i, dset in enumerate(databook._datasets): - title = (dset.title if dset.title else 'Set %s' % (i)) - wrapper.write(f'<{cls.BOOK_ENDINGS}>{title}\n') - wrapper.write(dset.html) - wrapper.write('\n') + title = dset.title if dset.title else f'Set {i}' + result += f'<{cls.BOOK_ENDINGS}>{title}\n' + result += dset.html + result += '\n' - return stream.getvalue().decode('utf-8') + return result diff --git a/tests/requirements.txt b/tests/requirements.txt index abfc1b5f..d2f56f91 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,5 @@ pytest pytest-cov -MarkupPy odfpy openpyxl>=2.6.0 pyyaml diff --git a/tests/test_tablib.py b/tests/test_tablib.py index a3d86b48..11a92fa6 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -14,7 +14,6 @@ from uuid import uuid4 import pytest -from MarkupPy import markup from openpyxl.reader.excel import load_workbook import tablib @@ -624,47 +623,49 @@ def test_row_has_tags(self): class HTMLTests(BaseTestCase): - def test_html_export(self): + founders_html = ( + "
" + "" + "" + "" + "" + "" + "" + "" + "" + "
first_namelast_namegpa
JohnAdams90
GeorgeWashington67
ThomasJefferson50
" + ) + + def test_html_dataset_export(self): """HTML export""" - - html = markup.page() - html.table.open() - html.thead.open() - - html.tr(markup.oneliner.th(self.founders.headers)) - html.thead.close() - - html.tbody.open() - for founder in self.founders: - html.tr(markup.oneliner.td(founder)) - html.tbody.close() - - html.table.close() - html = str(html) - - self.assertEqual(html, self.founders.html) + self.assertEqual(self.founders_html, self.founders.html.replace('\n', '')) def test_html_export_none_value(self): """HTML export""" - html = markup.page() - html.table.open() - html.thead.open() - - html.tr(markup.oneliner.th(['foo', '', 'bar'])) - html.thead.close() - - html.tbody.open() - html.tr(markup.oneliner.td(['foo', '', 'bar'])) - html.tbody.close() - - html.table.close() - html = str(html) - headers = ['foo', None, 'bar'] - d = tablib.Dataset(['foo', None, 'bar'], headers=headers) + d = tablib.Dataset(['foø', None, 'bar'], headers=headers) + expected = ( + "" + "" + "" + "" + "" + "" + "" + "
foobar
foøbar
" + ) + self.assertEqual(expected, d.html.replace('\n', '')) - self.assertEqual(html, d.html) + def test_html_databook_export(self): + book = tablib.Databook() + book.add_sheet(self.founders) + book.add_sheet(self.founders) + self.maxDiff = None + self.assertEqual( + book.html.replace('\n', ''), + f"

Founders

{self.founders_html}

Founders

{self.founders_html}" + ) class RSTTests(BaseTestCase):