diff --git a/README.md b/README.md index ef5c148..73bb320 100644 --- a/README.md +++ b/README.md @@ -18,22 +18,22 @@ from display_xml import XML XML('content') ``` -![single xml example](./images/single_display_xml_screenshot.jpg) +![single xml example](./images/single_display_xml_screenshot.png) -You can pass in: +You can pass in: - `str` - `bytes` - `lxml.etree._Element` - `lxml.etree._ElementTree` -## To display all available styles +## To display all available styles ```python from display_xml import XML XML.display_all_styles('content') ``` -![multiple style xml example](./images/multi_display_xml_screenshot.jpg) +![multiple style xml example](./images/multi_display_xml_screenshot.png) Once you know the style you want, pass that string in as an argument to `XML`: diff --git a/display_xml/__init__.py b/display_xml/__init__.py index c83ede4..dc3fc9e 100644 --- a/display_xml/__init__.py +++ b/display_xml/__init__.py @@ -1,2 +1,2 @@ from ._version import __version__ -from .xml import XML +from .xml import XML, tostring diff --git a/display_xml/xml.py b/display_xml/xml.py index de83e6b..e065ff6 100644 --- a/display_xml/xml.py +++ b/display_xml/xml.py @@ -1,3 +1,7 @@ +"""from https://github.com/mpacer/display_xml + +Make IPython foldable XML display, similar to the JSON functionality. +""" from pygments import highlight from pygments.lexers import XmlLexer from pygments.formatters import HtmlFormatter @@ -7,97 +11,198 @@ from IPython.display import display -no_blank_parser = et.XMLParser(remove_blank_text=True) +no_blank_parser = et.XMLParser(remove_blank_text=False) + + +def tostring(element, hook=None, indent=0, indent_increment_size=1): + """Turn XML element to string. + + Applied recursively (depth first to all elements in the tree. + + Parameters + ---------- + element: xml.etree.Element + An XML element. + hook: callable + A function of the form f(tag, attributes, text, children, + tail) that receives strings representing all the components of + the element and that returns a string representation of the + entire element. This can be used to have more control over the + formatting of the element. + indent: int + Indentation level for element. + indent_increment_size: bool + By how much to increase the indentation level when going + deeper into the tree. + """ + child_indent = indent + indent_increment_size + children = ''.join( + tostring(child, hook, child_indent, indent_increment_size) + for child in element + ) + attributes = ' '.join( + f'{key}="{value}"' for (key, value) in element.attrib.items() + ) + + if attributes: + attributes = ' ' + attributes + if callable(hook): + return hook( + element.tag, + attributes, + element.text or '', children, + element.tail or '', + indent + ) + else: + indentation = indent * " " # non breakable space + result = ( + f"{indentation}<{element.tag}{attributes}>" + f"{element.text or ''}{children}" + f"{element.tail or ''}" + ) + return result + class XML: - '''Class for displaying XML in a pretty way that supports pygments styles. - ''' + """Class for displaying XML in a pretty way that supports pygments styles. + """ HTML_TEMPLATE = """ -
+
{content}
+ """ - + NAMED_STYLE_TEMPLATE = ( - """

{extras[style_name]}

""" + - HTML_TEMPLATE + + """

{extras[style_name]}

""" + + HTML_TEMPLATE + "
" ) - def __init__(self, in_obj, style='default', template=None, - extras={}): - ''' + _other_params_docstring = """expand: bool + Whether or not to expand the XML elements by default. + pretty_print: int + By how much to increase the indentation level between parent element + and child element. + """ + + def __init__(self, in_obj, style='default', template=None, + extras=None, expand=False, pretty_print=1): + f""" Parameters ---------- in_obj : str, lxml.etree._Element, lxml.etree._ElementTree, or bytes Object to be displayed as html style : str, optional Pygment style names (the default is 'default') - ''' + expand: bool + Whether or not to expand the XML elements by default. + pretty_print: int + By how much to increase the indentation level between parent element + and child element. + """ + if extras is None: + extras = {} + if template is None: template = self.HTML_TEMPLATE - + if isinstance(in_obj, (str, bytes)): - self.xml = et.fromstring(in_obj, parser=no_blank_parser) + self._xml = et.fromstring(in_obj, parser=no_blank_parser) elif isinstance(in_obj, et._ElementTree): - self.xml = in_obj.getroot() + self._xml = in_obj.getroot() elif isinstance(in_obj, et._Element): - self.xml = in_obj + self._xml = in_obj else: raise TypeError(f"{in_obj} is of type {type(in_obj)}." "This object only can displays objects of type " "str, bytes, lxml.etree._ElementTree, or " "lxml.etree._Element.") - - self.text = et.tostring(self.xml, pretty_print=True) + self.style = style self.formatter = HtmlFormatter(style=self.style) self.uuid_class = "a"+str(self.uuid) self.template = template self.extras = extras - + self.expand = expand + self.pretty_print = pretty_print + + + def _make_foldable(self, xml): + """Make the document foldable. + + Add
elements above all parent nodes. + """ + details = et.Element("details") + summary = et.Element("summary") + summary.text = xml.tag # TODO: include attributes + details.append(summary) + root = et.Element(xml.tag) + root.text = xml.text + root.tail = xml.tail + for node in xml: + foldable_node = self._make_foldable(node) + root.append(foldable_node) + details.append(root) + return details + @classmethod - def display_all_styles(cls, in_obj): + def display_all_styles(cls, in_obj, **kwargs): """ Displays all available pygments styles using XML.style_gen() - + Parameters ---------- - + in_obj: str lxml.etree._Element, lxml.ettree._ElementTree, or bytes Object to be displayed as html - """ - for disp in cls.style_gen(in_obj): + kwargs: passed to cls.style_gen method. + """.format(cls._other_params_docstring) + for disp in cls.style_gen(in_obj, **kwargs): display(disp) - + @classmethod - def style_gen(cls, in_obj): + def style_gen(cls, in_obj, **kwargs): """ Generator for iterating over all of the styles available from pygments. - + If you declare this xml = XML.style_gen(text), use next(xml). + + in_obj: str lxml.etree._Element, lxml.ettree._ElementTree, or bytes + Object to be displayed as html + kwargs: passed to cls.__init__. """ for style in get_all_styles(): - yield(cls(in_obj, - style=style, - template=cls.NAMED_STYLE_TEMPLATE, - extras={"style_name": style} + yield(cls(in_obj, + style=style, + template=cls.NAMED_STYLE_TEMPLATE, + extras={"style_name": style}, + **kwargs )) - + @property def style_css(self): """ - Generates the css classes needed to apply this uniquely. - - TODO: it might be nice to move toward a vdom based displayer for more versatile control - - TODO: figure out a way to add a toggleable arrow for collapsing this + Generates the css classes needed to apply this uniquely. + + TODO: it might be nice to move toward a vdom based displayer + for more versatile control """ + details_css = """ +{div} pre {{ margin: 0 0; font-family: inherit;}} /* consistent font for opening and closing tags */ +{div} .highlight {{ display: inline}} +{div} summary::marker {{ font-size: 0.66em; margin-inline-end: 0.4em; }} /* marker for firefox */ +{div} summary::-webkit-details-marker {{width: 0.66em; margin-inline-end: 0.4em;}} /* marker for chrome */ +{div} .indented-xml-content {{padding-left:1.06em;}} +""".format(div=f"div.{self.uuid_class}") # noqa temp_css = self.formatter.get_style_defs() - css_list = [f"div.{self.uuid_class} {x}" for x in temp_css.split("\n")] + css_list = [ + f"div.{self.uuid_class} {x}" for x in temp_css.split("\n") + ] + details_css.split('\n') return "\n".join(css_list) @property @@ -105,9 +210,60 @@ def uuid(self): return uuid4() def _repr_html_(self): - content = highlight(self.text, XmlLexer(), self.formatter) - return self.template.format(uuid_class=self.uuid_class, - style_css=self.style_css, - content=content, - extras=self.extras - ) + content = tostring( + self._xml, + hook=self._foldable_to_string_hook, + indent_increment_size=int(self.pretty_print) + ) + return self.template.format( + uuid_class=self.uuid_class, + style_css=self.style_css, + content=content, + extras=self.extras, + ) + + def _foldable_to_string_hook(self, tag, attributes, text, children, + tail, indent): + """Return foldable content string. + + Use
element to make the string foldable. + https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details + + This is a hook function to use in combination with the ``tostring`` + function defined in this module. It receives HTML string representation + of all the components in the XML's element. + + Parameters + ---------- + tag: str + The element's tag name. + attributes: str + The element's tag's attributes, of teh form "attr1='val1' + attr2='val2'". + text: str + The element's text. + children: str + The HTML text representing the element's children. + tail: str + Text representing the element's tail. + indent: integer + Indentation level for this element (in ems). + """ + def highlight_string(string): + return highlight(string, XmlLexer(), self.formatter) + + opening_tag = highlight_string(f"<{tag}{attributes}>").replace( + '
', '').replace('
', '') + closing_tag = highlight_string(f"") + foldable_content = f""" +
+ + {opening_tag} +
+ {text}{children} + {closing_tag}{tail} +
+
+
+""" + return foldable_content diff --git a/example_notebook.ipynb b/example_notebook.ipynb new file mode 100644 index 0000000..5fc3569 --- /dev/null +++ b/example_notebook.ipynb @@ -0,0 +1,4893 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example Notebook for display_xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default the output is folded. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from display_xml import XML\n", + "XML('content')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To expand all elements by default, use `expand=True`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "XML('content', expand=True) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set `pretty_print` to control the width of the indentation level. The default is 1\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "XML('content', expand=True, pretty_print=0) " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "XML('content', expand=True, pretty_print=3) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Styles" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

default

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

emacs

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

friendly

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

colorful

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

autumn

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

murphy

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

manni

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

monokai

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

perldoc

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

pastie

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

borland

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

trac

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

native

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

fruity

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

bw

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

vim

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

vs

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

tango

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

rrt

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

xcode

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

igor

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

paraiso-light

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

paraiso-dark

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

lovelace

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

algol

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

algol_nu

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

arduino

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

rainbow_dash

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

abap

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

solarized-dark

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

solarized-light

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

sas

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

stata

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

stata-light

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

stata-dark

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

inkpot

\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "XML.display_all_styles('content', expand=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + "
\n", + "
\n", + "
<body>\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
<tag>\n", + "
\n", + "
\n", + "
\n", + " content\n", + "
</tag>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
</body>\n",
+       "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + "\n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "XML('content', expand=True, style=\"perldoc\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "display_xml", + "language": "python", + "name": "display_xml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/images/multi_display_xml_screenshot.jpg b/images/multi_display_xml_screenshot.jpg deleted file mode 100644 index 4de7ab1..0000000 Binary files a/images/multi_display_xml_screenshot.jpg and /dev/null differ diff --git a/images/multi_display_xml_screenshot.png b/images/multi_display_xml_screenshot.png new file mode 100644 index 0000000..9c5136a Binary files /dev/null and b/images/multi_display_xml_screenshot.png differ diff --git a/images/single_display_xml_screenshot.jpg b/images/single_display_xml_screenshot.jpg deleted file mode 100644 index ece6363..0000000 Binary files a/images/single_display_xml_screenshot.jpg and /dev/null differ diff --git a/images/single_display_xml_screenshot.png b/images/single_display_xml_screenshot.png new file mode 100644 index 0000000..3db5e44 Binary files /dev/null and b/images/single_display_xml_screenshot.png differ diff --git a/images/styled_single_xml_screenshot.png b/images/styled_single_xml_screenshot.png index 509a7ea..c904805 100644 Binary files a/images/styled_single_xml_screenshot.png and b/images/styled_single_xml_screenshot.png differ diff --git a/setup.py b/setup.py index 3b650d1..9090fc7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setupbase import ( ensure_python, find_packages, get_version ) - + setup_dict = dict( name='display_xml', version=get_version('display_xml/_version.py'), @@ -29,6 +29,7 @@ 'ipython>=6.2.1', 'pygments>=2.2.0', 'lxml>=4.1.1', + 'pytest>=3.0' ] ) @@ -44,4 +45,4 @@ setuptools.setup( **setup_dict -) \ No newline at end of file +) diff --git a/tests/test_display_xml.py b/tests/test_display_xml.py new file mode 100644 index 0000000..bbc5c67 --- /dev/null +++ b/tests/test_display_xml.py @@ -0,0 +1,42 @@ +""" +""" +from display_xml import tostring +import lxml.etree as ET +import pytest + + +@pytest.fixture +def element(): + xml = """ + + Princess Diana - Prince Charles + Prince William + Prince Harry + + the end + + """ + element = ET.fromstring(xml) + return element + + +def test_to_string(element): + """Test implementation of tostring against lxml's version.""" + assert tostring(element) == ET.tostring(element).decode('utf-8') + + +def test_to_string_hook(element): + """Show how we can now control formatting of xml recursively by + using a hook function that receives each formatted component. + + Here we upcase the tags and put the whole element on one line. + """ + def one_liner_hook(tag, attributes, text, children, tail, indent): + print(locals()) + tag = tag.upper() + return f"""<{tag}{attributes}>{text.strip()}{children}{tail.strip()}""" # noqa + + assert tostring(element, hook=one_liner_hook, pretty_print=False) == ( + 'Princess Diana - Prince Charles' + 'Prince WilliamPrince Harrythe end' # noqa + )