diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index e3b99e4b6..63401f10d 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -26,7 +26,8 @@ from _typeshed import SupportsWrite -_unescape_re = re.compile(r'\\([\\trn"])') +_unescape_re = re.compile(r'\\([\\abfnrtv"])') +_unescape_map = {'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'} def unescape(string: str) -> str: @@ -42,14 +43,8 @@ def unescape(string: str) -> str: def replace_escapes(match): m = match.group(1) - if m == 'n': - return '\n' - elif m == 't': - return '\t' - elif m == 'r': - return '\r' - # m is \ or " - return m + # m is a letter of a C escape, or \ or " + return _unescape_map.get(m, m) if "\\" not in string: # Fast path: there's nothing to unescape return string[1:-1] diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index 1b753eac8..14db8182f 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -87,6 +87,20 @@ def test_unescape_of_quoted_newline(): assert pofile.unescape(r'"\\n"') == '\\n' +def test_unescape_of_c_escapes(): + # GNU xgettext and msgcat write these control characters as C escapes + assert pofile.unescape(r'"\a\b\f\n\r\t\v"') == '\a\b\f\n\r\t\v' + + +def test_read_po_unescapes_vertical_tab(): + buf = StringIO(r''' +msgid "col1\vcol2" +msgstr "Spalte1\vSpalte2" +''') + catalog = pofile.read_po(buf) + assert catalog.get('col1\vcol2').string == 'Spalte1\vSpalte2' + + def test_denormalize_on_msgstr_without_empty_first_line(): # handle irregular multi-line msgstr (no "" as first line) # gracefully (#171)