diff options
author | Stuart Rackham <srackham@methods.co.nz> | 2009-04-21 08:49:37 +1200 |
---|---|---|
committer | Stuart Rackham <srackham@methods.co.nz> | 2009-04-21 08:49:37 +1200 |
commit | b244ea953b9e032aba3295a28fcceb5524400bd6 (patch) | |
tree | b4527403ae8f5c87d31ac49cd69c5310db749e84 | |
parent | 8e538d71d3132a9ffc9bb700dcfe761703d20d17 (diff) | |
download | asciidoc-py3-b244ea953b9e032aba3295a28fcceb5524400bd6.tar.gz |
Implemented table column spanning.
-rw-r--r-- | asciidoc.conf | 2 | ||||
-rwxr-xr-x | asciidoc.py | 109 | ||||
-rw-r--r-- | doc/asciidoc.txt | 2 | ||||
-rw-r--r-- | docbook.conf | 10 | ||||
-rw-r--r-- | examples/website/newtables.txt | 28 | ||||
-rw-r--r-- | html4.conf | 12 | ||||
-rw-r--r-- | vim/syntax/asciidoc.vim | 2 | ||||
-rw-r--r-- | xhtml11.conf | 10 |
8 files changed, 122 insertions, 53 deletions
diff --git a/asciidoc.conf b/asciidoc.conf index 3906245..eba5dac 100644 --- a/asciidoc.conf +++ b/asciidoc.conf @@ -487,7 +487,7 @@ asciidoc-style=tags="asciidoc",subs=[],filter='python "{asciidoc-file}" -b {back [tabledef-nested]
# Same as [tabledef-default] but with different delimiter and separator.
delimiter=^!={3,}$
-separator=((?P<cellcount>\d+)\*)?!
+separator=((?P<cellcount>\d+)(?P<cellop>[*+]))?!
posattrs=style
template=table
verse-style=tags="verse"
diff --git a/asciidoc.py b/asciidoc.py index 201e8d2..5f4a725 100755 --- a/asciidoc.py +++ b/asciidoc.py @@ -2590,13 +2590,20 @@ class Column: self.abswidth=None # 1.. (page units). self.pcwidth=None # 1..99 (percentage). +class Cell: + def __init__(self, data, span=1): + self.data = data + self.span = span + def __repr__(self): + return '<Cell: %d+|%s>' % (self.span, self.data) + class Table(AbstractBlock): ALIGNMENTS = {'<':'left', '>':'right', '^':'center'} FORMATS = ('psv','csv','dsv') SEPARATORS = dict( csv=',', dsv=r':|\n', - psv=r'((?P<cellcount>\d+)\*)?\|', + psv=r'((?P<cellcount>\d+)(?P<cellop>[*+]))?\|', ) def __init__(self): AbstractBlock.__init__(self) @@ -2608,7 +2615,7 @@ class Table(AbstractBlock): # Calculated parameters. self.abswidth=None # 1.. (page units). self.pcwidth = None # 1..99 (percentage). - self.rows=[] # Parsed rows, each row is a list of cell data. + self.rows=[] # Parsed rows, each row is a list of Cells. self.columns=[] # List of Columns. def load(self,name,entries): AbstractBlock.load(self,name,entries) @@ -2770,40 +2777,62 @@ class Table(AbstractBlock): Generate column related substitution attributes. """ cols = [] - i = 0 + i = 1 for col in self.columns: - i += 1 colspec = self.get_tags(col.style).colspec if colspec: self.attributes['colalign'] = col.colalign self.attributes['colabswidth'] = col.abswidth self.attributes['colpcwidth'] = col.pcwidth - self.attributes['colnumber'] = str(i+1) + self.attributes['colnumber'] = str(i) s = subs_attrs(colspec, self.attributes) if not s: warning('colspec dropped: contains undefined attribute') else: cols.append(s) + i += 1 if cols: self.attributes['colspecs'] = writer.newline.join(cols) def parse_rows(self, text): """ Parse the table source text into self.rows (a list of rows, each row - is a list of raw cell text. + is a list of Cells. """ if self.parameters.format in ('psv','dsv'): cells = self.parse_psv_dsv(text) colcount = len(self.columns) - for i in range(0, len(cells), colcount): - self.rows.append(cells[i:i+colcount]) + row = [] + i = 0 + for cell in cells: + i += cell.span + if i <= colcount: + row.append(cell) + if i >= colcount: + self.rows.append(row) + row = [] + i = 0 + if i > colcount: + warning('table row %d: span exceeds number of columns' + % len(self.rows)) elif self.parameters.format == 'csv': - self.parse_csv(text) + self.rows = self.parse_csv(text) else: assert True,'illegal table format' + # Check that all row spans match. + for i,row in enumerate(self.rows): + row_span = 0 + for cell in row: + row_span += cell.span + if i == 0: + header_span = row_span + if row_span < header_span: + warning('table row %d: does not span all columns' % (i+1)) + if row_span > header_span: + warning('table row %d: exceeds columns span' % (i+1)) def subs_rows(self, rows, rowtype='body'): """ Return a string of output markup from a list of rows, each row - is a list of raw cell text. + is a list of raw data text. """ tags = tables.tags[self.parameters.tags] if rowtype == 'header': @@ -2821,21 +2850,21 @@ class Table(AbstractBlock): return writer.newline.join(result) def subs_row(self, row, rowtype): """ - Substitute the list of cells using the cell data tag. + Substitute the list of Cells using the data tag. Returns a list of marked up table cell elements. """ - if len(row) < len(self.columns): - warning('fewer row data items than table columns') - if len(row) > len(self.columns): - warning('more row data items than table columns') result = [] - for i in range(len(self.columns)): + i = 0 + for cell in row: col = self.columns[i] tags = self.get_tags(col.style) self.attributes['colalign'] = col.colalign self.attributes['colabswidth'] = col.abswidth self.attributes['colpcwidth'] = col.pcwidth self.attributes['colnumber'] = str(i+1) + self.attributes['colspan'] = str(cell.span) + self.attributes['colstart'] = self.attributes['colnumber'] + self.attributes['colend'] = str(i+cell.span) if rowtype == 'header': dtag = tags.headdata elif rowtype == 'footer': @@ -2843,10 +2872,10 @@ class Table(AbstractBlock): else: dtag = tags.bodydata # Fill missing column data with blanks. - if i > len(row) - 1: + if i > len(self.columns) - 1: data = '' else: - data = row[i] + data = cell.data # Format header cells with the table style not column style. if rowtype == 'header': colstyle = None @@ -2868,51 +2897,61 @@ class Table(AbstractBlock): data += dovetail_tags([stag],para.split('\n'),[etag]) stag,etag = subs_tag(dtag,self.attributes) result = result + dovetail_tags([stag],data,[etag]) + i += cell.span return result def parse_csv(self,text): """ Parse the table source text and return a list of rows, each row - is a list of raw cell text. + is a list of Cells. """ import StringIO import csv - self.rows = [] + rows = [] rdr = csv.reader(StringIO.StringIO('\r\n'.join(text)), delimiter=self.parameters.separator, skipinitialspace=True) try: for row in rdr: - self.rows.append(row) + rows.append([Cell(data) for data in row]) except Exception: self.error('csv parse error: %s' % row) + return rows def parse_psv_dsv(self,text): """ Parse list of PSV or DSV table source text lines and return a list of - cells. + Cells. """ + def append_cell(data, cellcount, cellop): + if cellop == '*': # Cell multiplier. + for i in range(cellcount): + cells.append(Cell(data)) + elif cellop == '+': # Cell spanner. + cells.append(Cell(data, cellcount)) + else: + self.error('illegal table cell operator') text = '\n'.join(text) separator = '(?msu)'+self.parameters.separator format = self.parameters.format start = 0 cellcount = 1 + cellop = '*' cells = [] - cell = '' + data = '' for mo in re.finditer(separator,text): - cell += text[start:mo.start()] - if cell.endswith('\\'): - cell = cell[:-1]+mo.group() # Reinstate escaped separators. + data += text[start:mo.start()] + if data.endswith('\\'): + data = data[:-1]+mo.group() # Reinstate escaped separators. else: - for i in range(cellcount): - cells.append(cell) + append_cell(data, cellcount, cellop) cellcount = int(mo.groupdict().get('cellcount') or '1') - cell = '' + cellop = mo.groupdict().get('cellop') or '*' + data = '' start = mo.end() # Last cell follows final separator. - cell += text[start:] - for i in range(cellcount): - cells.append(cell) + data += text[start:] + append_cell(data, cellcount, cellop) # We expect a dummy blank item preceeding first PSV cell. if format == 'psv': - if cells[0] != '': + if cells[0].data != '': self.error('missing leading separator: %s' % separator, self.start) else: @@ -2951,7 +2990,9 @@ class Table(AbstractBlock): if self.parameters.format == 'csv': cols = text[0].count(self.parameters.separator) else: - cols = len(self.parse_psv_dsv(text[:1])) + cols = 0 + for cell in self.parse_psv_dsv(text[:1]): + cols += cell.span self.parse_cols(cols) # Set calculated attributes. self.attributes['colcount'] = len(self.columns) diff --git a/doc/asciidoc.txt b/doc/asciidoc.txt index 72f0381..d22c926 100644 --- a/doc/asciidoc.txt +++ b/doc/asciidoc.txt @@ -2656,7 +2656,7 @@ as a sequence of cells -- there are no mandatory row separators. cells with a separator, that really the only difference (apart from different default separators). - 'psv' and 'dsv' separators are Python regular expressions. -- The default 'psv' separator is `((?P<cellcount>\d+)\*)?\|` (a pipe +- The default 'psv' separator is `((?P<cellcount>\d+)[*+])?\|` (a pipe character with optional cell multiplier prefix); the default 'dsv' separator is `:|\n` (a colon or a line break). - 'psv' and 'dsv' cell separators can be escaped by preceding them diff --git a/docbook.conf b/docbook.conf index 9df1a87..fc0d102 100644 --- a/docbook.conf +++ b/docbook.conf @@ -335,10 +335,10 @@ template::[verseblock] # Tables.
[tabletags-default]
-colspec=<colspec colwidth="{width!{colpcwidth}*}{width?{colabswidth}{pageunits}}" align="{colalign}"/>
+colspec=<colspec colname="col_{colnumber}" colwidth="{width!{colpcwidth}*}{width?{colabswidth}{pageunits}}" align="{colalign}"/>
bodyrow=<row>|</row>
-headdata=<entry>|</entry>
-bodydata=<entry>|</entry>
+headdata=<entry{colspan@1:: namest="col_{colstart}" nameend="col_{colend}"}>|</entry>
+bodydata=<entry{colspan@1:: namest="col_{colstart}" nameend="col_{colend}"}>|</entry>
paragraph=<simpara>|</simpara>
[tabletags-emphasis]
@@ -351,11 +351,11 @@ paragraph=<simpara><emphasis role="strong">|</emphasis></simpara> paragraph=<simpara><literal>|</literal></simpara>
[tabletags-verse]
-bodydata=<entry><literallayout>|</literallayout></entry>
+bodydata=<entry{colspan@1:: namest="col_{colstart}" nameend="col_{colend}"}><literallayout>|</literallayout></entry>
paragraph=
[tabletags-literal]
-bodydata=<entry><literallayout class="monospaced">|</literallayout></entry>
+bodydata=<entry{colspan@1:: namest="col_{colstart}" nameend="col_{colend}"}><literallayout class="monospaced">|</literallayout></entry>
paragraph=
[tabletags-asciidoc]
diff --git a/examples/website/newtables.txt b/examples/website/newtables.txt index 370eda5..9f34a5c 100644 --- a/examples/website/newtables.txt +++ b/examples/website/newtables.txt @@ -395,3 +395,31 @@ Nested table |============================================== --------------------------------------------------------------------- + + +Spanned table +------------- + +.An example table +[width="75%",cols="s,^2,^2m,^2e",options="header,footer"] +|======================================================== +|Column 1 2+|Columns 2 and 3 |Column 4 +|1 3+|Item 1 +|2 |Item 2 |Item 2 |Item 2 +2+|3 |Item 3 |Item 3 +4+|4 +|footer 1 |footer 2 2+|footer 3 and 4 +|======================================================== + +.AsciiDoc source +--------------------------------------------------------------------- +[width="75%",cols="s,^2,^2m,^2e",options="header,footer"] +|======================================================== +|Column 1 2+|Columns 2 and 3 |Column 4 +|1 3+|Item 1 +|2 |Item 2 |Item 2 |Item 2 +2+|3 |Item 3 |Item 3 +4+|4 +|footer 1 |footer 2 2+|footer 3 and 4 +|======================================================== +--------------------------------------------------------------------- @@ -289,9 +289,9 @@ template::[quoteblock] # Tables.
[tabletags-default]
bodyrow=<tr>|</tr>
-headdata=<th align="{colalign}"{autowidth-option! width="{colpcwidth}%"}>|</th>
-footdata=<td align="{colalign}"{autowidth-option! width="{colpcwidth}%"} style="font-weight:bold">|</td>
-bodydata=<td align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}">|</td>
+headdata=<th {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"}>|</th>
+footdata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"} style="font-weight:bold">|</td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}">|</td>
paragraph=<p>|</p>
[tabletags-emphasis]
@@ -304,15 +304,15 @@ paragraph=<p><strong>|</strong></p> paragraph=<p><tt>|</tt></p>
[tabletags-verse]
-bodydata=<td align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><pre style="font-family: inherit;">|</pre></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><pre style="font-family: inherit;">|</pre></td>
paragraph=
[tabletags-literal]
-bodydata=<td align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><pre><tt>|</tt></pre></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><pre><tt>|</tt></pre></td>
paragraph=
[tabletags-asciidoc]
-bodydata=<td align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><div>|</div></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"{autowidth-option! width="{colpcwidth}%"} valign="{valign=top}"><div>|</div></td>
paragraph=
[table]
diff --git a/vim/syntax/asciidoc.vim b/vim/syntax/asciidoc.vim index c92664f..fdd8a90 100644 --- a/vim/syntax/asciidoc.vim +++ b/vim/syntax/asciidoc.vim @@ -72,7 +72,7 @@ syn match asciidocSidebarDelimiter /^*\{4,}$/ "See http://vimdoc.sourceforge.net/htmldoc/usr_44.html for excluding region "contents from highlighting. -syn match asciidocTablePrefix /\(\d\+\*\)\?|/ containedin=asciidocTableBlock contained +syn match asciidocTablePrefix /\(\d\+[*+]\)\?|/ containedin=asciidocTableBlock contained syn region asciidocTableBlock matchgroup=asciidocTableDelimiter start=/^|=\{3,}$/ end=/^|=\{3,}$/ keepend contains=ALL syn match asciidocTablePrefix2 /\(\d\+\*\)\?!/ containedin=asciidocTableBlock2 contained syn region asciidocTableBlock2 matchgroup=asciidocTableDelimiter2 start=/^!=\{3,}$/ end=/^!=\{3,}$/ keepend contains=ALL diff --git a/xhtml11.conf b/xhtml11.conf index 6228180..c8232b6 100644 --- a/xhtml11.conf +++ b/xhtml11.conf @@ -324,8 +324,8 @@ template::[quoteblock] # {colalign} should be in <col> but Firefox 2 & 3 require it in <td>.
colspec=<col{autowidth-option! width="{colpcwidth}%"} />
bodyrow=<tr>|</tr>
-headdata=<th align="{colalign}">|</th>
-bodydata=<td align="{colalign}">|</td>
+headdata=<th {colspan@1::colspan="{colspan}" }align="{colalign}">|</th>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}">|</td>
paragraph=<p class="table">|</p>
[tabletags-emphasis]
@@ -338,15 +338,15 @@ paragraph=<p class="table"><strong>|</strong></p> paragraph=<p class="table"><tt>|</tt></p>
[tabletags-verse]
-bodydata=<td align="{colalign}"><div class="verse">|</div></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"><div class="verse">|</div></td>
paragraph=
[tabletags-literal]
-bodydata=<td align="{colalign}"><div class="literal"><pre><tt>|</tt></pre></div></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"><div class="literal"><pre><tt>|</tt></pre></div></td>
paragraph=
[tabletags-asciidoc]
-bodydata=<td align="{colalign}"><div>|</div></td>
+bodydata=<td {colspan@1::colspan="{colspan}" }align="{colalign}"><div>|</div></td>
paragraph=
[table]
|