Package translate :: Package storage :: Module xliff
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.xliff

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2005-2011 Zuza Software Foundation 
  5  # 
  6  # This file is part of the Translate Toolkit. 
  7  # 
  8  # This program 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. 
 12  # 
 13  # This program 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. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, see <http://www.gnu.org/licenses/>. 
 20   
 21  """Module for handling XLIFF files for translation. 
 22   
 23  The official recommendation is to use the extention .xlf for XLIFF files. 
 24  """ 
 25   
 26  from lxml import etree 
 27   
 28  from translate.misc.multistring import multistring 
 29  from translate.storage import base, lisa 
 30  from translate.storage.lisa import getXMLspace 
 31  from translate.storage.placeables.lisa import xml_to_strelem, strelem_to_xml 
 32  from translate.storage.workflow import StateEnum as state 
 33   
 34  # TODO: handle translation types 
 35   
 36  ID_SEPARATOR = u"\04" 
 37  # ID_SEPARATOR is commonly used through toolkit to generate compound 
 38  # unit ids (for instance to concatenate msgctxt and msgid in po), but 
 39  # \04 is an illegal char in XML 1.0, ID_SEPARATOR_SAFE will be used 
 40  # instead when converting between xliff and other toolkit supported 
 41  # formats 
 42  ID_SEPARATOR_SAFE = u"__%04__" 
 43   
 44   
