Source code for plainbox.impl.exporter.html
# This file is part of Checkbox.
#
# Copyright 2013 Canonical Ltd.
# Written by:
# Sylvain Pineau <sylvain.pineau@canonical.com>
# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
# Daniel Manrique <daniel.manrique@canonical.com>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
"""
:mod:`plainbox.impl.exporter.html`
==================================
HTML exporter for human consumption
.. warning::
THIS MODULE DOES NOT HAVE A STABLE PUBLIC API
"""
from string import Template
import base64
import logging
import mimetypes
from lxml import etree as ET
from pkg_resources import resource_filename
from plainbox.i18n import gettext as _
from plainbox.impl.exporter.xml import XMLSessionStateExporter
logger = logging.getLogger("plainbox.exporter.html")
[docs]class HTMLResourceInliner(object):
""" A helper class to inline resources referenced in an lxml tree.
"""
def _resource_content(self, url):
try:
with open(url, 'rb') as f:
file_contents = f.read()
except (IOError, OSError):
logger.warning(
_("Unable to load resource %s, not inlining"), url)
return ""
type, encoding = mimetypes.guess_type(url)
if not encoding:
encoding = "utf-8"
if type in("text/css", "application/javascript"):
return file_contents.decode(encoding)
elif type in("image/png", "image/jpg"):
b64_data = base64.b64encode(file_contents)
b64_data = b64_data.decode("ascii")
return_string = "data:{};base64,{}".format(type, b64_data)
return return_string
else:
logger.warning(_("Resource of type %s unknown"), type)
#Strip it out, better not to have it.
return ""
[docs] def inline_resources(self, document_tree):
"""
Replace references to external resources by an in-place (inlined)
representation of each resource.
Currently images, stylesheets and scripts are inlined.
Only local (i.e. file) resources/locations are supported. If a
non-local resource is requested for inlining, it will be removed
(replaced by a blank string), with the goal that the resulting
lxml tree will not reference any unreachable resources.
:param document_tree:
lxml tree to process.
:returns:
lxml tree with some elements replaced by their inlined
representation.
"""
# Try inlining using result_tree here.
for node in document_tree.xpath('//script'):
# These have src attribute, need to remove the
# attribute and add the content of the src file
# as the node's text
src = node.attrib.pop('src')
node.text = self._resource_content(src)
for node in document_tree.xpath('//link[@rel="stylesheet"]'):
# These have a href attribute and need to be completely replaced
# by a new <style> node with contents of the href file
# as its text.
src = node.attrib.pop('href')
type = node.attrib.pop('type')
style_elem = ET.Element("style")
style_elem.attrib['type'] = type
style_elem.text = self._resource_content(src)
node.getparent().append(style_elem)
# Now zorch the existing node
node.getparent().remove(node)
for node in document_tree.xpath('//img'):
# src attribute points to a file and needs to
# contain the base64 encoded version of that file.
src = node.attrib.pop('src')
node.attrib['src'] = self._resource_content(src)
return document_tree
[docs]class HTMLSessionStateExporter(XMLSessionStateExporter):
"""
Session state exporter creating HTML documents.
It basically applies an xslt to the XMLSessionStateExporter output,
and then inlines some resources to produce a monolithic report in a
single file.
"""
[docs] def dump(self, data, stream):
"""
Public method to dump the HTML report to a stream
"""
root = self.get_root_element(data)
self.xslt_filename = resource_filename(
"plainbox", "data/report/checkbox.xsl")
template_substitutions = {
'PLAINBOX_ASSETS': resource_filename("plainbox", "data/")}
with open(self.xslt_filename, encoding="UTF-8") as xslt_file:
xslt_template = Template(xslt_file.read())
return self.dump_etree(root,
stream,
xslt_template,
template_substitutions)
[docs] def dump_etree(self, root, stream, xslt_template, template_substitutions):
"""
Dumps the given lxml root tree into the given stream, by applying the
provided xslt. If template_substitutions is provided, the xslt will
first be processed as a string.Template with those substitutions.
:param root:
lxml root element of tree to process.
:param stream:
Byte stream into which to dump the resulting output.
:param xslt_template:
String containing an xslt with which to process the lxml
tree to output the desired document type.
:param template_substitutions:
Dictionary with substitutions for variables which may be
in the xslt_template.
"""
if template_substitutions and isinstance(template_substitutions, dict):
xslt_data = xslt_template.substitute(template_substitutions)
xslt_root = ET.XML(xslt_data)
transformer = ET.XSLT(xslt_root)
r_tree = transformer(root)
inlined_result_tree = HTMLResourceInliner().inline_resources(r_tree)
stream.write(ET.tostring(inlined_result_tree, pretty_print=True))