3 # $Id: table_cleanup.pl 482 2008-01-12 23:07:32Z fletcher $
5 # Cleanup the spacing and alignment of MultiMarkdown tables
7 # Used by my TextMate Bundle, but can be used elsewhere as well
9 # Copyright (c) 2006-2008 Fletcher T. Penney
10 # <http://fletcherpenney.net/>
12 # MultiMarkdown Version 2.0.b5
22 # Reusable regexp's to match table
23 my $less_than_tab = 3;
42 my $table_caption = qr{
47 my $table_divider = qr{
49 [\|\-\+\:\.][ \-\+\|\:\.]*?\|[ \-\+\|\:\.]*
53 ($table_caption)? # Optional caption
54 ($first_row # First line must start at beginning
55 ($table_row)*?)? # Header Rows
56 $table_divider # Divider/Alignment definitions
57 $table_rows+ # Body Rows
58 \n?[^\n]*?\|[^\n]*? # Allow last row not to have a "\n" for cleaning while editing
59 ($table_caption)? # Optional caption
63 # Find whole tables, then break them up and process them
66 ^($whole_table) # Whole table in $1
67 (\n|\Z) # End of file or 2 blank lines
69 my $table = $1 . "\n";
70 my $table_original = $table;
75 # Strip Caption and Summary
76 $table =~ s/^$line_start\[\s*(.*?)\s*\](\[\s*(.*?)\s*\])?[ \t]*$//m;
77 $table =~ s/\n$line_start\[\s*(.*?)\s*\][ \t]*\n/\n/s;
79 $table = "\n" . $table;
81 $table =~ s/\n($table_divider)\n($table_rows+)//s;
82 my $alignment_string = $1;
86 # Process column alignment
87 while ($alignment_string =~ /\|?\s*(.+?)\s*(\||\Z)/gs) {
91 push(@alignments,"center");
93 push(@alignments,"right");
97 push(@alignments,"left");
99 if (($cell =~ /^\./) || ($cell =~ /\.$/)) {
100 push(@alignments,"char");
102 push(@alignments,"");
108 $table = $header . "\n" . $body;
110 # First pass - find widest cell in each column (for single column cells only)
111 foreach my $line (split(/\n/, $table)) {
113 while ($line =~ /(\|?\s*[^\|]+?\s*(\|+|\Z))/gs) {
114 my $cell = $1; # Width of actual text in cell
115 my $ending = $2; # Is there a trailing `|`?
117 if ($ending =~ /\|\|/) {
118 # For first pass, do single cells only
119 $count += (length($ending));
123 setWidth($count, $cell);
128 # Second pass - handle cells that span multiple rows
129 foreach my $line (split(/\n/, $table)) {
131 while ($line =~ /(\|?\s*[^\|]+?\s*(\|+|\Z))/gs) {
132 my $cell = $1; # Width of actual text in cell
133 my $ending = $2; # Is there a trailing `|`?
135 if ($ending =~ /\|\|/) {
136 setWidth($count, $cell);
137 $count += (length($ending));
144 # Fix length of alignment definitions
146 $table_original =~ s{
152 (\|?)\s*([^\|]+?)\s*(\|+|\Z)
159 my $goal_length = $max_width{$count} -3;
167 if ($cell =~ /^\:/) {
171 if ($cell =~ /\:$/) {
174 for (my $i=0;$i < $goal_length;$i++){
177 if ($cell =~ /\:$/) {
182 $opening . "$result" . $ending;
187 # Second pass - reformat table cells to appropriate width
189 $table_original =~ s{
199 if (($line =~ /^\[/) && ($line !~ /\|/)){
202 while ($line =~ /(\|?)\s*([^\|]+?)\s*(\|+|\Z)/gs) {
209 my $len = length($2); # Length of actual contents
211 # Not all first column cells have a leading `|`
214 } elsif (length($opening) > 0) {
218 # Buffer before trailing `|`
219 if (length($ending) > 0) {
223 # How much space to fill? (account for multiple columns)
225 if ($ending =~ /\|/) {
226 $width = maxWidth($count,length($ending));
228 $width = maxWidth($count, 1);
231 if ($alignments[$count] =~ /^(left)?$/) {
232 $lead = $len + $pad_lead;
233 $trail = $width - $lead - length($opening);
236 if ($alignments[$count] =~ /^right$/) {
238 if ($opening eq "") {
244 $trail = $pad_trail+length($ending);
245 $lead = $width - $trail - length($opening);
248 if ($alignments[$count] =~ /^center$/) {
250 if ($opening eq "") {
256 # Divide padding space
257 my $pad_total = $width - $len;
258 $pad_lead = int($pad_total/2)+1;
259 $pad_trail = $pad_total - $pad_lead;
260 $trail = $pad_trail+length($ending);
261 $lead = $width - $trail - length($opening);
264 $result .= $opening . sprintf("%*s", $lead, $cell) . sprintf("%*s", $trail, $ending);
266 if ($ending =~ /\|\|/) {
267 $count += (length($ending));
285 # Return the total width for a range of columns
286 my ($start_col, $cols) = @_;
289 for (my $i = $start_col;$i < ($start_col + $cols);$i++) {
290 $total += $max_width{$i};
297 # Set widths for column(s) based on cell contents
298 my ($start_col, $cell) = @_;
300 $cell =~ /(\|?)\s*([^\|]+?)\s*(\|+|\Z)/;
307 $padding++ if (length($opening) > 0); # For first cell
308 $padding++ if ($start_col > 0); # All cells except first definitely have an opening `|`
309 $padding++ if (length($closing) > 0);
311 $contents =~ s/&\s*(.*?)\s*$/$1/; # I don't remember what this does
313 my $cell_length = length($contents) + $padding + length($opening) + length($closing);
315 if ($closing =~ /\|\|/) {
316 # This cell spans multiple columns
317 my @current_max = ();
318 my $cols = length($closing);
319 my $current_total = 0;
321 for (my $i = $start_col;$i < ($start_col + $cols);$i++) {
322 $current_total += $max_width{$i};
325 if ($current_total < $cell_length) {
327 # Proportionally divide extra space
328 for (my $i = $start_col; $i < ($start_col + $cols);$i++) {
329 $max_width{$i} = int($max_width{$i} * ($cell_length/$current_total));
330 $columns{$i} = $max_width{$i};
333 for (my $i = $start_col;$i < ($start_col + $cols);$i++) {
334 $current_total += $max_width{$i};
336 my $missing = $cell_length - $current_total;
338 # Now find the amount lost from fractions, and add back to largest columns
339 foreach my $a_col (sort { $max_width{$b} <=> $max_width{$a} }keys %columns) {
341 $max_width{$a_col}++;
348 if ($max_width{$start_col}< $cell_length) {
349 $max_width{$start_col} = $cell_length;