Repository: lzakharov/csv2md

Issue:
Escape pipe symbol in items
# Problem

Given a table with a pipe symbol (`|`) in an item, glow wrongly splits the item into pieces seperated by the pipe symbol. 
This messes up the table when it is post-rendered by other markdown tools (i.e. glow).

# Steps to reproduce: 

See the wrongly formatted line 2 of the table in the last command output (glow):

```sh
$ cat table-with-pipe-symbols.csv 
"a","b"
"foo","bar"
"test|with|pipe|symbol","baz"
"test\|with\|pipe\|symbol","baz"
"test&#124;with&#124;escaped&#124;pipe","baz"

$ cat table-with-pipe-symbols.csv | csv2md
| a                                     | b   |
| ------------------------------------- | --- |
| foo                                   | bar |
| test|with|pipe|symbol                 | baz |
| test\|with\|pipe\|symbol              | baz |
| test&#124;with&#124;escaped&#124;pipe | baz |

$ cat table-with-pipe-symbols.csv | csv2md | glow

                                                                                  
  a                                                │b                             
  ─────────────────────────────────────────────────┼──────────────────────────────
  foo                                              │bar                           
  test                                             │with                          
  test|with|pipe|symbol                            │baz                           
  test|with|escaped|pipe                           │baz          
``` 


# Solution

