fix(export): escape backslashes in tag table cells

A backslash right before a pipe, as in the tag name a\|b, turned the escaped
pipe into "\|": the table splitter reads "\\" as an escaped backslash and the
pipe as a column separator, shifting the row. Cells now escape '\' before '|'.
Recorded in PLAN 5.8.
This commit is contained in:
doyaGu committed 2026-10-03 08:20:23 -04:00
1 parent b6e8e07d78
commit 9277e2a331
3 files changed
+29 -4

No files matched your search

@@ -204,6 +204,29 @@ public class MarkdownExporterTests
}
}
[TestMethod]
public void TagTableCellsEscapeBackslashesSoAPipeAfterOneStaysEscaped()
{
CultureInfo? original = Resources.Culture;
try
{
Resources.Culture = new CultureInfo("en");
var tag = new TagModel(1, @"a\|b", RgbColor.Parse("#112233"), @"C:\temp\");
string markdown = MarkdownExporter.Export(
"W",
[new ColumnModel(1, "C", string.Empty, 1, 1)],
[],
[(tag, 0L)],
ExportedAt);
StringAssert.Contains(markdown, @"| a\\\|b | #112233 | C:\\temp\\ | 0 |");
}
finally
{
Resources.Culture = original;
}
}
[TestMethod]
[DataRow("plain", "`plain`")]
[DataRow("a`b", "``a`b``")]
+5 -3
View File
@@ -11,7 +11,7 @@ namespace YKanBan.Export;
/// uses that one per-language pattern. The whole document uses LF line endings
/// on every platform. Card bodies and column descriptions are emitted verbatim
/// apart from line endings; column and card titles are collapsed to one line;
/// tag names become inline code with a long enough backtick fence; tag table cells escape '|' and turn line breaks into <br>. The
/// tag names become inline code with a long enough backtick fence; tag table cells escape '\' and '|' and turn line breaks into <br>. The
/// output is meant for reading only — there is no re-import.
/// </summary>
public static class MarkdownExporter
@@ -130,12 +130,14 @@ public static class MarkdownExporter
}
/// <summary>
/// Formats a table cell: escaped pipe characters, line breaks as &lt;br&gt;.
/// Formats a table cell: escaped backslashes and pipe characters, line breaks as &lt;br&gt;.
/// </summary>
/// <param name="text">The raw cell text.</param>
/// <returns>The escaped single-line text.</returns>
private static string Cell(string text) =>
text.Replace("|", "\\|").Replace("\r\n", "<br>").Replace("\r", "<br>").Replace("\n", "<br>");
// Backslashes first: the table splitter reads "\\" as an escaped backslash, so the
// '|' after a literal backslash would otherwise end the cell.
text.Replace("\\", "\\\\").Replace("|", "\\|").Replace("\r\n", "<br>").Replace("\r", "<br>").Replace("\n", "<br>");
/// <summary>
/// Renders text as an inline code span whose backtick fence is one longer than
+1 -1
View File
@@ -246,7 +246,7 @@ card_tags: (card_id, tag_id) 复合主键,双向 ON DELETE CASCADE
- 空列也保留(体现看板结构);无标题卡片以 `### #61` 呈现
- 转义规则:
- 标签名以行内代码呈现,定界反引号数量 = 名称内最长连续反引号串长度 + 1(名称以反引号开头/结尾时两侧加空格)
- 标签总表单元格内 `|` → `\|`,换行 → `<br>`
- 标签总表单元格内 `\` → `\\`(先于 `|` 处理,否则 `a\|b` 中的 `|` 会被当成列分隔符),`|` → `\|`,换行 → `<br>`
- 卡片正文与列 description **原样输出**(接受其中 Markdown 结构干扰文档层级,已接受)
### 5.9 主题(亮 / 暗 / 跟随系统)