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