class DiscussionItemContainer(Persistent, Implicit, Traversable): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ implements(IDiscussable, ICallableOpaqueItemEvents) # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId(self): return self.id security.declareProtected(View, 'getReply') def getReply(self, reply_id): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get(reply_id).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent(aq_inner(self)) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).indexObject() security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).notifyWorkflowCreated() security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).unindexObject() # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds(self, spec=None): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r = [] a = r.append g = self._container.get for id in self.objectIds(spec): a((id, g(id))) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # IDiscussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply(self, title, text, Creator=None, text_format='structured-text'): """ Create a reply in the proper place """ container = self._container id = int(DateTime().timeTime()) while self._container.get(str(id), None) is not None: id = id + 1 id = str(id) item = DiscussionItem(id, title=title, description=title) self._container[id] = item item = item.__of__(self) item.setFormat(text_format) item._edit(text) item.addCreator(Creator) item.setReplyTo(self._getDiscussable()) item.indexObject() item.notifyWorkflowCreated() return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply(self, reply_id): """ Remove a reply from this container """ if self._container.has_key(reply_id): reply = self._container.get(reply_id).__of__(self) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() self.deleteReply(my_reply_id) if hasattr(reply, 'unindexObject'): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies(self, content_obj): """ Test to see if there are any dicussion items """ outer = self._getDiscussable(outer=1) if content_obj == outer: return bool(len(self._container)) else: return bool(len(content_obj.talkback._getReplyResults())) security.declareProtected(View, 'replyCount') def replyCount(self, content_obj): """ How many replies do i have? """ outer = self._getDiscussable(outer=1) if content_obj == outer: return len(self._container) else: replies = content_obj.talkback.getReplies() return self._repcount(replies) security.declarePrivate('_repcount') def _repcount(self, replies): """ counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount(replies) return count security.declareProtected(View, 'getReplies') def getReplies(self): """ Return a sequence of the IDiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a(self._container.get(id).__of__(self)) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate('_getReplyParent') def _getReplyParent(self, in_reply_to): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable(outer=1) if in_reply_to is None: return outer parent = self._container[in_reply_to].__of__(aq_inner(self)) return parent.__of__(outer) security.declarePrivate('_getDiscussable') def _getDiscussable(self, outer=0): """ """ tb = outer and aq_inner(self) or self return getattr(tb, 'aq_parent', None) security.declarePrivate('_getReplyResults') def _getReplyResults(self): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable(outer=1) if discussable == outer: in_reply_to = None else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a((key, value)) result.sort(lambda a, b: cmp(a[1].creation_date, b[1].creation_date)) return [x[0] for x in result]
class NyMessageCatalog(Persistent): """Stores messages and their translations""" implements(INyTranslationCatalog) def __init__(self, id, title, languages=('en', )): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) # We suppose all zope/portal products are written in English # therefore we consider all new messages in English # Default language in Catalog is always 'en', thus it can be different # from the default language of the portal self._default_language = 'en' # self._languages[0] # Here the message translations are stored self._messages = PersistentMapping() self._po_headers = PersistentMapping() ### INyTranslationCatalog def edit_message(self, msgid, lang, translation): # language existance test **not present in Localizer**: if lang not in self.get_languages(): return # Add-by-edit functionality **not present in Localizer**: if not self._message_exists(msgid): self.gettext(msgid, lang) self._messages[msgid][lang] = translation def del_message(self, msgid): """ """ if self._messages.has_key(msgid): del self._messages[msgid] def gettext(self, msgid, lang=None, default=None): """Returns the corresponding translation of msgid in Catalog. """ msgstr = None if not isinstance(msgid, basestring): raise TypeError('Only strings can be translated.') # saving everything unicode, utf-8 elif isinstance(msgid, str): msgstr = msgid msgid = force_to_unicode(msgid) if not lang: raise ValueError("No language provided for gettext") msgid = msgid.strip() # empty message is translated as empty message, regardless of lang if not msgid: return msgid # default `default translation` is the msgid itself if default is None: default = msgid if lang not in self.get_languages(): # we don't have that lang, thus we can't translate and won't add msg return default # Add it if it's not in the dictionary if not self._message_exists(msgid): if msgstr is not None: import logging logger = logging.getLogger(__name__) logger.warn('Got str "%s" in gettext, expecting unicode' % msgstr) self._messages[msgid] = PersistentMapping() update_transaction_note() if not self._messages[msgid].has_key(self._default_language): self._messages[msgid][self._default_language] = default # translation may be blank (supposition), then-> default (usually msgid) in_catalog = self._messages[msgid].get(lang, '') return in_catalog or default def get_languages(self): """Get available languages""" return self._languages def add_language(self, lang): """Add language""" if lang not in self._languages: self._languages = self._languages + (lang, ) def del_language(self, lang): """Delete language with corresponding messages""" if lang not in self.get_languages(): return langlist = list(self._languages) langlist.pop(langlist.index(lang)) self._languages = tuple(langlist) def clear(self): """Erase all messages""" self._messages.clear() def messages(self): """ Returns a generator used for catalog entries iteration. """ for (msgid, translations_dict) in self._messages.items(): yield (msgid, translations_dict) def _message_exists(self, message): return self._messages.has_key(message) ### Dictionary-like API def __getitem__(self, key): return self._messages[key] def __delitem__(self, key): del self._messages[key]
class ElementSpec( SimpleItem ): """ Represent all the tool knows about a single metadata element. """ security = ClassSecurityInfo() # # Default values. # is_multi_valued = 0 def __init__( self, is_multi_valued ): self.is_multi_valued = is_multi_valued self.policies = PersistentMapping() self.policies[ None ] = self._makePolicy() # set default policy security.declarePrivate( '_makePolicy' ) def _makePolicy( self ): return MetadataElementPolicy( self.is_multi_valued ) security.declareProtected(View , 'isMultiValued') def isMultiValued( self ): """ Is this element multi-valued? """ return self.is_multi_valued security.declareProtected(View , 'getPolicy') def getPolicy( self, typ=None ): """ Find the policy for this element for objects of the given type. o Return a default, if none found. """ try: return self.policies[ typ ].__of__(self) except KeyError: return self.policies[ None ].__of__(self) security.declareProtected(View , 'listPolicies') def listPolicies( self ): """ Return a list of all policies for this element. """ res = [] for k, v in self.policies.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal , 'addPolicy') def addPolicy( self, typ ): """ Add a policy to this element for objects of the given type. """ if typ is None: raise MetadataError, "Can't replace default policy." if self.policies.has_key( typ ): raise MetadataError, "Existing policy for content type:" + typ self.policies[ typ ] = self._makePolicy() security.declareProtected(ManagePortal, 'removePolicy') def removePolicy( self, typ ): """ Remove the policy from this element for objects of the given type. o Do *not* remvoe the default, however. """ if typ is None: raise MetadataError, "Can't remove default policy." del self.policies[ typ ]
class MetadataSchema( SimpleItem ): """ Describe a metadata schema. """ security = ClassSecurityInfo() meta_type = 'Metadata Schema' publisher = '' def __init__( self, id, element_specs=() ): self._setId( id ) self.element_specs = PersistentMapping() for name, is_multi_valued in element_specs: self.element_specs[ name ] = ElementSpec( is_multi_valued ) # # ZMI methods # manage_options = ( ( { 'label' : 'Elements' , 'action' : 'elementPoliciesForm' } , ) + SimpleItem.manage_options ) security.declareProtected(ManagePortal, 'elementPoliciesForm') elementPoliciesForm = DTMLFile( 'metadataElementPolicies', _dtmldir ) security.declareProtected(ManagePortal, 'addElementPolicy') def addElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Add a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) spec.addPolicy( content_type ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+added.' ) security.declareProtected(ManagePortal, 'removeElementPolicy') def removeElementPolicy( self , element , content_type , REQUEST=None ): """ Remvoe a type-specific policy for one of our elements. """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) spec.removePolicy( content_type ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+removed.' ) security.declareProtected(ManagePortal, 'updateElementPolicy') def updateElementPolicy( self , element , content_type , is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary , REQUEST=None ): """ Update a policy for one of our elements o 'content_type' will be '<default>' when we edit the default. """ if content_type == '<default>': content_type = None spec = self.getElementSpec( element ) policy = spec.getPolicy( content_type ) policy.edit( is_required , supply_default , default_value , enforce_vocabulary , allowed_vocabulary ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/elementPoliciesForm' + '?element=' + element + '&manage_tabs_message=Policy+updated.' ) # # Element spec manipulation. # security.declareProtected(ManagePortal, 'listElementSpecs') def listElementSpecs( self ): """ Return a list of ElementSpecs representing the elements we manage. """ res = [] for k, v in self.element_specs.items(): res.append((k, v.__of__(self))) return res security.declareProtected(ManagePortal, 'getElementSpec') def getElementSpec( self, element ): """ Return an ElementSpec for the given 'element'. """ return self.element_specs[ element ].__of__( self ) security.declareProtected(ManagePortal, 'addElementSpec') def addElementSpec( self, element, is_multi_valued, REQUEST=None ): """ Add 'element' to our list of managed elements. """ # Don't replace. if self.element_specs.has_key( element ): return self.element_specs[ element ] = ElementSpec( is_multi_valued ) if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+added.' ) security.declareProtected(ManagePortal, 'removeElementSpec') def removeElementSpec( self, element, REQUEST=None ): """ Remove 'element' from our list of managed elements. """ del self.element_specs[ element ] if REQUEST is not None: REQUEST[ 'RESPONSE' ].redirect( self.absolute_url() + '/propertiesForm' + '?manage_tabs_message=Element+' + element + '+removed.' ) security.declareProtected(ManagePortal, 'listPolicies') def listPolicies( self, typ=None ): """ Show all policies for a given content type o If 'typ' is none, return the list of default policies. """ result = [] for element, spec in self.listElementSpecs(): result.append( ( element, spec.getPolicy( typ ) ) ) return result
class DiscussionItemContainer( Persistent, Implicit, Traversable ): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ implements(IDiscussable, ICallableOpaqueItemEvents) # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId( self ): return self.id security.declareProtected(View, 'getReply') def getReply( self, reply_id ): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get( reply_id ).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent( aq_inner( self ) ) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).indexObject() security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).notifyWorkflowCreated() security.declarePrivate( 'manage_beforeDelete' ) def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).unindexObject() # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds( self, spec=None ): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r=[] a=r.append g=self._container.get for id in self.objectIds(spec): a( (id, g( id ) ) ) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # IDiscussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply( self, title, text, Creator=None, text_format='structured-text' ): """ Create a reply in the proper place """ container = self._container id = int(DateTime().timeTime()) while self._container.get( str(id), None ) is not None: id = id + 1 id = str( id ) item = DiscussionItem( id, title=title, description=title ) self._container[id] = item item = item.__of__(self) item.setFormat(text_format) item._edit(text) item.addCreator(Creator) item.setReplyTo(self._getDiscussable()) item.indexObject() item.notifyWorkflowCreated() return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply( self, reply_id ): """ Remove a reply from this container """ if self._container.has_key( reply_id ): reply = self._container.get( reply_id ).__of__( self ) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() self.deleteReply(my_reply_id) if hasattr( reply, 'unindexObject' ): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies( self, content_obj ): """ Test to see if there are any dicussion items """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return bool( len(self._container) ) else: return bool( len( content_obj.talkback._getReplyResults() ) ) security.declareProtected(View, 'replyCount') def replyCount( self, content_obj ): """ How many replies do i have? """ outer = self._getDiscussable( outer=1 ) if content_obj == outer: return len( self._container ) else: replies = content_obj.talkback.getReplies() return self._repcount( replies ) security.declarePrivate('_repcount') def _repcount( self, replies ): """ counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount( replies ) return count security.declareProtected(View, 'getReplies') def getReplies( self ): """ Return a sequence of the IDiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a( self._container.get( id ).__of__( self ) ) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate( '_getReplyParent' ) def _getReplyParent( self, in_reply_to ): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable( outer=1 ) if in_reply_to is None: return outer parent = self._container[ in_reply_to ].__of__( aq_inner( self ) ) return parent.__of__( outer ) security.declarePrivate( '_getDiscussable' ) def _getDiscussable( self, outer=0 ): """ """ tb = outer and aq_inner( self ) or self return getattr( tb, 'aq_parent', None ) security.declarePrivate( '_getReplyResults' ) def _getReplyResults( self ): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable( outer=1 ) if discussable == outer: in_reply_to = None else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a( ( key, value ) ) result.sort( lambda a, b: cmp(a[1].creation_date, b[1].creation_date) ) return [ x[0] for x in result ]
class WorklistDefinition(SimpleItem): """Worklist definiton""" meta_type = 'Worklist' security = ClassSecurityInfo() security.declareObjectProtected(ManagePortal) description = '' var_matches = None # Compared with catalog when set. actbox_name = '' actbox_url = '' actbox_icon = '' actbox_category = 'global' guard = None manage_options = ( {'label': 'Properties', 'action': 'manage_properties'}, ) def __init__(self, id): self.id = id def getGuard(self): if self.guard is not None: return self.guard else: return Guard().__of__(self) # Create a temporary guard. def getGuardSummary(self): res = None if self.guard is not None: res = self.guard.getSummary() return res def getWorkflow(self): return aq_parent(aq_inner(aq_parent(aq_inner(self)))) def getAvailableCatalogVars(self): res = [] res.append(self.getWorkflow().state_var) for id, vdef in self.getWorkflow().variables.items(): if vdef.for_catalog: res.append(id) res.sort() return res def getVarMatchKeys(self): if self.var_matches: return self.var_matches.keys() else: return [] def getVarMatch(self, id): if self.var_matches: matches = self.var_matches.get(id, ()) if not isinstance(matches, (tuple, Expression)): # Old version, convert it. matches = (matches,) self.var_matches[id] = matches return matches else: return () def getVarMatchText(self, id): values = self.getVarMatch(id) if isinstance(values, Expression): return values.text return '; '.join(values) _properties_form = DTMLFile('worklist_properties', _dtmldir) def manage_properties(self, REQUEST, manage_tabs_message=None): ''' ''' return self._properties_form(REQUEST, management_view='Properties', manage_tabs_message=manage_tabs_message, ) def setProperties(self, description, actbox_name='', actbox_url='', actbox_category='global', actbox_icon='', props=None, REQUEST=None): ''' ''' if props is None: props = REQUEST self.description = str(description) for key in self.getAvailableCatalogVars(): # Populate var_matches. fieldname = 'var_match_%s' % key v = props.get(fieldname, '') if v: if not self.var_matches: self.var_matches = PersistentMapping() if tales_re.match(v).group(1): # Found a TALES prefix self.var_matches[key] = Expression(v) else: # Falling back to formatted string v = [ var.strip() for var in v.split(';') ] self.var_matches[key] = tuple(v) else: if self.var_matches and self.var_matches.has_key(key): del self.var_matches[key] self.actbox_name = str(actbox_name) self.actbox_url = str(actbox_url) self.actbox_category = str(actbox_category) self.actbox_icon = str(actbox_icon) g = Guard() if g.changeFromProperties(props or REQUEST): self.guard = g else: self.guard = None if REQUEST is not None: return self.manage_properties(REQUEST, 'Properties changed.') def search(self, info=None, **kw): """ Perform the search corresponding to this worklist Returns sequence of ZCatalog brains - info is a mapping for resolving formatted string variable references - additional keyword/value pairs may be used to restrict the query """ if not self.var_matches: return if info is None: info = {} catalog = getToolByName(self, 'portal_catalog') criteria = {} for key, values in self.var_matches.items(): if isinstance(values, Expression): wf = self.getWorkflow() portal = wf._getPortalRoot() context = createExprContext(StateChangeInfo(portal, wf)) criteria[key] = values(context) else: criteria[key] = [x % info for x in values] criteria.update(kw) return catalog.searchResults(**criteria)
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() POLICY_ADD_FALSE = 0 POLICY_ADD_TRUE = 1 POLICY_ADD_LOG = 2 def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title self.policy = self.POLICY_ADD_TRUE # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) # BBB support str in mapping by converting to unicode for # backward compatibility. if mapping: mapping = dict([to_unicode(k), to_unicode(v)] for k, v in mapping.iteritems()) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. return bool(self.get_message_key(message)) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=None, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, basestring): raise TypeError, 'only strings can be translated.' if default is None: default = message message = message.strip() # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or to_unicode(message) # Add it if it's not in the dictionary if add is None: add = getattr(self, 'policy', self.POLICY_ADD_TRUE) if add != self.POLICY_ADD_FALSE and not self._messages.has_key(message) and message: if add == self.POLICY_ADD_LOG: LOG('New entry added to message catalog %s :' % self.id, INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1])))) self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title self.policy = int(policy) if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_policies') def get_policies(self): """ """ if not hasattr(self, 'policy'): self.policy = self.POLICY_ADD_TRUE policies = [ [self.POLICY_ADD_FALSE, "Never add new entries automatically"], [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"], [self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace"], ] return policies security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. # Convert keys to unicode for proper sorting. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[to_unicode(k, encoding=charset)] = u"" else: filename = '%s.po' % x for k, v in self._messages.items(): k = to_unicode(k, encoding=charset) d[k] = to_unicode(v.get(x, ""), encoding=charset) # Generate the file def backslashescape(x): x = to_str(x) quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) return '\n'.join(r) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = polib.pofile(data) encoding = to_str(po.encoding) for entry in po: msgid = to_unicode(entry.msgid, encoding=encoding) if msgid: msgstr = to_unicode(entry.msgstr or '', encoding=encoding) translation_map = messages.get(msgid) if translation_map is None: # convert old non-unicode translations if they exist: translation_map = messages.pop(self.get_message_key(msgid), None) if translation_map is None: translation_map = PersistentMapping() messages[msgid] = translation_map translation_map[lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=encoding) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=1, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, (str, unicode)): raise TypeError, 'only strings can be translated.' message = message.strip() if default is None: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[k] = "" else: filename = '%s.po' % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) r2 = [] for x in r: if isinstance(x, unicode): r2.append(x.encode(charset)) else: r2.append(x) return '\n'.join(r2) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = itools.gettext.POFile(string=data) for msgid in po.get_msgids(): # TODO Keep the context if any _context, msgid = msgid if msgid: msgstr = po.get_msgstr(msgid) or '' if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) security.declareProtected('Manage messages', 'tmx_export') def tmx_export(self, REQUEST, RESPONSE=None): """Exports the content of the message catalog to a TMX file """ src_lang = self._default_language # Get the header info header = self.get_po_header(src_lang) charset = header['charset'] # Init the TMX handler tmx = TMXFile() tmx.header['creationtool'] = u'Localizer' tmx.header['creationtoolversion'] = u'1.x' tmx.header['datatype'] = u'plaintext' tmx.header['segtype'] = u'paragraph' tmx.header['adminlang'] = src_lang tmx.header['srclang'] = src_lang tmx.header['o-encoding'] = u'%s' % charset.lower() # handle messages for msgkey, transunit in self._messages.items(): unit = TMXUnit({}) for lang in transunit.keys(): if lang != 'note': sentence = Sentence({'lang': lang}) sentence.text = transunit[lang] unit.msgstr[lang] = sentence if src_lang not in transunit.keys(): sentence = Sentence({'lang': src_lang}) sentence.text = msgkey unit.msgstr[src_lang] = sentence if transunit.has_key('note'): note = TMXNote(transunit.get('note')) unit.notes.append(note) tmx.messages[msgkey] = unit if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s.tmx"' % self.id) return tmx.to_str() security.declareProtected('Manage messages', 'tmx_import') def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """Imports a TMX level 1 file. """ try: data = file.read() tmx = TMXFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = tmx.get_srclang() for id, msg in tmx.messages.items(): if not self._messages.has_key(id) and howmuch == 'existing': continue msg.msgstr.pop(self._default_language) if not self._messages.has_key(id): self._messages[id] = {} for lang in msg.msgstr.keys(): # normalize the languageTag and extract the core (core, local) = LanguageTag.decode(lang) lang = LanguageTag.encode((core, local)) if lang not in self._languages: self._languages += (lang,) if msg.msgstr[lang].text: self._messages[id][lang] = msg.msgstr[lang].text if core != lang and core != self._default_language: if core not in self._languages: self._languages += (core,) if not msg.msgstr.has_key(core): self._messages[id][core] = msg.msgstr[lang].text if msg.notes: ns = [m.text for m in msg.notes] self._messages[id]['note'] = u' '.join(ns) num_notes += 1 num_trans += 1 if REQUEST is not None: message = _(u'Imported %d messages and %d notes') return MessageDialog( title = _(u'Messages imported'), message = message % (num_trans, num_notes), action = 'manage_messages') ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected('Manage messages', 'xliff_export') def xliff_export(self, dst_lang, export_all=1, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog to an XLIFF file """ from DateTime import DateTime src_lang = self._default_language export_all = int(export_all) # Init the XLIFF handler xliff = XLFFile() # Add the translation units original = '/%s' % self.absolute_url(1) for msgkey, transunit in self._messages.items(): target = transunit.get(dst_lang, '') # If 'export_all' is true export all messages, otherwise export # only untranslated messages if export_all or not target: unit = xliff.add_unit(original, msgkey, None) unit.attributes['id'] = md5text(msgkey) if target: unit.target = target # Add note note = transunit.get('note') if note: unit.notes.append(XLFNote(note)) # build the data-stucture for the File tag file = xliff.files[original] attributes = file.attributes attributes['original'] = original attributes['product-name'] = u'Localizer' attributes['product-version'] = u'1.1.x' attributes['data-type'] = u'plaintext' attributes['source-language'] = src_lang attributes['target-language'] = dst_lang attributes['date'] = DateTime().HTML4() # Serialize xliff = xliff.to_str() # Set response headers RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8') filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang) RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s"' % filename) # Ok return xliff security.declareProtected('Manage messages', 'xliff_import') def xliff_import(self, howmuch, file, REQUEST=None): """XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ try: data = file.read() xliff = XLFFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 (file_ids, sources, targets) = xliff.get_languages() if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = sources[0] # update languages if len(sources) > 1 or sources[0] != self._default_language: return MessageDialog(title = 'Language error', message = _('incompatible language sources') , action = 'manage_Import_form',) for lang in targets: if lang != self._default_language and lang not in self._languages: self._languages += (lang,) # get messages for file in xliff.files: cur_target = file.attributes.get('target-language', '') for msg in file.body.keys(): if not self._messages.has_key(msg) and howmuch == 'existing': pass else: if not self._messages.has_key(msg): self._messages[msg] = {} if cur_target and file.body[msg].target: self._messages[msg][cur_target] = file.body[msg].target num_trans += 1 if file.body[msg].notes: ns = [n.text for n in file.body[msg].notes] comment = ' '.join(ns) self._messages[msg]['note'] = comment num_notes += 1 if REQUEST is not None: return MessageDialog( title = _(u'Messages imported'), message = (_(u'Imported %d messages and %d notes to %s') % \ (num_trans, num_notes, ' '.join(targets))), action = 'manage_messages')
class HBTreeFolder2Base (Persistent): """Base for BTree-based folders. """ security = ClassSecurityInfo() manage_options=( ({'label':'Contents', 'action':'manage_main',}, ) + Folder.manage_options[1:] ) security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('contents', globals()) _htree = None # OOBTree: { id -> object } _count = None # A BTrees.Length _v_nextid = 0 # The integer component of the next generated ID title = '' _tree_list = None def __init__(self, id=None): if id is not None: self.id = id self._initBTrees() def _initBTrees(self): self._htree = OOBTree() self._count = Length() self._tree_list = PersistentMapping() def _populateFromFolder(self, source): """Fill this folder with the contents of another folder. """ for name in source.objectIds(): value = source._getOb(name, None) if value is not None: self._setOb(name, aq_base(value)) security.declareProtected(view_management_screens, 'manage_fixCount') def manage_fixCount(self): """Calls self._fixCount() and reports the result as text. """ old, new = self._fixCount() path = '/'.join(self.getPhysicalPath()) if old == new: return "No count mismatch detected in HBTreeFolder2 at %s." % path else: return ("Fixed count mismatch in HBTreeFolder2 at %s. " "Count was %d; corrected to %d" % (path, old, new)) def _fixCount(self): """Checks if the value of self._count disagrees with len(self.objectIds()). If so, corrects self._count. Returns the old and new count values. If old==new, no correction was performed. """ old = self._count() new = len(self.objectIds()) if old != new: self._count.set(new) return old, new security.declareProtected(view_management_screens, 'manage_cleanup') def manage_cleanup(self): """Calls self._cleanup() and reports the result as text. """ v = self._cleanup() path = '/'.join(self.getPhysicalPath()) if v: return "No damage detected in HBTreeFolder2 at %s." % path else: return ("Fixed HBTreeFolder2 at %s. " "See the log for more details." % path) def _cleanup(self): """Cleans up errors in the BTrees. Certain ZODB bugs have caused BTrees to become slightly insane. Fortunately, there is a way to clean up damaged BTrees that always seems to work: make a new BTree containing the items() of the old one. Returns 1 if no damage was detected, or 0 if damage was detected and fixed. """ def hCheck(htree): """ Recursively check the btree """ check(htree) for key in htree.keys(): if not htree.has_key(key): raise AssertionError( "Missing value for key: %s" % repr(key)) else: ob = htree[key] if isinstance(ob, OOBTree): hCheck(ob) return 1 from BTrees.check import check path = '/'.join(self.getPhysicalPath()) try: return hCheck(self._htree) except AssertionError: LOG('HBTreeFolder2', WARNING, 'Detected damage to %s. Fixing now.' % path, error=sys.exc_info()) try: self._htree = OOBTree(self._htree) # XXX hFix needed except: LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path, error=sys.exc_info()) raise else: LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path) return 0 def hashId(self, id): """Return a tuple of ids """ # XXX: why tolerate non-string ids ? id_list = str(id).split(H_SEPARATOR) # We use '-' as the separator by default if len(id_list) > 1: return tuple(id_list) else: return [id,] # try: # We then try int hashing # id_int = int(id) # except ValueError: # return id_list # result = [] # while id_int: # result.append(id_int % MAX_OBJECT_PER_LEVEL) # id_int = id_int / MAX_OBJECT_PER_LEVEL # result.reverse() # return tuple(result) def _getOb(self, id, default=_marker): """ Return the named object from the folder. """ htree = self._htree ob = htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if default is _marker: ob = ob[sub_id] else: ob = ob.get(sub_id, _marker) if ob is _marker: return default if default is _marker: ob = ob[id] else: ob = ob.get(id, _marker) if ob is _marker: return default return ob.__of__(self) def _setOb(self, id, object): """Store the named object in the folder. """ htree = self._htree id_list = self.hashId(id) for idx in xrange(len(id_list) - 1): sub_id = id_list[idx] if not htree.has_key(sub_id): # Create a new level htree[sub_id] = OOBTree() if isinstance(sub_id, (int, long)): tree_id = 0 for id in id_list[:idx+1]: tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL else: tree_id = H_SEPARATOR.join(id_list[:idx+1]) # Index newly created level self._tree_list[tree_id] = None htree = htree[sub_id] if len(id_list) == 1 and not htree.has_key(None): self._tree_list[None] = None # set object in subtree ob_id = id_list[-1] if htree.has_key(id): raise KeyError('There is already an item named "%s".' % id) htree[id] = object self._count.change(1) def _delOb(self, id): """Remove the named object from the folder. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: htree = htree[sub_id] del htree[id] self._count.change(-1) security.declareProtected(view_management_screens, 'getBatchObjectListing') def getBatchObjectListing(self, REQUEST=None): """Return a structure for a page template to show the list of objects. """ if REQUEST is None: REQUEST = {} pref_rows = int(REQUEST.get('dtpref_rows', 20)) b_start = int(REQUEST.get('b_start', 1)) b_count = int(REQUEST.get('b_count', 1000)) b_end = b_start + b_count - 1 url = self.absolute_url() + '/manage_main' count = self.objectCount() if b_end < count: next_url = url + '?b_start=%d' % (b_start + b_count) else: b_end = count next_url = '' if b_start > 1: prev_url = url + '?b_start=%d' % max(b_start - b_count, 1) else: prev_url = '' formatted = [listtext0 % pref_rows] for optID in islice(self.objectIds(), b_start - 1, b_end): optID = escape(optID) formatted.append(listtext1 % (escape(optID, quote=1), optID)) formatted.append(listtext2) return {'b_start': b_start, 'b_end': b_end, 'prev_batch_url': prev_url, 'next_batch_url': next_url, 'formatted_list': ''.join(formatted)} security.declareProtected(view_management_screens, 'manage_object_workspace') def manage_object_workspace(self, ids=(), REQUEST=None): '''Redirects to the workspace of the first object in the list.''' if ids and REQUEST is not None: REQUEST.RESPONSE.redirect( '%s/%s/manage_workspace' % ( self.absolute_url(), quote(ids[0]))) else: return self.manage_main(self, REQUEST) security.declareProtected(access_contents_information, 'tpValues') def tpValues(self): """Ensures the items don't show up in the left pane. """ return () security.declareProtected(access_contents_information, 'objectCount') def objectCount(self): """Returns the number of items in the folder.""" return self._count() security.declareProtected(access_contents_information, 'has_key') def has_key(self, id): """Indicates whether the folder has an item by ID. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if not isinstance(htree, OOBTree): return 0 if not htree.has_key(sub_id): return 0 htree = htree[sub_id] if not htree.has_key(id): return 0 return 1 # Work around for the performance regression introduced in Zope 2.12.23. # Otherwise, we use superclass' __contains__ implementation, which uses # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key. __contains__ = has_key def _htree_iteritems(self, min=None): # BUG: Due to bad design of HBTreeFolder2, buckets other than the root # one must not contain both buckets & leafs. Otherwise, this method # fails. h = self._htree recurse_stack = [] try: for sub_id in min and self.hashId(min) or ('',): if recurse_stack: i.next() if type(h) is not OOBTree: break id += H_SEPARATOR + sub_id if type(h.itervalues().next()) is not OOBTree: sub_id = id else: id = sub_id i = h.iteritems(sub_id) recurse_stack.append(i) h = h[sub_id] except (KeyError, StopIteration): pass while recurse_stack: i = recurse_stack.pop() try: while 1: id, h = i.next() if type(h) is OOBTree: recurse_stack.append(i) i = h.iteritems() else: yield id, h except StopIteration: pass security.declareProtected(access_contents_information, 'treeIds') def treeIds(self, base_id=None): """ Return a list of subtree ids """ tree = self._getTree(base_id=base_id) return [k for k, v in self._htree.items() if isinstance(v, OOBTree)] def _getTree(self, base_id): """ Return the tree wich has the base_id """ htree = self._htree id_list = self.hashId(base_id) for sub_id in id_list: if not isinstance(htree, OOBTree): return None if not htree.has_key(sub_id): raise IndexError, base_id htree = htree[sub_id] return htree def _getTreeIdList(self, htree=None): """ recursively build a list of btree ids """ if htree is None: htree = self._htree btree_list = [] else: btree_list = [] for obj_id in htree.keys(): obj = htree[obj_id] if isinstance(obj, OOBTree): btree_list.extend(["%s-%s"%(obj_id, x) for x in self._getTreeIdList(htree=obj)]) btree_list.append(obj_id) return btree_list security.declareProtected(access_contents_information, 'getTreeIdList') def getTreeIdList(self, htree=None): """ Return list of all tree ids """ if self._tree_list is None or len(self._tree_list.keys()) == 0: tree_list = self._getTreeIdList(htree=htree) self._tree_list = PersistentMapping() for tree in tree_list: self._tree_list[tree] = None return sorted(self._tree_list.keys()) def _checkObjectId(self, ids): """ test id is not in btree id list """ base_id, obj_id = ids if base_id is not None: obj_id = "%s%s%s" %(base_id, H_SEPARATOR, obj_id) return not self._tree_list.has_key(obj_id) security.declareProtected(access_contents_information, 'objectValues') def objectValues(self, base_id=_marker): return HBTreeObjectValues(self, base_id) security.declareProtected(access_contents_information, 'objectIds') def objectIds(self, base_id=_marker): return HBTreeObjectIds(self, base_id) security.declareProtected(access_contents_information, 'objectItems') def objectItems(self, base_id=_marker): # Returns a list of (id, subobject) tuples of the current object. # If 'spec' is specified, returns only objects whose meta_type match # 'spec' return HBTreeObjectItems(self, base_id) # superValues() looks for the _objects attribute, but the implementation # would be inefficient, so superValues() support is disabled. _objects = () security.declareProtected(access_contents_information, 'objectIds_d') def objectIds_d(self, t=None): return dict.fromkeys(self.objectIds(t), 1) def _checkId(self, id, allow_dup=0): if not allow_dup and self.has_key(id): raise BadRequestException, ('The id "%s" is invalid--' 'it is already in use.' % id) def _setObject(self, id, object, roles=None, user=None, set_owner=1): v=self._checkId(id) if v is not None: id=v # If an object by the given id already exists, remove it. if self.has_key(id): self._delObject(id) self._setOb(id, object) object = self._getOb(id) if set_owner: object.manage_fixupOwnershipAfterAdd() # Try to give user the local role "Owner", but only if # no local roles have been set on the object yet. if hasattr(object, '__ac_local_roles__'): if object.__ac_local_roles__ is None: user=getSecurityManager().getUser() if user is not None: userid=user.getId() if userid is not None: object.manage_setLocalRoles(userid, ['Owner']) object.manage_afterAdd(object, self) return id def _delObject(self, id, dp=1): object = self._getOb(id) try: object.manage_beforeDelete(object, self) except BeforeDeleteException, ob: raise except ConflictError: raise
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() POLICY_ADD_FALSE = 0 POLICY_ADD_TRUE = 1 POLICY_ADD_LOG = 2 def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title self.policy = self.POLICY_ADD_TRUE # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) # BBB support str in mapping by converting to unicode for # backward compatibility. if mapping: mapping = dict([to_unicode(k), to_unicode(v)] for k, v in mapping.iteritems()) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. return bool(self.get_message_key(message)) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=None, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, basestring): raise TypeError('only strings can be translated, not: %r' % (message, )) if default is None: default = message message = message.strip() # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or to_unicode(message) # Add it if it's not in the dictionary if add is None: add = getattr(self, 'policy', self.POLICY_ADD_TRUE) if add != self.POLICY_ADD_FALSE and not self._messages.has_key( message) and message: if add == self.POLICY_ADD_LOG: LOG( 'New entry added to message catalog %s :' % self.id, INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1])))) self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url(REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url }) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title self.policy = int(policy) if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = { 'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset } self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_policies') def get_policies(self): """ """ if not hasattr(self, 'policy'): self.policy = self.POLICY_ADD_TRUE policies = [ [self.POLICY_ADD_FALSE, "Never add new entries automatically"], [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"], [ self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace" ], ] return policies security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = [ 'msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', '' ] # Get the messages, and perhaps its translations. # Convert keys to unicode for proper sorting. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[to_unicode(k, encoding=charset)] = u"" else: filename = '%s.po' % x for k, v in self._messages.items(): k = to_unicode(k, encoding=charset) d[k] = to_unicode(v.get(x, ""), encoding=charset) # Generate the file def backslashescape(x): x = to_str(x) quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type', 'application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) return '\n'.join(r) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = polib.pofile(data) encoding = to_str(po.encoding) for entry in po: msgid = to_unicode(entry.msgid, encoding=encoding) if msgid: msgstr = to_unicode(entry.msgstr or '', encoding=encoding) translation_map = messages.get(msgid) if translation_map is None: # convert old non-unicode translations if they exist: translation_map = messages.pop(self.get_message_key(msgid), None) if translation_map is None: translation_map = PersistentMapping() messages[msgid] = translation_map translation_map[lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=encoding) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) ####################################################################### # Backwards compatibility (XXX) ####################################################################### security.declarePublic('hasmsg') hasmsg = message_exists security.declarePublic('hasLS') hasLS = message_exists # CMFLocalizer uses it
class NyMessageCatalog(Persistent): """Stores messages and their translations""" implements(INyTranslationCatalog) def __init__(self, id, title, languages=('en', )): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) # We suppose all zope/portal products are written in English # therefore we consider all new messages in English # Default language in Catalog is always 'en', thus it can be different # from the default language of the portal self._default_language = 'en' # self._languages[0] # Here the message translations are stored self._messages = PersistentMapping() self._po_headers = PersistentMapping() ### INyTranslationCatalog def edit_message(self, msgid, lang, translation): # language existance test **not present in Localizer**: if lang not in self.get_languages(): return # Add-by-edit functionality **not present in Localizer**: if not self._message_exists(msgid): self.gettext(msgid, lang) self._messages[msgid][lang] = translation def del_message(self, msgid): """ """ if self._messages.has_key(msgid): del self._messages[msgid] def gettext(self, msgid, lang=None, default=None): """Returns the corresponding translation of msgid in Catalog. """ msgstr = None if not isinstance(msgid, basestring): raise TypeError('Only strings can be translated.') # saving everything unicode, utf-8 elif isinstance(msgid, str): msgstr = msgid msgid = force_to_unicode(msgid) if not lang: raise ValueError("No language provided for gettext") msgid = msgid.strip() # empty message is translated as empty message, regardless of lang if not msgid: return msgid # default `default translation` is the msgid itself if default is None: default = msgid if lang not in self.get_languages(): # we don't have that lang, thus we can't translate and won't add msg return default # Add it if it's not in the dictionary if not self._message_exists(msgid): if msgstr is not None: import logging logger = logging.getLogger(__name__) logger.warn('Got str "%s" in gettext, expecting unicode' % msgstr) self._messages[msgid] = PersistentMapping() update_transaction_note() if not self._messages[msgid].has_key(self._default_language): self._messages[msgid][self._default_language] = default # translation may be blank (supposition), then-> default (usually msgid) in_catalog = self._messages[msgid].get(lang, '') return in_catalog or default def get_languages(self): """Get available languages""" return self._languages def add_language(self, lang): """Add language""" if lang not in self._languages: self._languages = self._languages + (lang, ) def del_language(self, lang): """Delete language with corresponding messages""" if lang not in self.get_languages(): return langlist = list(self._languages) langlist.pop(langlist.index(lang)) self._languages = tuple(langlist) def clear(self): """Erase all messages""" self._messages.clear() def messages(self): """ Returns a generator used for catalog entries iteration. """ for (msgid, translations_dict) in self._messages.items(): yield (msgid, translations_dict) def _message_exists(self, message): return self._messages.has_key(message) ### Dictionary-like API def __getitem__(self, key): return self._messages[key] def __delitem__(self, key): del self._messages[key]
class DuplicatesCriteriaManager(Implicit): """ An object that contains : _criteria {bib_type : [criteria_name]}: contains all possible criteria (meta-data) for each bibliography type duplicates_criteria {bib_type : [criteria] } : contains criteria to be checked for duplication while importing for each bibliography type _nonmeta_criteria = ( 'reference_type', ) : contains criteria not comprised in meta_data _criteria_names = {'publication_authors' : 'authors'} : contains mappings for meta_data that need a special treatement like 'authors' _ignored_criteria = (critrias) : contains the names of same meta_data like 'id' or 'allowDiscussion' to be filtered IMPORTANT : only 'authors','reference_type', 'publication_year', 'title' are fully functional for now """ _nonmeta_criteria = ('reference_type', ) _default_duplicates_criteria = ( #FIXME - we may need a default value for these 'authors', 'reference_type', 'publication_year', 'title', ) _criteria_names = {'publication_authors': 'authors'} _ignored_criteria = ( 'allowDiscussion', 'id', ) _www = os.path.join(os.path.dirname(__file__), 'www') security = ClassSecurityInfo() manage_options = ({ 'label': 'Criteria', 'action': 'manageImportCriteria', }, ) security.declareProtected(ManagePortal, 'manageImportCriteria') manageImportCriteria = PageTemplateFile('manage_criteria', _www) def __init__(self): self._criteria = PersistentMapping() self.duplicates_criteria = PersistentMapping() self.criteriaUpdated = False def allCriteria(self, bib_type=None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, '_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join( self.getId()) self._criteria = PersistentMapping() self.initCriteria() try: delattr(self, '_criterias') except: pass # this should have been performed by __init__ but after some product migrations we might want to check it here again if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() if not shasattr(self, 'duplicates_criteria'): self.duplicates_criteria = PersistentMapping() # first call? initialize self._criteria (available duplicates criteria per reference type) if not self._criteria: self.initCriteria() # always init criteria, during development, schema changes, etc. self.initCriteria() if bib_type: try: self._criteria[bib_type] except KeyError: return False return self._criteria[bib_type] else: critKeys = self._criteria.keys() critKeys.sort() return [(key, self._criteria[key]) for key in critKeys] # fixing a linguistic fault-pas allcriterias = allCriteria def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'): """ take a list of criteria tuples (as returned by allCriteria) and sort it according to their i18n names. used for nice display on screen """ # sorting reference types by their i18n titles ref_types_i18n = [(self.translate(domain='plone', msgid=criterion[0], default=criterion[0]), criterion[0]) for criterion in criteria] ref_types_i18n.sort() ref_types = [t[1] for t in ref_types_i18n] # turning criteria into a dictionary criteria_dict = {} for ref_type in ref_types: criteria_dict[ref_type] = [ t[1] for t in criteria if t[0] == ref_type ][0] # sorting fields for each ref_type, standard fields always preceed non-standard fields standard_fields = [ 'authors', 'publication_year', 'title', 'reference_type', ] for ref_type in ref_types: nonstandard_fields_i18n = [ (self.translate(domain=i18n_domain, msgid='label_%s' % field, default=field), field) for field in criteria_dict[ref_type] if field not in standard_fields ] nonstandard_fields_i18n.sort() nonstandard_fields = [f[1] for f in nonstandard_fields_i18n] criteria_dict[ref_type] = standard_fields + nonstandard_fields return [(t, tuple(criteria_dict[t])) for t in ref_types] def initCriteria(self): """ initialize the dictionary of import criteria for each bibliography type """ # this is a migration 0.8 -> 0.9 fix: if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() bib_tool = getToolByName(self, 'portal_bibliography') has = self._criteria_names.has_key for bib_type in bib_tool.getBibliographyContentTypes(): bibname = bib_type['name'] self._criteria[bibname] = [ criteria for criteria in self._nonmeta_criteria ] #adds all meta_data as criteria for each bibliography type for field in bib_type['schema'].fields(): field_name = field.getName() if field_name in self._ignored_criteria: continue if not shasattr(field, 'is_duplicates_criterion'): continue if not field.is_duplicates_criterion: continue if has(field_name): self._criteria[bibname].append( self._criteria_names[field_name]) else: self._criteria[bibname].append(field_name) self._criteria[bibname].sort() self._criteria[bibname] = tuple(self._criteria[bibname]) # fixing a linguistic fault-pas initCriterias = initCriteria def manage_changeCriteria(self, REQUEST): """Changes all criteria for a bibliography type given, called by management screen """ reference_type = REQUEST.get('bibtype') has = REQUEST.has_key if reference_type != 'all': reference_types = [reference_type] else: bib_tool = getToolByName(self, 'portal_bibliography') reference_types = bib_tool.getReferenceTypes() for reference_type in reference_types: criteria = [] for criterion in self._criteria[reference_type]: # the form bibtype_criterion is not useful for now, but it was set up # in case we want to use one submit input for all the bibliography types if has("%s_%s" % (reference_type, criterion)): criteria.append(criterion) try: self.setCriteriaForType(reference_type, criteria) except: return MessageDialog( title='Warning!', message='Your changes have not been saved', action='manageImportCriteria') return MessageDialog(title='Success!', message='Your changes have been saved', action='manageImportCriteria') # fixing a linguistic fault-pas manage_changeCriterias = manage_changeCriteria def setCriteriaForType(self, bib_type, criteria): """update criteria for a bibliography type given""" self.duplicates_criteria[bib_type] = PersistentList(criteria) #FIXME may need to check if any change has been done self.criteriaUpdated = True return True # fixing a linguistic fault-pas setCriteriasForType = setCriteriaForType def setCriteria(self, duplicates_criteria): """update criteria for all bibliography types""" if not duplicates_criteria: duplicates_criteria = {} bib_tool = getToolByName(self, 'portal_bibliography') for key in bib_tool.getReferenceTypes(): duplicates_criteria[key] = [] self.duplicates_criteria = PersistentMapping(duplicates_criteria) self.criteriaUpdated = True # fixing a linguistic fault-pas setCriterias = setCriteria def isCriterionSelected(self, bib_type, criterion): return (criterion in self.getSelectedCriteria(bib_type)) and True or False def isNonMetaCriterion(self, criterion): return criterion in self._nonmeta_criteria # fixing a linguistic fault-pas isCriteriaSelected = isCriterionSelected def getSelectedCriteria(self, bib_type=None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, 'imp_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join( self.getId()) self.duplicates_criteria = PersistentMapping() self.duplicates_criteria = copy.deepcopy(self.imp_criterias) self.criteriaUpdated = self.criteriasUpdated try: delattr(self, 'imp_criterias') except: pass try: delattr(self, 'criteriasUpdated') except: pass # first call? initialize self.duplicates_criteria bib_tool = getToolByName(self, 'portal_bibliography') if not shasattr(self, 'duplicates_criteria') or not self.duplicates_criteria: if self.getId() == bib_tool.getId(): for reference_type in bib_tool.getReferenceTypes(): self.duplicates_criteria[reference_type] = PersistentList( self._default_duplicates_criteria) self.criteriaUpdated = True else: self.duplicates_criteria = bib_tool.getSelectedCriteria() self.criteriaUpdated = True if not shasattr(self, '_criteria') or not self._criteria: self.initCriteria() # make sure, our selected criteria are in sync with available criteria duplicates_criteria = {} for key in self._criteria.keys(): duplicates_criteria[key] = [ criterion for criterion in self._criteria[key] if self.duplicates_criteria.has_key(key) and ( criterion in self.duplicates_criteria[key]) ] if bib_type: try: duplicates_criteria[bib_type] except KeyError: return False return duplicates_criteria[bib_type] else: return duplicates_criteria # fixing a linguistic fault-pas getSelectedCriterias = getSelectedCriteria
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=1, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, (str, unicode)): raise TypeError, 'only strings can be translated.' message = message.strip() if default is None: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url(REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url }) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = { 'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset } self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = [ 'msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', '' ] # Get the messages, and perhaps its translations. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[k] = "" else: filename = '%s.po' % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type', 'application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) r2 = [] for x in r: if isinstance(x, unicode): r2.append(x.encode(charset)) else: r2.append(x) return '\n'.join(r2) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = itools.gettext.POFile(string=data) for msgid in po.get_msgids(): # TODO Keep the context if any _context, msgid = msgid if msgid: msgstr = po.get_msgstr(msgid) or '' if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) security.declareProtected('Manage messages', 'tmx_export') def tmx_export(self, REQUEST, RESPONSE=None): """Exports the content of the message catalog to a TMX file """ src_lang = self._default_language # Get the header info header = self.get_po_header(src_lang) charset = header['charset'] # Init the TMX handler tmx = TMXFile() tmx.header['creationtool'] = u'Localizer' tmx.header['creationtoolversion'] = u'1.x' tmx.header['datatype'] = u'plaintext' tmx.header['segtype'] = u'paragraph' tmx.header['adminlang'] = src_lang tmx.header['srclang'] = src_lang tmx.header['o-encoding'] = u'%s' % charset.lower() # handle messages for msgkey, transunit in self._messages.items(): unit = TMXUnit({}) for lang in transunit.keys(): if lang != 'note': sentence = Sentence({'lang': lang}) sentence.text = transunit[lang] unit.msgstr[lang] = sentence if src_lang not in transunit.keys(): sentence = Sentence({'lang': src_lang}) sentence.text = msgkey unit.msgstr[src_lang] = sentence if transunit.has_key('note'): note = TMXNote(transunit.get('note')) unit.notes.append(note) tmx.messages[msgkey] = unit if RESPONSE is not None: RESPONSE.setHeader('Content-type', 'application/data') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s.tmx"' % self.id) return tmx.to_str() security.declareProtected('Manage messages', 'tmx_import') def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """Imports a TMX level 1 file. """ try: data = file.read() tmx = TMXFile(string=data) except: return MessageDialog( title='Parse error', message=_('impossible to parse the file'), action='manage_Import_form', ) num_notes = 0 num_trans = 0 if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = tmx.get_srclang() for id, msg in tmx.messages.items(): if not self._messages.has_key(id) and howmuch == 'existing': continue msg.msgstr.pop(self._default_language) if not self._messages.has_key(id): self._messages[id] = {} for lang in msg.msgstr.keys(): # normalize the languageTag and extract the core (core, local) = LanguageTag.decode(lang) lang = LanguageTag.encode((core, local)) if lang not in self._languages: self._languages += (lang, ) if msg.msgstr[lang].text: self._messages[id][lang] = msg.msgstr[lang].text if core != lang and core != self._default_language: if core not in self._languages: self._languages += (core, ) if not msg.msgstr.has_key(core): self._messages[id][core] = msg.msgstr[lang].text if msg.notes: ns = [m.text for m in msg.notes] self._messages[id]['note'] = u' '.join(ns) num_notes += 1 num_trans += 1 if REQUEST is not None: message = _(u'Imported %d messages and %d notes') return MessageDialog(title=_(u'Messages imported'), message=message % (num_trans, num_notes), action='manage_messages') ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected('Manage messages', 'xliff_export') def xliff_export(self, dst_lang, export_all=1, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog to an XLIFF file """ from DateTime import DateTime src_lang = self._default_language export_all = int(export_all) # Init the XLIFF handler xliff = XLFFile() # Add the translation units original = '/%s' % self.absolute_url(1) for msgkey, transunit in self._messages.items(): target = transunit.get(dst_lang, '') # If 'export_all' is true export all messages, otherwise export # only untranslated messages if export_all or not target: unit = xliff.add_unit(original, msgkey, None) unit.attributes['id'] = md5text(msgkey) if target: unit.target = target # Add note note = transunit.get('note') if note: unit.notes.append(XLFNote(note)) # build the data-stucture for the File tag file = xliff.files[original] attributes = file.attributes attributes['original'] = original attributes['product-name'] = u'Localizer' attributes['product-version'] = u'1.1.x' attributes['data-type'] = u'plaintext' attributes['source-language'] = src_lang attributes['target-language'] = dst_lang attributes['date'] = DateTime().HTML4() # Serialize xliff = xliff.to_str() # Set response headers RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8') filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang) RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s"' % filename) # Ok return xliff security.declareProtected('Manage messages', 'xliff_import') def xliff_import(self, howmuch, file, REQUEST=None): """XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ try: data = file.read() xliff = XLFFile(string=data) except: return MessageDialog( title='Parse error', message=_('impossible to parse the file'), action='manage_Import_form', ) num_notes = 0 num_trans = 0 (file_ids, sources, targets) = xliff.get_languages() if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = sources[0] # update languages if len(sources) > 1 or sources[0] != self._default_language: return MessageDialog( title='Language error', message=_('incompatible language sources'), action='manage_Import_form', ) for lang in targets: if lang != self._default_language and lang not in self._languages: self._languages += (lang, ) # get messages for file in xliff.files: cur_target = file.attributes.get('target-language', '') for msg in file.body.keys(): if not self._messages.has_key(msg) and howmuch == 'existing': pass else: if not self._messages.has_key(msg): self._messages[msg] = {} if cur_target and file.body[msg].target: self._messages[msg][cur_target] = file.body[msg].target num_trans += 1 if file.body[msg].notes: ns = [n.text for n in file.body[msg].notes] comment = ' '.join(ns) self._messages[msg]['note'] = comment num_notes += 1 if REQUEST is not None: return MessageDialog( title = _(u'Messages imported'), message = (_(u'Imported %d messages and %d notes to %s') % \ (num_trans, num_notes, ' '.join(targets))), action = 'manage_messages')
class DuplicatesCriteriaManager(Implicit): """ An object that contains : _criteria {bib_type : [criteria_name]}: contains all possible criteria (meta-data) for each bibliography type duplicates_criteria {bib_type : [criteria] } : contains criteria to be checked for duplication while importing for each bibliography type _nonmeta_criteria = ( 'reference_type', ) : contains criteria not comprised in meta_data _criteria_names = {'publication_authors' : 'authors'} : contains mappings for meta_data that need a special treatement like 'authors' _ignored_criteria = (critrias) : contains the names of same meta_data like 'id' or 'allowDiscussion' to be filtered IMPORTANT : only 'authors','reference_type', 'publication_year', 'title' are fully functional for now """ _nonmeta_criteria = ( 'reference_type', ) _default_duplicates_criteria = ( #FIXME - we may need a default value for these 'authors', 'reference_type', 'publication_year', 'title',) _criteria_names = {'publication_authors' : 'authors'} _ignored_criteria = ('allowDiscussion','id', ) _www = os.path.join(os.path.dirname(__file__), 'www') security = ClassSecurityInfo() manage_options = ( { 'label' : 'Criteria', 'action' : 'manageImportCriteria', }, ) security.declareProtected(ManagePortal, 'manageImportCriteria') manageImportCriteria = PageTemplateFile('manage_criteria', _www) def __init__(self): self._criteria = PersistentMapping() self.duplicates_criteria = PersistentMapping() self.criteriaUpdated = False def allCriteria(self, bib_type=None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, '_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join(self.getId()) self._criteria = PersistentMapping() self.initCriteria() try: delattr(self, '_criterias') except: pass # this should have been performed by __init__ but after some product migrations we might want to check it here again if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() if not shasattr(self, 'duplicates_criteria'): self.duplicates_criteria = PersistentMapping() # first call? initialize self._criteria (available duplicates criteria per reference type) if not self._criteria: self.initCriteria() # always init criteria, during development, schema changes, etc. self.initCriteria() if bib_type: try: self._criteria[bib_type] except KeyError: return False return self._criteria[bib_type] else: critKeys = self._criteria.keys() critKeys.sort() return [(key, self._criteria[key]) for key in critKeys] # fixing a linguistic fault-pas allcriterias = allCriteria def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'): """ take a list of criteria tuples (as returned by allCriteria) and sort it according to their i18n names. used for nice display on screen """ # sorting reference types by their i18n titles ref_types_i18n = [ (self.translate(domain='plone', msgid=criterion[0], default=criterion[0]), criterion[0]) for criterion in criteria ] ref_types_i18n.sort() ref_types = [ t[1] for t in ref_types_i18n ] # turning criteria into a dictionary criteria_dict = {} for ref_type in ref_types: criteria_dict[ref_type] = [ t[1] for t in criteria if t[0] == ref_type ][0] # sorting fields for each ref_type, standard fields always preceed non-standard fields standard_fields = ['authors', 'publication_year', 'title', 'reference_type',] for ref_type in ref_types: nonstandard_fields_i18n = [ (self.translate(domain=i18n_domain, msgid='label_%s' % field, default=field), field) for field in criteria_dict[ref_type] if field not in standard_fields ] nonstandard_fields_i18n.sort() nonstandard_fields = [ f[1] for f in nonstandard_fields_i18n ] criteria_dict[ref_type] = standard_fields + nonstandard_fields return [ (t, tuple(criteria_dict[t])) for t in ref_types ] def initCriteria(self): """ initialize the dictionary of import criteria for each bibliography type """ # this is a migration 0.8 -> 0.9 fix: if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() bib_tool = getToolByName(self, 'portal_bibliography') has = self._criteria_names.has_key for bib_type in bib_tool.getBibliographyContentTypes(): bibname = bib_type['name'] self._criteria[bibname] = [criteria for criteria in self._nonmeta_criteria] #adds all meta_data as criteria for each bibliography type for field in bib_type['schema'].fields(): field_name = field.getName() if field_name in self._ignored_criteria: continue if not shasattr(field, 'is_duplicates_criterion'): continue if not field.is_duplicates_criterion: continue if has(field_name): self._criteria[bibname].append(self._criteria_names[field_name]) else : self._criteria[bibname].append(field_name) self._criteria[bibname].sort() self._criteria[bibname] = tuple(self._criteria[bibname]) # fixing a linguistic fault-pas initCriterias = initCriteria def manage_changeCriteria(self, REQUEST): """Changes all criteria for a bibliography type given, called by management screen """ reference_type = REQUEST.get('bibtype') has = REQUEST.has_key if reference_type != 'all': reference_types = [reference_type] else: bib_tool = getToolByName(self, 'portal_bibliography') reference_types = bib_tool.getReferenceTypes() for reference_type in reference_types: criteria = [] for criterion in self._criteria[reference_type]: # the form bibtype_criterion is not useful for now, but it was set up # in case we want to use one submit input for all the bibliography types if has("%s_%s" % (reference_type, criterion)): criteria.append(criterion) try: self.setCriteriaForType(reference_type, criteria) except: return MessageDialog( title ='Warning!', message='Your changes have not been saved', action ='manageImportCriteria') return MessageDialog( title ='Success!', message='Your changes have been saved', action ='manageImportCriteria' ) # fixing a linguistic fault-pas manage_changeCriterias = manage_changeCriteria def setCriteriaForType(self, bib_type, criteria): """update criteria for a bibliography type given""" self.duplicates_criteria[bib_type] = PersistentList(criteria) #FIXME may need to check if any change has been done self.criteriaUpdated = True return True # fixing a linguistic fault-pas setCriteriasForType = setCriteriaForType def setCriteria(self, duplicates_criteria): """update criteria for all bibliography types""" if not duplicates_criteria: duplicates_criteria = {} bib_tool = getToolByName(self, 'portal_bibliography') for key in bib_tool.getReferenceTypes(): duplicates_criteria[key] = [] self.duplicates_criteria = PersistentMapping(duplicates_criteria) self.criteriaUpdated = True # fixing a linguistic fault-pas setCriterias = setCriteria def isCriterionSelected(self, bib_type, criterion): return (criterion in self.getSelectedCriteria(bib_type)) and True or False def isNonMetaCriterion(self, criterion): return criterion in self._nonmeta_criteria # fixing a linguistic fault-pas isCriteriaSelected = isCriterionSelected def getSelectedCriteria(self, bib_type = None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, 'imp_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join(self.getId()) self.duplicates_criteria = PersistentMapping() self.duplicates_criteria = copy.deepcopy(self.imp_criterias) self.criteriaUpdated = self.criteriasUpdated try: delattr(self, 'imp_criterias') except: pass try: delattr(self, 'criteriasUpdated') except: pass # first call? initialize self.duplicates_criteria bib_tool = getToolByName(self, 'portal_bibliography') if not shasattr(self, 'duplicates_criteria') or not self.duplicates_criteria: if self.getId() == bib_tool.getId(): for reference_type in bib_tool.getReferenceTypes(): self.duplicates_criteria[reference_type] = PersistentList(self._default_duplicates_criteria) self.criteriaUpdated = True else: self.duplicates_criteria = bib_tool.getSelectedCriteria() self.criteriaUpdated = True if not shasattr(self, '_criteria') or not self._criteria: self.initCriteria() # make sure, our selected criteria are in sync with available criteria duplicates_criteria = {} for key in self._criteria.keys(): duplicates_criteria[key] = [ criterion for criterion in self._criteria[key] if self.duplicates_criteria.has_key(key) and (criterion in self.duplicates_criteria[key]) ] if bib_type: try: duplicates_criteria[bib_type] except KeyError: return False return duplicates_criteria[bib_type] else: return duplicates_criteria # fixing a linguistic fault-pas getSelectedCriterias = getSelectedCriteria
class MimeTypesRegistry(UniqueObject, ActionProviderBase, Folder): """Mimetype registry that deals with a) registering types b) wildcarding of rfc-2046 types c) classifying data into a given type """ implements(IMimetypesRegistry, ISourceAdapter) id = 'mimetypes_registry' meta_type = 'MimeTypes Registry' isPrincipiaFolderish = 1 # Show up in the ZMI meta_types = all_meta_types = ( { 'name' : 'MimeType', 'action' : 'manage_addMimeTypeForm'}, ) manage_options = ( ( { 'label' : 'MimeTypes', 'action' : 'manage_main'},) + Folder.manage_options[2:] ) manage_addMimeTypeForm = PageTemplateFile('addMimeType', _www) manage_main = PageTemplateFile('listMimeTypes', _www) manage_editMimeTypeForm = PageTemplateFile('editMimeType', _www) security = ClassSecurityInfo() # FIXME __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, id=None): if id is not None: assert id == self.id self.encodings_map = encodings_map.copy() self.suffix_map = suffix_map.copy() # Major key -> minor IMimetype objects self._mimetypes = PersistentMapping() # ext -> IMimetype mapping self.extensions = PersistentMapping() # glob -> (regex, mimetype) mapping self.globs = OOBTree() self.manage_addProperty('defaultMimetype', 'text/plain', 'string') self.manage_addProperty('unicodePolicies', 'strict ignore replace', 'tokens') self.manage_addProperty('unicodePolicy', 'unicodePolicies', 'selection') self.manage_addProperty('fallbackEncoding', 'latin1', 'string') # initialize mime types initialize(self) self._new_style_mtr = 1 security.declareProtected(ManagePortal, 'register') def register(self, mimetype): """ Register a new mimetype mimetype must implement IMimetype """ mimetype = aq_base(mimetype) assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: self.register_mimetype(t, mimetype) for extension in mimetype.extensions: self.register_extension(extension, mimetype) for glob in mimetype.globs: self.register_glob(glob, mimetype) security.declareProtected(ManagePortal, 'register_mimetype') def register_mimetype(self, mt, mimetype): major, minor = split(mt) if not major or not minor or minor == '*': raise MimeTypeException('Can\'t register mime type %s' % mt) group = self._mimetypes.setdefault(major, PersistentMapping()) if group.has_key(minor): if group.get(minor) != mimetype: log('Warning: redefining mime type %s (%s)' % ( mt, mimetype.__class__)) group[minor] = mimetype security.declareProtected(ManagePortal, 'register_extension') def register_extension(self, extension, mimetype): """ Associate a file's extension to a IMimetype extension is a string representing a file extension (not prefixed by a dot) mimetype must implement IMimetype """ mimetype = aq_base(mimetype) if self.extensions.has_key(extension): if self.extensions.get(extension) != mimetype: log('Warning: redefining extension %s from %s to %s' % ( extension, self.extensions[extension], mimetype)) # we don't validate fmt yet, but its ["txt", "html"] self.extensions[extension] = mimetype security.declareProtected(ManagePortal, 'register_glob') def register_glob(self, glob, mimetype): """ Associate a glob to a IMimetype glob is a shell-like glob that will be translated to a regex to match against whole filename. mimetype must implement IMimetype. """ globs = getattr(self, 'globs', None) if globs is None: self.globs = globs = OOBTree() mimetype = aq_base(mimetype) existing = globs.get(glob) if existing is not None: regex, mt = existing if mt != mimetype: log('Warning: redefining glob %s from %s to %s' % ( glob, mt, mimetype)) # we don't validate fmt yet, but its ["txt", "html"] pattern = re.compile(fnmatch.translate(glob)) globs[glob] = (pattern, mimetype) security.declareProtected(ManagePortal, 'unregister') def unregister(self, mimetype): """ Unregister a new mimetype mimetype must implement IMimetype """ assert IMimetype.providedBy(mimetype) for t in mimetype.mimetypes: major, minor = split(t) group = self._mimetypes.get(major, {}) if group.get(minor) == mimetype: del group[minor] for e in mimetype.extensions: if self.extensions.get(e) == mimetype: del self.extensions[e] globs = getattr(self, 'globs', None) if globs is not None: for glob in mimetype.globs: existing = globs.get(glob) if existing is None: continue regex, mt = existing if mt == mimetype: del globs[glob] security.declarePublic('mimetypes') def mimetypes(self): """Return all defined mime types, each one implements at least IMimetype """ res = {} for g in self._mimetypes.values(): for mt in g.values(): res[mt] =1 return [aq_base(mtitem) for mtitem in res.keys()] security.declarePublic('list_mimetypes') def list_mimetypes(self): """Return all defined mime types, as string""" return [str(mt) for mt in self.mimetypes()] security.declarePublic('lookup') def lookup(self, mimetypestring): """Lookup for IMimetypes object matching mimetypestring mimetypestring may have an empty minor part or containing a wildcard (*) mimetypestring may and IMimetype object (in this case it will be returned unchanged Return a list of mimetypes objects associated with the RFC-2046 name return an empty list if no one is known. """ if IMimetype.providedBy(mimetypestring): return (aq_base(mimetypestring), ) __traceback_info__ = (repr(mimetypestring), str(mimetypestring)) major, minor = split(str(mimetypestring)) group = self._mimetypes.get(major, {}) if not minor or minor == '*': res = group.values() else: res = group.get(minor) if res: res = (res,) else: return () return tuple([aq_base(mtitem) for mtitem in res]) security.declarePublic('lookupExtension') def lookupExtension(self, filename): """Lookup for IMimetypes object matching filename Filename maybe a file name like 'content.txt' or an extension like 'rest' Return an IMimetype object associated with the file's extension or None """ if filename.find('.') != -1: base, ext = os.path.splitext(filename) ext = ext[1:] # remove the dot while self.suffix_map.has_key(ext): base, ext = os.path.splitext(base + self.suffix_map[ext]) ext = ext[1:] # remove the dot else: ext = filename base = None # XXX This code below make no sense and may break because base # isn't defined. if self.encodings_map.has_key(ext) and base: encoding = self.encodings_map[ext] base, ext = os.path.splitext(base) ext = ext[1:] # remove the dot else: encoding = None result = aq_base(self.extensions.get(ext)) if result is None: result = aq_base(self.extensions.get(ext.lower())) return result security.declarePublic('globFilename') def globFilename(self, filename): """Lookup for IMimetypes object matching filename Filename must be a complete filename with extension. Return an IMimetype object associated with the glob's or None """ globs = getattr(self, 'globs', None) if globs is None: return None for key in globs.keys(): glob, mimetype = globs[key] if glob.match(filename): return aq_base(mimetype) return None security.declarePublic('lookupGlob') def lookupGlob(self, glob): globs = getattr(self, 'globs', None) if globs is None: return None return aq_base(globs.get(glob)) def _classifiers(self): return [mt for mt in self.mimetypes() if IClassifier.providedBy(mt)] security.declarePublic('classify') def classify(self, data, mimetype=None, filename=None): """Classify works as follows: 1) you tell me the rfc-2046 name and I give you an IMimetype object 2) the filename includes an extension from which we can guess the mimetype 3) we can optionally introspect the data 4) default to self.defaultMimetype if no data was provided else to application/octet-stream of no filename was provided, else to text/plain Return an IMimetype object or None """ mt = None if mimetype: mt = self.lookup(mimetype) if mt: mt = mt[0] elif filename: mt = self.lookupExtension(filename) if mt is None: mt = self.globFilename(filename) if data and not mt: for c in self._classifiers(): if c.classify(data): mt = c break if not mt: mstr = magic.guessMime(data) if mstr: _mt = self.lookup(mstr) if len(_mt) > 0: mt = _mt[0] if not mt: if not data: mtlist = self.lookup(self.defaultMimetype) elif filename: mtlist = self.lookup('application/octet-stream') else: failed = 'text/x-unknown-content-type' filename = filename or '' data = data or '' ct, enc = guess_content_type(filename, data, None) if ct == failed: ct = 'text/plain' mtlist = self.lookup(ct) if len(mtlist)>0: mt = mtlist[0] else: return None # Remove acquisition wrappers return aq_base(mt) def __call__(self, data, **kwargs): """ Return a triple (data, filename, mimetypeobject) given some raw data and optional paramters method from the isourceAdapter interface """ mimetype = kwargs.get('mimetype', None) filename = kwargs.get('filename', None) encoding = kwargs.get('encoding', None) mt = None if hasattr(data, 'filename'): filename = os.path.basename(data.filename) elif hasattr(data, 'name'): filename = os.path.basename(data.name) if hasattr(data, 'read'): _data = data.read() if hasattr(data, 'seek'): data.seek(0) data = _data # We need to figure out if data is binary and skip encoding if # it is mt = self.classify(data, mimetype=mimetype, filename=filename) if not mt.binary and not type(data) is UnicodeType: # if no encoding specified, try to guess it from data if encoding is None: encoding = self.guess_encoding(data) # ugly workaround for # https://sourceforge.net/tracker/?func=detail&aid=1068001&group_id=75272&atid=543430 # covered by # https://sourceforge.net/tracker/?func=detail&atid=355470&aid=843590&group_id=5470 # dont remove this code unless python is fixed. if encoding is "macintosh": encoding = 'mac_roman' try: try: data = unicode(data, encoding, self.unicodePolicy) except (ValueError, LookupError): # wrong unicodePolicy data = unicode(data, encoding) except: data = unicode(data, self.fallbackEncoding) return (data, filename, aq_base(mt)) security.declarePublic('guess_encoding') def guess_encoding(self, data): """ Try to guess encoding from a text value if no encoding guessed, used the default charset from site properties (Zope) with a fallback to UTF-8 (should never happen with correct site_properties, but always raise Attribute error without Zope) """ if type(data) is type(u''): # data maybe unicode but with another encoding specified data = data.encode('UTF-8') encoding = guess_encoding(data) if encoding is None: try: site_props = getToolByName(self, 'portal_properties').site_properties encoding = site_props.getProperty('default_charset', 'UTF-8') except: encoding = 'UTF-8' return encoding security.declareProtected(ManagePortal, 'manage_delObjects') def manage_delObjects(self, ids, REQUEST=None): """ delete the selected mime types """ for id in ids: self.unregister(self.lookup(id)[0]) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main') security.declareProtected(ManagePortal, 'manage_addMimeType') def manage_addMimeType(self, id, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """add a mime type to the tool""" mt = MimeTypeItem(id, mimetypes, extensions=extensions, binary=binary, icon_path=icon_path, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main') security.declareProtected(ManagePortal, 'manage_editMimeType') def manage_editMimeType(self, name, new_name, mimetypes, extensions, icon_path, binary=0, globs=None, REQUEST=None): """Edit a mime type by name """ mt = self.lookup(name)[0] self.unregister(mt) mt.edit(new_name, mimetypes, extensions, icon_path=icon_path, binary=binary, globs=globs) self.register(mt) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')