2 # -*- coding: utf-8 -*-
4 # Copyright 2006-2007 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
23 """An xliff file specifically suited for handling the po representation of
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
33 def hasplurals(thing
):
34 if not isinstance(thing
, multistring
):
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):
46 if not hasplurals(source
):
47 super(PoXliffUnit
, self
).__init
__(source
)
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
):
58 if len(self
.units
) == 0:
60 if not super(PoXliffUnit
, self
).__eq
__(other
):
62 for i
in range(len(self
.units
)-1):
63 if not self
.units
[i
+1] == other
.units
[i
+1]:
66 if len(self
.units
) <= 1:
67 if isinstance(other
, lisa
.LISAunit
):
68 return super(PoXliffUnit
, self
).__eq
__(other
)
70 return self
.source
== other
.source
and self
.target
== other
.target
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
)
79 for unit
in self
.units
:
81 self
.xmlelement
.remove(unit
.xmlelement
)
82 except xml
.dom
.NotFoundErr
:
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
)
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
:
101 if not self
.hasplural():
102 super(PoXliffUnit
, self
).settarget(text
, lang
, append
)
104 if not isinstance(text
, multistring
):
105 text
= multistring(text
)
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
113 self
.source
= multistring(sources
)
115 elif targetl
< sourcel
:
116 targets
= text
.strings
+ [""] * (sourcel
- targetl
)
118 targets
= text
.strings
120 for i
in range(len(self
.units
)):
121 self
.units
[i
].target
= targets
[i
]
125 strings
= [unit
.target
for unit
in self
.units
]
127 return multistring(strings
)
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"))
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:
153 elif notes
.find(trancomments
) >= 0:
156 trancomments
= trancomments
+ notes
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:
163 elif devcomments
.find(autocomments
) >= 0:
164 autocomments
= devcomments
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()
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")
193 for (type, text
) in group
:
194 if type == "sourcefile":
196 elif type == "linenumber":
200 sourcefile
= sourcefile
+ ":" + linenumber
201 references
.append(sourcefile
)
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")
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")
225 commentpairs
= filter(hastrancomment
, group
)
226 for (type, text
) in commentpairs
:
227 comments
.append(text
)
228 return "\n".join(comments
)
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
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'))
245 subunit
= xliff
.xliffunit
.createfromxmlElement(unit
)
246 subunit
.namespace
= namespace
247 group
.units
.append(subunit
)
249 createfromxmlElement
= classmethod(createfromxmlElement
)
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)
271 unit
.xmlelement
.set("restype", "x-gettext-domain-header")
272 unit
.xmlelement
.set("approved", "no")
273 lisa
.setXMLspace(unit
.xmlelement
, "preserve")
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
287 sources
= source
.strings
288 targets
= target
.strings
289 self
._messagenum
+= 1
291 group
= self
.creategroup(filename
, True, restype
="x-gettext-plural")
292 for (src
, tgt
) in zip(sources
, targets
):
293 unit
= self
.UnitClass(src
)
295 unit
.setid("%d[%d]" % (self
._messagenum
, pluralnum
))
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
))
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
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"):
335 self
.document
= etree
.fromstring(xml
).getroottree()
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:
344 singularunits
= filter(isnonpluralunit
, termEntries
)
345 pluralunit_iter
= pluralunits(pluralgroups
)
347 nextplural
= pluralunit_iter
.next()
348 except StopIteration:
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
)
356 nextplural
= pluralunit_iter
.next()
357 except StopIteration, i
:
360 self
.units
.append(term
)