← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-parse-po-file-content-bytes into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-parse-po-file-content-bytes into launchpad:master.

Commit message:
Parse PO file content as bytes

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/397817
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-parse-po-file-content-bytes into launchpad:master.
diff --git a/lib/lp/translations/interfaces/translationimporter.py b/lib/lp/translations/interfaces/translationimporter.py
index 7547050..10f3775 100644
--- a/lib/lp/translations/interfaces/translationimporter.py
+++ b/lib/lp/translations/interfaces/translationimporter.py
@@ -15,6 +15,7 @@ __all__ = [
     'TranslationFormatInvalidInputError',
     ]
 
+import six
 from zope.interface import Interface
 from zope.schema import (
     Bool,
@@ -83,9 +84,10 @@ class TranslationFormatBaseError(TranslationImportExportBaseException):
         else:
             text = default_message
 
-        return "%s%s" % (location_prefix, text)
+        return u"%s%s" % (location_prefix, text)
 
 
+@six.python_2_unicode_compatible
 class TranslationFormatSyntaxError(TranslationFormatBaseError):
     """A syntax error occurred while parsing a translation file."""
 
@@ -93,6 +95,7 @@ class TranslationFormatSyntaxError(TranslationFormatBaseError):
         return self.represent("Unknown syntax error")
 
 
+@six.python_2_unicode_compatible
 class TranslationFormatInvalidInputError(TranslationFormatBaseError):
     """Some fields in the parsed file contain bad content."""
 
diff --git a/lib/lp/translations/utilities/doc/gettext_po_parser.txt b/lib/lp/translations/utilities/doc/gettext_po_parser.txt
index f2f63bb..8ce9e60 100644
--- a/lib/lp/translations/utilities/doc/gettext_po_parser.txt
+++ b/lib/lp/translations/utilities/doc/gettext_po_parser.txt
@@ -14,7 +14,7 @@ The PO parser handles GNU gettext format human readable files.
 PO files with empty headers are not allowed.
 
     >>> parser = POParser()
-    >>> parser.parse('msgid "foo"\nmsgstr ""\n')
+    >>> parser.parse(b'msgid "foo"\nmsgstr ""\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -22,8 +22,8 @@ PO files with empty headers are not allowed.
 
 PO files with context after msgids are reported as broken.
 
-    >>> parser.parse('msgid ""\nmsgstr ""\n'
-    ...              'msgid "blah"\nmsgctxt "foo"\nmsgstr "bar"\n')
+    >>> parser.parse(b'msgid ""\nmsgstr ""\n'
+    ...              b'msgid "blah"\nmsgctxt "foo"\nmsgstr "bar"\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -31,9 +31,9 @@ PO files with context after msgids are reported as broken.
 
 And a msgctxt followed by msgctxt is caught as well.
 
-    >>> parser.parse('msgid ""\nmsgstr ""\n'
-    ...              'msgctxt "foo"\nmsgctxt "foo1"\n'
-    ...              'msgid "blah"\nmsgstr "bar"\n')
+    >>> parser.parse(b'msgid ""\nmsgstr ""\n'
+    ...              b'msgctxt "foo"\nmsgctxt "foo1"\n'
+    ...              b'msgid "blah"\nmsgstr "bar"\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -42,8 +42,8 @@ And a msgctxt followed by msgctxt is caught as well.
 When a string is followed by non-string, non-space data, it is caught
 as an error.
 
-    >>> parser.parse('msgid ""\nmsgstr "something"\n'
-    ...              '"foo"  whatever\n')
+    >>> parser.parse(b'msgid ""\nmsgstr "something"\n'
+    ...              b'"foo"  whatever\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -51,7 +51,7 @@ as an error.
 
 Unrecognized escape sequences are caught as well.
 
-    >>> parser.parse('msgid "\!"\nmsgstr ""\n')
+    >>> parser.parse(b'msgid "\!"\nmsgstr ""\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -59,7 +59,7 @@ Unrecognized escape sequences are caught as well.
 
 Unclosed strings (missing closing quotes) are caught.
 
-    >>> parser.parse('msgid ""\nmsgstr "\n')
+    >>> parser.parse(b'msgid ""\nmsgstr "\n')
     ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
@@ -283,7 +283,7 @@ template.
 
 Parsing a PO template:
 
-    >>> content = """
+    >>> content = b"""
     ... msgid ""
     ... msgstr ""
     ... "Project-Id-Version: Foobar 1.0\\n"
@@ -312,7 +312,7 @@ PO templates, and other PO files that do not specify their encoding,
 are parsed as UTF-8 text.  If they contain non UTF-8 characters, parsing
 errors occur:
 
-    >>> chunk2 = """
+    >>> chunk2 = b"""
     ... #:foo/bar.c:42
     ... msgid "Bar"
     ... msgstr "\xb5\x7b\xa6\xa1\xbf\xf9\xbb\x7e"
@@ -338,7 +338,7 @@ may include backslashes inside multibyte sequences.  These backslashes
 must be interpreted as part of the character rather than as an escape
 character.
 
-    >>> content = """
+    >>> content = b"""
     ... msgid ""
     ... msgstr ""
     ... "Last-Translator:  \xb5\x7b\xa6\xa1\xbf\xf9\xbb\x7e\\n"
@@ -384,7 +384,7 @@ which would cause problems if we naively use '\n' as a line separator.
 
 Change the last PO file to use Mac-style newlines:
 
-    >>> content = content.replace('\n', '\r')
+    >>> content = content.replace(b'\n', b'\r')
 
 Verify that it still parses:
 
@@ -401,7 +401,7 @@ them have special meaning like 'new line': '\n' or 'tabs' '\t' and others are
 just the numeric representation of a character in the declared encoding by
 the Content-Type field of the header.
 
-    >>> content = """
+    >>> content = b"""
     ... msgid ""
     ... msgstr ""
     ... "POT-Creation-Date: 2004-05-11 20:22+0800\\n"
@@ -521,7 +521,7 @@ Let's parse a Spanish PO file with an inverted plural formula.
 
     >>> spanish_pluralformula = 'n!=1'
 
-    >>> content = """
+    >>> content = b"""
     ... msgid ""
     ... msgstr ""
     ... "POT-Creation-Date: 2004-05-11 20:22+0800\\n"
diff --git a/lib/lp/translations/utilities/tests/test_gettext_mo_exporter.py b/lib/lp/translations/utilities/tests/test_gettext_mo_exporter.py
index b56970d..6a39c57 100644
--- a/lib/lp/translations/utilities/tests/test_gettext_mo_exporter.py
+++ b/lib/lp/translations/utilities/tests/test_gettext_mo_exporter.py
@@ -33,7 +33,7 @@ class TestGettextMOExporter(TestCaseWithFactory):
 
             msgid "foo"
             msgstr "bar"
-            """))
+            """).encode("UTF-8"))
         file_data.is_template = is_template
         file_data.language_code = 'my'
         file_data.translation_domain = 'main'
@@ -69,9 +69,9 @@ class TestGettextMOExporter(TestCaseWithFactory):
             universal_newlines=False)
 
         self.assertEqual(0, retval)
-        self.assertIn('MIME-Version', text)
-        self.assertIn('msgid', text)
-        self.assertIn('"foo"', text)
+        self.assertIn(b'MIME-Version', text)
+        self.assertIn(b'msgid', text)
+        self.assertIn(b'"foo"', text)
 
     def test_export_template_stays_pot(self):
         # The MO exporter exports templates in their original POT
@@ -85,7 +85,7 @@ class TestGettextMOExporter(TestCaseWithFactory):
         self.assertEqual('application/x-po', output.content_type)
         self.assertTrue(output.path.endswith('.pot'))
         text = output.read()
-        self.assertIn('POT-Creation-Date:', text)
-        self.assertIn('MIME-Version:', text)
-        self.assertIn('msgid', text)
-        self.assertIn('"foo"', text)
+        self.assertIn(b'POT-Creation-Date:', text)
+        self.assertIn(b'MIME-Version:', text)
+        self.assertIn(b'msgid', text)
+        self.assertIn(b'"foo"', text)
diff --git a/lib/lp/translations/utilities/tests/test_gettext_po_exporter.py b/lib/lp/translations/utilities/tests/test_gettext_po_exporter.py
index 0a9a38a..7bf5479 100644
--- a/lib/lp/translations/utilities/tests/test_gettext_po_exporter.py
+++ b/lib/lp/translations/utilities/tests/test_gettext_po_exporter.py
@@ -1,10 +1,13 @@
 # Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 from textwrap import dedent
 
+import six
 from zope.interface.verify import verifyObject
 
 from lp.services.helpers import test_diff
@@ -43,12 +46,18 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
         :param import_file: buffer with the source file content.
         :param export_file: buffer with the output file content.
         """
-        import_lines = [line for line in import_file.split('\n')]
+        if import_file == export_file:
+            return
+
+        import_lines = [
+            six.ensure_text(line, errors='replace')
+            for line in import_file.split(b'\n')]
         # Remove X-Launchpad-Export-Date line to prevent time bombs in tests.
         export_lines = [
-            line for line in export_file.split('\n')
-            if (not line.startswith('"X-Launchpad-Export-Date:') and
-                not line.startswith('"X-Generator: Launchpad'))]
+            six.ensure_text(line, errors='replace')
+            for line in export_file.split(b'\n')
+            if (not line.startswith(b'"X-Launchpad-Export-Date:') and
+                not line.startswith(b'"X-Generator: Launchpad'))]
 
         line_pairs = zip(export_lines, import_lines)
         debug_diff = test_diff(import_lines, export_lines)
@@ -124,7 +133,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
 
             #~ msgid "zot"
             #~ msgstr "zat"
-            ''')
+            ''').encode('UTF-8')
         cy_translation_file = self.parser.parse(pofile_cy)
         cy_translation_file.is_template = False
         cy_translation_file.language_code = 'cy'
@@ -159,7 +168,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             #| msgid "zog"
             msgid "zig"
             msgstr "zag"
-            ''')
+            ''').encode('UTF-8')
 
         pofile_eo_obsolete = dedent('''
             msgid ""
@@ -179,7 +188,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             #~| msgid "zog"
             #~ msgid "zig"
             #~ msgstr "zag"
-            ''')
+            ''').encode('UTF-8')
         eo_translation_file = self.parser.parse(pofile_eo)
         eo_translation_file.is_template = False
         eo_translation_file.language_code = 'eo'
@@ -220,64 +229,30 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             self._compareImportAndExport(
                 pofile.strip(), storage.export().read().strip())
 
-        # File representing the same PO file three times. Each is identical
-        # except for the charset declaration in the header.
-        pofiles = [
-            dedent('''
-                msgid ""
-                msgstr ""
-                "Project-Id-Version: foo\\n"
-                "Report-Msgid-Bugs-To: \\n"
-                "POT-Creation-Date: 2007-07-09 03:39+0100\\n"
-                "PO-Revision-Date: 2001-09-09 01:46+0000\\n"
-                "Last-Translator: Kubla Kahn <kk@xxxxxxxxxxxxxxxxx>\\n"
-                "Language-Team: LANGUAGE <LL@xxxxxx>\\n"
-                "MIME-Version: 1.0\\n"
-                "Content-Type: text/plain; charset=UTF-8\\n"
-                "Content-Transfer-Encoding: 8bit\\n"
-
-                msgid "Japanese"
-                msgstr "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
-                '''),
-            dedent('''
-                msgid ""
-                msgstr ""
-                "Project-Id-Version: foo\\n"
-                "Report-Msgid-Bugs-To: \\n"
-                "POT-Creation-Date: 2007-07-09 03:39+0100\\n"
-                "PO-Revision-Date: 2001-09-09 01:46+0000\\n"
-                "Last-Translator: Kubla Kahn <kk@xxxxxxxxxxxxxxxxx>\\n"
-                "Language-Team: LANGUAGE <LL@xxxxxx>\\n"
-                "MIME-Version: 1.0\\n"
-                "Content-Type: text/plain; charset=Shift-JIS\\n"
-                "Content-Transfer-Encoding: 8bit\\n"
-
-                msgid "Japanese"
-                msgstr "\x93\xfa\x96\x7b\x8c\xea"
-                '''),
-            dedent('''
-                msgid ""
-                msgstr ""
-                "Project-Id-Version: foo\\n"
-                "Report-Msgid-Bugs-To: \\n"
-                "POT-Creation-Date: 2007-07-09 03:39+0100\\n"
-                "PO-Revision-Date: 2001-09-09 01:46+0000\\n"
-                "Last-Translator: Kubla Kahn <kk@xxxxxxxxxxxxxxxxx>\\n"
-                "Language-Team: LANGUAGE <LL@xxxxxx>\\n"
-                "MIME-Version: 1.0\\n"
-                "Content-Type: text/plain; charset=EUC-JP\\n"
-                "Content-Transfer-Encoding: 8bit\\n"
-
-                msgid "Japanese"
-                msgstr "\xc6\xfc\xcb\xdc\xb8\xec"
-                '''),
-            ]
-        for pofile in pofiles:
+        pofile_content = dedent('''
+            msgid ""
+            msgstr ""
+            "Project-Id-Version: foo\\n"
+            "Report-Msgid-Bugs-To: \\n"
+            "POT-Creation-Date: 2007-07-09 03:39+0100\\n"
+            "PO-Revision-Date: 2001-09-09 01:46+0000\\n"
+            "Last-Translator: Kubla Kahn <kk@xxxxxxxxxxxxxxxxx>\\n"
+            "Language-Team: LANGUAGE <LL@xxxxxx>\\n"
+            "MIME-Version: 1.0\\n"
+            "Content-Type: text/plain; charset=%(charset)s\\n"
+            "Content-Transfer-Encoding: 8bit\\n"
+
+            msgid "Japanese"
+            msgstr "\u65e5\u672c\u8a9e"
+            ''')
+        for charset in ('UTF-8', 'Shift-JIS', 'EUC-JP'):
+            pofile = (pofile_content % {'charset': charset}).encode(charset)
             compare(self, pofile)
 
     def _testBrokenEncoding(self, pofile_content):
         translation_file = self.parser.parse(
-            pofile_content % {'charset': 'ISO-8859-15', 'special': '\xe1'})
+            (pofile_content %
+             {'charset': 'ISO-8859-15'}).encode('ISO-8859-15'))
         translation_file.is_template = False
         translation_file.language_code = 'es'
         translation_file.path = 'po/es.po'
@@ -291,8 +266,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             translation_file, storage)
 
         self._compareImportAndExport(
-            pofile_content.strip() % {
-                'charset': 'UTF-8', 'special': '\xc3\xa1'},
+            (pofile_content.strip() % {'charset': 'UTF-8'}).encode('UTF-8'),
             storage.export().read().strip())
 
     def testBrokenEncodingExport(self):
@@ -317,7 +291,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             "Content-Transfer-Encoding: 8bit\\n"
 
             msgid "a"
-            msgstr "%(special)s"
+            msgstr "\xe1"
             ''')
         self._testBrokenEncoding(pofile_content)
 
@@ -339,7 +313,7 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             "Report-Msgid-Bugs-To: \\n"
             "POT-Creation-Date: 2007-07-09 03:39+0100\\n"
             "PO-Revision-Date: 2001-09-09 01:46+0000\\n"
-            "Last-Translator: Kubla K%(special)shn <kk@xxxxxxxxxxxxxxxxx>\\n"
+            "Last-Translator: Kubla K\xe1hn <kk@xxxxxxxxxxxxxxxxx>\\n"
             "Language-Team: LANGUAGE <LL@xxxxxx>\\n"
             "MIME-Version: 1.0\\n"
             "Content-Type: text/plain; charset=%(charset)s\\n"
@@ -369,9 +343,9 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
 
             msgid "1 dead horse"
             msgid_plural "%%d dead horses"
-            msgstr[0] "ning\xc3\xban caballo muerto"
+            msgstr[0] "ning\xfan caballo muerto"
             %s''')
-        translation_file = self.parser.parse(pofile % (''))
+        translation_file = self.parser.parse((pofile % '').encode('UTF-8'))
         translation_file.is_template = False
         translation_file.language_code = 'es'
         translation_file.path = 'po/es.po'
@@ -381,7 +355,8 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             translation_file, storage)
 
         self._compareImportAndExport(
-            pofile.strip() % 'msgstr[1] ""', storage.export().read().strip())
+            (pofile.strip() % 'msgstr[1] ""').encode('UTF-8'),
+            storage.export().read().strip())
 
     def testClashingSingularMsgIds(self):
         # We don't accept it in gettext imports directly, since it's not
@@ -404,9 +379,9 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             msgid_plural "%d foos"
             msgstr[0] ""
             msgstr[1] ""
-            """).strip()
+            """).strip().encode('UTF-8')
 
-        body = exported_file.split('\n\n', 1)[1].strip()
+        body = exported_file.split(b'\n\n', 1)[1].strip()
         self.assertEqual(body, expected_output)
 
     def testObsoleteMessageYieldsToNonObsoleteClashingOne(self):
@@ -442,9 +417,9 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
             msgid_plural "%d gooim"
             msgstr[0] "%d gargl"
             msgstr[1] "%d garglii"
-            """).strip()
+            """).strip().encode('UTF-8')
 
-        body = exported_file.split('\n\n', 1)[1].strip()
+        body = exported_file.split(b'\n\n', 1)[1].strip()
         self.assertEqual(expected_output, body)
 
     def test_strip_last_newline(self):
@@ -489,5 +464,5 @@ class GettextPOExporterTestCase(TestCaseWithFactory):
         # Exporting an empty gettext file does not break the exporter.
         # The output does contain one message: the header.
         output = self.factory.makePOFile('nl').export()
-        self.assertTrue(output.startswith('# Dutch translation for '))
-        self.assertEqual(1, output.count('msgid'))
+        self.assertTrue(output.startswith(b'# Dutch translation for '))
+        self.assertEqual(1, output.count(b'msgid'))
diff --git a/lib/lp/translations/utilities/tests/test_gettext_po_parser.py b/lib/lp/translations/utilities/tests/test_gettext_po_parser.py
index 0ea27cd..ad3619e 100644
--- a/lib/lp/translations/utilities/tests/test_gettext_po_parser.py
+++ b/lib/lp/translations/utilities/tests/test_gettext_po_parser.py
@@ -27,15 +27,18 @@ class POBasicTestCase(unittest.TestCase):
     def setUp(self):
         self.parser = gettext_po_parser.POParser()
 
+    def parse(self, content_text):
+        return self.parser.parse(content_text.encode('ASCII'))
+
     def testEmptyFile(self):
         # The parser reports an empty file as an error.
-        self.assertRaises(TranslationFormatSyntaxError, self.parser.parse, '')
+        self.assertRaises(TranslationFormatSyntaxError, self.parse, '')
 
     def testEmptyFileError(self):
         # Trying to import an empty file produces a sensible error
         # message.
         try:
-            self.parser.parse('')
+            self.parse('')
             self.assertTrue(
                 False,
                 "Importing an empty file succeeded; it should have failed.")
@@ -48,10 +51,10 @@ class POBasicTestCase(unittest.TestCase):
         # The parser reports a non-empty file holding no messages as an
         # error.
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse, '# Comment')
+            TranslationFormatSyntaxError, self.parse, '# Comment')
 
     def testSingular(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%smsgid "foo"\nmsgstr "bar"\n' % DEFAULT_HEADER)
         messages = translation_file.messages
         self.assertEqual(len(messages), 1, "incorrect number of messages")
@@ -63,7 +66,7 @@ class POBasicTestCase(unittest.TestCase):
 
     def testNoNewLine(self):
         # note, no trailing newline; this raises a warning
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%smsgid "foo"\nmsgstr "bar"' % DEFAULT_HEADER)
         messages = translation_file.messages
         self.assertEqual(messages[0].msgid_singular, "foo", "incorrect msgid")
@@ -73,37 +76,37 @@ class POBasicTestCase(unittest.TestCase):
 
     def testMissingQuote(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '%smsgid "foo"\nmsgstr "bar' % DEFAULT_HEADER)
 
     def testBadNewline(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '%smsgid "foo\n"\nmsgstr "bar"\n' % DEFAULT_HEADER)
 
     def testBadBackslash(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '%smsgid "foo\\"\nmsgstr "bar"\n' % DEFAULT_HEADER)
 
     def testMissingMsgstr(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '%smsgid "foo"\n' % DEFAULT_HEADER)
 
     def testMissingMsgid1(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '%smsgid_plural "foos"\n' % DEFAULT_HEADER)
 
     def testFuzzy(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%s#, fuzzy\nmsgid "foo"\nmsgstr "bar"\n' % DEFAULT_HEADER)
         messages = translation_file.messages
         assert 'fuzzy' in messages[0].flags, "missing fuzziness"
 
     def testComment(self):
-        translation_file = self.parser.parse('''
+        translation_file = self.parse('''
             %s
             #. foo/bar.baz\n
             # cake not drugs\n
@@ -117,7 +120,7 @@ class POBasicTestCase(unittest.TestCase):
         assert 'fuzzy' not in messages[0].flags, "incorrect fuzziness"
 
     def testEscape(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%smsgid "foo\\"bar\\nbaz\\\\xyzzy"\nmsgstr"z"\n' % (
                 DEFAULT_HEADER))
         messages = translation_file.messages
@@ -127,11 +130,11 @@ class POBasicTestCase(unittest.TestCase):
     # def badEscapeTest(self):
     #
     #     self.assertRaises(
-    #         TranslationFormatSyntaxError, self.parser.parse,
+    #         TranslationFormatSyntaxError, self.parse,
     #         'msgid "foo\."\nmsgstr "bar"\n')
 
     def testPlural(self):
-        translation_file = self.parser.parse('''
+        translation_file = self.parse('''
             %s"Plural-Forms: nplurals=2; plural=foo;\\n"
 
             msgid "foo"
@@ -161,7 +164,7 @@ class POBasicTestCase(unittest.TestCase):
             msgid_plural "%%d bottles of beer on the wall"
             msgstr[zero] = "%%d flessen"''' % DEFAULT_HEADER
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse, content)
+            TranslationFormatSyntaxError, self.parse, content)
 
     def testNegativePluralCase(self):
         # If a msgid's plural case number is negative, that's a syntax
@@ -173,7 +176,7 @@ class POBasicTestCase(unittest.TestCase):
             msgid_plural "%%d ts"
             msgstr[-1] "%%d bs"''' % DEFAULT_HEADER
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse, content)
+            TranslationFormatSyntaxError, self.parse, content)
 
     def testUnsupportedPluralCase(self):
         # If a msgid's plural case number isn't lower than the maximum
@@ -187,10 +190,10 @@ class POBasicTestCase(unittest.TestCase):
             msgstr[%d] "%%d bs"''' % (
                 DEFAULT_HEADER, TranslationConstants.MAX_PLURAL_FORMS)
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse, content)
+            TranslationFormatSyntaxError, self.parse, content)
 
     def testObsolete(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%s#, fuzzy\n#~ msgid "foo"\n#~ msgstr "bar"\n' % DEFAULT_HEADER)
         messages = translation_file.messages
         self.assertEqual(messages[0].msgid_singular, "foo", "incorrect msgid")
@@ -203,7 +206,7 @@ class POBasicTestCase(unittest.TestCase):
     def testObsoleteChangedMsgid(self):
         # Test "previous msgid" feature in obsolete messages.  These are
         # marked with "#~|"
-        translation_file = self.parser.parse("""
+        translation_file = self.parse("""
             %s
 
             #~| msgid "old"
@@ -216,7 +219,7 @@ class POBasicTestCase(unittest.TestCase):
         self.assertEqual(messages[0].translations, ["nouveau"])
 
     def testMultiLineObsolete(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             '%s#~ msgid "foo"\n#~ msgstr ""\n#~ "bar"\n' % DEFAULT_HEADER)
         messages = translation_file.messages
         self.assertEqual(messages[0].msgid_singular, "foo")
@@ -226,7 +229,7 @@ class POBasicTestCase(unittest.TestCase):
 
     def testDuplicateMsgid(self):
         self.assertRaises(
-            TranslationFormatInvalidInputError, self.parser.parse,
+            TranslationFormatInvalidInputError, self.parse,
             '''
                 %s
                 msgid "foo"
@@ -238,7 +241,7 @@ class POBasicTestCase(unittest.TestCase):
 
     def testRedundantMsgstr(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '''
                 %s
                 msgid "foo"
@@ -248,7 +251,7 @@ class POBasicTestCase(unittest.TestCase):
 
     def testRedundantPlural(self):
         self.assertRaises(
-            TranslationFormatSyntaxError, self.parser.parse,
+            TranslationFormatSyntaxError, self.parse,
             '''
                 %s
                 msgid "%%d foo"
@@ -260,7 +263,7 @@ class POBasicTestCase(unittest.TestCase):
 
     def testSquareBracketAndPlural(self):
         try:
-            self.parser.parse('''
+            self.parse('''
                 %s
                 msgid "foo %%d"
                 msgid_plural "foos %%d"
@@ -274,14 +277,14 @@ class POBasicTestCase(unittest.TestCase):
         # DEFAULT_HEADER already contains a msgid and one msgstr
         # declaring ASCII encoding.  Adding a second msgstr with a
         # different text is an error.
-        self.assertRaises(TranslationFormatSyntaxError, self.parser.parse,
+        self.assertRaises(TranslationFormatSyntaxError, self.parse,
             '''
                 %s
                 msgstr "Content-Type: text/plain; charset=UTF-8\\n
             ''' % DEFAULT_HEADER)
 
     def testUpdateHeader(self):
-        translation_file = self.parser.parse(
+        translation_file = self.parse(
             'msgid ""\nmsgstr "foo: bar\\n"\n')
         translation_file.header.number_plurals = 2
         translation_file.header.plural_form_expression = 'random()'
@@ -328,14 +331,14 @@ class POBasicTestCase(unittest.TestCase):
             msgid "foo"
             msgstr "barf"
             """ % DEFAULT_HEADER
-        translation_file = self.parser.parse(easy_string)
+        translation_file = self.parse(easy_string)
 
         # If we add an escaped newline, this breaks with a syntax error.
         (hard_string, changes) = re.subn("barf", "bar\\\nf", easy_string)
         self.assertEqual(changes, 1,
             "Failed to add 1 escaped newline to test string.")
 
-        translation_file = self.parser.parse(hard_string)
+        translation_file = self.parse(hard_string)
         messages = translation_file.messages
         self.assertEqual(len(messages), 1, "Expected exactly 1 message.")
         self.assertEqual(
@@ -345,7 +348,7 @@ class POBasicTestCase(unittest.TestCase):
         # Test escaped newlines at beginning and end of string, and check for
         # interaction with multiple strings on a line.
         hard_string = re.sub("barf", "\\\nb" "a\\\nr\\\nf\\\n", easy_string)
-        translation_file = self.parser.parse(hard_string)
+        translation_file = self.parse(hard_string)
         messages = translation_file.messages
         self.assertEqual(
             messages[0].translations[TranslationConstants.SINGULAR_FORM],
@@ -354,7 +357,7 @@ class POBasicTestCase(unittest.TestCase):
         # After an escaped newline, any indentation on the continued line is
         # removed.
         hard_string = re.sub("barf", "bar\\\n  f", easy_string)
-        translation_file = self.parser.parse(hard_string)
+        translation_file = self.parse(hard_string)
         messages = translation_file.messages
         self.assertEqual(
             messages[0].translations[TranslationConstants.SINGULAR_FORM],
@@ -363,7 +366,7 @@ class POBasicTestCase(unittest.TestCase):
         # Escaped newlines inside a string do not interfere with the ability
         # to continue the string on the next line.
         hard_string = re.sub("barf", 'b\\\n"\n"arf', easy_string)
-        translation_file = self.parser.parse(hard_string)
+        translation_file = self.parse(hard_string)
         messages = translation_file.messages
         self.assertEqual(
             messages[0].translations[TranslationConstants.SINGULAR_FORM],
@@ -385,7 +388,7 @@ class POBasicTestCase(unittest.TestCase):
         break up long strings into multiple lines in the PO file.
         """
         foos = 9
-        translation_file = self.parser.parse('''
+        translation_file = self.parse('''
             %s
             msgid "foo1"
             msgstr ""
@@ -429,14 +432,14 @@ class POBasicTestCase(unittest.TestCase):
 
     def testGetLastTranslator(self):
         """Tests whether we extract last translator information correctly."""
-        template_file = self.parser.parse(DEFAULT_HEADER)
+        template_file = self.parse(DEFAULT_HEADER)
         # When it's the default one in Gettext (FULL NAME <EMAIL@ADDRESS>),
         # used in templates, we get a tuple with None values.
         name, email = template_file.header.getLastTranslator()
         self.assertIsNone(name, "Didn't detect default Last Translator name")
         self.assertIsNone(email, "Didn't detect default Last Translator email")
 
-        translation_file = self.parser.parse('''
+        translation_file = self.parse('''
             msgid ""
             msgstr ""
             "Last-Translator: Carlos Perello Marin <carlos@xxxxxxxxxxxxx>\\n"