fix: escape pipes and newlines in CSV values - #2266
Open
asjad3 wants to merge 1 commit into
Open
Conversation
CsvConverter joins cells with " | " and passes values through unescaped, so two characters that are legal inside a CSV field silently corrupt the table: a pipe is read as a column separator, and a newline inside a quoted field ends the row early. The pipe case loses data rather than just looking wrong. A row with an unescaped pipe declares more columns than the header, and renderers discard the surplus -- 'cheap | fast' renders as 'cheap'. Escape pipes as \| and turn embedded CR/LF into <br> so the record stays on one line.
Author
|
@microsoft-github-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CsvConverterbuilds its Markdown table with" | ".join(...)and passes cell valuesthrough unescaped. Two characters that are perfectly legal inside a CSV field corrupt
the table, and neither raises:
declared
after the table
The pipe case loses data rather than just looking wrong. Given
cheap | fast:mainemits a row with three columns under a two-column header:A renderer drops the surplus column, so the word
fastdisappears entirely:With this change the pipe is escaped and the value survives:
(HTML above is
python-markdownwith thetablesextension, before and after.)Change
A small
_escape_table_cell()helper applied to header and data cells: pipes areescaped as
\|, and CR/LF/CRLF inside a quoted field become<br>so the record stayson one line.
Deliberately narrow:
changes output for ordinary content like Windows paths, with no reported problem to
justify it.
<br>is the usual convention for a line break inside a GFM table cell; thealternative is collapsing to a space, which loses the author's intent.
Scope note
The HTML converter has the same gap —
<td>cheap | fast</td>also renders as a splitcell — and since
XlsxConverterroutes throughDataFrame.to_html()→HtmlConverter,it inherits it. I left that alone here: it runs through
markdownifyrather than ahand-rolled join, so it is a different fix with much wider blast radius across existing
outputs. Happy to follow up if you would like it handled, and equally happy to fold it
in here if you would rather have one change.
Tests
packages/markitdown/tests/test_csv_escaping.py— four cases: pipe in a cell, pipe inthe header, newline in a quoted cell, and a guard that ordinary content is not
over-escaped. The first three fail on
mainand pass with this change; the fourthpasses either way and exists to catch over-escaping.
test_module_vectors.py+test_module_misc.py: 121 passed, 2 skipped, 1 failed —test_speech_transcription, which fails identically on a clean checkout here (audiomodel output, unrelated to this change). The existing
test_mskanji.csvvector containsno pipes or embedded newlines and is unaffected.
blackclean.Written with AI assistance (Claude Code); I reviewed the change and ran the checks
above.