from __future__ import unicode_literals
import six
import xml.dom.minidom as minidom
from .base import FormattedText
__all__ = [
'HTML'
]
class HTML(object):
"""
HTML formatted text.
Take something HTML-like, for use as a formatted string.
::
# Turn something into red.
HTML('')
# Italic, bold and underline.
HTML('...')
HTML('...')
HTML('...')
All HTML elements become available as a "class" in the style sheet.
E.g. ``...`` can be styled, by setting a style for
``username``.
"""
def __init__(self, value):
assert isinstance(value, six.text_type)
self.value = value
document = minidom.parseString('%s' % (value, ))
result = []
name_stack = []
fg_stack = []
bg_stack = []
def get_current_style():
" Build style string for current node. "
parts = []
if name_stack:
parts.append('class:' + ','.join(name_stack))
if fg_stack:
parts.append('fg:' + fg_stack[-1])
if bg_stack:
parts.append('bg:' + bg_stack[-1])
return ' '.join(parts)
def process_node(node):
" Process node recursively. "
for child in node.childNodes:
if child.nodeType == child.TEXT_NODE:
result.append((get_current_style(), child.data))
else:
add_to_name_stack = child.nodeName not in ('#document', 'html-root', 'style')
fg = bg = ''
for k, v in child.attributes.items():
if k == 'fg': fg = v
if k == 'bg': bg = v
if k == 'color': fg = v # Alias for 'fg'.
# Check for spaces in attributes. This would result in
# invalid style strings otherwise.
if ' ' in fg: raise ValueError('"fg" attribute contains a space.')
if ' ' in bg: raise ValueError('"bg" attribute contains a space.')
if add_to_name_stack: name_stack.append(child.nodeName)
if fg: fg_stack.append(fg)
if bg: bg_stack.append(bg)
process_node(child)
if add_to_name_stack: name_stack.pop()
if fg: fg_stack.pop()
if bg: bg_stack.pop()
process_node(document)
self.formatted_text = FormattedText(result)
def __repr__(self):
return 'HTML(%r)' % (self.value, )
def __pt_formatted_text__(self):
return self.formatted_text
def format(self, *args, **kwargs):
"""
Like `str.format`, but make sure that the arguments are properly
escaped.
"""
# Escape all the arguments.
args = [html_escape(a) for a in args]
kwargs = dict((k, html_escape(v)) for k, v in kwargs.items())
return HTML(self.value.format(*args, **kwargs))
def __mod__(self, value):
"""
HTML('%s') % value
"""
if not isinstance(value, tuple):
value = (value, )
value = tuple(html_escape(i) for i in value)
return HTML(self.value % value)
def html_escape(text):
# The string interpolation functions also take integers and other types.
# Convert to string first.
if not isinstance(text, six.text_type):
text = '{}'.format(text)
return text.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')