Добавлен формат XmlOutput для внутреннего вывода в цвете

master3.3
Mike Hiretsky 10 years ago
parent d847825495
commit bae95d943c

@ -16,11 +16,13 @@
from output import BaseOutput
from palette import (TextState, BaseColorMapping, ConsoleCodesInfo,
LightColorMapping, ConsoleColor256)
LightColorMapping, ConsoleColor256, XmlFormat)
from calculate.lib.utils.tools import SavableIterator
from itertools import ifilter
from HTMLParser import HTMLParser
import re
class ConsoleCodesConverter(object):
"""Преобразователь текста из цветного консольного вывода через объект
форматирования.
@ -39,19 +41,20 @@ class ConsoleCodesConverter(object):
class CodeElement:
"""Элемент кода в ESC последовательности"""
def __init__(self,condition=lambda code:False,action=lambda :None):
def __init__(self, condition=lambda code: False, action=lambda: None):
self.action = action
self.condition = condition
def tryParse(self,code):
def tryParse(self, code):
"""Обрабатывает ли экземпляр код"""
return self.condition(code)
def parse(self,code,codes):
def parse(self, code, codes):
"""Обработать код, вызвать действие"""
self.action()
def _next_code(self,other):
def _next_code(self, other):
"""
Получить следующий код
"""
@ -68,91 +71,93 @@ class ConsoleCodesConverter(object):
# соответствие консольных цветов внутренним цветам
mapColors = BaseColorMapping.mapConsole_TS
def __init__(self,action=lambda x:None, begin=None, end=None):
def __init__(self, action=lambda x: None, begin=None, end=None):
self.action = action
self.begin = begin
self.end = end
def tryParse(self,code):
def tryParse(self, code):
cci = ConsoleCodesInfo
return code >=self.begin and code <= self.end
return code >= self.begin and code <= self.end
def parse(self,code,codes):
def parse(self, code, codes):
cci = ConsoleCodesInfo
return self.action(self.mapColors.get(code-self.begin,
TextState.Colors.DEFAULT))
return self.action(self.mapColors.get(code - self.begin,
TextState.Colors.DEFAULT))
def __init__(self,output=None,escSymb="\033"):
def __init__(self, output=None, escSymb="\033"):
self.output = output or BaseOutput()
self.escSymb = escSymb
self.escBlock = r"{esc}\[(\d+(?:;\d+)*)m".format(esc=escSymb)
self.reEscBlock = re.compile(self.escBlock)
self.reParse = re.compile(
"(?:{0})?(.*?)(?=$|{0})".format(self.escBlock),
re.DOTALL)
resetBoldHalfbright = lambda : (
(self.output.resetBold() or "") +
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,
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,
underline = element(lambda code: code == cci.UNDERLINE,
self.output.setUnderline)
nounderline = element(lambda code:code==cci.NOUNDERLINE,
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)
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)
end=cci.FOREGROUND_END,
action=self.output.setForeground)
background = self.ColorElement(begin=cci.BACKGROUND,
end=cci.BACKGROUND_END,action=self.output.setBackground)
self.grams = [reset,bold,halfbright,underline,nounderline,normal,
invert,noinvert,foreground,background]
def outputText(self,s):
return self.output.outputText(s)
end=cci.BACKGROUND_END,
action=self.output.setBackground)
self.grams = [reset, bold, halfbright, underline, nounderline, normal,
invert, noinvert, foreground, background]
def transform(self,s):
def transform(self, s):
"""
Запустить преобразование текста
"""
def generator():
for ctrl,txt,_s in self.reParse.findall(s):
for ctrl, txt, _s in self.reParse.findall(s):
if ctrl:
codes = SavableIterator(ctrl.split(';'))
for code in codes:
code = int(code)
res = ""
for gram in ifilter(lambda x:x.tryParse(code),
for gram in ifilter(lambda x: x.tryParse(code),
self.grams):
res = gram.parse(code,codes)
res = gram.parse(code, codes)
break
if res:
yield res
if txt:
yield self.outputText(txt)
yield self.output.outputText(txt)
yield self.output.endText()
return "".join(list(filter(None,generator())))
def detect(self,s):
return "".join(list(filter(None, generator())))
def detect(self, s):
"""
Определить есть ли в тексте управляющие последовательности
"""
return bool(self.reEscBlock.search(s))
class ConsoleCodes256Converter(ConsoleCodesConverter):
"""Расширяет возможность обработки 256 цветного терминала"""
@ -160,7 +165,7 @@ class ConsoleCodes256Converter(ConsoleCodesConverter):
def __init__(self, action=lambda x: None, begin=None):
self.action = action
self.begin = begin
def tryParse(self, code):
return code == self.begin
@ -188,8 +193,90 @@ class ConsoleCodes256Converter(ConsoleCodesConverter):
cci = ConsoleCodesInfo
# обработчики кодов для вывода в 256
foreground256 = self.Color256Element(begin=cci.FOREGROUND256,
action=self.output.setForeground)
action=self.output.setForeground)
background256 = self.Color256Element(begin=cci.BACKGROUND256,
action=self.output.setBackground)
self.grams.insert(0,foreground256)
self.grams.insert(0,background256)
action=self.output.setBackground)
self.grams.insert(0, foreground256)
self.grams.insert(0, background256)
class XmlConverter(object):
"""
Преобразователь текста из внутреннего xml формата
"""
def __init__(self, output=None):
Tags = XmlFormat.Tags
FontAttr = XmlFormat.FontAttributes
self.output = output or BaseOutput()
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.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 = HTMLParser()
self.parser.handle_starttag = self.startElementHandler
self.parser.handle_endtag = self.endElementHandler
self.parser.handle_data = self.characterDataHandler
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(list(filter(None, self.__outdata)))
def addResultToOutdata(f):
"""Добавить возвращаемый результат в список self.__outdata"""
def wrapper(self, *args):
res = f(self, *args)
if res:
self.__outdata.append(res)
return res
return wrapper
@addResultToOutdata
def startElementHandler(self, name, attrs):
if name in self.tagMap:
self.output.pushState()
self.__tagStack.append(name)
if attrs:
return self.tagMap[name](attrs)
else:
return self.tagMap[name]()
else:
if attrs:
s = "<%s %s>" % (name,
" ".join(['%s="%s"' % (k, v)
for k, v in attrs.items()]))
return self.output.outputText(s)
else:
return self.output.outputText("<%s>" % name)
@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("</%s>" % name)
@addResultToOutdata
def characterDataHandler(self, data):
return self.output.outputText(data)
def detect(self, s):
return bool(self.reMatch.search(s))
addResultToOutdata = staticmethod(addResultToOutdata)

@ -14,9 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from xml.etree import ElementTree as ET
from palette import (TextState, BaseColorMapping,
ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
ConsoleCodesInfo, SpanPalette)
ConsoleCodesInfo, SpanPalette, XmlFormat)
class BaseOutput(object):
@ -95,13 +96,13 @@ class BaseOutput(object):
"""
pass
def resetForeground(self, color):
def resetForeground(self):
"""
Использовать цвет шрифта по умолчанию
"""
pass
def resetBackground(self, color):
def resetBackground(self):
"""
Использовать цвет фона по умолчанию
"""
@ -117,6 +118,15 @@ class BaseOutput(object):
Выключить инверсию
"""
def pushState(self):
"""
Сохранить состояние текста
"""
def popState(self):
"""
Восстановить состояние текста из стека
"""
class SaveAttrOutput(BaseOutput):
"""
@ -126,6 +136,7 @@ class SaveAttrOutput(BaseOutput):
def __init__(self, state=None):
self.prev_state = state.clone() if state else TextState()
self.current_state = self.prev_state.clone()
self.state_stack = []
def setBold(self):
self.current_state.bold = True
@ -171,6 +182,11 @@ class SaveAttrOutput(BaseOutput):
def setInvert(self):
self.current_state.invert = True
def pushState(self):
self.state_stack.append(self.current_state.clone())
def popState(self):
self.current_state = self.state_stack.pop()
class ColorTerminalOutput(SaveAttrOutput):
"""
@ -186,13 +202,14 @@ class ColorTerminalOutput(SaveAttrOutput):
def setBold(self):
self.resetHalfbright()
SaveAttrOutput.setBold(self)
super(ColorTerminalOutput, self).setBold()
def setHalfbright(self):
self.resetBold()
SaveAttrOutput.setHalfbright(self)
super(ColorTerminalOutput, self).setHalfbright()
def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать изменение тона"""
cci = ConsoleCodesInfo
color = curstate.foreground
if color in self.mapColors:
@ -207,6 +224,7 @@ class ColorTerminalOutput(SaveAttrOutput):
self.resetForeground()
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать изменение фона"""
color = curstate.background
if color in self.mapBackgroundColors:
attrs.append(self.mapBackgroundColors[color])
@ -214,6 +232,7 @@ class ColorTerminalOutput(SaveAttrOutput):
attrs.append(ConsoleCodesInfo.BACKGROUND_DEFAULT)
def handleIntensity(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать интенсивность"""
if curstate.bold and curstate.bold != prevstate.bold:
attrs.append(ConsoleCodesInfo.BOLD)
elif (curstate.halfbright and
@ -223,22 +242,31 @@ class ColorTerminalOutput(SaveAttrOutput):
attrs.append(ConsoleCodesInfo.NORMAL)
def handleUnderline(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать подчеркивание"""
if curstate.underline:
attrs.append(ConsoleCodesInfo.UNDERLINE)
else:
attrs.append(ConsoleCodesInfo.NOUNDERLINE)
def handleInvert(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать инверсию"""
if curstate.invert:
attrs.append(ConsoleCodesInfo.INVERT)
else:
attrs.append(ConsoleCodesInfo.NOINVERT)
def handleColor(self, prevstate, curstate, attrs, tail_attrs):
"""Обработать изменение цветов (фон и/или тон)"""
if prevstate.foreground != curstate.foreground:
self.handleForeground(prevstate, curstate, attrs, tail_attrs)
if prevstate.background != curstate.background:
self.handleBackground(prevstate, curstate, attrs, tail_attrs)
def _createAttrs(self, prevstate, curstate):
"""
Создать ESC последовательность для установки параметров текста
"""
attrs, tail_attrs = [],[]
attrs, tail_attrs = [], []
# получить интенсивность (полутон и жирность относятся к интенсивности)
intensity = lambda x: x & (TextState.Attributes.HALFBRIGHT |
TextState.Attributes.BOLD)
@ -255,10 +283,9 @@ class ColorTerminalOutput(SaveAttrOutput):
self.handleUnderline(prevstate, curstate, attrs, tail_attrs)
if prevstate.invert != curstate.invert:
self.handleInvert(prevstate, curstate, attrs, tail_attrs)
if prevstate.foreground != curstate.foreground:
self.handleForeground(prevstate, curstate, attrs, tail_attrs)
if prevstate.background != curstate.background:
self.handleBackground(prevstate, curstate, attrs, tail_attrs)
if (prevstate.foreground != curstate.foreground or
prevstate.background != curstate.background):
self.handleColor(prevstate, curstate, attrs, tail_attrs)
return attrs, tail_attrs
def _createEscCode(self, attrs):
@ -307,8 +334,8 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256,
color256])
else:
ColorTerminalOutput.handleForeground(self, prevstate, curstate,
attrs, tail_attrs)
super(ColorTerminal256Output,
self).handleForeground(prevstate, curstate, attrs, tail_attrs)
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.background
@ -320,12 +347,16 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256,
color256])
else:
ColorTerminalOutput.handleBackground(self, prevstate, curstate,
attrs, tail_attrs)
super(ColorTerminal256Output,
self).handleBackground(prevstate, curstate, attrs, tail_attrs)
class ColorTerminal16Output(ColorTerminalOutput):
"""
Вывод на 16 цветный терминал с преобразованием RGB к ближайшему базовому
Bugs:
После преобразования текст и фон могут одинакового цвета
"""
def __init__(self, state=None, palette=None):
@ -368,73 +399,148 @@ class ColorTerminal16Output(ColorTerminalOutput):
prevstate, _curstate,
attrs, tail_attrs)
class SpanCssOutput(SaveAttrOutput):
"""
"""
Форматирует текст для вывода в консоль
"""
def __init__(self, state=None, palette=SpanPalette()):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
def __init__(self, state=None, palette=SpanPalette()):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
def getStringColor(self, color, bold=False, halfbright=False,
background=False):
"""
def getStringColor(self, color, bold=False, halfbright=False,
background=False):
"""
Получить название цвета по номеру и состоянию текста
"""
if halfbright:
bright = SpanPalette.LOW_BRIGHT
elif bold:
bright = SpanPalette.HIGH_BRIGHT
else:
bright = SpanPalette.NORMAL_BRIGHT
if halfbright:
bright = SpanPalette.LOW_BRIGHT
elif bold:
bright = SpanPalette.HIGH_BRIGHT
else:
bright = SpanPalette.NORMAL_BRIGHT
if background:
return self.palette.getBackgroundColor(color)
else:
return self.palette.getTextColor(color, bright)
if background:
return self.palette.getBackgroundColor(color)
def getTags(self, prevstate, curstate):
"""
Создать tag span для указания параметров текста
"""
style = []
colorAttr = ["color", "background"]
if curstate.invert:
colorAttr = colorAttr[1], colorAttr[0]
if (prevstate.foreground != curstate.foreground or
prevstate.bold != curstate.bold or
curstate.invert or
prevstate.halfbright != curstate.halfbright):
sColor = self.getStringColor(curstate.foreground,
curstate.bold,
curstate.halfbright,
background=False)
style.append("%s:%s;" % (colorAttr[0], sColor))
if prevstate.background != curstate.background or curstate.invert:
sColor = self.getStringColor(curstate.background,
background=True)
style.append("%s:%s;" % (colorAttr[1], sColor))
if prevstate.underline != curstate.underline:
if curstate.underline:
style.append("text-decoration:underline;")
else:
return self.palette.getTextColor(color, bright)
style.append("text-decoration:none;")
if prevstate.bold != curstate.bold:
if curstate.bold:
style.append("font-weight:bold;")
else:
style.append("font-weight:normal;")
return '<span style="%s">' % "".join(style), '</span>'
def getTags(self, prevstate, curstate):
"""
Создать ESC последовательность для установки параметров текста
def outputText(self, s):
if self.prev_state != self.current_state:
lattr, rattr = self.getTags(self.prev_state, self.current_state)
else:
lattr = rattr = ""
return lattr + s + rattr
def endText(self):
self.reset()
return ""
class XmlOutput(SaveAttrOutput):
"""
Форматирует текст c описанием формата в XML для внутренней передачи
Bugs: игнорирует первоначальное состояние (state)
"""
def __init__(self, state=None):
super(XmlOutput, self).__init__(state=state)
self.clear_state = TextState()
def getXML(self, curstate, text):
"""
style = []
Создать управляющие тэги
colorAttr = ["colortext", "background"]
if curstate.invert:
colorAttr = colorAttr[1], colorAttr[0]
if (prevstate.foreground != curstate.foreground or
prevstate.bold != curstate.bold or
curstate.invert or
prevstate.halfbright != curstate.halfbright):
sColor = self.getStringColor(curstate.foreground,
curstate.bold,
curstate.halfbright,
background=False)
style.append("%s:%s;" % (colorAttr[0], sColor))
if prevstate.background != curstate.background or curstate.invert:
sColor = self.getStringColor(curstate.background,
background=True)
style.append("%s:%s;" % (colorAttr[1], sColor))
if prevstate.underline != curstate.underline:
if curstate.underline:
style.append("text-decoration:underline;")
else:
style.append("text-decoration:none;")
if prevstate.bold != curstate.bold:
if curstate.bold:
style.append("font-weight:bold;")
else:
style.append("font-weight:normal;")
return '<span style="%s">' % "".join(style), '</span>'
def outputText(self, s):
if self.prev_state != self.current_state:
lattr, rattr = self.getTags(self.prev_state, self.current_state)
:type curstate: TextState
:type text: str
:type rtype: ET.Element
"""
Tags, FontAttributes = XmlFormat.Tags, XmlFormat.FontAttributes
root = ET.Element("root")
tail = root
if (curstate.foreground != TextState.Colors.DEFAULT or
curstate.background != TextState.Colors.DEFAULT):
tail = ET.SubElement(tail, Tags.FONT)
sColor = str(curstate.foreground or "")
if sColor:
tail.attrib[FontAttributes.FOREGROUND] = sColor
sColor = str(curstate.background or "")
if sColor:
tail.attrib[FontAttributes.BACKGROUND] = sColor
if curstate.halfbright:
tail = ET.SubElement(tail, Tags.HALFBRIGHT)
if curstate.invert:
tail = ET.SubElement(tail, Tags.INVERT)
if curstate.underline:
tail = ET.SubElement(tail, Tags.UNDERLINE)
if curstate.bold:
tail = ET.SubElement(tail, Tags.BOLD)
tail.text = text
return root[0]
def xmlToString(self, xml):
"""
Получить строку xml не преобразовывая &, <, >
"""
def generator(root):
if root.attrib:
yield "<%s %s>" % (root.tag,
" ".join(['%s="%s"' % (k, v) for k, v in
root.attrib.items()]))
else:
lattr = rattr = ""
return lattr + s + rattr
yield "<%s>" % root.tag
if len(root):
for element in root:
for text in generator(element):
yield text
if root.text:
yield root.text
yield "</%s>" % root.tag
return "".join(filter(None, list(generator(xml))))
def outputText(self, s):
if self.clear_state != self.current_state:
return self.xmlToString(self.getXML(self.current_state, s))
else:
return s
def endText(self):
self.reset()
return ""
def endText(self):
self.reset()
return ""

@ -334,7 +334,6 @@ class SpanPalette:
Returns: TextState.Colors если подходящий цвет найден
"""
# TODO: исключить при приобразовании одинаковые цвет фона и тона
def calculate_color_diff(Ri,Ro,Gi,Go,Bi,Bo):
# вычислить отличие цветов
kR, kG, kB = 30, 59, 25
@ -371,3 +370,18 @@ class DarkPastelsPalette(SpanPalette):
highBright = dict(zip(TextState.lightColors,
["#709080", "#DCA3A3", "#72D5A3", "#F0DFAF",
"#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF"]))
class XmlFormat:
"""
Константы для внутреннего формата XML
"""
class Tags:
BOLD = "b"
UNDERLINE = "u"
FONT = "font"
HALFBRIGHT = "dark"
INVERT = "invert"
class FontAttributes:
BACKGROUND = "bgColor"
FOREGROUND = "color"

Loading…
Cancel
Save