diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index de0bfb229e..dca38359e3 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -23,6 +23,7 @@ from __future__ import (absolute_import, division, print_function) from six.moves import (filter, input, map, range, zip) # noqa +from html import escape import re @@ -96,7 +97,7 @@ class CubeRepresentation(object): def __init__(self, cube): self.cube = cube self.cube_id = id(self.cube) - self.cube_str = str(self.cube) + self.cube_str = escape(str(self.cube)) self.str_headings = { 'Dimension coordinates:': None, @@ -115,9 +116,9 @@ def __init__(self, cube): self.scalar_cube = self.shapes == () self.ndims = self.cube.ndim - self.name = self.cube.name().title().replace('_', ' ') - self.names = self._dim_names() - self.units = self.cube.units + self.name = escape(self.cube.name().title().replace('_', ' ')) + self.names = [escape(dim_name) for dim_name in self._dim_names()] + self.units = escape(str(self.cube.units)) def _get_dim_names(self): """ @@ -401,6 +402,7 @@ def make_content(self): for i, cube in enumerate(self.cubelist): title = '{i}: {summary}'.format(i=i, summary=cube.summary(shorten=True)) + title = escape(title) content = cube._repr_html_() html.append(self._accordian_panel.format(uid=self.cubelist_id, title=title, diff --git a/lib/iris/tests/integration/experimental/test_CubeRepresentation.py b/lib/iris/tests/integration/experimental/test_CubeRepresentation.py index fa01ab37a7..a9d40bcd56 100644 --- a/lib/iris/tests/integration/experimental/test_CubeRepresentation.py +++ b/lib/iris/tests/integration/experimental/test_CubeRepresentation.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2018, Met Office +# (C) British Crown Copyright 2018 - 2019, Met Office # # This file is part of Iris. # @@ -18,6 +18,7 @@ from __future__ import (absolute_import, division, print_function) from six.moves import (filter, input, map, range, zip) # noqa +from html import escape # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -136,12 +137,12 @@ def test_identfication(self): def test_header__name(self): header = self.representer._make_header() - expected_name = self.cube.name().title().replace('_', ' ') + expected_name = escape(self.cube.name().title().replace('_', ' ')) self.assertIn(expected_name, header) def test_header__units(self): header = self.representer._make_header() - expected_units = self.cube.units.symbol + expected_units = escape(self.cube.units.symbol) self.assertIn(expected_units, header) def test_header__scalar_str(self): @@ -160,9 +161,9 @@ def test_content__specific_scalar_coord(self): # Check a specific scalar coord is present in the main content. content = self.representer._make_content() expected_coord = self.cube.coords()[0] - expected_coord_name = expected_coord.name() + expected_coord_name = escape(expected_coord.name()) self.assertIn(expected_coord_name, content) - expected_coord_val = str(expected_coord.points[0]) + expected_coord_val = escape(str(expected_coord.points[0])) self.assertIn(expected_coord_val, content) def test_content__attributes(self): diff --git a/lib/iris/tests/unit/experimental/representation/__init__.py b/lib/iris/tests/unit/experimental/representation/__init__.py new file mode 100644 index 0000000000..057b091456 --- /dev/null +++ b/lib/iris/tests/unit/experimental/representation/__init__.py @@ -0,0 +1,20 @@ +# (C) British Crown Copyright 2019, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Unit tests for the :mod:`iris.experimental.representation` package.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py index a9cf07ef9b..16c3b3f840 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py @@ -18,6 +18,7 @@ from __future__ import (absolute_import, division, print_function) from six.moves import (filter, input, map, range, zip) # noqa +from html import escape # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -44,6 +45,7 @@ class Test_make_content(tests.IrisTest): def setUp(self): self.cubes = CubeList([stock.simple_3d(), stock.lat_lon_cube()]) + self.cubes[0].rename('name & ') self.representer = CubeListRepresentation(self.cubes) self.content = self.representer.make_content() @@ -53,17 +55,18 @@ def test_repr_len(self): def test_summary_lines(self): names = [c.name() for c in self.cubes] for name, content in zip(names, self.content): + name = escape(name) self.assertIn(name, content) def test__cube_name_summary_consistency(self): # Just check the first cube in the CubeList. single_cube_html = self.content[0] first_contents_line = single_cube_html.split('\n')[1] - # Get the cube name out of the repr html... - cube_name = first_contents_line.split('>0: ')[1].split('/')[0] - # ... and prettify it (to be the same as in the following cube repr). + # Get a "prettified" cube name, as it should be in the cubelist repr. + cube_name = self.cubes[0].name() pretty_cube_name = cube_name.strip().replace('_', ' ').title() - self.assertIn(pretty_cube_name, single_cube_html) + pretty_escaped_name = escape(pretty_cube_name) + self.assertIn(pretty_escaped_name, single_cube_html) @tests.skip_data diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index fa05c00abf..d16dcf28f9 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -18,6 +18,7 @@ from __future__ import (absolute_import, division, print_function) from six.moves import (filter, input, map, range, zip) # noqa +from html import escape # Import iris.tests first so that some things can be initialised before # importing anything else. @@ -81,13 +82,13 @@ class Test__summary_content(tests.IrisTest): def setUp(self): self.cube = stock.lat_lon_cube() # Check we're not tripped up by names containing spaces. - self.cube.rename('Electron density') + self.cube.rename('Electron density (&)') self.cube.units = '1e11 e/m^3' self.representer = CubeRepresentation(self.cube) def test_name(self): # Check the cube name is being set and formatted correctly. - expected = self.cube.name().replace('_', ' ').title() + expected = escape(self.cube.name().replace('_', ' ').title()) result = self.representer.name self.assertEqual(expected, result)