Merge pull request #2006 from dolanmiu/feat/deprecate-templates
Deprecate import dotx
This commit is contained in:
9
.github/workflows/demos.yml
vendored
9
.github/workflows/demos.yml
vendored
@ -301,15 +301,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xml-file: build/extracted-doc/word/document.xml
|
xml-file: build/extracted-doc/word/document.xml
|
||||||
xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
|
xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
|
||||||
- name: Run Demo
|
|
||||||
run: npm run ts-node -- ./demo/30-template-document.ts
|
|
||||||
- name: Extract Word Document
|
|
||||||
run: npm run extract
|
|
||||||
- name: Validate XML
|
|
||||||
uses: ChristophWurst/xmllint-action@v1
|
|
||||||
with:
|
|
||||||
xml-file: build/extracted-doc/word/document.xml
|
|
||||||
xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
|
|
||||||
- name: Run Demo
|
- name: Run Demo
|
||||||
run: npm run ts-node -- ./demo/31-tables.ts
|
run: npm run ts-node -- ./demo/31-tables.ts
|
||||||
- name: Extract Word Document
|
- name: Extract Word Document
|
||||||
|
6
.nycrc
6
.nycrc
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"check-coverage": true,
|
"check-coverage": true,
|
||||||
"statements": 99.79,
|
"statements": 99.87,
|
||||||
"branches": 98.17,
|
"branches": 98.2,
|
||||||
"functions": 100,
|
"functions": 100,
|
||||||
"lines": 99.78,
|
"lines": 99.86,
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"src/**/*.ts"
|
||||||
],
|
],
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
// Example on how to use a template document
|
|
||||||
// Import from 'docx' rather than '../build' if you install from npm
|
|
||||||
import * as fs from "fs";
|
|
||||||
import { Document, ImportDotx, Packer, Paragraph } from "../build";
|
|
||||||
|
|
||||||
const importDotx = new ImportDotx();
|
|
||||||
const filePath = "./demo/dotx/template.dotx";
|
|
||||||
|
|
||||||
fs.readFile(filePath, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
throw new Error(`Failed to read file ${filePath}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
importDotx.extract(data).then((templateDocument) => {
|
|
||||||
const doc = new Document(
|
|
||||||
{
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
properties: {
|
|
||||||
titlePage: templateDocument.titlePageIsDefined,
|
|
||||||
},
|
|
||||||
children: [new Paragraph("Hello World")],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
template: templateDocument,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Packer.toBuffer(doc).then((buffer) => {
|
|
||||||
fs.writeFileSync("My Document.docx", buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { IDocumentTemplate } from "../import-dotx";
|
|
||||||
|
|
||||||
export interface IFileProperties {
|
|
||||||
readonly template?: IDocumentTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export const WORKAROUND = "";
|
|
@ -1,12 +1,11 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import { Formatter } from "@export/formatter";
|
import { Formatter } from "@export/formatter";
|
||||||
import { sectionMarginDefaults, sectionPageSizeDefaults } from "./document";
|
|
||||||
|
|
||||||
|
import { sectionMarginDefaults, sectionPageSizeDefaults } from "./document";
|
||||||
import { File } from "./file";
|
import { File } from "./file";
|
||||||
import { Footer, Header } from "./header";
|
import { Footer, Header } from "./header";
|
||||||
import { Paragraph } from "./paragraph";
|
import { Paragraph } from "./paragraph";
|
||||||
import { Media } from "./media";
|
|
||||||
|
|
||||||
const PAGE_SIZE_DEFAULTS = {
|
const PAGE_SIZE_DEFAULTS = {
|
||||||
"w:h": sectionPageSizeDefaults.HEIGHT,
|
"w:h": sectionPageSizeDefaults.HEIGHT,
|
||||||
@ -433,29 +432,6 @@ describe("File", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#templates", () => {
|
|
||||||
// Test will be deprecated when import-dotx and templates are deprecated
|
|
||||||
it("should work with template", () => {
|
|
||||||
const doc = new File(
|
|
||||||
{
|
|
||||||
sections: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
template: {
|
|
||||||
currentRelationshipId: 1,
|
|
||||||
headers: [],
|
|
||||||
footers: [],
|
|
||||||
styles: "",
|
|
||||||
titlePageIsDefined: true,
|
|
||||||
media: new Media(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(doc).to.not.be.undefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#externalStyles", () => {
|
describe("#externalStyles", () => {
|
||||||
it("should work with external styles", () => {
|
it("should work with external styles", () => {
|
||||||
const doc = new File({
|
const doc = new File({
|
||||||
|
@ -4,7 +4,6 @@ import { CoreProperties, IPropertiesOptions } from "./core-properties";
|
|||||||
import { CustomProperties } from "./custom-properties";
|
import { CustomProperties } from "./custom-properties";
|
||||||
import { DocumentWrapper } from "./document-wrapper";
|
import { DocumentWrapper } from "./document-wrapper";
|
||||||
import { HeaderFooterReferenceType, ISectionPropertiesOptions } from "./document/body/section-properties";
|
import { HeaderFooterReferenceType, ISectionPropertiesOptions } from "./document/body/section-properties";
|
||||||
import { IFileProperties } from "./file-properties";
|
|
||||||
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
|
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
|
||||||
import { FootnotesWrapper } from "./footnotes-wrapper";
|
import { FootnotesWrapper } from "./footnotes-wrapper";
|
||||||
import { Footer, Header } from "./header";
|
import { Footer, Header } from "./header";
|
||||||
@ -55,7 +54,7 @@ export class File {
|
|||||||
private readonly styles: Styles;
|
private readonly styles: Styles;
|
||||||
private readonly comments: Comments;
|
private readonly comments: Comments;
|
||||||
|
|
||||||
public constructor(options: IPropertiesOptions, fileProperties: IFileProperties = {}) {
|
public constructor(options: IPropertiesOptions) {
|
||||||
this.coreProperties = new CoreProperties({
|
this.coreProperties = new CoreProperties({
|
||||||
...options,
|
...options,
|
||||||
creator: options.creator ?? "Un-named",
|
creator: options.creator ?? "Un-named",
|
||||||
@ -80,20 +79,9 @@ export class File {
|
|||||||
updateFields: options.features?.updateFields,
|
updateFields: options.features?.updateFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.media = fileProperties.template && fileProperties.template.media ? fileProperties.template.media : new Media();
|
this.media = new Media();
|
||||||
|
|
||||||
if (fileProperties.template) {
|
if (options.externalStyles) {
|
||||||
this.currentRelationshipId = fileProperties.template.currentRelationshipId + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up styles
|
|
||||||
if (fileProperties.template && options.externalStyles) {
|
|
||||||
throw Error("can not use both template and external styles");
|
|
||||||
}
|
|
||||||
if (fileProperties.template && fileProperties.template.styles) {
|
|
||||||
const stylesFactory = new ExternalStylesFactory();
|
|
||||||
this.styles = stylesFactory.newInstance(fileProperties.template.styles);
|
|
||||||
} else if (options.externalStyles) {
|
|
||||||
const stylesFactory = new ExternalStylesFactory();
|
const stylesFactory = new ExternalStylesFactory();
|
||||||
this.styles = stylesFactory.newInstance(options.externalStyles);
|
this.styles = stylesFactory.newInstance(options.externalStyles);
|
||||||
} else if (options.styles) {
|
} else if (options.styles) {
|
||||||
@ -110,18 +98,6 @@ export class File {
|
|||||||
|
|
||||||
this.addDefaultRelationships();
|
this.addDefaultRelationships();
|
||||||
|
|
||||||
if (fileProperties.template && fileProperties.template.headers) {
|
|
||||||
for (const templateHeader of fileProperties.template.headers) {
|
|
||||||
this.addHeaderToDocument(templateHeader.header, templateHeader.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileProperties.template && fileProperties.template.footers) {
|
|
||||||
for (const templateFooter of fileProperties.template.footers) {
|
|
||||||
this.addFooterToDocument(templateFooter.footer, templateFooter.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const section of options.sections) {
|
for (const section of options.sections) {
|
||||||
this.addSection(section);
|
this.addSection(section);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
export * from "./paragraph";
|
export * from "./paragraph";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
export * from "./file";
|
export * from "./file";
|
||||||
export * from "./file-properties";
|
|
||||||
export * from "./numbering";
|
export * from "./numbering";
|
||||||
export * from "./media";
|
export * from "./media";
|
||||||
export * from "./drawing";
|
export * from "./drawing";
|
||||||
|
@ -15,94 +15,28 @@ describe("Media", () => {
|
|||||||
(convenienceFunctions.uniqueId as SinonStub).restore();
|
(convenienceFunctions.uniqueId as SinonStub).restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#addMedia", () => {
|
describe("#Array", () => {
|
||||||
it("should add media", () => {
|
it("Get images as array", () => {
|
||||||
const image = new Media().addMedia("", {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
expect(image.fileName).to.equal("test.png");
|
|
||||||
expect(image.transformation).to.deep.equal({
|
|
||||||
pixels: {
|
|
||||||
x: 100,
|
|
||||||
y: 100,
|
|
||||||
},
|
|
||||||
flip: undefined,
|
|
||||||
emus: {
|
|
||||||
x: 952500,
|
|
||||||
y: 952500,
|
|
||||||
},
|
|
||||||
rotation: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return UInt8Array if atob is present", () => {
|
|
||||||
// eslint-disable-next-line functional/immutable-data
|
|
||||||
global.atob = () => "atob result";
|
|
||||||
|
|
||||||
const image = new Media().addMedia("", {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, functional/immutable-data
|
|
||||||
(global as any).atob = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should use data as is if its not a string", () => {
|
|
||||||
// eslint-disable-next-line functional/immutable-data
|
|
||||||
global.atob = () => "atob result";
|
|
||||||
|
|
||||||
const image = new Media().addMedia(Buffer.from(""), {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, functional/immutable-data
|
|
||||||
(global as any).atob = undefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#addImage", () => {
|
|
||||||
it("should add media", () => {
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
media.addMedia("", {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
media.addImage("test2.png", {
|
media.addImage("test2.png", {
|
||||||
stream: Buffer.from(""),
|
stream: Buffer.from(""),
|
||||||
fileName: "",
|
fileName: "test.png",
|
||||||
transformation: {
|
transformation: {
|
||||||
pixels: {
|
pixels: {
|
||||||
x: Math.round(1),
|
x: Math.round(100),
|
||||||
y: Math.round(1),
|
y: Math.round(100),
|
||||||
|
},
|
||||||
|
flip: {
|
||||||
|
vertical: true,
|
||||||
|
horizontal: true,
|
||||||
},
|
},
|
||||||
emus: {
|
emus: {
|
||||||
x: Math.round(1 * 9525),
|
x: Math.round(1 * 9525),
|
||||||
y: Math.round(1 * 9525),
|
y: Math.round(1 * 9525),
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(media.Array).to.be.lengthOf(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#Array", () => {
|
|
||||||
it("Get images as array", () => {
|
|
||||||
const media = new Media();
|
|
||||||
media.addMedia("", {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
flip: {
|
|
||||||
vertical: true,
|
|
||||||
horizontal: true,
|
|
||||||
},
|
|
||||||
rotation: 90,
|
rotation: 90,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const array = media.Array;
|
const array = media.Array;
|
||||||
@ -121,10 +55,10 @@ describe("Media", () => {
|
|||||||
horizontal: true,
|
horizontal: true,
|
||||||
},
|
},
|
||||||
emus: {
|
emus: {
|
||||||
x: 952500,
|
x: 9525,
|
||||||
y: 952500,
|
y: 9525,
|
||||||
},
|
},
|
||||||
rotation: 5400000,
|
rotation: 90,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { uniqueId } from "@util/convenience-functions";
|
|
||||||
|
|
||||||
import { IMediaData } from "./data";
|
import { IMediaData } from "./data";
|
||||||
|
|
||||||
export interface IMediaTransformation {
|
export interface IMediaTransformation {
|
||||||
@ -20,34 +18,6 @@ export class Media {
|
|||||||
this.map = new Map<string, IMediaData>();
|
this.map = new Map<string, IMediaData>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Unused
|
|
||||||
public addMedia(data: Buffer | string | Uint8Array | ArrayBuffer, transformation: IMediaTransformation): IMediaData {
|
|
||||||
const key = `${uniqueId()}.png`;
|
|
||||||
|
|
||||||
const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data;
|
|
||||||
|
|
||||||
const imageData: IMediaData = {
|
|
||||||
stream: newData,
|
|
||||||
fileName: key,
|
|
||||||
transformation: {
|
|
||||||
pixels: {
|
|
||||||
x: Math.round(transformation.width),
|
|
||||||
y: Math.round(transformation.height),
|
|
||||||
},
|
|
||||||
emus: {
|
|
||||||
x: Math.round(transformation.width * 9525),
|
|
||||||
y: Math.round(transformation.height * 9525),
|
|
||||||
},
|
|
||||||
flip: transformation.flip,
|
|
||||||
rotation: transformation.rotation ? transformation.rotation * 60000 : undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.map.set(key, imageData);
|
|
||||||
|
|
||||||
return imageData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addImage(key: string, mediaData: IMediaData): void {
|
public addImage(key: string, mediaData: IMediaData): void {
|
||||||
this.map.set(key, mediaData);
|
this.map.set(key, mediaData);
|
||||||
}
|
}
|
||||||
@ -55,24 +25,4 @@ export class Media {
|
|||||||
public get Array(): readonly IMediaData[] {
|
public get Array(): readonly IMediaData[] {
|
||||||
return Array.from(this.map.values());
|
return Array.from(this.map.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertDataURIToBinary(dataURI: string): Uint8Array {
|
|
||||||
// https://gist.github.com/borismus/1032746
|
|
||||||
// https://github.com/mafintosh/base64-to-uint8array
|
|
||||||
const BASE64_MARKER = ";base64,";
|
|
||||||
|
|
||||||
const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
|
|
||||||
|
|
||||||
if (typeof atob === "function") {
|
|
||||||
return new Uint8Array(
|
|
||||||
atob(dataURI.substring(base64Index))
|
|
||||||
.split("")
|
|
||||||
.map((c) => c.charCodeAt(0)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
||||||
const b = require("buf" + "fer");
|
|
||||||
return new b.Buffer(dataURI, "base64");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { expect } from "chai";
|
|
||||||
|
|
||||||
import { ImportDotx } from "./import-dotx";
|
|
||||||
|
|
||||||
describe("ImportDotx", () => {
|
|
||||||
describe("#constructor", () => {
|
|
||||||
it("should create", () => {
|
|
||||||
const file = new ImportDotx();
|
|
||||||
|
|
||||||
expect(file).to.deep.equal({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// describe("#extract", () => {
|
|
||||||
// it("should create", async () => {
|
|
||||||
// const file = new ImportDotx();
|
|
||||||
// const filePath = "./demo/dotx/template.dotx";
|
|
||||||
|
|
||||||
// const templateDocument = await file.extract(data);
|
|
||||||
|
|
||||||
// await file.extract(data);
|
|
||||||
|
|
||||||
// expect(templateDocument).to.be.equal({ currentRelationshipId: 1 });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
|
@ -1,266 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// This will be deprecated soon
|
|
||||||
import * as JSZip from "jszip";
|
|
||||||
import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js";
|
|
||||||
|
|
||||||
import { HeaderFooterReferenceType } from "@file/document/body/section-properties";
|
|
||||||
import { FooterWrapper, IDocumentFooter } from "@file/footer-wrapper";
|
|
||||||
import { HeaderWrapper, IDocumentHeader } from "@file/header-wrapper";
|
|
||||||
import { Media } from "@file/media";
|
|
||||||
import { TargetModeType } from "@file/relationships/relationship/relationship";
|
|
||||||
import { convertToXmlComponent, ImportedXmlComponent } from "@file/xml-components";
|
|
||||||
|
|
||||||
const schemeToType = {
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer": "footer",
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image": "image",
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink": "hyperlink",
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IDocumentRefs {
|
|
||||||
readonly headers: { readonly id: number; readonly type: HeaderFooterReferenceType }[];
|
|
||||||
readonly footers: { readonly id: number; readonly type: HeaderFooterReferenceType }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RelationshipType {
|
|
||||||
HEADER = "header",
|
|
||||||
FOOTER = "footer",
|
|
||||||
IMAGE = "image",
|
|
||||||
HYPERLINK = "hyperlink",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IRelationshipFileInfo {
|
|
||||||
readonly id: number;
|
|
||||||
readonly target: string;
|
|
||||||
readonly type: RelationshipType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Document Template
|
|
||||||
// https://fileinfo.com/extension/dotx
|
|
||||||
export interface IDocumentTemplate {
|
|
||||||
readonly currentRelationshipId: number;
|
|
||||||
readonly headers: IDocumentHeader[];
|
|
||||||
readonly footers: IDocumentFooter[];
|
|
||||||
readonly styles: string;
|
|
||||||
readonly titlePageIsDefined: boolean;
|
|
||||||
readonly media: Media;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ImportDotx {
|
|
||||||
public async extract(
|
|
||||||
data: Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream,
|
|
||||||
): Promise<IDocumentTemplate> {
|
|
||||||
const zipContent = await JSZip.loadAsync(data);
|
|
||||||
|
|
||||||
const documentContent = await zipContent.files["word/document.xml"].async("text");
|
|
||||||
const relationshipContent = await zipContent.files["word/_rels/document.xml.rels"].async("text");
|
|
||||||
|
|
||||||
const documentRefs = this.extractDocumentRefs(documentContent);
|
|
||||||
const documentRelationships = this.findReferenceFiles(relationshipContent);
|
|
||||||
|
|
||||||
const media = new Media();
|
|
||||||
|
|
||||||
const templateDocument: IDocumentTemplate = {
|
|
||||||
headers: await this.createHeaders(zipContent, documentRefs, documentRelationships, media, 0),
|
|
||||||
footers: await this.createFooters(zipContent, documentRefs, documentRelationships, media, documentRefs.headers.length),
|
|
||||||
currentRelationshipId: documentRefs.footers.length + documentRefs.headers.length,
|
|
||||||
styles: await zipContent.files["word/styles.xml"].async("text"),
|
|
||||||
titlePageIsDefined: this.checkIfTitlePageIsDefined(documentContent),
|
|
||||||
media: media,
|
|
||||||
};
|
|
||||||
|
|
||||||
return templateDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createFooters(
|
|
||||||
zipContent: JSZip,
|
|
||||||
documentRefs: IDocumentRefs,
|
|
||||||
documentRelationships: IRelationshipFileInfo[],
|
|
||||||
media: Media,
|
|
||||||
startingRelationshipId: number,
|
|
||||||
): Promise<IDocumentFooter[]> {
|
|
||||||
const result = documentRefs.footers
|
|
||||||
.map(async (reference, i) => {
|
|
||||||
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === reference.id);
|
|
||||||
|
|
||||||
if (relationshipFileInfo === null || !relationshipFileInfo) {
|
|
||||||
throw new Error(`Can not find target file for id ${reference.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmlData = await zipContent.files[`word/${relationshipFileInfo.target}`].async("text");
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
|
|
||||||
|
|
||||||
if (!xmlObj.elements) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmlElement = xmlObj.elements.reduce((acc, current) => (current.name === "w:ftr" ? current : acc));
|
|
||||||
|
|
||||||
const importedComp = convertToXmlComponent(xmlElement) as ImportedXmlComponent;
|
|
||||||
const wrapper = new FooterWrapper(media, startingRelationshipId + i, importedComp);
|
|
||||||
await this.addRelationshipToWrapper(relationshipFileInfo, zipContent, wrapper, media);
|
|
||||||
|
|
||||||
return { type: reference.type, footer: wrapper };
|
|
||||||
})
|
|
||||||
.filter((x) => !!x) as Promise<IDocumentFooter>[];
|
|
||||||
|
|
||||||
return Promise.all(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createHeaders(
|
|
||||||
zipContent: JSZip,
|
|
||||||
documentRefs: IDocumentRefs,
|
|
||||||
documentRelationships: IRelationshipFileInfo[],
|
|
||||||
media: Media,
|
|
||||||
startingRelationshipId: number,
|
|
||||||
): Promise<IDocumentHeader[]> {
|
|
||||||
const result = documentRefs.headers
|
|
||||||
.map(async (reference, i) => {
|
|
||||||
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === reference.id);
|
|
||||||
|
|
||||||
if (relationshipFileInfo === null || !relationshipFileInfo) {
|
|
||||||
throw new Error(`Can not find target file for id ${reference.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmlData = await zipContent.files[`word/${relationshipFileInfo.target}`].async("text");
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
|
|
||||||
|
|
||||||
if (!xmlObj.elements) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmlElement = xmlObj.elements.reduce((acc, current) => (current.name === "w:hdr" ? current : acc));
|
|
||||||
|
|
||||||
const importedComp = convertToXmlComponent(xmlElement) as ImportedXmlComponent;
|
|
||||||
const wrapper = new HeaderWrapper(media, startingRelationshipId + i, importedComp);
|
|
||||||
await this.addRelationshipToWrapper(relationshipFileInfo, zipContent, wrapper, media);
|
|
||||||
|
|
||||||
return { type: reference.type, header: wrapper };
|
|
||||||
})
|
|
||||||
.filter((x) => !!x) as Promise<IDocumentHeader>[];
|
|
||||||
|
|
||||||
return Promise.all(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async addRelationshipToWrapper(
|
|
||||||
relationshipFile: IRelationshipFileInfo,
|
|
||||||
zipContent: JSZip,
|
|
||||||
wrapper: HeaderWrapper | FooterWrapper,
|
|
||||||
media: Media,
|
|
||||||
): Promise<void> {
|
|
||||||
const refFile = zipContent.files[`word/_rels/${relationshipFile.target}.rels`];
|
|
||||||
|
|
||||||
if (!refFile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmlRef = await refFile.async("text");
|
|
||||||
const wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === RelationshipType.IMAGE);
|
|
||||||
const hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === RelationshipType.HYPERLINK);
|
|
||||||
|
|
||||||
for (const r of wrapperImagesReferences) {
|
|
||||||
const bufferType = JSZip.support.arraybuffer ? "arraybuffer" : "nodebuffer";
|
|
||||||
const buffer = await zipContent.files[`word/${r.target}`].async(bufferType);
|
|
||||||
const mediaData = media.addMedia(buffer, {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.Relationships.createRelationship(
|
|
||||||
r.id,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
||||||
`media/${mediaData.fileName}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const r of hyperLinkReferences) {
|
|
||||||
wrapper.Relationships.createRelationship(
|
|
||||||
r.id,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
||||||
r.target,
|
|
||||||
TargetModeType.EXTERNAL,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
|
||||||
const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
|
||||||
? xmlObj.Relationships.Relationship
|
|
||||||
: [xmlObj.Relationships.Relationship];
|
|
||||||
const relationships: IRelationshipFileInfo[] = relationXmlArray
|
|
||||||
.map((item: XMLElementCompact) => {
|
|
||||||
if (item._attributes === undefined) {
|
|
||||||
throw Error("relationship element has no attributes");
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: this.parseRefId(item._attributes.Id as string),
|
|
||||||
type: schemeToType[item._attributes.Type as string],
|
|
||||||
target: item._attributes.Target as string,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((item) => item.type !== null);
|
|
||||||
return relationships;
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractDocumentRefs(xmlData: string): IDocumentRefs {
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
|
||||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
|
||||||
|
|
||||||
const headerProps: XMLElementCompact = sectionProp["w:headerReference"];
|
|
||||||
let headersXmlArray: XMLElementCompact[];
|
|
||||||
if (headerProps === undefined) {
|
|
||||||
headersXmlArray = [];
|
|
||||||
} else if (Array.isArray(headerProps)) {
|
|
||||||
headersXmlArray = headerProps;
|
|
||||||
} else {
|
|
||||||
headersXmlArray = [headerProps];
|
|
||||||
}
|
|
||||||
const headers = headersXmlArray.map((item) => {
|
|
||||||
if (item._attributes === undefined) {
|
|
||||||
throw Error("header reference element has no attributes");
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: item._attributes["w:type"] as HeaderFooterReferenceType,
|
|
||||||
id: this.parseRefId(item._attributes["r:id"] as string),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const footerProps: XMLElementCompact = sectionProp["w:footerReference"];
|
|
||||||
let footersXmlArray: XMLElementCompact[];
|
|
||||||
if (footerProps === undefined) {
|
|
||||||
footersXmlArray = [];
|
|
||||||
} else if (Array.isArray(footerProps)) {
|
|
||||||
footersXmlArray = footerProps;
|
|
||||||
} else {
|
|
||||||
footersXmlArray = [footerProps];
|
|
||||||
}
|
|
||||||
|
|
||||||
const footers = footersXmlArray.map((item) => {
|
|
||||||
if (item._attributes === undefined) {
|
|
||||||
throw Error("footer reference element has no attributes");
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: item._attributes["w:type"] as HeaderFooterReferenceType,
|
|
||||||
id: this.parseRefId(item._attributes["r:id"] as string),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { headers, footers };
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkIfTitlePageIsDefined(xmlData: string): boolean {
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
|
||||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
|
||||||
|
|
||||||
return sectionProp["w:titlePg"] !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseRefId(str: string): number {
|
|
||||||
const match = /^rId(\d+)$/.exec(str);
|
|
||||||
if (match === null) {
|
|
||||||
throw new Error("Invalid ref id");
|
|
||||||
}
|
|
||||||
return parseInt(match[1], 10);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from "./import-dotx";
|
|
@ -3,6 +3,5 @@
|
|||||||
export { File as Document } from "./file";
|
export { File as Document } from "./file";
|
||||||
export * from "./file";
|
export * from "./file";
|
||||||
export * from "./export";
|
export * from "./export";
|
||||||
export * from "./import-dotx";
|
|
||||||
export * from "./util";
|
export * from "./util";
|
||||||
export * from "./patcher";
|
export * from "./patcher";
|
||||||
|
Reference in New Issue
Block a user