Package translate :: Package storage :: Module poparser

[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.poparser

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2002-2007 Zuza Software Foundation
      5  #
      6  # This file is part of translate.
      7  #
      8  # translate 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  # translate 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 translate; if not, write to the Free Software
     20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     21
     22  import re
     23
     24  """
     25  From the GNU gettext manual:
     26       WHITE-SPACE
     27       #  TRANSLATOR-COMMENTS
     28       #. AUTOMATIC-COMMENTS
     29       #| PREVIOUS MSGID                 (Gettext 0.16 - check if this is the correct position - not yet implemented)
     30       #: REFERENCE...
     31       #, FLAG...
     32       msgctxt CONTEXT                   (Gettext 0.15)
     33       msgid UNTRANSLATED-STRING
     34       msgstr TRANSLATED-STRING
     35  """
     36
     37  isspace = str.isspace
     38  find = str.find
     39  rfind = str.rfind
     40  startswith = str.startswith
     41  append = list.append
     42  decode = str.decode
     43
     44 -class ParseState(object):
     45 -    def __init__(self, input_iterator, UnitClass, encoding = None):
     46          self._input_iterator = input_iterator
     47          self.next_line = ''
     48          self.eof = False
     49          self.encoding = encoding
     50          self.read_line()
     51          self.UnitClass = UnitClass
     52
     53 -    def decode(self, string):
     54          if self.encoding is not None:
     55              return decode(string, self.encoding)
     56          else:
     57              return string
     58
     59 -    def read_line(self):
     60          current = self.next_line
     61          if self.eof:
     62              return current
     63          try:
     64              self.next_line = self._input_iterator.next()
     65              while not self.eof and isspace(self.next_line):
     66                  self.next_line = self._input_iterator.next()
     67          except StopIteration:
     68              self.next_line = ''
     69              self.eof = True
     70          return current
     71
     72 -    def new_input(self, _input):
     73          return ParseState(_input, self.UnitClass, self.encoding)
     74
     75 -def read_prevmsgid_lines(parse_state):
     76      """Read all the lines belonging starting with #|. These lines contain
     77      the previous msgid and msgctxt info. We strip away the leading '#| '
     78      and read until we stop seeing #|."""
     79      prevmsgid_lines = []
     80      next_line = parse_state.next_line
     81      while startswith(next_line, '#| ') or startswith(next_line, '| '):
     82          content = parse_state.read_line()
     83          prefix_len = content.index('| ')
     84          content = content[prefix_len+2:]
     85          append(prevmsgid_lines, content)
     86          next_line = parse_state.next_line
     87      return prevmsgid_lines
     88
     89 -def parse_prev_msgctxt(parse_state, unit):
     90      parse_message(parse_state, 'msgctxt', 7, unit.prev_msgctxt)
     91      return len(unit.prev_msgctxt) > 0
     92
     93 -def parse_prev_msgid(parse_state, unit):
     94      parse_message(parse_state, 'msgid', 5, unit.prev_msgid)
     95      return len(unit.prev_msgid) > 0
     96
     97 -def parse_prev_msgid_plural(parse_state, unit):
     98      parse_message(parse_state, 'msgid_plural', 12, unit.prev_msgid_plural)
     99      return len(unit.prev_msgid_plural) > 0
    100
    101 -def parse_comment(parse_state, unit):
    102      next_line = parse_state.next_line.lstrip()
    103      if len(next_line) > 0 and next_line[0] in ('#', '|'):
    104          next_char = next_line[1]
    105          if next_char == '.':
    106              append(unit.automaticcomments, parse_state.decode(next_line))
    107          elif next_line[0] == '|' or next_char == '|':
    108              # Read all the lines starting with #|
    109              prevmsgid_lines = read_prevmsgid_lines(parse_state)
    110              # Create a parse state object that holds these lines
    111              ps = parse_state.new_input(iter(prevmsgid_lines))
    112              # Parse the msgctxt if any
    113              parse_prev_msgctxt(ps, unit)
    114              # Parse the msgid if any
    115              parse_prev_msgid(ps, unit)
    116              # Parse the msgid_plural if any
    117              parse_prev_msgid_plural(ps, unit)
    118              return parse_state.next_line
    119          elif next_char == ':':
    120              append(unit.sourcecomments, parse_state.decode(next_line))
    121          elif next_char == ',':
    122              append(unit.typecomments, parse_state.decode(next_line))
    123          elif next_char == '~':
    124              # Special case: we refuse to parse obsoletes: they are done
    125              # elsewhere to ensure we reuse the normal unit parsing code
    126              return None
    127          else:
    128              append(unit.othercomments, parse_state.decode(next_line))
    129          return parse_state.read_line()
    130      else:
    131          return None
    132
    133 -def parse_comments(parse_state, unit):
    134      if not parse_comment(parse_state, unit):
    135          return None
    136      else:
    137          while parse_comment(parse_state, unit):
    138              pass
    139          return True
    140
    141 -def read_obsolete_lines(parse_state):
    142      """Read all the lines belonging to the current unit if obsolete."""
    143      obsolete_lines = []
    144      next_line = parse_state.next_line
    145      while startswith(next_line, '#~'):
    146          content = parse_state.read_line()[2:].lstrip()
    147          append(obsolete_lines, content)
    148          next_line = parse_state.next_line
    149          if startswith(content, 'msgstr'):
    150              # now we saw a msgstr, so we need to become more conservative to
    151              # avoid parsing into the following unit
    152              while startswith(next_line, '#~ "') or startswith(next_line, '#~ msgstr'):
    153                  content = parse_state.read_line()[3:]
    154                  append(obsolete_lines, content)
    155                  next_line = parse_state.next_line
    156              break
    157      return obsolete_lines
    158
    159 -def parse_obsolete(parse_state, unit):
    160      obsolete_lines = read_obsolete_lines(parse_state)
    161      if obsolete_lines == []:
    162          return None
    163      unit = parse_unit(parse_state.new_input(iter(obsolete_lines)), unit)
    164      if unit is not None:
    165          unit.makeobsolete()
    166      return unit
    167
    168 -def parse_quoted(parse_state, start_pos = 0):
    169      line = parse_state.next_line
    170      left = find(line, '"', start_pos)
    171      if left == start_pos or isspace(line[start_pos:left]):
    172          right = rfind(line, '"')
    173          if left != right:
    174              return parse_state.read_line()[left:right+1]
    175          else:
    176              # There is no terminating quote, so we append an extra quote, but
    177              # we also ignore the newline at the end (therefore the -1)
    178              return parse_state.read_line()[left:-1] + '"'
    179      return None
    180
    181 -def parse_msg_comment(parse_state, msg_comment_list, string):
    182      while string is not None:
    183          append(msg_comment_list, parse_state.decode(string))
    184          if find(string, '\\n') > -1:
    185              return parse_quoted(parse_state)
    186          string = parse_quoted(parse_state)
    187      return None
    188
    189 -def parse_multiple_quoted(parse_state, msg_list, msg_comment_list, first_start_pos=0):
    190      string = parse_quoted(parse_state, first_start_pos)
    191      while string is not None:
    192          if not startswith(string, '"_:'):
    193              append(msg_list, parse_state.decode(string))
    194              string = parse_quoted(parse_state)
    195          else:
    196              string = parse_msg_comment(parse_state, msg_comment_list, string)
    197
    198 -def parse_message(parse_state, start_of_string, start_of_string_len, msg_list, msg_comment_list=None):
    199      if msg_comment_list is None:
    200          msg_comment_list = []
    201      if startswith(parse_state.next_line, start_of_string):
    202          return parse_multiple_quoted(parse_state, msg_list, msg_comment_list, start_of_string_len)
    203
    204 -def parse_msgctxt(parse_state, unit):
    205      parse_message(parse_state, 'msgctxt', 7, unit.msgctxt)
    206      return len(unit.msgctxt) > 0
    207
    208 -def parse_msgid(parse_state, unit):
    209      parse_message(parse_state, 'msgid', 5, unit.msgid, unit.msgidcomments)
    210      return len(unit.msgid) > 0 or len(unit.msgidcomments) > 0
    211
    212 -def parse_msgstr(parse_state, unit):
    213      parse_message(parse_state, 'msgstr', 6, unit.msgstr)
    214      return len(unit.msgstr) > 0
    215
    216 -def parse_msgid_plural(parse_state, unit):
    217      parse_message(parse_state, 'msgid_plural', 12, unit.msgid_plural, unit.msgid_pluralcomments)
    218      return len(unit.msgid_plural) > 0 or len(unit.msgid_pluralcomments) > 0
    219
    220  MSGSTR_ARRAY_ENTRY_LEN = len('msgstr[')
    221
    222 -def add_to_dict(msgstr_dict, line, right_bracket_pos, entry):
    223      index = int(line[MSGSTR_ARRAY_ENTRY_LEN:right_bracket_pos])
    224      if index not in msgstr_dict:
    225          msgstr_dict[index] = []
    226      msgstr_dict[index].extend(entry)
    227
    228 -def get_entry(parse_state, right_bracket_pos):
    229      entry = []
    230      parse_message(parse_state, 'msgstr[', right_bracket_pos + 1, entry)
    231      return entry
    232
    233 -def parse_msgstr_array_entry(parse_state, msgstr_dict):
    234      line = parse_state.next_line
    235      right_bracket_pos = find(line, ']', MSGSTR_ARRAY_ENTRY_LEN)
    236      if right_bracket_pos >= 0:
    237          entry = get_entry(parse_state, right_bracket_pos)
    238          if len(entry) > 0:
    239              add_to_dict(msgstr_dict, line, right_bracket_pos, entry)
    240              return True
    241          else:
    242              return False
    243      else:
    244          return False
    245
    246 -def parse_msgstr_array(parse_state, unit):
    247      msgstr_dict = {}
    248      result = parse_msgstr_array_entry(parse_state, msgstr_dict)
    249      if not result: # We require at least one result
    250          return False
    251      while parse_msgstr_array_entry(parse_state, msgstr_dict):
    252          pass
    253      unit.msgstr = msgstr_dict
    254      return True
    255
    256 -def parse_plural(parse_state, unit):
    257      if parse_msgid_plural(parse_state, unit) and \
    258         (parse_msgstr_array(parse_state, unit) or parse_msgstr(parse_state, unit)):
    259          return True
    260      else:
    261          return False
    262
    263 -def parse_msg_entries(parse_state, unit):
    264      parse_msgctxt(parse_state, unit)
    265      if parse_msgid(parse_state, unit) and \
    266         (parse_msgstr(parse_state, unit) or parse_plural(parse_state, unit)):
    267          return True
    268      else:
    269          return False
    270
    271 -def parse_unit(parse_state, unit=None):
    272      unit = unit or parse_state.UnitClass()
    273      parsed_comments = parse_comments(parse_state, unit)
    274      obsolete_unit = parse_obsolete(parse_state, unit)
    275      if obsolete_unit is not None:
    276          return obsolete_unit
    277      parsed_msg_entries = parse_msg_entries(parse_state, unit)
    278      unit.infer_state()
    279      if parsed_comments or parsed_msg_entries:
    280          return unit
    281      else:
    282          return None
    283
    284 -def set_encoding(parse_state, store, unit):
    285      charset = None
    286      if isinstance(unit.msgstr, list) and len(unit.msgstr) > 0 and isinstance(unit.msgstr[0], str):
    287          charset = re.search("charset=([^\\s\\\\n]+)", "".join(unit.msgstr))
    288      if charset:
    289          encoding = charset.group(1)
    290          if encoding != 'CHARSET':
    291              store._encoding = encoding
    292          else:
    293              store._encoding = 'utf-8'
    294      else:
    295          store._encoding = 'utf-8'
    296      parse_state.encoding = store._encoding
    297
    298 -def decode_list(lst, decode):
    299      return [decode(item) for item in lst]
    300
    301 -def decode_header(unit, decode):
    302      for attr in ('msgctxt', 'msgid', 'msgid_pluralcomments',
    303                   'msgid_plural', 'msgstr', 'obsoletemsgctxt',
    304                   'obsoletemsgid', 'obsoletemsgid_pluralcomments',
    305                   'obsoletemsgid_plural', 'obsoletemsgstr',
    306                   'othercomments', 'automaticcomments', 'sourcecomments',
    307                   'typecomments', 'msgidcomments', 'obsoletemsgidcomments'):
    308          element = getattr(unit, attr)
    309          if isinstance(element, list):
    310              setattr(unit, attr, decode_list(element, decode))
    311          else:
    312              setattr(unit, attr, dict([(key, decode_list(value, decode)) for key, value in element.items()]))
    313
    314 -def parse_header(parse_state, store):
    315      first_unit = parse_unit(parse_state)
    316      if first_unit is None:
    317          return None
    318      set_encoding(parse_state, store, first_unit)
    319      decode_header(first_unit, parse_state.decode)
    320      return first_unit
    321
    322 -def parse_units(parse_state, store):
    323      unit = parse_header(parse_state, store)
    324      while unit:
    325          store.addunit(unit)
    326          unit = parse_unit(parse_state)
    327      return parse_state.eof
    328