1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
75 except ImportError:
76 import simplejson as json
77
78 from translate.storage import base
79
80
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
100
103 source = property(getsource, setsource)
104
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
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
138
141
143 return [self.getid()]
144
145
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
160 return json.dumps(self._file, sort_keys=True,
161 indent=4, ensure_ascii=False).encode('utf-8')
162
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
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
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