1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ImprovedTableParser library 4 5 @copyright: 2012 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin import wikiutil 10 from shlex import shlex 11 from StringIO import StringIO 12 import re 13 14 # Regular expressions. 15 16 syntax = { 17 # For section markers. 18 "markers" : (r"^\s*(?P<n>\\+)(?P<b>{|})(?P=n)(?P=b)(?P=n)(?P=b)", re.MULTILINE), 19 "marker" : (r"(\\+)", 0), 20 21 # At start of line: 22 "sections" : (r"(^\s*{{{.*?^\s*}}})", re.MULTILINE | re.DOTALL), # {{{ ... }}} 23 "rows" : (r"^==", re.MULTILINE), # == 24 25 # Within text: 26 "columns" : (r"\|\|[ \t]*", 0), # || ws-excl-nl 27 28 # At start of column text: 29 "column" : (r"^\s*<(.*?)>\s*(.*)", re.DOTALL), # ws < attributes > ws 30 } 31 32 patterns = {} 33 for name, (value, flags) in syntax.items(): 34 patterns[name] = re.compile(value, re.UNICODE | flags) 35 36 # Functions. 37 38 def parse(s): 39 40 "Parse 's', returning a table definition." 41 42 s = replaceMarkers(s) 43 44 table_attrs = {} 45 rows = [] 46 47 # The following will be redefined upon the construction of the first row. 48 49 row_attrs = {} 50 columns = [] 51 52 # Process exposed text and sections. 53 54 exposed = True 55 56 # Initially, start a new row. 57 58 row_continued = False 59 60 for region in patterns["sections"].split(s): 61 62 # Only look for table features in exposed text. 63 64 if exposed: 65 66 # Extract each row from the definition. 67 68 for row_text in patterns["rows"].split(region): 69 70 # Only create a new row when a boundary has been found. 71 72 if not row_continued: 73 if columns: 74 extractAttributes(columns[0][0], row_attrs, table_attrs) 75 76 row_attrs = {} 77 columns = [] 78 rows.append((row_attrs, columns)) 79 column_continued = False 80 81 # Extract each column from the row. 82 83 for text in patterns["columns"].split(row_text): 84 85 # Only create a new column when a boundary has been found. 86 87 if not column_continued: 88 89 # Extract the attribute and text sections. 90 91 match = patterns["column"].search(text) 92 if match: 93 attribute_text, text = match.groups() 94 columns.append([parseAttributes(attribute_text, True), text]) 95 else: 96 columns.append([{}, text]) 97 98 else: 99 columns[-1][1] += text 100 101 # Permit columns immediately following this one. 102 103 column_continued = False 104 105 # Permit a continuation of the current column. 106 107 column_continued = True 108 109 # Permit rows immediately following this one. 110 111 row_continued = False 112 113 # Permit a continuation if the current row. 114 115 row_continued = True 116 117 # Write any section into the current column. 118 119 else: 120 columns[-1][1] += region 121 122 exposed = not exposed 123 124 if columns: 125 extractAttributes(columns[0][0], row_attrs, table_attrs) 126 127 return table_attrs, rows 128 129 def extractAttributes(attrs, row_attrs, table_attrs): 130 131 """ 132 Extract row- and table-level attributes from 'attrs', storing them in 133 'row_attrs' and 'table_attrs' respectively. 134 """ 135 136 for name, value in attrs.items(): 137 if name.startswith("row"): 138 row_attrs[name] = value 139 del attrs[name] 140 elif name.startswith("table"): 141 table_attrs[name] = value 142 del attrs[name] 143 144 def replaceMarkers(s): 145 146 "Convert the section notation in 's'." 147 148 l = [] 149 last = 0 150 151 # Get each marker and convert it. 152 153 for match in patterns["markers"].finditer(s): 154 start, stop = match.span() 155 l.append(s[last:start]) 156 157 # Convert the marker. 158 159 marker = [] 160 brace = True 161 for text in patterns["marker"].split(match.group()): 162 if brace: 163 marker.append(text) 164 else: 165 marker.append(text[:-1]) 166 brace = not brace 167 168 l.append("".join(marker)) 169 last = stop 170 else: 171 l.append(s[last:]) 172 173 return "".join(l) 174 175 def parseAttributes(s, escape=True): 176 177 """ 178 Parse the table attributes string 's', returning a mapping of names to 179 values. If 'escape' is set to a true value, the attributes will be suitable 180 for use with the formatter API. 181 """ 182 183 attrs = {} 184 f = StringIO(s) 185 name = None 186 need_value = False 187 188 for token in shlex(f): 189 190 # Capture the name if needed. 191 192 if name is None: 193 name = escape and wikiutil.escape(token) or token 194 195 # Detect either an equals sign or another name. 196 197 elif not need_value: 198 if token == "=": 199 need_value = True 200 else: 201 attrs[name.lower()] = escape and "true" or True 202 name = wikiutil.escape(token) 203 204 # Otherwise, capture a value. 205 206 else: 207 # Quoting of attributes done similarly to parseAttributes. 208 209 if escape and token: 210 if token[0] in ("'", '"'): 211 token = wikiutil.escape(token) 212 else: 213 token = '"%s"' % wikiutil.escape(token, 1) 214 215 attrs[name.lower()] = token 216 name = None 217 need_value = False 218 219 return attrs 220 221 # Formatting of embedded content. 222 # NOTE: Borrowed from EventAggregator. 223 224 def getParserClass(request, format): 225 226 """ 227 Return a parser class using the 'request' for the given 'format', returning 228 a plain text parser if no parser can be found for the specified 'format'. 229 """ 230 231 try: 232 return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain") 233 except wikiutil.PluginMissingError: 234 return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain") 235 236 def formatText(text, request, fmt): 237 238 "Format the given 'text' using the specified 'request' and formatter 'fmt'." 239 240 parser_cls = getParserClass(request, request.page.pi["format"]) 241 parser = parser_cls(text, request, line_anchors=False) 242 return request.redirectedOutput(parser.format, fmt, inhibit_p=True) 243 244 # Common formatting functions. 245 246 def formatTable(text, request, fmt): 247 248 "Format the given 'text' using the specified 'request' and formatter 'fmt'." 249 250 attrs, table = parse(text) 251 252 request.write(fmt.table(1, attrs)) 253 254 for row_attrs, columns in table: 255 request.write(fmt.table_row(1, row_attrs)) 256 257 for column_attrs, column_text in columns: 258 request.write(fmt.table_cell(1, column_attrs)) 259 request.write(formatText(column_text, request, fmt)) 260 request.write(fmt.table_cell(0)) 261 262 request.write(fmt.table_row(0)) 263 264 request.write(fmt.table(0)) 265 266 # vim: tabstop=4 expandtab shiftwidth=4