Python - ncurses printing html as colors

From NoskeWiki
Jump to navigation Jump to search


NOTE: This page is a daughter page of: Ncurses and Python

ncurses is text-based user interface library. Here we build on examples and provide functions capable of taking a html string, and using it to change font color and style as it prints.

The goal.... instead of needing to break apart formatted text like this:

  screen.addstr(1, 0, 'Hello ', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(1, 6, 'bold', curses.A_BOLD | curses.color_pair(1))
  screen.addstr(1, 10, ' world', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(2, 0, 'See: ', curses.A_NORMAL | curses.color_pair(1))
  screen.addstr(2, 5, '', curses.A_UNDERLINED | curses.color_pair(8))

We want this:

  print_html_str(1, 0, 'Hello <b>bold</b> world<br>See: <a></a>', True)

#!/usr/bin/env python
# - ncurses interface which inputs a HTML like
# string and outputs with color.
# See:
# Usage:
# Usage examples:
#   $ python

import curses

demo_str = """<h1>Heading</h1>
Bold text: <b>bold is yellow</b>.
Blue hyperlink: <a></a>.

def get_style_for_html_tag(tag_inner_str):
  """Inputs html tag label and returns matching font color/style for ncurses.

    tag_inner_str: inner contents of a HTML tag, such as 'b' for a start bold tag.
        Not many tag types are supported and any unrecognized tags or closing
        tags (eg: '/b') return a default style - in this case normal white text.
    A curses font specification suitable for input into 'screen.addstr()'.
  style_default = curses.A_NORMAL | curses.color_pair(7)  # White.
  style_b = curses.A_NORMAL | curses.color_pair(3)        # Yellow.
  style_h1 = curses.A_BOLD | curses.color_pair(6)         # Cyan bold.
  style_a = curses.A_UNDERLINE | curses.color_pair(4)     # Blue underlined.
  if tag_inner_str == 'b':
    return style_b
  elif tag_inner_str == 'h1':
    return style_h1
  elif tag_inner_str == 'a' or tag_inner_str.startswith('a '):
    return style_a
    return style_default

def print_html_str(y_start, x_start, html_str,
                   in_screen, make_newlines_br):
  """Prints to ncurses html where certain tags change the font color/style.

    y_start: col position to start at.
    x_start: row position to start at.
    html_str: a html text string such as 'this <b>example</b>'.
    in_screen: an ncurses screen generated with 'curses.initscr()'
    make_newlines_br: if true will turn any newlines into '<br>' before
        processing otherwise newlines are simply ignored and only '<br>'
        or '<p>' will go to the next line.

    The number of lines (rows) output, which will be at least one, but more
    if there are '<br>' and/or '<p>' tags.
  curr_color = get_style_for_html_tag('default')
  x = x_start
  y = y_start
  remaining_str = html_str.replace('\n', '')
  if make_newlines_br:
    remaining_str = html_str.replace('\n', '<br>')
  while len(remaining_str) > 0:
    pos = remaining_str.find('<')  # Find start of tag (<).
    if pos >= 0:
      # Output part up to start of tag:
      part_str = remaining_str[:pos]
      in_screen.addstr(y, x, part_str, curr_color)
      remaining_str = remaining_str[pos+1:]
      x = x + pos
      # Determine what tag is:
      pos_end = remaining_str.find('>')  # Find end of tag (<).
      if pos_end >= 0:
        tag_str = remaining_str[:pos_end]
        curr_color = get_style_for_html_tag(tag_str)
        remaining_str = remaining_str[pos_end+1:]
        if tag_str == 'br' or tag_str == 'p':
          y = y+1
          x = x_start
      # Output everything remaining:
      in_screen.addstr(y, x, remaining_str, curr_color)
      remaining_str = ''
      return y - y_start + 1

def create_color_pairs():
  """Creates a set of indexed color pairs for curses for each text color.
  Notice these are ordered 1-8 in rough order of 'ANSI color' values
  but with black as 8 instead of 0, since numbers start at 1.
  curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
  curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
  curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
  curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
  curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
  curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
  curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK)
  curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK)

def main():
  screen = curses.initscr()
  print_html_str(2, 5, 'Here is a <b>basic test</b>', screen, False)
  print_html_str(5, 5, demo_str, screen, True)
  opt = screen.getch()  # Wait for user to enter character.

if __name__ == '__main__':

See Also