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 html repr #883

Merged
merged 12 commits into from
Jun 29, 2023
97 changes: 97 additions & 0 deletions src/hdmf/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,103 @@
template += " {}: {}\n".format(k, v)
return template

def _repr_html_(self):
CSS_STYLE = """
<style>
.container-fields {
font-family: "Open Sans", Arial, sans-serif;
}
.container-fields .field-value {
color: #00788E;
}
.container-fields details > summary {
cursor: pointer;
display: list-item;
}
.container-fields details > summary:hover {
color: #0A6EAA;
}
</style>
"""

JS_SCRIPT = """
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
console.log('Copied to clipboard: ' + text);
}, function(err) {
console.error('Could not copy text: ', err);
});
}

document.addEventListener('DOMContentLoaded', function() {
let fieldKeys = document.querySelectorAll('.container-fields .field-key');
fieldKeys.forEach(function(fieldKey) {
fieldKey.addEventListener('click', function() {
let accessCode = fieldKey.getAttribute('title').replace('Access code: ', '');
copyToClipboard(accessCode);
});
});
});
</script>
"""
if self.name == self.__class__.__name__:
header_text = self.name

Check warning on line 496 in src/hdmf/container.py

View check run for this annotation

Codecov / codecov/patch

src/hdmf/container.py#L496

Added line #L496 was not covered by tests
else:
header_text = f"{self.name} ({self.__class__.__name__})"
html_repr = CSS_STYLE
html_repr += JS_SCRIPT
html_repr += "<div class='container-wrap'>"
html_repr += (
f"<div class='container-header'><div class='xr-obj-type'><h3>{header_text}</h3></div></div>"
)
html_repr += self._generate_html_repr(self.fields)
html_repr += "</div>"
return html_repr

def _generate_html_repr(self, fields, level=0, access_code=".fields"):
html_repr = ""

if isinstance(fields, dict):
for key, value in fields.items():
current_access_code = f"{access_code}['{key}']"
if (
isinstance(value, (list, dict, np.ndarray))
or hasattr(value, "fields")
):
html_repr += (
f'<details><summary style="display: list-item; margin-left: {level * 20}px;" '
f'class="container-fields field-key" title="{current_access_code}"><b>{key}</b></summary>'
)
if hasattr(value, "fields"):
value = value.fields
current_access_code = current_access_code + ".fields"
html_repr += self._generate_html_repr(
value, level + 1, current_access_code
)
html_repr += "</details>"
else:
html_repr += (
f'<div style="margin-left: {level * 20}px;" class="container-fields"><span class="field-key"'
f' title="{current_access_code}">{key}:</span> <span class="field-value">{value}</span></div>'
)
elif isinstance(fields, list):
for index, item in enumerate(fields):
current_access_code = f"{access_code}[{index}]"
html_repr += (
f'<div style="margin-left: {level * 20}px;" class="container-fields"><span class="field-value"'
f' title="{current_access_code}">{str(item)}</span></div>'
)
elif isinstance(fields, np.ndarray):
str_ = str(fields).replace("\n", "</br>")
html_repr += (

Check warning on line 544 in src/hdmf/container.py

View check run for this annotation

Codecov / codecov/patch

src/hdmf/container.py#L543-L544

Added lines #L543 - L544 were not covered by tests
f'<div style="margin-left: {level * 20}px;" class="container-fields">{str_}</div>'
)
else:
pass

Check warning on line 548 in src/hdmf/container.py

View check run for this annotation

Codecov / codecov/patch

src/hdmf/container.py#L548

Added line #L548 was not covered by tests

return html_repr

