CSV to Textile Table Converter
A CSV to Textile converter turns a spreadsheet into a table Redmine, Basecamp classic and older Rails wikis will render. Textile marks header cells inline rather than with a separator row, and carries alignment as a per-cell modifier, both of which this writes for you. Pipes inside your data become HTML entities, since Textile has no backslash escape. Nothing is uploaded.
Need to trim or reorder columns first? Open the app
It looks like Markdown and is not
A Textile table is pipe delimited, so the first thing everybody does is write a Markdown table and hope. It renders as a table, which is the trap: it renders as a table with no header, because the row of dashes you added is being drawn as a data row full of hyphens.
Textile has no separator row. It marks each header cell inline, with an underscore and a dot at the start of the cell:
|_. sku |_. name |_. price |
| 00412 | Bracket | 4.25 |
The same slot carries alignment. A modifier goes before the dot: > right, < left, = centre, and ^ and ~ for vertical alignment. They combine with the header underscore, so a right-aligned header cell begins _>. and a right-aligned data cell begins >..
That is more expressive than Markdown, where alignment is one setting per column declared once in the separator row. In Textile you can align a single cell differently from the rest of its column, which is occasionally exactly what a summary row needs.
A worked example
Three rows, one with a pipe in a cell:
ticket,status,hours,note
1042,open,3.5,"blocked | waiting"
1043,closed,12,shipped
1044,open,0.5,triage
With a class and a border, the output is:
table(issues){border:1px solid #ccc}.
|_. ticket|_. status|_>. hours|_. note|
|1042|open|>. 3.5|blocked | waiting|
|1043|closed|>. 12|shipped|
|1044|open|>. 0.5|triage|
The hours column carries the right modifier on both its header and its cells, because it typed as numeric. The ticket column did not, even though its values look like numbers, because ticket numbers are identifiers rather than quantities and mixing that judgment with alignment is how a column of IDs ends up right aligned against a column of names.
The pipe in the note became |. Without that the row would have five cells instead of four, and Textile would render a ragged table rather than reporting an error.
Entities, attributes and Redmine
Textile has no escape character. Like MediaWiki and BBCode, there is no backslash that makes the next character literal, so the HTML entity is the only reliable route for a pipe. Every Textile implementation passes entities through to the output untouched, which is what makes the trick work rather than merely plausible.
The table attribute line above the rows carries the table's own properties. A class goes in parentheses, so table(issues). attaches your stylesheet's issues class. An inline style goes in braces. Both together read table(issues){border:1px solid #ccc}. and the trailing dot is required, which is the sort of thing that is obvious once and infuriating the first time.
The bordered option exists because Redmine's default stylesheet gives a plain Textile table no visible grid at all. If you want the table to look like a table rather than columns of floating text, the inline border is the shortest way there without editing a theme.
A caption is written as a row using the centre modifier, |=. My caption |, which is how Textile spells one. It renders as a spanning row above the table.
Line breaks inside a cell become spaces, with a count. A Textile row is one line and a raw newline in the middle of a cell ends it early, leaving the tail of your value as a broken row underneath.
Questions
Why is there no row of dashes under the header?
Because Textile does not use one. Unlike Markdown, where a separator row declares the header and the alignment, Textile marks each header cell inline with an underscore and a dot at the start of the cell. That is correct Textile rather than a missing row, and adding a dashes row would render as a data row full of hyphens.
How does alignment work in Textile?
With a modifier before the dot at the start of each cell: > right, < left, = centre, ^ top, ~ bottom. They combine with the header underscore, so a right-aligned header cell reads _>. at the start. Numeric columns get the right modifier automatically here, and you can force one alignment for the whole table or turn modifiers off entirely.
How are pipes inside a cell handled?
As the HTML entity |. Textile has no backslash escape, so that entity is the only thing that reliably renders as a pipe rather than ending the cell. Every Textile implementation passes an HTML entity through untouched, which is what makes it safe. A warning tells you when any cell needed it.
Can I give the table a class or a caption?
Yes. A table attribute line above the rows takes a class in parentheses and an inline style in braces, so you can attach your own CSS or ask for a visible border. The caption is written as a row using the centre modifier spanning the table, which is how Textile expresses one. Redmine renders both.
Does this work with Redmine?
Yes. Redmine uses Textile as its default markup for issue descriptions, comments and wiki pages, and this output pastes straight into any of them. The bordered option produces the inline style Redmine renders as a visible grid, which its default stylesheet does not otherwise give a plain table.
What happens to a line break inside a cell?
It becomes a space, and the count is reported. Textile's table syntax is line-oriented: a row is one line, and a raw newline in the middle of a cell ends the row early, leaving the rest of the value as a malformed row of its own.
Is anything uploaded?
No. The conversion runs in your browser tab, with nothing sent to a server, nothing kept between visits and no row cap. Copy the result and paste it into your wiki or issue editor.
Related
Convert your CSV to Textile
No sign-up, no upload, no row cap. Inline header marks, per-column alignment, pipes made safe.
Back to the converter