Compare commits

...

4 commits

Author SHA1 Message Date
f487d02f1c settling in
Some checks failed
CI / Linting - PHP 7.4 (push) Has been cancelled
CI / Linting - PHP 8.0 (push) Has been cancelled
CI / Linting - PHP 8.1 (push) Has been cancelled
CI / Linting - PHP 8.2 (push) Has been cancelled
2025-12-14 12:11:56 -06:00
bec3dca171 . 2025-12-07 16:39:32 -06:00
dcbf8db706 . 2025-12-07 15:43:36 -06:00
4d6169ff59 . 2025-12-07 12:44:46 -06:00
4 changed files with 594 additions and 206 deletions

View file

@ -1352,7 +1352,7 @@ class MarkdownExtra extends \Michelf\Markdown {
$text .= "<tr>\n"; $text .= "<tr>\n";
foreach ($headers as $n => $header) foreach ($headers as $n => $header)
{ {
if (preg_match('/\[([a-zA-Z]+)\]/', $header, $matches)) if (preg_match('/\[@([a-zA-Z]+)\]/', $header, $matches))
{ {
$label = $matches[1]; $label = $matches[1];
@ -1830,17 +1830,17 @@ class MarkdownExtra extends \Michelf\Markdown {
$less_than_tab = $this->tab_width - 1; $less_than_tab = $this->tab_width - 1;
$text = preg_replace_callback('{ $text = preg_replace_callback('{
^[ ]{0,' . $less_than_tab . '}\[(.+?)\][ ]?: [ ]* \n ^[ ]{0,' . $less_than_tab . '}\[@(.+?)\][ ]?: [ ]* \n
( (
(?> (?>
(?!^\[\1\]) (?!^\[@\1\])
.*\n+ .*\n+
)+ )+
) )
# Closing marker. # Closing marker.
\[\1\][ ]* (?= \n ) \[@\1\][ ]* (?= \n )
}xm', }xm',
array($this, '_stripTomCells_block_callback'), array($this, '_stripTomCells_block_callback'),
$text); $text);

View file

@ -28,6 +28,7 @@
gedit gedit
python3 python3
meld meld
zip
]; ];
}; };
}); });

67
test.md
View file

@ -6,15 +6,24 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nunc non fr
line 9 | 2
- | -
3 | 4
xyz
<table q=w>
<table q=w markdown="1">
| a | b | <p> c </p> <p> d </p> | | a | b | <p> c </p> <p> d </p> |
| - | :-: | - | | - | :-: | - |
<caption> 1 2 3 | 4 5 6 <caption> 1 2 3 | 4 5 6
<caption> q w e r t y <tr a="b"> | d | e | f | <caption> q w e r t y <tr a="b"> | d | e | f |
| g <th a="b" align="left"> h </th> i | | g <th a="b" style="text-align: left"> h </th> i |
<tr> x | y | z </tr> <tr> x | y | z </tr>
x | y | z </tr> </tbody> x | y | z </tr> </tbody>
[ def ] [ def ]
@ -30,7 +39,53 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nunc non fr
<caption> <ol> <li> 1 <li> 2 <li> 3 </ol> | foo
<table markdown=1>
tiptoe | 2
3 | 4
</table>
<table markdown=1>
tiptoe | 2
3 | 4
5 | 6
[duh]
</table>
<table markdown=1>
<tfoot>
1 | 2
<tbody>
3 | 4
<thead>
5 | 6
</table>
<table markdown=1>
1 | 2
3 | 4
5 | 6
[duh]
</table>
<caption> <ol> <li> 1 <li> 2 <li> 3 </ol> | foo88
<caption>foo<thead> bar | baz <caption>foo<thead> bar | baz
| - | -
| bar | bar
@ -58,15 +113,15 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nunc non fr
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nunc non fringilla pulvinar, augue arcu tempor lacus, vel gravida justo sapien vel nibh. Curabitur eget dignissim lorem. Vivamus sit amet urna nec lorem aliquet viverra. Aliquam erat volutpat. Maecenas sed ante vitae erat feugiat faucibus. Integer porttitor nibh eu risus suscipit, ut efficitur mauris sollicitudin. Suspendisse potenti. Integer feugiat mi sed ligula sagittis, id sagittis justo ullamcorper. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nunc non fringilla pulvinar, augue arcu tempor lacus, vel gravida justo sapien vel nibh. Curabitur eget dignissim lorem. Vivamus sit amet urna nec lorem aliquet viverra. Aliquam erat volutpat. Maecenas sed ante vitae erat feugiat faucibus. Integer porttitor nibh eu risus suscipit, ut efficitur mauris sollicitudin. Suspendisse potenti. Integer feugiat mi sed ligula sagittis, id sagittis justo ullamcorper.
<table> <table markdown="1">
|a |a117
|- |-
</table> </table>
<table> <table markdown="1">
|- |-
|b |b
</table> </table>

