migrate_spip-1.0.0/src/Plugin/SpipRichText/Tables.php
src/Plugin/SpipRichText/Tables.php
<?php
declare(strict_types=1);
namespace Drupal\migrate_spip\Plugin\SpipRichText;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\migrate_spip\Attribute\SpipRichText;
use Drupal\migrate_spip\SpipRichTextBase;
/**
* Manage SPIP tables.
*/
#[SpipRichText(
id: 'tables',
label: new TranslatableMarkup('Tables'),
weight: -30,
)]
final class Tables extends SpipRichTextBase {
/**
* Define th span shortcut pattern.
*
* @var string
*/
const TH_SPAN_PATTERN = '\s*(:?{{[^{}]+}}\s*)?|<';
/**
* {@inheritdoc}
*/
public function apply(string $text): string {
$patterns = $replacements = [];
// Tables init.
$patterns[] = "#^\n?\|#S";
$replacements[] = "\n\n|";
// Tables start.
$patterns[] = "#\n\n+\|#S";
$replacements[] = "\n\n\n\n|";
// Tables end.
$patterns[] = "#\|(\n\n+|\n?$)#S";
$replacements[] = "|\n\n\n\n";
$text = preg_replace(
$patterns,
$replacements,
$text
);
return preg_replace_callback(
"#((?!\n\|)*)(\n\|.*\|\n)([^|]+|$)#UmsS",
[self::class, 'replaceTable'],
$text
);
}
/**
* Replace SPIP table element to HTML table.
*
* @param array $match
* The matched table element.
*
* @return string
* The HTML table.
*
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
private static function replaceTable(array $match): string {
return $match[1] .
self::applyTableBlock($match[2]) .
$match[3];
}
/**
* Converting string block to tables.
*
* @param string $block
* The table block string.
*
* @return string
* The processed table block.
*
* @see https://github.com/spip/textwheel/blob/2.0/inc/texte.php
* @see traiter_tableau()
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ElseExpression)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.IfStatementAssignment)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ShortVariable)
*/
private static function applyTableBlock(string $block): string {
// Define "unique" id for array ids.
$tabid = substr(md5($block), 0, 4);
// Break the table into rows.
preg_match_all(',([|].*)[|]\n,UmsS', $block, $regs, PREG_PATTERN_ORDER);
$lignes = [];
$tableStart = $summary = '';
$l = 0;
// Process each row.
$reg_line1 = ',^(\|(' . self::TH_SPAN_PATTERN . '))+$,sS';
$reg_line_all = ',^(' . self::TH_SPAN_PATTERN . ')$,sS';
$hc = $hl = [];
$theadOk = FALSE;
foreach ($regs[1] as $ligne) {
$l++;
// Manage first line.
if (!$theadOk and $l == 1) {
// - <caption> et summary dans la premiere ligne (seulement si on n'a pas dépassé le premier thead) :
// || caption | summary || (|summary est optionnel)
if (preg_match(',^\|\|([^|]*)(\|(.*))?$,sS', rtrim($ligne, '|'), $cap)) {
$cap = array_pad($cap, 4, '');
$l = 0;
$summary = '';
$describedBy = trim($cap[3]);
$describedById = 'dby' . $tabid;
if ($describedBy) {
$summary = ' aria-describedby="' . $describedById . '"';
}
if ($caption = trim($cap[1])) {
if ($describedBy) {
$caption .= '<br /> <small id="' . $describedById . '" class="summary offscreen">' . $describedBy . '</small>';
}
$tableStart .= '<caption>' . $caption . "</caption>\n";
}
elseif ($describedBy) {
$tableStart .= '<caption id="' . $describedById . '" class="summary offscreen"><small>' . $describedBy . "</small></caption>\n";
}
}
// - <thead> sous la forme |{{titre}}|{{titre}}|
// Attention thead oblige a avoir tbody
else {
if (preg_match($reg_line1, $ligne)) {
preg_match_all('/\|([^|]*)/S', $ligne, $cols);
$ligne = '';
$cols = $cols[1];
$colspan = 1;
for ($c = count($cols) - 1; $c >= 0; $c--) {
$attr = '';
if ($cols[$c] == '<') {
$colspan++;
}
else {
if ($colspan > 1) {
$attr = ' colspan="' . $colspan . '"';
$colspan = 1;
}
// Inutile de garder le strong qui n'a servi que de marqueur.
$cols[$c] = str_replace(['{', '}'], '', $cols[$c]);
$ligne = "<th id=\"id{$tabid}_c$c\"$attr>" . trim($cols[$c]) . "</th>$ligne";
// Pour mettre dans les headers des td.
$hc[$c] = "id{$tabid}_c$c";
}
}
$tableStart .= '<thead><tr class="row_first">' .
$ligne . "</tr></thead>\n";
$l = 0;
$theadOk = TRUE;
}
}
}
// Normal line otherwise.
if ($l) {
// Gerer les listes a puce dans les cellules
// on declenche simplement sur \n- car il y a les
// -* -# -? -! (qui produisent des - !)
if (strpos($ligne, "\n-") !== FALSE) {
// Apply list formatting.
$ligne = \Drupal::service('plugin.manager.spip_rich_text')
->createInstance('lists')
->apply($ligne);
}
// Tout mettre dans un tableau 2d.
preg_match_all('/\|([^|]*)/S', $ligne, $cols);
// Pas de paragraphes dans les cellules.
foreach ($cols[1] as &$col) {
if (strlen($col = trim($col))) {
$col = preg_replace("/\n{2,}/S", '<br /> <br />', $col);
$col = str_replace("\n", self::AUTOBR . "\n", $col);
}
}
// Assembler le tableau.
$lignes[] = $cols[1];
}
}
// Maintenant qu'on a toutes les cellules,
// on prépare une liste de rowspan par défaut, à partir
// du nombre de colonnes dans la premiere ligne.
// Repérer également les colonnes numériques pour les cadrer a droite.
$rowspans = $numeric = [];
$n = $lignes ? count($lignes[0]) : 0;
$k = count($lignes);
// Distinguer les colonnes numériques à point ou a virgule,
// pour les alignements éventuels sur "," ou ".".
$numeric_class = [
'.' => 'point',
',' => 'virgule',
TRUE => '',
];
for ($i = 0; $i < $n; $i++) {
$align = TRUE;
for ($j = 0; $j < $k; $j++) {
$rowspans[$j][$i] = 1;
if ($align and preg_match('/^[{+-]*(?:\s|\d)*([.,]?)\d*[}]*$/', trim($lignes[$j][$i] ?? ''), $r)) {
if ($r[1]) {
$align = $r[1];
}
}
else {
$align = '';
}
}
$numeric[$i] = $align ? (' class="numeric ' . $numeric_class[$align] . '"') : '';
}
for ($j = 0; $j < $k; $j++) {
if (preg_match($reg_line_all, $lignes[$j][0])) {
// Pour mettre dans les headers des td.
$hl[$j] = "id{$tabid}_l$j";
}
else {
unset($hl[0]);
}
}
if (!isset($hl[0])) {
$hl = [];
} // toute la colonne ou rien
// Et on parcourt le tableau a l'envers pour ramasser les
// colspan et rowspan en passant.
$html = '';
for ($l = count($lignes) - 1; $l >= 0; $l--) {
$cols = $lignes[$l];
$colspan = 1;
$ligne = '';
for ($c = count($cols) - 1; $c >= 0; $c--) {
$attr = $numeric[$c] ?? '';
$cell = trim($cols[$c]);
if ($cell == '<') {
$colspan++;
}
elseif ($cell == '^') {
$rowspans[$l - 1][$c] += $rowspans[$l][$c];
}
else {
if ($colspan > 1) {
$attr .= ' colspan="' . $colspan . '"';
$colspan = 1;
}
if (($x = $rowspans[$l][$c] ?? NULL) > 1) {
$attr .= ' rowspan="' . $x . '"';
}
$b = ($c == 0 and isset($hl[$l])) ? 'th' : 'td';
$h = ($hc[$c] ?? '') . ' ' . (($b == 'td' and isset($hl[$l])) ? $hl[$l] : '');
if ($h = trim($h)) {
$attr .= ' headers="' . $h . '"';
}
// Inutile de garder le strong qui n'a servi que de marqueur.
if ($b == 'th') {
$attr .= ' id="' . $hl[$l] . '"';
$cols[$c] = str_replace(['{', '}'], '', $cols[$c]);
}
$ligne = "\n<$b" . $attr . '>' . trim($cols[$c]) . "</$b>" . $ligne;
}
}
// Ligne complete.
$class = $l % 2 ? 'odd' : 'even';
$html = "<tr class=\"row_$class $class\">$ligne</tr>\n$html";
}
return '<table' . $summary . ">\n"
. $tableStart
. "<tbody>\n"
. $html
. "</tbody>\n</table>";
}
}
