~ubuntu-branches/ubuntu/trusty/python-babel/trusty

« back to all changes in this revision

Viewing changes to babel/messages/mofile.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-10-28 10:11:31 UTC
  • mfrom: (4.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20131028101131-zwbmm8sc29iemmlr
Tags: 1.3-2ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - debian/rules: Run the testsuite during builds.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
 
#
3
 
# Copyright (C) 2007 Edgewall Software
4
 
# All rights reserved.
5
 
#
6
 
# This software is licensed as described in the file COPYING, which
7
 
# you should have received as part of this distribution. The terms
8
 
# are also available at http://babel.edgewall.org/wiki/License.
9
 
#
10
 
# This software consists of voluntary contributions made by many
11
 
# individuals. For the exact contribution history, see the revision
12
 
# history and logs, available at http://babel.edgewall.org/log/.
13
 
 
14
 
"""Writing of files in the ``gettext`` MO (machine object) format.
15
 
 
16
 
:since: version 0.9
17
 
:see: `The Format of MO Files
18
 
       <http://www.gnu.org/software/gettext/manual/gettext.html#MO-Files>`_
 
2
"""
 
3
    babel.messages.mofile
 
4
    ~~~~~~~~~~~~~~~~~~~~~
 
5
 
 
6
    Writing of files in the ``gettext`` MO (machine object) format.
 
7
 
 
8
    :copyright: (c) 2013 by the Babel Team.
 
9
    :license: BSD, see LICENSE for more details.
19
10
"""
20
11
 
21
12
import array
22
13
import struct
23
14
 
24
 
__all__ = ['write_mo']
25
 
__docformat__ = 'restructuredtext en'
 
15
from babel.messages.catalog import Catalog, Message
 
16
from babel._compat import range_type
 
17
 
 
18
 
 
19
LE_MAGIC = 0x950412de
 
20
BE_MAGIC = 0xde120495
 
21
 
 
22
 
 
23
def read_mo(fileobj):
 
24
    """Read a binary MO file from the given file-like object and return a
 
25
    corresponding `Catalog` object.
 
26
 
 
27
    :param fileobj: the file-like object to read the MO file from
 
28
 
 
29
    :note: The implementation of this function is heavily based on the
 
30
           ``GNUTranslations._parse`` method of the ``gettext`` module in the
 
31
           standard library.
 
32
    """
 
33
    catalog = Catalog()
 
34
    headers = {}
 
35
 
 
36
    filename = getattr(fileobj, 'name', '')
 
37
 
 
38
    buf = fileobj.read()
 
39
    buflen = len(buf)
 
40
    unpack = struct.unpack
 
41
 
 
42
    # Parse the .mo file header, which consists of 5 little endian 32
 
43
    # bit words.
 
44
    magic = unpack('<I', buf[:4])[0] # Are we big endian or little endian?
 
45
    if magic == LE_MAGIC:
 
46
        version, msgcount, origidx, transidx = unpack('<4I', buf[4:20])
 
47
        ii = '<II'
 
48
    elif magic == BE_MAGIC:
 
49
        version, msgcount, origidx, transidx = unpack('>4I', buf[4:20])
 
50
        ii = '>II'
 
51
    else:
 
52
        raise IOError(0, 'Bad magic number', filename)
 
53
 
 
54
    # Now put all messages from the .mo file buffer into the catalog
 
55
    # dictionary
 
56
    for i in range_type(0, msgcount):
 
57
        mlen, moff = unpack(ii, buf[origidx:origidx + 8])
 
58
        mend = moff + mlen
 
59
        tlen, toff = unpack(ii, buf[transidx:transidx + 8])
 
60
        tend = toff + tlen
 
61
        if mend < buflen and tend < buflen:
 
62
            msg = buf[moff:mend]
 
63
            tmsg = buf[toff:tend]
 
64
        else:
 
65
            raise IOError(0, 'File is corrupt', filename)
 
66
 
 
67
        # See if we're looking at GNU .mo conventions for metadata
 
68
        if mlen == 0:
 
69
            # Catalog description
 
70
            lastkey = key = None
 
71
            for item in tmsg.splitlines():
 
72
                item = item.strip()
 
73
                if not item:
 
74
                    continue
 
75
                if b':' in item:
 
76
                    key, value = item.split(b':', 1)
 
77
                    lastkey = key = key.strip().lower()
 
78
                    headers[key] = value.strip()
 
79
                elif lastkey:
 
80
                    headers[lastkey] += b'\n' + item
 
81
 
 
82
        if b'\x04' in msg: # context
 
83
            ctxt, msg = msg.split(b'\x04')
 
84
        else:
 
85
            ctxt = None
 
86
 
 
87
        if b'\x00' in msg: # plural forms
 
88
            msg = msg.split(b'\x00')
 
89
            tmsg = tmsg.split(b'\x00')
 
90
            if catalog.charset:
 
91
                msg = [x.decode(catalog.charset) for x in msg]
 
92
                tmsg = [x.decode(catalog.charset) for x in tmsg]
 
93
        else:
 
94
            if catalog.charset:
 
95
                msg = msg.decode(catalog.charset)
 
96
                tmsg = tmsg.decode(catalog.charset)
 
97
        catalog[msg] = Message(msg, tmsg, context=ctxt)
 
98
 
 
99
        # advance to next entry in the seek tables
 
100
        origidx += 8
 
101
        transidx += 8
 
102
 
 
103
    catalog.mime_headers = headers.items()
 
104
    return catalog
 
105
 
26
106
 
27
107
def write_mo(fileobj, catalog, use_fuzzy=False):
28
108
    """Write a catalog to the specified file-like object using the GNU MO file
29
109
    format.
30
 
    
 
110
 
31
111
    >>> from babel.messages import Catalog
32
112
    >>> from gettext import GNUTranslations
33
113
    >>> from StringIO import StringIO
34
 
    
 
114
 
35
115
    >>> catalog = Catalog(locale='en_US')
36
116
    >>> catalog.add('foo', 'Voh')
 
117
    <Message ...>
37
118
    >>> catalog.add((u'bar', u'baz'), (u'Bahr', u'Batz'))
 
119
    <Message ...>
38
120
    >>> catalog.add('fuz', 'Futz', flags=['fuzzy'])
 
121
    <Message ...>
39
122
    >>> catalog.add('Fizz', '')
 
123
    <Message ...>
40
124
    >>> catalog.add(('Fuzz', 'Fuzzes'), ('', ''))
 
125
    <Message ...>
41
126
    >>> buf = StringIO()
42
 
    
 
127
 
43
128
    >>> write_mo(buf, catalog)
44
129
    >>> buf.seek(0)
45
130
    >>> translations = GNUTranslations(fp=buf)
57
142
    u'Fuzz'
58
143
    >>> translations.ugettext('Fuzzes')
59
144
    u'Fuzzes'
60
 
    
 
145
 
61
146
    :param fileobj: the file-like object to write to
62
147
    :param catalog: the `Catalog` instance
63
148
    :param use_fuzzy: whether translations marked as "fuzzy" should be included
68
153
        messages[1:] = [m for m in messages[1:] if not m.fuzzy]
69
154
    messages.sort()
70
155
 
71
 
    ids = strs = ''
 
156
    ids = strs = b''
72
157
    offsets = []
73
158
 
74
159
    for message in messages:
75
160
        # For each string, we need size and file offset.  Each string is NUL
76
161
        # terminated; the NUL does not count into the size.
77
162
        if message.pluralizable:
78
 
            msgid = '\x00'.join([
 
163
            msgid = b'\x00'.join([
79
164
                msgid.encode(catalog.charset) for msgid in message.id
80
165
            ])
81
166
            msgstrs = []
84
169
                    msgstrs.append(message.id[min(int(idx), 1)])
85
170
                else:
86
171
                    msgstrs.append(string)
87
 
            msgstr = '\x00'.join([
 
172
            msgstr = b'\x00'.join([
88
173
                msgstr.encode(catalog.charset) for msgstr in msgstrs
89
174
            ])
90
175
        else:
93
178
                msgstr = message.id.encode(catalog.charset)
94
179
            else:
95
180
                msgstr = message.string.encode(catalog.charset)
 
181
        if message.context:
 
182
            msgid = b'\x04'.join([message.context.encode(catalog.charset),
 
183
                                 msgid])
96
184
        offsets.append((len(ids), len(msgid), len(strs), len(msgstr)))
97
 
        ids += msgid + '\x00'
98
 
        strs += msgstr + '\x00'
 
185
        ids += msgid + b'\x00'
 
186
        strs += msgstr + b'\x00'
99
187
 
100
188
    # The header is 7 32-bit unsigned integers.  We don't use hash tables, so
101
189
    # the keys start right after the index tables.
112
200
    offsets = koffsets + voffsets
113
201
 
114
202
    fileobj.write(struct.pack('Iiiiiii',
115
 
        0x950412deL,                # magic
 
203
        LE_MAGIC,                   # magic
116
204
        0,                          # version
117
205
        len(messages),              # number of entries
118
206
        7 * 4,                      # start of key index