Import QWord DOCX paragraph alignment
This commit is contained in:
@@ -31,7 +31,7 @@ npm run build
|
|||||||
|
|
||||||
## Поддержка форматов
|
## Поддержка форматов
|
||||||
|
|
||||||
- QWord открывает `.docx` и сохраняет `.docx`; при импорте базовых DOCX-абзацев читает явные цвета текста и подсветку из OOXML, а при экспорте DOCX сохраняются заголовки, абзацы, выравнивание абзацев, списки, простые таблицы, внешние гиперссылки, базовые растровые изображения и базовое inline-форматирование текста: полужирный, курсив, подчеркивание, цвет текста и подсветка.
|
- QWord открывает `.docx` и сохраняет `.docx`; при импорте базовых DOCX-абзацев читает выравнивание, явные цвета текста и подсветку из OOXML, а при экспорте DOCX сохраняются заголовки, абзацы, выравнивание абзацев, списки, простые таблицы, внешние гиперссылки, базовые растровые изображения и базовое inline-форматирование текста: полужирный, курсив, подчеркивание, цвет текста и подсветка.
|
||||||
- QExcell открывает `.xlsx` и сохраняет `.xlsx`; сетка расширяется по фактически заполненным ячейкам, поддерживает несколько листов, формулы за пределами стартового диапазона A-H, базовые merged cells, внешние гиперссылки ячеек, базовые диаграммы листа и базовое форматирование ячеек: полужирный, курсив, подчеркивание, заливку и числовые форматы XLSX для дат, процентов, денежных и дробных значений.
|
- QExcell открывает `.xlsx` и сохраняет `.xlsx`; сетка расширяется по фактически заполненным ячейкам, поддерживает несколько листов, формулы за пределами стартового диапазона A-H, базовые merged cells, внешние гиперссылки ячеек, базовые диаграммы листа и базовое форматирование ячеек: полужирный, курсив, подчеркивание, заливку и числовые форматы XLSX для дат, процентов, денежных и дробных значений.
|
||||||
- QPowerPoint открывает `.pptx` и сохраняет `.pptx`; импорт учитывает порядок слайдов из `presentation.xml`, читает заметки докладчика через relationships слайда и сохраняет базовые растровые изображения слайдов.
|
- QPowerPoint открывает `.pptx` и сохраняет `.pptx`; импорт учитывает порядок слайдов из `presentation.xml`, читает заметки докладчика через relationships слайда и сохраняет базовые растровые изображения слайдов.
|
||||||
- QOutlook открывает `.eml` и одиночные сообщения Outlook `.msg`, сохраняет письма в `.eml`; импорт EML поддерживает простые текстовые письма, `multipart/alternative`, `multipart/mixed`, `text/plain`, fallback из `text/html` в читаемый текст, base64, quoted-printable и MIME-вложения. Импорт MSG читает тему, отправителя, получателей, текст/HTML body и вложения, если эти поля доступны в MSG-файле. Новые письма можно сохранять в EML вместе с добавленными файлами. `.pst` и экспорт в `.msg` пока не реализованы.
|
- QOutlook открывает `.eml` и одиночные сообщения Outlook `.msg`, сохраняет письма в `.eml`; импорт EML поддерживает простые текстовые письма, `multipart/alternative`, `multipart/mixed`, `text/plain`, fallback из `text/html` в читаемый текст, base64, quoted-printable и MIME-вложения. Импорт MSG читает тему, отправителя, получателей, текст/HTML body и вложения, если эти поля доступны в MSG-файле. Новые письма можно сохранять в EML вместе с добавленными файлами. `.pst` и экспорт в `.msg` пока не реализованы.
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ async function styledDocxHtmlFromArrayBuffer(arrayBuffer: ArrayBuffer) {
|
|||||||
const parser = new XMLParser(xmlParserOptions);
|
const parser = new XMLParser(xmlParserOptions);
|
||||||
const parsed = parser.parse(documentXml);
|
const parsed = parser.parse(documentXml);
|
||||||
const result = parsedDocxDocumentToHtml(parsed);
|
const result = parsedDocxDocumentToHtml(parsed);
|
||||||
return result.hasInlineColorOrHighlight ? result.html : "";
|
return result.hasInlineColorOrHighlight || result.hasParagraphAlignment ? result.html : "";
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -570,17 +570,20 @@ function parsedDocxDocumentToHtml(documentXml: unknown) {
|
|||||||
const document = childRecord(documentXml, "w:document");
|
const document = childRecord(documentXml, "w:document");
|
||||||
const body = childRecord(document, "w:body");
|
const body = childRecord(document, "w:body");
|
||||||
let hasInlineColorOrHighlight = false;
|
let hasInlineColorOrHighlight = false;
|
||||||
|
let hasParagraphAlignment = false;
|
||||||
const paragraphs = toArray(body["w:p"])
|
const paragraphs = toArray(body["w:p"])
|
||||||
.map((paragraph) => {
|
.map((paragraph) => {
|
||||||
const result = docxParagraphToHtml(paragraph);
|
const result = docxParagraphToHtml(paragraph);
|
||||||
hasInlineColorOrHighlight = hasInlineColorOrHighlight || result.hasInlineColorOrHighlight;
|
hasInlineColorOrHighlight = hasInlineColorOrHighlight || result.hasInlineColorOrHighlight;
|
||||||
|
hasParagraphAlignment = hasParagraphAlignment || result.hasParagraphAlignment;
|
||||||
return result.html;
|
return result.html;
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
html: paragraphs.join(""),
|
html: paragraphs.join(""),
|
||||||
hasInlineColorOrHighlight
|
hasInlineColorOrHighlight,
|
||||||
|
hasParagraphAlignment
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,6 +592,7 @@ function docxParagraphToHtml(paragraph: unknown) {
|
|||||||
const properties = childRecord(record, "w:pPr");
|
const properties = childRecord(record, "w:pPr");
|
||||||
const tagName = docxParagraphTagName(properties);
|
const tagName = docxParagraphTagName(properties);
|
||||||
const style = docxParagraphStyleAttribute(properties);
|
const style = docxParagraphStyleAttribute(properties);
|
||||||
|
const hasParagraphAlignment = Boolean(style);
|
||||||
let hasInlineColorOrHighlight = false;
|
let hasInlineColorOrHighlight = false;
|
||||||
const runs = docxParagraphInlineRecords(record).map((run) => {
|
const runs = docxParagraphInlineRecords(record).map((run) => {
|
||||||
const result = docxRunToHtml(run);
|
const result = docxRunToHtml(run);
|
||||||
@@ -598,12 +602,13 @@ function docxParagraphToHtml(paragraph: unknown) {
|
|||||||
const content = runs.join("");
|
const content = runs.join("");
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
return { html: "", hasInlineColorOrHighlight };
|
return { html: "", hasInlineColorOrHighlight, hasParagraphAlignment };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
html: `<${tagName}${style ? ` style="${style}"` : ""}>${content}</${tagName}>`,
|
html: `<${tagName}${style ? ` style="${style}"` : ""}>${content}</${tagName}>`,
|
||||||
hasInlineColorOrHighlight
|
hasInlineColorOrHighlight,
|
||||||
|
hasParagraphAlignment
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,24 @@ describe("wordOffice DOCX import", () => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("imports DOCX paragraph alignment from OOXML", async () => {
|
||||||
|
const blob = await exportDocxBlob(
|
||||||
|
"Alignment.docx",
|
||||||
|
'<p style="text-align: center;">Centered</p><p align="right">Right</p><p style="text-align: justify;">Justified</p>'
|
||||||
|
);
|
||||||
|
const file = new File([blob], "Alignment.docx", {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
});
|
||||||
|
const imported = await importDocxFile(file);
|
||||||
|
|
||||||
|
expect(imported.html).toContain('style="text-align: center;"');
|
||||||
|
expect(imported.html).toContain('style="text-align: right;"');
|
||||||
|
expect(imported.html).toContain('style="text-align: justify;"');
|
||||||
|
expect(htmlToWordBlocks(imported.html)).toEqual([
|
||||||
|
{ type: "paragraph", text: "Centered", runs: [{ text: "Centered" }], alignment: "center" },
|
||||||
|
{ type: "paragraph", text: "Right", runs: [{ text: "Right" }], alignment: "right" },
|
||||||
|
{ type: "paragraph", text: "Justified", runs: [{ text: "Justified" }], alignment: "both" }
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user