You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/utils/colortext/converter.py

370 lines
14 KiB

9 years ago
# -*- coding: utf-8 -*-
# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
3 years ago
from .output import BaseOutput
from .palette import (TextState, BaseColorMapping, ConsoleCodesInfo,
LightColorMapping, ConsoleColor256, XmlFormat)
from ..tools import SaveableIterator, ignore
3 years ago
from html.parser import HTMLParser
# from HTMLParser import HTMLParser
import re
class BaseConverter():
"""
Базовый класс обработки (ничего не конвертирует - возвращает как есть)
"""
9 years ago
def __init__(self, output=BaseOutput()):
self.output = output
def transform(self, s):
return self.output.outputText(s)
def detect(self, s):
return True
9 years ago
class ConsoleCodesConverter(BaseConverter):
"""Преобразователь текста из цветного консольного вывода через объект
форматирования.
Объект форматирования должен реализовывать BaseOutput
>>> cct = ConsoleCodesConverter(BaseOutput())
>>> outtext = "\033[32;1mHello\033[0;39m"
>>> cct.transform(outtext)
'Hello'
>>> from output import SpanCssOutput
>>> cct = ConsoleCodesConverter(SpanCssOutput())
>>> outtext = "\033[32;1mHello\033[0;39m"
>>> cct.transform(outtext)
'<span style="color:Green;font-weight:bold;">Hello</span>'
"""
class CodeElement():
"""Элемент кода в ESC последовательности"""
9 years ago
def __init__(self, condition=lambda code: False, action=None):
self.action = action
self.condition = condition
def tryParse(self, code):
"""Обрабатывает ли экземпляр код"""
return self.condition(code)
def parse(self, code, codes):
"""Обработать код, вызвать действие"""
9 years ago
if callable(self.action):
return self.action()
return None
def _next_code(self, other):
"""
Получить следующий код
"""
try:
3 years ago
return int(next(other))
except StopIteration:
return None
class ColorElement():
"""Элемент кода для указания стандартного цвета
Проверка кода в интервале, запуск действия с передачей цвета
"""
# соответствие консольных цветов внутренним цветам
mapColors = BaseColorMapping.mapConsole_TS
9 years ago
def __init__(self, action=None, begin=None, end=None):
self.action = action
self.begin = begin
self.end = end
def tryParse(self, code):
try:
code = ord(code)
except Exception as e:
return False
9 years ago
return self.begin <= code <= self.end
def parse(self, code, codes):
9 years ago
if callable(self.action):
return self.action(self.mapColors.get(code - self.begin,
TextState.Colors.DEFAULT))
else:
return None
9 years ago
def __init__(self, output=BaseOutput(), escSymb="\033"):
super().__init__(output)
self.escSymb = escSymb
self.escBlock = (r"{esc}(?:\[(\d*(?:;\d+)*)(m)|"
"\]\d+;.*?\x07|"
"\([B0UK]|"
"\[\d*[A-D])".format(esc=escSymb))
self.otherSymb = "(?:\r*\n|\t)"
self.reEscBlock = re.compile(self.escBlock)
self.reParse = re.compile(
"(?:{0}|({1}))?(.*?)(?=$|{0}|{1})".format(self.escBlock,
9 years ago
self.otherSymb),
re.DOTALL)
resetBoldHalfbright = lambda: (
(self.output.resetBold() or "") +
(self.output.resetHalfbright() or ""))
cci = ConsoleCodesInfo
element = self.CodeElement
# набор правил обработки кодов
reset = element(lambda code: code == cci.RESET, self.output.reset)
bold = element(lambda code: code == cci.BOLD, self.output.setBold)
halfbright = element(lambda code: code == cci.HALFBRIGHT,
self.output.setHalfbright)
underline = element(lambda code: code == cci.UNDERLINE,
self.output.setUnderline)
nounderline = element(lambda code: code == cci.NOUNDERLINE,
self.output.resetUnderline)
invert = element(lambda code: code == cci.INVERT,
self.output.setInvert)
noinvert = element(lambda code: code == cci.NOINVERT,
self.output.resetInvert)
normal = element(lambda code: code == cci.NORMAL,
resetBoldHalfbright)
reset_foreground = element(lambda code: code == cci.FOREGROUND_DEFAULT,
self.output.resetForeground)
reset_background = element(lambda code: code == cci.BACKGROUND_DEFAULT,
self.output.resetBackground)
foreground = self.ColorElement(begin=cci.FOREGROUND,
end=cci.FOREGROUND_END,
action=self.output.setForeground)
background = self.ColorElement(begin=cci.BACKGROUND,
end=cci.BACKGROUND_END,
action=self.output.setBackground)
9 years ago
newline = element(
lambda code: type(code) != int and ("\r" in code or "\n" in code),
self.output.newLine)
tab = element(lambda code: type(code) != int and "\t" in code,
self.output.tab)
self.grams = [reset, bold, halfbright, underline, nounderline, normal,
invert, noinvert, reset_foreground, reset_background,
foreground, background, tab, newline]
def evaluteGram(self, code, codes=None):
"""Выполнить грамматику"""
if codes is None:
codes = SaveableIterator([])
for gram in (x for x in self.grams if x.tryParse(code)):
return gram.parse(code, codes)
def transform(self, s):
"""
Запустить преобразование текста
"""
def generator():
for ctrl, m, other, txt, _s, _m in self.reParse.findall(s):
if m:
codes = SaveableIterator(ctrl.split(';'))
for code in codes:
code = int(code or '0')
res = self.evaluteGram(code, codes)
if res:
yield res
elif other:
res = self.evaluteGram(other)
if res:
yield res
if txt:
yield self.output.outputText(txt)
yield self.output.endText()
return "".join((x for x in generator() if x))
def detect(self, s):
"""
Определить есть ли в тексте управляющие последовательности
"""
return bool(self.reEscBlock.search(s))
class ConsoleCodes256Converter(ConsoleCodesConverter):
"""Расширяет возможность обработки 256 цветного терминала"""
class Color256Element(ConsoleCodesConverter.CodeElement):
9 years ago
def __init__(self, action=None, begin=None):
super().__init__(condition=lambda code: code == begin,
9 years ago
action=action)
def parse(self, code, codes):
"""
Тон: 38;5;0-255
Фон: 48;5;0-255
"""
colorMap = LightColorMapping(BaseColorMapping).mapConsole_TS
codes.save()
if self._next_code(codes) == ConsoleCodesInfo.COLOR256:
code = self._next_code(codes)
if code is not None:
if code in colorMap:
9 years ago
var = colorMap[code]
else:
9 years ago
var = ConsoleColor256.consoleToRgb(code)
if callable(self.action):
self.action(var)
else:
# если после 38 не 5 - не обрабатываем этот код
codes.restore()
def __init__(self, *args, **kwargs):
ConsoleCodesConverter.__init__(self, *args, **kwargs)
cci = ConsoleCodesInfo
# обработчики кодов для вывода в 256
foreground256 = self.Color256Element(begin=cci.FOREGROUND256,
action=self.output.setForeground)
background256 = self.Color256Element(begin=cci.BACKGROUND256,
action=self.output.setBackground)
self.grams.insert(0, foreground256)
self.grams.insert(0, background256)
class XmlConverter(BaseConverter):
"""
Преобразователь текста из внутреннего xml формата
"""
unescaper = XmlFormat.unescaper
9 years ago
def __init__(self, output=BaseOutput()):
super().__init__(output)
Tags = XmlFormat.Tags
FontAttr = XmlFormat.FontAttributes
self.tagMap = {
Tags.BOLD: self.output.setBold,
Tags.HALFBRIGHT: self.output.setHalfbright,
Tags.INVERT: self.output.setInvert,
Tags.UNDERLINE: self.output.setUnderline,
Tags.FONT: self.parseFont
}
self.singletagMap = {
Tags.NEWLINE: self.output.newLine,
Tags.TAB: self.output.tab
}
self.colorMap = {FontAttr.FOREGROUND.lower(): self.output.setForeground,
FontAttr.BACKGROUND.lower(): self.output.setBackground}
self.reMatch = re.compile("<(?:%s)" % "|".join(self.tagMap.keys()),
re.I)
self.parser = self.createParser()
def createParser(self):
"""
Создать парсер HTML кода
"""
parser = HTMLParser()
parser.handle_starttag = self.startElementHandler
parser.handle_endtag = self.endElementHandler
parser.handle_data = self.characterDataHandler
parser.handle_startendtag = self.startendElementHandler
parser.handle_entityref = self.entityrefElementHandler
return parser
def parseFont(self, *attrs):
for k, v in attrs:
k = str(k).lower()
if k in self.colorMap:
self.colorMap[k](str(v))
def transform(self, s):
self.__outdata = []
self.__tagStack = []
self.parser.feed(s)
self.__outdata.append(self.output.endText())
return "".join((x for x in self.__outdata if x))
def addResultToOutdata(f):
"""Добавить возвращаемый результат в список self.__outdata"""
def wrapper(self, *args):
res = f(self, *args)
if res:
self.__outdata.append(res)
return res
return wrapper
9 years ago
def _buildTaq(self, name, attrs=(), startendTag=False, endTag=False):
"""
Создать тэг по параметрам
"""
lslash, rslash = '', ''
if startendTag:
rslash = '/'
elif endTag:
lslash = '/'
if attrs:
return "<{name} {attrs}{rslash}>".format(
name=name, attrs=" ".join(['%s="%s"' % (k, v)
for k, v in attrs]),
rslash=rslash)
else:
return "<{lslash}{name}{rslash}>".format(lslash=lslash, name=name,
rslash=rslash)
@addResultToOutdata
def startElementHandler(self, name, attrs):
"""Обработчик начального тега"""
if name in self.tagMap:
self.output.pushState()
self.__tagStack.append(name)
with ignore(TypeError):
return self.tagMap[name](*attrs)
else:
return self.output.outputText(self._buildTaq(name, attrs))
@addResultToOutdata
def startendElementHandler(self, name, attrs):
"""Обработчик одиночного тега"""
if name in self.singletagMap:
with ignore(TypeError):
return self.singletagMap[name](*attrs)
else:
return self.output.outputText(
self._buildTaq(name, attrs, startendTag=True))
@addResultToOutdata
def endElementHandler(self, name):
"""Обработчик завершающего тега"""
if name in self.tagMap:
if name in self.__tagStack:
while self.__tagStack and self.__tagStack.pop() != name:
self.output.popState()
self.output.popState()
else:
return self.output.outputText(self._buildTaq(name, endTag=True))
@addResultToOutdata
def characterDataHandler(self, data):
"""Обработчик текста в тэгах"""
return self.output.outputText(self.unescaper(data))
@addResultToOutdata
def entityrefElementHandler(self, data):
return self.output.outputText(self.unescaper("&%s;" % data))
def detect(self, s):
return bool(self.reMatch.search(s))
addResultToOutdata = staticmethod(addResultToOutdata)