1 #!/usr/bin/env python3 1 #!/usr/bin/env python3 2 # -*- coding: utf-8; mode: python -*- 2 # -*- coding: utf-8; mode: python -*- 3 # pylint: disable=C0330, R0903, R0912 3 # pylint: disable=C0330, R0903, R0912 4 4 5 u""" 5 u""" 6 flat-table 6 flat-table 7 ~~~~~~~~~~ 7 ~~~~~~~~~~ 8 8 9 Implementation of the ``flat-table`` reST- 9 Implementation of the ``flat-table`` reST-directive. 10 10 11 :copyright: Copyright (C) 2016 Markus He 11 :copyright: Copyright (C) 2016 Markus Heiser 12 :license: GPL Version 2, June 1991 see 12 :license: GPL Version 2, June 1991 see linux/COPYING for details. 13 13 14 The ``flat-table`` (:py:class:`FlatTable`) 14 The ``flat-table`` (:py:class:`FlatTable`) is a double-stage list similar to 15 the ``list-table`` with some additional fe 15 the ``list-table`` with some additional features: 16 16 17 * *column-span*: with the role ``cspan`` a 17 * *column-span*: with the role ``cspan`` a cell can be extended through 18 additional columns 18 additional columns 19 19 20 * *row-span*: with the role ``rspan`` a ce 20 * *row-span*: with the role ``rspan`` a cell can be extended through 21 additional rows 21 additional rows 22 22 23 * *auto span* rightmost cell of a table ro 23 * *auto span* rightmost cell of a table row over the missing cells on the 24 right side of that table-row. With Opti 24 right side of that table-row. With Option ``:fill-cells:`` this behavior 25 can be changed from *auto span* to *auto !! 25 can changed from *auto span* to *auto fill*, which automaticly inserts 26 (empty) cells instead of spanning the la 26 (empty) cells instead of spanning the last cell. 27 27 28 Options: 28 Options: 29 29 30 * header-rows: [int] count of header row 30 * header-rows: [int] count of header rows 31 * stub-columns: [int] count of stub colum 31 * stub-columns: [int] count of stub columns 32 * widths: [[int] [int] ... ] widths 32 * widths: [[int] [int] ... ] widths of columns 33 * fill-cells: instead of autospann miss 33 * fill-cells: instead of autospann missing cells, insert missing cells 34 34 35 roles: 35 roles: 36 36 37 * cspan: [int] additionale columns (*morec 37 * cspan: [int] additionale columns (*morecols*) 38 * rspan: [int] additionale rows (*morerows 38 * rspan: [int] additionale rows (*morerows*) 39 """ 39 """ 40 40 41 # ============================================ 41 # ============================================================================== 42 # imports 42 # imports 43 # ============================================ 43 # ============================================================================== 44 44 >> 45 import sys >> 46 45 from docutils import nodes 47 from docutils import nodes 46 from docutils.parsers.rst import directives, r 48 from docutils.parsers.rst import directives, roles 47 from docutils.parsers.rst.directives.tables im 49 from docutils.parsers.rst.directives.tables import Table 48 from docutils.utils import SystemMessagePropag 50 from docutils.utils import SystemMessagePropagation 49 51 50 # ============================================ 52 # ============================================================================== 51 # common globals 53 # common globals 52 # ============================================ 54 # ============================================================================== 53 55 54 __version__ = '1.0' 56 __version__ = '1.0' 55 57 >> 58 PY3 = sys.version_info[0] == 3 >> 59 PY2 = sys.version_info[0] == 2 >> 60 >> 61 if PY3: >> 62 # pylint: disable=C0103, W0622 >> 63 unicode = str >> 64 basestring = str >> 65 56 # ============================================ 66 # ============================================================================== 57 def setup(app): 67 def setup(app): 58 # ============================================ 68 # ============================================================================== 59 69 60 app.add_directive("flat-table", FlatTable) 70 app.add_directive("flat-table", FlatTable) 61 roles.register_local_role('cspan', c_span) 71 roles.register_local_role('cspan', c_span) 62 roles.register_local_role('rspan', r_span) 72 roles.register_local_role('rspan', r_span) 63 73 64 return dict( 74 return dict( 65 version = __version__, 75 version = __version__, 66 parallel_read_safe = True, 76 parallel_read_safe = True, 67 parallel_write_safe = True 77 parallel_write_safe = True 68 ) 78 ) 69 79 70 # ============================================ 80 # ============================================================================== 71 def c_span(name, rawtext, text, lineno, inline 81 def c_span(name, rawtext, text, lineno, inliner, options=None, content=None): 72 # ============================================ 82 # ============================================================================== 73 # pylint: disable=W0613 83 # pylint: disable=W0613 74 84 75 options = options if options is not None 85 options = options if options is not None else {} 76 content = content if content is not None 86 content = content if content is not None else [] 77 nodelist = [colSpan(span=int(text))] 87 nodelist = [colSpan(span=int(text))] 78 msglist = [] 88 msglist = [] 79 return nodelist, msglist 89 return nodelist, msglist 80 90 81 # ============================================ 91 # ============================================================================== 82 def r_span(name, rawtext, text, lineno, inline 92 def r_span(name, rawtext, text, lineno, inliner, options=None, content=None): 83 # ============================================ 93 # ============================================================================== 84 # pylint: disable=W0613 94 # pylint: disable=W0613 85 95 86 options = options if options is not None 96 options = options if options is not None else {} 87 content = content if content is not None 97 content = content if content is not None else [] 88 nodelist = [rowSpan(span=int(text))] 98 nodelist = [rowSpan(span=int(text))] 89 msglist = [] 99 msglist = [] 90 return nodelist, msglist 100 return nodelist, msglist 91 101 92 102 93 # ============================================ 103 # ============================================================================== 94 class rowSpan(nodes.General, nodes.Element): p 104 class rowSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 95 class colSpan(nodes.General, nodes.Element): p 105 class colSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 96 # ============================================ 106 # ============================================================================== 97 107 98 # ============================================ 108 # ============================================================================== 99 class FlatTable(Table): 109 class FlatTable(Table): 100 # ============================================ 110 # ============================================================================== 101 111 102 u"""FlatTable (``flat-table``) directive"" 112 u"""FlatTable (``flat-table``) directive""" 103 113 104 option_spec = { 114 option_spec = { 105 'name': directives.unchanged 115 'name': directives.unchanged 106 , 'class': directives.class_option 116 , 'class': directives.class_option 107 , 'header-rows': directives.nonnegativ 117 , 'header-rows': directives.nonnegative_int 108 , 'stub-columns': directives.nonnegati 118 , 'stub-columns': directives.nonnegative_int 109 , 'widths': directives.positive_int_li 119 , 'widths': directives.positive_int_list 110 , 'fill-cells' : directives.flag } 120 , 'fill-cells' : directives.flag } 111 121 112 def run(self): 122 def run(self): 113 123 114 if not self.content: 124 if not self.content: 115 error = self.state_machine.reporte 125 error = self.state_machine.reporter.error( 116 'The "%s" directive is empty; 126 'The "%s" directive is empty; content required.' % self.name, 117 nodes.literal_block(self.block 127 nodes.literal_block(self.block_text, self.block_text), 118 line=self.lineno) 128 line=self.lineno) 119 return [error] 129 return [error] 120 130 121 title, messages = self.make_title() 131 title, messages = self.make_title() 122 node = nodes.Element() # anon 132 node = nodes.Element() # anonymous container for parsing 123 self.state.nested_parse(self.content, 133 self.state.nested_parse(self.content, self.content_offset, node) 124 134 125 tableBuilder = ListTableBuilder(self) 135 tableBuilder = ListTableBuilder(self) 126 tableBuilder.parseFlatTableNode(node) 136 tableBuilder.parseFlatTableNode(node) 127 tableNode = tableBuilder.buildTableNod 137 tableNode = tableBuilder.buildTableNode() 128 # SDK.CONSOLE() # print --> tableNode 138 # SDK.CONSOLE() # print --> tableNode.asdom().toprettyxml() 129 if title: 139 if title: 130 tableNode.insert(0, title) 140 tableNode.insert(0, title) 131 return [tableNode] + messages 141 return [tableNode] + messages 132 142 133 143 134 # ============================================ 144 # ============================================================================== 135 class ListTableBuilder(object): 145 class ListTableBuilder(object): 136 # ============================================ 146 # ============================================================================== 137 147 138 u"""Builds a table from a double-stage lis 148 u"""Builds a table from a double-stage list""" 139 149 140 def __init__(self, directive): 150 def __init__(self, directive): 141 self.directive = directive 151 self.directive = directive 142 self.rows = [] 152 self.rows = [] 143 self.max_cols = 0 153 self.max_cols = 0 144 154 145 def buildTableNode(self): 155 def buildTableNode(self): 146 156 147 colwidths = self.directive.get_colu 157 colwidths = self.directive.get_column_widths(self.max_cols) 148 if isinstance(colwidths, tuple): 158 if isinstance(colwidths, tuple): 149 # Since docutils 0.13, get_column_ 159 # Since docutils 0.13, get_column_widths returns a (widths, 150 # colwidths) tuple, where widths i 160 # colwidths) tuple, where widths is a string (i.e. 'auto'). 151 # See https://sourceforge.net/p/do 161 # See https://sourceforge.net/p/docutils/patches/120/. 152 colwidths = colwidths[1] 162 colwidths = colwidths[1] 153 stub_columns = self.directive.options. 163 stub_columns = self.directive.options.get('stub-columns', 0) 154 header_rows = self.directive.options. 164 header_rows = self.directive.options.get('header-rows', 0) 155 165 156 table = nodes.table() 166 table = nodes.table() 157 tgroup = nodes.tgroup(cols=len(colwidt 167 tgroup = nodes.tgroup(cols=len(colwidths)) 158 table += tgroup 168 table += tgroup 159 169 160 170 161 for colwidth in colwidths: 171 for colwidth in colwidths: 162 colspec = nodes.colspec(colwidth=c 172 colspec = nodes.colspec(colwidth=colwidth) 163 # FIXME: It seems, that the stub m 173 # FIXME: It seems, that the stub method only works well in the 164 # absence of rowspan (observed by !! 174 # absence of rowspan (observed by the html buidler, the docutils-xml 165 # build seems OK). This is not ex 175 # build seems OK). This is not extraordinary, because there exists 166 # no table directive (except *this 176 # no table directive (except *this* flat-table) which allows to 167 # define coexistent of rowspan and 177 # define coexistent of rowspan and stubs (there was no use-case 168 # before flat-table). This should 178 # before flat-table). This should be reviewed (later). 169 if stub_columns: 179 if stub_columns: 170 colspec.attributes['stub'] = 1 180 colspec.attributes['stub'] = 1 171 stub_columns -= 1 181 stub_columns -= 1 172 tgroup += colspec 182 tgroup += colspec 173 stub_columns = self.directive.options. 183 stub_columns = self.directive.options.get('stub-columns', 0) 174 184 175 if header_rows: 185 if header_rows: 176 thead = nodes.thead() 186 thead = nodes.thead() 177 tgroup += thead 187 tgroup += thead 178 for row in self.rows[:header_rows] 188 for row in self.rows[:header_rows]: 179 thead += self.buildTableRowNod 189 thead += self.buildTableRowNode(row) 180 190 181 tbody = nodes.tbody() 191 tbody = nodes.tbody() 182 tgroup += tbody 192 tgroup += tbody 183 193 184 for row in self.rows[header_rows:]: 194 for row in self.rows[header_rows:]: 185 tbody += self.buildTableRowNode(ro 195 tbody += self.buildTableRowNode(row) 186 return table 196 return table 187 197 188 def buildTableRowNode(self, row_data, clas 198 def buildTableRowNode(self, row_data, classes=None): 189 classes = [] if classes is None else c 199 classes = [] if classes is None else classes 190 row = nodes.row() 200 row = nodes.row() 191 for cell in row_data: 201 for cell in row_data: 192 if cell is None: 202 if cell is None: 193 continue 203 continue 194 cspan, rspan, cellElements = cell 204 cspan, rspan, cellElements = cell 195 205 196 attributes = {"classes" : classes} 206 attributes = {"classes" : classes} 197 if rspan: 207 if rspan: 198 attributes['morerows'] = rspan 208 attributes['morerows'] = rspan 199 if cspan: 209 if cspan: 200 attributes['morecols'] = cspan 210 attributes['morecols'] = cspan 201 entry = nodes.entry(**attributes) 211 entry = nodes.entry(**attributes) 202 entry.extend(cellElements) 212 entry.extend(cellElements) 203 row += entry 213 row += entry 204 return row 214 return row 205 215 206 def raiseError(self, msg): 216 def raiseError(self, msg): 207 error = self.directive.state_machine. 217 error = self.directive.state_machine.reporter.error( 208 msg 218 msg 209 , nodes.literal_block(self.directi 219 , nodes.literal_block(self.directive.block_text 210 , self.direc 220 , self.directive.block_text) 211 , line = self.directive.lineno ) 221 , line = self.directive.lineno ) 212 raise SystemMessagePropagation(error) 222 raise SystemMessagePropagation(error) 213 223 214 def parseFlatTableNode(self, node): 224 def parseFlatTableNode(self, node): 215 u"""parses the node from a :py:class:` 225 u"""parses the node from a :py:class:`FlatTable` directive's body""" 216 226 217 if len(node) != 1 or not isinstance(no 227 if len(node) != 1 or not isinstance(node[0], nodes.bullet_list): 218 self.raiseError( 228 self.raiseError( 219 'Error parsing content block f 229 'Error parsing content block for the "%s" directive: ' 220 'exactly one bullet list expec 230 'exactly one bullet list expected.' % self.directive.name ) 221 231 222 for rowNum, rowItem in enumerate(node[ 232 for rowNum, rowItem in enumerate(node[0]): 223 row = self.parseRowItem(rowItem, r 233 row = self.parseRowItem(rowItem, rowNum) 224 self.rows.append(row) 234 self.rows.append(row) 225 self.roundOffTableDefinition() 235 self.roundOffTableDefinition() 226 236 227 def roundOffTableDefinition(self): 237 def roundOffTableDefinition(self): 228 u"""Round off the table definition. 238 u"""Round off the table definition. 229 239 230 This method rounds off the table defin 240 This method rounds off the table definition in :py:member:`rows`. 231 241 232 * This method inserts the needed ``Non 242 * This method inserts the needed ``None`` values for the missing cells 233 arising from spanning cells over rows 243 arising from spanning cells over rows and/or columns. 234 244 235 * recount the :py:member:`max_cols` 245 * recount the :py:member:`max_cols` 236 246 237 * Autospan or fill (option ``fill-cell 247 * Autospan or fill (option ``fill-cells``) missing cells on the right 238 side of the table-row 248 side of the table-row 239 """ 249 """ 240 250 241 y = 0 251 y = 0 242 while y < len(self.rows): 252 while y < len(self.rows): 243 x = 0 253 x = 0 244 254 245 while x < len(self.rows[y]): 255 while x < len(self.rows[y]): 246 cell = self.rows[y][x] 256 cell = self.rows[y][x] 247 if cell is None: 257 if cell is None: 248 x += 1 258 x += 1 249 continue 259 continue 250 cspan, rspan = cell[:2] 260 cspan, rspan = cell[:2] 251 # handle colspan in current ro 261 # handle colspan in current row 252 for c in range(cspan): 262 for c in range(cspan): 253 try: 263 try: 254 self.rows[y].insert(x+ 264 self.rows[y].insert(x+c+1, None) 255 except: # pylint: disable= 265 except: # pylint: disable=W0702 256 # the user sets ambigu 266 # the user sets ambiguous rowspans 257 pass # SDK.CONSOLE() 267 pass # SDK.CONSOLE() 258 # handle colspan in spanned ro 268 # handle colspan in spanned rows 259 for r in range(rspan): 269 for r in range(rspan): 260 for c in range(cspan + 1): 270 for c in range(cspan + 1): 261 try: 271 try: 262 self.rows[y+r+1].i 272 self.rows[y+r+1].insert(x+c, None) 263 except: # pylint: disa 273 except: # pylint: disable=W0702 264 # the user sets am 274 # the user sets ambiguous rowspans 265 pass # SDK.CONSOLE 275 pass # SDK.CONSOLE() 266 x += 1 276 x += 1 267 y += 1 277 y += 1 268 278 269 # Insert the missing cells on the righ 279 # Insert the missing cells on the right side. For this, first 270 # re-calculate the max columns. 280 # re-calculate the max columns. 271 281 272 for row in self.rows: 282 for row in self.rows: 273 if self.max_cols < len(row): 283 if self.max_cols < len(row): 274 self.max_cols = len(row) 284 self.max_cols = len(row) 275 285 276 # fill with empty cells or cellspan? 286 # fill with empty cells or cellspan? 277 287 278 fill_cells = False 288 fill_cells = False 279 if 'fill-cells' in self.directive.opti 289 if 'fill-cells' in self.directive.options: 280 fill_cells = True 290 fill_cells = True 281 291 282 for row in self.rows: 292 for row in self.rows: 283 x = self.max_cols - len(row) 293 x = self.max_cols - len(row) 284 if x and not fill_cells: 294 if x and not fill_cells: 285 if row[-1] is None: 295 if row[-1] is None: 286 row.append( ( x - 1, 0, [] 296 row.append( ( x - 1, 0, []) ) 287 else: 297 else: 288 cspan, rspan, content = ro 298 cspan, rspan, content = row[-1] 289 row[-1] = (cspan + x, rspa 299 row[-1] = (cspan + x, rspan, content) 290 elif x and fill_cells: 300 elif x and fill_cells: 291 for i in range(x): 301 for i in range(x): 292 row.append( (0, 0, nodes.c 302 row.append( (0, 0, nodes.comment()) ) 293 303 294 def pprint(self): 304 def pprint(self): 295 # for debugging 305 # for debugging 296 retVal = "[ " 306 retVal = "[ " 297 for row in self.rows: 307 for row in self.rows: 298 retVal += "[ " 308 retVal += "[ " 299 for col in row: 309 for col in row: 300 if col is None: 310 if col is None: 301 retVal += ('%r' % col) 311 retVal += ('%r' % col) 302 retVal += "\n , " 312 retVal += "\n , " 303 else: 313 else: 304 content = col[2][0].astext 314 content = col[2][0].astext() 305 if len (content) > 30: 315 if len (content) > 30: 306 content = content[:30] 316 content = content[:30] + "..." 307 retVal += ('(cspan=%s, rsp 317 retVal += ('(cspan=%s, rspan=%s, %r)' 308 % (col[0], col[ 318 % (col[0], col[1], content)) 309 retVal += "]\n , " 319 retVal += "]\n , " 310 retVal = retVal[:-2] 320 retVal = retVal[:-2] 311 retVal += "]\n , " 321 retVal += "]\n , " 312 retVal = retVal[:-2] 322 retVal = retVal[:-2] 313 return retVal + "]" 323 return retVal + "]" 314 324 315 def parseRowItem(self, rowItem, rowNum): 325 def parseRowItem(self, rowItem, rowNum): 316 row = [] 326 row = [] 317 childNo = 0 327 childNo = 0 318 error = False 328 error = False 319 cell = None 329 cell = None 320 target = None 330 target = None 321 331 322 for child in rowItem: 332 for child in rowItem: 323 if (isinstance(child , nodes.comme 333 if (isinstance(child , nodes.comment) 324 or isinstance(child, nodes.sys 334 or isinstance(child, nodes.system_message)): 325 pass 335 pass 326 elif isinstance(child , nodes.targ 336 elif isinstance(child , nodes.target): 327 target = child 337 target = child 328 elif isinstance(child, nodes.bulle 338 elif isinstance(child, nodes.bullet_list): 329 childNo += 1 339 childNo += 1 330 cell = child 340 cell = child 331 else: 341 else: 332 error = True 342 error = True 333 break 343 break 334 344 335 if childNo != 1 or error: 345 if childNo != 1 or error: 336 self.raiseError( 346 self.raiseError( 337 'Error parsing content block f 347 'Error parsing content block for the "%s" directive: ' 338 'two-level bullet list expecte 348 'two-level bullet list expected, but row %s does not ' 339 'contain a second-level bullet 349 'contain a second-level bullet list.' 340 % (self.directive.name, rowNum 350 % (self.directive.name, rowNum + 1)) 341 351 342 for cellItem in cell: 352 for cellItem in cell: 343 cspan, rspan, cellElements = self. 353 cspan, rspan, cellElements = self.parseCellItem(cellItem) 344 if target is not None: 354 if target is not None: 345 cellElements.insert(0, target) 355 cellElements.insert(0, target) 346 row.append( (cspan, rspan, cellEle 356 row.append( (cspan, rspan, cellElements) ) 347 return row 357 return row 348 358 349 def parseCellItem(self, cellItem): 359 def parseCellItem(self, cellItem): 350 # search and remove cspan, rspan colsp 360 # search and remove cspan, rspan colspec from the first element in 351 # this listItem (field). 361 # this listItem (field). 352 cspan = rspan = 0 362 cspan = rspan = 0 353 if not len(cellItem): 363 if not len(cellItem): 354 return cspan, rspan, [] 364 return cspan, rspan, [] 355 for elem in cellItem[0]: 365 for elem in cellItem[0]: 356 if isinstance(elem, colSpan): 366 if isinstance(elem, colSpan): 357 cspan = elem.get("span") 367 cspan = elem.get("span") 358 elem.parent.remove(elem) 368 elem.parent.remove(elem) 359 continue 369 continue 360 if isinstance(elem, rowSpan): 370 if isinstance(elem, rowSpan): 361 rspan = elem.get("span") 371 rspan = elem.get("span") 362 elem.parent.remove(elem) 372 elem.parent.remove(elem) 363 continue 373 continue 364 return cspan, rspan, cellItem[:] 374 return cspan, rspan, cellItem[:]
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.