00001 from silme.core.entity import Entity, EntityList
00002
00003
00004 class Blob(object):
00005 uri = None
00006 id = None
00007 source = None
00008
00009
00010 class L10nObject(list, Blob):
00011 process_function = None
00012 fallback = None
00013
00014 def __init__(self, id=None):
00015 """
00016 L10nObject can be initialized with optional argument: id
00017 """
00018 list.__init__(self)
00019 Blob.__init__(self)
00020 self.id = id
00021
00022 def _get_pos(self, pos):
00023 """
00024 It is an internal method for translating tuple in form of:
00025 ('before', 'entity.id') or ('after', 'entity.id')
00026 which are returned internally by our methods into a solid number.
00027
00028 If pos is a number it will just be returned as integer.
00029 """
00030 if isinstance(pos, tuple):
00031 p = self.get_entity_pos(pos[1])
00032 return p + 1 if pos[0] == 'after' else p
00033 else:
00034 return int(pos)
00035
00036 def add_at_pos(self, element, pos=None):
00037 """
00038 adds an element to L10nObject.
00039 pos - if given, allows you to decide where the element will be added
00040 pos may be in form of an integer or a tuple like ('before', 'entity.id'), ('after', 'entity.id')
00041 """
00042 if pos == None:
00043 self.append(element)
00044 else:
00045 self.insert(self._get_pos(pos), element)
00046
00047 def add_entity(self, entity, pos=None):
00048 """
00049 adds an entity to L10nObject
00050 pos - if given, allows you to decide where the entity will be added
00051 """
00052 self.add_at_pos(entity, pos)
00053 return 1
00054
00055 def get_value(self, id, fallback=None):
00056 """
00057 returns a value of an entity with given id
00058 fallback - if given overrides default locale fallback (e.g. ['de', 'fr', 'en-US'])
00059
00060 if given entity does not exist it will raise a KeyError
00061 """
00062 if fallback == None and self.fallback:
00063 fallback = self.fallback
00064 return self.get_entity(id).get_value(fallback)
00065
00066 def get_locale_codes(self):
00067 """
00068 returns list of locale codes existing in L10nObject
00069 """
00070 locales = []
00071 for entity in self.get_entities():
00072 for key in entity._value.keys():
00073 if key not in locales:
00074 locales.append(key)
00075 return locales
00076
00077 def get_entity_ids(self):
00078 """
00079 returns list of id's of entities in L10nObject
00080 """
00081 return [item.id for item in self if isinstance(item, Entity)]
00082
00083 def get_entities(self):
00084 """
00085 returns all entities from L10nObject
00086 """
00087 return [item for item in self if isinstance(item, Entity)]
00088
00089 def has_entity(self, id):
00090 """
00091 returns True if entity with given id exists or False otherwise
00092 """
00093 for item in self:
00094 if isinstance(item, Entity) and item.id == id:
00095 return True
00096 return False
00097
00098 def modify_entity(self, id, value, code=None):
00099 """
00100 modifies entity value
00101 code - if given modified the value for given locale code
00102 """
00103 for item in self:
00104 if isinstance(item, Entity) and item.id == id:
00105 item.set_value(value, code)
00106 return True
00107 raise KeyError('No such entity')
00108
00109 def get_entity(self, id):
00110 """
00111 returns an entity for a given id
00112 """
00113 for item in self:
00114 if isinstance(item, Entity) and item.id == id:
00115 return item
00116 raise KeyError('No such entity')
00117
00118 def get_entity_pos(self, id):
00119 """
00120 returns position of entity in L10nObject
00121 """
00122 for (i, item) in enumerate(self):
00123 if isinstance(item, Entity) and item.id == id:
00124 return i
00125 raise KeyError('No such entity')
00126
00127 def remove_entity(self, id):
00128 """
00129 removes entity for given id or raises KeyError
00130 """
00131 for (num, item) in enumerate(self):
00132 if isinstance(item, Entity) and item.id == id:
00133 del self[num]
00134 return True
00135 raise KeyError('No such entity')
00136
00137 def set_fallback(self, fallback):
00138 self.fallback = fallback
00139
00140 def add_comment(self, comment, pos=None):
00141 self.add_at_pos(comment, pos)
00142 return 1
00143
00144 def add_string(self, string, pos=None):
00145 self.add_at_pos(string, pos)
00146 return 1
00147
00148 def add_element(self, element, pos=None):
00149 """
00150 adds an element (string, entity or comment) to l10nObject
00151 pos - if given addes an element at given position
00152
00153 returns a number representing how many new elements have been added
00154 Usually one, but if a new string gets added and is concatanated to previous/next one
00155 the value will be 0.
00156 """
00157 if element == None:
00158 return 0
00159 t = type(element).__name__[0]
00160 if t == 's' or t == 'u': # s - str, u - unicode
00161 return self.add_string(element, pos)
00162 elif t == 'E':
00163 return self.add_entity(element, pos)
00164 elif t == 'C':
00165 return self.add_comment(element, pos)
00166 else:
00167 raise Exception('Cannot add element of type "' + type(element).__name__ +
00168 '" to L10nObject')
00169
00170 def add_elements(self, sequence, pos=None):
00171 """
00172 adds set of elements
00173 pos - if given addes the elements at given position
00174
00175 returns a number representing how many new elements have been added
00176 Usually the number will be equal the number of
00177 """
00178 it = iter(sequence)
00179 tshift = 0
00180 if not pos == None:
00181 pos = self._get_pos(pos)
00182 while True:
00183 try:
00184 shift = self.add_element(it.next(), pos=(None if pos == None else pos+tshift))
00185 tshift += shift
00186 except StopIteration:
00187 break
00188 return tshift
00189
00190 def remove_element(self, pos):
00191 """
00192 removes an element at given position from the L10nObject
00193 """
00194 del self[pos]
00195
00196 def get_element(self, position):
00197 return self[position]
00198
00199 def get_entitylist(self):
00200 """
00201 returns an ElementList representation of the L10nObject
00202 """
00203 entityList = EntityList()
00204 entityList.id = self.id
00205 entityList.fallback = self.fallback
00206 for entity in self.get_entities():
00207 entityList.add_entity(entity)
00208 return entityList
00209
00210 def process(self):
00211 """
00212 launches a process function on the L10nObject if
00213 processing method is provided
00214 """
00215 return self.process_function(self)
00216
00217 def get_locales(self, localelist):
00218 """
00219 returns a clone of L10nObject with entities only
00220 in given list of locales
00221 """
00222 l10n_object = L10nObject()
00223 l10n_object.id = self.id
00224 l10n_object.uri = self.uri
00225 l10n_object.fallback = localelist
00226 l10n_object.source = self.source
00227 for element in self:
00228 if isinstance(element, Entity):
00229 l10n_object.add_entity(element.get_locales(localelist))
00230 else:
00231 l10n_object.add_element(element)
00232 return l10n_object
00233
00234 def merge(self, l10n_object):
00235 """
00236 merges L10nObject with another L10nObject (only entities)
00237 """
00238 for entity in l10n_object.get_entities():
00239 if self.has_entity(entity.id):
00240 self.get_entity(entity.id).merge(entity)
00241 else:
00242 self.add_entity(entity)
00243 if l10n_object.fallback:
00244 for code in l10n_object.fallback:
00245 if code not in self.fallback:
00246 self.fallback.append(code)
00247
00248
00249 class Comment(L10nObject):
00250 """
00251 Comment class is a sub-class of L10nObject but cannot
00252 take Comment as an element.
00253
00254 It means that by default Comments store strings and Entities.
00255 """
00256 def __init__(self, elist=None):
00257 L10nObject.__init__(self)
00258 if elist:
00259 for i in elist:
00260 self.add_element(i)
00261
00262 def add_element(self, element, pos=None):
00263 if isinstance(element, Comment):
00264 raise Exception('Cannot add comment to comment')
00265 return L10nObject.add_element(self, element, pos)
00266
00267
00268 class L10nPackage(object):
00269 """
00270 L10nPackage is a container object that stores
00271 set of objects (L10nobject or Object) and sub-l10npackages.
00272
00273 It's easiest to think of it as a filesystem directory that
00274 can store files and nested directories.
00275 It abstracts the package from the file system, so once you load
00276 L10nPackage into memory you can serialize it, send it, save as .zip file or
00277 simply diff.
00278 """
00279 uri = None
00280
00281 def __init__(self, id=None):
00282 self.objects = {}
00283 self.packages = {}
00284 self.id = id
00285
00286 def add_object(self, object, path=None):
00287 """
00288 Adds an object to L10nPackage.
00289
00290 Optional parameter path allows to declare place
00291 inside the package where the object should be added.
00292
00293 For example l10npack.add_object(l10nobject, 'pkg1/pkg2') is similar to
00294 l10npack.get_package('pkg1').get_package('pkg2').add_object(l10nobject)
00295 with the difference that it will create missing sub packages.
00296 """
00297 if path == None or path == '':
00298 self.objects[object.id] = object
00299 else:
00300 path = path.split('/')
00301 if path[0] in self.packages:
00302 self.packages[path[0]].add_object(object, '/'.join(path[1:]))
00303 else:
00304 sub_l10n_pack = L10nPackage()
00305 sub_l10n_pack.id = path[0]
00306 self.add_package(sub_l10n_pack)
00307 sub_l10n_pack.add_object(object, '/'.join(path[1:]))
00308
00309 def add_package(self, l10npackage, path=None):
00310 """
00311 Adds a package to L10nPackage.
00312
00313 Optional parameter path allows to declare place
00314 inside the package where the subpackage should be added.
00315
00316 For example l10npack.add_package(subl10npack, 'pkg1/pkg2') is similar to
00317 l10npack.get_package('pkg1').get_package('pkg2').add_package(subl10npack)
00318 with the difference that it will create missing sub packages.
00319 """
00320 if path == None or path == '':
00321 self.packages[l10npackage.id] = l10npackage
00322 else:
00323 path = path.split('/')
00324 if path[0] in self.packages:
00325 self.packages[path[0]].add_package(l10npackage, '/'.join(path[1:]))
00326 else:
00327 sub_l10n_pack = L10nPackage()
00328 sub_l10n_pack.id = path[0]
00329 self.packages[path[0]] = sub_l10n_pack
00330 sub_l10n_pack.add_package(l10npackage,'/'.join(path[1:]))
00331
00332 def get_packages(self, names=False):
00333 """
00334 Returns a list of packages inside L10nPackage.
00335 If parameter names is set to True list of
00336 names is returned instead of objects.
00337 """
00338 if names == True:
00339 return self.packages.keys()
00340 else:
00341 return self.packages.values()
00342
00343 def get_objects(self, type='all', names=False):
00344 """
00345 Returns a list of objects inside L10nPackage.
00346 If parameter names is set to True list of
00347 names is returned instead of objects.
00348 """
00349 if type == 'all':
00350 if names == True:
00351 return self.objects.keys()
00352 else:
00353 return self.objects.values()
00354 else:
00355 l10n_objects = {}
00356 if type == 'entitylist':
00357 type = EntityList
00358 elif type == 'l10nobject':
00359 type = L10nObject
00360 elif type == 'object':
00361 type = Object
00362 for object in self.objects:
00363 if isinstance(self.objects[object], type):
00364 l10n_objects[object] = self.objects[object]
00365 if names == True:
00366 return l10n_objects.keys()
00367 else:
00368 return l10n_objects.values()
00369
00370 def get_entities(self, recursive=True):
00371 """
00372 Returns a list of all entities inside the L10nPackage
00373
00374 If optional parameter recursive is set to True it will
00375 return all packages from this package and its subpackages.
00376 """
00377 entities = []
00378 if recursive:
00379 for pack in self.packages.values():
00380 entities.extend(pack.get_entities())
00381 for i in self.objects:
00382 if isinstance(self.objects[i], L10nObject):
00383 entities.extend(self.objects[i].get_entities())
00384 elif isinstance(self.objects[i], EntityList):
00385 entities.extend(self.objects[i].values())
00386 return entities
00387
00388 def has_object(self, id):
00389 return id in self.objects
00390
00391 def has_package(self, id):
00392 return id in self.packages
00393
00394 def get_object(self, id):
00395 if id in self.objects:
00396 return self.objects[id]
00397 raise KeyError('No such object')
00398
00399 def get_package(self, id):
00400 if id in self.packages:
00401 return self.packages[id]
00402 raise KeyError('No such package')
00403
00404 def get_element(self, path):
00405 """
00406 Returns an element from inside L10nPackage
00407 by its path.
00408
00409 l10npack.get_element('pkg1/pkg2/object.po') will return
00410 the same as
00411 l10npack.get_package('pkg1').get_package('pkg2').get_object('object.po')
00412
00413 IF the path is empty the result will be None
00414 """
00415 if not path:
00416 return None
00417 elems = path.split('/')
00418 if len(elems) == 0:
00419 return None
00420
00421 if len(elems) == 2 and elems[1] == '':
00422 elems = elems[:-1]
00423
00424 if len(elems) == 1:
00425 if self.has_package(elems[0]):
00426 elem = self.get_package(elems[0])
00427 elif self.has_object(elems[0]):
00428 elem = self.get_object(elems[0])
00429 else:
00430 return None
00431 return elem
00432 else:
00433 if self.packages.has_key(elems[0]):
00434 return self.packages[elems[0]].get_element('/'.join(elems[1:]))
00435 else:
00436 return None
00437
00438 def remove_object(self, id):
00439 del self.objects[id]
00440
00441 def remove_package(self, id):
00442 del self.packages[id]
00443
00444 def get_value(self, path, entity):
00445 elem = self.get_element(path)
00446 return elem.get_value(entity)
00447
00448 def merge(self, l10n_package):
00449 for id in l10n_package.objects:
00450 object2 = l10n_package.get_object(id)
00451 if self.has_object(id):
00452 object = self.get_object(id)
00453 if type(object) is not type(object2):
00454 raise Exception('Object type mismatch! (' + id + ': ' +
00455 type(object) + ',' + type(object2) + ')')
00456 elif not isinstance(object, EntityList):
00457 self.add_object(object2)
00458 else:
00459 object.merge(object2)
00460 else:
00461 self.add_object(object2)
00462 for id in l10n_package.packages:
00463 package = l10n_package.get_package(id)
00464 if self.has_package(id):
00465 self.get_package(id).merge(package)
00466 else:
00467 self.add_package(package)
00468
00469 def get_locales(self, localelist):
00470 l10n_package = L10nPackage()
00471 l10n_package.id = self.id
00472 l10n_package.uri = self.uri
00473 for object in self.objects.values():
00474 l10n_package.add_object(object.get_locales(localelist))
00475 for package in self.packages.values():
00476 l10nPackage.add_package(package.get_locales(localelist))
00477 return l10n_package