Linux Terminal Escape Sequence nach HTML konvertieren???

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab schon ein wenig rum gesucht, aber nicht's gefunden... Ich suche nach einer Möglichkeit Escape Sequenzen nach HTML zu konvertieren...

Also wenn man unter Linux "ls --color" Ausführt ist ja alles schön Bund... Die Farben könnte man doch auch in HTML übernehmen...

Außerdem, die man-Pages sind auch unterschiedlich Hell... Das scheind aber was anderes zu sein als die Escape Sequenzen bei "ls --color"... Gibt es dafür auch evtl. was zum konvertieren nach HTML ???

Die Grundlage ist die Ausgabe von subprocess.Popen() stdout welches ich für die Web-Kommandozeile einsetzte. s. http://python.sandtner.org/viewtopic.php?p=16176#16176
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Guck dir mal die Datei ansi.py an, dort sind die ANSI Escape Sequenzen aufgeführt, diese kannst du nehmen und die benötigten CSS Farbeigenschaften setzen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Der Aufwand um das Umzusetzten erscheint mir ein wenig hoch... Ich kann aber dennoch dein Link nutzen, denn damit weiß ich wie ich die EscSequenzen per RE raus filtern kann:

Code: Alles auswählen

txt = re.sub( r"\033\[.*?m", "", txt )
BlackJack

So hoch ist der Aufwand eigentlich nicht. Man kann doch nach den Sequenzen suchen und sie durch span-Tags mit entsprechenden style-Attributen ersetzen.

Die man-Pages arbeiten mit Backspaces ("\b"), so ähnlich wie man das mit einer Schreibmachine oder einem Nadeldrucker tun würde. "Fett" wird dadurch erreicht, dass jedes Zeichen zweimal an der selben Position "gedruckt" wird und beim Unterstreichen wird immer erst ein Unterstrich ("_") und dann das gewünschte Zeichen an der gleichen Stelle ausgegeben.
BlackJack

Hier ist ein fast "file-like" Objekt zum Umwandeln der ANSI-Escapesequenzen in HTML:

Code: Alles auswählen

#!/usr/bin/env python
import re

ansi_re = re.compile('\033\\['      # Escape characters.
                     '([\\d;]*)'    # Parameters.
                     '([a-zA-Z])')  # Command.
#
# Map ANSI colors to HTML 3.2 color names.
#
ansi_color = ('black', 'red', 'green', 'yellow',
              'blue', 'purple', 'teal', 'white')

class Writer:
    def __init__(self, fileobj, colors=('black', 'white')):
        self._outfile = fileobj
        
        self.normal = colors
        self.foreground, self.background = self.normal
        self.open_span = False
        
        self._outfile.write('<pre style="color:%s;background:%s">'
                            % self.normal)

    def _close_span_tag(self):
        if self.open_span:
            self._outfile.write('</span>')
            self.open_span = False
    
    def _write_span_tag(self, style):
        self._close_span_tag()
        if style:        
            style_str = ';'.join(['%s:%s' % (name, value)
                                  for name, value in style.iteritems()])
            self._outfile.write('<span style="%s">' % style_str)
            self.open_span = True
    
    def _write(self, data):
        for character, html_escape in (('&', '&'),
                                       ('<', '<'),
                                       ('>', '>')):
            data = data.replace(character, html_escape)
        self._outfile.write(data)
    
    def writeline(self, line):
        last_end = 0
        for match in ansi_re.finditer(line):
            #
            # Write string before match.
            #
            self._write(line[last_end:match.start()])
            last_end = match.end()

            #
            # Process escape sequence.
            #
            parameters, command = match.groups()

            try:
                parameters = map(int, parameters.split(';'))
            except ValueError:
                parameters = [0]

            #
            # *Set Graphics Rendition* is the only command to handle.
            # The 'blink' effect is not supported.
            #
            if command in 'mM':
                attributes = dict()
                for param in parameters:
                    if param == 0:
                        attributes.clear()
                    elif param == 1:
                        attributes['font-weight'] = 'bold'
                    elif param == 4:
                        attributes['text-decoration'] = 'underline'
                    elif param == 7:
                        attributes['color'] = self.background
                        attributes['background'] = self.foreground
                        self.background, self.foreground = (self.foreground,
                                                            self.background)
                    elif 30 <= param <= 37:
                        color = ansi_color[param - 30]
                        attributes['color'] = color
                        self.foreground = color
                    elif 40 <= param <= 47:
                        color = ansi_color[param - 40]
                        attributes['background'] = color
                        self.background = color
                self._write_span_tag(attributes)
        #
        # Write string after last match.
        #
        self._write(line[last_end:])
    
    write = writeline
    
    def writelines(self, lines):
        for line in lines:
            self.write(line)
    
    def flush(self):
        self._outfile.flush()
    
    def close(self):
        self._close_span_tag()
        self._outfile.write('</pre>')
        self.flush()

