Regex unescape

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
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

Guten Morgen an alle,

ich habe folgendes Problem - ich möchte einen regulären Ausdruck "unescapen". Unter normalen
Umständen hätte ich ja auf das codecs zurückgegriffen um die Unicodes sauber zu dekodieren aber bedauerlicherweise geht das hier leider nicht so ganz.

Folgenden Helfer habe ich dazu verfasst. Es sei dabei folgendes anzumerken:

- Categorien [\d,\D, ...], Verhaltensmuster [\A, \B, ...] und Referenzen können nicht unescaped werden
- [\a,\f,\n,\r,\t,\v] bleiben erhalten
- alle anderen werden "unescaped" oder entsprechend dekodiert

Was mich stört ist, dass es sich eigentlich um einen UnicodeDecodeError handelt wenn die escape
sequence unvollständig ist und nicht direkt um einen ValueError.
Die Exception UnicodeDecodeError benötigt jedoch einen output-buffer und bei Mapping-Problemen
via "chr" Funktion wird ebenfalls ein UnicodeError geraised. Das kann m.U. verwirrend sein. :?

Habt Ihr vielleicht eine Idee wie man so etwas implementieren könnte?

Code: Alles auswählen

class UnescapeError(Exception):
    pass

import re
_unescapeprog = re.compile(
    r'''\\(?:
            x[0-9a-fA-F]{,2}|
            u[0-9a-fA-F]{,4}|U[0-9a-fA-F]{,8}|
            0[0-7]{,2}|[0-9]+|
            .|$
          )''', re.VERBOSE)

def _unescape(mo):
    escape = mo.group()
    try:
        if escape[1] in "afnrtv":
            return escape        
        elif escape[1] in "ABbDdSsWwZ":
            raise UnescapeError(escape)
        elif escape[1] == 'x':
            if len(escape) != 4:
                raise ValueError(
                    "incomplete escape sequence {:-<04}".format(escape))
            return chr(int(escape[2:], 16) & 0xff)
        elif escape[1] == 'u':
            if len(escape) != 6:
                raise ValueError(
                    "incomplete escape sequence {:-<06}".format(escape))
            return chr(int(escape[2:], 16))
        elif escape[1] == 'U':
            if len(escape) != 10:
                raise ValueError(
                    "incomplete escape sequence {:-<10}".format(escape))
            return chr(int(escape[2:], 16))
        elif escape[1] == '0':
            return chr(int(escape[1:], 8) & 0xff)
        # Unicde escape sequence (exactly three octal-digits) *or*
        # decimal group reference (sign)
        elif escape[1] in "123456789":
            if len(escape) != 3 or any(c in "89" for c in escape[1:]):
                raise UnescapeError(escape)
            return chr(int(escape[1:], 8) & 0xff)
        assert len(escape) == 2
        return escape[1]
    except IndexError:
        pass
    raise ValueError() # \ at end of string

def unescape(string):
    return _unescapeprog.sub(_unescape, string)
be or not to be
BlackJack

@maxwell: Vielleicht könntest Du noch mal in Worten und Beispielen erklären was diese Funktion überhaupt machen soll.
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

Hallo BlackJack,

es sollen alle in einem regulären Ausdruck befindlichen führenden Backslash-Zeichen entfernt werden -
ähnlich der Regex.Unescape Methode aus c#. Wobei Unicodes dekodiert werden sollen.

Code: Alles auswählen

unescape(r"\x0")
#Traceback (most recent call last):
  File "__init__.py", line 173, in <module>
    print(unescape(r"\x0"))
  File "__init__.py", line 170, in unescape
    _unescapeprog.sub(_unescape, string)
  File "__init__.py", line 140, in _unescape
    "incomplete escape sequence {:-<04}".format(escape))
ValueError: incomplete escape sequence \x0-
Dieses Beispiel wirft einen ValueError. Normalerweise wird aber mit '\x0' ein SyntaxError
oder mit Hilfe von codecs.decode(r'\x0', 'unicode-escape') einUnicodeDecodeError geworfen.
Ich würde gerne einen UnicodeDecodeError werfen wenn die Unicode sequenz unvollständig ist.
Oder noch besser einen UnicodeDecodeError from ValueError ...

Die Signatur der UnicodeDecodeError Exception benötigt jedoch ein "buffer interface". Vermutl. weil
mit der Instanz dann der ErrorHandler aufgerufen wird.

Code: Alles auswählen

