Attachment 'IndentTable.py'
Download 1 """
2 MoinMoin - IndentTable.py processor
3
4 Processor for turning indented lists of data into tables.
5 $Id: IndentTable.py,v 1.8 2004/12/19 19:36:22 nigel Exp $
6
7 With classical MoinMoin tables syntax:
8 * the source can become pretty unreadable when cells contents grow
9 * it is difficult to handle multiple lines
10 * it is impossible to have lists (other than by including a page
11 in a cell)
12
13 This processor is meant to correct the above problems, by the
14 means of an indented list:
15 * each cell consists of one line
16 * its indent determines its position in the table
17 * one line can be continued on the next line by means of '\' at the
18 end; this helps keeping long contents readable
19 * macros accepted in regular tables are accepted too, e.g [[BR]]
20 * all regular table formatting (formats in <>'s, cell spanning)
21 are supported
22 * optionally, the cell contents can be parsed, allowing to have
23 any kind of lists, headings, etc.
24
25 The price to pay for the increased power and readability of the
26 source, versus the regular table syntax, is the loss of the 2D
27 matrix layout.
28
29 @copyright: Pascal Bauermeister <pascal DOT bauermeister AT hispeed DOT ch>
30 @license: GPL
31
32 Updates:
33 * [v0.0.1] Pascal - Thu Jul 29 15:41:25 CEST 2004
34 - initial release
35 * [v0.0.2] NigelMetheringham - Sun Dec 19 19:05:21 +0000 2004
36 - minimal rework for MoinMoin 1.3.x
37 - change of option parse means some option forms have changed
38 - this needs further work
39
40 -------------------------------------------------------------------------------
41
42 Usage:
43 {{{#!IndentTable OPTIONS
44 indented data
45 }}}
46
47 Options:
48 debug=on insert debug info in the output
49 debug=full insert more debug info in the output
50 extended=on parse each cell content (useful for lists and headings)
51 row=on force row mode
52 column=on force column mode
53
54 -------------------------------------------------------------------------------
55
56 Samples:
57
58 {{{#!IndentTable
59 1 1
60 2 ==> 2
61 3 3
62 4 4
63 }}}
64
65 {{{#!IndentTable
66 1
67 2 ==> 1 2 3 4
68 3
69 4
70 }}}
71
72 {{{#!IndentTable
73 1
74 2 ==> 1 2 3
75 3 4
76 4
77 }}}
78
79 {{{#!IndentTable row=on
80 1
81 2 ==> 1 2 3
82 3 4
83 4
84 }}}
85
86 {{{#!IndentTable
87 1
88 2 ==> 1 2 3
89 3 4
90 4
91 }}}
92
93 {{{#!IndentTable
94 1
95 2 ==> 1 2
96 3 3 4
97 4
98 }}}
99
100 {{{#!IndentTable
101 1
102 2 ==> 1 3 4
103 3 2
104 4
105 }}}
106
107 {{{#!IndentTable row=on
108 1
109 2 ==> 1
110 3 2 3 4
111 4
112 }}}
113
114 {{{#!IndentTable
115 1
116 2 ==> 1 3 5
117 3 2 4 6
118 4
119 5
120 6
121 }}}
122
123 {{{ #!IndentTable extended=on
124 <width='30%'>1
125 <width='30%'>2
126 <width='30%'>3
127 4 ==> 1 2 3
128 <|2(>== Cool ! ==\
129 [[SystemInfo]]\ 4 == Cool! == 6
130 Terrific, isn't it ? <system>
131 6 7 <information> 9
132 7 Terrific, isn't it ?
133 9 10 11 12
134 10
135 11
136 12
137
138 }}
139
140 {{{#!IndentTable extended=on
141 A1 ==> +----+-----------------+------------+--+
142 B1 |A1 |B1 | C1+D1 | |
143 ||C1+D1 +----+-----------------+------------+--+
144 A2 | | |C2: Bullets:| |
145 B2 | | | * bullet 1|D2|
146 C2: Bullets: \ |A2 |B2 | * bullet 2| |
147 * bullet 1 \ | | |end of cell | |
148 * bullet 2 \ +----+-----------------+------------+--+
149 end of cell |A3 | a. (B3) numberes|C3 | |
150 D2 | | b. numbered item| | |
151 A3 +----+-----------------+------------+--+
152 a. (B3) numbers \ |(A4)|B4 | | |
153 a. numbered item +----+-----------------+------------+--+
154 C3
155 '''''' (A4)
156 B4
157 ## You find this list unreadable ? try to do the same with just ||'s !
158 }}}
159 """
160
161 from MoinMoin import wikiutil
162 from MoinMoin.parser import wiki
163 import cStringIO, re, string, random
164
165 LETTERS = string.ascii_lowercase
166 LETTERS_LEN = len (LETTERS)
167
168 LIST_HEADERS = ["*", "1.", "a.", "A.", "i.", "I."]
169
170 COL_FMT_RE1 = re.compile("\\|*<[^>]*>")
171 COL_FMT_RE2 = re.compile("\\|*") # FIXME: unify these two regexes
172
173 class Parser:
174 """ Parser for turning indented lists of data into tables. """
175
176 def __init__(self, raw, request, **kw):
177 # save call arguments for later use in format
178 self.lines = raw.split("\n");
179 self.request = request
180 self.form = request.form
181 self._ = request.getText
182 self.out = kw.get('out', request)
183 # default options values
184 self.opt_dbg = False
185 self.opt_dbgf = False # verbose debug
186 self.opt_ext = False
187 self.opt_row = False
188 self.opt_col = False # not sure this is really useful...
189 # deal with arguments
190 attrs, msg = wikiutil.parseAttributes(request,
191 kw.get('format_args',''))
192 if not msg:
193 if attrs.get('debug','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
194 self.opt_dbg = True
195 if attrs.get('debug','"off"')[1:-1].lower() in ('full'):
196 self.opt_dbgf = True
197 if attrs.get('extended','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
198 self.opt_ext = True
199 if attrs.get('row','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
200 self.opt_row = True
201 if attrs.get('column','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
202 self.opt_col = True
203
204
205 def format (self, formatter):
206 substitute_content = True
207
208 #
209 # collect src lines
210 #
211 lines_info = []
212 line_buf = ""
213 last_indent = -1
214 nb_indent_eq, nb_indent_dec = 0, 0
215 for line in self.lines:
216 # skip comments
217 if line.lstrip ().startswith ("##"): continue
218
219 # handle unterminated lines
220 if line.strip ().endswith ("\\"):
221 line_buf = line_buf + line.rstrip ('\\ ')
222 if self.opt_ext: line_buf = line_buf + "\n"
223 continue # continue, to finish line
224
225 # append current line to any previously unterminated line
226 else: line_buf = line_buf + line
227
228 # skip empty lines
229 if len (line_buf.strip ()) == 0: continue
230
231 # calculate indent
232 lline = line_buf.lstrip ()
233 cur_indent = len (line_buf) - len (lline)
234 if cur_indent == last_indent: nb_indent_eq = nb_indent_eq + 1
235 if cur_indent < last_indent: nb_indent_dec = nb_indent_dec + 1
236
237 # detect table formatting
238 m = COL_FMT_RE1.match (lline) or COL_FMT_RE2.match (lline)
239 if m and m.start() == 0:
240 fmt = lline [:m.end ()]
241 data = lline [m.end():].strip ()
242 else:
243 fmt = ""
244 data = line_buf.strip ()
245
246 # in extended mode, adjust leading spaces of data lines so
247 # that the first data line has none, and all other data lines
248 # are aligned relatively to the first one; for lists, preserve
249 # one leading space
250 if self.opt_ext:
251 start = cur_indent # number of unwanted leading spaces
252 for s in ["*", "1.", "a.", "A.", "i.", "I."]:
253 if data.startswith (s): start = start -1 # preserve 1 space
254 data = " "*cur_indent+data # 'unstrip' the 1st line (w/o tbl fmt)
255 data_lines = data.split ("\n")
256 for i in range (len (data_lines)):
257 data_lines [i] = data_lines [i] [start:] # del unwanted spaces
258 data = ("\n").join (data_lines)
259
260
261 # store cell
262 lines_info.append ( (cur_indent, fmt, data) )
263
264 # ready for next line
265 line_buf = ""
266 last_indent = cur_indent
267
268 #
269 # generate table structure
270 #
271 table_fmt_buf = ""
272
273 # decide whether row or column-oriented
274 is_by_col = nb_indent_dec==0 and nb_indent_eq > 0
275 if self.opt_col: is_by_col = True
276 if self.opt_row: is_by_col = False
277
278 # generate a token base that does not occur in the source, and
279 # that is MoinMoin neutral, and not an HTML sequence
280 token_base = "token"
281 src = "\n".join (self.lines)
282 while src.find (token_base) >=0:
283 # append some random letter
284 token_base = token_base + LETTERS [random.randint (0, LETTERS_LEN-1)]
285
286 # function to generate tokens
287 mk_token = lambda i: "%s%i" % (token_base, i)
288
289 # function to generate a cell, either with a token, or with raw
290 # content, depending on whether we must interpret the content
291 if self.opt_ext: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, mk_token (i))
292 else: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, data)
293
294 # row-oriented structure:
295 # the table flow is the same as regular MoinMoin tables, all we
296 # have to do is detect the end of rows and generate end of lines
297 if not is_by_col:
298 indent = 0
299 line_index = 0
300 if not self.opt_ext: substitute_content = False
301 for cur_indent, fmt, line in lines_info:
302 # same or lower indent ? ==> new row: close previous and start new
303 if cur_indent <= indent and len (table_fmt_buf):
304 table_fmt_buf = table_fmt_buf +"||\n"
305
306 # add cell
307 table_fmt_buf = table_fmt_buf + mk_cell (fmt, line_index, line)
308
309 indent = cur_indent
310 line_index = line_index + 1
311
312 # close table
313 if len (table_fmt_buf): table_fmt_buf = table_fmt_buf + "||"
314
315 # column-oriented structure:
316 # a bit more complicated; the cells must be reordered; we first
317 # determine the coordinates of data and store them in a (pseudo)
318 # table; then we generate the table structure, picking the right
319 # data
320 else:
321 # determine coordinates and store data
322 indent = -1
323 col, row = 0, 0
324 max_col, max_row = 0, 0 # will be needed to generate the table
325 table = {}
326 for index in range (len (lines_info)):
327 cur_indent = lines_info [index] [0]
328 if cur_indent == indent:
329 # new row
330 row = row + 1
331 if row > max_row: max_row = row
332 else:
333 # new column
334 row = 1
335 col = col + 1
336 if col > max_col: max_col = col
337 indent = cur_indent
338 # store coordinates and data index
339 table [col-1,row-1] = index
340
341 # generate table
342 for row in range (max_row):
343 for col in range (max_col):
344 if table.has_key ((col,row)):
345 index = table [col,row]
346 fmt, line = lines_info [index] [1:]
347 table_fmt_buf = table_fmt_buf + mk_cell (fmt, index, line)
348 else:
349 table_fmt_buf = table_fmt_buf + "|| " # empty cell
350 table_fmt_buf = table_fmt_buf +"||\n"
351
352 #
353 # final table generation
354 #
355
356 # emit debug
357 if self.opt_dbg or self.opt_dbgf:
358 if self.opt_dbgf:
359 if substitute_content:
360 data = "\nData:\n"
361 for i in range (len (lines_info)):
362 line = lines_info [i] [2]
363 data = data + "%d: [%s]\n" % (i, line)
364 else: data = ""
365 output = "{{{\nSource:\n{{ %s\n}}\n" \
366 "\nTable:\n%s\n" \
367 "%s}}}" % \
368 ("\n".join (self.lines), table_fmt_buf, data)
369
370 else: output = "{{{\n%s\n}}}" % "\n".join (self.lines)
371
372 parser = wiki.Parser (output, self.request)
373 parser.format (formatter)
374
375 # generate html for table structure, generate each cell, then
376 # merge them
377 if substitute_content:
378 # gen table struct
379 html_buf = self.reformat (table_fmt_buf, self.request, formatter)
380 # gen cells contents and merge in table
381 for i in range (len (lines_info)):
382 line = lines_info [i] [2]
383 token = mk_token (i)
384 content = self.reformat (line, self.request, formatter)
385 html_buf = html_buf.replace (token, content, 1)
386 # proudly emit the result
387 self.request.write(html_buf) # emit html-formatted content
388
389 # we have the table in MoinMoin source format, just HTML-convert it
390 else:
391 output = "%s\n" % table_fmt_buf
392 parser = wiki.Parser (output, self.request)
393 parser.format (formatter)
394
395 # done!
396 return
397
398
399 def reformat (self, src_text, request, formatter):
400 # parse the text (in wiki source format) and make HTML,
401 # after diverting sys.stdout to a string
402 str_out = cStringIO.StringIO () # create str to collect output
403 request.redirect (str_out) # divert output to that string
404 # parse this line
405 wiki.Parser (src_text, request).format (formatter)
406 request.redirect () # restore output
407 return str_out.getvalue ().strip () # return what was generated
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.