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