Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to disable escaping HTML tables #305

Merged
merged 7 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
MrMarble marked this conversation as resolved.
Show resolved Hide resolved

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)
MrMarble marked this conversation as resolved.
Show resolved Hide resolved

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
Loading