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.
  • [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

You are not allowed to attach a file to this page.