512
test.py
View file

@ -67,6 +67,7 @@ def do_table_line(state, line):
I'm supposed to return the whole HTML. I'm supposed to return the whole HTML.
Including the <tr> if needed. Including the <tr> if needed.
""" """
print("do_table_line");
print(f'line = "{line}"'); print(f'line = "{line}"');
@ -74,7 +75,7 @@ def do_table_line(state, line):
passthrough_pattern = fr"(?:<{tags}(?:[\s]+[^>]*)?>|</{tags}>)"; passthrough_pattern = fr"(?:<{tags}(?:[\s]+[^>]*)?>|</{tags}>)";
start_tag_pattern = fr"<{state.section_tag}(?:[\s]+[^>]*)?>" start_tag_pattern = fr"<(thead|tbody|tfoot)(?:[\s]+[^>]*)?>"
open_tr_pattern = r"<tr(?:[\s]+[^<>]*)?>[\s]*"; open_tr_pattern = r"<tr(?:[\s]+[^<>]*)?>[\s]*";
@ -84,7 +85,7 @@ def do_table_line(state, line):
caption_sentinel_pattern = "(?:" + \ caption_sentinel_pattern = "(?:" + \
'|'.join((cell_delimiter, passthrough_pattern, start_tag_pattern, '|'.join((cell_delimiter, passthrough_pattern, start_tag_pattern,
open_tr_pattern, open_caption_pattern)) + ")" open_tr_pattern, open_caption_pattern, r'</?table.*')) + ")"
already_open_tr = 0; already_open_tr = 0;
@ -94,15 +95,21 @@ def do_table_line(state, line):
# Is it whatever our start tag is? # Is it whatever our start tag is?
if (m := re.match(start_tag_pattern, line)): if (m := re.match(start_tag_pattern, line)):
# we'll pass this through, and remember that we don't need to do # we'll pass this through, and remember that we don't need to do
# it ourselves # it ourselves also possibly close previous section also change
print("found our start tag"); # "section_tag" to be thead
print(f"found our start tag: '{m.group(0)}'");
out += m.group(0); # if we're already open, close whatever that was
if state.already_opened_section:
out += f'</{state.section_tag}>' + "\n";
out += m.group(0) + "\n";
line = line[len(m.group(0)):]; line = line[len(m.group(0)):];
print(f'line = "{line}"'); print(f'line = "{line}"');
state.section_tag = m.group(1);
state.already_opened_section = 1; state.already_opened_section = 1;
continue; continue;
@ -119,7 +126,7 @@ def do_table_line(state, line):
out += f"<{state.section_tag}>"; out += f"<{state.section_tag}>";
state.already_opened_section = 1; state.already_opened_section = 1;
out += m.group(0); out += m.group(0) + "\n";
line = line[len(m.group(0)):]; line = line[len(m.group(0)):];
@ -160,7 +167,7 @@ def do_table_line(state, line):
print(f'line = "{line}"'); print(f'line = "{line}"');
out += "</caption>"; out += "</caption>" + "\n";
continue; continue;
@ -168,7 +175,7 @@ def do_table_line(state, line):
if (m := re.match(passthrough_pattern, line)): if (m := re.match(passthrough_pattern, line)):
print(f'found passthrough tag: "{m.group(0)}"'); print(f'found passthrough tag: "{m.group(0)}"');
out += m.group(0); out += m.group(0) + "\n";
line = line[len(m.group(0)):]; line = line[len(m.group(0)):];
@ -187,11 +194,11 @@ def do_table_line(state, line):
column_index = 0; column_index = 0;
if not state.already_opened_section: if not state.already_opened_section:
out += f"<{state.section_tag}>"; out += f"<{state.section_tag}>" + "\n";
state.already_opened_section = 1; state.already_opened_section = 1;
if not already_open_tr: if not already_open_tr:
out += "<tr>"; out += "<tr>" + "\n";
while line: while line:
print("new cell"); print("new cell");
@ -206,7 +213,7 @@ def do_table_line(state, line):
align = state.column_info[column_index]['align']; align = state.column_info[column_index]['align'];
if align != 'default': if align != 'default':
attributes['align'] = align; attributes['style'] = f'text-align: {align}';
print(f'attributes = "{attributes}"'); print(f'attributes = "{attributes}"');
@ -225,7 +232,7 @@ def do_table_line(state, line):
line = line[1:]; line = line[1:];
print(f'line = "{line}"'); print(f'line = "{line}"');
elif (m := re.match(r"<([a-z]+)(?:[\s]+([^<>]*))?>", line)): elif (m := re.match(r"<(th|td)(?:[\s]+([^<>]*))?>", line)):
print("found opening HTML tag"); print("found opening HTML tag");
tag = m.group(1); tag = m.group(1);
@ -262,7 +269,7 @@ def do_table_line(state, line):
line = line[len(m.group(0)):]; line = line[len(m.group(0)):];
print(f'line = "{line}"'); print(f'line = "{line}"');
elif (m := re.match(r"<([a-z]+)", line)): elif (m := re.match(r"<(th|td)", line)):
print("found HTML open, but it's incomplete? huh?! throwing!"); print("found HTML open, but it's incomplete? huh?! throwing!");
raise SyntaxError("could not find '>' for HTML open tag"); raise SyntaxError("could not find '>' for HTML open tag");
@ -272,6 +279,8 @@ def do_table_line(state, line):
tag = state.column_info[column_index]['default-tag'] tag = state.column_info[column_index]['default-tag']
print(f'tag = "{tag}"'); print(f'tag = "{tag}"');
print(f'line = "{line}"');
else: else:
print(f"found nothing, defaulting default_tag ({state.default_cell_tag})"); print(f"found nothing, defaulting default_tag ({state.default_cell_tag})");
@ -279,6 +288,8 @@ def do_table_line(state, line):
print(f'tag = "{tag}"'); print(f'tag = "{tag}"');
print(f'line = "{line}"');
print("looking for closer"); print("looking for closer");
content = ""; content = "";
@ -318,15 +329,10 @@ def do_table_line(state, line):
print(f'depth = {depth}'); print(f'depth = {depth}');
elif (m := re.match("</table>", line)): elif (m := re.match("</table>", line)):
content += m.group(0); content += m.group(0);
depth -= 1; if depth > 0: depth -= 1;
if depth < 0: raise SyntaxError("negative depth; bad HTML");
line = line[len(m.group(0)):] line = line[len(m.group(0)):]
print(f'line = "{line}"'); print(f'line = "{line}"');
print(f'depth = {depth}'); print(f'depth = {depth}');
# elif (m := re.match(fr"</{tags}>", line)):
# # ignore the closers for table parts, no passthrough
# line = line[len(m.group(0)):]
# print(f'line = "{line}"');
elif (m := re.match("<[a-z]+(?:\\s+[^<>]*)?>", line)): elif (m := re.match("<[a-z]+(?:\\s+[^<>]*)?>", line)):
content += m.group(0); content += m.group(0);
line = line[len(m.group(0)):] line = line[len(m.group(0)):]
@ -358,82 +364,358 @@ def do_table_line(state, line):
column_index += 1; column_index += 1;
# end the row of content
out += "\n";
# close tr on its own line
out += "</tr>" + "\n";
print(f'out = "{out}"'); print(f'out = "{out}"');
return out; return out;
def do_table(table_open_tag, header_lines, seperator_line, body_lines, optional_caption): # def do_table(table_open_tag, header_lines, seperator_line, body_lines, optional_caption):
#
out = ""; # # handle explicit table tag?
# if table_open_tag:
# handle explicit table tag? # open_tag = table_open_tag + "\n";
if table_open_tag: # else:
out += table_open_tag + "\n"; # # otherwise, add a default one:
else: # open_tag = "<table>" + "\n";
# otherwise, add a default one: #
out += "<table>" + "\n"; # inner = "";
#
state = State(section_tag = "thead", default_cell_tag = "th"); # state = State(section_tag = "thead", default_cell_tag = "th");
#
# Process the header lines: # # Process the header lines:
for line in header_lines: # for line in header_lines:
html_table_line = do_table_line(state, line); # inner = do_table_line(state, line);
#
out += html_table_line + "\n"; # if state.already_opened_section:
# inner += f"</{state.section_tag}>" "\n";
# Handle line seperator: #
column_info = parse_colinfo(seperator_line); # # Handle line seperator:
# column_info = parse_colinfo(seperator_line);
# Process the body lines: #
for lines in body_lines: # # Process the body lines:
state = State(section_tag = "tbody", \ # for lines in body_lines:
default_cell_tag = "td", \ # state = State(section_tag = "tbody", \
column_info = column_info); # default_cell_tag = "td", \
# column_info = column_info);
for line in lines: #
html_table_line = do_table_line(state, line); # for line in lines:
# inner += do_table_line(state, line);
out += html_table_line + "\n"; #
# if state.already_opened_section:
# Consider the optional caption # inner += f"</{state.section_tag}>" "\n";
if optional_caption: #
out += f"<caption> {optional_caption} </caption>\n"; # # Consider the optional caption.
# # If it happens, it goes before everything else
out += "</table>\n"; # if optional_caption:
# inner = f"<caption> {optional_caption} </caption>\n" + inner;
for o in out.split("\n"): #
print(o); # close_tag = "</table>\n";
#
return "\n\n" + out + "\n\n"; # for o in inner.split("\n"):
# print(o);
#
# return "\n\n" + open_tag + inner + close_tag + "\n\n";
def handle_table(m): def handle_table(m):
print("handle_table"); print("handle_table");
assert(not "TODO");
# matched = m.group(0);
# optional_table_open = m.group(1);
# one_or_more_header_lines = m.group(2);
# header_lines = one_or_more_header_lines.strip().split("\n")
# seperator_line = m.group(3);
# one_or_more_body_lines = m.group(4);
# body_lines = [e.strip().split("\n") for e in one_or_more_body_lines.strip().split("\n\n")]
# optional_caption = m.group(5);
# assert(seperator_line is not None)
# try:
# # handle explicit table tag?
# if optional_table_open:
# open_tag = optional_table_open + "\n";
# else:
# # otherwise, add a default one:
# open_tag = "<table>" + "\n";
# inner = "";
# state = State(section_tag = "thead", default_cell_tag = "th");
# # Process the header lines:
# for line in header_lines:
# inner = do_table_line(state, line);
# if state.already_opened_section:
# inner += f"</{state.section_tag}>" "\n";
# # Handle line seperator:
# column_info = parse_colinfo(seperator_line);
# # Process the body lines:
# for lines in body_lines:
# state = State(section_tag = "tbody", \
# default_cell_tag = "td", \
# column_info = column_info);
# for line in lines:
# inner += do_table_line(state, line);
# if state.already_opened_section:
# inner += f"</{state.section_tag}>" "\n";
# # Consider the optional caption.
# # If it happens, it goes before everything else
# if optional_caption:
# inner = f"<caption> {optional_caption} </caption>\n" + inner;
# close_tag = "</table>\n";
# return "\n\n" + open_tag + inner + close_tag + "\n\n";
# except SyntaxError as e:
# print(f"caught syntax error: {e}");
# print("moving on to next table...");
# return m.group(0);
def handle_table_no_sep(m):
print("handle_table_no_sep");
assert(not "TODO");
# matched = m.group(0);
# print(f'matched = """{matched}"""');
# table_open_tag = m.group(1) + "\n";
# one_or_more_body_lines = m.group(2);
# body_lines = [e.strip().split("\n") for e in one_or_more_body_lines.strip().split("\n\n")]
# optional_caption = m.group(3);
# try:
# inner = "";
# # Process the body lines:
# for lines in body_lines:
# state = State(section_tag = "tbody", \
# default_cell_tag = "td", \
# column_info = []);
# for line in lines:
# inner += do_table_line(state, line);
# if state.already_opened_section:
# inner += f"</{state.section_tag}>" "\n";
# # Consider the optional caption.
# # If it happens, it goes before everything else
# if optional_caption:
# inner = f"<caption> {optional_caption} </caption>\n" + inner;
# table_close_tag = "</table>\n";
# return "\n\n" + table_open_tag + inner + table_close_tag + "\n\n";
# except SyntaxError as e:
# print(f"caught syntax error: {e}");
# print("moving on to next table...");
# return m.group(0);
def handle_table_case_1(m):
print("handle_table_case_1");
matched = m.group(0); matched = m.group(0);
print(f'matched = """{matched}"""'); print(f'matched = """{matched}"""');
optional_table_open = m.group(1); # required open table tag:
table_open_tag = m.group(1);
one_or_more_header_lines = m.group(2); # remove the 'markdown="1"' syntax
table_open_tag = re.sub(r"markdown=(?:\"1\"|'1'|1)", "", table_open_tag);
# zero or more header rows:
header_rows = m.group(2);
# required seperator line:
seperator_line = m.group(3); seperator_line = m.group(3);
one_or_more_body_lines = m.group(4); # zero or more body rows, with empty lines of one:
body_rows = m.group(4);
# optional caption:
optional_caption = m.group(5); optional_caption = m.group(5);
assert(seperator_line is not None) try:
inner = "";
# Process the (optional) header lines:
if header_rows is not None:
state = State(section_tag = "thead", default_cell_tag = "th");
for line in header_rows.strip().split('\n'):
inner += do_table_line(state, line);
if state.already_opened_section:
inner += f"</{state.section_tag}>" "\n";
# Handle line seperator:
column_info = parse_colinfo(seperator_line);
if body_rows is not None and body_rows.strip():
for body in body_rows.strip().split('\n\n'):
state = State(section_tag = "tbody", \
default_cell_tag = "td", \
column_info = column_info);
for line in body.strip().split('\n'):
inner += do_table_line(state, line);
if state.already_opened_section:
inner += f"</{state.section_tag}>" "\n";
# Consider the optional caption.
# If it happens, it goes before everything else
if optional_caption:
inner = f"<caption> {optional_caption} </caption>\n" + inner;
table_open_tag = table_open_tag + "\n";
table_close_tag = "</table>\n";
return "\n\n" + table_open_tag + inner + table_close_tag + "\n";
except SyntaxError as e:
print(f"caught syntax error: {e}");
print("moving on to next table...");
return m.group(0);
def handle_table_case_2(m):
print("handle_table_case_2");
matched = m.group(0);
print(f'matched = """{matched}"""');
# no open table tag:
# one or more header rows:
header_rows = m.group(1);
# line seperator:
seperator_line = m.group(2);
print(f'seperator_line = "{seperator_line.strip()}"');
# one or more body rows, with empty lines of one:
body_rows = m.group(3);
print(f'body_rows = "{body_rows}"');
# optional caption:
optional_caption = m.group(4);
# no close table tag:
try: try:
return do_table( inner = "";
optional_table_open,
one_or_more_header_lines.strip().split("\n"), state = State(section_tag = "thead", default_cell_tag = "th");
seperator_line,
[e.strip().split("\n") # Process the required header lines:
for e in one_or_more_body_lines.strip().split("\n\n")], for line in header_rows.strip().split('\n'):
optional_caption, inner += do_table_line(state, line);
);
if state.already_opened_section:
inner += f"</{state.section_tag}>" "\n";
# Handle line seperator:
column_info = parse_colinfo(seperator_line);
for body in body_rows.strip().split('\n\n'):
state = State(section_tag = "tbody", \
default_cell_tag = "td", \
column_info = column_info);
for line in body.strip().split('\n'):
inner += do_table_line(state, line);
if state.already_opened_section:
inner += f"</{state.section_tag}>" "\n";
# Consider the optional caption.
# If it happens, it goes before everything else
if optional_caption:
inner = f"<caption> {optional_caption} </caption>\n" + inner;
table_open_tag = "<table>\n";
table_close_tag = "</table>\n";
return "\n\n" + table_open_tag + inner + table_close_tag + "\n";
except SyntaxError as e:
print(f"caught syntax error: {e}");
print("moving on to next table...");
return m.group(0);
def handle_table_case_3(m):
print("handle_table_case_3");
matched = m.group(0);
print(f'matched = """{matched}"""');
# required open table tag:
table_open_tag = m.group(1);
# remove the 'markdown="1"' syntax
table_open_tag = re.sub(r"markdown=(?:\"1\"|'1'|1)", "", table_open_tag);
# one or more body rows, with empty lines of one:
body_rows = m.group(2);
# no line seperator
# optional caption:
optional_caption = m.group(3);
# optional close table tag.
try:
inner = "";
for body in body_rows.strip().split('\n\n'):
state = State(section_tag = "tbody", \
default_cell_tag = "td", \
column_info = []);
for line in body.strip().split('\n'):
inner += do_table_line(state, line);
if state.already_opened_section:
inner += f"</{state.section_tag}>" "\n";
# Consider the optional caption.
# If it happens, it goes before everything else
if optional_caption:
inner = f"<caption> {optional_caption} </caption>\n" + inner;
table_open_tag = table_open_tag + "\n";
table_close_tag = "</table>\n";
return "\n\n" + table_open_tag + inner + table_close_tag + "\n";
except SyntaxError as e: except SyntaxError as e:
print(f"caught syntax error: {e}"); print(f"caught syntax error: {e}");
print("moving on to next table..."); print("moving on to next table...");
@ -443,7 +725,7 @@ with open("test.md") as stream:
text = stream.read(); text = stream.read();
# delimiters between cells # delimiters between cells
delimiter = r"(?:[|]|<(?:tr|th)(?:[\s]+[^<>]*)?>)"; delimiter = r"(?:[|]|<(?:td|th)(?:[\s]+[^<>]*)?>)";
# A row is anything with at least one delimiter # A row is anything with at least one delimiter
row = fr"(?: .* {delimiter} .*)"; row = fr"(?: .* {delimiter} .*)";
@ -466,35 +748,85 @@ row = fr"(?:{row} | {table_part}+)";
# Between the header rows and the body rows there is a line seperator. # Between the header rows and the body rows there is a line seperator.
seperator_line = r"\s* [|]? \s* [-=:]+ \s* (?: \s* [|] \s* [-=:]* \s* )* \s*" seperator_line = r"\s* [|]? \s* [-=:]+ \s* (?: \s* [|] \s* [-=:]* \s* )* \s*"
# Regex for whole table: table = fr"""
for o, c in product((1, 0), repeat=2):
table = fr"""
# two blank lines: # two blank lines:
[\n]{{2}} [\n]{{2}}
# optional or required open table tag: # required open table tag:
(?:(<table(?:[\s]+[^<>]*)?>) \n){{{o},1}} (?:(<table[\s]+[^<>]*markdown=(?:"1"|'1'|1)[^<>]*>) \n)
# zero or one or more header rows: # zero or more header rows:
((?: {row} \n){{{1-o},}}) ((?: {row} \n)+)
# line seperator: # required line seperator:
({seperator_line}) [\n] ({seperator_line} [\n])
# zero or one or more body rows, with empty lines of one: # zero or more body rows, with empty lines of one:
((?: {row} [\n]{{1,2}}){{{1-c},}}) ((?: {row} [\n]{{1,2}})*)
# optional caption: # optional caption:
(?: \[ ([a-z0-9 "']+) \] \n)? (?: \[ ([a-z0-9 "']+) \] \n)?
# optional or required close table tag: # optional close table tag:
(?: </table> [\n]){{{c},1}} (?: </table> [\n])?
# two blank lines (another newline already matched earlier)
[\n]{{1}}
""";
text = re.sub(table, handle_table_case_1, text, flags=re.VERBOSE)
table = fr"""
# two blank lines: # two blank lines:
[\n]{{2}} [\n]{{2}}
""";
text = re.sub(table, handle_table, text, flags=re.VERBOSE) # no open table tag:
# (?:(<table[\s]+[^<>]*markdown=(?:"1"|'1'|1)[^<>]*>) \n)?
# one or more header rows:
((?: {row} \n)+)
# line seperator:
({seperator_line} [\n])
# one or more body rows, with empty lines of one:
((?: {row} [\n]{{1,2}})+)
# optional caption:
(?: \[ ([a-z0-9 "']+) \] \n)?
# no close table tag:
# (?: </table> [\n])?
# two blank lines (another newline already matched earlier)
[\n]{{1}}
""";
text = re.sub(table, handle_table_case_2, text, flags=re.VERBOSE)
table = fr"""
# two blank lines:
[\n]{{2}}
# required open table tag:
(?:(<table[\s]+[^<>]*markdown=(?:"1"|'1'|1)[^<>]*>) \n)
# one or more body rows, with empty lines of one:
((?: {row} [\n]{{1,2}})+)
# no line seperator
# optional caption:
(?: \[ ([a-z0-9 "']+) \] \n)?
# optional close table tag:
(?: </table> [\n])?
# two blank lines (another newline already matched earlier)
[\n]{{1}}
""";
text = re.sub(table, handle_table_case_3, text, flags=re.VERBOSE)
text += """ text += """
<style> <style>