fix git support for v1.5.3 (or higher) by setting "--work-tree"
[translate_toolkit.git] / storage / poxliff.py
blob34f517b9ffce012504d12415ff833f01ae123c0f
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright 2006-2007 Zuza Software Foundation
5 #
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
23 """An xliff file specifically suited for handling the po representation of
24 xliff. """
26 from translate.storage import xliff
27 from translate.storage import lisa
28 from translate.storage import poheader
29 from translate.misc.multistring import multistring
30 from lxml import etree
31 import re
33 def hasplurals(thing):
34 if not isinstance(thing, multistring):
35 return False
36 return len(thing.strings) > 1
38 class PoXliffUnit(xliff.xliffunit):
39 """A class to specifically handle the plural units created from a po file."""
40 def __init__(self, source, empty=False):
41 self.units = []
43 if empty:
44 return
46 if not hasplurals(source):
47 super(PoXliffUnit, self).__init__(source)
48 return
50 self.xmlelement = etree.Element(self.namespaced("group"))
51 self.xmlelement.set("restype", "x-gettext-plurals")
52 self.setsource(source)
54 def __eq__(self, other):
55 if isinstance(other, PoXliffUnit):
56 if len(self.units) != len(other.units):
57 return False
58 if len(self.units) == 0:
59 return True
60 if not super(PoXliffUnit, self).__eq__(other):
61 return False
62 for i in range(len(self.units)-1):
63 if not self.units[i+1] == other.units[i+1]:
64 return False
65 return True
66 if len(self.units) <= 1:
67 if isinstance(other, lisa.LISAunit):
68 return super(PoXliffUnit, self).__eq__(other)
69 else:
70 return self.source == other.source and self.target == other.target
71 return False
73 def setsource(self, source, sourcelang="en"):
74 # TODO: consider changing from plural to singular, etc.
75 if not hasplurals(source):
76 super(PoXliffUnit, self).setsource(source, sourcelang)
77 else:
78 target = self.target
79 for unit in self.units:
80 try:
81 self.xmlelement.remove(unit.xmlelement)
82 except xml.dom.NotFoundErr:
83 pass
84 self.units = []
85 for s in source.strings:
86 newunit = xliff.xliffunit(s)
87 # newunit.namespace = self.namespace #XXX?necessary?
88 self.units.append(newunit)
89 self.xmlelement.append(newunit.xmlelement)
90 self.target = target
92 def getsource(self):
93 strings = [super(PoXliffUnit, self).getsource()]
94 strings.extend([unit.source for unit in self.units[1:]])
95 return multistring(strings)
96 source = property(getsource, setsource)
98 def settarget(self, text, lang='xx', append=False):
99 if self.gettarget() == text:
100 return
101 if not self.hasplural():
102 super(PoXliffUnit, self).settarget(text, lang, append)
103 return
104 if not isinstance(text, multistring):
105 text = multistring(text)
106 source = self.source
107 sourcel = len(source.strings)
108 targetl = len(text.strings)
109 if sourcel < targetl:
110 sources = source.strings + [source.strings[-1]] * (targetl - sourcel)
111 targets = text.strings
112 id = self.getid()
113 self.source = multistring(sources)
114 self.setid(id)
115 elif targetl < sourcel:
116 targets = text.strings + [""] * (sourcel - targetl)
117 else:
118 targets = text.strings
120 for i in range(len(self.units)):
121 self.units[i].target = targets[i]
123 def gettarget(self):
124 if self.hasplural():
125 strings = [unit.target for unit in self.units]
126 if strings:
127 return multistring(strings)
128 else:
129 return None
130 else:
131 return super(PoXliffUnit, self).gettarget()
133 target = property(gettarget, settarget)
135 def addnote(self, text, origin=None):
136 """Add a note specifically in a "note" tag"""
137 if isinstance(text, str):
138 text = text.decode("utf-8")
139 note = etree.SubElement(self.xmlelement, self.namespaced("note"))
140 note.text = text
141 if origin:
142 note.set("from", origin)
143 for unit in self.units[1:]:
144 unit.addnote(text, origin)
146 def getnotes(self, origin=None):
147 #NOTE: We support both <context> and <note> tags in xliff files for comments
148 if origin == "translator":
149 notes = super(PoXliffUnit, self).getnotes("translator")
150 trancomments = self.gettranslatorcomments()
151 if notes == trancomments or trancomments.find(notes) >= 0:
152 notes = ""
153 elif notes.find(trancomments) >= 0:
154 trancomments = notes
155 notes = ""
156 trancomments = trancomments + notes
157 return trancomments
158 elif origin in ["programmer", "developer", "source code"]:
159 devcomments = super(PoXliffUnit, self).getnotes("developer")
160 autocomments = self.getautomaticcomments()
161 if devcomments == autocomments or autocomments.find(devcomments) >= 0:
162 devcomments = ""
163 elif devcomments.find(autocomments) >= 0:
164 autocomments = devcomments
165 devcomments = ""
166 return autocomments
167 else:
168 return super(PoXliffUnit, self).getnotes(origin)
170 def markfuzzy(self, value=True):
171 super(PoXliffUnit, self).markfuzzy(value)
172 for unit in self.units[1:]:
173 unit.markfuzzy(value)
175 def marktranslated(self):
176 super(PoXliffUnit, self).marktranslated()
177 for unit in self.units[1:]:
178 unit.marktranslated()
180 def setid(self, id):
181 self.xmlelement.set("id", id)
182 if len(self.units) > 1:
183 for i in range(len(self.units)):
184 self.units[i].setid("%s[%d]" % (id, i))
186 def getlocations(self):
187 """Returns all the references (source locations)"""
188 groups = self.getcontextgroups("po-reference")
189 references = []
190 for group in groups:
191 sourcefile = ""
192 linenumber = ""
193 for (type, text) in group:
194 if type == "sourcefile":
195 sourcefile = text
196 elif type == "linenumber":
197 linenumber = text
198 assert sourcefile
199 if linenumber:
200 sourcefile = sourcefile + ":" + linenumber
201 references.append(sourcefile)
202 return references
204 def getautomaticcomments(self):
205 """Returns the automatic comments (x-po-autocomment), which corresponds
206 to the #. style po comments."""
207 def hasautocomment((type, text)):
208 return type == "x-po-autocomment"
209 groups = self.getcontextgroups("po-entry")
210 comments = []
211 for group in groups:
212 commentpairs = filter(hasautocomment, group)
213 for (type, text) in commentpairs:
214 comments.append(text)
215 return "\n".join(comments)
217 def gettranslatorcomments(self):
218 """Returns the translator comments (x-po-trancomment), which corresponds
219 to the # style po comments."""
220 def hastrancomment((type, text)):
221 return type == "x-po-trancomment"
222 groups = self.getcontextgroups("po-entry")
223 comments = []
224 for group in groups:
225 commentpairs = filter(hastrancomment, group)
226 for (type, text) in commentpairs:
227 comments.append(text)
228 return "\n".join(comments)
230 def isheader(self):
231 return "gettext-domain-header" in (self.getrestype() or "")
233 def createfromxmlElement(cls, element, namespace=None):
234 if element.tag.endswith("trans-unit"):
235 object = cls(None, empty=True)
236 object.xmlelement = element
237 object.namespace = namespace
238 return object
239 assert element.tag.endswith("group")
240 group = cls(None, empty=True)
241 group.xmlelement = element
242 group.namespace = namespace
243 units = element.findall('.//%s' % group.namespaced('trans-unit'))
244 for unit in units:
245 subunit = xliff.xliffunit.createfromxmlElement(unit)
246 subunit.namespace = namespace
247 group.units.append(subunit)
248 return group
249 createfromxmlElement = classmethod(createfromxmlElement)
251 def hasplural(self):
252 return self.xmlelement.tag == self.namespaced("group")
255 class PoXliffFile(xliff.xlifffile, poheader.poheader):
256 """a file for the po variant of Xliff files"""
257 UnitClass = PoXliffUnit
258 def __init__(self, *args, **kwargs):
259 if not "sourcelanguage" in kwargs:
260 kwargs["sourcelanguage"] = "en-US"
261 xliff.xlifffile.__init__(self, *args, **kwargs)
263 def createfilenode(self, filename, sourcelanguage="en-US", datatype="po"):
264 # Let's ignore the sourcelanguage parameter opting for the internal
265 # one. PO files will probably be one language
266 return super(PoXliffFile, self).createfilenode(filename, sourcelanguage=self.sourcelanguage, datatype="po")
268 def addheaderunit(self, target, filename):
269 unit = self.addsourceunit(target, filename, True)
270 unit.target = target
271 unit.xmlelement.set("restype", "x-gettext-domain-header")
272 unit.xmlelement.set("approved", "no")
273 lisa.setXMLspace(unit.xmlelement, "preserve")
274 return unit
276 def addplural(self, source, target, filename, createifmissing=False):
277 """This method should now be unnecessary, but is left for reference"""
278 assert isinstance(source, multistring)
279 if not isinstance(target, multistring):
280 target = multistring(target)
281 sourcel = len(source.strings)
282 targetl = len(target.strings)
283 if sourcel < targetl:
284 sources = source.strings + [source.strings[-1]] * targetl - sourcel
285 targets = target.strings
286 else:
287 sources = source.strings
288 targets = target.strings
289 self._messagenum += 1
290 pluralnum = 0
291 group = self.creategroup(filename, True, restype="x-gettext-plural")
292 for (src, tgt) in zip(sources, targets):
293 unit = self.UnitClass(src)
294 unit.target = tgt
295 unit.setid("%d[%d]" % (self._messagenum, pluralnum))
296 pluralnum += 1
297 group.append(unit.xmlelement)
298 self.units.append(unit)
300 if pluralnum < sourcel:
301 for string in sources[pluralnum:]:
302 unit = self.UnitClass(src)
303 unit.xmlelement.set("translate", "no")
304 unit.setid("%d[%d]" % (self._messagenum, pluralnum))
305 pluralnum += 1
306 group.append(unit.xmlelement)
307 self.units.append(unit)
309 return self.units[-pluralnum]
311 def parse(self, xml):
312 """Populates this object from the given xml string"""
313 #TODO: Make more robust
314 def ispluralgroup(node):
315 """determines whether the xml node refers to a getttext plural"""
316 return node.get("restype") == "x-gettext-plurals"
318 def isnonpluralunit(node):
319 """determindes whether the xml node contains a plural like id.
321 We want to filter out all the plural nodes, except the very first
322 one in each group.
324 return re.match(r"\d+\[[123456]\]$", node.get("id") or "") is None
326 def pluralunits(pluralgroups):
327 for pluralgroup in pluralgroups:
328 yield self.UnitClass.createfromxmlElement(pluralgroup, namespace=self.namespace)
330 self.filename = getattr(xml, 'name', '')
331 if hasattr(xml, "read"):
332 xml.seek(0)
333 xmlsrc = xml.read()
334 xml = xmlsrc
335 self.document = etree.fromstring(xml).getroottree()
336 self.initbody()
337 assert self.document.getroot().tag == self.namespaced(self.rootNode)
338 groups = self.document.findall(".//%s" % self.namespaced("group"))
339 pluralgroups = filter(ispluralgroup, groups)
340 termEntries = self.body.findall('.//%s' % self.namespaced(self.UnitClass.rootNode))
341 if termEntries is None:
342 return
344 singularunits = filter(isnonpluralunit, termEntries)
345 pluralunit_iter = pluralunits(pluralgroups)
346 try:
347 nextplural = pluralunit_iter.next()
348 except StopIteration:
349 nextplural = None
351 for entry in singularunits:
352 term = self.UnitClass.createfromxmlElement(entry, namespace=self.namespace)
353 if nextplural and unicode(term.source) in nextplural.source.strings:
354 self.units.append(nextplural)
355 try:
356 nextplural = pluralunit_iter.next()
357 except StopIteration, i:
358 nextplural = None
359 else:
360 self.units.append(term)