1 # Copyright (C) 2006-2015 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Export an XML representation of a mailing list."""
25 from xml
.sax
.saxutils
import escape
27 from mailman
import Defaults
28 from mailman
import errors
29 from mailman
import MemberAdaptor
30 from mailman
.MailList
import MailList
31 from mailman
.configuration
import config
32 from mailman
.core
.i18n
import _
33 from mailman
.initialize
import initialize
34 from mailman
.version
import MAILMAN_VERSION
40 Defaults
.Toggle
: 'bool',
41 Defaults
.Radio
: 'radio',
42 Defaults
.String
: 'string',
43 Defaults
.Text
: 'text',
44 Defaults
.Email
: 'email',
45 Defaults
.EmailList
: 'email_list',
46 Defaults
.Host
: 'host',
47 Defaults
.Number
: 'number',
48 Defaults
.FileUpload
: 'upload',
49 Defaults
.Select
: 'select',
50 Defaults
.Topics
: 'topics',
51 Defaults
.Checkbox
: 'checkbox',
52 Defaults
.EmailListEx
: 'email_list_ex',
53 Defaults
.HeaderFilter
: 'header_filter',
59 def __init__(self
, fp
, indentwidth
=4):
62 self
._width
= indentwidth
69 assert self
._indent
>= 0
73 self
._fp
.write(self
._indent
* self
._width
* ' ')
78 class XMLDumper(object):
79 def __init__(self
, fp
):
80 self
._fp
= Indenter(fp
)
81 self
._tagbuffer
= None
84 def _makeattrs(self
, tagattrs
):
85 # The attribute values might contain angle brackets. They might also
88 for k
, v
in tagattrs
.items():
93 attrs
.append('%s="%s"' % (k
, v
))
94 return SPACE
.join(attrs
)
96 def _flush(self
, more
=True):
97 if not self
._tagbuffer
:
99 name
, attributes
= self
._tagbuffer
100 self
._tagbuffer
= None
102 attrstr
= ' ' + self
._makeattrs
(attributes
)
106 print >> self
._fp
, '<%s%s>' % (name
, attrstr
)
108 self
._stack
.append(name
)
110 print >> self
._fp
, '<%s%s/>' % (name
, attrstr
)
112 # Use this method when you know you have sub-elements.
113 def _push_element(self
, _name
, **_tagattrs
):
115 self
._tagbuffer
= (_name
, _tagattrs
)
117 def _pop_element(self
, _name
):
118 buffered
= bool(self
._tagbuffer
)
119 self
._flush
(more
=False)
121 name
= self
._stack
.pop()
122 assert name
== _name
, 'got: %s, expected: %s' % (_name
, name
)
124 print >> self
._fp
, '</%s>' % name
126 # Use this method when you do not have sub-elements
127 def _element(self
, _name
, _value
=None, **_attributes
):
130 attrs
= ' ' + self
._makeattrs
(_attributes
)
134 print >> self
._fp
, '<%s%s/>' % (_name
, attrs
)
136 # The value might contain angle brackets.
137 value
= escape(_value
.decode('utf-8'))
138 print >> self
._fp
, '<%s%s>%s</%s>' % (_name
, attrs
, value
, _name
)
140 def _do_list_categories(self
, mlist
, k
, subcat
=None):
141 info
= mlist
.GetConfigInfo(k
, subcat
)
142 label
, gui
= mlist
.GetConfigCategories()[k
]
145 for data
in info
[1:]:
146 if not isinstance(data
, tuple):
149 # Variable could be volatile
150 if varname
.startswith('_'):
153 # Munge the value based on its type
155 if hasattr(gui
, 'getValue'):
156 value
= gui
.getValue(mlist
, vtype
, varname
, data
[2])
158 value
= getattr(mlist
, varname
)
159 widget_type
= TYPES
[vtype
]
160 if isinstance(value
, list):
161 self
._push
_element
('option', name
=varname
, type=widget_type
)
163 self
._element
('value', v
)
164 self
._pop
_element
('option')
166 self
._element
('option', value
, name
=varname
, type=widget_type
)
168 def _dump_list(self
, mlist
):
169 # Write list configuration values
170 self
._push
_element
('list', name
=mlist
.fqdn_listname
)
171 self
._push
_element
('configuration')
172 self
._element
('option',
173 mlist
.preferred_language
,
174 name
='preferred_language')
175 for k
in config
.ADMIN_CATEGORIES
:
176 subcats
= mlist
.GetConfigSubCategories(k
)
178 self
._do
_list
_categories
(mlist
, k
)
180 for subcat
in [t
[0] for t
in subcats
]:
181 self
._do
_list
_categories
(mlist
, k
, subcat
)
182 self
._pop
_element
('configuration')
184 self
._push
_element
('roster')
185 digesters
= set(mlist
.getDigestMemberKeys())
186 for member
in sorted(mlist
.getMembers()):
187 attrs
= dict(id=member
)
188 cased
= mlist
.getMemberCPAddress(member
)
190 attrs
['original'] = cased
191 self
._push
_element
('member', **attrs
)
192 self
._element
('realname', mlist
.getMemberName(member
))
193 self
._element
('password', mlist
.getMemberPassword(member
))
194 self
._element
('language', mlist
.getMemberLanguage(member
))
195 # Delivery status, combined with the type of delivery
197 status
= mlist
.getDeliveryStatus(member
)
198 if status
== MemberAdaptor
.ENABLED
:
199 attrs
['status'] = 'enabled'
201 attrs
['status'] = 'disabled'
202 attrs
['reason'] = {MemberAdaptor
.BYUSER
: 'byuser',
203 MemberAdaptor
.BYADMIN
: 'byadmin',
204 MemberAdaptor
.BYBOUNCE
: 'bybounce',
205 }.get(mlist
.getDeliveryStatus(member
),
207 if member
in digesters
:
208 if mlist
.getMemberOption(member
, Defaults
.DisableMime
):
209 attrs
['delivery'] = 'plain'
211 attrs
['delivery'] = 'mime'
213 attrs
['delivery'] = 'regular'
214 changed
= mlist
.getDeliveryStatusChangeTime(member
)
216 when
= datetime
.datetime
.fromtimestamp(changed
)
217 attrs
['changed'] = when
.isoformat()
218 self
._element
('delivery', **attrs
)
219 for option
, flag
in Defaults
.OPTINFO
.items():
220 # Digest/Regular delivery flag must be handled separately
221 if option
in ('digest', 'plain'):
223 value
= mlist
.getMemberOption(member
, flag
)
224 self
._element
(option
, value
)
225 topics
= mlist
.getMemberTopics(member
)
227 self
._element
('topics')
229 self
._push
_element
('topics')
231 self
._element
('topic', topic
)
232 self
._pop
_element
('topics')
233 self
._pop
_element
('member')
234 self
._pop
_element
('roster')
235 self
._pop
_element
('list')
237 def dump(self
, listnames
):
238 print >> self
._fp
, '<?xml version="1.0" encoding="UTF-8"?>'
239 self
._push
_element
('mailman', **{
240 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
241 'xsi:noNamespaceSchemaLocation': 'ssi-1.0.xsd',
243 for listname
in sorted(listnames
):
245 mlist
= MailList(listname
, lock
=False)
246 except errors
.MMUnknownListError
:
247 print >> sys
.stderr
, _('No such list: $listname')
249 self
._dump
_list
(mlist
)
250 self
._pop
_element
('mailman')
259 parser
= optparse
.OptionParser(version
=MAILMAN_VERSION
,
263 Export the configuration and members of a mailing list in XML format."""))
264 parser
.add_option('-o', '--outputfile',
265 metavar
='FILENAME', default
=None, type='string',
267 Output XML to FILENAME. If not given, or if FILENAME is '-', standard out is
269 parser
.add_option('-l', '--listname',
270 default
=[], action
='append', type='string',
271 metavar
='LISTNAME', dest
='listnames', help=_("""\
272 The list to include in the output. If not given, then all mailing lists are
273 included in the XML output. Multiple -l flags may be given."""))
274 parser
.add_option('-C', '--config',
275 help=_('Alternative configuration file to use'))
276 opts
, args
= parser
.parse_args()
279 parser
.error(_('Unexpected arguments'))
280 return parser
, opts
, args
285 parser
, opts
, args
= parseargs()
286 initialize(opts
.config
)
289 if opts
.outputfile
in (None, '-'):
290 writer
= codecs
.getwriter('utf-8')
291 fp
= writer(sys
.stdout
)
293 fp
= codecs
.open(opts
.outputfile
, 'w', 'utf-8')
297 dumper
= XMLDumper(fp
)
300 for listname
in opts
.listnames
:
301 if '@' not in listname
:
302 listname
= '%s@%s' % (listname
, config
.DEFAULT_EMAIL_HOST
)
303 listnames
.append(listname
)
305 listnames
= config
.list_manager
.names
306 dumper
.dump(listnames
)