translate.storage.xliff

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