Please properly escape pipe symbols (`|`) in csv items, as the [Markdownguide: Escaping Pipe Characters in ables](https://www.markdownguide.org/extended-syntax/#escaping-pipe-characters-in-tables) proposes:

    You can display a pipe (|) character in a table by using its HTML character code (&#124;).

Escaping with a backslash (`\|`) also works, and it much more readabled, so this might be the preferred solution.

Hints from issue comments:
@varac: Just found out that miller also had this problem and they fixed it with backslahing the pipe: https://github.com/johnkerl/miller/pull/691@lzakharov: Hi, @varac! Thanks for this issue, seems reasonable. Will try to fix it this week.@varac: Thanks !!


Solution patch:
diff --git a/csv2md/table.py b/csv2md/table.py
index 36a9ae1..eb7691d 100644
--- a/csv2md/table.py
+++ b/csv2md/table.py
@@ -6,36 +6,43 @@
 class Table:
     def __init__(self, cells):
         self.cells = cells
-        self.widths = list(map(max, zip(*[list(map(len, row)) for row in cells])))
+        self.widths = self.__widths(cells)
 
     def markdown(self, center_aligned_columns=None, right_aligned_columns=None, no_header_row=False):
         if len(self.cells) == 0:
             return ''
 
+        cells = [[cell.replace('|', r'\|') for cell in row] for row in self.cells]
+        widths = self.__widths(cells)
+
         def ljust_row(row):
-            return [cell.ljust(width) for cell, width in zip(row, self.widths)]
+            return [cell.ljust(width) for cell, width in zip(row, widths)]
 
         def format_row(row):
             return '| ' + ' | '.join(row) + ' |'
 
-        rows = [format_row(ljust_row(row)) for row in self.cells]
-        separators = ['-' * width for width in self.widths]
+        rows = [format_row(ljust_row(row)) for row in cells]
+        separators = ['-' * width for width in widths]
 
         if right_aligned_columns is not None:
             for column in right_aligned_columns:
-                separators[column] = ('-' * (self.widths[column] - 1)) + ':'
+                separators[column] = ('-' * (widths[column] - 1)) + ':'
         if center_aligned_columns is not None:
             for column in center_aligned_columns:
-                separators[column] = ':' + ('-' * (self.widths[column] - 2)) + ':'
+                separators[column] = ':' + ('-' * (widths[column] - 2)) + ':'
 
         if no_header_row:
-            width = len(self.cells[0])
+            width = len(cells[0])
             rows.insert(0, format_row(ljust_row(self.make_default_headers(width))))
 
         rows.insert(1, format_row(separators))
 
         return '\n'.join(rows)
 
+    @staticmethod
+    def __widths(cells):
+        return list(map(max, zip(*[list(map(len, row)) for row in cells])))
+
     @staticmethod
     def parse_csv(file, delimiter=',', quotechar='"', columns=None):
         reader = csv.reader(file, delimiter=delimiter, quotechar=quotechar)


Test patch:
diff --git a/csv2md/test_table.py b/csv2md/test_table.py
index 0ca823e..fe18836 100644
--- a/csv2md/test_table.py
+++ b/csv2md/test_table.py
@@ -7,57 +7,57 @@
     'year,make,model,description,price\n'
     '1997,Ford,E350,"ac, abs, moon",3000.00\n'
     '1999,Chevy,"Venture «Extended Edition»","",4900.00\n'
-    '1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00'
+    '1996,Jeep,Grand Cherokee,"MUST SELL! air | moon roof | loaded",4799.00'
 )
 
 normal_csv_with_custom_delimiter = (
     'year;make;model;description;price\n'
     '1997;Ford;E350;"ac, abs, moon";3000.00\n'
     '1999;Chevy;"Venture «Extended Edition»";"";4900.00\n'
-    '1996;Jeep;Grand Cherokee;"MUST SELL! air, moon roof, loaded";4799.00'
+    '1996;Jeep;Grand Cherokee;"MUST SELL! air | moon roof | loaded";4799.00'
 )
 
 normal_cells = [
     ['year', 'make', 'model', 'description', 'price'],
     ['1997', 'Ford', 'E350', 'ac, abs, moon', '3000.00'],
     ['1999', 'Chevy', 'Venture «Extended Edition»', '', '4900.00'],
-    ['1996', 'Jeep', 'Grand Cherokee', 'MUST SELL! air, moon roof, loaded', '4799.00']
+    ['1996', 'Jeep', 'Grand Cherokee', 'MUST SELL! air | moon roof | loaded', '4799.00']
 ]
 
-normal_widths = [4, 5, 26, 33, 7]
+normal_widths = [4, 5, 26, 35, 7]
 
 filtered_columns_cells = [
     ['year', 'model', 'description'],
     ['1997', 'E350', 'ac, abs, moon'],
     ['1999', 'Venture «Extended Edition»', ''],
-    ['1996', 'Grand Cherokee', 'MUST SELL! air, moon roof, loaded']
+    ['1996', 'Grand Cherokee', 'MUST SELL! air | moon roof | loaded']
 ]
 
-filtered_columns_widths = [4, 26, 33]
+filtered_columns_widths = [4, 26, 35]
 
 normal_md = (
-    '| year | make  | model                      | description                       | price   |\n'
-    '| ---- | ----- | -------------------------- | --------------------------------- | ------- |\n'
-    '| 1997 | Ford  | E350                       | ac, abs, moon                     | 3000.00 |\n'
-    '| 1999 | Chevy | Venture «Extended Edition» |                                   | 4900.00 |\n'
-    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air, moon roof, loaded | 4799.00 |'
+    '| year | make  | model                      | description                           | price   |\n'
+    '| ---- | ----- | -------------------------- | ------------------------------------- | ------- |\n'
+    '| 1997 | Ford  | E350                       | ac, abs, moon                         | 3000.00 |\n'
+    '| 1999 | Chevy | Venture «Extended Edition» |                                       | 4900.00 |\n'
+    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air \| moon roof \| loaded | 4799.00 |'
 )
 
 normal_md_with_alignment = (
-    '| year | make  | model                      | description                       | price   |\n'
-    '| ---- | :---: | :------------------------: | --------------------------------- | ------: |\n'
-    '| 1997 | Ford  | E350                       | ac, abs, moon                     | 3000.00 |\n'
-    '| 1999 | Chevy | Venture «Extended Edition» |                                   | 4900.00 |\n'
-    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air, moon roof, loaded | 4799.00 |'
+    '| year | make  | model                      | description                           | price   |\n'
+    '| ---- | :---: | :------------------------: | ------------------------------------- | ------: |\n'
+    '| 1997 | Ford  | E350                       | ac, abs, moon                         | 3000.00 |\n'
+    '| 1999 | Chevy | Venture «Extended Edition» |                                       | 4900.00 |\n'
+    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air \| moon roof \| loaded | 4799.00 |'
 )
 
 normal_md_with_default_columns = (
-    '| a    | b     | c                          | d                                 | e       |\n'
-    '| ---- | ----- | -------------------------- | --------------------------------- | ------- |\n'
-    '| year | make  | model                      | description                       | price   |\n'
-    '| 1997 | Ford  | E350                       | ac, abs, moon                     | 3000.00 |\n'
-    '| 1999 | Chevy | Venture «Extended Edition» |                                   | 4900.00 |\n'
-    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air, moon roof, loaded | 4799.00 |'
+    '| a    | b     | c                          | d                                     | e       |\n'
+    '| ---- | ----- | -------------------------- | ------------------------------------- | ------- |\n'
+    '| year | make  | model                      | description                           | price   |\n'
+    '| 1997 | Ford  | E350                       | ac, abs, moon                         | 3000.00 |\n'
+    '| 1999 | Chevy | Venture «Extended Edition» |                                       | 4900.00 |\n'
+    '| 1996 | Jeep  | Grand Cherokee             | MUST SELL! air \| moon roof \| loaded | 4799.00 |'
 )