2626class TableProcessor (BlockProcessor ):
2727 """ Process Tables. """
2828
29- RE_CODE_PIPES = re .compile (r'(?:(\\\\)|(`+)|(\\\|)|(\|))' )
29+ RE_CODE_PIPES = re .compile (r'(?:(\\\\)|(\\`+)|(`+)|(\\\|)|(\|))' )
30+ RE_END_BORDER = re .compile (r'(?<!\\)(?:\\\\)*\|$' )
31+
32+ def __init__ (self , parser ):
33+ self .border = False
34+ self .separator = ''
35+ super (TableProcessor , self ).__init__ (parser )
3036
3137 def test (self , parent , block ):
32- rows = block .split ('\n ' )
33- return (len (rows ) > 1 and '|' in rows [0 ] and
34- '|' in rows [1 ] and '-' in rows [1 ] and
35- rows [1 ].strip ()[0 ] in ['|' , ':' , '-' ] and
36- set (rows [1 ]) <= set ('|:- ' ))
38+ """
39+ Ensure first two rows (column header and separator row) are valid table rows.
40+
41+ Keep border check and separator row do avoid repeating the work.
42+ """
43+ is_table = False
44+ header = [row .strip () for row in block .split ('\n ' )[0 :2 ]]
45+ if len (header ) == 2 :
46+ self .border = header [0 ].startswith ('|' )
47+ row = self ._split_row (header [0 ])
48+ is_table = len (row ) > 1
49+
50+ if is_table :
51+ row = self ._split_row (header [1 ])
52+ is_table = len (row ) > 1 and set ('' .join (row )) <= set ('|:- ' )
53+ if is_table :
54+ self .separator = row
55+ return is_table
3756
3857 def run (self , parent , blocks ):
3958 """ Parse a table block and build table. """
4059 block = blocks .pop (0 ).split ('\n ' )
4160 header = block [0 ].strip ()
42- seperator = block [1 ].strip ()
4361 rows = [] if len (block ) < 3 else block [2 :]
44- # Get format type (bordered by pipes or not)
45- border = False
46- if header .startswith ('|' ):
47- border = True
62+
4863 # Get alignment of columns
4964 align = []
50- for c in self ._split_row ( seperator , border ) :
65+ for c in self .separator :
5166 c = c .strip ()
5267 if c .startswith (':' ) and c .endswith (':' ):
5368 align .append ('center' )
@@ -57,21 +72,22 @@ def run(self, parent, blocks):
5772 align .append ('right' )
5873 else :
5974 align .append (None )
75+
6076 # Build table
6177 table = etree .SubElement (parent , 'table' )
6278 thead = etree .SubElement (table , 'thead' )
63- self ._build_row (header , thead , align , border )
79+ self ._build_row (header , thead , align )
6480 tbody = etree .SubElement (table , 'tbody' )
6581 for row in rows :
66- self ._build_row (row .strip (), tbody , align , border )
82+ self ._build_row (row .strip (), tbody , align )
6783
68- def _build_row (self , row , parent , align , border ):
84+ def _build_row (self , row , parent , align ):
6985 """ Given a row of text, build table cells. """
7086 tr = etree .SubElement (parent , 'tr' )
7187 tag = 'td'
7288 if parent .tag == 'thead' :
7389 tag = 'th'
74- cells = self ._split_row (row , border )
90+ cells = self ._split_row (row )
7591 # We use align here rather than cells to ensure every row
7692 # contains the same number of columns.
7793 for i , a in enumerate (align ):
@@ -83,13 +99,12 @@ def _build_row(self, row, parent, align, border):
8399 if a :
84100 c .set ('align' , a )
85101
86- def _split_row (self , row , border ):
102+ def _split_row (self , row ):
87103 """ split a row of text into list of cells. """
88- if border :
104+ if self . border :
89105 if row .startswith ('|' ):
90106 row = row [1 :]
91- if row .endswith ('|' ):
92- row = row [:- 1 ]
107+ row = self .RE_END_BORDER .sub ('' , row )
93108 return self ._split (row )
94109
95110 def _split (self , row ):
@@ -106,23 +121,33 @@ def _split(self, row):
106121 for m in self .RE_CODE_PIPES .finditer (row ):
107122 # Store ` data (len, start_pos, end_pos)
108123 if m .group (2 ):
124+ # \`+
125+ # Store length of each tic group: subtract \
126+ tics .append (len (m .group (2 )) - 1 )
127+ # Store start of group, end of group, and escape length
128+ tic_points .append ((m .start (2 ), m .end (2 ) - 1 , 1 ))
129+ elif m .group (3 ):
109130 # `+
110131 # Store length of each tic group
111- tics .append (len (m .group (2 )))
112- # Store start and end of tic group
113- tic_points .append ((m .start (2 ), m .end (2 ) - 1 ))
132+ tics .append (len (m .group (3 )))
133+ # Store start of group, end of group, and escape length
134+ tic_points .append ((m .start (3 ), m .end (3 ) - 1 , 0 ))
114135 # Store pipe location
115- elif m .group (4 ):
116- pipes .append (m .start (4 ))
136+ elif m .group (5 ):
137+ pipes .append (m .start (5 ))
117138
118139 # Pair up tics according to size if possible
140+ # Subtract the escape length *only* from the opening.
119141 # Walk through tic list and see if tic has a close.
120142 # Store the tic region (start of region, end of region).
121143 pos = 0
122144 tic_len = len (tics )
123145 while pos < tic_len :
124146 try :
125- index = tics [pos + 1 :].index (tics [pos ]) + 1
147+ tic_size = tics [pos ] - tic_points [pos ][2 ]
148+ if tic_size == 0 :
149+ raise ValueError
150+ index = tics [pos + 1 :].index (tic_size ) + 1
126151 tic_region .append ((tic_points [pos ][0 ], tic_points [pos + index ][1 ]))
127152 pos += index + 1
128153 except ValueError :
@@ -160,6 +185,8 @@ class TableExtension(Extension):
160185
161186 def extendMarkdown (self , md , md_globals ):
162187 """ Add an instance of TableProcessor to BlockParser. """
188+ if '|' not in md .ESCAPED_CHARS :
189+ md .ESCAPED_CHARS .append ('|' )
163190 md .parser .blockprocessors .add ('table' ,
164191 TableProcessor (md .parser ),
165192 '<hashheader' )
0 commit comments