Получение корректной длины строки в Python для строк с цветовыми кодами ANSI

У меня есть некоторый код Python, который автоматически распечатает ряд данных в хорошем формате столбца, включая включение соответствующих escape-последовательностей ASCII для окраски различных частей данных для удобочитаемости.

Я в конечном счете заканчиваю с каждой строкой, представленной как список, при этом каждый объект является столбцом, который дополнен пространством так, чтобы те же столбцы на каждой строке всегда были той же длиной. К сожалению, когда я на самом деле иду для печати этого, не, все столбцы выстраиваются в линию. Я подозреваю, что это относится к escape-последовательностям ASCII - потому что len функция, кажется, не распознает их:

>>> a = '\x1b[1m0.0\x1b[0m'
>>> len(a)
11
>>> print a
0.0

И поэтому в то время как каждый столбец является той же длиной согласно len, они не на самом деле та же длина при печати на экране.

Есть ли какой-либо путь (сохраните для того, чтобы сделать некоторое хакерство с регулярными выражениями, которые я не сделал бы) взять завершенную строку и узнать то, что печатная длина так, я могу расположить клавиатуру с интервалами соответственно? Возможно, некоторый способ просто "распечатать" его назад, чтобы представить в виде строки и исследовать длину этого?

18
задан ThiefMaster 6 August 2014 в 15:05
поделиться

3 ответа

Pyparding Wiki включает в себя это полезное выражение для сопоставления на ANSI escape Sequence:

ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) + 
                oneOf(list(alphas)))

Вот как сделать это в escape-Sequence-Stripmer:

from pyparsing import *

ESC = Literal('\x1b')
integer = Word(nums)
escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer,';')) + 
                oneOf(list(alphas)))

nonAnsiString = lambda s : Suppress(escapeSeq).transformString(s)

unColorString = nonAnsiString('\x1b[1m0.0\x1b[0m')
print unColorString, len(unColorString)

:

0.0 3
11
ответ дан 30 November 2019 в 09:14
поделиться

Я не понимаю двух вещей.

(1) Это ваш код, под вашим контролем. Вы хотите добавить к своим данным экранирующие последовательности, а затем снова их удалить, чтобы вычислить длину ваших данных?? Кажется намного проще вычислить подкладку до добавления экранирующих последовательностей. Что я пропустил?

Предположим, что ни одна из экранирующих последовательностей не изменит положение курсора. Если они изменят, то текущий принятый ответ все равно не сработает.

Предположим, что у вас есть строковые данные для каждого столбца (до добавления управляющих последовательностей) в списке с именем string_data, а заданная ширина столбца находится в списке с именем width. Попробуйте что-нибудь вроде этого:

temp = []
for colx, text in enumerate(string_data):
    npad = width[colx] - len(text) # calculate padding size
    assert npad >= 0
    enhanced = fancy_text(text, colx, etc, whatever) # add escape sequences
    temp.append(enhanced + " " * npad)
sys.stdout.write("".join(temp))

Обновление 1

После комментария OP:

Причина, по которой я хочу снять их и вычислить длину после строка содержит цветовые коды, потому что все данные построены программно. У меня есть куча методов раскрашивания, и я создаю поднять данные что-то вроде этого: str = "%s/%s/%s" % (GREEN(data1), BLUE(data2), RED(data3)) Было бы довольно сложно окрасить смс после факта.

Если данные собраны из кусков, каждый из которых имеет свое собственное форматирование, вы все равно можете рассчитать отображаемую длину и блокнот в зависимости от ситуации. Вот функция, которая делает это для содержимого одной ячейки:

BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(40, 48)
BOLD = 1

def render_and_pad(reqd_width, components, sep="/"):
    temp = []
    actual_width = 0
    for fmt_code, text in components:
        actual_width += len(text)
        strg = "\x1b[%dm%s\x1b[m" % (fmt_code, text)
        temp.append(strg)
    if temp:
        actual_width += len(temp) - 1
    npad = reqd_width - actual_width
    assert npad >= 0
    return sep.join(temp) + " " * npad

print repr(
    render_and_pad(20, zip([BOLD, GREEN, YELLOW], ["foo", "bar", "zot"]))
    )

Если вы думаете, что вызов перегружен пунктуацией, вы можете сделать что-нибудь вроде:

BOLD = lambda s: (1, s)
BLACK = lambda s: (40, s)
# etc
def render_and_pad(reqd_width, sep, *components):
    # etc

x = render_and_pad(20, '/', BOLD(data1), GREEN(data2), YELLOW(data3))

(2) Я не понимаю, почему вы не хотите использовать поставляемый в комплекте набор регулярных выражений With-Python? Никакого "хакерства" (для любого возможного значения "хакерства", о котором я знаю):

>>> import re
>>> test = "1\x1b[a2\x1b[42b3\x1b[98;99c4\x1b[77;66;55d5"
>>> expected = "12345"
>>> # regex = re.compile(r"\x1b\[[;\d]*[A-Za-z]")
... regex = re.compile(r"""
...     \x1b     # literal ESC
...     \[       # literal [
...     [;\d]*   # zero or more digits or semicolons
...     [A-Za-z] # a letter
...     """, re.VERBOSE)
>>> print regex.findall(test)
['\x1b[a', '\x1b[42b', '\x1b[98;99c', '\x1b[77;66;55d']
>>> actual = regex.sub("", test)
>>> print repr(actual)
'12345'
>>> assert actual == expected
>>>

Обновление-2

После комментария OP:

Я все еще предпочитаю ответ Пола, так как он более лаконичен

Больше лаконичен, чем что? Разве следующее решение регекса не достаточно лаконично для вас?

# === setup ===
import re
strip_ANSI_escape_sequences_sub = re.compile(r"""
    \x1b     # literal ESC
    \[       # literal [
    [;\d]*   # zero or more digits or semicolons
    [A-Za-z] # a letter
    """, re.VERBOSE).sub
def strip_ANSI_escape_sequences(s):
    return strip_ANSI_escape_sequences_sub("", s)

# === usage ===
raw_data = strip_ANSI_escape_sequences(formatted_data)

[Выше приведен код, исправленный после того, как @Nick Perkins указал, что он не работает]

3
ответ дан 30 November 2019 в 09:14
поделиться

При просмотре ANSI_escape_code последовательность в вашем примере следующая: Выбрать графическое представление (вероятно жирный ).

Попытайтесь управлять позиционированием столбца с помощью последовательности CUrsor Position ( CSI n; m H ). Таким образом, ширина предыдущего текста не влияет на текущий столбец положение, и не нужно беспокоиться о ширине строки.

Лучшим вариантом, если вы ориентируетесь на Unix, является использование оконных объектов модуля curses . Например, строку можно разместить на экране с помощью:

window. addnstr ([y, x], str, n [, attr])

Закрасьте не более n символов строки str at (y, x) с атрибутами attr, перезаписав все, что ранее отображалось на дисплее.

1
ответ дан 30 November 2019 в 09:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: