aboutsummaryrefslogtreecommitdiffstats
path: root/markdown/extensions/tables.py
diff options
context:
space:
mode:
Diffstat (limited to 'markdown/extensions/tables.py')
-rw-r--r--markdown/extensions/tables.py79
1 files changed, 53 insertions, 26 deletions
diff --git a/markdown/extensions/tables.py b/markdown/extensions/tables.py
index 4bb2076..ebe6ffa 100644
--- a/markdown/extensions/tables.py
+++ b/markdown/extensions/tables.py
@@ -26,28 +26,43 @@ import re
class TableProcessor(BlockProcessor):
""" Process Tables. """
- RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(`+)|(\\\|)|(\|))')
+ RE_CODE_PIPES = re.compile(r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))')
+ RE_END_BORDER = re.compile(r'(?<!\\)(?:\\\\)*\|$')
+
+ def __init__(self, parser):
+ self.border = False
+ self.separator = ''
+ super(TableProcessor, self).__init__(parser)
def test(self, parent, block):
- rows = block.split('\n')
- return (len(rows) > 1 and '|' in rows[0] and
- '|' in rows[1] and '-' in rows[1] and
- rows[1].strip()[0] in ['|', ':', '-'] and
- set(rows[1]) <= set('|:- '))
+ """
+ Ensure first two rows (column header and separator row) are valid table rows.
+
+ Keep border check and separator row do avoid repeating the work.
+ """
+ is_table = False
+ header = [row.strip() for row in block.split('\n')[0:2]]
+ if len(header) == 2:
+ self.border = header[0].startswith('|')
+ row = self._split_row(header[0])
+ is_table = len(row) > 1
+
+ if is_table:
+ row = self._split_row(header[1])
+ is_table = len(row) > 1 and set(''.join(row)) <= set('|:- ')
+ if is_table:
+ self.separator = row
+ return is_table
def run(self, parent, blocks):
""" Parse a table block and build table. """
block = blocks.pop(0).split('\n')
header = block[0].strip()
- seperator = block[1].strip()
rows = [] if len(block) < 3 else block[2:]
- # Get format type (bordered by pipes or not)
- border = False
- if header.startswith('|'):
- border = True
+
# Get alignment of columns
align = []
- for c in self._split_row(seperator, border):
+ for c in self.separator:
c = c.strip()
if c.startswith(':') and c.endswith(':'):
align.append('center')
@@ -57,21 +72,22 @@ class TableProcessor(BlockProcessor):
align.append('right')
else:
align.append(None)
+
# Build table
table = etree.SubElement(parent, 'table')
thead = etree.SubElement(table, 'thead')
- self._build_row(header, thead, align, border)
+ self._build_row(header, thead, align)
tbody = etree.SubElement(table, 'tbody')
for row in rows:
- self._build_row(row.strip(), tbody, align, border)
+ self._build_row(row.strip(), tbody, align)
- def _build_row(self, row, parent, align, border):
+ def _build_row(self, row, parent, align):
""" Given a row of text, build table cells. """
tr = etree.SubElement(parent, 'tr')
tag = 'td'
if parent.tag == 'thead':
tag = 'th'
- cells = self._split_row(row, border)
+ cells = self._split_row(row)
# We use align here rather than cells to ensure every row
# contains the same number of columns.
for i, a in enumerate(align):
@@ -83,13 +99,12 @@ class TableProcessor(BlockProcessor):
if a:
c.set('align', a)
- def _split_row(self, row, border):
+ def _split_row(self, row):
""" split a row of text into list of cells. """
- if border:
+ if self.border:
if row.startswith('|'):
row = row[1:]
- if row.endswith('|'):
- row = row[:-1]
+ row = self.RE_END_BORDER.sub('', row)
return self._split(row)
def _split(self, row):
@@ -106,23 +121,33 @@ class TableProcessor(BlockProcessor):
for m in self.RE_CODE_PIPES.finditer(row):
# Store ` data (len, start_pos, end_pos)
if m.group(2):
+ # \`+
+ # Store length of each tic group: subtract \
+ tics.append(len(m.group(2)) - 1)
+ # Store start of group, end of group, and escape length
+ tic_points.append((m.start(2), m.end(2) - 1, 1))
+ elif m.group(3):
# `+
# Store length of each tic group
- tics.append(len(m.group(2)))
- # Store start and end of tic group
- tic_points.append((m.start(2), m.end(2) - 1))
+ tics.append(len(m.group(3)))
+ # Store start of group, end of group, and escape length
+ tic_points.append((m.start(3), m.end(3) - 1, 0))
# Store pipe location
- elif m.group(4):
- pipes.append(m.start(4))
+ elif m.group(5):
+ pipes.append(m.start(5))
# Pair up tics according to size if possible
+ # Subtract the escape length *only* from the opening.
# Walk through tic list and see if tic has a close.
# Store the tic region (start of region, end of region).
pos = 0
tic_len = len(tics)
while pos < tic_len:
try:
- index = tics[pos + 1:].index(tics[pos]) + 1
+ tic_size = tics[pos] - tic_points[pos][2]
+ if tic_size == 0:
+ raise ValueError
+ index = tics[pos + 1:].index(tic_size) + 1
tic_region.append((tic_points[pos][0], tic_points[pos + index][1]))
pos += index + 1
except ValueError:
@@ -160,6 +185,8 @@ class TableExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Add an instance of TableProcessor to BlockParser. """
+ if '|' not in md.ESCAPED_CHARS:
+ md.ESCAPED_CHARS.append('|')
md.parser.blockprocessors.add('table',
TableProcessor(md.parser),
'<hashheader')