22
21
except ImportError:
32
except NameError: # Python 2.2 & 2.1 compatibility
37
def table(name, arguments, options, content, lineno,
38
content_offset, block_text, state, state_machine):
40
warning = state_machine.reporter.warning(
41
'Content block expected for the "%s" directive; none found.'
42
% name, nodes.literal_block(block_text, block_text),
45
title, messages = make_title(arguments, state, lineno)
46
node = nodes.Element() # anonymous container for parsing
47
state.nested_parse(content, content_offset, node)
48
if len(node) != 1 or not isinstance(node[0], nodes.table):
49
error = state_machine.reporter.error(
50
'Error parsing content block for the "%s" directive: '
51
'exactly one table expected.'
52
% name, nodes.literal_block(block_text, block_text),
56
table_node['classes'] += options.get('class', [])
58
table_node.insert(0, title)
59
return [table_node] + messages
61
table.arguments = (0, 1, 1)
62
table.options = {'class': directives.class_option}
65
def make_title(arguments, state, lineno):
67
title_text = arguments[0]
68
text_nodes, messages = state.inline_text(title_text, lineno)
69
title = nodes.title(title_text, '', *text_nodes)
73
return title, messages
77
class DocutilsDialect(csv.Dialect):
79
"""CSV dialect for `csv_table` directive function."""
84
skipinitialspace = True
86
quoting = csv.QUOTE_MINIMAL
88
def __init__(self, options):
89
if options.has_key('delim'):
90
self.delimiter = str(options['delim'])
91
if options.has_key('keepspace'):
92
self.skipinitialspace = False
93
if options.has_key('quote'):
94
self.quotechar = str(options['quote'])
95
if options.has_key('escape'):
96
self.doublequote = False
97
self.escapechar = str(options['escape'])
98
csv.Dialect.__init__(self)
101
class HeaderDialect(csv.Dialect):
103
"""CSV dialect to use for the "header" option data."""
109
skipinitialspace = True
110
lineterminator = '\n'
111
quoting = csv.QUOTE_MINIMAL
114
def csv_table(name, arguments, options, content, lineno,
115
content_offset, block_text, state, state_machine):
117
if ( not state.document.settings.file_insertion_enabled
118
and (options.has_key('file') or options.has_key('url')) ):
119
warning = state_machine.reporter.warning(
120
'File and URL access deactivated; ignoring "%s" directive.' %
121
name, nodes.literal_block(block_text,block_text), line=lineno)
25
class Table(Directive):
28
Generic table base class.
31
required_arguments = 0
32
optional_arguments = 1
33
final_argument_whitespace = True
34
option_spec = {'class': directives.class_option}
39
title_text = self.arguments[0]
40
text_nodes, messages = self.state.inline_text(title_text,
42
title = nodes.title(title_text, '', *text_nodes)
46
return title, messages
48
def process_header_option(self):
49
source = self.state_machine.get_source(self.lineno - 1)
52
if self.options.has_key('header'): # separate table header in option
53
rows, max_header_cols = self.parse_csv_data_into_rows(
54
self.options['header'].split('\n'), self.HeaderDialect(),
56
table_head.extend(rows)
57
return table_head, max_header_cols
59
def check_table_dimensions(self, rows, header_rows, stub_columns):
60
if len(rows) < header_rows:
61
error = self.state_machine.reporter.error(
62
'%s header row(s) specified but only %s row(s) of data '
63
'supplied ("%s" directive).'
64
% (header_rows, len(rows), self.name), nodes.literal_block(
65
self.block_text, self.block_text), line=self.lineno)
66
raise SystemMessagePropagation(error)
67
if len(rows) == header_rows > 0:
68
error = self.state_machine.reporter.error(
69
'Insufficient data supplied (%s row(s)); no data remaining '
70
'for table body, required by "%s" directive.'
71
% (len(rows), self.name), nodes.literal_block(
72
self.block_text, self.block_text), line=self.lineno)
73
raise SystemMessagePropagation(error)
75
if len(row) < stub_columns:
76
error = self.state_machine.reporter.error(
77
'%s stub column(s) specified but only %s columns(s) of '
78
'data supplied ("%s" directive).' %
79
(stub_columns, len(row), self.name), nodes.literal_block(
80
self.block_text, self.block_text), line=self.lineno)
81
raise SystemMessagePropagation(error)
82
if len(row) == stub_columns > 0:
83
error = self.state_machine.reporter.error(
84
'Insufficient data supplied (%s columns(s)); no data remaining '
85
'for table body, required by "%s" directive.'
86
% (len(row), self.name), nodes.literal_block(
87
self.block_text, self.block_text), line=self.lineno)
88
raise SystemMessagePropagation(error)
90
def get_column_widths(self, max_cols):
91
if self.options.has_key('widths'):
92
col_widths = self.options['widths']
93
if len(col_widths) != max_cols:
94
error = self.state_machine.reporter.error(
95
'"%s" widths do not match the number of columns in table '
96
'(%s).' % (self.name, max_cols), nodes.literal_block(
97
self.block_text, self.block_text), line=self.lineno)
98
raise SystemMessagePropagation(error)
100
col_widths = [100 // max_cols] * max_cols
102
error = self.state_machine.reporter.error(
103
'No table data detected in CSV file.', nodes.literal_block(
104
self.block_text, self.block_text), line=self.lineno)
105
raise SystemMessagePropagation(error)
108
def extend_short_rows_with_empty_cells(self, columns, parts):
111
if len(row) < columns:
112
row.extend([(0, 0, 0, [])] * (columns - len(row)))
115
class RSTTable(Table):
119
warning = self.state_machine.reporter.warning(
120
'Content block expected for the "%s" directive; none found.'
121
% self.name, nodes.literal_block(
122
self.block_text, self.block_text), line=self.lineno)
123
check_requirements(name, lineno, block_text, state_machine)
124
title, messages = make_title(arguments, state, lineno)
125
csv_data, source = get_csv_data(
126
name, options, content, lineno, block_text, state, state_machine)
127
table_head, max_header_cols = process_header_option(
128
options, state_machine, lineno)
129
rows, max_cols = parse_csv_data_into_rows(
130
csv_data, DocutilsDialect(options), source, options)
131
max_cols = max(max_cols, max_header_cols)
132
header_rows = options.get('header-rows', 0) # default 0
133
stub_columns = options.get('stub-columns', 0) # default 0
134
check_table_dimensions(
135
rows, header_rows, stub_columns, name, lineno,
136
block_text, state_machine)
137
table_head.extend(rows[:header_rows])
138
table_body = rows[header_rows:]
139
col_widths = get_column_widths(
140
max_cols, name, options, lineno, block_text, state_machine)
141
extend_short_rows_with_empty_cells(max_cols, (table_head, table_body))
142
except SystemMessagePropagation, detail:
143
return [detail.args[0]]
144
except csv.Error, detail:
145
error = state_machine.reporter.error(
146
'Error with CSV data in "%s" directive:\n%s' % (name, detail),
147
nodes.literal_block(block_text, block_text), line=lineno)
149
table = (col_widths, table_head, table_body)
150
table_node = state.build_table(table, content_offset, stub_columns)
151
table_node['classes'] += options.get('class', [])
153
table_node.insert(0, title)
154
return [table_node] + messages
156
csv_table.arguments = (0, 1, 1)
157
csv_table.options = {'header-rows': directives.nonnegative_int,
158
'stub-columns': directives.nonnegative_int,
159
'header': directives.unchanged,
160
'widths': directives.positive_int_list,
161
'file': directives.path,
162
'url': directives.uri,
163
'encoding': directives.encoding,
164
'class': directives.class_option,
165
# field delimiter char
166
'delim': directives.single_char_or_whitespace_or_unicode,
167
# treat whitespace after delimiter as significant
168
'keepspace': directives.flag,
169
# text field quote/unquote char:
170
'quote': directives.single_char_or_unicode,
171
# char used to escape delim & quote as-needed:
172
'escape': directives.single_char_or_unicode,}
173
csv_table.content = 1
175
def check_requirements(name, lineno, block_text, state_machine):
177
error = state_machine.reporter.error(
178
'The "%s" directive is not compatible with this version of '
179
'Python (%s). Requires the "csv" module, new in Python 2.3.'
180
% (name, sys.version.split()[0]),
181
nodes.literal_block(block_text, block_text), line=lineno)
182
raise SystemMessagePropagation(error)
184
def get_csv_data(name, options, content, lineno, block_text,
185
state, state_machine):
187
CSV data can come from the directive content, from an external file, or
188
from a URL reference.
190
encoding = options.get('encoding', state.document.settings.input_encoding)
191
if content: # CSV data is from directive content
192
if options.has_key('file') or options.has_key('url'):
193
error = state_machine.reporter.error(
194
'"%s" directive may not both specify an external file and '
195
'have content.' % name,
196
nodes.literal_block(block_text, block_text), line=lineno)
197
raise SystemMessagePropagation(error)
198
source = content.source(0)
200
elif options.has_key('file'): # CSV data is from an external file
201
if options.has_key('url'):
202
error = state_machine.reporter.error(
203
'The "file" and "url" options may not be simultaneously '
204
'specified for the "%s" directive.' % name,
205
nodes.literal_block(block_text, block_text), line=lineno)
206
raise SystemMessagePropagation(error)
207
source_dir = os.path.dirname(
208
os.path.abspath(state.document.current_source))
209
source = os.path.normpath(os.path.join(source_dir, options['file']))
210
source = utils.relative_path(None, source)
124
title, messages = self.make_title()
125
node = nodes.Element() # anonymous container for parsing
126
self.state.nested_parse(self.content, self.content_offset, node)
127
if len(node) != 1 or not isinstance(node[0], nodes.table):
128
error = self.state_machine.reporter.error(
129
'Error parsing content block for the "%s" directive: exactly '
130
'one table expected.' % self.name, nodes.literal_block(
131
self.block_text, self.block_text), line=self.lineno)
134
table_node['classes'] += self.options.get('class', [])
136
table_node.insert(0, title)
137
return [table_node] + messages
140
class CSVTable(Table):
142
option_spec = {'header-rows': directives.nonnegative_int,
143
'stub-columns': directives.nonnegative_int,
144
'header': directives.unchanged,
145
'widths': directives.positive_int_list,
146
'file': directives.path,
147
'url': directives.uri,
148
'encoding': directives.encoding,
149
'class': directives.class_option,
150
# field delimiter char
151
'delim': directives.single_char_or_whitespace_or_unicode,
152
# treat whitespace after delimiter as significant
153
'keepspace': directives.flag,
154
# text field quote/unquote char:
155
'quote': directives.single_char_or_unicode,
156
# char used to escape delim & quote as-needed:
157
'escape': directives.single_char_or_unicode,}
161
class DocutilsDialect(csv.Dialect):
163
"""CSV dialect for `csv_table` directive."""
168
skipinitialspace = True
169
lineterminator = '\n'
170
quoting = csv.QUOTE_MINIMAL
172
def __init__(self, options):
173
if options.has_key('delim'):
174
self.delimiter = str(options['delim'])
175
if options.has_key('keepspace'):
176
self.skipinitialspace = False
177
if options.has_key('quote'):
178
self.quotechar = str(options['quote'])
179
if options.has_key('escape'):
180
self.doublequote = False
181
self.escapechar = str(options['escape'])
182
csv.Dialect.__init__(self)
185
class HeaderDialect(csv.Dialect):
187
"""CSV dialect to use for the "header" option data."""
193
skipinitialspace = True
194
lineterminator = '\n'
195
quoting = csv.QUOTE_MINIMAL
197
def check_requirements(self):
199
error = self.state_machine.reporter.error(
200
'The "%s" directive is not compatible with this version of '
201
'Python (%s). Requires the "csv" module, new in Python 2.3.'
202
% (self.name, sys.version.split()[0]), nodes.literal_block(
203
self.block_text, self.block_text), line=self.lineno)
204
raise SystemMessagePropagation(error)
212
state.document.settings.record_dependencies.add(source)
213
csv_file = io.FileInput(
214
source_path=source, encoding=encoding,
216
=state.document.settings.input_encoding_error_handler,
217
handle_io_errors=None)
208
if (not self.state.document.settings.file_insertion_enabled
209
and (self.options.has_key('file')
210
or self.options.has_key('url'))):
211
warning = self.state_machine.reporter.warning(
212
'File and URL access deactivated; ignoring "%s" '
213
'directive.' % self.name, nodes.literal_block(
214
self.block_text, self.block_text), line=self.lineno)
216
self.check_requirements()
217
title, messages = self.make_title()
218
csv_data, source = self.get_csv_data()
219
table_head, max_header_cols = self.process_header_option()
220
rows, max_cols = self.parse_csv_data_into_rows(
221
csv_data, self.DocutilsDialect(self.options), source)
222
max_cols = max(max_cols, max_header_cols)
223
header_rows = self.options.get('header-rows', 0)
224
stub_columns = self.options.get('stub-columns', 0)
225
self.check_table_dimensions(rows, header_rows, stub_columns)
226
table_head.extend(rows[:header_rows])
227
table_body = rows[header_rows:]
228
col_widths = self.get_column_widths(max_cols)
229
self.extend_short_rows_with_empty_cells(max_cols,
230
(table_head, table_body))
231
except SystemMessagePropagation, detail:
232
return [detail.args[0]]
233
except csv.Error, detail:
234
error = self.state_machine.reporter.error(
235
'Error with CSV data in "%s" directive:\n%s'
236
% (self.name, detail), nodes.literal_block(
237
self.block_text, self.block_text), line=self.lineno)
239
table = (col_widths, table_head, table_body)
240
table_node = self.state.build_table(table, self.content_offset,
242
table_node['classes'] += self.options.get('class', [])
244
table_node.insert(0, title)
245
return [table_node] + messages
247
def get_csv_data(self):
249
Get CSV data from the directive content, from an external
250
file, or from a URL reference.
252
encoding = self.options.get(
253
'encoding', self.state.document.settings.input_encoding)
255
# CSV data is from directive content.
256
if self.options.has_key('file') or self.options.has_key('url'):
257
error = self.state_machine.reporter.error(
258
'"%s" directive may not both specify an external file and'
259
' have content.' % self.name, nodes.literal_block(
260
self.block_text, self.block_text), line=self.lineno)
261
raise SystemMessagePropagation(error)
262
source = self.content.source(0)
263
csv_data = self.content
264
elif self.options.has_key('file'):
265
# CSV data is from an external file.
266
if self.options.has_key('url'):
267
error = self.state_machine.reporter.error(
268
'The "file" and "url" options may not be simultaneously'
269
' specified for the "%s" directive.' % self.name,
270
nodes.literal_block(self.block_text, self.block_text),
272
raise SystemMessagePropagation(error)
273
source_dir = os.path.dirname(
274
os.path.abspath(self.state.document.current_source))
275
source = os.path.normpath(os.path.join(source_dir,
276
self.options['file']))
277
source = utils.relative_path(None, source)
279
self.state.document.settings.record_dependencies.add(source)
280
csv_file = io.FileInput(
281
source_path=source, encoding=encoding,
282
error_handler=(self.state.document.settings.\
283
input_encoding_error_handler),
284
handle_io_errors=None)
285
csv_data = csv_file.read().splitlines()
286
except IOError, error:
287
severe = self.state_machine.reporter.severe(
288
'Problems with "%s" directive path:\n%s.'
289
% (self.name, error), nodes.literal_block(
290
self.block_text, self.block_text), line=self.lineno)
291
raise SystemMessagePropagation(severe)
292
elif self.options.has_key('url'):
293
# CSV data is from a URL.
294
# Do not import urllib2 at the top of the module because
295
# it may fail due to broken SSL dependencies, and it takes
296
# about 0.15 seconds to load.
298
source = self.options['url']
300
csv_text = urllib2.urlopen(source).read()
301
except (urllib2.URLError, IOError, OSError, ValueError), error:
302
severe = self.state_machine.reporter.severe(
303
'Problems with "%s" directive URL "%s":\n%s.'
304
% (self.name, self.options['url'], error),
305
nodes.literal_block(self.block_text, self.block_text),
307
raise SystemMessagePropagation(severe)
308
csv_file = io.StringInput(
309
source=csv_text, source_path=source, encoding=encoding,
310
error_handler=(self.state.document.settings.\
311
input_encoding_error_handler))
218
312
csv_data = csv_file.read().splitlines()
219
except IOError, error:
220
severe = state_machine.reporter.severe(
221
'Problems with "%s" directive path:\n%s.' % (name, error),
222
nodes.literal_block(block_text, block_text), line=lineno)
223
raise SystemMessagePropagation(severe)
224
elif options.has_key('url'): # CSV data is from a URL
226
severe = state_machine.reporter.severe(
227
'Problems with the "%s" directive and its "url" option: '
228
'unable to access the required functionality (from the '
229
'"urllib2" module).' % name,
230
nodes.literal_block(block_text, block_text), line=lineno)
231
raise SystemMessagePropagation(severe)
232
source = options['url']
234
csv_text = urllib2.urlopen(source).read()
235
except (urllib2.URLError, IOError, OSError, ValueError), error:
236
severe = state_machine.reporter.severe(
237
'Problems with "%s" directive URL "%s":\n%s.'
238
% (name, options['url'], error),
239
nodes.literal_block(block_text, block_text), line=lineno)
240
raise SystemMessagePropagation(severe)
241
csv_file = io.StringInput(
242
source=csv_text, source_path=source, encoding=encoding,
243
error_handler=state.document.settings.input_encoding_error_handler)
244
csv_data = csv_file.read().splitlines()
246
error = state_machine.reporter.warning(
247
'The "%s" directive requires content; none supplied.' % (name),
248
nodes.literal_block(block_text, block_text), line=lineno)
249
raise SystemMessagePropagation(error)
250
return csv_data, source
252
def process_header_option(options, state_machine, lineno):
253
source = state_machine.get_source(lineno - 1)
256
if options.has_key('header'): # separate table header in option
257
rows, max_header_cols = parse_csv_data_into_rows(
258
options['header'].split('\n'), HeaderDialect(), source, options)
259
table_head.extend(rows)
260
return table_head, max_header_cols
262
def parse_csv_data_into_rows(csv_data, dialect, source, options):
263
# csv.py doesn't do Unicode; encode temporarily as UTF-8
264
csv_reader = csv.reader([line.encode('utf-8') for line in csv_data],
268
for row in csv_reader:
271
# decode UTF-8 back to Unicode
272
cell_text = unicode(cell, 'utf-8')
273
cell_data = (0, 0, 0, statemachine.StringList(
274
cell_text.splitlines(), source=source))
275
row_data.append(cell_data)
276
rows.append(row_data)
277
max_cols = max(max_cols, len(row))
278
return rows, max_cols
280
def check_table_dimensions(rows, header_rows, stub_columns, name, lineno,
281
block_text, state_machine):
282
if len(rows) < header_rows:
283
error = state_machine.reporter.error(
284
'%s header row(s) specified but only %s row(s) of data supplied '
285
'("%s" directive).' % (header_rows, len(rows), name),
286
nodes.literal_block(block_text, block_text), line=lineno)
287
raise SystemMessagePropagation(error)
288
if len(rows) == header_rows > 0:
289
error = state_machine.reporter.error(
290
'Insufficient data supplied (%s row(s)); no data remaining for '
291
'table body, required by "%s" directive.' % (len(rows), name),
292
nodes.literal_block(block_text, block_text), line=lineno)
293
raise SystemMessagePropagation(error)
295
if len(row) < stub_columns:
296
error = state_machine.reporter.error(
297
'%s stub column(s) specified but only %s columns(s) of data '
298
'supplied ("%s" directive).' % (stub_columns, len(row), name),
299
nodes.literal_block(block_text, block_text), line=lineno)
300
raise SystemMessagePropagation(error)
301
if len(row) == stub_columns > 0:
302
error = state_machine.reporter.error(
303
'Insufficient data supplied (%s columns(s)); no data remaining '
304
'for table body, required by "%s" directive.'
306
nodes.literal_block(block_text, block_text), line=lineno)
307
raise SystemMessagePropagation(error)
309
def get_column_widths(max_cols, name, options, lineno, block_text,
311
if options.has_key('widths'):
312
col_widths = options['widths']
313
if len(col_widths) != max_cols:
314
error = state_machine.reporter.error(
315
'"%s" widths do not match the number of columns in table (%s).'
317
nodes.literal_block(block_text, block_text), line=lineno)
318
raise SystemMessagePropagation(error)
320
col_widths = [100 / max_cols] * max_cols
322
error = state_machine.reporter.error(
323
'No table data detected in CSV file.',
324
nodes.literal_block(block_text, block_text), line=lineno)
325
raise SystemMessagePropagation(error)
328
def extend_short_rows_with_empty_cells(columns, parts):
331
if len(row) < columns:
332
row.extend([(0, 0, 0, [])] * (columns - len(row)))
334
def list_table(name, arguments, options, content, lineno,
335
content_offset, block_text, state, state_machine):
314
error = self.state_machine.reporter.warning(
315
'The "%s" directive requires content; none supplied.'
316
% self.name, nodes.literal_block(
317
self.block_text, self.block_text), line=self.lineno)
318
raise SystemMessagePropagation(error)
319
return csv_data, source
321
def parse_csv_data_into_rows(self, csv_data, dialect, source):
322
# csv.py doesn't do Unicode; encode temporarily as UTF-8
323
csv_reader = csv.reader([(line.encode('utf-8') + '\n')
324
for line in csv_data],
328
for row in csv_reader:
331
# decode UTF-8 back to Unicode
332
cell_text = unicode(cell, 'utf-8')
333
cell_data = (0, 0, 0, statemachine.StringList(
334
cell_text.splitlines(), source=source))
335
row_data.append(cell_data)
336
rows.append(row_data)
337
max_cols = max(max_cols, len(row))
338
return rows, max_cols
341
class ListTable(Table):
337
344
Implement tables whose data is encoded as a uniform two-level bullet list.
338
345
For further ideas, see
339
346
http://docutils.sf.net/docs/dev/rst/alternatives.html#list-driven-tables
342
error = state_machine.reporter.error(
343
'The "%s" directive is empty; content required.' % name,
344
nodes.literal_block(block_text, block_text), line=lineno)
346
title, messages = make_title(arguments, state, lineno)
347
node = nodes.Element() # anonymous container for parsing
348
state.nested_parse(content, content_offset, node)
350
num_cols, col_widths = check_list_content(
351
node, name, options, content, lineno, block_text, state_machine)
352
table_data = [[item.children for item in row_list[0]]
353
for row_list in node[0]]
354
header_rows = options.get('header-rows', 0) # default 0
355
stub_columns = options.get('stub-columns', 0) # default 0
356
check_table_dimensions(
357
table_data, header_rows, stub_columns, name, lineno,
358
block_text, state_machine)
359
except SystemMessagePropagation, detail:
360
return [detail.args[0]]
361
table_node = build_table_from_list(table_data, col_widths,
362
header_rows, stub_columns)
363
table_node['classes'] += options.get('class', [])
365
table_node.insert(0, title)
366
return [table_node] + messages
368
list_table.arguments = (0, 1, 1)
369
list_table.options = {'header-rows': directives.nonnegative_int,
370
'stub-columns': directives.nonnegative_int,
371
'widths': directives.positive_int_list,
372
'class': directives.class_option}
373
list_table.content = 1
375
def check_list_content(node, name, options, content, lineno, block_text,
377
if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
378
error = state_machine.reporter.error(
379
'Error parsing content block for the "%s" directive: '
380
'exactly one bullet list expected.' % name,
381
nodes.literal_block(block_text, block_text), line=lineno)
382
raise SystemMessagePropagation(error)
384
# Check for a uniform two-level bullet list:
385
for item_index in range(len(list_node)):
386
item = list_node[item_index]
387
if len(item) != 1 or not isinstance(item[0], nodes.bullet_list):
388
error = state_machine.reporter.error(
349
option_spec = {'header-rows': directives.nonnegative_int,
350
'stub-columns': directives.nonnegative_int,
351
'widths': directives.positive_int_list,
352
'class': directives.class_option}
356
error = self.state_machine.reporter.error(
357
'The "%s" directive is empty; content required.' % self.name,
358
nodes.literal_block(self.block_text, self.block_text),
361
title, messages = self.make_title()
362
node = nodes.Element() # anonymous container for parsing
363
self.state.nested_parse(self.content, self.content_offset, node)
365
num_cols, col_widths = self.check_list_content(node)
366
table_data = [[item.children for item in row_list[0]]
367
for row_list in node[0]]
368
header_rows = self.options.get('header-rows', 0)
369
stub_columns = self.options.get('stub-columns', 0)
370
self.check_table_dimensions(table_data, header_rows, stub_columns)
371
except SystemMessagePropagation, detail:
372
return [detail.args[0]]
373
table_node = self.build_table_from_list(table_data, col_widths,
374
header_rows, stub_columns)
375
table_node['classes'] += self.options.get('class', [])
377
table_node.insert(0, title)
378
return [table_node] + messages
380
def check_list_content(self, node):
381
if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
382
error = self.state_machine.reporter.error(
389
383
'Error parsing content block for the "%s" directive: '
390
'two-level bullet list expected, but row %s does not contain '
391
'a second-level bullet list.' % (name, item_index + 1),
392
nodes.literal_block(block_text, block_text), line=lineno)
384
'exactly one bullet list expected.' % self.name,
385
nodes.literal_block(self.block_text, self.block_text),
393
387
raise SystemMessagePropagation(error)
395
# ATTN pychecker users: num_cols is guaranteed to be set in the
396
# "else" clause below for item_index==0, before this branch is
398
if len(item[0]) != num_cols:
399
error = state_machine.reporter.error(
389
# Check for a uniform two-level bullet list:
390
for item_index in range(len(list_node)):
391
item = list_node[item_index]
392
if len(item) != 1 or not isinstance(item[0], nodes.bullet_list):
393
error = self.state_machine.reporter.error(
400
394
'Error parsing content block for the "%s" directive: '
401
'uniform two-level bullet list expected, but row %s does '
402
'not contain the same number of items as row 1 (%s vs %s).'
403
% (name, item_index + 1, len(item[0]), num_cols),
404
nodes.literal_block(block_text, block_text), line=lineno)
395
'two-level bullet list expected, but row %s does not '
396
'contain a second-level bullet list.'
397
% (self.name, item_index + 1), nodes.literal_block(
398
self.block_text, self.block_text), line=self.lineno)
405
399
raise SystemMessagePropagation(error)
407
num_cols = len(item[0])
408
col_widths = get_column_widths(
409
num_cols, name, options, lineno, block_text, state_machine)
410
if len(col_widths) != num_cols:
411
error = state_machine.reporter.error(
412
'Error parsing "widths" option of the "%s" directive: '
413
'number of columns does not match the table data (%s vs %s).'
414
% (name, len(col_widths), num_cols),
415
nodes.literal_block(block_text, block_text), line=lineno)
416
raise SystemMessagePropagation(error)
417
return num_cols, col_widths
401
# ATTN pychecker users: num_cols is guaranteed to be set in the
402
# "else" clause below for item_index==0, before this branch is
404
if len(item[0]) != num_cols:
405
error = self.state_machine.reporter.error(
406
'Error parsing content block for the "%s" directive: '
407
'uniform two-level bullet list expected, but row %s '
408
'does not contain the same number of items as row 1 '
410
% (self.name, item_index + 1, len(item[0]), num_cols),
411
nodes.literal_block(self.block_text, self.block_text),
413
raise SystemMessagePropagation(error)
415
num_cols = len(item[0])
416
col_widths = self.get_column_widths(num_cols)
417
return num_cols, col_widths
419
def build_table_from_list(table_data, col_widths, header_rows, stub_columns):
420
table = nodes.table()
421
tgroup = nodes.tgroup(cols=len(col_widths))
423
for col_width in col_widths:
424
colspec = nodes.colspec(colwidth=col_width)
426
colspec.attributes['stub'] = 1
430
for row in table_data:
431
row_node = nodes.row()
433
entry = nodes.entry()
436
rows.append(row_node)
438
thead = nodes.thead()
439
thead.extend(rows[:header_rows])
441
tbody = nodes.tbody()
442
tbody.extend(rows[header_rows:])
419
def build_table_from_list(self, table_data, col_widths, header_rows, stub_columns):
420
table = nodes.table()
421
tgroup = nodes.tgroup(cols=len(col_widths))
423
for col_width in col_widths:
424
colspec = nodes.colspec(colwidth=col_width)
426
colspec.attributes['stub'] = 1
430
for row in table_data:
431
row_node = nodes.row()
433
entry = nodes.entry()
436
rows.append(row_node)
438
thead = nodes.thead()
439
thead.extend(rows[:header_rows])
441
tbody = nodes.tbody()
442
tbody.extend(rows[header_rows:])