2 # -*- coding: utf-8 -*-
4 # Copyright 2002-2006 Zuza Software Foundation
6 # This file is part of translate.
8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 """class that handles all header functions for a header in a po file"""
24 from translate
.misc
import dictutils
25 from translate
import __version__
29 author_re
= re
.compile(r
".*<\S+@\S+>.*\d{4,4}")
31 def parseheaderstring(input):
32 """Parses an input string with the definition of a PO header and returns
33 the interpreted values as a dictionary"""
34 headervalues
= dictutils
.ordereddict()
35 for line
in input.split("\n"):
36 if not line
or ":" not in line
:
38 key
, value
= line
.split(":", 1)
39 #We don't want unicode keys
40 key
= str(key
.strip())
41 headervalues
[key
] = value
.strip()
45 """Returns the timezone as a string in the format [+-]0000, eg +0200."""
47 tzoffset
= time
.altzone
49 tzoffset
= time
.timezone
51 hours
, minutes
= time
.gmtime(abs(tzoffset
))[3:5]
54 tz
= str("%+d" % hours
).zfill(3) + str(minutes
).zfill(2)
57 def update(existing
, add
=False, **kwargs
):
58 """Update an existing header dictionary with the values in kwargs, adding new values
61 @return: Updated dictionary of header entries
64 headerargs
= dictutils
.ordereddict()
65 fixedargs
= dictutils
.cidict()
66 for key
, value
in kwargs
.items():
67 key
= key
.replace("_", "-")
70 fixedargs
[key
] = value
72 for key
in poheader
.header_order
:
73 if existing
.has_key(key
):
75 headerargs
[key
] = fixedargs
.pop(key
)
77 headerargs
[key
] = existing
[key
]
79 elif add
and fixedargs
.has_key(key
):
80 headerargs
[key
] = fixedargs
.pop(key
)
81 for key
, value
in existing
.iteritems():
82 if not key
in removed
:
83 headerargs
[key
] = value
86 headerargs
[key
] = fixedargs
[key
]
90 class poheader(object):
91 """This class implements functionality for manipulation of po file headers.
92 This class is a mix-in class and useless on its own. It must be used from all
93 classes which represent a po file"""
95 x_generator
= "Translate Toolkit %s" % __version__
.ver
99 "Report-Msgid-Bugs-To",
106 "Content-Transfer-Encoding",
112 def makeheaderdict(self
, charset
="CHARSET", encoding
="ENCODING", project_id_version
=None, pot_creation_date
=None, po_revision_date
=None, last_translator
=None, language_team
=None, mime_version
=None, plural_forms
=None, report_msgid_bugs_to
=None, **kwargs
):
113 """create a header for the given filename. arguments are specially handled, kwargs added as key: value
114 pot_creation_date can be None (current date) or a value (datetime or string)
115 po_revision_date can be None (form), False (=pot_creation_date), True (=now), or a value (datetime or string)
117 @return: Dictionary with the header items
120 if project_id_version
is None:
121 project_id_version
= "PACKAGE VERSION"
122 if pot_creation_date
is None or pot_creation_date
== True:
123 pot_creation_date
= time
.strftime("%Y-%m-%d %H:%M") + tzstring()
124 if isinstance(pot_creation_date
, time
.struct_time
):
125 pot_creation_date
= time
.strftime("%Y-%m-%d %H:%M", pot_creation_date
) + tzstring()
126 if po_revision_date
is None:
127 po_revision_date
= "YEAR-MO-DA HO:MI+ZONE"
128 elif po_revision_date
== False:
129 po_revision_date
= pot_creation_date
130 elif po_revision_date
== True:
131 po_revision_date
= time
.strftime("%Y-%m-%d %H:%M") + tzstring()
132 if isinstance(po_revision_date
, time
.struct_time
):
133 po_revision_date
= time
.strftime("%Y-%m-%d %H:%M", po_revision_date
) + tzstring()
134 if last_translator
is None:
135 last_translator
= "FULL NAME <EMAIL@ADDRESS>"
136 if language_team
is None:
137 language_team
= "LANGUAGE <LL@li.org>"
138 if mime_version
is None:
140 if report_msgid_bugs_to
is None:
141 report_msgid_bugs_to
= ""
143 defaultargs
= dictutils
.ordereddict()
144 defaultargs
["Project-Id-Version"] = project_id_version
145 defaultargs
["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
146 defaultargs
["POT-Creation-Date"] = pot_creation_date
147 defaultargs
["PO-Revision-Date"] = po_revision_date
148 defaultargs
["Last-Translator"] = last_translator
149 defaultargs
["Language-Team"] = language_team
150 defaultargs
["MIME-Version"] = mime_version
151 defaultargs
["Content-Type"] = "text/plain; charset=%s" % charset
152 defaultargs
["Content-Transfer-Encoding"] = encoding
154 defaultargs
["Plural-Forms"] = plural_forms
155 defaultargs
["X-Generator"] = self
.x_generator
157 return update(defaultargs
, add
=True, **kwargs
)
160 """Returns the header element, or None. Only the first element is allowed
161 to be a header. Note that this could still return an empty header element,
163 if len(self
.units
) == 0:
165 candidate
= self
.units
[0]
166 if candidate
.isheader():
171 def parseheader(self
):
172 """Parses the PO header and returns
173 the interpreted values as a dictionary"""
174 header
= self
.header()
177 return parseheaderstring(header
.target
)
179 def updateheader(self
, add
=False, **kwargs
):
180 """Updates the fields in the PO style header.
181 This will create a header if add == True"""
182 header
= self
.header()
184 # FIXME: does not work for xliff files yet
185 if add
and callable(getattr(self
, "makeheader", None)):
186 header
= self
.makeheader(**kwargs
)
187 self
.units
.insert(0, header
)
189 headeritems
= update(self
.parseheader(), add
, **kwargs
)
190 keys
= headeritems
.keys()
191 if not "Content-Type" in keys
or "charset=CHARSET" in headeritems
["Content-Type"]:
192 headeritems
["Content-Type"] = "text/plain; charset=UTF-8"
193 if not "Content-Transfer-Encoding" in keys
or "ENCODING" in headeritems
["Content-Transfer-Encoding"]:
194 headeritems
["Content-Transfer-Encoding"] = "8bit"
196 for key
, value
in headeritems
.items():
197 headerString
+= "%s: %s\n" % (key
, value
)
198 header
.target
= headerString
199 header
.markfuzzy(False) # TODO: check why we do this?
202 def getheaderplural(self
):
203 """returns the nplural and plural values from the header"""
204 header
= self
.parseheader()
205 pluralformvalue
= header
.get('Plural-Forms', None)
206 if pluralformvalue
is None:
208 nplural
= re
.findall("nplurals=(.+?);", pluralformvalue
)
209 plural
= re
.findall("plural=(.+?);?$", pluralformvalue
)
210 if not nplural
or nplural
[0] == "INTEGER":
214 if not plural
or plural
[0] == "EXPRESSION":
218 return nplural
, plural
220 def updateheaderplural(self
, nplurals
, plural
):
221 """update the Plural-Form PO header"""
222 if isinstance(nplurals
, basestring
):
223 nplurals
= int(nplurals
)
224 self
.updateheader( Plural_Forms
= "nplurals=%d; plural=%s;" % (nplurals
, plural
) )
226 def mergeheaders(self
, otherstore
):
227 """Merges another header with this header.
229 This header is assumed to be the template.
231 @type otherstore: L{base.TranslationStore}
235 newvalues
= otherstore
.parseheader()
237 "Project_Id_Version": newvalues
['Project-Id-Version'],
238 "PO_Revision_Date" : newvalues
['PO-Revision-Date'],
239 "Last_Translator" : newvalues
['Last-Translator'],
240 "Language_Team" : newvalues
['Language-Team'],
242 # not necessarily there:
243 plurals
= newvalues
.get('Plural-Forms', None)
245 retain
['Plural-Forms'] = plurals
246 self
.updateheader(**retain
)
248 def updatecontributor(self
, name
, email
=None):
249 """Add contribution comments
251 header
= self
.header()
257 contribexists
= False
260 for line
in header
.getnotes("translator").split('\n'):
262 if line
== u
"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
265 if author_re
.match(line
):
267 contriblines
.append(line
)
269 if line
== "" and incontrib
:
273 contriblines
.append(line
)
275 prelines
.append(line
)
277 postlines
.append(line
)
279 year
= time
.strftime("%Y")
280 contribexists
= False
281 for line
in contriblines
:
282 if name
in line
and (email
is None or email
in line
):
285 if not contribexists
:
286 # Add a new contributor
288 contriblines
.append("%s <%s>, %s" % (name
, email
, year
))
290 contriblines
.append("%s, %s" % (name
, year
))
293 header
.addnote("\n".join(prelines
))
294 header
.addnote("\n".join(contriblines
))
295 header
.addnote("\n".join(postlines
))