diff --git a/README.md b/README.md index aa442709..bb365317 100644 --- a/README.md +++ b/README.md @@ -675,6 +675,16 @@ will print: ``` +#### 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 diff --git a/src/prettytable/prettytable.py b/src/prettytable/prettytable.py index 62a1df1d..ca8ae512 100644 --- a/src/prettytable/prettytable.py +++ b/src/prettytable/prettytable.py @@ -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: @@ -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 {} @@ -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": @@ -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 # ############################## @@ -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) @@ -2156,6 +2189,7 @@ def get_html_string(self, **kwargs) -> str: 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
tags if True,
tags if False""" options = self._get_options(kwargs) @@ -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( - " ".format( - escape(field).replace("\n", linebreak) - ) + " ".format(field.replace("\n", linebreak)) ) + lines.append(" ") lines.append(" ") @@ -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( - " ".format( - escape(datum).replace("\n", linebreak) - ) + " ".format(datum.replace("\n", linebreak)) ) lines.append(" ") lines.append(" ") @@ -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( ' ' # noqa: E501 - % (lpad, rpad, escape(field).replace("\n", linebreak)) + % (lpad, rpad, field.replace("\n", linebreak)) ) lines.append(" ") lines.append(" ") @@ -2300,6 +2340,9 @@ 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( ' ' # noqa: E501 % ( @@ -2307,7 +2350,7 @@ def _get_formatted_html_string(self, options): rpad, align, valign, - escape(datum).replace("\n", linebreak), + datum.replace("\n", linebreak), ) ) lines.append(" ") diff --git a/tests/test_prettytable.py b/tests/test_prettytable.py index 9b51e4a2..750b84ce 100644 --- a/tests/test_prettytable.py +++ b/tests/test_prettytable.py @@ -1195,6 +1195,234 @@ def test_html_output_formatted_with_title(self) -> None:
{}{}
{}{}
%s
%s
+""".strip() # noqa: E501 + ) + + def test_html_output_without_escaped_header(self) -> None: + t = helper_table(rows=0) + t.field_names = ["", "Field 1", "Field 2", "Field 3"] + result = t.get_html_string(escape_header=False) + assert ( + result.strip() + == """ + + + + + + + + + + + +
Field 1Field 2Field 3
+""".strip() + ) + + def test_html_output_without_escaped_data(self) -> None: + t = helper_table(rows=0) + t.add_row( + [ + 1, + "value 1", + "value2", + "value3", + ] + ) + result = t.get_html_string(escape_data=False) + assert ( + result.strip() + == """ + + + + + + + + + + + + + + + + + +
Field 1Field 2Field 3
1value 1value2value3
+""".strip() + ) + + def test_html_output_with_escaped_header(self) -> None: + t = helper_table(rows=0) + t.field_names = ["", "Field 1", "Field 2", "Field 3"] + result = t.get_html_string(escape_header=True) + assert ( + result.strip() + == """ + + + + + + + + + + + +
Field 1<em>Field 2</em><a href='#'>Field 3</a>
+""".strip() + ) + + def test_html_output_with_escaped_data(self) -> None: + t = helper_table(rows=0) + t.add_row( + [ + 1, + "value 1", + "value2", + "value3", + ] + ) + result = t.get_html_string(escape_data=True) + assert ( + result.strip() + == """ + + + + + + + + + + + + + + + + + +
Field 1Field 2Field 3
1<b>value 1</b><span style='text-decoration: underline;'>value2</span><a href='#'>value3</a>
+""".strip() # noqa: E501 + ) + + def test_html_output_formatted_without_escaped_header(self) -> None: + t = helper_table(rows=0) + t.field_names = ["", "Field 1", "Field 2", "Field 3"] + result = t.get_html_string(escape_header=False, format=True) + assert ( + result.strip() + == """ + + + + + + + + + + + +
Field 1Field 2Field 3
+""".strip() # noqa: E501 + ) + + def test_html_output_formatted_without_escaped_data(self) -> None: + t = helper_table(rows=0) + t.add_row( + [ + 1, + "value 1", + "value2", + "value3", + ] + ) + result = t.get_html_string(escape_data=False, format=True) + assert ( + result.strip() + == """ + + + + + + + + + + + + + + + + + +
Field 1Field 2Field 3
1value 1value2value3
+""".strip() # noqa: E501 + ) + + def test_html_output_formatted_with_escaped_header(self) -> None: + t = helper_table(rows=0) + t.field_names = ["", "Field 1", "Field 2", "Field 3"] + result = t.get_html_string(escape_header=True, format=True) + assert ( + result.strip() + == """ + + + + + + + + + + + +
Field 1<em>Field 2</em><a href='#'>Field 3</a>
+""".strip() # noqa: E501 + ) + + def test_html_output_formatted_with_escaped_data(self) -> None: + t = helper_table(rows=0) + t.add_row( + [ + 1, + "value 1", + "value2", + "value3", + ] + ) + result = t.get_html_string(escape_data=True, format=True) + assert ( + result.strip() + == """ + + + + + + + + + + + + + + + + + +
Field 1Field 2Field 3
1<b>value 1</b><span style='text-decoration: underline;'>value2</span><a href='#'>value3</a>
""".strip() # noqa: E501 )