We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
setcontext is not working correctly for mounit
Calling setcontext on mounit does currently nothing as it inherits code from base class:

``` python
    def setcontext(self, context):
        """Set the message context"""
        pass
```

I'd expect it to properly update context as it does for other storages.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `translate/storage/mo.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright 2007 Zuza Software Foundation
4 #
5 # the function "serialize" was derived from Python v2.4
6 #       (Tools/i18n/msgfmt.py - function "generate"):
7 #   Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
8 #   Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation.
9 #   All rights reserved.
10 #   original license: Python Software Foundation (version 2)
11 #
12 #
13 # This file is part of translate.
14 #
15 # translate is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation; either version 2 of the License, or
18 # (at your option) any later version.
19 #
20 # translate is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, see <http://www.gnu.org/licenses/>.
27 #
28 
29 """Module for parsing Gettext .mo files for translation.
30 
31 The coding of .mo files was produced from `Gettext documentation
32 <http://www.gnu.org/software/gettext/manual/gettext.html#MO-Files>`_,
33 Pythons msgfmt.py and by observing and testing existing .mo files in the wild.
34 
35 The hash algorithm is implemented for MO files, this should result in
36 faster access of the MO file.  The hash is optional for Gettext
37 and is not needed for reading or writing MO files, in this implementation
38 it is always on and does produce sometimes different results to Gettext
39 in very small files.
40 """
41 
42 import array
43 import re
44 import six
45 import struct
46 
47 from translate.misc.multistring import multistring
48 from translate.storage import base, poheader
49 
50 
51 MO_MAGIC_NUMBER = 0x950412de
52 
53 
54 def mounpack(filename='messages.mo'):
55     """Helper to unpack Gettext MO files into a Python string"""
56     with open(filename, 'rb') as fh:
57         s = fh.read()
58         print("\\x%02x" * len(s) % tuple(map(ord, s)))
59 
60 
61 def my_swap4(result):
62     c0 = (result >> 0) & 0xff
63     c1 = (result >> 8) & 0xff
64     c2 = (result >> 16) & 0xff
65     c3 = (result >> 24) & 0xff
66 
67     return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3
68 
69 
70 def hashpjw(str_param):
71     HASHWORDBITS = 32
72     hval = 0
73     g = None
74     s = str_param
75     for s in str_param:
76         hval = hval << 4
77         hval += ord(s) if six.PY2 else s
78         g = hval & 0xf << (HASHWORDBITS - 4)
79         if (g != 0):
80             hval = hval ^ g >> (HASHWORDBITS - 8)
81             hval = hval ^ g
82     return hval
83 
84 
85 def get_next_prime_number(start):
86     # find the smallest prime number that is greater or equal "start"
87 
88     def is_prime(num):
89         # special small numbers
90         if (num < 2) or (num == 4):
91             return False
92         if (num == 2) or (num == 3):
93             return True
94         # check for numbers > 4
95         for divider in range(2, num // 2):
96             if num % divider == 0:
97                 return False
98         return True
99 
100     candidate = start
101     while not is_prime(candidate):
102         candidate += 1
103     return candidate
104 
105 
106 class mounit(base.TranslationUnit):
107     """A class representing a .mo translation message."""
108 
109     def __init__(self, source=None, **kwargs):
110         self.msgctxt = []
111         self.msgidcomments = []
112         super(mounit, self).__init__(source)
113 
114     def getcontext(self):
115         """Get the message context"""
116         # Still need to handle KDE comments
117         if self.msgctxt is None:
118             return None
119         return "".join(self.msgctxt)
120 
121     def isheader(self):
122         """Is this a header entry?"""
123         return self.source == u""
124 
125     def istranslatable(self):
126         """Is this message translateable?"""
127         return bool(self.source)
128 
129 
130 class mofile(poheader.poheader, base.TranslationStore):
131     """A class representing a .mo file."""
132 
133     UnitClass = mounit
134     Name = "Gettext MO file"
135     Mimetypes = ["application/x-gettext-catalog", "application/x-mo"]
136     Extensions = ["mo", "gmo"]
137     _binary = True
138 
139     def __init__(self, inputfile=None, **kwargs):
140         super(mofile, self).__init__(**kwargs)
141         self.filename = ''
142         if inputfile is not None:
143             self.parsestring(inputfile)
144 
145     def serialize(self, out):
146         """Output a string representation of the MO data file"""
147         # check the header of this file for the copyright note of this function
148 
149         def add_to_hash_table(string, i):
150             V = hashpjw(string)
151             # Taken from gettext-0.17:gettext-tools/src/write-mo.c:408-409
152             S = hash_size <= 2 and 3 or hash_size
153             hash_cursor = V % S
154             orig_hash_cursor = hash_cursor
155             increment = 1 + (V % (S - 2))
156             while True:
157                 index = hash_table[hash_cursor]
158                 if (index == 0):
159                     hash_table[hash_cursor] = i + 1
160                     break
161                 hash_cursor += increment
162                 hash_cursor = hash_cursor % S
163                 assert (hash_cursor != orig_hash_cursor)
164 
165         def lst_encode(lst, join_char=b''):
166             return join_char.join([i.encode('utf-8') for i in lst])
167 
168         # hash_size should be the smallest prime number that is greater
169         # or equal (4 / 3 * N) - where N is the number of keys/units.
170         # see gettext-0.17:gettext-tools/src/write-mo.c:406
171         hash_size = get_next_prime_number(int((len(self.units) * 4) / 3))
172         if hash_size <= 2:
173             hash_size = 3
174         MESSAGES = {}
175         for unit in self.units:
176             # If the unit is not translated, we should rather omit it entirely
177             if not unit.istranslated():
178                 continue
179             if isinstance(unit.source, multistring):
180                 source = (lst_encode(unit.msgidcomments) +
181                           lst_encode(unit.source.strings, b"\0"))
182             else:
183                 source = lst_encode(unit.msgidcomments) + unit.source.encode('utf-8')
184             if unit.msgctxt:
185                 source = lst_encode(unit.msgctxt) + b"\x04" + source
186             if isinstance(unit.target, multistring):
187                 target = lst_encode(unit.target.strings, b"\0")
188             else:
189                 target = unit.target.encode('utf-8')
190             if unit.target:
191                 MESSAGES[source] = target
192         # using "I" works for 32- and 64-bit systems, but not for 16-bit!
193         hash_table = array.array("I", [0] * hash_size)
194         # the keys are sorted in the .mo file
195         keys = sorted(MESSAGES.keys())
196         offsets = []
197         ids = strs = b''
198         for i, id in enumerate(keys):
199             # For each string, we need size and file offset.  Each string is
200             # NUL terminated; the NUL does not count into the size.
201             # TODO: We don't do any encoding detection from the PO Header
202             add_to_hash_table(id, i)
203             string = MESSAGES[id]  # id already encoded for use as dictionary key
204             offsets.append((len(ids), len(id), len(strs), len(string)))
205             ids = ids + id + b'\0'
206             strs = strs + string + b'\0'
207         output = ''
208         # The header is 7 32-bit unsigned integers
209         keystart = 7 * 4 + 16 * len(keys) + hash_size * 4
210         # and the values start after the keys
211         valuestart = keystart + len(ids)
212         koffsets = []
213         voffsets = []
214         # The string table first has the list of keys, then the list of values.
215         # Each entry has first the size of the string, then the file offset.
216         for o1, l1, o2, l2 in offsets:
217             koffsets = koffsets + [l1, o1 + keystart]
218             voffsets = voffsets + [l2, o2 + valuestart]
219         offsets = koffsets + voffsets
220         output = struct.pack("Iiiiiii",
221                              MO_MAGIC_NUMBER,   # Magic
222                              0,                 # Version
223                              len(keys),         # # of entries
224                              7 * 4,             # start of key index
225                              7 * 4 + len(keys) * 8,  # start of value index
226                              hash_size,         # size of hash table
227                              7 * 4 + 2 * (len(keys) * 8))  # offset of hash table
228         # additional data is not necessary for empty mo files
229         if (len(keys) > 0):
230             output = output + array.array("i", offsets).tostring()
231             output = output + hash_table.tostring()
232             output = output + ids
233             output = output + strs
234         return out.write(output)
235 
236     def parse(self, input):
237         """parses the given file or file source string"""
238         if hasattr(input, 'name'):
239             self.filename = input.name
240         elif not getattr(self, 'filename', ''):
241             self.filename = ''
242         if hasattr(input, "read"):
243             mosrc = input.read()
244             input.close()
245             input = mosrc
246         little, = struct.unpack("<L", input[:4])
247         big, = struct.unpack(">L", input[:4])
248         if little == MO_MAGIC_NUMBER:
249             endian = "<"
250         elif big == MO_MAGIC_NUMBER:
251             endian = ">"
252         else:
253             raise ValueError("This is not an MO file")
254         magic, version_maj, version_min, lenkeys, startkey, \
255             startvalue, sizehash, offsethash = struct.unpack("%sLHHiiiii" % endian,
256                                                              input[:(7 * 4)])
257         if version_maj >= 1:
258             raise base.ParseError("""Unable to process version %d.%d MO files""" % (version_maj, version_min))
259         for i in range(lenkeys):
260             nextkey = startkey + (i * 2 * 4)
261             nextvalue = startvalue + (i * 2 * 4)
262             klength, koffset = struct.unpack("%sii" % endian,
263                                              input[nextkey:nextkey + (2 * 4)])
264             vlength, voffset = struct.unpack("%sii" % endian,
265                                              input[nextvalue:nextvalue + (2 * 4)])
266             source = input[koffset:koffset + klength]
267             context = None
268             if b"\x04" in source:
269                 context, source = source.split(b"\x04")
270             # Still need to handle KDE comments
271             if source == "":
272                 charset = re.search(b"charset=([^\\s]+)",
273                                     input[voffset:voffset + vlength])
274                 if charset:
275                     self.encoding = charset.group(1)
276             source = multistring([s.decode(self.encoding)
277                                   for s in source.split(b"\0")])
278             target = multistring([s.decode(self.encoding)
279                                   for s in input[voffset:voffset + vlength].split(b"\0")])
280             newunit = mounit(source)
281             newunit.target = target
282             if context is not None:
283                 newunit.msgctxt.append(context)
284             self.addunit(newunit)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/translate/storage/mo.py b/translate/storage/mo.py
--- a/translate/storage/mo.py
+++ b/translate/storage/mo.py
@@ -118,6 +118,9 @@
             return None
         return "".join(self.msgctxt)
 
+    def setcontext(self, context):
+        self.msgctxt = [context]
+
     def isheader(self):
         """Is this a header entry?"""
         return self.source == u""

{"golden_diff": "diff --git a/translate/storage/mo.py b/translate/storage/mo.py\n--- a/translate/storage/mo.py\n+++ b/translate/storage/mo.py\n@@ -118,6 +118,9 @@\n             return None\n         return \"\".join(self.msgctxt)\n \n+    def setcontext(self, context):\n+        self.msgctxt = [context]\n+\n     def isheader(self):\n         \"\"\"Is this a header entry?\"\"\"\n         return self.source == u\"\"\n", "issue": "setcontext is not working correctly for mounit\nCalling setcontext on mounit does currently nothing as it inherits code from base class:\n\n``` python\n    def setcontext(self, context):\n        \"\"\"Set the message context\"\"\"\n        pass\n```\n\nI'd expect it to properly update context as it does for other storages.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2007 Zuza Software Foundation\n#\n# the function \"serialize\" was derived from Python v2.4\n#       (Tools/i18n/msgfmt.py - function \"generate\"):\n#   Written by Martin v. L\u00f6wis <loewis@informatik.hu-berlin.de>\n#   Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation.\n#   All rights reserved.\n#   original license: Python Software Foundation (version 2)\n#\n#\n# This file is part of translate.\n#\n# translate is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n# translate is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <http://www.gnu.org/licenses/>.\n#\n\n\"\"\"Module for parsing Gettext .mo files for translation.\n\nThe coding of .mo files was produced from `Gettext documentation\n<http://www.gnu.org/software/gettext/manual/gettext.html#MO-Files>`_,\nPythons msgfmt.py and by observing and testing existing .mo files in the wild.\n\nThe hash algorithm is implemented for MO files, this should result in\nfaster access of the MO file.  The hash is optional for Gettext\nand is not needed for reading or writing MO files, in this implementation\nit is always on and does produce sometimes different results to Gettext\nin very small files.\n\"\"\"\n\nimport array\nimport re\nimport six\nimport struct\n\nfrom translate.misc.multistring import multistring\nfrom translate.storage import base, poheader\n\n\nMO_MAGIC_NUMBER = 0x950412de\n\n\ndef mounpack(filename='messages.mo'):\n    \"\"\"Helper to unpack Gettext MO files into a Python string\"\"\"\n    with open(filename, 'rb') as fh:\n        s = fh.read()\n        print(\"\\\\x%02x\" * len(s) % tuple(map(ord, s)))\n\n\ndef my_swap4(result):\n    c0 = (result >> 0) & 0xff\n    c1 = (result >> 8) & 0xff\n    c2 = (result >> 16) & 0xff\n    c3 = (result >> 24) & 0xff\n\n    return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3\n\n\ndef hashpjw(str_param):\n    HASHWORDBITS = 32\n    hval = 0\n    g = None\n    s = str_param\n    for s in str_param:\n        hval = hval << 4\n        hval += ord(s) if six.PY2 else s\n        g = hval & 0xf << (HASHWORDBITS - 4)\n        if (g != 0):\n            hval = hval ^ g >> (HASHWORDBITS - 8)\n            hval = hval ^ g\n    return hval\n\n\ndef get_next_prime_number(start):\n    # find the smallest prime number that is greater or equal \"start\"\n\n    def is_prime(num):\n        # special small numbers\n        if (num < 2) or (num == 4):\n            return False\n        if (num == 2) or (num == 3):\n            return True\n        # check for numbers > 4\n        for divider in range(2, num // 2):\n            if num % divider == 0:\n                return False\n        return True\n\n    candidate = start\n    while not is_prime(candidate):\n        candidate += 1\n    return candidate\n\n\nclass mounit(base.TranslationUnit):\n    \"\"\"A class representing a .mo translation message.\"\"\"\n\n    def __init__(self, source=None, **kwargs):\n        self.msgctxt = []\n        self.msgidcomments = []\n        super(mounit, self).__init__(source)\n\n    def getcontext(self):\n        \"\"\"Get the message context\"\"\"\n        # Still need to handle KDE comments\n        if self.msgctxt is None:\n            return None\n        return \"\".join(self.msgctxt)\n\n    def isheader(self):\n        \"\"\"Is this a header entry?\"\"\"\n        return self.source == u\"\"\n\n    def istranslatable(self):\n        \"\"\"Is this message translateable?\"\"\"\n        return bool(self.source)\n\n\nclass mofile(poheader.poheader, base.TranslationStore):\n    \"\"\"A class representing a .mo file.\"\"\"\n\n    UnitClass = mounit\n    Name = \"Gettext MO file\"\n    Mimetypes = [\"application/x-gettext-catalog\", \"application/x-mo\"]\n    Extensions = [\"mo\", \"gmo\"]\n    _binary = True\n\n    def __init__(self, inputfile=None, **kwargs):\n        super(mofile, self).__init__(**kwargs)\n        self.filename = ''\n        if inputfile is not None:\n            self.parsestring(inputfile)\n\n    def serialize(self, out):\n        \"\"\"Output a string representation of the MO data file\"\"\"\n        # check the header of this file for the copyright note of this function\n\n        def add_to_hash_table(string, i):\n            V = hashpjw(string)\n            # Taken from gettext-0.17:gettext-tools/src/write-mo.c:408-409\n            S = hash_size <= 2 and 3 or hash_size\n            hash_cursor = V % S\n            orig_hash_cursor = hash_cursor\n            increment = 1 + (V % (S - 2))\n            while True:\n                index = hash_table[hash_cursor]\n                if (index == 0):\n                    hash_table[hash_cursor] = i + 1\n                    break\n                hash_cursor += increment\n                hash_cursor = hash_cursor % S\n                assert (hash_cursor != orig_hash_cursor)\n\n        def lst_encode(lst, join_char=b''):\n            return join_char.join([i.encode('utf-8') for i in lst])\n\n        # hash_size should be the smallest prime number that is greater\n        # or equal (4 / 3 * N) - where N is the number of keys/units.\n        # see gettext-0.17:gettext-tools/src/write-mo.c:406\n        hash_size = get_next_prime_number(int((len(self.units) * 4) / 3))\n        if hash_size <= 2:\n            hash_size = 3\n        MESSAGES = {}\n        for unit in self.units:\n            # If the unit is not translated, we should rather omit it entirely\n            if not unit.istranslated():\n                continue\n            if isinstance(unit.source, multistring):\n                source = (lst_encode(unit.msgidcomments) +\n                          lst_encode(unit.source.strings, b\"\\0\"))\n            else:\n                source = lst_encode(unit.msgidcomments) + unit.source.encode('utf-8')\n            if unit.msgctxt:\n                source = lst_encode(unit.msgctxt) + b\"\\x04\" + source\n            if isinstance(unit.target, multistring):\n                target = lst_encode(unit.target.strings, b\"\\0\")\n            else:\n                target = unit.target.encode('utf-8')\n            if unit.target:\n                MESSAGES[source] = target\n        # using \"I\" works for 32- and 64-bit systems, but not for 16-bit!\n        hash_table = array.array(\"I\", [0] * hash_size)\n        # the keys are sorted in the .mo file\n        keys = sorted(MESSAGES.keys())\n        offsets = []\n        ids = strs = b''\n        for i, id in enumerate(keys):\n            # For each string, we need size and file offset.  Each string is\n            # NUL terminated; the NUL does not count into the size.\n            # TODO: We don't do any encoding detection from the PO Header\n            add_to_hash_table(id, i)\n            string = MESSAGES[id]  # id already encoded for use as dictionary key\n            offsets.append((len(ids), len(id), len(strs), len(string)))\n            ids = ids + id + b'\\0'\n            strs = strs + string + b'\\0'\n        output = ''\n        # The header is 7 32-bit unsigned integers\n        keystart = 7 * 4 + 16 * len(keys) + hash_size * 4\n        # and the values start after the keys\n        valuestart = keystart + len(ids)\n        koffsets = []\n        voffsets = []\n        # The string table first has the list of keys, then the list of values.\n        # Each entry has first the size of the string, then the file offset.\n        for o1, l1, o2, l2 in offsets:\n            koffsets = koffsets + [l1, o1 + keystart]\n            voffsets = voffsets + [l2, o2 + valuestart]\n        offsets = koffsets + voffsets\n        output = struct.pack(\"Iiiiiii\",\n                             MO_MAGIC_NUMBER,   # Magic\n                             0,                 # Version\n                             len(keys),         # # of entries\n                             7 * 4,             # start of key index\n                             7 * 4 + len(keys) * 8,  # start of value index\n                             hash_size,         # size of hash table\n                             7 * 4 + 2 * (len(keys) * 8))  # offset of hash table\n        # additional data is not necessary for empty mo files\n        if (len(keys) > 0):\n            output = output + array.array(\"i\", offsets).tostring()\n            output = output + hash_table.tostring()\n            output = output + ids\n            output = output + strs\n        return out.write(output)\n\n    def parse(self, input):\n        \"\"\"parses the given file or file source string\"\"\"\n        if hasattr(input, 'name'):\n            self.filename = input.name\n        elif not getattr(self, 'filename', ''):\n            self.filename = ''\n        if hasattr(input, \"read\"):\n            mosrc = input.read()\n            input.close()\n            input = mosrc\n        little, = struct.unpack(\"<L\", input[:4])\n        big, = struct.unpack(\">L\", input[:4])\n        if little == MO_MAGIC_NUMBER:\n            endian = \"<\"\n        elif big == MO_MAGIC_NUMBER:\n            endian = \">\"\n        else:\n            raise ValueError(\"This is not an MO file\")\n        magic, version_maj, version_min, lenkeys, startkey, \\\n            startvalue, sizehash, offsethash = struct.unpack(\"%sLHHiiiii\" % endian,\n                                                             input[:(7 * 4)])\n        if version_maj >= 1:\n            raise base.ParseError(\"\"\"Unable to process version %d.%d MO files\"\"\" % (version_maj, version_min))\n        for i in range(lenkeys):\n            nextkey = startkey + (i * 2 * 4)\n            nextvalue = startvalue + (i * 2 * 4)\n            klength, koffset = struct.unpack(\"%sii\" % endian,\n                                             input[nextkey:nextkey + (2 * 4)])\n            vlength, voffset = struct.unpack(\"%sii\" % endian,\n                                             input[nextvalue:nextvalue + (2 * 4)])\n            source = input[koffset:koffset + klength]\n            context = None\n            if b\"\\x04\" in source:\n                context, source = source.split(b\"\\x04\")\n            # Still need to handle KDE comments\n            if source == \"\":\n                charset = re.search(b\"charset=([^\\\\s]+)\",\n                                    input[voffset:voffset + vlength])\n                if charset:\n                    self.encoding = charset.group(1)\n            source = multistring([s.decode(self.encoding)\n                                  for s in source.split(b\"\\0\")])\n            target = multistring([s.decode(self.encoding)\n                                  for s in input[voffset:voffset + vlength].split(b\"\\0\")])\n            newunit = mounit(source)\n            newunit.target = target\n            if context is not None:\n                newunit.msgctxt.append(context)\n            self.addunit(newunit)\n", "path": "translate/storage/mo.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright 2007 Zuza Software Foundation\n#\n# the function \"serialize\" was derived from Python v2.4\n#       (Tools/i18n/msgfmt.py - function \"generate\"):\n#   Written by Martin v. L\u00f6wis <loewis@informatik.hu-berlin.de>\n#   Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation.\n#   All rights reserved.\n#   original license: Python Software Foundation (version 2)\n#\n#\n# This file is part of translate.\n#\n# translate is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n# translate is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, see <http://www.gnu.org/licenses/>.\n#\n\n\"\"\"Module for parsing Gettext .mo files for translation.\n\nThe coding of .mo files was produced from `Gettext documentation\n<http://www.gnu.org/software/gettext/manual/gettext.html#MO-Files>`_,\nPythons msgfmt.py and by observing and testing existing .mo files in the wild.\n\nThe hash algorithm is implemented for MO files, this should result in\nfaster access of the MO file.  The hash is optional for Gettext\nand is not needed for reading or writing MO files, in this implementation\nit is always on and does produce sometimes different results to Gettext\nin very small files.\n\"\"\"\n\nimport array\nimport re\nimport six\nimport struct\n\nfrom translate.misc.multistring import multistring\nfrom translate.storage import base, poheader\n\n\nMO_MAGIC_NUMBER = 0x950412de\n\n\ndef mounpack(filename='messages.mo'):\n    \"\"\"Helper to unpack Gettext MO files into a Python string\"\"\"\n    with open(filename, 'rb') as fh:\n        s = fh.read()\n        print(\"\\\\x%02x\" * len(s) % tuple(map(ord, s)))\n\n\ndef my_swap4(result):\n    c0 = (result >> 0) & 0xff\n    c1 = (result >> 8) & 0xff\n    c2 = (result >> 16) & 0xff\n    c3 = (result >> 24) & 0xff\n\n    return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3\n\n\ndef hashpjw(str_param):\n    HASHWORDBITS = 32\n    hval = 0\n    g = None\n    s = str_param\n    for s in str_param:\n        hval = hval << 4\n        hval += ord(s) if six.PY2 else s\n        g = hval & 0xf << (HASHWORDBITS - 4)\n        if (g != 0):\n            hval = hval ^ g >> (HASHWORDBITS - 8)\n            hval = hval ^ g\n    return hval\n\n\ndef get_next_prime_number(start):\n    # find the smallest prime number that is greater or equal \"start\"\n\n    def is_prime(num):\n        # special small numbers\n        if (num < 2) or (num == 4):\n            return False\n        if (num == 2) or (num == 3):\n            return True\n        # check for numbers > 4\n        for divider in range(2, num // 2):\n            if num % divider == 0:\n                return False\n        return True\n\n    candidate = start\n    while not is_prime(candidate):\n        candidate += 1\n    return candidate\n\n\nclass mounit(base.TranslationUnit):\n    \"\"\"A class representing a .mo translation message.\"\"\"\n\n    def __init__(self, source=None, **kwargs):\n        self.msgctxt = []\n        self.msgidcomments = []\n        super(mounit, self).__init__(source)\n\n    def getcontext(self):\n        \"\"\"Get the message context\"\"\"\n        # Still need to handle KDE comments\n        if self.msgctxt is None:\n            return None\n        return \"\".join(self.msgctxt)\n\n    def setcontext(self, context):\n        self.msgctxt = [context]\n\n    def isheader(self):\n        \"\"\"Is this a header entry?\"\"\"\n        return self.source == u\"\"\n\n    def istranslatable(self):\n        \"\"\"Is this message translateable?\"\"\"\n        return bool(self.source)\n\n\nclass mofile(poheader.poheader, base.TranslationStore):\n    \"\"\"A class representing a .mo file.\"\"\"\n\n    UnitClass = mounit\n    Name = \"Gettext MO file\"\n    Mimetypes = [\"application/x-gettext-catalog\", \"application/x-mo\"]\n    Extensions = [\"mo\", \"gmo\"]\n    _binary = True\n\n    def __init__(self, inputfile=None, **kwargs):\n        super(mofile, self).__init__(**kwargs)\n        self.filename = ''\n        if inputfile is not None:\n            self.parsestring(inputfile)\n\n    def serialize(self, out):\n        \"\"\"Output a string representation of the MO data file\"\"\"\n        # check the header of this file for the copyright note of this function\n\n        def add_to_hash_table(string, i):\n            V = hashpjw(string)\n            # Taken from gettext-0.17:gettext-tools/src/write-mo.c:408-409\n            S = hash_size <= 2 and 3 or hash_size\n            hash_cursor = V % S\n            orig_hash_cursor = hash_cursor\n            increment = 1 + (V % (S - 2))\n            while True:\n                index = hash_table[hash_cursor]\n                if (index == 0):\n                    hash_table[hash_cursor] = i + 1\n                    break\n                hash_cursor += increment\n                hash_cursor = hash_cursor % S\n                assert (hash_cursor != orig_hash_cursor)\n\n        def lst_encode(lst, join_char=b''):\n            return join_char.join([i.encode('utf-8') for i in lst])\n\n        # hash_size should be the smallest prime number that is greater\n        # or equal (4 / 3 * N) - where N is the number of keys/units.\n        # see gettext-0.17:gettext-tools/src/write-mo.c:406\n        hash_size = get_next_prime_number(int((len(self.units) * 4) / 3))\n        if hash_size <= 2:\n            hash_size = 3\n        MESSAGES = {}\n        for unit in self.units:\n            # If the unit is not translated, we should rather omit it entirely\n            if not unit.istranslated():\n                continue\n            if isinstance(unit.source, multistring):\n                source = (lst_encode(unit.msgidcomments) +\n                          lst_encode(unit.source.strings, b\"\\0\"))\n            else:\n                source = lst_encode(unit.msgidcomments) + unit.source.encode('utf-8')\n            if unit.msgctxt:\n                source = lst_encode(unit.msgctxt) + b\"\\x04\" + source\n            if isinstance(unit.target, multistring):\n                target = lst_encode(unit.target.strings, b\"\\0\")\n            else:\n                target = unit.target.encode('utf-8')\n            if unit.target:\n                MESSAGES[source] = target\n        # using \"I\" works for 32- and 64-bit systems, but not for 16-bit!\n        hash_table = array.array(\"I\", [0] * hash_size)\n        # the keys are sorted in the .mo file\n        keys = sorted(MESSAGES.keys())\n        offsets = []\n        ids = strs = b''\n        for i, id in enumerate(keys):\n            # For each string, we need size and file offset.  Each string is\n            # NUL terminated; the NUL does not count into the size.\n            # TODO: We don't do any encoding detection from the PO Header\n            add_to_hash_table(id, i)\n            string = MESSAGES[id]  # id already encoded for use as dictionary key\n            offsets.append((len(ids), len(id), len(strs), len(string)))\n            ids = ids + id + b'\\0'\n            strs = strs + string + b'\\0'\n        output = ''\n        # The header is 7 32-bit unsigned integers\n        keystart = 7 * 4 + 16 * len(keys) + hash_size * 4\n        # and the values start after the keys\n        valuestart = keystart + len(ids)\n        koffsets = []\n        voffsets = []\n        # The string table first has the list of keys, then the list of values.\n        # Each entry has first the size of the string, then the file offset.\n        for o1, l1, o2, l2 in offsets:\n            koffsets = koffsets + [l1, o1 + keystart]\n            voffsets = voffsets + [l2, o2 + valuestart]\n        offsets = koffsets + voffsets\n        output = struct.pack(\"Iiiiiii\",\n                             MO_MAGIC_NUMBER,   # Magic\n                             0,                 # Version\n                             len(keys),         # # of entries\n                             7 * 4,             # start of key index\n                             7 * 4 + len(keys) * 8,  # start of value index\n                             hash_size,         # size of hash table\n                             7 * 4 + 2 * (len(keys) * 8))  # offset of hash table\n        # additional data is not necessary for empty mo files\n        if (len(keys) > 0):\n            output = output + array.array(\"i\", offsets).tostring()\n            output = output + hash_table.tostring()\n            output = output + ids\n            output = output + strs\n        return out.write(output)\n\n    def parse(self, input):\n        \"\"\"parses the given file or file source string\"\"\"\n        if hasattr(input, 'name'):\n            self.filename = input.name\n        elif not getattr(self, 'filename', ''):\n            self.filename = ''\n        if hasattr(input, \"read\"):\n            mosrc = input.read()\n            input.close()\n            input = mosrc\n        little, = struct.unpack(\"<L\", input[:4])\n        big, = struct.unpack(\">L\", input[:4])\n        if little == MO_MAGIC_NUMBER:\n            endian = \"<\"\n        elif big == MO_MAGIC_NUMBER:\n            endian = \">\"\n        else:\n            raise ValueError(\"This is not an MO file\")\n        magic, version_maj, version_min, lenkeys, startkey, \\\n            startvalue, sizehash, offsethash = struct.unpack(\"%sLHHiiiii\" % endian,\n                                                             input[:(7 * 4)])\n        if version_maj >= 1:\n            raise base.ParseError(\"\"\"Unable to process version %d.%d MO files\"\"\" % (version_maj, version_min))\n        for i in range(lenkeys):\n            nextkey = startkey + (i * 2 * 4)\n            nextvalue = startvalue + (i * 2 * 4)\n            klength, koffset = struct.unpack(\"%sii\" % endian,\n                                             input[nextkey:nextkey + (2 * 4)])\n            vlength, voffset = struct.unpack(\"%sii\" % endian,\n                                             input[nextvalue:nextvalue + (2 * 4)])\n            source = input[koffset:koffset + klength]\n            context = None\n            if b\"\\x04\" in source:\n                context, source = source.split(b\"\\x04\")\n            # Still need to handle KDE comments\n            if source == \"\":\n                charset = re.search(b\"charset=([^\\\\s]+)\",\n                                    input[voffset:voffset + vlength])\n                if charset:\n                    self.encoding = charset.group(1)\n            source = multistring([s.decode(self.encoding)\n                                  for s in source.split(b\"\\0\")])\n            target = multistring([s.decode(self.encoding)\n                                  for s in input[voffset:voffset + vlength].split(b\"\\0\")])\n            newunit = mounit(source)\n            newunit.target = target\n            if context is not None:\n                newunit.msgctxt.append(context)\n            self.addunit(newunit)\n", "path": "translate/storage/mo.py"}]}