Package translate :: Package storage :: Module jsonl10n

[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.jsonl10n

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2007,2009,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  """Class that manages JSON data files for translation
     22
     23  JSON is an acronym for JavaScript Object Notation, it is an open standard
     24  designed for human-readable data interchange.
     25
     26  JSON basic types
     27  ================
     28    - Number (integer or real)
     29    - String (double-quoted Unicode with backslash escaping)
     30    - Boolean (true or false)
     31    - Array (an ordered sequence of values, comma-separated and enclosed
     32      in square brackets)
     33    - Object (a collection of key:value pairs, comma-separated and
     34      enclosed in curly braces)
     35    - null
     36
     37  Example
     38  =======
     39
     40    {
     41         "firstName": "John",
     42         "lastName": "Smith",
     43         "age": 25,
     44         "address": {
     45             "streetAddress": "21 2nd Street",
     46             "city": "New York",
     47             "state": "NY",
     48             "postalCode": "10021"
     49         },
     50         "phoneNumber": [
     51             {
     52               "type": "home",
     53               "number": "212 555-1234"
     54             },
     55             {
     56               "type": "fax",
     57               "number": "646 555-4567"
     58             }
     59         ]
     60     }
     61
     62
     63  TODO
     64  ====
     65    - Handle \u and other escapes in Unicode
     66    - Manage data type storage and conversion. True -> "True" -> True
     67    - Sort the extracted data to the order of the JSON file
     68
     69  """
     70
     71  import os
     72  from StringIO import StringIO
     73  try:
     74      import json as json #available since Python 2.6
     75  except ImportError:
     76      import simplejson as json #API compatible with the json module
     77
     78  from translate.storage import base
     79
     80
     81 -class JsonUnit(base.TranslationUnit):
     82      """A JSON entry"""
     83
     84 -    def __init__(self, source=None, ref=None, item=None, encoding="UTF-8"):
     85          self._id = None
     86          self._item = str(os.urandom(30))
     87          if item is not None:
     88              self._item = item
     89          self._ref = {}
     90          if ref is not None:
     91              self._ref = ref
     92          if ref is None and item is None:
     93              self._ref[self._item] = ""
     94          if source:
     95              self.source = source
     96          super(JsonUnit, self).__init__(source)
     97
     98 -    def getsource(self):
     99          return self.gettarget()
    100
    101 -    def setsource(self, source):
    102          self.settarget(source)
    103      source = property(getsource, setsource)
    104
    105 -    def gettarget(self):
    106
    107          def change_type(value):
    108              if isinstance(value, bool):
    109                  return str(value)
    110              return value
    111
    112              return newvalue
    113          if isinstance(self._ref, list):
    114              return change_type(self._ref[self._item])
    115          elif isinstance(self._ref, dict):
    116              return change_type(self._ref[self._item])
    117
    118 -    def settarget(self, target):
    119
    120          def change_type(oldvalue, newvalue):
    121              if isinstance(oldvalue, bool):
    122                  newvalue = bool(newvalue)
    123              return newvalue
    124
    125          if isinstance(self._ref, list):
    126              self._ref[int(self._item)] = change_type(self._ref[int(self._item)],
    127                                                       target)
    128          elif isinstance(self._ref, dict):
    129              self._ref[self._item] = change_type(self._ref[self._item], target)
    130          else:
    131              raise ValueError("We don't know how to handle:\n"
    132                               "Type: %s\n"
    133                               "Value: %s" % (type(self._ref), target))
    134      target = property(gettarget, settarget)
    135
    136 -    def setid(self, value):
    137          self._id = value
    138
    139 -    def getid(self):
    140          return self._id
    141
    142 -    def getlocations(self):
    143          return [self.getid()]
    144
    145
    146 -class JsonFile(base.TranslationStore):
    147      """A JSON file"""
    148      UnitClass = JsonUnit
    149
    150 -    def __init__(self, inputfile=None, unitclass=UnitClass, filter=None):
    151          """construct a JSON file, optionally reading in from inputfile."""
    152          base.TranslationStore.__init__(self, unitclass=unitclass)
    153          self._filter = filter
    154          self.filename = ''
    155          self._file = u''
    156          if inputfile is not None:
    157              self.parse(inputfile)
    158
    159 -    def __str__(self):
    160          return json.dumps(self._file, sort_keys=True,
    161                            indent=4, ensure_ascii=False).encode('utf-8')
    162
    163 -    def _extract_translatables(self, data, stop=None, prev="", name_node=None,
    164                                 name_last_node=None, last_node=None):
    165          """Recursive function to extract items from the data files
    166
    167          data - is the current branch to walk down
    168          stop - is a list of leaves to extract or None to extract everything
    169          prev - is the heirchy of the tree at this iteration
    170          last - is the name of the last node
    171          last_node - the last list or dict
    172          """
    173          usable = {}
    174          if isinstance(data, dict):
    175              for k, v in data.iteritems():
    176                  usable.update(self._extract_translatables(v, stop,
    177                                                            "%s.%s" % (prev, k),
    178                                                            k, None, data))
    179          elif isinstance(data, list):
    180              for i, item in enumerate(data):
    181                  usable.update(self._extract_translatables(item, stop,
    182                                                            "%s[%s]" % (prev, i),
    183                                                            i, name_node, data))
    184          elif isinstance(data, str) or isinstance(data, unicode):
    185              if (stop is None \
    186                  or (isinstance(last_node, dict) and name_node in stop) \
    187                  or (isinstance(last_node, list) and name_last_node in stop)):
    188                  usable[prev] = (data, last_node, name_node)
    189          elif isinstance(data, bool):
    190              if (stop is None \
    191                  or (isinstance(last_node, dict) and name_node in stop) \
    192                  or (isinstance(last_node, list) and name_last_node in stop)):
    193                  usable[prev] = (str(data), last_node, name_node)
    194          elif data is None:
    195              pass
    196              #usable[prev] = None
    197          else:
    198              raise ValueError("We don't handle these values:\n"
    199                               "Type: %s\n"
    200                               "Data: %s\n"
    201                               "Previous: %s" % (type(data), data, prev))
    202          return usable
    203
    204 -    def parse(self, input):
    205          """parse the given file or file source string"""
    206          if hasattr(input, 'name'):
    207              self.filename = input.name
    208          elif not getattr(self, 'filename', ''):
    209              self.filename = ''
    210          if hasattr(input, "read"):
    211              src = input.read()
    212              input.close()
    213              input = src
    214          if isinstance(input, str):
    215              input = StringIO(input)
    216          try:
    217              self._file = json.load(input)
    218          except ValueError, e:
    219              raise base.ParseError(e.message)
    220
    221          for k, v in self._extract_translatables(self._file,
    222                                                  stop=self._filter).iteritems():
    223              data, ref, item = v
    224              unit = self.UnitClass(data, ref, item)
    225              unit.setid(k)
    226              self.addunit(unit)
    227