##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" CMFSetup product utilities
$Id: utils.py 40769 2005-12-13 17:22:05Z efge $
"""
import os
from warnings import warn
from xml.dom.minidom import parseString as domParseString
import Products
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base
from Acquisition import Implicit
from Globals import InitializeClass
from Globals import package_home
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from exceptions import BadRequest
from permissions import ManagePortal
_pkgdir = package_home( globals() )
_xmldir = os.path.join( _pkgdir, 'xml' )
CONVERTER, DEFAULT, KEY = range(3)
class ImportConfiguratorBase(Implicit):
""" Synthesize data from XML description.
"""
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
def __init__(self, site, encoding=None):
self._site = site
self._encoding = encoding
security.declareProtected(ManagePortal, 'parseXML')
def parseXML(self, xml):
""" Pseudo API.
"""
reader = getattr(xml, 'read', None)
if reader is not None:
xml = reader()
dom = domParseString(xml)
root = dom.documentElement
return self._extractNode(root)
def _extractNode(self, node):
nodes_map = self._getImportMapping()
if node.nodeName not in nodes_map:
nodes_map = self._getSharedImportMapping()
if node.nodeName not in nodes_map:
raise ValueError('Unknown node: %s' % node.nodeName)
node_map = nodes_map[node.nodeName]
info = {}
for name, val in node.attributes.items():
key = node_map[name].get( KEY, str(name) )
if self._encoding is not None:
val = val.encode(self._encoding)
info[key] = val
for child in node.childNodes:
name = child.nodeName
if name == '#comment':
continue
if not name == '#text':
key = node_map[name].get(KEY, str(name) )
info[key] = info.setdefault( key, () ) + (
self._extractNode(child),)
elif '#text' in node_map:
key = node_map['#text'].get(KEY, 'value')
val = child.nodeValue.lstrip()
if self._encoding is not None:
val = val.encode(self._encoding)
info[key] = info.setdefault(key, '') + val
for k, v in node_map.items():
key = v.get(KEY, k)
if DEFAULT in v and not key in info:
if isinstance( v[DEFAULT], basestring ):
info[key] = v[DEFAULT] % info
else:
info[key] = v[DEFAULT]
elif CONVERTER in v and key in info:
info[key] = v[CONVERTER]( info[key] )
if key is None:
info = info[key]
return info
def _getSharedImportMapping(self):
return {
'object':
{ 'i18n:domain': {},
'name': {KEY: 'id'},
'meta_type': {},
'insert-before': {},
'insert-after': {},
'property': {KEY: 'properties', DEFAULT: ()},
'object': {KEY: 'objects', DEFAULT: ()},
'xmlns:i18n': {} },
'property':
{ 'name': {KEY: 'id'},
'#text': {KEY: 'value', DEFAULT: ''},
'element': {KEY: 'elements', DEFAULT: ()},
'type': {},
'select_variable': {},
'i18n:translate': {} },
'element':
{ 'value': {KEY: None} },
'description':
{ '#text': {KEY: None, DEFAULT: ''} } }
def _convertToBoolean(self, val):
return val.lower() in ('true', 'yes', '1')
def _convertToInteger(self, val):
return int(val.strip())
def _convertToUnique(self, val):
assert len(val) == 1
return val[0]
security.declareProtected(ManagePortal, 'initObject')
def initObject(self, parent, o_info):
warn('CMFSetup.utils including ImportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
obj_id = str(o_info['id'])
if obj_id not in parent.objectIds():
meta_type = o_info['meta_type']
for mt_info in Products.meta_types:
if mt_info['name'] == meta_type:
parent._setObject( obj_id, mt_info['instance'](obj_id) )
break
else:
raise ValueError('unknown meta_type \'%s\'' % obj_id)
obj = parent._getOb(obj_id)
if 'insert-before' in o_info:
if o_info['insert-before'] == '*':
parent.moveObjectsToTop(obj_id)
else:
try:
position = parent.getObjectPosition(o_info['insert-before'])
parent.moveObjectToPosition(obj_id, position)
except ValueError:
pass
elif 'insert-after' in o_info:
if o_info['insert-after'] == '*':
parent.moveObjectsToBottom(obj_id)
else:
try:
position = parent.getObjectPosition(o_info['insert-after'])
parent.moveObjectToPosition(obj_id, position+1)
except ValueError:
pass
[ self.initObject(obj, info) for info in o_info['objects'] ]
if 'i18n:domain' in o_info:
obj.i18n_domain = o_info['i18n:domain']
[ self.initProperty(obj, info) for info in o_info['properties'] ]
security.declareProtected(ManagePortal, 'initProperty')
def initProperty(self, obj, p_info):
warn('CMFSetup.utils including ImportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
prop_id = p_info['id']
prop_map = obj.propdict().get(prop_id, None)
if prop_map is None:
type = p_info.get('type', None)
if type:
val = p_info.get('select_variable', '')
obj._setProperty(prop_id, val, type)
prop_map = obj.propdict().get(prop_id, None)
else:
raise ValueError('undefined property \'%s\'' % prop_id)
if not 'w' in prop_map.get('mode', 'wd'):
raise BadRequest('%s cannot be changed' % prop_id)
if prop_map.get('type') == 'multiple selection':
prop_value = p_info['elements'] or ()
elif prop_map.get('type') == 'boolean':
# Make sure '0' is imported as False
prop_value = str(p_info['value'])
if prop_value == '0':
prop_value = ''
else:
# if we pass a *string* to _updateProperty, all other values
# are converted to the right type
prop_value = p_info['elements'] or str( p_info['value'] )
obj._updateProperty(prop_id, prop_value)
InitializeClass(ImportConfiguratorBase)
class ExportConfiguratorBase(Implicit):
""" Synthesize XML description.
"""
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
def __init__(self, site, encoding=None):
self._site = site
self._encoding = encoding
self._template = self._getExportTemplate()
security.declareProtected(ManagePortal, 'generateXML')
def generateXML(self, **kw):
""" Pseudo API.
"""
return self._template(**kw)
#
# generic object and property support
#
_ob_nodes = PageTemplateFile('object_nodes.xml', _xmldir)
_prop_nodes = PageTemplateFile('property_nodes.xml', _xmldir)
security.declareProtected(ManagePortal, 'generateObjectNodes')
def generateObjectNodes(self, obj_infos):
""" Pseudo API.
"""
warn('CMFSetup.utils including ExportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
lines = self._ob_nodes(objects=obj_infos).splitlines()
return '\n'.join(lines)
security.declareProtected(ManagePortal, 'generatePropertyNodes')
def generatePropertyNodes(self, prop_infos):
""" Pseudo API.
"""
warn('CMFSetup.utils including ExportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
lines = self._prop_nodes(properties=prop_infos).splitlines()
return '\n'.join(lines)
def _extractObject(self, obj):
warn('CMFSetup.utils including ExportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
properties = []
subobjects = []
i18n_domain = getattr(obj, 'i18n_domain', None)
if getattr( aq_base(obj), '_propertyMap' ):
for prop_map in obj._propertyMap():
prop_info = self._extractProperty(obj, prop_map)
if i18n_domain and prop_info['id'] in ('title', 'description'):
prop_info['i18ned'] = ''
if prop_info['id'] != 'i18n_domain':
properties.append(prop_info)
if getattr( aq_base(obj), 'objectValues' ):
for sub in obj.objectValues():
subobjects.append( self._extractObject(sub) )
return { 'id': obj.getId(),
'meta_type': obj.meta_type,
'i18n_domain': i18n_domain or None,
'properties': tuple(properties),
'subobjects': tuple(subobjects) }
def _extractProperty(self, obj, prop_map):
warn('CMFSetup.utils including ExportConfiguratorBase is deprecated. '
'Please use NodeAdapterBase from GenericSetup.utils instead.',
DeprecationWarning)
prop_id = prop_map['id']
prop = obj.getProperty(prop_id)
if isinstance(prop, tuple):
prop_value = ''
prop_elements = prop
elif isinstance(prop, list):
# Backward compat for old instances that stored
# properties as list.
prop_value = ''
prop_elements = tuple(prop)
else:
prop_value = prop
prop_elements = ()
if 'd' in prop_map.get('mode', 'wd') and not prop_id == 'title':
type = prop_map.get('type', 'string')
select_variable = prop_map.get('select_variable', None)
else:
type = None
select_variable = None
return { 'id': prop_id,
'value': prop_value,
'elements': prop_elements,
'type': type,
'select_variable': select_variable }
InitializeClass(ExportConfiguratorBase)
# BBB: old class mixing the two, will be removed in CMF 2.0
class ConfiguratorBase(ImportConfiguratorBase, ExportConfiguratorBase):
""" Synthesize XML description.
"""
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
def __init__(self, site, encoding=None):
ImportConfiguratorBase.__init__(self, site, encoding)
ExportConfiguratorBase.__init__(self, site, encoding)
InitializeClass(ConfiguratorBase)
# BBB: deprecated DOM parsing utilities, will be removed in CMF 2.0
_marker = object()
def _queryNodeAttribute( node, attr_name, default, encoding=None ):
""" Extract a string-valued attribute from node.
o Return 'default' if the attribute is not present.
"""
attr_node = node.attributes.get( attr_name, _marker )
if attr_node is _marker:
return default
value = attr_node.nodeValue
if encoding is not None:
value = value.encode( encoding )
return value
def _getNodeAttribute( node, attr_name, encoding=None ):
""" Extract a string-valued attribute from node.
"""
value = _queryNodeAttribute( node, attr_name, _marker, encoding )
if value is _marker:
raise ValueError, 'Invalid attribute: %s' % attr_name
return value
def _queryNodeAttributeBoolean( node, attr_name, default ):
""" Extract a string-valued attribute from node.
o Return 'default' if the attribute is not present.
"""
attr_node = node.attributes.get( attr_name, _marker )
if attr_node is _marker:
return default
value = node.attributes[ attr_name ].nodeValue.lower()
return value in ( 'true', 'yes', '1' )
def _getNodeAttributeBoolean( node, attr_name ):
""" Extract a string-valued attribute from node.
"""
value = node.attributes[ attr_name ].nodeValue.lower()
return value in ( 'true', 'yes', '1' )
def _coalesceTextNodeChildren( node, encoding=None ):
""" Concatenate all childe text nodes into a single string.
"""
from xml.dom import Node
fragments = []
node.normalize()
child = node.firstChild
while child is not None:
if child.nodeType == Node.TEXT_NODE:
fragments.append( child.nodeValue )
child = child.nextSibling
joined = ''.join( fragments )
if encoding is not None:
joined = joined.encode( encoding )
return ''.join( [ line.lstrip() for line in joined.splitlines(True) ] )
def _extractDescriptionNode(parent, encoding=None):
d_nodes = parent.getElementsByTagName('description')
if d_nodes:
return _coalesceTextNodeChildren(d_nodes[0], encoding)
else:
return ''