45 -class xliffunit(lisa.LISAunit):
46 """A single term in the xliff file.""" 47 48 rootNode = "trans-unit" 49 languageNode = "source" 50 textNode = "" 51 namespace = 'urn:oasis:names:tc:xliff:document:1.1' 52 53 _default_xml_space = "default" 54 55 #TODO: id and all the trans-unit level stuff 56 57 S_UNTRANSLATED = state.EMPTY 58 S_NEEDS_TRANSLATION = state.NEEDS_WORK 59 S_NEEDS_REVIEW = state.NEEDS_REVIEW 60 S_TRANSLATED = state.UNREVIEWED 61 S_SIGNED_OFF = state.FINAL 62 S_FINAL = state.MAX 63 64 statemap = { 65 "new": S_UNTRANSLATED + 1, 66 "needs-translation": S_NEEDS_TRANSLATION, 67 "needs-adaptation": S_NEEDS_TRANSLATION + 1, 68 "needs-l10n": S_NEEDS_TRANSLATION + 2, 69 "needs-review-translation": S_NEEDS_REVIEW, 70 "needs-review-adaptation": S_NEEDS_REVIEW + 1, 71 "needs-review-l10n": S_NEEDS_REVIEW + 2, 72 "translated": S_TRANSLATED, 73 "signed-off": S_SIGNED_OFF, 74 "final": S_FINAL, 75 } 76 77 statemap_r = dict((i[1], i[0]) for i in statemap.iteritems()) 78 79 STATE = { 80 S_UNTRANSLATED: (state.EMPTY, state.NEEDS_WORK), 81 S_NEEDS_TRANSLATION: (state.NEEDS_WORK, state.NEEDS_REVIEW), 82 S_NEEDS_REVIEW: (state.NEEDS_REVIEW, state.UNREVIEWED), 83 S_TRANSLATED: (state.UNREVIEWED, state.FINAL), 84 S_SIGNED_OFF: (state.FINAL, state.MAX), 85 } 86
87 - def __init__(self, source, empty=False, **kwargs):
88 """Override the constructor to set xml:space="preserve".""" 89 super(xliffunit, self).__init__(source, empty, **kwargs) 90 if empty: 91 return 92 lisa.setXMLspace(self.xmlelement, "preserve")
93
94 - def createlanguageNode(self, lang, text, purpose):
95 """Returns an xml Element setup with given parameters.""" 96 97 #TODO: for now we do source, but we have to test if it is target, perhaps 98 # with parameter. Alternatively, we can use lang, if supplied, since an xliff 99 #file has to conform to the bilingual nature promised by the header. 100 assert purpose 101 langset = etree.Element(self.namespaced(purpose)) 102 #TODO: check language 103 # lisa.setXMLlang(langset, lang) 104 105 langset.text = text 106 return langset
107
108 - def getlanguageNodes(self):
109 """We override this to get source and target nodes.""" 110 source = None 111 target = None 112 nodes = [] 113 try: 114 source = self.xmlelement.iterchildren(self.namespaced(self.languageNode)).next() 115 target = self.xmlelement.iterchildren(self.namespaced('target')).next() 116 nodes = [source, target] 117 except StopIteration: 118 if source is not None: 119 nodes.append(source) 120 if not target is None: 121 nodes.append(target) 122 return nodes
123
124 - def set_rich_source(self, value, sourcelang='en'):
125 sourcelanguageNode = self.get_source_dom() 126 if sourcelanguageNode is None: 127 sourcelanguageNode = self.createlanguageNode(sourcelang, u'', "source") 128 self.set_source_dom(sourcelanguageNode) 129 130 # Clear sourcelanguageNode first 131 for i in range(len(sourcelanguageNode)): 132 del sourcelanguageNode[0] 133 sourcelanguageNode.text = None 134 135 strelem_to_xml(sourcelanguageNode, value[0])
136
137 - def get_rich_source(self):
138 #rsrc = xml_to_strelem(self.source_dom) 139 #logging.debug('rich source: %s' % (repr(rsrc))) 140 #from dubulib.debug.misc import print_stack_funcs 141 #print_stack_funcs() 142 return [xml_to_strelem(self.source_dom, getXMLspace(self.xmlelement, self._default_xml_space))]
143 rich_source = property(get_rich_source, set_rich_source) 144
145 - def set_rich_target(self, value, lang='xx', append=False):
146 self._rich_target = None 147 if value is None: 148 self.set_target_dom(self.createlanguageNode(lang, u'', "target")) 149 return 150 151 languageNode = self.get_target_dom() 152 if languageNode is None: 153 languageNode = self.createlanguageNode(lang, u'', "target") 154 self.set_target_dom(languageNode, append) 155 156 # Clear languageNode first 157 for i in range(len(languageNode)): 158 del languageNode[0] 159 languageNode.text = None 160 161 strelem_to_xml(languageNode, value[0])
162 ### currently giving some issues in Virtaal: self._rich_target = value 163
164 - def get_rich_target(self, lang=None):
165 """retrieves the "target" text (second entry), or the entry in the 166 specified language, if it exists""" 167 if self._rich_target is None: 168 self._rich_target = [xml_to_strelem(self.get_target_dom(lang), getXMLspace(self.xmlelement, self._default_xml_space))] 169 return self._rich_target
170 rich_target = property(get_rich_target, set_rich_target) 171
172 - def addalttrans(self, txt, origin=None, lang=None, sourcetxt=None, matchquality=None):
173 """Adds an alt-trans tag and alt-trans components to the unit. 174 175 @type txt: String 176 @param txt: Alternative translation of the source text. 177 """ 178 179 #TODO: support adding a source tag ad match quality attribute. At 180 # the source tag is needed to inject fuzzy matches from a TM. 181 if isinstance(txt, str): 182 txt = txt.decode("utf-8") 183 alttrans = etree.SubElement(self.xmlelement, self.namespaced("alt-trans")) 184 lisa.setXMLspace(alttrans, "preserve") 185 if sourcetxt: 186 if isinstance(sourcetxt, str): 187 sourcetxt = sourcetxt.decode("utf-8") 188 altsource = etree.SubElement(alttrans, self.namespaced("source")) 189 altsource.text = sourcetxt 190 alttarget = etree.SubElement(alttrans, self.namespaced("target")) 191 alttarget.text = txt 192 if matchquality: 193 alttrans.set("match-quality", matchquality) 194 if origin: 195 alttrans.set("origin", origin) 196 if lang: 197 lisa.setXMLlang(alttrans, lang)
198
199 - def getalttrans(self, origin=None):
200 """Returns <alt-trans> for the given origin as a list of units. No 201 origin means all alternatives.""" 202 translist = [] 203 for node in self.xmlelement.iterdescendants(self.namespaced("alt-trans")): 204 if self.correctorigin(node, origin): 205 # We build some mini units that keep the xmlelement. This 206 # makes it easier to delete it if it is passed back to us. 207 newunit = base.TranslationUnit(self.source) 208 209 # the source tag is optional 210 sourcenode = node.iterdescendants(self.namespaced("source")) 211 try: 212 newunit.source = lisa.getText(sourcenode.next(), getXMLspace(node, self._default_xml_space)) 213 except StopIteration: 214 pass 215 216 # must have one or more targets 217 targetnode = node.iterdescendants(self.namespaced("target")) 218 newunit.target = lisa.getText(targetnode.next(), getXMLspace(node, self._default_xml_space)) 219 #TODO: support multiple targets better 220 #TODO: support notes in alt-trans 221 newunit.xmlelement = node 222 223 translist.append(newunit) 224 return translist
225
226 - def delalttrans(self, alternative):
227 """Removes the supplied alternative from the list of alt-trans tags""" 228 self.xmlelement.remove(alternative.xmlelement)
229
230 - def addnote(self, text, origin=None, position="append"):
231 """Add a note specifically in a "note" tag""" 232 if position != "append": 233 self.removenotes(origin=origin) 234 235 if text: 236 text = text.strip() 237 if not text: 238 return 239 if isinstance(text, str): 240 text = text.decode("utf-8") 241 note = etree.SubElement(self.xmlelement, self.namespaced("note")) 242 note.text = text 243 if origin: 244 note.set("from", origin)
245
246 - def getnotelist(self, origin=None):
247 """Private method that returns the text from notes matching 'origin' or all notes.""" 248 notenodes = self.xmlelement.iterdescendants(self.namespaced("note")) 249 # TODO: consider using xpath to construct initial_list directly 250 # or to simply get the correct text from the outset (just remember to 251 # check for duplication. 252 initial_list = [lisa.getText(note, getXMLspace(self.xmlelement, self._default_xml_space)) for note in notenodes if self.correctorigin(note, origin)] 253 254 # Remove duplicate entries from list: 255 dictset = {} 256 notelist = [dictset.setdefault(note, note) for note in initial_list if note not in dictset] 257 258 return notelist
259
260 - def getnotes(self, origin=None):
261 return '\n'.join(self.getnotelist(origin=origin))
262
263 - def removenotes(self, origin="translator"):
264 """Remove all the translator notes.""" 265 notes = self.xmlelement.iterdescendants(self.namespaced("note")) 266 for note in notes: 267 if self.correctorigin(note, origin=origin): 268 self.xmlelement.remove(note)
269
270 - def adderror(self, errorname, errortext):
271 """Adds an error message to this unit.""" 272 #TODO: consider factoring out: some duplication between XLIFF and TMX 273 text = errorname 274 if errortext: 275 text += ': ' + errortext 276 self.addnote(text, origin="pofilter")
277
278 - def geterrors(self):
279 """Get all error messages.""" 280 #TODO: consider factoring out: some duplication between XLIFF and TMX 281 notelist = self.getnotelist(origin="pofilter") 282 errordict = {} 283 for note in notelist: 284 errorname, errortext = note.split(': ') 285 errordict[errorname] = errortext 286 return errordict
287
288 - def get_state_n(self):
289 targetnode = self.getlanguageNode(lang=None, index=1) 290 if targetnode is None: 291 if self.isapproved(): 292 return self.S_UNREVIEWED 293 else: 294 return self.S_UNTRANSLATED 295 296 xmlstate = targetnode.get("state", None) 297 state_n = self.statemap.get(xmlstate, self.S_UNTRANSLATED) 298 299 if state_n < self.S_NEEDS_TRANSLATION and self.target: 300 state_n = self.S_NEEDS_TRANSLATION 301 302 if self.isapproved() and state_n < self.S_UNREVIEWED: 303 state_n = self.S_UNREVIEWED 304 305 if not self.isapproved() and state_n > self.S_UNREVIEWED: 306 state_n = self.S_UNREVIEWED 307 308 return state_n
309
310 - def set_state_n(self, value):
311 if value not in self.statemap_r: 312 value = self.get_state_id(value) 313 314 targetnode = self.getlanguageNode(lang=None, index=1) 315 316 #FIXME: handle state qualifiers 317 if value == self.S_UNTRANSLATED: 318 if targetnode is not None and "state" in targetnode.attrib: 319 del targetnode.attrib["state"] 320 else: 321 if targetnode is not None: 322 xmlstate = self.statemap_r.get(value) 323 targetnode.set("state", xmlstate) 324 325 self.markapproved(value > self.S_NEEDS_REVIEW)
326
327 - def isapproved(self):
328 """States whether this unit is approved.""" 329 return self.xmlelement.get("approved") == "yes"
330
331 - def markapproved(self, value=True):
332 """Mark this unit as approved.""" 333 if value: 334 self.xmlelement.set("approved", "yes") 335 elif self.isapproved(): 336 self.xmlelement.set("approved", "no")
337
338 - def isreview(self):
339 """States whether this unit needs to be reviewed""" 340 return self.get_state_id() == self.S_NEEDS_REVIEW
341
342 - def markreviewneeded(self, needsreview=True, explanation=None):
343 """Marks the unit to indicate whether it needs review. Adds an optional explanation as a note.""" 344 state_id = self.get_state_id() 345 if needsreview and state_id != self.S_NEEDS_REVIEW: 346 self.set_state_n(self.S_NEEDS_REVIEW) 347 if explanation: 348 self.addnote(explanation, origin="translator") 349 elif not needsreview and state_id < self.S_UNREVIEWED: 350 self.set_state_n(self.S_UNREVIEWED)
351
352 - def isfuzzy(self):
353 # targetnode = self.getlanguageNode(lang=None, index=1) 354 # return not targetnode is None and \ 355 # (targetnode.get("state-qualifier") == "fuzzy-match" or \ 356 # targetnode.get("state") == "needs-review-translation") 357 return not self.isapproved() and bool(self.target)
358
359 - def markfuzzy(self, value=True):
360 state_id = self.get_state_id() 361 if value: 362 self.markapproved(False) 363 if state_id != self.S_NEEDS_TRANSLATION: 364 self.set_state_n(self.S_NEEDS_TRANSLATION) 365 else: 366 self.markapproved(True) 367 if state_id < self.S_UNREVIEWED: 368 self.set_state_n(self.S_UNREVIEWED)
369
370 - def settarget(self, text, lang='xx', append=False):
371 """Sets the target string to the given value.""" 372 super(xliffunit, self).settarget(text, lang, append) 373 if text: 374 self.marktranslated()
375 376 # This code is commented while this will almost always return false. 377 # This way pocount, etc. works well. 378 # def istranslated(self): 379 # targetnode = self.getlanguageNode(lang=None, index=1) 380 # return not targetnode is None and \ 381 # (targetnode.get("state") == "translated") 382
383 - def istranslatable(self):
384 value = self.xmlelement.get("translate") 385 if value and value.lower() == 'no': 386 return False 387 return True
388
389 - def marktranslated(self):
390 state_id = self.get_state_id() 391 if state_id < self.S_UNREVIEWED: 392 self.set_state_n(self.S_UNREVIEWED)
393
394 - def setid(self, id):
395 # sanitize id in case ID_SEPERATOR is present 396 self.xmlelement.set("id", id.replace(ID_SEPARATOR, ID_SEPARATOR_SAFE))
397
398 - def getid(self):
399 uid = u"" 400 try: 401 filename = self.xmlelement.iterancestors(self.namespaced('file')).next().get('original') 402 if filename: 403 uid = filename + ID_SEPARATOR 404 except StopIteration: 405 # unit has no proper file ancestor, probably newly created 406 pass 407 # hide the fact that we sanitize ID_SEPERATOR 408 uid += unicode(self.xmlelement.get("id") or u"").replace(ID_SEPARATOR_SAFE, ID_SEPARATOR) 409 return uid
410
411 - def addlocation(self, location):
412 self.setid(location)
413
414 - def getlocations(self):
415 id_attr = unicode(self.xmlelement.get("id") or u"") 416 # XLIFF files downloaded from PO projects in Pootle 417 # might have id equal to .source, so let's avoid 418 # that: 419 if id_attr and id_attr != self.source: 420 return [id_attr] 421 return []
422
423 - def createcontextgroup(self, name, contexts=None, purpose=None):
424 """Add the context group to the trans-unit with contexts a list with 425 (type, text) tuples describing each context.""" 426 assert contexts 427 group = etree.Element(self.namespaced("context-group")) 428 # context-group tags must appear at the start within <group> 429 # tags. Otherwise it must be appended to the end of a group 430 # of tags. 431 if self.xmlelement.tag == self.namespaced("group"): 432 self.xmlelement.insert(0, group) 433 else: 434 self.xmlelement.append(group) 435 group.set("name", name) 436 if purpose: 437 group.set("purpose", purpose) 438 for type, text in contexts: 439 if isinstance(text, str): 440 text = text.decode("utf-8") 441 context = etree.SubElement(group, self.namespaced("context")) 442 context.text = text 443 context.set("context-type", type)
444
445 - def getcontextgroups(self, name):
446 """Returns the contexts in the context groups with the specified name""" 447 groups = [] 448 grouptags = self.xmlelement.iterdescendants(self.namespaced("context-group")) 449 #TODO: conbine name in query 450 for group in grouptags: 451 if group.get("name") == name: 452 contexts = group.iterdescendants(self.namespaced("context")) 453 pairs = [] 454 for context in contexts: 455 pairs.append((context.get("context-type"), lisa.getText(context, getXMLspace(self.xmlelement, self._default_xml_space)))) 456 groups.append(pairs) #not extend 457 return groups
458
459 - def getrestype(self):
460 """returns the restype attribute in the trans-unit tag""" 461 return self.xmlelement.get("restype")
462
463 - def merge(self, otherunit, overwrite=False, comments=True, authoritative=False):
464 #TODO: consider other attributes like "approved" 465 super(xliffunit, self).merge(otherunit, overwrite, comments) 466 if self.target: 467 self.marktranslated() 468 if otherunit.isfuzzy(): 469 self.markfuzzy() 470 elif otherunit.source == self.source: 471 self.markfuzzy(False) 472 if comments: 473 self.addnote(otherunit.getnotes())
474
475 - def correctorigin(self, node, origin):
476 """Check against node tag's origin (e.g note or alt-trans)""" 477 if origin == None: 478 return True 479 elif origin in node.get("from", ""): 480 return True 481 elif origin in node.get("origin", ""): 482 return True 483 else: 484 return False
485
486 - def multistring_to_rich(cls, mstr):
487 """Override L{TranslationUnit.multistring_to_rich} which is used by the 488 C{rich_source} and C{rich_target} properties.""" 489 strings = mstr 490 if isinstance(mstr, multistring): 491 strings = mstr.strings 492 elif isinstance(mstr, basestring): 493 strings = [mstr] 494 495 return [xml_to_strelem(s) for s in strings]
496 multistring_to_rich = classmethod(multistring_to_rich) 497
498 - def rich_to_multistring(cls, elem_list):
499 """Override L{TranslationUnit.rich_to_multistring} which is used by the 500 C{rich_source} and C{rich_target} properties.""" 501 return multistring([unicode(elem) for elem in elem_list])
502 rich_to_multistring = classmethod(rich_to_multistring)
503 504
505 -class xlifffile(lisa.LISAfile):
506 """Class representing a XLIFF file store.""" 507 UnitClass = xliffunit 508 Name = _("XLIFF Translation File") 509 Mimetypes = ["application/x-xliff", "application/x-xliff+xml"] 510 Extensions = ["xlf", "xliff", "sdlxliff"] 511 rootNode = "xliff" 512 bodyNode = "body" 513 XMLskeleton = '''<?xml version="1.0" ?> 514 <xliff version='1.1' xmlns='urn:oasis:names:tc:xliff:document:1.1'> 515 <file original='NoName' source-language='en' datatype='plaintext'> 516 <body> 517 </body> 518 </file> 519 </xliff>''' 520 namespace = 'urn:oasis:names:tc:xliff:document:1.1' 521 suggestions_in_format = True 522 """xliff units have alttrans tags which can be used to store suggestions""" 523
524 - def __init__(self, *args, **kwargs):
525 self._filename = None 526 lisa.LISAfile.__init__(self, *args, **kwargs) 527 self._messagenum = 0
528
529 - def initbody(self):
530 self.namespace = self.document.getroot().nsmap.get(None, None) 531 532 if self._filename: 533 filenode = self.getfilenode(self._filename, createifmissing=True) 534 else: 535 filenode = self.document.getroot().iterchildren(self.namespaced('file')).next() 536 self.body = self.getbodynode(filenode, createifmissing=True)
537
538 - def addheader(self):
539 """Initialise the file header.""" 540 pass
541
542 - def createfilenode(self, filename, sourcelanguage=None, targetlanguage=None, datatype='plaintext'):
543 """creates a filenode with the given filename. All parameters 544 are needed for XLIFF compliance.""" 545 if sourcelanguage is None: 546 sourcelanguage = self.sourcelanguage 547 if targetlanguage is None: 548 targetlanguage = self.targetlanguage 549 550 # find the default NoName file tag and use it instead of creating a new one 551 for filenode in self.document.getroot().iterchildren(self.namespaced("file")): 552 if filenode.get("original") == "NoName": 553 filenode.set("original", filename) 554 filenode.set("source-language", sourcelanguage) 555 if targetlanguage: 556 filenode.set("target-language", targetlanguage) 557 return filenode 558 559 filenode = etree.Element(self.namespaced("file")) 560 filenode.set("original", filename) 561 filenode.set("source-language", sourcelanguage) 562 if targetlanguage: 563 filenode.set("target-language", targetlanguage) 564 filenode.set("datatype", datatype) 565 bodyNode = etree.SubElement(filenode, self.namespaced(self.bodyNode)) 566 return filenode
567
568 - def getfilename(self, filenode):
569 """returns the name of the given file""" 570 return filenode.get("original")
571
572 - def setfilename(self, filenode, filename):
573 """set the name of the given file""" 574 return filenode.set("original", filename)
575
576 - def getfilenames(self):
577 """returns all filenames in this XLIFF file""" 578 filenodes = self.document.getroot().iterchildren(self.namespaced("file")) 579 filenames = [self.getfilename(filenode) for filenode in filenodes] 580 filenames = filter(None, filenames) 581 if len(filenames) == 1 and filenames[0] == '': 582 filenames = [] 583 return filenames
584
585 - def getfilenode(self, filename, createifmissing=False):
586 """finds the filenode with the given name""" 587 filenodes = self.document.getroot().iterchildren(self.namespaced("file")) 588 for filenode in filenodes: 589 if self.getfilename(filenode) == filename: 590 return filenode 591 if createifmissing: 592 filenode = self.createfilenode(filename) 593 return filenode 594 return None
595
596 - def getids(self, filename=None):
597 if not filename: 598 return super(xlifffile, self).getids() 599 600 self.id_index = {} 601 prefix = filename + ID_SEPARATOR 602 units = (unit for unit in self.units if unit.getid().startswith(prefix)) 603 for index, unit in enumerate(units): 604 self.id_index[unit.getid()[len(prefix):]] = unit 605 return self.id_index.keys()
606
607 - def setsourcelanguage(self, language):
608 if not language: 609 return 610 filenode = self.document.getroot().iterchildren(self.namespaced('file')).next() 611 filenode.set("source-language", language)
612
613 - def getsourcelanguage(self):
614 filenode = self.document.getroot().iterchildren(self.namespaced('file')).next() 615 return filenode.get("source-language")
616 sourcelanguage = property(getsourcelanguage, setsourcelanguage) 617
618 - def settargetlanguage(self, language):
619 if not language: 620 return 621 filenode = self.document.getroot().iterchildren(self.namespaced('file')).next() 622 filenode.set("target-language", language)
623
624 - def gettargetlanguage(self):
625 filenode = self.document.getroot().iterchildren(self.namespaced('file')).next() 626 return filenode.get("target-language")
627 targetlanguage = property(gettargetlanguage, settargetlanguage) 628
629 - def getdatatype(self, filename=None):
630 """Returns the datatype of the stored file. If no filename is given, 631 the datatype of the first file is given.""" 632 if filename: 633 node = self.getfilenode(filename) 634 if not node is None: 635 return node.get("datatype") 636 else: 637 filenames = self.getfilenames() 638 if len(filenames) > 0 and filenames[0] != "NoName": 639 return self.getdatatype(filenames[0]) 640 return ""
641
642 - def getdate(self, filename=None):
643 """Returns the date attribute for the file. If no filename is given, 644 the date of the first file is given. If the date attribute is not 645 specified, None is returned.""" 646 if filename: 647 node = self.getfilenode(filename) 648 if not node is None: 649 return node.get("date") 650 else: 651 filenames = self.getfilenames() 652 if len(filenames) > 0 and filenames[0] != "NoName": 653 return self.getdate(filenames[0]) 654 return None
655
656 - def removedefaultfile(self):
657 """We want to remove the default file-tag as soon as possible if we 658 know if still present and empty.""" 659 filenodes = list(self.document.getroot().iterchildren(self.namespaced("file"))) 660 if len(filenodes) > 1: 661 for filenode in filenodes: 662 if filenode.get("original") == "NoName" and \ 663 not list(filenode.iterdescendants(self.namespaced(self.UnitClass.rootNode))): 664 self.document.getroot().remove(filenode) 665 break
666
667 - def getheadernode(self, filenode, createifmissing=False):
668 """finds the header node for the given filenode""" 669 # TODO: Deprecated? 670 headernode = filenode.iterchildren(self.namespaced("header")) 671 try: 672 return headernode.next() 673 except StopIteration: 674 pass 675 if not createifmissing: 676 return None 677 headernode = etree.SubElement(filenode, self.namespaced("header")) 678 return headernode
679
680 - def getbodynode(self, filenode, createifmissing=False):
681 """finds the body node for the given filenode""" 682 bodynode = filenode.iterchildren(self.namespaced("body")) 683 try: 684 return bodynode.next() 685 except StopIteration: 686 pass 687 if not createifmissing: 688 return None 689 bodynode = etree.SubElement(filenode, self.namespaced("body")) 690 return bodynode
691
692 - def addsourceunit(self, source, filename="NoName", createifmissing=False):
693 """adds the given trans-unit to the last used body node if the 694 filename has changed it uses the slow method instead (will 695 create the nodes required if asked). Returns success""" 696 if self._filename != filename: 697 if not self.switchfile(filename, createifmissing): 698 return None 699 unit = super(xlifffile, self).addsourceunit(source) 700 self._messagenum += 1 701 unit.setid("%d" % self._messagenum) 702 return unit
703
704 - def switchfile(self, filename, createifmissing=False):
705 """adds the given trans-unit (will create the nodes required if asked). Returns success""" 706 self._filename = filename 707 filenode = self.getfilenode(filename) 708 if filenode is None: 709 if not createifmissing: 710 return False 711 filenode = self.createfilenode(filename) 712 self.document.getroot().append(filenode) 713 714 self.body = self.getbodynode(filenode, createifmissing=createifmissing) 715 if self.body is None: 716 return False 717 self._messagenum = len(list(self.body.iterdescendants(self.namespaced("trans-unit")))) 718 #TODO: was 0 based before - consider 719 # messagenum = len(self.units) 720 #TODO: we want to number them consecutively inside a body/file tag 721 #instead of globally in the whole XLIFF file, but using len(self.units) 722 #will be much faster 723 return True
724
725 - def creategroup(self, filename="NoName", createifmissing=False, restype=None):
726 """adds a group tag into the specified file""" 727 if self._filename != filename: 728 if not self.switchfile(filename, createifmissing): 729 return None 730 group = etree.SubElement(self.body, self.namespaced("group")) 731 if restype: 732 group.set("restype", restype) 733 return group
734
735 - def __str__(self):
736 self.removedefaultfile() 737 return super(xlifffile, self).__str__()
738
739 - def parsestring(cls, storestring):
740 """Parses the string to return the correct file object""" 741 xliff = super(xlifffile, cls).parsestring(storestring) 742 if xliff.units: 743 header = xliff.units[0] 744 if ("gettext-domain-header" in (header.getrestype() or "") \ 745 or xliff.getdatatype() == "po") \ 746 and cls.__name__.lower() != "poxlifffile": 747 from translate.storage import poxliff 748 xliff = poxliff.PoXliffFile.parsestring(storestring) 749 return xliff
750 parsestring = classmethod(parsestring)
751