Formatter: CSV

Compatible with: R6.1
Current version: 0.2 (beta)
Credits: http://docs.wikkawiki.org/FormatterCSV

This formatter converts inline CSV data into a table.

1. Features

  • supports both comma (default) and semicolon as separator
  • rows of the table alternate background color
  • column alignment: \left\, /right/, |center| or default
  • table headers/footers using wiki style ==heading markers==
  • add comments in between data by starting a line with (#); blank lines are ignored
  • cells can be surrounded in double quotes ("); text in quotes is preserved e.g., whitespace, no splitting on the separator
  • it is possible to escape a separator with a backslash (\), that should appear in text
  • wiki links in cell text are linked to their internal wiki pages

2. Usage

Call via (csv [delim=[comma|semicolon])


%%(csv delim="semicolon") source%%


formatter/highlight/csv.php


<?php

/*
    converts inline csv data into a table

    (csv
        [delim="comma|semicolon"]
        )
    content

*/

$options['delim']    ??= 'comma';

$delim    = match($options['delim']) {
    
'semicolon'    => ';',
    default        => 
',',
};

$blanks        0;
$csv_lines    preg_split('/[\n]/'$text);

// asserts what precedes the ; is not a backslash \\\\, doesn't account for \\; (escaped backslash semicolon)
// https://stackoverflow.com/questions/40479546/how-to-split-on-white-spaces-not-between-quotes

// split on delimiter but not between quotes
$regex_split    '/(?<!\\\\)' $delim '(?=(?:[^\"]*([\"])[^\"]*\\1)*[^\"]*$)/';
$regex_escaped    '/\\\\' $delim '/';

echo 
'<table class="usertable"><tbody>' "\n";

foreach (
$csv_lines as $i => $line)
{
    if (
preg_match('/^#|^\s*$/'$line))
    {
        
$blanks++;
        continue;
    }

    echo 
"\t" '<tr>' "\n";

    foreach (
preg_split($regex_split$line) as $r => $field)
    {
        if (
$i == $blanks)
        {
            
$class[$r] = ''// 'padding: 1px 10px 1px 10px; ';
        
}

        if (
preg_match('/^\"?\s*==(.*)==\s*\"?$/'$field$header))
        {
            
$title[$r] = $header[1];

            if (
preg_match('/([\/\\\\|])(.*)\\1$/'$title[$r], $align))
            {
                
$class[$r] .= match($align[1]) {
                    
'/'        => 't-right',
                    
'\\'    => 't-left',
                    
'|'        => 't-center',
                };

                
$title[$r] = $align[2];
            }

            echo 
"\t\t" '<th class="' $class[$r] . '">' Ut::html($title[$r]) . '</th>' "\n";
            continue;
        }

        
// if a cell is blank, echo &nbsp;
        
if (preg_match('/^\s*$/'$field))
        {
            echo 
"\t\t" '<td class="' $class[$r] . '">&nbsp;</td>' "\n";
            continue;
        }
        
// extract the cell out of it's quotes
        
else if (preg_match('/^\s*(\"?)(.*?)\\1\s*$/'$field$matches))
        {
            if (
$matches[1] == '"')
            {
                
#$style[$r]    = 'white-space: pre; ' . $style[$r];
                
$cell        $matches[2];
            }
            else
            {
                
$cell        preg_replace($regex_escaped$delim$matches[2]);
            }

            
// [[wiki link]]
            
if (preg_match_all('/\[\[([[:alnum:]]+)\]\]/'$cell$all_links))
            {
                
$linked $cell;

                foreach (
$all_links[1] as $n => $camel_link)
                {
                    
$linked preg_replace('/\[\[' $camel_link '\]\]/'$this->link($camel_link), $linked);
                }
            }
            
// [[url label]]
            
else if (preg_match_all('/\[\[(.*?)\]\]/'$cell$all_links))
            {
                
$linked $cell;

                foreach (
$all_links[1] as $n => $url_link)
                {
                    if(
preg_match('/^(\S+)(\s+(.+))?$/us'$url_link$matches))
                    {
                        
$url    $matches[1] ?? '';
                        
$text    $matches[3] ?? '';
                        
$linked    preg_replace('#\[\[' $matches[0] . '\]\]#'$this->link($url''$text), $linked);
                    }
                }
            }
            else
            {
                
$linked Ut::html($cell);
            }

            echo 
"\t\t" '<td class="' $class[$r] . '">' $linked '</td>' "\n";
            continue;
        }

        echo 
"\t\t" '<td class="' $class[$r] . '">ERROR!</td>'// Ut::html($csv_cell)
    
}

    echo 
"\t" '</tr>' "\n";
}

echo 
'</tbody></table>' "\n";

3. Example

%%(csv delim="semicolon")
; ==/First Name/==; ==\Last Name\==; ==|Address|==; == Age ==
==Norwegian==; Sigurd; Nordmo; [[Viggo]],\, Hansteens allé 119\; 1524 MOSS; 38
==Swede==; Chanelle; Blomqvist; Överhogdal 95\; 282 02 HÖRJA; 61
==German==; Leah; Ackermann; "Landhausstraße 73; 15702 Königs Wusterhausen"; 25
# Comments are possible. Yes, the following person is a Hobbit!
==Hobbit==; Celendine; "Gam gee"; ; 216
%%


  First Name Last Name Address Age 
Norwegian Sigurd Nordmo Viggo?,\, Hansteens allé 119; 1524 MOSS 38
Swede Chanelle Blomqvist Överhogdal 95; 282 02 HÖRJA 61
German Leah Ackermann Landhausstraße 73; 15702 Königs Wusterhausen 25
Hobbit Celendine Gam gee   216

4. Changelog

0.1 Backported from WikkaWiki
0.2 Simplified & updated to work with PHP 8 (Null coalescing assignment operator, Match expression)

5. To Do

  • fix link parsing
  • add additional $options['parameter'] and CSS class styles
  • modify the formatter for your purpose