Preserve QWord table cell inline formatting
This commit is contained in:
@@ -376,6 +376,39 @@ describe("wordOffice helpers", () => {
|
||||
expect(documentXml).toContain("Итог");
|
||||
});
|
||||
|
||||
it("сохраняет inline-форматирование ячеек таблицы для DOCX", async () => {
|
||||
const html = `
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Итог</strong> <em>готов</em></td>
|
||||
<td><a href="https://example.com/report">Отчет</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
const blocks = htmlToWordBlocks(html);
|
||||
const blob = await exportDocxBlob("Формат таблицы.docx", html);
|
||||
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
|
||||
const documentXml = (await zip.file("word/document.xml")?.async("string")) ?? "";
|
||||
const relationshipsXml = (await zip.file("word/_rels/document.xml.rels")?.async("string")) ?? "";
|
||||
|
||||
expect(blocks).toEqual([
|
||||
{
|
||||
type: "table",
|
||||
rows: [
|
||||
[
|
||||
{ text: "Итог готов", runs: [{ text: "Итог", bold: true }, { text: " " }, { text: "готов", italics: true }] },
|
||||
{ text: "Отчет", runs: [{ text: "Отчет", href: "https://example.com/report" }] }
|
||||
]
|
||||
]
|
||||
}
|
||||
]);
|
||||
expect(documentXml).toContain("<w:tbl>");
|
||||
expect(documentXml).toContain("<w:b/>");
|
||||
expect(documentXml).toContain("<w:i/>");
|
||||
expect(documentXml).toContain("<w:hyperlink");
|
||||
expect(relationshipsXml).toContain('Target="https://example.com/report"');
|
||||
});
|
||||
|
||||
it("сохраняет размер шрифта из HTML inline-стилей для DOCX", () => {
|
||||
const blocks = htmlToWordBlocks(`
|
||||
<p>Normal <span style="font-size: 18pt;">Large</span> <span style="font-size: 24px;">Pixel size</span></p>
|
||||
|
||||
+21
-7
@@ -25,6 +25,8 @@ export interface WordListItem {
|
||||
runs: WordInlineRun[];
|
||||
}
|
||||
|
||||
export type WordTableCell = string | { text: string; runs: WordInlineRun[] };
|
||||
|
||||
export interface WordImageBlock {
|
||||
type: "image";
|
||||
src: string;
|
||||
@@ -74,7 +76,7 @@ export type WordBlock =
|
||||
quote?: boolean;
|
||||
}
|
||||
| { type: "list"; ordered: boolean; items: WordListItem[] }
|
||||
| { type: "table"; rows: string[][] }
|
||||
| { type: "table"; rows: WordTableCell[][] }
|
||||
| WordImageBlock;
|
||||
|
||||
type MammothModule = {
|
||||
@@ -405,11 +407,7 @@ function wordBlockToDocxChildren(block: WordBlock, docx: DocxModule) {
|
||||
children: row.map(
|
||||
(cell) =>
|
||||
new docx.TableCell({
|
||||
children: [
|
||||
new docx.Paragraph({
|
||||
children: [new docx.TextRun(cell)]
|
||||
})
|
||||
]
|
||||
children: wordTableCellToDocxParagraphs(cell, docx)
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -418,6 +416,15 @@ function wordBlockToDocxChildren(block: WordBlock, docx: DocxModule) {
|
||||
];
|
||||
}
|
||||
|
||||
function wordTableCellToDocxParagraphs(cell: WordTableCell, docx: DocxModule) {
|
||||
const runs = typeof cell === "string" ? [{ text: cell }] : cell.runs;
|
||||
return [
|
||||
new docx.Paragraph({
|
||||
children: wordInlineRunsToTextRuns(runs, docx)
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
export function wordInlineRunsToTextRunOptions(runs: WordInlineRun[]) {
|
||||
return runs.map(wordInlineRunToTextRunOption);
|
||||
}
|
||||
@@ -647,10 +654,17 @@ function parseHtmlFragment(html: string) {
|
||||
|
||||
function tableRows(table: Element) {
|
||||
return Array.from(table.querySelectorAll("tr"))
|
||||
.map((row) => Array.from(row.querySelectorAll("th,td")).map(elementText))
|
||||
.map((row) => Array.from(row.querySelectorAll("th,td")).map(tableCellValue))
|
||||
.filter((row) => row.length > 0);
|
||||
}
|
||||
|
||||
function tableCellValue(cell: Element): WordTableCell {
|
||||
const runs = elementInlineRuns(cell);
|
||||
const text = inlineRunsText(runs);
|
||||
const hasFormattedRuns = runs.some((run) => Object.keys(cleanInlineFormat(run)).length > 0);
|
||||
return hasFormattedRuns ? { text, runs } : text;
|
||||
}
|
||||
|
||||
function paragraphElementToBlocks(element: Element, options: { quote?: boolean } = {}): WordBlock[] {
|
||||
const segments = collectInlineSegments(Array.from(element.childNodes), {});
|
||||
const alignment = paragraphAlignmentFromElement(element);
|
||||
|
||||
Reference in New Issue
Block a user