const BAIJIAHAO_TABLE_STYLE = [
"width: 100%",
"border-collapse: collapse",
"border-spacing: 0",
"margin: 12px 0",
"font-size: 15px",
"line-height: 1.6",
"color: #1f2937",
].join("; ");
const BAIJIAHAO_TABLE_CELL_STYLE = [
"border: 1px solid #d9d9d9",
"padding: 8px 10px",
"vertical-align: middle",
"word-break: break-word",
"min-width: 64px",
].join("; ");
const BAIJIAHAO_TABLE_HEADER_CELL_STYLE = [
BAIJIAHAO_TABLE_CELL_STYLE,
"background-color: #f5f7fa",
"font-weight: 600",
].join("; ");
const TABLE_PARAGRAPH_CELL_SEPARATOR = " ";
const BAIJIAHAO_TABLE_ALLOWED_ATTRS = ["colspan", "rowspan", "align", "valign", "width"];
type ParsedParagraphTableRow = string[];
function hasUnescapedPipe(value: string): boolean {
let escaped = false;
for (const char of value) {
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
continue;
}
if (char === "|") {
return true;
}
}
return false;
}
function splitMarkdownTableCells(value: string): string[] {
const cells: string[] = [];
let cell = "";
let escaped = false;
const trimmed = value.trim();
const source = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
const content = source.endsWith("|") && !source.endsWith("\\|") ? source.slice(0, -1) : source;
for (const char of content) {
if (escaped) {
cell += char;
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
cell += char;
continue;
}
if (char === "|") {
cells.push(cell.trim());
cell = "";
continue;
}
cell += char;
}
cells.push(cell.trim());
return cells;
}
function isMarkdownTableRow(value: string): boolean {
const trimmed = value.trim();
return trimmed !== "" && hasUnescapedPipe(trimmed) && splitMarkdownTableCells(trimmed).length >= 2;
}
function isMarkdownTableSeparator(value: string): boolean {
const cells = splitMarkdownTableCells(value).filter(Boolean);
return cells.length >= 2 && cells.every((cell) => /^:?-{2,}:?$/.test(cell.trim()));
}
function isMarkdownTableStart(lines: string[], index: number): boolean {
return isMarkdownTableRow(lines[index] ?? "") && isMarkdownTableSeparator(lines[index + 1] ?? "");
}
function isFenceBoundary(value: string): boolean {
return /^\s*(```|~~~)/.test(value);
}
export function prepareBaijiahaoMarkdown(markdown: string): string {
const source = markdown.replace(/\r\n?/g, "\n").trim();
if (!source) {
return "";
}
const lines = source.split("\n");
const output: string[] = [];
let inCodeFence = false;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index] ?? "";
if (isFenceBoundary(line)) {
inCodeFence = !inCodeFence;
output.push(line);
continue;
}
if (!inCodeFence && isMarkdownTableStart(lines, index)) {
while (output.length > 0 && output[output.length - 1]?.trim() === "") {
output.pop();
}
if (/^\s*#{1,6}\s*$/.test(output[output.length - 1] ?? "")) {
output.pop();
}
if (output.length > 0 && output[output.length - 1]?.trim() !== "") {
output.push("");
}
output.push(lines[index] ?? "");
output.push(lines[index + 1] ?? "");
index += 2;
while (index < lines.length && isMarkdownTableRow(lines[index] ?? "")) {
output.push(lines[index] ?? "");
index += 1;
}
if (index < lines.length && lines[index]?.trim() !== "") {
output.push("");
}
index -= 1;
continue;
}
output.push(line);
}
return output.join("\n").trim();
}
function escapeAttribute(value: string): string {
return value
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(//g, ">");
}
function extractAttribute(sourceTag: string, attributeName: string): string | null {
const matcher = new RegExp(`\\s${attributeName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i");
const match = matcher.exec(sourceTag);
const value = match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
const trimmed = value.trim();
return trimmed ? trimmed : null;
}
function allowedTableAttributes(sourceTag: string): string {
return BAIJIAHAO_TABLE_ALLOWED_ATTRS
.map((attributeName) => {
const value = extractAttribute(sourceTag, attributeName);
return value ? `${attributeName}="${escapeAttribute(value)}"` : "";
})
.filter(Boolean)
.join(" ");
}
function normalizeCellContent(value: string): string {
const trimmed = value.trim();
return trimmed ? trimmed : "
";
}
function normalizeTableCell(sourceTag: string, content: string, header: boolean): string {
const attrs = allowedTableAttributes(sourceTag);
const attrPrefix = attrs ? `${attrs} ` : "";
const style = header ? BAIJIAHAO_TABLE_HEADER_CELL_STYLE : BAIJIAHAO_TABLE_CELL_STYLE;
const normalizedContent = normalizeCellContent(content);
const cellContent = header ? `${normalizedContent}` : normalizedContent;
return `
| ]*)>([\s\S]*?)<\/th>/gi, (_match, attrs: string, content: string) => normalizeTableCell(` | `, content, true), ); body = body.replace(/ | ]*)>([\s\S]*?)<\/td>/gi, (_match, attrs: string, content: string) => normalizeTableCell(` | `, content, false), ); let rowIndex = 0; body = body.replace(/ |
|---|