diff --git a/dash_html_components/__init__.py b/dash_html_components/__init__.py index ccbe49b8..4a39d2d3 100644 --- a/dash_html_components/__init__.py +++ b/dash_html_components/__init__.py @@ -2,6 +2,7 @@ import dash as _dash import sys as _sys from .version import __version__ +from ._to_html5 import _to_html5 _current_path = _os.path.dirname(_os.path.abspath(__file__)) @@ -24,3 +25,4 @@ for component in _components: setattr(_this_module, component.__name__, component) setattr(component, '_js_dist', _js_dist) + setattr(component, 'to_html5', _to_html5) diff --git a/dash_html_components/_to_html5.py b/dash_html_components/_to_html5.py new file mode 100644 index 00000000..e1ea883e --- /dev/null +++ b/dash_html_components/_to_html5.py @@ -0,0 +1,65 @@ +import collections +from dash.development.base_component import Component +import re + +_VOID_ELEMENTS = [ + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', + 'link', 'meta', 'param', 'source', 'track', 'wbr' +] + +_CAMELCASE_REGEX = re.compile('([a-z0-9])([A-Z])') + + +def _camel_case_to_css_case(name): + return _CAMELCASE_REGEX.sub(r'\1-\2', name).lower() + + +def _attribute(name, value): + if name == 'className': + return ('class', value,) + elif name == 'style': + # Dash CSS is camel-cased but CSS is hyphenated + # convert e.g. fontColor to font-color + inline_style = '; '.join([ + '='.join([_camel_case_to_css_case(k), v]) + for (k, v) in value.iteritems() + ]) + return (name, inline_style,) + else: + return (name, value,) + + +def _to_html5(self): + def __to_html5(component): + if not isinstance(component, Component): + return str(component) + component_type = component._type.lower() + + children = '' + if component_type not in _VOID_ELEMENTS: + children = getattr(component, 'children', '') + if isinstance(children, Component): + children = __to_html5(children) + elif isinstance(children, collections.MutableSequence): + children = '\n'.join([__to_html5(child) for child in children]) + else: + children = str(children) + if children != '': + children = '\n' + children + + closing = '' + if component_type not in _VOID_ELEMENTS: + closing = '\n{}>'.format(component_type) + + return '<{type}{properties}>{children}{closing}'.format( + type=component_type, + properties=''.join([ + ' {}="{}"'.format( + _attribute(p, getattr(component, p))) + for p in component._prop_names + if (hasattr(component, p) and p is not 'children') + ]), + children=children, + closing=closing) + + return __to_html5(self) diff --git a/tests/test_dash_html_components.py b/tests/test_dash_html_components.py index b5d2f44f..6345af9f 100644 --- a/tests/test_dash_html_components.py +++ b/tests/test_dash_html_components.py @@ -39,3 +39,77 @@ def test_sample_items(self): self.assertEqual( layout._namespace, 'dash_html_components' ) + + def test_to_html5(self): + test_cases = [ + { + 'name': 'None Children', + 'input': html.Div(), + 'output': '
' + }, + { + 'name': 'Text Children', + 'input': html.Script('alert'), + 'output': '' + }, + { + 'name': 'Numerical Children', + 'input': html.Div(3), + 'output': '',
+ ''
+ '