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

Correct handling of ansi colour codes when nbconverting to latex #4807

Merged
merged 3 commits into from Jan 29, 2014
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 43 additions & 24 deletions IPython/nbconvert/filters/ansi.py
Expand Up @@ -106,37 +106,53 @@ def ansi2html(text):


def single_ansi2latex(code):
"""Converts single ansi markup to latex format
"""Converts single ansi markup to latex format.

Return latex code and number of open brackets.

Accepts codes like '\x1b[1;32m' (bold, red) and the short form '\x1b[32m' (red)

Colors are matched to those defined in coloransi, which defines colors
using the 0, 1 (bold) and 5 (blinking) styles. Styles 1 and 5 are
interpreted as bold. All other styles are mapped to 0. Note that in
coloransi, a style of 1 does not just mean bold; for example, Brown is
"0;33", but Yellow is "1;33". An empty string is returned for unrecognised
codes and the "reset" code '\x1b[m'.
"""
for color in coloransi.color_templates:

#Make sure to get the color code (which is a part of the overall style)
# i.e. 0;31 is valid
# 31 is also valid, and means the same thing
#coloransi.color_templates stores the longer of the two formats %d;%d
#Get the short format so we can parse that too. Short format only exist
#if no other formating is applied (the other number must be a 0)!
style_code = getattr(coloransi.TermColors, color[0])
color_code = style_code.split(';')[1]
is_normal = style_code.split(';')[0] == '0'

# regular weight
if (code == style_code) or (is_normal and code == color_code):

return r'{\color{'+color[0].lower()+'}', 1
# bold
if code == style_code[:3]+str(1)+style_code[3:]:
return r'\textbf{\color{'+color[0].lower()+'}', 1
return '', 0
components = code.split(';')
if len(components) > 1:
style = components[0][-1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use style = components[0][2:], in case more than one digit is passed ('01' is valid and comes up sometimes, and technically '001' is also valid).

It probably makes sense to use int(style), rather than strings.

color = components[1][:-1]
else:
style = '0'
color = components[0][-3:-1]

# If the style is not normal (0), bold (1) or blinking (5) then treat it as normal
if style not in '015':
style = '0'

for name, tcode in coloransi.color_templates:
tstyle, tcolor = tcode.split(';')
if tstyle == style and tcolor == color:
break
else:
return '', 0

if style == '5':
name = name[5:] # BlinkRed -> Red, etc
name = name.lower()

if style in '15':
return r'\textbf{\color{'+name+'}', 1
else:
return r'{\color{'+name+'}', 1

def ansi2latex(text):
"""Converts ansi formated text to latex version

based on https://bitbucket.org/birkenfeld/sphinx-contrib/ansi.py
"""
color_pattern = re.compile('\x1b\\[([^m]+)m')
color_pattern = re.compile('\x1b\\[([^m]*)m')
last_end = 0
openbrack = 0
outstring = ''
Expand All @@ -146,8 +162,9 @@ def ansi2latex(text):
if openbrack:
outstring += '}'*openbrack
openbrack = 0
if not (match.group() == coloransi.TermColors.Normal or openbrack):
texform, openbrack = single_ansi2latex(match.group())
code = match.group()
if not (code == coloransi.TermColors.Normal or openbrack):
texform, openbrack = single_ansi2latex(code)
outstring += texform
last_end = match.end()

Expand All @@ -156,3 +173,5 @@ def ansi2latex(text):
if openbrack:
outstring += '}'*openbrack
return outstring.strip()


9 changes: 6 additions & 3 deletions IPython/nbconvert/filters/tests/test_ansi.py
Expand Up @@ -71,10 +71,13 @@ def test_ansi2latex(self):
'%s' % (TermColors.Red) : r'{\color{red}}',
'hello%s' % TermColors.Blue: r'hello{\color{blue}}',
'he%s%sllo' % (TermColors.Green, TermColors.Cyan) : r'he{\color{green}}{\color{cyan}llo}',
'%shello' % TermColors.Yellow : r'{\color{yellow}hello}',
'{0}h{0}e{0}l{0}l{0}o{0}'.format(TermColors.White) : r'{\color{white}h}{\color{white}e}{\color{white}l}{\color{white}l}{\color{white}o}{\color{white}}',
'%shello' % TermColors.Yellow : r'\textbf{\color{yellow}hello}',
'{0}h{0}e{0}l{0}l{0}o{0}'.format(TermColors.White) : r'\textbf{\color{white}h}\textbf{\color{white}e}\textbf{\color{white}l}\textbf{\color{white}l}\textbf{\color{white}o}\textbf{\color{white}}',
'hel%slo' % TermColors.Green : r'hel{\color{green}lo}',
'hello' : 'hello'}
'hello' : 'hello',
u'hello\x1b[34mthere\x1b[mworld' : u'hello{\\color{blue}there}world',
u'hello\x1b[mthere': u'hellothere'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a test for u'hello\x1b[01;34mthere', which I think will not be correctly handled just yet.

}

for inval, outval in correct_outputs.items():
self._try_ansi2latex(inval, outval)
Expand Down
2 changes: 1 addition & 1 deletion IPython/nbconvert/templates/latex/base.tplx
Expand Up @@ -20,8 +20,8 @@ This template does not define a docclass, the inheriting class must define this.
\usepackage{geometry} % Used to adjust the document margins
\usepackage{amsmath} % Equations
\usepackage{amssymb} % Equations
\usepackage[utf8]{inputenc} % Allow utf-8 characters in the tex document
\usepackage[mathletters]{ucs} % Extended unicode (utf-8) support
\usepackage[utf8x]{inputenc} % Allow utf-8 characters in the tex document
\usepackage{fancyvrb} % verbatim replacement that allows latex
\usepackage{grffile} % extends the file name processing of package graphics
% to support a larger range
Expand Down