IndentTable

This is a rework of Pascal Bauermeister's IndentTable.py processor as a Moin 1.3.x parser.

This allows tables to be laid out in a way that is easier to edit, at least for complex tables.

IndentTable.py
Original version
IndentTable-0.0.3.py

Patched version which allows non-ASCII chars. Only reformat() at the end was modified. -- Aaron Digulla

  • [get | view] (2006-06-02 07:02:56, 14.3 KB) [[attachment:IndentTable-0.0.3.py]]
  • [get | view] (2006-06-02 06:53:55, 14.1 KB) [[attachment:IndentTable-UTF8.py]]
  • [get | view] (2004-12-19 19:42:23, 13.8 KB) [[attachment:IndentTable.py]]
 All files | Selected Files: delete move to page copy to page

Code

   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 * [v0.0.3] Aaron Digulla - Thursday Jun 02 20:00:00 +0100 2006
  40   - Small patch to allow non-ASCII characters (see reformat())
  41 
  42 -------------------------------------------------------------------------------
  43 
  44 Usage:
  45 {{{#!IndentTable OPTIONS
  46 indented data
  47 }}}
  48 
  49 Options:
  50   debug=on      insert debug info in the output
  51   debug=full    insert more debug info in the output
  52   extended=on   parse each cell content (useful for lists and headings)
  53   row=on        force row mode
  54   column=on     force column mode
  55 
  56 -------------------------------------------------------------------------------
  57 
  58 Samples:
  59 
  60 {{{#!IndentTable
  61 1                               1
  62 2                       ==>     2
  63 3                               3
  64 4                               4
  65 }}}
  66 
  67 {{{#!IndentTable
  68 1
  69  2                      ==>     1 2 3 4
  70   3
  71    4
  72 }}}
  73 
  74 {{{#!IndentTable
  75 1
  76  2                      ==>     1 2 3
  77   3                                 4
  78   4
  79 }}}
  80 
  81 {{{#!IndentTable row=on
  82 1
  83  2                      ==>     1 2 3
  84   3                             4
  85   4
  86 }}}
  87 
  88 {{{#!IndentTable
  89 1
  90  2                      ==>     1 2 3
  91   3                             4
  92  4
  93 }}}
  94 
  95 {{{#!IndentTable
  96 1
  97  2                      ==>     1 2
  98 3                               3 4
  99  4
 100 }}}
 101 
 102 {{{#!IndentTable
 103 1
 104 2                       ==>     1 3 4
 105  3                              2
 106   4
 107 }}}
 108 
 109 {{{#!IndentTable row=on
 110 1
 111 2                       ==>     1
 112  3                              2 3 4
 113   4
 114 }}}
 115 
 116 {{{#!IndentTable
 117 1
 118 2                       ==>     1 3 5
 119  3                              2 4 6
 120  4
 121   5
 122   6
 123 }}}
 124 
 125 {{{ #!IndentTable extended=on
 126 <width='30%'>1
 127  <width='30%'>2
 128   <width='30%'>3
 129 4                       ==>     1            2            3
 130  <|2(>== Cool ! ==\
 131  [[SystemInfo]]\                4  == Cool! ==            6
 132  Terrific, isn't it ?              <system>     
 133   6                             7  <information>          9
 134 7                                  Terrific, isn't it ?
 135   9                             10           11           12
 136 10
 137  11
 138   12
 139 
 140 }}
 141 
 142 {{{#!IndentTable extended=on
 143   A1                    ==> +----+-----------------+------------+--+
 144     B1                      |A1  |B1               |    C1+D1   |  |
 145       ||C1+D1               +----+-----------------+------------+--+
 146   A2                        |    |                 |C2: Bullets:|  |
 147     B2                      |    |                 |  * bullet 1|D2|
 148       C2: Bullets: \        |A2  |B2               |  * bullet 2|  |
 149         * bullet 1 \        |    |                 |end of cell |  |
 150         * bullet 2 \        +----+-----------------+------------+--+
 151       end of cell           |A3  | a. (B3) numberes|C3          |  |
 152         D2                  |    | b. numbered item|            |  |
 153   A3                        +----+-----------------+------------+--+
 154     a. (B3) numbers \       |(A4)|B4               |            |  |
 155     a. numbered item        +----+-----------------+------------+--+
 156       C3
 157   '''''' (A4)
 158     B4
 159 ## You find this list unreadable ?  try to do the same with just ||'s !
 160 }}}
 161 """
 162 
 163 from MoinMoin import wikiutil
 164 from MoinMoin.parser import wiki
 165 import cStringIO, re, string, random
 166 
 167 LETTERS = string.ascii_lowercase
 168 LETTERS_LEN = len (LETTERS)
 169 
 170 LIST_HEADERS = ["*", "1.", "a.", "A.", "i.", "I."]
 171 
 172 COL_FMT_RE1 = re.compile("\\|*<[^>]*>")
 173 COL_FMT_RE2 = re.compile("\\|*") # FIXME: unify these two regexes
 174 
 175 class Parser:
 176     """ Parser for turning indented lists of data into tables. """
 177 
 178     def __init__(self, raw, request, **kw):
 179         # save call arguments for later use in format
 180         self.lines = raw.split("\n");
 181         self.request = request
 182         self.form = request.form
 183         self._ = request.getText
 184         self.out = kw.get('out', request)
 185         # default options values
 186         self.opt_dbg = False
 187         self.opt_dbgf = False # verbose debug
 188         self.opt_ext = False
 189         self.opt_row = False
 190         self.opt_col = False # not sure this is really useful...
 191         # deal with arguments
 192         attrs, msg = wikiutil.parseAttributes(request,
 193                                               kw.get('format_args',''))
 194         if not msg:
 195             if attrs.get('debug','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
 196                 self.opt_dbg = True
 197             if attrs.get('debug','"off"')[1:-1].lower() in ('full'):
 198                 self.opt_dbgf = True
 199             if attrs.get('extended','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
 200                 self.opt_ext = True
 201             if attrs.get('row','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
 202                 self.opt_row = True
 203             if attrs.get('column','"off"')[1:-1].lower() in ('on', 'true', 'yes'):
 204                 self.opt_col = True
 205 
 206 
 207     def format (self, formatter):
 208         substitute_content = True
 209 
 210         #
 211         # collect src lines
 212         #
 213         lines_info = []
 214         line_buf = ""
 215         last_indent = -1
 216         nb_indent_eq, nb_indent_dec = 0, 0
 217         for line in self.lines:
 218             # skip comments
 219             if line.lstrip ().startswith ("##"): continue
 220 
 221             # handle unterminated lines
 222             if line.strip ().endswith ("\\"):
 223                 line_buf = line_buf + line.rstrip ('\\ ')
 224                 if self.opt_ext: line_buf = line_buf + "\n"
 225                 continue # continue, to finish line
 226 
 227             # append current line to any previously unterminated line
 228             else: line_buf = line_buf + line
 229 
 230             # skip empty lines
 231             if len (line_buf.strip ()) == 0: continue
 232 
 233             # calculate indent
 234             lline = line_buf.lstrip ()
 235             cur_indent = len (line_buf) - len (lline)
 236             if cur_indent == last_indent: nb_indent_eq = nb_indent_eq + 1
 237             if cur_indent < last_indent:  nb_indent_dec = nb_indent_dec + 1
 238 
 239             # detect table formatting
 240             m = COL_FMT_RE1.match (lline) or COL_FMT_RE2.match (lline)
 241             if m and m.start() == 0:
 242                 fmt = lline [:m.end ()]
 243                 data = lline [m.end():].strip ()
 244             else:
 245                 fmt = ""
 246                 data = line_buf.strip ()
 247 
 248             # in extended mode, adjust leading spaces of data lines so
 249             # that the first data line has none, and all other data lines
 250             # are aligned relatively to the first one; for lists, preserve
 251             # one leading space
 252             if self.opt_ext:
 253                 start = cur_indent # number of unwanted leading spaces
 254                 for s in ["*", "1.", "a.", "A.", "i.", "I."]:
 255                     if data.startswith (s): start = start -1 # preserve 1 space
 256                 data = " "*cur_indent+data # 'unstrip' the 1st line (w/o tbl fmt)
 257                 data_lines = data.split ("\n")
 258                 for i in range (len (data_lines)):
 259                     data_lines [i] = data_lines [i] [start:] # del unwanted spaces
 260                 data = ("\n").join (data_lines)
 261 
 262 
 263             # store cell
 264             lines_info.append ( (cur_indent, fmt, data) )
 265 
 266             # ready for next line
 267             line_buf = ""
 268             last_indent = cur_indent
 269 
 270         #
 271         # generate table structure
 272         #
 273         table_fmt_buf = ""
 274 
 275         # decide whether row or column-oriented
 276         is_by_col = nb_indent_dec==0 and nb_indent_eq > 0
 277         if self.opt_col: is_by_col = True
 278         if self.opt_row: is_by_col = False
 279 
 280         # generate a token base that does not occur in the source, and
 281         # that is MoinMoin neutral, and not an HTML sequence
 282         token_base = "token"
 283         src = "\n".join (self.lines)
 284         while src.find (token_base) >=0:
 285             # append some random letter
 286             token_base = token_base + LETTERS [random.randint (0, LETTERS_LEN-1)]
 287 
 288         # function to generate tokens
 289         mk_token = lambda i: "%s%i" % (token_base, i)
 290 
 291         # function to generate a cell, either with a token, or with raw
 292         # content, depending on whether we must interpret the content
 293         if self.opt_ext: mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, mk_token (i))
 294         else:       mk_cell = lambda fmt, i, data: "||%s %s " % (fmt, data)
 295 
 296         # row-oriented structure:
 297         #  the table flow is the same as regular MoinMoin tables, all we
 298         #  have to do is detect the end of rows and generate end of lines
 299         if not is_by_col:
 300             indent = 0
 301             line_index = 0
 302             if not self.opt_ext: substitute_content = False
 303             for cur_indent, fmt, line in lines_info:
 304                 # same or lower indent ?  ==> new row: close previous and start new
 305                 if cur_indent <= indent and len (table_fmt_buf):
 306                     table_fmt_buf = table_fmt_buf +"||\n"
 307 
 308                 # add cell
 309                 table_fmt_buf = table_fmt_buf + mk_cell (fmt, line_index, line)
 310 
 311                 indent = cur_indent
 312                 line_index = line_index + 1
 313 
 314             # close table
 315             if len (table_fmt_buf): table_fmt_buf = table_fmt_buf + "||"
 316 
 317         # column-oriented structure:
 318         #  a bit more complicated; the cells must be reordered; we first
 319         #  determine the coordinates of data and store them in a (pseudo)
 320         #  table; then we generate the table structure, picking the right
 321         #  data
 322         else:
 323             # determine coordinates and store data
 324             indent = -1
 325             col, row = 0, 0
 326             max_col, max_row = 0, 0 # will be needed to generate the table
 327             table = {}
 328             for index in range (len (lines_info)):
 329                 cur_indent = lines_info [index] [0]
 330                 if cur_indent == indent:
 331                     # new row
 332                     row = row + 1
 333                     if row > max_row: max_row = row
 334                 else:
 335                     # new column
 336                     row = 1
 337                     col = col + 1
 338                     if col > max_col: max_col = col
 339                     indent = cur_indent
 340                 # store coordinates and data index
 341                 table [col-1,row-1] = index
 342 
 343             # generate table
 344             for row in range (max_row):
 345                 for col in range (max_col):
 346                     if table.has_key ((col,row)):
 347                         index = table [col,row]
 348                         fmt, line = lines_info [index] [1:]
 349                         table_fmt_buf = table_fmt_buf + mk_cell (fmt, index, line)
 350                     else:
 351                         table_fmt_buf = table_fmt_buf + "|| " # empty cell
 352                 table_fmt_buf = table_fmt_buf +"||\n"                
 353 
 354         #
 355         # final table generation
 356         #
 357 
 358         # emit debug
 359         if self.opt_dbg or self.opt_dbgf:
 360             if self.opt_dbgf:
 361                 if substitute_content:
 362                     data = "\nData:\n"
 363                     for i in range (len (lines_info)):
 364                         line = lines_info [i] [2]
 365                         data = data + "%d: [%s]\n" % (i, line)
 366                 else: data = ""
 367                 output = "{{{\nSource:\n{{ %s\n}}\n" \
 368                          "\nTable:\n%s\n" \
 369                          "%s}}}" % \
 370                          ("\n".join (self.lines), table_fmt_buf, data)
 371 
 372             else: output = "{{{\n%s\n}}}" % "\n".join (self.lines)
 373 
 374             parser = wiki.Parser (output, self.request)
 375             parser.format (formatter)
 376 
 377         # generate html for table structure, generate each cell, then
 378         # merge them
 379         if substitute_content:
 380             # gen table struct
 381             html_buf = self.reformat (table_fmt_buf, self.request, formatter)
 382             # gen cells contents and merge in table
 383             for i in range (len (lines_info)):
 384                 line = lines_info [i] [2]
 385                 token = mk_token (i)
 386                 content = self.reformat (line, self.request, formatter)
 387                 html_buf = html_buf.replace (token, content, 1)
 388             # proudly emit the result
 389             self.request.write(html_buf) # emit html-formatted content
 390 
 391         # we have the table in MoinMoin source format, just HTML-convert it
 392         else:
 393             output = "%s\n" % table_fmt_buf
 394             parser = wiki.Parser (output, self.request)
 395             parser.format (formatter)
 396 
 397         # done!
 398         return
 399 
 400     def reformat (self, src_text, request, formatter):
 401         # parse the text (in wiki source format) and make HTML,
 402         # after diverting sys.stdout to a string
 403         str_out = cStringIO.StringIO ()     # create str to collect output
 404 	
 405         # cStringIO doesn't like Unicode, so wrap with a utf8 encoder/decoder.
 406         encoder, decoder, reader, writer = codecs.lookup('utf8')
 407         utf8io = codecs.StreamReaderWriter(str_out, reader, writer, 'replace')
 408 	
 409         request.redirect (utf8io)          # divert output to that string
 410         # parse this line
 411         wiki.Parser (src_text, request).format (formatter)
 412         request.redirect ()                 # restore output
 413 	
 414         utf8io.seek(0)
 415         result = utf8io.read()
 416         str_out.close()
 417         utf8io.close()
 418         return result.strip () # return what was generated
IndentTable-0.0.3.py


MoinMoin: NigelMetheringham/IndentTable (last edited 2007-10-29 19:10:36 by localhost)