18c2ecf20Sopenharmony_ci#!/usr/bin/env python3
28c2ecf20Sopenharmony_ci# -*- coding: utf-8; mode: python -*-
38c2ecf20Sopenharmony_ci# pylint: disable=C0330, R0903, R0912
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ciu"""
68c2ecf20Sopenharmony_ci    flat-table
78c2ecf20Sopenharmony_ci    ~~~~~~~~~~
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci    Implementation of the ``flat-table`` reST-directive.
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci    :copyright:  Copyright (C) 2016  Markus Heiser
128c2ecf20Sopenharmony_ci    :license:    GPL Version 2, June 1991 see linux/COPYING for details.
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci    The ``flat-table`` (:py:class:`FlatTable`) is a double-stage list similar to
158c2ecf20Sopenharmony_ci    the ``list-table`` with some additional features:
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci    * *column-span*: with the role ``cspan`` a cell can be extended through
188c2ecf20Sopenharmony_ci      additional columns
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci    * *row-span*: with the role ``rspan`` a cell can be extended through
218c2ecf20Sopenharmony_ci      additional rows
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci    * *auto span* rightmost cell of a table row over the missing cells on the
248c2ecf20Sopenharmony_ci      right side of that table-row.  With Option ``:fill-cells:`` this behavior
258c2ecf20Sopenharmony_ci      can changed from *auto span* to *auto fill*, which automaticly inserts
268c2ecf20Sopenharmony_ci      (empty) cells instead of spanning the last cell.
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci    Options:
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci    * header-rows:   [int] count of header rows
318c2ecf20Sopenharmony_ci    * stub-columns:  [int] count of stub columns
328c2ecf20Sopenharmony_ci    * widths:        [[int] [int] ... ] widths of columns
338c2ecf20Sopenharmony_ci    * fill-cells:    instead of autospann missing cells, insert missing cells
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci    roles:
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci    * cspan: [int] additionale columns (*morecols*)
388c2ecf20Sopenharmony_ci    * rspan: [int] additionale rows (*morerows*)
398c2ecf20Sopenharmony_ci"""
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci# ==============================================================================
428c2ecf20Sopenharmony_ci# imports
438c2ecf20Sopenharmony_ci# ==============================================================================
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ciimport sys
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cifrom docutils import nodes
488c2ecf20Sopenharmony_cifrom docutils.parsers.rst import directives, roles
498c2ecf20Sopenharmony_cifrom docutils.parsers.rst.directives.tables import Table
508c2ecf20Sopenharmony_cifrom docutils.utils import SystemMessagePropagation
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci# ==============================================================================
538c2ecf20Sopenharmony_ci# common globals
548c2ecf20Sopenharmony_ci# ==============================================================================
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci__version__  = '1.0'
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ciPY3 = sys.version_info[0] == 3
598c2ecf20Sopenharmony_ciPY2 = sys.version_info[0] == 2
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ciif PY3:
628c2ecf20Sopenharmony_ci    # pylint: disable=C0103, W0622
638c2ecf20Sopenharmony_ci    unicode     = str
648c2ecf20Sopenharmony_ci    basestring  = str
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci# ==============================================================================
678c2ecf20Sopenharmony_cidef setup(app):
688c2ecf20Sopenharmony_ci# ==============================================================================
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci    app.add_directive("flat-table", FlatTable)
718c2ecf20Sopenharmony_ci    roles.register_local_role('cspan', c_span)
728c2ecf20Sopenharmony_ci    roles.register_local_role('rspan', r_span)
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci    return dict(
758c2ecf20Sopenharmony_ci        version = __version__,
768c2ecf20Sopenharmony_ci        parallel_read_safe = True,
778c2ecf20Sopenharmony_ci        parallel_write_safe = True
788c2ecf20Sopenharmony_ci    )
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci# ==============================================================================
818c2ecf20Sopenharmony_cidef c_span(name, rawtext, text, lineno, inliner, options=None, content=None):
828c2ecf20Sopenharmony_ci# ==============================================================================
838c2ecf20Sopenharmony_ci    # pylint: disable=W0613
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci    options  = options if options is not None else {}
868c2ecf20Sopenharmony_ci    content  = content if content is not None else []
878c2ecf20Sopenharmony_ci    nodelist = [colSpan(span=int(text))]
888c2ecf20Sopenharmony_ci    msglist  = []
898c2ecf20Sopenharmony_ci    return nodelist, msglist
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci# ==============================================================================
928c2ecf20Sopenharmony_cidef r_span(name, rawtext, text, lineno, inliner, options=None, content=None):
938c2ecf20Sopenharmony_ci# ==============================================================================
948c2ecf20Sopenharmony_ci    # pylint: disable=W0613
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci    options  = options if options is not None else {}
978c2ecf20Sopenharmony_ci    content  = content if content is not None else []
988c2ecf20Sopenharmony_ci    nodelist = [rowSpan(span=int(text))]
998c2ecf20Sopenharmony_ci    msglist  = []
1008c2ecf20Sopenharmony_ci    return nodelist, msglist
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci# ==============================================================================
1048c2ecf20Sopenharmony_ciclass rowSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321
1058c2ecf20Sopenharmony_ciclass colSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321
1068c2ecf20Sopenharmony_ci# ==============================================================================
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci# ==============================================================================
1098c2ecf20Sopenharmony_ciclass FlatTable(Table):
1108c2ecf20Sopenharmony_ci# ==============================================================================
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci    u"""FlatTable (``flat-table``) directive"""
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci    option_spec = {
1158c2ecf20Sopenharmony_ci        'name': directives.unchanged
1168c2ecf20Sopenharmony_ci        , 'class': directives.class_option
1178c2ecf20Sopenharmony_ci        , 'header-rows': directives.nonnegative_int
1188c2ecf20Sopenharmony_ci        , 'stub-columns': directives.nonnegative_int
1198c2ecf20Sopenharmony_ci        , 'widths': directives.positive_int_list
1208c2ecf20Sopenharmony_ci        , 'fill-cells' : directives.flag }
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci    def run(self):
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci        if not self.content:
1258c2ecf20Sopenharmony_ci            error = self.state_machine.reporter.error(
1268c2ecf20Sopenharmony_ci                'The "%s" directive is empty; content required.' % self.name,
1278c2ecf20Sopenharmony_ci                nodes.literal_block(self.block_text, self.block_text),
1288c2ecf20Sopenharmony_ci                line=self.lineno)
1298c2ecf20Sopenharmony_ci            return [error]
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci        title, messages = self.make_title()
1328c2ecf20Sopenharmony_ci        node = nodes.Element()          # anonymous container for parsing
1338c2ecf20Sopenharmony_ci        self.state.nested_parse(self.content, self.content_offset, node)
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci        tableBuilder = ListTableBuilder(self)
1368c2ecf20Sopenharmony_ci        tableBuilder.parseFlatTableNode(node)
1378c2ecf20Sopenharmony_ci        tableNode = tableBuilder.buildTableNode()
1388c2ecf20Sopenharmony_ci        # SDK.CONSOLE()  # print --> tableNode.asdom().toprettyxml()
1398c2ecf20Sopenharmony_ci        if title:
1408c2ecf20Sopenharmony_ci            tableNode.insert(0, title)
1418c2ecf20Sopenharmony_ci        return [tableNode] + messages
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci# ==============================================================================
1458c2ecf20Sopenharmony_ciclass ListTableBuilder(object):
1468c2ecf20Sopenharmony_ci# ==============================================================================
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci    u"""Builds a table from a double-stage list"""
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci    def __init__(self, directive):
1518c2ecf20Sopenharmony_ci        self.directive = directive
1528c2ecf20Sopenharmony_ci        self.rows      = []
1538c2ecf20Sopenharmony_ci        self.max_cols  = 0
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci    def buildTableNode(self):
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci        colwidths    = self.directive.get_column_widths(self.max_cols)
1588c2ecf20Sopenharmony_ci        if isinstance(colwidths, tuple):
1598c2ecf20Sopenharmony_ci            # Since docutils 0.13, get_column_widths returns a (widths,
1608c2ecf20Sopenharmony_ci            # colwidths) tuple, where widths is a string (i.e. 'auto').
1618c2ecf20Sopenharmony_ci            # See https://sourceforge.net/p/docutils/patches/120/.
1628c2ecf20Sopenharmony_ci            colwidths = colwidths[1]
1638c2ecf20Sopenharmony_ci        stub_columns = self.directive.options.get('stub-columns', 0)
1648c2ecf20Sopenharmony_ci        header_rows  = self.directive.options.get('header-rows', 0)
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci        table = nodes.table()
1678c2ecf20Sopenharmony_ci        tgroup = nodes.tgroup(cols=len(colwidths))
1688c2ecf20Sopenharmony_ci        table += tgroup
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci        for colwidth in colwidths:
1728c2ecf20Sopenharmony_ci            colspec = nodes.colspec(colwidth=colwidth)
1738c2ecf20Sopenharmony_ci            # FIXME: It seems, that the stub method only works well in the
1748c2ecf20Sopenharmony_ci            # absence of rowspan (observed by the html buidler, the docutils-xml
1758c2ecf20Sopenharmony_ci            # build seems OK).  This is not extraordinary, because there exists
1768c2ecf20Sopenharmony_ci            # no table directive (except *this* flat-table) which allows to
1778c2ecf20Sopenharmony_ci            # define coexistent of rowspan and stubs (there was no use-case
1788c2ecf20Sopenharmony_ci            # before flat-table). This should be reviewed (later).
1798c2ecf20Sopenharmony_ci            if stub_columns:
1808c2ecf20Sopenharmony_ci                colspec.attributes['stub'] = 1
1818c2ecf20Sopenharmony_ci                stub_columns -= 1
1828c2ecf20Sopenharmony_ci            tgroup += colspec
1838c2ecf20Sopenharmony_ci        stub_columns = self.directive.options.get('stub-columns', 0)
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci        if header_rows:
1868c2ecf20Sopenharmony_ci            thead = nodes.thead()
1878c2ecf20Sopenharmony_ci            tgroup += thead
1888c2ecf20Sopenharmony_ci            for row in self.rows[:header_rows]:
1898c2ecf20Sopenharmony_ci                thead += self.buildTableRowNode(row)
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci        tbody = nodes.tbody()
1928c2ecf20Sopenharmony_ci        tgroup += tbody
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci        for row in self.rows[header_rows:]:
1958c2ecf20Sopenharmony_ci            tbody += self.buildTableRowNode(row)
1968c2ecf20Sopenharmony_ci        return table
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci    def buildTableRowNode(self, row_data, classes=None):
1998c2ecf20Sopenharmony_ci        classes = [] if classes is None else classes
2008c2ecf20Sopenharmony_ci        row = nodes.row()
2018c2ecf20Sopenharmony_ci        for cell in row_data:
2028c2ecf20Sopenharmony_ci            if cell is None:
2038c2ecf20Sopenharmony_ci                continue
2048c2ecf20Sopenharmony_ci            cspan, rspan, cellElements = cell
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci            attributes = {"classes" : classes}
2078c2ecf20Sopenharmony_ci            if rspan:
2088c2ecf20Sopenharmony_ci                attributes['morerows'] = rspan
2098c2ecf20Sopenharmony_ci            if cspan:
2108c2ecf20Sopenharmony_ci                attributes['morecols'] = cspan
2118c2ecf20Sopenharmony_ci            entry = nodes.entry(**attributes)
2128c2ecf20Sopenharmony_ci            entry.extend(cellElements)
2138c2ecf20Sopenharmony_ci            row += entry
2148c2ecf20Sopenharmony_ci        return row
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci    def raiseError(self, msg):
2178c2ecf20Sopenharmony_ci        error =  self.directive.state_machine.reporter.error(
2188c2ecf20Sopenharmony_ci            msg
2198c2ecf20Sopenharmony_ci            , nodes.literal_block(self.directive.block_text
2208c2ecf20Sopenharmony_ci                                  , self.directive.block_text)
2218c2ecf20Sopenharmony_ci            , line = self.directive.lineno )
2228c2ecf20Sopenharmony_ci        raise SystemMessagePropagation(error)
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci    def parseFlatTableNode(self, node):
2258c2ecf20Sopenharmony_ci        u"""parses the node from a :py:class:`FlatTable` directive's body"""
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci        if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
2288c2ecf20Sopenharmony_ci            self.raiseError(
2298c2ecf20Sopenharmony_ci                'Error parsing content block for the "%s" directive: '
2308c2ecf20Sopenharmony_ci                'exactly one bullet list expected.' % self.directive.name )
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci        for rowNum, rowItem in enumerate(node[0]):
2338c2ecf20Sopenharmony_ci            row = self.parseRowItem(rowItem, rowNum)
2348c2ecf20Sopenharmony_ci            self.rows.append(row)
2358c2ecf20Sopenharmony_ci        self.roundOffTableDefinition()
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci    def roundOffTableDefinition(self):
2388c2ecf20Sopenharmony_ci        u"""Round off the table definition.
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci        This method rounds off the table definition in :py:member:`rows`.
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci        * This method inserts the needed ``None`` values for the missing cells
2438c2ecf20Sopenharmony_ci        arising from spanning cells over rows and/or columns.
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci        * recount the :py:member:`max_cols`
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci        * Autospan or fill (option ``fill-cells``) missing cells on the right
2488c2ecf20Sopenharmony_ci          side of the table-row
2498c2ecf20Sopenharmony_ci        """
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci        y = 0
2528c2ecf20Sopenharmony_ci        while y < len(self.rows):
2538c2ecf20Sopenharmony_ci            x = 0
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci            while x < len(self.rows[y]):
2568c2ecf20Sopenharmony_ci                cell = self.rows[y][x]
2578c2ecf20Sopenharmony_ci                if cell is None:
2588c2ecf20Sopenharmony_ci                    x += 1
2598c2ecf20Sopenharmony_ci                    continue
2608c2ecf20Sopenharmony_ci                cspan, rspan = cell[:2]
2618c2ecf20Sopenharmony_ci                # handle colspan in current row
2628c2ecf20Sopenharmony_ci                for c in range(cspan):
2638c2ecf20Sopenharmony_ci                    try:
2648c2ecf20Sopenharmony_ci                        self.rows[y].insert(x+c+1, None)
2658c2ecf20Sopenharmony_ci                    except: # pylint: disable=W0702
2668c2ecf20Sopenharmony_ci                        # the user sets ambiguous rowspans
2678c2ecf20Sopenharmony_ci                        pass # SDK.CONSOLE()
2688c2ecf20Sopenharmony_ci                # handle colspan in spanned rows
2698c2ecf20Sopenharmony_ci                for r in range(rspan):
2708c2ecf20Sopenharmony_ci                    for c in range(cspan + 1):
2718c2ecf20Sopenharmony_ci                        try:
2728c2ecf20Sopenharmony_ci                            self.rows[y+r+1].insert(x+c, None)
2738c2ecf20Sopenharmony_ci                        except: # pylint: disable=W0702
2748c2ecf20Sopenharmony_ci                            # the user sets ambiguous rowspans
2758c2ecf20Sopenharmony_ci                            pass # SDK.CONSOLE()
2768c2ecf20Sopenharmony_ci                x += 1
2778c2ecf20Sopenharmony_ci            y += 1
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci        # Insert the missing cells on the right side. For this, first
2808c2ecf20Sopenharmony_ci        # re-calculate the max columns.
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci        for row in self.rows:
2838c2ecf20Sopenharmony_ci            if self.max_cols < len(row):
2848c2ecf20Sopenharmony_ci                self.max_cols = len(row)
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci        # fill with empty cells or cellspan?
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci        fill_cells = False
2898c2ecf20Sopenharmony_ci        if 'fill-cells' in self.directive.options:
2908c2ecf20Sopenharmony_ci            fill_cells = True
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci        for row in self.rows:
2938c2ecf20Sopenharmony_ci            x =  self.max_cols - len(row)
2948c2ecf20Sopenharmony_ci            if x and not fill_cells:
2958c2ecf20Sopenharmony_ci                if row[-1] is None:
2968c2ecf20Sopenharmony_ci                    row.append( ( x - 1, 0, []) )
2978c2ecf20Sopenharmony_ci                else:
2988c2ecf20Sopenharmony_ci                    cspan, rspan, content = row[-1]
2998c2ecf20Sopenharmony_ci                    row[-1] = (cspan + x, rspan, content)
3008c2ecf20Sopenharmony_ci            elif x and fill_cells:
3018c2ecf20Sopenharmony_ci                for i in range(x):
3028c2ecf20Sopenharmony_ci                    row.append( (0, 0, nodes.comment()) )
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci    def pprint(self):
3058c2ecf20Sopenharmony_ci        # for debugging
3068c2ecf20Sopenharmony_ci        retVal = "[   "
3078c2ecf20Sopenharmony_ci        for row in self.rows:
3088c2ecf20Sopenharmony_ci            retVal += "[ "
3098c2ecf20Sopenharmony_ci            for col in row:
3108c2ecf20Sopenharmony_ci                if col is None:
3118c2ecf20Sopenharmony_ci                    retVal += ('%r' % col)
3128c2ecf20Sopenharmony_ci                    retVal += "\n    , "
3138c2ecf20Sopenharmony_ci                else:
3148c2ecf20Sopenharmony_ci                    content = col[2][0].astext()
3158c2ecf20Sopenharmony_ci                    if len (content) > 30:
3168c2ecf20Sopenharmony_ci                        content = content[:30] + "..."
3178c2ecf20Sopenharmony_ci                    retVal += ('(cspan=%s, rspan=%s, %r)'
3188c2ecf20Sopenharmony_ci                               % (col[0], col[1], content))
3198c2ecf20Sopenharmony_ci                    retVal += "]\n    , "
3208c2ecf20Sopenharmony_ci            retVal = retVal[:-2]
3218c2ecf20Sopenharmony_ci            retVal += "]\n  , "
3228c2ecf20Sopenharmony_ci        retVal = retVal[:-2]
3238c2ecf20Sopenharmony_ci        return retVal + "]"
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci    def parseRowItem(self, rowItem, rowNum):
3268c2ecf20Sopenharmony_ci        row = []
3278c2ecf20Sopenharmony_ci        childNo = 0
3288c2ecf20Sopenharmony_ci        error   = False
3298c2ecf20Sopenharmony_ci        cell    = None
3308c2ecf20Sopenharmony_ci        target  = None
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci        for child in rowItem:
3338c2ecf20Sopenharmony_ci            if (isinstance(child , nodes.comment)
3348c2ecf20Sopenharmony_ci                or isinstance(child, nodes.system_message)):
3358c2ecf20Sopenharmony_ci                pass
3368c2ecf20Sopenharmony_ci            elif isinstance(child , nodes.target):
3378c2ecf20Sopenharmony_ci                target = child
3388c2ecf20Sopenharmony_ci            elif isinstance(child, nodes.bullet_list):
3398c2ecf20Sopenharmony_ci                childNo += 1
3408c2ecf20Sopenharmony_ci                cell = child
3418c2ecf20Sopenharmony_ci            else:
3428c2ecf20Sopenharmony_ci                error = True
3438c2ecf20Sopenharmony_ci                break
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci        if childNo != 1 or error:
3468c2ecf20Sopenharmony_ci            self.raiseError(
3478c2ecf20Sopenharmony_ci                'Error parsing content block for the "%s" directive: '
3488c2ecf20Sopenharmony_ci                'two-level bullet list expected, but row %s does not '
3498c2ecf20Sopenharmony_ci                'contain a second-level bullet list.'
3508c2ecf20Sopenharmony_ci                % (self.directive.name, rowNum + 1))
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci        for cellItem in cell:
3538c2ecf20Sopenharmony_ci            cspan, rspan, cellElements = self.parseCellItem(cellItem)
3548c2ecf20Sopenharmony_ci            if target is not None:
3558c2ecf20Sopenharmony_ci                cellElements.insert(0, target)
3568c2ecf20Sopenharmony_ci            row.append( (cspan, rspan, cellElements) )
3578c2ecf20Sopenharmony_ci        return row
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci    def parseCellItem(self, cellItem):
3608c2ecf20Sopenharmony_ci        # search and remove cspan, rspan colspec from the first element in
3618c2ecf20Sopenharmony_ci        # this listItem (field).
3628c2ecf20Sopenharmony_ci        cspan = rspan = 0
3638c2ecf20Sopenharmony_ci        if not len(cellItem):
3648c2ecf20Sopenharmony_ci            return cspan, rspan, []
3658c2ecf20Sopenharmony_ci        for elem in cellItem[0]:
3668c2ecf20Sopenharmony_ci            if isinstance(elem, colSpan):
3678c2ecf20Sopenharmony_ci                cspan = elem.get("span")
3688c2ecf20Sopenharmony_ci                elem.parent.remove(elem)
3698c2ecf20Sopenharmony_ci                continue
3708c2ecf20Sopenharmony_ci            if isinstance(elem, rowSpan):
3718c2ecf20Sopenharmony_ci                rspan = elem.get("span")
3728c2ecf20Sopenharmony_ci                elem.parent.remove(elem)
3738c2ecf20Sopenharmony_ci                continue
3748c2ecf20Sopenharmony_ci        return cspan, rspan, cellItem[:]
375