Skip to content

Commit

Permalink
Add escaping options to HTML tables (#305)
Browse files Browse the repository at this point in the history
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
  • Loading branch information
MrMarble and hugovk authored Aug 4, 2024
1 parent a49f90b commit 467dcc3
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 8 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,16 @@ will print:
</table>
```

#### Setting HTML escaping

By default, PrettyTable will escape the data contained in the header and data fields
when sending output to HTML. This can be disabled by setting the `escape_header` and
`escape_data` to false. For example:

```python
print(table.get_html_string(escape_header=False, escape_data=False))
```

### Miscellaneous things

#### Copying a table
Expand Down
59 changes: 51 additions & 8 deletions src/prettytable/prettytable.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ def __init__(self, field_names=None, **kwargs) -> None:
"max_width",
"min_width",
"none_format",
"escape_header",
"escape_data",
]
for option in self._options:
if option in kwargs:
Expand Down Expand Up @@ -224,6 +226,14 @@ def __init__(self, field_names=None, **kwargs) -> None:
self._reversesort = False
self._sort_key = kwargs["sort_key"] or (lambda x: x)

if kwargs["escape_data"] in (True, False):
self._escape_data = kwargs["escape_data"]
else:
self._escape_data = True
if kwargs["escape_header"] in (True, False):
self._escape_header = kwargs["escape_header"]
else:
self._escape_header = True
# Column specific arguments, use property.setters
self.align = kwargs["align"] or {}
self.valign = kwargs["valign"] or {}
Expand Down Expand Up @@ -380,6 +390,8 @@ def _validate_option(self, option, val) -> None:
"xhtml",
"print_empty",
"oldsortslice",
"escape_header",
"escape_data",
):
self._validate_true_or_false(option, val)
elif option == "header_style":
Expand Down Expand Up @@ -1252,6 +1264,26 @@ def oldsortslice(self, val) -> None:
self._validate_option("oldsortslice", val)
self._oldsortslice = val

@property
def escape_header(self):
"""Escapes the text within a header (True or False)"""
return self._escape_header

@escape_header.setter
def escape_header(self, val):
self._validate_option("escape_header", val)
self._escape_header = val

@property
def escape_data(self):
"""Escapes the text within a data field (True or False)"""
return self._escape_data

@escape_data.setter
def escape_data(self, val):
self._validate_option("escape_data", val)
self._escape_data = val

##############################
# OPTION MIXER #
##############################
Expand Down Expand Up @@ -2136,6 +2168,7 @@ def get_html_string(self, **kwargs) -> str:
end - index of last data row to include in output PLUS ONE (list slice style)
fields - names of fields (columns) to include
header - print a header showing field names (True or False)
escape_header - escapes the text within a header (True or False)
border - print a border around the table (True or False)
preserve_internal_border - print a border inside the table even if
border is disabled (True or False)
Expand All @@ -2156,6 +2189,7 @@ def get_html_string(self, **kwargs) -> str:
<table> tag
format - Controls whether or not HTML tables are formatted to match
styling options (True or False)
escape_data - escapes the text within a data field (True or False)
xhtml - print <br/> tags if True, <br> tags if False"""

options = self._get_options(kwargs)
Expand Down Expand Up @@ -2197,11 +2231,13 @@ def _get_simple_html_string(self, options):
for field in self._field_names:
if options["fields"] and field not in options["fields"]:
continue
if options["escape_header"]:
field = escape(field)

lines.append(
" <th>{}</th>".format(
escape(field).replace("\n", linebreak)
)
" <th>{}</th>".format(field.replace("\n", linebreak))
)

lines.append(" </tr>")
lines.append(" </thead>")

Expand All @@ -2214,10 +2250,11 @@ def _get_simple_html_string(self, options):
for field, datum in zip(self._field_names, row):
if options["fields"] and field not in options["fields"]:
continue
if options["escape_data"]:
datum = escape(datum)

lines.append(
" <td>{}</td>".format(
escape(datum).replace("\n", linebreak)
)
" <td>{}</td>".format(datum.replace("\n", linebreak))
)
lines.append(" </tr>")
lines.append(" </tbody>")
Expand Down Expand Up @@ -2273,9 +2310,12 @@ def _get_formatted_html_string(self, options):
for field in self._field_names:
if options["fields"] and field not in options["fields"]:
continue
if options["escape_header"]:
field = escape(field)

lines.append(
' <th style="padding-left: %dem; padding-right: %dem; text-align: center">%s</th>' # noqa: E501
% (lpad, rpad, escape(field).replace("\n", linebreak))
% (lpad, rpad, field.replace("\n", linebreak))
)
lines.append(" </tr>")
lines.append(" </thead>")
Expand All @@ -2300,14 +2340,17 @@ def _get_formatted_html_string(self, options):
):
if options["fields"] and field not in options["fields"]:
continue
if options["escape_data"]:
datum = escape(datum)

lines.append(
' <td style="padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s">%s</td>' # noqa: E501
% (
lpad,
rpad,
align,
valign,
escape(datum).replace("\n", linebreak),
datum.replace("\n", linebreak),
)
)
lines.append(" </tr>")
Expand Down
228 changes: 228 additions & 0 deletions tests/test_prettytable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,234 @@ def test_html_output_formatted_with_title(self) -> None:
</tr>
</tbody>
</table>
""".strip() # noqa: E501
)

def test_html_output_without_escaped_header(self) -> None:
t = helper_table(rows=0)
t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
result = t.get_html_string(escape_header=False)
assert (
result.strip()
== """
<table>
<thead>
<tr>
<th></th>
<th>Field 1</th>
<th><em>Field 2</em></th>
<th><a href='#'>Field 3</a></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
""".strip()
)

def test_html_output_without_escaped_data(self) -> None:
t = helper_table(rows=0)
t.add_row(
[
1,
"<b>value 1</b>",
"<span style='text-decoration: underline;'>value2</span>",
"<a href='#'>value3</a>",
]
)
result = t.get_html_string(escape_data=False)
assert (
result.strip()
== """
<table>
<thead>
<tr>
<th></th>
<th>Field 1</th>
<th>Field 2</th>
<th>Field 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><b>value 1</b></td>
<td><span style='text-decoration: underline;'>value2</span></td>
<td><a href='#'>value3</a></td>
</tr>
</tbody>
</table>
""".strip()
)

def test_html_output_with_escaped_header(self) -> None:
t = helper_table(rows=0)
t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
result = t.get_html_string(escape_header=True)
assert (
result.strip()
== """
<table>
<thead>
<tr>
<th></th>
<th>Field 1</th>
<th>&lt;em&gt;Field 2&lt;/em&gt;</th>
<th>&lt;a href=&#x27;#&#x27;&gt;Field 3&lt;/a&gt;</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
""".strip()
)

def test_html_output_with_escaped_data(self) -> None:
t = helper_table(rows=0)
t.add_row(
[
1,
"<b>value 1</b>",
"<span style='text-decoration: underline;'>value2</span>",
"<a href='#'>value3</a>",
]
)
result = t.get_html_string(escape_data=True)
assert (
result.strip()
== """
<table>
<thead>
<tr>
<th></th>
<th>Field 1</th>
<th>Field 2</th>
<th>Field 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>&lt;b&gt;value 1&lt;/b&gt;</td>
<td>&lt;span style=&#x27;text-decoration: underline;&#x27;&gt;value2&lt;/span&gt;</td>
<td>&lt;a href=&#x27;#&#x27;&gt;value3&lt;/a&gt;</td>
</tr>
</tbody>
</table>
""".strip() # noqa: E501
)

def test_html_output_formatted_without_escaped_header(self) -> None:
t = helper_table(rows=0)
t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
result = t.get_html_string(escape_header=False, format=True)
assert (
result.strip()
== """
<table frame="box" rules="cols">
<thead>
<tr>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
""".strip() # noqa: E501
)

def test_html_output_formatted_without_escaped_data(self) -> None:
t = helper_table(rows=0)
t.add_row(
[
1,
"<b>value 1</b>",
"<span style='text-decoration: underline;'>value2</span>",
"<a href='#'>value3</a>",
]
)
result = t.get_html_string(escape_data=False, format=True)
assert (
result.strip()
== """
<table frame="box" rules="cols">
<thead>
<tr>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td>
</tr>
</tbody>
</table>
""".strip() # noqa: E501
)

def test_html_output_formatted_with_escaped_header(self) -> None:
t = helper_table(rows=0)
t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
result = t.get_html_string(escape_header=True, format=True)
assert (
result.strip()
== """
<table frame="box" rules="cols">
<thead>
<tr>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">&lt;em&gt;Field 2&lt;/em&gt;</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">&lt;a href=&#x27;#&#x27;&gt;Field 3&lt;/a&gt;</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
""".strip() # noqa: E501
)

def test_html_output_formatted_with_escaped_data(self) -> None:
t = helper_table(rows=0)
t.add_row(
[
1,
"<b>value 1</b>",
"<span style='text-decoration: underline;'>value2</span>",
"<a href='#'>value3</a>",
]
)
result = t.get_html_string(escape_data=True, format=True)
assert (
result.strip()
== """
<table frame="box" rules="cols">
<thead>
<tr>
<th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
<th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;b&gt;value 1&lt;/b&gt;</td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;span style=&#x27;text-decoration: underline;&#x27;&gt;value2&lt;/span&gt;</td>
<td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;a href=&#x27;#&#x27;&gt;value3&lt;/a&gt;</td>
</tr>
</tbody>
</table>
""".strip() # noqa: E501
)

Expand Down

0 comments on commit 467dcc3

Please sign in to comment.