1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
"""
Tables Extension for Python-Markdown
====================================
Added parsing of tables to Python-Markdown.
See <https://pythonhosted.org/Markdown/extensions/tables.html>
for documentation.
Original code Copyright 2009 [Waylan Limberg](http://achinghead.com)
All changes Copyright 2008-2014 The Python Markdown Project
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
"""
from __future__ import absolute_import
from __future__ import unicode_literals
from . import Extension
from ..blockprocessors import BlockProcessor
from ..inlinepatterns import BacktickPattern, BACKTICK_RE
from ..util import etree
class TableProcessor(BlockProcessor):
""" Process Tables. """
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 ['|', ':', '-'])
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):
c = c.strip()
if c.startswith(':') and c.endswith(':'):
align.append('center')
elif c.startswith(':'):
align.append('left')
elif c.endswith(':'):
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)
tbody = etree.SubElement(table, 'tbody')
for row in rows:
self._build_row(row.strip(), tbody, align, border)
def _build_row(self, row, parent, align, border):
""" 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)
# We use align here rather than cells to ensure every row
# contains the same number of columns.
for i, a in enumerate(align):
c = etree.SubElement(tr, tag)
try:
c.text = cells[i].strip()
except IndexError: # pragma: no cover
c.text = ""
if a:
c.set('align', a)
def _split_row(self, row, border):
""" split a row of text into list of cells. """
if border:
if row.startswith('|'):
row = row[1:]
if row.endswith('|'):
row = row[:-1]
return self._split(row, '|')
def _split(self, row, marker):
""" split a row of text with some code into a list of cells. """
if self._row_has_unpaired_backticks(row):
# fallback on old behaviour
return row.split(marker)
# modify the backtick pattern to only match at the beginning of the search string
backtick_pattern = BacktickPattern('^' + BACKTICK_RE)
elements = []
current = ''
i = 0
while i < len(row):
letter = row[i]
if letter == marker:
if current != '' or len(elements) == 0:
# Don't append empty string unless it is the first element
# The border is already removed when we get the row, then the line is strip()'d
# If the first element is a marker, then we have an empty first cell
elements.append(current)
current = ''
else:
match = backtick_pattern.getCompiledRegExp().match(row[i:])
if not match:
current += letter
else:
groups = match.groups()
delim = groups[1] # the code block delimeter (ie 1 or more backticks)
row_contents = groups[2] # the text contained inside the code block
i += match.start(4) - 1 # jump pointer to the beginning of the rest of the text (group #4)
element = delim + row_contents + delim # reinstert backticks
current += element
i += 1
elements.append(current)
return elements
def _row_has_unpaired_backticks(self, row):
count_total_backtick = row.count('`')
count_escaped_backtick = row.count('\`')
count_backtick = count_total_backtick - count_escaped_backtick
# odd number of backticks,
# we won't be able to build correct code blocks
return count_backtick & 1
class TableExtension(Extension):
""" Add tables to Markdown. """
def extendMarkdown(self, md, md_globals):
""" Add an instance of TableProcessor to BlockParser. """
md.parser.blockprocessors.add('table',
TableProcessor(md.parser),
'<hashheader')
def makeExtension(*args, **kwargs):
return TableExtension(*args, **kwargs)
|