def main():
    import os, sys
    
    # pipe = os.popen('ls --color ~')
    pipe = os.popen('pycolor -s LightBG ansi2html.py')
    writer = Writer(sys.stdout)
    writer.writelines(pipe)
    writer.close()
    pipe.close()

if __name__ == '__main__':
    main()
Beim close() wird das darunterliegende Dateiobjekt nicht geschlossen, da möchte man ja vielleicht noch etwas HTML hinterherschreiben. Und beim write(), das eigentlich ein alias für writeline() ist, dürfen keine "halben" ANSI-Escape-Sequenzen vorkommen -- die werden nur erkannt wenn sie vollständig sind.

Oberflächlich getestet mit ``ls --color`` und ``pycolor`` aus dem IPython Paket.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wow, da hast du dir aber arbeit gemacht!

Ich habs allerdings mal getestet. Der testString stammt von einem "ls --color", es scheind nicht so ganz zu klappen:

Code: Alles auswählen

testString="""\033[00m\033[01;34mbin\033[00m
\033[01;34mboot\033[00m
\033[01;36mcdrom\033[00m
\033[01;34mdaten\033[00m
\033[01;34mdev\033[00m
\033[01;34metc\033[00m
\033[01;34mhome\033[00m
\033[01;34minitrd\033[00m
\033[01;36minitrd.img\033[00m
\033[01;36minitrd.img.old\033[00m
\033[01;34mlib\033[00m
\033[01;34mlost+found\033[00m
\033[01;34mmedia\033[00m
\033[01;34mmnt\033[00m
\033[01;34mopt\033[00m
\033[01;34mproc\033[00m
\033[01;34mroot\033[00m
\033[01;34msbin\033[00m
\033[01;34msrv\033[00m
\033[01;34msys\033[00m
\033[01;34mtmp\033[00m
\033[01;34musr\033[00m
\033[01;34mvar\033[00m
\033[01;36mvmlinuz\033[00m
\033[01;36mvmlinuz.old\033[00m"""

if __name__ == '__main__':
    import sys
    writer = Writer( sys.stdout )
    writer.writelines( testString )
    writer.close()
Raus kommt:

Code: Alles auswählen