@staticmethod
def __smart_str(v, num_indent):
"""
Expand Down
75 changes: 60 additions & 15 deletions tests/unit/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ class Subcontainer(Container):
pass


class ContainerWithChild(Container):
__fields__ = ({'name': 'field1', 'child': True}, )

@docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None})
def __init__(self, **kwargs):
super().__init__('test name')
self.field1 = kwargs['field1']


class TestExternalResourcesManager(TestCase):
def test_link_and_get_resources(self):
em = ExternalResourcesManager()
Expand Down Expand Up @@ -263,6 +272,57 @@ def test_reset_parent_no_parent(self):
self.assertIsNone(obj.parent)


class TestHTMLRepr(TestCase):

class ContainerWithChildAndData(Container):
__fields__ = (
{'name': 'child', 'child': True},
"data",
"str"
)

@docval(
{'name': 'child', 'doc': 'field1 doc', 'type': Container},
{'name': "data", "doc": 'data', 'type': list, "default": None},
{'name': "str", "doc": 'str', 'type': str, "default": None},

)
def __init__(self, **kwargs):
super().__init__('test name')
self.child = kwargs['child']
self.data = kwargs['data']
self.str = kwargs['str']

def test_repr_html_(self):
child_obj1 = Container('test child 1')
obj1 = self.ContainerWithChildAndData(child=child_obj1, data=[1, 2, 3], str="hello")
assert obj1._repr_html_() == (
'\n <style>\n .container-fields {\n font-family: "Open Sans", Arial, sans-'
'serif;\n }\n .container-fields .field-value {\n color: #00788E;\n '
' }\n .container-fields details > summary {\n cursor: pointer;\n '
' display: list-item;\n }\n .container-fields details > summary:hover {\n '
' color: #0A6EAA;\n }\n </style>\n \n <script>\n functio'
'n copyToClipboard(text) {\n navigator.clipboard.writeText(text).then(function() {\n '
' console.log(\'Copied to clipboard: \' + text);\n }, function(err) {\n '
' console.error(\'Could not copy text: \', err);\n });\n }\n\n '
' document.addEventListener(\'DOMContentLoaded\', function() {\n let fieldKeys = document.q'
'uerySelectorAll(\'.container-fields .field-key\');\n fieldKeys.forEach(function(fieldKey) {'
'\n fieldKey.addEventListener(\'click\', function() {\n let acces'
'sCode = fieldKey.getAttribute(\'title\').replace(\'Access code: \', \'\');\n copyTo'
'Clipboard(accessCode);\n });\n });\n });\n </script>\n'
' <div class=\'container-wrap\'><div class=\'container-header\'><div class=\'xr-obj-type\'><h3>test '
'name (ContainerWithChildAndData)</h3></div></div><details><summary style="display: list-item; margin-left:'
' 0px;" class="container-fields field-key" title=".fields[\'child\']"><b>child</b></summary></details><deta'
'ils><summary style="display: list-item; margin-left: 0px;" class="container-fields field-key" title=".fiel'
'ds[\'data\']"><b>data</b></summary><div style="margin-left: 20px;" class="container-fields"><span class="f'
'ield-value" title=".fields[\'data\'][0]">1</span></div><div style="margin-left: 20px;" class="container-fi'
'elds"><span class="field-value" title=".fields[\'data\'][1]">2</span></div><div style="margin-left: 20px;"'
' class="container-fields"><span class="field-value" title=".fields[\'data\'][2]">3</span></div></details><'
'div style="margin-left: 0px;" class="container-fields"><span class="field-key" title=".fields[\'str\']">st'
'r:</span> <span class="field-value">hello</span></div></div>'
)


class TestData(TestCase):

def test_constructor_scalar(self):
Expand Down Expand Up @@ -507,14 +567,6 @@ def __init__(self, **kwargs):
self.assertIsNone(obj4.field1)

def test_child(self):
class ContainerWithChild(Container):
__fields__ = ({'name': 'field1', 'child': True}, )

@docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None})
def __init__(self, **kwargs):
super().__init__('test name')
self.field1 = kwargs['field1']

child_obj1 = Container('test child 1')
obj1 = ContainerWithChild(child_obj1)
self.assertIs(child_obj1.parent, obj1)
Expand All @@ -532,13 +584,6 @@ def __init__(self, **kwargs):
self.assertIsNone(obj2.field1)

def test_setter_set_modified(self):
class ContainerWithChild(Container):
__fields__ = ({'name': 'field1', 'child': True}, )

@docval({'name': 'field1', 'doc': 'field1 doc', 'type': None, 'default': None})
def __init__(self, **kwargs):
super().__init__('test name')
self.field1 = kwargs['field1']

child_obj1 = Container('test child 1')
obj1 = ContainerWithChild()
Expand Down