raise UnicodeDecodeError("unicode-escape", bytes(), mo.start(), mo.end())
Mir fehlt jedoch der Buffer. Ja, klar es wird ja auch in keinen geschieben. Ich habe versucht mich mit bytes() zu behelfen, aber das ist total hässlich.
Wenn Fragen sind, dann weiter Fragen.
be or not to be
BlackJack

@maxwell: Da sollte das Objekt stehen welches dekodiert werden sollte und das dann fehlgeschlagen ist. Nichts wo etwas ”geschrieben” wird.
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

Hi BlackJack,

erst einmal Danke für Dein Feedback.
Mir fehlt jedoch der Buffer. Ja, klar es wird ja auch in keinen geschieben. Ich habe versucht mich mit bytes() zu behelfen, aber das ist total hässlich.
Vergiss das ganz schnell, ich habe hier etwas falsch gelesen bzw. verstanden. Trotz alledem erwartet
UnicodeDecodeError als zweites Argument bspw. einen byte-string. Ggf. sollte ich mir wirklich etwas
anderes überlegen. Irgendwie fühlt sich das nicht gut an.
be or not to be
BlackJack

@maxwell: Ist ja irgendwie auch logisch weil man nur Bytestrings *de*kodieren kann, also kann auch nur dort der `UnicodeDecodeError` auftreten.
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

Das ist so nicht ganz korrekt denn es gibt auch codecs wie "unicode-escape". Es gab dazu auch
im Zuge v. Python3 lange Diskussionen wie mit diesen codecs umgegangen werden soll.

Ich habe bezüglich meines Problems jetzt folgendes gefunden:
subject object (bytestring, i.eb"") it can actually be blank, makes no difference

Code: Alles auswählen

reason = 'genau darum'
raise UnicodeDecodeError('unicode-escape', b'', mo.start(), mo.end(), reason)
Es fehlt dann nur die Portition des Substrings wo der Fehler aufgetreten ist. Stattdessen steht dann
eben "bytes".

Code: Alles auswählen

>>>print(unescape(r"abc\\x"))
Traceback (most recent call last):
  File "__init__.py", line 175, in <module>
    print(unescape(r"abc\\x"))
  File "__init__.py", line 171, in unescape
    return unescapere.sub(_unescape, string)
  File "__init__.py", line 165, in _unescape
    raise UnicodeDecodeError('unicode-escape', b'', mo.start(), mo.end(), reason
)
UnicodeDecodeError: 'unicode-escape' codec can't decode bytes in position 3-4: i
ncomplete escape sequence \x--
be or not to be
BlackJack

@maxwell: Der `UnicodeDecodeError` kann nur beim Dekodieren von Bytestrings auftreten. Man kann in Python 2 zwar `decode()` auch auf `unicode`-Objekten aufrufen, allerdings werden die selber nie dekodiert, sondern als erstes wird das `unicode`-Objekt mit ASCII zu einem Bytestring kodiert, und nur wenn das klappt, wird *der* dann dekodiert.

In Python 3 haben `str` gar nicht erst eine `decode()`-Methode.
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

Guten Morgen BlackJack,
Du hast ja recht. Aber was passiert denn bei folgendem:

Code: Alles auswählen

import codecs
codecs.decode(r"\xff", "unicode-escape")
Wird ein byte-string erzeugt und anschließend dekodiert?
be or not to be
BlackJack

@maxwell: Wir reden von Python 3 nehme ich an, denn in 2 ist das ja bereits ein Bytestring. Ich nehme mal sehr stark an das dem so ist, denn `codecs.decode()` möchte ja ein Objekt mit ”buffer interface”, was (Unicode-)Zeichenketten nicht haben.

Im Zweifelsfall müsste man im Quelltext nachsehen. :-)
maxwell
User
Beiträge: 69
Registriert: Samstag 11. Juli 2009, 15:36
Wohnort: am Fernsehturm in B.

BlackJack Du hast recht!

Code: Alles auswählen

static PyObject *
unicode_escape_decode(PyObject *self,
                     PyObject *args)
{
    Py_buffer pbuf;
    const char *errors = NULL;
        PyObject *unicode;

    if (!PyArg_ParseTuple(args, "s*|z:unicode_escape_decode",
                          &pbuf, &errors))
        return NULL;

    unicode = PyUnicode_DecodeUnicodeEscape(pbuf.buf, pbuf.len, errors);
    PyBuffer_Release(&pbuf);
    return codec_tuple(unicode, pbuf.len);
}
Laut Dokumentation "PyArg_ParseTuple":

s* (str, bytes, bytearray or buffer compatible object) [Py_buffer]
...
Unicode objects are converted to C strings using 'utf-8' encoding.
be or not to be
Antworten