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