<pre style="color:black;background:white">[00m[01;34mbin[00m
[01;34mboot[00m
[01;36mcdrom[00m
[01;34mdaten[00m
[01;34mdev[00m
[01;34metc[00m
[01;34mhome[00m
[01;34minitrd[00m
[01;36minitrd.img[00m
[01;36minitrd.img.old[00m
[01;34mlib[00m
[01;34mlost+found[00m
[01;34mmedia[00m
[01;34mmnt[00m
[01;34mopt[00m
[01;34mproc[00m
[01;34mroot[00m
[01;34msbin[00m
[01;34msrv[00m
[01;34msys[00m
[01;34mtmp[00m
[01;34musr[00m
[01;34mvar[00m
[01;36mvmlinuz[00m
[01;36mvmlinuz.old[00m</pre>
Bei def _write() könnte man da nicht auch xml.sax.saxutils.escape() oder from htmlentitydefs import entitydefs nutzen???


EDIT: Ah, jetzt bin ich drauf gekommen, das "lines" in writelines() für eine Liste ist... Ich habe aber einen kompletten String übergeben, das kann ja dann auch nicht klappen :oops:
Ich hab eine neue Methode hinzugefügt, damit es auch mit einem kompletten String klappt:

Code: Alles auswählen

    def writeString( self, txt ):
        for line in txt.splitlines():
            self.write(line)
Dabei wird aber glaub ich, der Zeilenumbruch verworfen... Zumindest hat der rausgeworfene Code keine Zeilenumbrüche mehr...

Naja, ich müßte mal lernen wie man fileobj richtig benutzt ;) Allerdings weiß ich noch nicht, wie ich das mit meinem console.py-Skript vereinen kann... Die letzte Version ist hier: http://python.sandtner.org/viewtopic.php?p=16193#16193
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

jens hat geschrieben:

Code: Alles auswählen

    def writeString( self, txt ):
        for line in txt.splitlines():
            self.write(line)
Dabei wird aber glaub ich, der Zeilenumbruch verworfen... Zumindest hat der rausgeworfene Code keine Zeilenumbrüche mehr...
Natürlich hat der Text keine Zeilenumbrücke mehr, weil write ja alles Hintereinander schreibt.
Dann mach einfach self.write(line + '\n') und schon hast du wieder Umbrüche.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Ich verstehe nicht ganz wozu die writeString()-Methode überhaupt gut ist!? Du kannst einfach ``w.write("blah\nsülz")`` aufrufen. Wobei `blah` und `sülz` auch ANSI-Sequenzen enthalten können. Es gibt keinen Grund die Zeichenkette erst an '\n's zu zerlegen.

Die Methoden `write()` und `writelines()` haben die gleiche Funktion wie die bei "normalen" Dateiobjekten.

Wenn Du das mit den Dateien nicht gebrauchen kannst, dann lässt sich mit dem Writer da oben und dem Modul 'StringIO' eine Konvertierungsfunktion von einer Zeichenkette mit Escape-Sequenzen in eine Zeichenkette mit einem HTML-Fragment schreiben. Ungetestet:

Code: Alles auswählen

from StringIO import StringIO
from ansi2html import Writer

def convert(ansi_str):
    sio = StringIO()
    writer = Writer(sio)
    writer.write(ansi_str)
    writer.close()
    sio.close()
    return sio.getvalue()
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Ich verstehe nicht ganz wozu die writeString()-Methode überhaupt gut ist!? Du kannst einfach ``w.write("blah\nsülz")`` aufrufen.
Oh, das wuste ich nicht, das es so einfach ist ;) Ich hatte keine write-Methode gesehen :?

Mit StringIO zu arbeiten ist auch nicht schlecht... Im Grunde genommen hat mich allerdings an diese fileobj Geschichte nur gestört, das stdout benutz wurde... Nun hab ich es geschafft ein eigenes fileobj zu schreiben. (War garnicht so schwer) Damit kann ich dann auch noch weitere schritte zum konvertieren einleiten ;)

Code: Alles auswählen

class EscWriterOut:
    def __init__( self ):
        self.txt = ""

    def write( self, txt ):
        if "\n" in txt: txt = txt.replace("\n", "<br>\n")
        self.txt += txt

    def get( self ):
        return self.txt

    def flush( self ):
        pass

MyEscWriterOut = EscWriterOut()
writer = Writer( MyEscWriterOut )
writer.write( testString )
writer.close()
print MyEscWriterOut.get()
Leider wird self._outfile.write(data) nicht nur bei einer kompletten Zeile benutzt, sondern bei allen "Teil-Strings"... Das erschwert die weiterverarbeitung etwas... Deswegen auch diese umständliche anhängen von "<br>"... (Wobei ich mir natürlich auch das if sparen und generell ein .replace() vornehme könnte)

Naja, mal sehen wie ich das in meine console.py einbauen kann... Ich dank dir erstmal :wink:
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich denke für ein fileobjekt hättest du auch von file erben können, dafür ist es ja auch da..
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

jens hat geschrieben:Ich hatte keine write-Methode gesehen :?
Ist ja auch nur eine unscheinbare Zeile nach der writeline()-Methode, die diese auch noch an den Namen `write` bindet.
Mit StringIO zu arbeiten ist auch nicht schlecht... Im Grunde genommen hat mich allerdings an diese fileobj Geschichte nur gestört, das stdout benutz wurde...
Man muss ja nicht `stdout` benutzen. War zum testen halt das naheliegenste.
Leider wird self._outfile.write(data) nicht nur bei einer kompletten Zeile benutzt, sondern bei allen "Teil-Strings"... Das erschwert die weiterverarbeitung etwas... Deswegen auch diese umständliche anhängen von "<br>"...
Das "<br>" sollte nicht notwendig sein, weil alles schon in einem <pre>-Tag steht. Bei mir fängt jedenfalls im Browser eine neue Zeile an, wenn ein '\n' im Originaltext steht. Ansonsten könntest Du es auch direkt in der `_write()`-Methode ergänzen.
Antworten