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