@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhutilRemarkupTableBlockRule extends PhutilRemarkupBlockRule {
4
5 public function getMatchingLineCount(array $lines, $cursor) {
6 $num_lines = 0;
7
8 if (preg_match('/^\s*<table>/i', $lines[$cursor])) {
9 $num_lines++;
10 $cursor++;
11
12 while (isset($lines[$cursor])) {
13 $num_lines++;
14 if (preg_match('@</table>\s*$@i', $lines[$cursor])) {
15 break;
16 }
17 $cursor++;
18 }
19 }
20
21 return $num_lines;
22 }
23
24 public function markupText($text, $children) {
25 $root = id(new PhutilHTMLParser())
26 ->parseDocument($text);
27
28 $nodes = $root->selectChildrenWithTags(array('table'));
29
30 $out = array();
31 $seen_table = false;
32 foreach ($nodes as $node) {
33 if ($node->isContentNode()) {
34 $content = $node->getContent();
35
36 if (!strlen(trim($content))) {
37 // Ignore whitespace.
38 continue;
39 }
40
41 // If we find other content, fail the rule. This can happen if the
42 // input is two consecutive table tags on one line with some text
43 // in between them, which we currently forbid.
44 return $text;
45 } else {
46 // If we have multiple table tags, just return the raw text.
47 if ($seen_table) {
48 return $text;
49 }
50 $seen_table = true;
51
52 $out[] = $this->newTable($node);
53 }
54 }
55
56 if ($this->getEngine()->isTextMode()) {
57 return implode('', $out);
58 } else {
59 return phutil_implode_html('', $out);
60 }
61 }
62
63 private function newTable(PhutilDOMNode $table) {
64 $nodes = $table->selectChildrenWithTags(
65 array(
66 'colgroup',
67 'tr',
68 ));
69
70 $colgroup = null;
71 $rows = array();
72
73 foreach ($nodes as $node) {
74 if ($node->isContentNode()) {
75 $content = $node->getContent();
76
77 // If this is whitespace, ignore it.
78 if (!strlen(trim($content))) {
79 continue;
80 }
81
82 // If we have nonempty content between the rows, this isn't a valid
83 // table. We can't really do anything reasonable with this, so just
84 // fail out and render the raw text.
85 return $table->newRawString();
86 }
87
88 if ($node->getTagName() === 'colgroup') {
89 // This table has multiple "<colgroup />" tags. Just bail out.
90 if ($colgroup !== null) {
91 return $table->newRawString();
92 }
93
94 // This table has a "<colgroup />" after a "<tr />". We could parse
95 // this, but just reject it out of an abundance of caution.
96 if ($rows) {
97 return $table->newRawString();
98 }
99
100 $colgroup = $node;
101 continue;
102 }
103
104 $rows[] = $node;
105 }
106
107 $row_specs = array();
108
109 foreach ($rows as $row) {
110 $cells = $row->selectChildrenWithTags(array('td', 'th'));
111
112 $cell_specs = array();
113 foreach ($cells as $cell) {
114 if ($cell->isContentNode()) {
115 $content = $node->getContent();
116
117 if ($content === null || trim($content) === '') {
118 continue;
119 }
120
121 return $table->newRawString();
122 }
123
124 // Respect newlines in table cells as literal linebreaks.
125
126 $content = $cell->newRawContentString();
127 $content = trim($content, "\r\n");
128
129 $lines = phutil_split_lines($content, $retain_endings = false);
130 foreach ($lines as $key => $line) {
131 $lines[$key] = $this->applyRules($line);
132 }
133
134 $content = phutil_implode_html(
135 phutil_tag('br'),
136 $lines);
137
138 $cell_specs[] = array(
139 'type' => $cell->getTagName(),
140 'content' => $content,
141 );
142 }
143
144 $row_specs[] = array(
145 'type' => 'tr',
146 'content' => $cell_specs,
147 );
148 }
149
150 return $this->renderRemarkupTable($row_specs);
151 }
152
153}