Merge branch 'master' into feat/improve-docs
# Conflicts: # src/file/footer-wrapper.spec.ts
This commit is contained in:
17
demo/demo35.ts
Normal file
17
demo/demo35.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Simple example to add text to a document
|
||||||
|
// Import from 'docx' rather than '../build' if you install from npm
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Document, Packer, Paragraph, TextRun } from "../build";
|
||||||
|
|
||||||
|
var doc = new Document();
|
||||||
|
var paragraph = new Paragraph();
|
||||||
|
var link = doc.createHyperlink('http://www.example.com', 'Hyperlink');
|
||||||
|
link.bold();
|
||||||
|
paragraph.addHyperLink(link);
|
||||||
|
doc.addParagraph(paragraph);
|
||||||
|
|
||||||
|
const packer = new Packer();
|
||||||
|
|
||||||
|
packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My Document.docx", buffer);
|
||||||
|
});
|
23
demo/demo36.ts
Normal file
23
demo/demo36.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Add images to header and footer
|
||||||
|
// Import from 'docx' rather than '../build' if you install from npm
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Document, Media, Packer, Table } from "../build";
|
||||||
|
|
||||||
|
const doc = new Document();
|
||||||
|
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
|
||||||
|
|
||||||
|
const table = new Table(2, 2);
|
||||||
|
table.getCell(1, 1).addContent(image.Paragraph);
|
||||||
|
|
||||||
|
// doc.createParagraph("Hello World");
|
||||||
|
doc.addTable(table);
|
||||||
|
|
||||||
|
// doc.Header.createImage(fs.readFileSync("./demo/images/pizza.gif"));
|
||||||
|
doc.Header.addTable(table);
|
||||||
|
// doc.Footer.createImage(fs.readFileSync("./demo/images/pizza.gif"));
|
||||||
|
|
||||||
|
const packer = new Packer();
|
||||||
|
|
||||||
|
packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My Document.docx", buffer);
|
||||||
|
});
|
18
demo/demo37.ts
Normal file
18
demo/demo37.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Add images to header and footer
|
||||||
|
// Import from 'docx' rather than '../build' if you install from npm
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Document, Media, Packer } from "../build";
|
||||||
|
|
||||||
|
const doc = new Document();
|
||||||
|
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
|
||||||
|
doc.createParagraph("Hello World");
|
||||||
|
|
||||||
|
doc.Header.addImage(image);
|
||||||
|
doc.Header.createImage(fs.readFileSync("./demo/images/pizza.gif"));
|
||||||
|
doc.Header.createImage(fs.readFileSync("./demo/images/image1.jpeg"));
|
||||||
|
|
||||||
|
const packer = new Packer();
|
||||||
|
|
||||||
|
packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My Document.docx", buffer);
|
||||||
|
});
|
@ -29,6 +29,7 @@ import * as docx from "docx";
|
|||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var fs = require("fs");
|
||||||
var docx = require("docx");
|
var docx = require("docx");
|
||||||
|
|
||||||
// Create document
|
// Create document
|
||||||
@ -41,11 +42,12 @@ paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
|
|||||||
doc.addParagraph(paragraph);
|
doc.addParagraph(paragraph);
|
||||||
|
|
||||||
// Used to export the file into a .docx file
|
// Used to export the file into a .docx file
|
||||||
var exporter = new docx.LocalPacker(doc);
|
var packer = new docx.Packer();
|
||||||
|
packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My First Document.docx", buffer);
|
||||||
|
});
|
||||||
|
|
||||||
exporter.pack("My First Document");
|
// Done! A file called 'My First Document.docx' will be in your file system.
|
||||||
|
|
||||||
// Done! A file called 'My First Document.docx' will be in your file system if you used LocalPacker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Honoured Mentions
|
## Honoured Mentions
|
||||||
|
17
src/export/packer/image-replacer.ts
Normal file
17
src/export/packer/image-replacer.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { IMediaData, Media } from "file/media";
|
||||||
|
|
||||||
|
export class ImageReplacer {
|
||||||
|
public replace(xmlData: string, mediaData: IMediaData[], offset: number): string {
|
||||||
|
let currentXmlData = xmlData;
|
||||||
|
|
||||||
|
mediaData.forEach((image, i) => {
|
||||||
|
currentXmlData = currentXmlData.replace(`{${image.fileName}}`, (offset + i).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
return currentXmlData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMediaData(xmlData: string, media: Media): IMediaData[] {
|
||||||
|
return media.Array.filter((image) => xmlData.search(`{${image.fileName}}`) > 0);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import * as xml from "xml";
|
|||||||
|
|
||||||
import { File } from "file";
|
import { File } from "file";
|
||||||
import { Formatter } from "../formatter";
|
import { Formatter } from "../formatter";
|
||||||
|
import { ImageReplacer } from "./image-replacer";
|
||||||
|
|
||||||
interface IXmlifyedFile {
|
interface IXmlifyedFile {
|
||||||
readonly data: string;
|
readonly data: string;
|
||||||
@ -28,14 +29,15 @@ interface IXmlifyedFileMapping {
|
|||||||
|
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
private readonly formatter: Formatter;
|
private readonly formatter: Formatter;
|
||||||
|
private readonly imageReplacer: ImageReplacer;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.formatter = new Formatter();
|
this.formatter = new Formatter();
|
||||||
|
this.imageReplacer = new ImageReplacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async compile(file: File): Promise<JSZip> {
|
public async compile(file: File): Promise<JSZip> {
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
const xmlifiedFileMapping = this.xmlifyFile(file);
|
const xmlifiedFileMapping = this.xmlifyFile(file);
|
||||||
|
|
||||||
for (const key in xmlifiedFileMapping) {
|
for (const key in xmlifiedFileMapping) {
|
||||||
@ -59,26 +61,39 @@ export class Compiler {
|
|||||||
zip.file(`word/media/${data.fileName}`, mediaData);
|
zip.file(`word/media/${data.fileName}`, mediaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const header of file.Headers) {
|
|
||||||
for (const data of header.Media.Array) {
|
|
||||||
zip.file(`word/media/${data.fileName}`, data.stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const footer of file.Footers) {
|
|
||||||
for (const data of footer.Media.Array) {
|
|
||||||
zip.file(`word/media/${data.fileName}`, data.stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return zip;
|
return zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
private xmlifyFile(file: File): IXmlifyedFileMapping {
|
private xmlifyFile(file: File): IXmlifyedFileMapping {
|
||||||
file.verifyUpdateFields();
|
file.verifyUpdateFields();
|
||||||
|
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Relationships: {
|
||||||
|
data: (() => {
|
||||||
|
const xmlData = xml(this.formatter.format(file.Document));
|
||||||
|
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||||
|
|
||||||
|
mediaDatas.forEach((mediaData, i) => {
|
||||||
|
file.DocumentRelationships.createRelationship(
|
||||||
|
documentRelationshipCount + i,
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
|
`media/${mediaData.fileName}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return xml(this.formatter.format(file.DocumentRelationships));
|
||||||
|
})(),
|
||||||
|
path: "word/_rels/document.xml.rels",
|
||||||
|
},
|
||||||
Document: {
|
Document: {
|
||||||
data: xml(this.formatter.format(file.Document), true),
|
data: (() => {
|
||||||
|
const tempXmlData = xml(this.formatter.format(file.Document), true);
|
||||||
|
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
|
||||||
|
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, documentRelationshipCount);
|
||||||
|
|
||||||
|
return xmlData;
|
||||||
|
})(),
|
||||||
path: "word/document.xml",
|
path: "word/document.xml",
|
||||||
},
|
},
|
||||||
Styles: {
|
Styles: {
|
||||||
@ -98,30 +113,66 @@ export class Compiler {
|
|||||||
data: xml(this.formatter.format(file.Numbering)),
|
data: xml(this.formatter.format(file.Numbering)),
|
||||||
path: "word/numbering.xml",
|
path: "word/numbering.xml",
|
||||||
},
|
},
|
||||||
Relationships: {
|
|
||||||
data: xml(this.formatter.format(file.DocumentRelationships)),
|
|
||||||
path: "word/_rels/document.xml.rels",
|
|
||||||
},
|
|
||||||
FileRelationships: {
|
FileRelationships: {
|
||||||
data: xml(this.formatter.format(file.FileRelationships)),
|
data: xml(this.formatter.format(file.FileRelationships)),
|
||||||
path: "_rels/.rels",
|
path: "_rels/.rels",
|
||||||
},
|
},
|
||||||
Headers: file.Headers.map((headerWrapper, index) => ({
|
HeaderRelationships: file.Headers.map((headerWrapper, index) => {
|
||||||
data: xml(this.formatter.format(headerWrapper.Header)),
|
const xmlData = xml(this.formatter.format(headerWrapper.Header));
|
||||||
path: `word/header${index + 1}.xml`,
|
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||||
})),
|
|
||||||
Footers: file.Footers.map((footerWrapper, index) => ({
|
mediaDatas.forEach((mediaData, i) => {
|
||||||
data: xml(this.formatter.format(footerWrapper.Footer)),
|
headerWrapper.Relationships.createRelationship(
|
||||||
path: `word/footer${index + 1}.xml`,
|
i,
|
||||||
})),
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
HeaderRelationships: file.Headers.map((headerWrapper, index) => ({
|
`media/${mediaData.fileName}`,
|
||||||
data: xml(this.formatter.format(headerWrapper.Relationships)),
|
);
|
||||||
path: `word/_rels/header${index + 1}.xml.rels`,
|
});
|
||||||
})),
|
|
||||||
FooterRelationships: file.Footers.map((footerWrapper, index) => ({
|
return {
|
||||||
data: xml(this.formatter.format(footerWrapper.Relationships)),
|
data: xml(this.formatter.format(headerWrapper.Relationships)),
|
||||||
path: `word/_rels/footer${index + 1}.xml.rels`,
|
path: `word/_rels/header${index + 1}.xml.rels`,
|
||||||
})),
|
};
|
||||||
|
}),
|
||||||
|
FooterRelationships: file.Footers.map((footerWrapper, index) => {
|
||||||
|
const xmlData = xml(this.formatter.format(footerWrapper.Footer));
|
||||||
|
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||||
|
|
||||||
|
mediaDatas.forEach((mediaData, i) => {
|
||||||
|
footerWrapper.Relationships.createRelationship(
|
||||||
|
i,
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
|
`media/${mediaData.fileName}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: xml(this.formatter.format(footerWrapper.Relationships)),
|
||||||
|
path: `word/_rels/footer${index + 1}.xml.rels`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
Headers: file.Headers.map((headerWrapper, index) => {
|
||||||
|
const tempXmlData = xml(this.formatter.format(headerWrapper.Header));
|
||||||
|
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
|
||||||
|
// TODO: 0 needs to be changed when headers get relationships of their own
|
||||||
|
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: xmlData,
|
||||||
|
path: `word/header${index + 1}.xml`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
Footers: file.Footers.map((footerWrapper, index) => {
|
||||||
|
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer));
|
||||||
|
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
|
||||||
|
// TODO: 0 needs to be changed when headers get relationships of their own
|
||||||
|
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: xmlData,
|
||||||
|
path: `word/footer${index + 1}.xml`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
ContentTypes: {
|
ContentTypes: {
|
||||||
data: xml(this.formatter.format(file.ContentTypes)),
|
data: xml(this.formatter.format(file.ContentTypes)),
|
||||||
path: "[Content_Types].xml",
|
path: "[Content_Types].xml",
|
||||||
|
@ -8,7 +8,20 @@ import { Anchor } from "./anchor";
|
|||||||
|
|
||||||
function createDrawing(drawingOptions: IDrawingOptions): Anchor {
|
function createDrawing(drawingOptions: IDrawingOptions): Anchor {
|
||||||
return new Anchor(
|
return new Anchor(
|
||||||
1,
|
{
|
||||||
|
fileName: "test.png",
|
||||||
|
stream: new Buffer(""),
|
||||||
|
dimensions: {
|
||||||
|
pixels: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
emus: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pixels: {
|
pixels: {
|
||||||
x: 100,
|
x: 100,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// http://officeopenxml.com/drwPicFloating.php
|
// http://officeopenxml.com/drwPicFloating.php
|
||||||
import { IMediaDataDimensions } from "file/media";
|
import { IMediaData, IMediaDataDimensions } from "file/media";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
import { IDrawingOptions } from "../drawing";
|
import { IDrawingOptions } from "../drawing";
|
||||||
import {
|
import {
|
||||||
@ -34,7 +34,7 @@ const defaultOptions: IFloating = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Anchor extends XmlComponent {
|
export class Anchor extends XmlComponent {
|
||||||
constructor(referenceId: number, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
|
constructor(mediaData: IMediaData, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
|
||||||
super("wp:anchor");
|
super("wp:anchor");
|
||||||
|
|
||||||
const floating = {
|
const floating = {
|
||||||
@ -83,6 +83,6 @@ export class Anchor extends XmlComponent {
|
|||||||
|
|
||||||
this.root.push(new DocProperties());
|
this.root.push(new DocProperties());
|
||||||
this.root.push(new GraphicFrameProperties());
|
this.root.push(new GraphicFrameProperties());
|
||||||
this.root.push(new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y));
|
this.root.push(new Graphic(mediaData, dimensions.emus.x, dimensions.emus.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ function createDrawing(drawingOptions?: IDrawingOptions): Drawing {
|
|||||||
return new Drawing(
|
return new Drawing(
|
||||||
{
|
{
|
||||||
fileName: "test.jpg",
|
fileName: "test.jpg",
|
||||||
referenceId: 1,
|
|
||||||
stream: Buffer.from(imageBase64Data, "base64"),
|
stream: Buffer.from(imageBase64Data, "base64"),
|
||||||
path: path,
|
path: path,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
|
@ -33,20 +33,16 @@ export class Drawing extends XmlComponent {
|
|||||||
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
|
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
|
||||||
super("w:drawing");
|
super("w:drawing");
|
||||||
|
|
||||||
if (imageData === undefined) {
|
|
||||||
throw new Error("imageData cannot be undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedOptions = {
|
const mergedOptions = {
|
||||||
...defaultDrawingOptions,
|
...defaultDrawingOptions,
|
||||||
...drawingOptions,
|
...drawingOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mergedOptions.position === PlacementPosition.INLINE) {
|
if (mergedOptions.position === PlacementPosition.INLINE) {
|
||||||
this.inline = new Inline(imageData.referenceId, imageData.dimensions);
|
this.inline = new Inline(imageData, imageData.dimensions);
|
||||||
this.root.push(this.inline);
|
this.root.push(this.inline);
|
||||||
} else if (mergedOptions.position === PlacementPosition.FLOATING) {
|
} else if (mergedOptions.position === PlacementPosition.FLOATING) {
|
||||||
this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, mergedOptions));
|
this.root.push(new Anchor(imageData, imageData.dimensions, mergedOptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
import { IMediaData } from "file/media";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
import { GraphicDataAttributes } from "./graphic-data-attribute";
|
import { GraphicDataAttributes } from "./graphic-data-attribute";
|
||||||
import { Pic } from "./pic";
|
import { Pic } from "./pic";
|
||||||
|
|
||||||
export class GraphicData extends XmlComponent {
|
export class GraphicData extends XmlComponent {
|
||||||
private readonly pic: Pic;
|
private readonly pic: Pic;
|
||||||
|
|
||||||
constructor(referenceId: number, x: number, y: number) {
|
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||||
super("a:graphicData");
|
super("a:graphicData");
|
||||||
|
|
||||||
this.root.push(
|
this.root.push(
|
||||||
@ -14,7 +16,7 @@ export class GraphicData extends XmlComponent {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pic = new Pic(referenceId, x, y);
|
this.pic = new Pic(mediaData, x, y);
|
||||||
|
|
||||||
this.root.push(this.pic);
|
this.root.push(this.pic);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
import { IMediaData } from "file/media";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
import { Blip } from "./blip";
|
import { Blip } from "./blip";
|
||||||
import { SourceRectangle } from "./source-rectangle";
|
import { SourceRectangle } from "./source-rectangle";
|
||||||
import { Stretch } from "./stretch";
|
import { Stretch } from "./stretch";
|
||||||
|
|
||||||
export class BlipFill extends XmlComponent {
|
export class BlipFill extends XmlComponent {
|
||||||
constructor(referenceId: number) {
|
constructor(mediaData: IMediaData) {
|
||||||
super("pic:blipFill");
|
super("pic:blipFill");
|
||||||
this.root.push(new Blip(referenceId));
|
|
||||||
|
this.root.push(new Blip(mediaData));
|
||||||
this.root.push(new SourceRectangle());
|
this.root.push(new SourceRectangle());
|
||||||
this.root.push(new Stretch());
|
this.root.push(new Stretch());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { IMediaData } from "file/media";
|
||||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
interface IBlipProperties {
|
interface IBlipProperties {
|
||||||
@ -13,11 +14,11 @@ class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Blip extends XmlComponent {
|
export class Blip extends XmlComponent {
|
||||||
constructor(referenceId: number) {
|
constructor(mediaData: IMediaData) {
|
||||||
super("a:blip");
|
super("a:blip");
|
||||||
this.root.push(
|
this.root.push(
|
||||||
new BlipAttributes({
|
new BlipAttributes({
|
||||||
embed: `rId${referenceId}`,
|
embed: `rId{${mediaData.fileName}}`,
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// http://officeopenxml.com/drwPic.php
|
// http://officeopenxml.com/drwPic.php
|
||||||
|
import { IMediaData } from "file/media";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
import { BlipFill } from "./blip/blip-fill";
|
import { BlipFill } from "./blip/blip-fill";
|
||||||
import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties";
|
import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties";
|
||||||
import { PicAttributes } from "./pic-attributes";
|
import { PicAttributes } from "./pic-attributes";
|
||||||
@ -8,7 +10,7 @@ import { ShapeProperties } from "./shape-properties/shape-properties";
|
|||||||
export class Pic extends XmlComponent {
|
export class Pic extends XmlComponent {
|
||||||
private readonly shapeProperties: ShapeProperties;
|
private readonly shapeProperties: ShapeProperties;
|
||||||
|
|
||||||
constructor(referenceId: number, x: number, y: number) {
|
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||||
super("pic:pic");
|
super("pic:pic");
|
||||||
|
|
||||||
this.root.push(
|
this.root.push(
|
||||||
@ -20,7 +22,7 @@ export class Pic extends XmlComponent {
|
|||||||
this.shapeProperties = new ShapeProperties(x, y);
|
this.shapeProperties = new ShapeProperties(x, y);
|
||||||
|
|
||||||
this.root.push(new NonVisualPicProperties());
|
this.root.push(new NonVisualPicProperties());
|
||||||
this.root.push(new BlipFill(referenceId));
|
this.root.push(new BlipFill(mediaData));
|
||||||
this.root.push(new ShapeProperties(x, y));
|
this.root.push(new ShapeProperties(x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { IMediaData } from "file/media";
|
||||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
import { GraphicData } from "./graphic-data";
|
import { GraphicData } from "./graphic-data";
|
||||||
|
|
||||||
interface IGraphicProperties {
|
interface IGraphicProperties {
|
||||||
@ -14,7 +16,7 @@ class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
|
|||||||
export class Graphic extends XmlComponent {
|
export class Graphic extends XmlComponent {
|
||||||
private readonly data: GraphicData;
|
private readonly data: GraphicData;
|
||||||
|
|
||||||
constructor(referenceId: number, x: number, y: number) {
|
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||||
super("a:graphic");
|
super("a:graphic");
|
||||||
this.root.push(
|
this.root.push(
|
||||||
new GraphicAttributes({
|
new GraphicAttributes({
|
||||||
@ -22,7 +24,7 @@ export class Graphic extends XmlComponent {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.data = new GraphicData(referenceId, x, y);
|
this.data = new GraphicData(mediaData, x, y);
|
||||||
|
|
||||||
this.root.push(this.data);
|
this.root.push(this.data);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// http://officeopenxml.com/drwPicInline.php
|
// http://officeopenxml.com/drwPicInline.php
|
||||||
import { IMediaDataDimensions } from "file/media";
|
import { IMediaData, IMediaDataDimensions } from "file/media";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
import { DocProperties } from "./../doc-properties/doc-properties";
|
import { DocProperties } from "./../doc-properties/doc-properties";
|
||||||
import { EffectExtent } from "./../effect-extent/effect-extent";
|
import { EffectExtent } from "./../effect-extent/effect-extent";
|
||||||
@ -12,7 +12,7 @@ export class Inline extends XmlComponent {
|
|||||||
private readonly extent: Extent;
|
private readonly extent: Extent;
|
||||||
private readonly graphic: Graphic;
|
private readonly graphic: Graphic;
|
||||||
|
|
||||||
constructor(referenceId: number, private readonly dimensions: IMediaDataDimensions) {
|
constructor(readonly mediaData: IMediaData, private readonly dimensions: IMediaDataDimensions) {
|
||||||
super("wp:inline");
|
super("wp:inline");
|
||||||
|
|
||||||
this.root.push(
|
this.root.push(
|
||||||
@ -25,7 +25,7 @@ export class Inline extends XmlComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.extent = new Extent(dimensions.emus.x, dimensions.emus.y);
|
this.extent = new Extent(dimensions.emus.x, dimensions.emus.y);
|
||||||
this.graphic = new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y);
|
this.graphic = new Graphic(mediaData, dimensions.emus.x, dimensions.emus.y);
|
||||||
|
|
||||||
this.root.push(this.extent);
|
this.root.push(this.extent);
|
||||||
this.root.push(new EffectExtent());
|
this.root.push(new EffectExtent());
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
|
|
||||||
import { File } from "./file";
|
import { File } from "./file";
|
||||||
|
import { Paragraph } from "./paragraph";
|
||||||
|
import { Table } from "./table";
|
||||||
|
|
||||||
describe("File", () => {
|
describe("File", () => {
|
||||||
describe("#constructor", () => {
|
describe("#constructor", () => {
|
||||||
@ -55,4 +58,24 @@ describe("File", () => {
|
|||||||
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"][0]._attr["w:type"]).to.equal("even");
|
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"][0]._attr["w:type"]).to.equal("even");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#addParagraph", () => {
|
||||||
|
it("should call the underlying header's addParagraph", () => {
|
||||||
|
const file = new File();
|
||||||
|
const spy = sinon.spy(file.Document, "addParagraph");
|
||||||
|
file.addParagraph(new Paragraph());
|
||||||
|
|
||||||
|
expect(spy.called).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#addTable", () => {
|
||||||
|
it("should call the underlying header's addParagraph", () => {
|
||||||
|
const wrapper = new File();
|
||||||
|
const spy = sinon.spy(wrapper.Document, "addTable");
|
||||||
|
wrapper.addTable(new Table(1, 1));
|
||||||
|
|
||||||
|
expect(spy.called).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,7 @@ import { Image, Media } from "./media";
|
|||||||
import { Numbering } from "./numbering";
|
import { Numbering } from "./numbering";
|
||||||
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
|
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
|
||||||
import { Relationships } from "./relationships";
|
import { Relationships } from "./relationships";
|
||||||
|
import { TargetModeType } from "./relationships/relationship/relationship";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
import { Styles } from "./styles";
|
import { Styles } from "./styles";
|
||||||
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
||||||
@ -147,7 +148,7 @@ export class File {
|
|||||||
hyperlink.linkId,
|
hyperlink.linkId,
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||||
link,
|
link,
|
||||||
"External",
|
TargetModeType.EXTERNAL,
|
||||||
);
|
);
|
||||||
return hyperlink;
|
return hyperlink;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
|
|||||||
|
|
||||||
import { FooterReferenceType } from "./document";
|
import { FooterReferenceType } from "./document";
|
||||||
import { Footer } from "./footer/footer";
|
import { Footer } from "./footer/footer";
|
||||||
import { Image, IMediaData, Media } from "./media";
|
import { Image, Media } from "./media";
|
||||||
import { ImageParagraph, Paragraph } from "./paragraph";
|
import { ImageParagraph, Paragraph } from "./paragraph";
|
||||||
import { Relationships } from "./relationships";
|
import { Relationships } from "./relationships";
|
||||||
import { Table } from "./table";
|
import { Table } from "./table";
|
||||||
@ -43,29 +43,8 @@ export class FooterWrapper {
|
|||||||
this.footer.addChildElement(childElement);
|
this.footer.addChildElement(childElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
|
|
||||||
const mediaData = this.media.addMedia(image, refId, width, height);
|
|
||||||
this.relationships.createRelationship(
|
|
||||||
mediaData.referenceId,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
||||||
`media/${mediaData.fileName}`,
|
|
||||||
);
|
|
||||||
return mediaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
|
|
||||||
this.relationships.createRelationship(
|
|
||||||
refId,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
||||||
target,
|
|
||||||
targetMode,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
|
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
|
||||||
// TODO
|
const mediaData = this.media.addMedia(image, width, height);
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
|
|
||||||
this.addImage(new Image(new ImageParagraph(mediaData)));
|
this.addImage(new Image(new ImageParagraph(mediaData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ import { Table } from "./table";
|
|||||||
describe("HeaderWrapper", () => {
|
describe("HeaderWrapper", () => {
|
||||||
describe("#addParagraph", () => {
|
describe("#addParagraph", () => {
|
||||||
it("should call the underlying header's addParagraph", () => {
|
it("should call the underlying header's addParagraph", () => {
|
||||||
const file = new HeaderWrapper(new Media(), 1);
|
const wrapper = new HeaderWrapper(new Media(), 1);
|
||||||
const spy = sinon.spy(file.Header, "addParagraph");
|
const spy = sinon.spy(wrapper.Header, "addParagraph");
|
||||||
file.addParagraph(new Paragraph());
|
wrapper.addParagraph(new Paragraph());
|
||||||
|
|
||||||
expect(spy.called).to.equal(true);
|
expect(spy.called).to.equal(true);
|
||||||
});
|
});
|
||||||
@ -19,9 +19,9 @@ describe("HeaderWrapper", () => {
|
|||||||
|
|
||||||
describe("#addTable", () => {
|
describe("#addTable", () => {
|
||||||
it("should call the underlying header's addParagraph", () => {
|
it("should call the underlying header's addParagraph", () => {
|
||||||
const file = new HeaderWrapper(new Media(), 1);
|
const wrapper = new HeaderWrapper(new Media(), 1);
|
||||||
const spy = sinon.spy(file.Header, "addTable");
|
const spy = sinon.spy(wrapper.Header, "addTable");
|
||||||
file.addTable(new Table(1, 1));
|
wrapper.addTable(new Table(1, 1));
|
||||||
|
|
||||||
expect(spy.called).to.equal(true);
|
expect(spy.called).to.equal(true);
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
|
|||||||
|
|
||||||
import { HeaderReferenceType } from "./document";
|
import { HeaderReferenceType } from "./document";
|
||||||
import { Header } from "./header/header";
|
import { Header } from "./header/header";
|
||||||
import { Image, IMediaData, Media } from "./media";
|
import { Image, Media } from "./media";
|
||||||
import { ImageParagraph, Paragraph } from "./paragraph";
|
import { ImageParagraph, Paragraph } from "./paragraph";
|
||||||
import { Relationships } from "./relationships";
|
import { Relationships } from "./relationships";
|
||||||
import { Table } from "./table";
|
import { Table } from "./table";
|
||||||
@ -43,29 +43,8 @@ export class HeaderWrapper {
|
|||||||
this.header.addChildElement(childElement);
|
this.header.addChildElement(childElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
|
|
||||||
const mediaData = this.media.addMedia(image, refId, width, height);
|
|
||||||
this.relationships.createRelationship(
|
|
||||||
mediaData.referenceId,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
||||||
`media/${mediaData.fileName}`,
|
|
||||||
);
|
|
||||||
return mediaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
|
|
||||||
this.relationships.createRelationship(
|
|
||||||
refId,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
||||||
target,
|
|
||||||
targetMode,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
|
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
|
||||||
// TODO
|
const mediaData = this.media.addMedia(image, width, height);
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
|
|
||||||
this.addImage(new Image(new ImageParagraph(mediaData)));
|
this.addImage(new Image(new ImageParagraph(mediaData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ export interface IMediaDataDimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IMediaData {
|
export interface IMediaData {
|
||||||
readonly referenceId: number;
|
|
||||||
readonly stream: Buffer | Uint8Array | ArrayBuffer;
|
readonly stream: Buffer | Uint8Array | ArrayBuffer;
|
||||||
readonly path?: string;
|
readonly path?: string;
|
||||||
readonly fileName: string;
|
readonly fileName: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// tslint:disable:object-literal-key-quotes
|
// tslint:disable:object-literal-key-quotes
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
import { stub } from "sinon";
|
||||||
|
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
|
|
||||||
@ -20,16 +21,18 @@ describe("Media", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should ensure the correct relationship id is used when adding image", () => {
|
it("should ensure the correct relationship id is used when adding image", () => {
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
stub(Media as any, "generateId").callsFake(() => "testId");
|
||||||
|
|
||||||
const file = new File();
|
const file = new File();
|
||||||
const image1 = Media.addImage(file, "test");
|
const image1 = Media.addImage(file, "test");
|
||||||
|
|
||||||
const tree = new Formatter().format(image1.Paragraph);
|
const tree = new Formatter().format(image1.Paragraph);
|
||||||
const inlineElements = tree["w:p"][1]["w:r"][1]["w:drawing"][0]["wp:inline"];
|
const inlineElements = tree["w:p"][1]["w:r"][1]["w:drawing"][0]["wp:inline"];
|
||||||
const graphicData = inlineElements.find((x) => x["a:graphic"]);
|
const graphicData = inlineElements.find((x) => x["a:graphic"]);
|
||||||
|
|
||||||
expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
|
expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
|
||||||
_attr: {
|
_attr: {
|
||||||
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`,
|
"r:embed": `rId{testId.png}`,
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -41,7 +44,7 @@ describe("Media", () => {
|
|||||||
|
|
||||||
expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
|
expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
|
||||||
_attr: {
|
_attr: {
|
||||||
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`,
|
"r:embed": `rId{testId.png}`,
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -53,9 +56,8 @@ describe("Media", () => {
|
|||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
(Media as any).generateId = () => "test";
|
(Media as any).generateId = () => "test";
|
||||||
|
|
||||||
const image = new Media().addMedia("", 1);
|
const image = new Media().addMedia("");
|
||||||
expect(image.fileName).to.equal("test.png");
|
expect(image.fileName).to.equal("test.png");
|
||||||
expect(image.referenceId).to.equal(1);
|
|
||||||
expect(image.dimensions).to.deep.equal({
|
expect(image.dimensions).to.deep.equal({
|
||||||
pixels: {
|
pixels: {
|
||||||
x: 100,
|
x: 100,
|
||||||
@ -74,7 +76,7 @@ describe("Media", () => {
|
|||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
(Media as any).generateId = () => "test";
|
(Media as any).generateId = () => "test";
|
||||||
|
|
||||||
const image = new Media().addMedia("", 1);
|
const image = new Media().addMedia("");
|
||||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -85,12 +87,11 @@ describe("Media", () => {
|
|||||||
(Media as any).generateId = () => "test";
|
(Media as any).generateId = () => "test";
|
||||||
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
media.addMedia("", 1);
|
media.addMedia("");
|
||||||
|
|
||||||
const image = media.getMedia("test.png");
|
const image = media.getMedia("test.png");
|
||||||
|
|
||||||
expect(image.fileName).to.equal("test.png");
|
expect(image.fileName).to.equal("test.png");
|
||||||
expect(image.referenceId).to.equal(1);
|
|
||||||
expect(image.dimensions).to.deep.equal({
|
expect(image.dimensions).to.deep.equal({
|
||||||
pixels: {
|
pixels: {
|
||||||
x: 100,
|
x: 100,
|
||||||
@ -116,7 +117,7 @@ describe("Media", () => {
|
|||||||
(Media as any).generateId = () => "test";
|
(Media as any).generateId = () => "test";
|
||||||
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
media.addMedia("", 1);
|
media.addMedia("");
|
||||||
|
|
||||||
const array = media.Array;
|
const array = media.Array;
|
||||||
expect(array).to.be.an.instanceof(Array);
|
expect(array).to.be.an.instanceof(Array);
|
||||||
@ -124,7 +125,6 @@ describe("Media", () => {
|
|||||||
|
|
||||||
const image = array[0];
|
const image = array[0];
|
||||||
expect(image.fileName).to.equal("test.png");
|
expect(image.fileName).to.equal("test.png");
|
||||||
expect(image.referenceId).to.equal(1);
|
|
||||||
expect(image.dimensions).to.deep.equal({
|
expect(image.dimensions).to.deep.equal({
|
||||||
pixels: {
|
pixels: {
|
||||||
x: 100,
|
x: 100,
|
||||||
|
@ -4,11 +4,6 @@ import { ImageParagraph } from "../paragraph";
|
|||||||
import { IMediaData } from "./data";
|
import { IMediaData } from "./data";
|
||||||
import { Image } from "./image";
|
import { Image } from "./image";
|
||||||
|
|
||||||
interface IHackedFile {
|
|
||||||
// tslint:disable-next-line:readonly-keyword
|
|
||||||
currentRelationshipId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Media {
|
export class Media {
|
||||||
public static addImage(
|
public static addImage(
|
||||||
file: File,
|
file: File,
|
||||||
@ -18,14 +13,7 @@ export class Media {
|
|||||||
drawingOptions?: IDrawingOptions,
|
drawingOptions?: IDrawingOptions,
|
||||||
): Image {
|
): Image {
|
||||||
// Workaround to expose id without exposing to API
|
// Workaround to expose id without exposing to API
|
||||||
const exposedFile = (file as {}) as IHackedFile;
|
const mediaData = file.Media.addMedia(buffer, width, height);
|
||||||
const mediaData = file.Media.addMedia(buffer, exposedFile.currentRelationshipId++, width, height);
|
|
||||||
file.DocumentRelationships.createRelationship(
|
|
||||||
mediaData.referenceId,
|
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
||||||
`media/${mediaData.fileName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Image(new ImageParagraph(mediaData, drawingOptions));
|
return new Image(new ImageParagraph(mediaData, drawingOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,17 +45,11 @@ export class Media {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMedia(
|
public addMedia(buffer: Buffer | string | Uint8Array | ArrayBuffer, width: number = 100, height: number = 100): IMediaData {
|
||||||
buffer: Buffer | string | Uint8Array | ArrayBuffer,
|
|
||||||
referenceId: number,
|
|
||||||
width: number = 100,
|
|
||||||
height: number = 100,
|
|
||||||
): IMediaData {
|
|
||||||
const key = `${Media.generateId()}.png`;
|
const key = `${Media.generateId()}.png`;
|
||||||
|
|
||||||
return this.createMedia(
|
return this.createMedia(
|
||||||
key,
|
key,
|
||||||
referenceId,
|
|
||||||
{
|
{
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
@ -78,7 +60,6 @@ export class Media {
|
|||||||
|
|
||||||
private createMedia(
|
private createMedia(
|
||||||
key: string,
|
key: string,
|
||||||
relationshipsCount: number,
|
|
||||||
dimensions: { readonly width: number; readonly height: number },
|
dimensions: { readonly width: number; readonly height: number },
|
||||||
data: Buffer | string | Uint8Array | ArrayBuffer,
|
data: Buffer | string | Uint8Array | ArrayBuffer,
|
||||||
filePath?: string,
|
filePath?: string,
|
||||||
@ -86,7 +67,6 @@ export class Media {
|
|||||||
const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data;
|
const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data;
|
||||||
|
|
||||||
const imageData: IMediaData = {
|
const imageData: IMediaData = {
|
||||||
referenceId: relationshipsCount,
|
|
||||||
stream: newData,
|
stream: newData,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
fileName: key,
|
fileName: key,
|
||||||
|
@ -10,10 +10,9 @@ describe("Image", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
image = new ImageParagraph({
|
image = new ImageParagraph({
|
||||||
referenceId: 0,
|
|
||||||
stream: new Buffer(""),
|
stream: new Buffer(""),
|
||||||
path: "",
|
path: "",
|
||||||
fileName: "",
|
fileName: "test.png",
|
||||||
dimensions: {
|
dimensions: {
|
||||||
pixels: {
|
pixels: {
|
||||||
x: 10,
|
x: 10,
|
||||||
@ -171,7 +170,7 @@ describe("Image", () => {
|
|||||||
{
|
{
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId0",
|
"r:embed": "rId{test.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -17,7 +17,9 @@ export type RelationshipType =
|
|||||||
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||||
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
|
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
|
||||||
|
|
||||||
export type TargetModeType = "External";
|
export enum TargetModeType {
|
||||||
|
EXTERNAL = "External",
|
||||||
|
}
|
||||||
|
|
||||||
export class Relationship extends XmlComponent {
|
export class Relationship extends XmlComponent {
|
||||||
constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) {
|
constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) {
|
||||||
|
@ -5,11 +5,11 @@ import { FooterReferenceType } from "file/document/body/section-properties/foote
|
|||||||
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
|
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
|
||||||
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
|
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
|
||||||
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
|
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
|
||||||
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
|
|
||||||
|
|
||||||
import { Media } from "file/media";
|
import { Media } from "file/media";
|
||||||
|
import { TargetModeType } from "file/relationships/relationship/relationship";
|
||||||
import { Styles } from "file/styles";
|
import { Styles } from "file/styles";
|
||||||
import { ExternalStylesFactory } from "file/styles/external-styles-factory";
|
import { ExternalStylesFactory } from "file/styles/external-styles-factory";
|
||||||
|
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
const schemeToType = {
|
const schemeToType = {
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
|
||||||
@ -23,10 +23,17 @@ interface IDocumentRefs {
|
|||||||
readonly footers: Array<{ readonly id: number; readonly type: FooterReferenceType }>;
|
readonly footers: Array<{ readonly id: number; readonly type: FooterReferenceType }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RelationshipType {
|
||||||
|
HEADER = "header",
|
||||||
|
FOOTER = "footer",
|
||||||
|
IMAGE = "image",
|
||||||
|
HYPERLINK = "hyperlink",
|
||||||
|
}
|
||||||
|
|
||||||
interface IRelationshipFileInfo {
|
interface IRelationshipFileInfo {
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
readonly target: string;
|
readonly target: string;
|
||||||
readonly type: "header" | "footer" | "image" | "hyperlink";
|
readonly type: RelationshipType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document Template
|
// Document Template
|
||||||
@ -51,19 +58,69 @@ export class ImportDotx {
|
|||||||
const zipContent = await JSZip.loadAsync(data);
|
const zipContent = await JSZip.loadAsync(data);
|
||||||
|
|
||||||
const stylesContent = await zipContent.files["word/styles.xml"].async("text");
|
const stylesContent = await zipContent.files["word/styles.xml"].async("text");
|
||||||
|
const documentContent = await zipContent.files["word/document.xml"].async("text");
|
||||||
|
const relationshipContent = await zipContent.files["word/_rels/document.xml.rels"].async("text");
|
||||||
|
|
||||||
const stylesFactory = new ExternalStylesFactory();
|
const stylesFactory = new ExternalStylesFactory();
|
||||||
const styles = stylesFactory.newInstance(stylesContent);
|
const documentRefs = this.extractDocumentRefs(documentContent);
|
||||||
|
const documentRelationships = this.findReferenceFiles(relationshipContent);
|
||||||
const documentContent = zipContent.files["word/document.xml"];
|
|
||||||
const documentRefs: IDocumentRefs = this.extractDocumentRefs(await documentContent.async("text"));
|
|
||||||
const titlePageIsDefined = this.titlePageIsDefined(await documentContent.async("text"));
|
|
||||||
|
|
||||||
const relationshipContent = zipContent.files["word/_rels/document.xml.rels"];
|
|
||||||
const documentRelationships: IRelationshipFileInfo[] = this.findReferenceFiles(await relationshipContent.async("text"));
|
|
||||||
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
|
|
||||||
|
const templateDocument: IDocumentTemplate = {
|
||||||
|
headers: await this.createHeaders(zipContent, documentRefs, documentRelationships, media),
|
||||||
|
footers: await this.createFooters(zipContent, documentRefs, documentRelationships, media),
|
||||||
|
currentRelationshipId: this.currentRelationshipId,
|
||||||
|
styles: stylesFactory.newInstance(stylesContent),
|
||||||
|
titlePageIsDefined: this.checkIfTitlePageIsDefined(documentContent),
|
||||||
|
};
|
||||||
|
|
||||||
|
return templateDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createFooters(
|
||||||
|
zipContent: JSZip,
|
||||||
|
documentRefs: IDocumentRefs,
|
||||||
|
documentRelationships: IRelationshipFileInfo[],
|
||||||
|
media: Media,
|
||||||
|
): Promise<IDocumentFooter[]> {
|
||||||
|
const footers: IDocumentFooter[] = [];
|
||||||
|
|
||||||
|
for (const footerRef of documentRefs.footers) {
|
||||||
|
const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
|
||||||
|
|
||||||
|
if (relationFileInfo === null || !relationFileInfo) {
|
||||||
|
throw new Error(`Can not find target file for id ${footerRef.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
|
||||||
|
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
|
||||||
|
let footerXmlElement: XMLElement | undefined;
|
||||||
|
for (const xmlElm of xmlObj.elements || []) {
|
||||||
|
if (xmlElm.name === "w:ftr") {
|
||||||
|
footerXmlElement = xmlElm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (footerXmlElement === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const importedComp = convertToXmlComponent(footerXmlElement) as ImportedXmlComponent;
|
||||||
|
const footer = new FooterWrapper(media, this.currentRelationshipId++, importedComp);
|
||||||
|
await this.addRelationshipToWrapper(relationFileInfo, zipContent, footer, media);
|
||||||
|
footers.push({ type: footerRef.type, footer });
|
||||||
|
}
|
||||||
|
|
||||||
|
return footers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createHeaders(
|
||||||
|
zipContent: JSZip,
|
||||||
|
documentRefs: IDocumentRefs,
|
||||||
|
documentRelationships: IRelationshipFileInfo[],
|
||||||
|
media: Media,
|
||||||
|
): Promise<IDocumentHeader[]> {
|
||||||
const headers: IDocumentHeader[] = [];
|
const headers: IDocumentHeader[] = [];
|
||||||
|
|
||||||
for (const headerRef of documentRefs.headers) {
|
for (const headerRef of documentRefs.headers) {
|
||||||
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
|
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
|
||||||
if (relationFileInfo === null || !relationFileInfo) {
|
if (relationFileInfo === null || !relationFileInfo) {
|
||||||
@ -83,66 +140,52 @@ export class ImportDotx {
|
|||||||
}
|
}
|
||||||
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
|
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
|
||||||
const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp);
|
const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp);
|
||||||
await this.addRelationToWrapper(relationFileInfo, zipContent, header);
|
// await this.addMedia(zipContent, media, documentRefs, documentRelationships);
|
||||||
|
await this.addRelationshipToWrapper(relationFileInfo, zipContent, header, media);
|
||||||
headers.push({ type: headerRef.type, header });
|
headers.push({ type: headerRef.type, header });
|
||||||
}
|
}
|
||||||
|
|
||||||
const footers: IDocumentFooter[] = [];
|
return headers;
|
||||||
for (const footerRef of documentRefs.footers) {
|
|
||||||
const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
|
|
||||||
if (relationFileInfo === null || !relationFileInfo) {
|
|
||||||
throw new Error(`Can not find target file for id ${footerRef.id}`);
|
|
||||||
}
|
|
||||||
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
|
|
||||||
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
|
|
||||||
let footerXmlElement: XMLElement | undefined;
|
|
||||||
for (const xmlElm of xmlObj.elements || []) {
|
|
||||||
if (xmlElm.name === "w:ftr") {
|
|
||||||
footerXmlElement = xmlElm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (footerXmlElement === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const importedComp = convertToXmlComponent(footerXmlElement) as ImportedXmlComponent;
|
|
||||||
const footer = new FooterWrapper(media, this.currentRelationshipId++, importedComp);
|
|
||||||
await this.addRelationToWrapper(relationFileInfo, zipContent, footer);
|
|
||||||
footers.push({ type: footerRef.type, footer });
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateDocument: IDocumentTemplate = {
|
|
||||||
headers,
|
|
||||||
footers,
|
|
||||||
currentRelationshipId: this.currentRelationshipId,
|
|
||||||
styles,
|
|
||||||
titlePageIsDefined,
|
|
||||||
};
|
|
||||||
return templateDocument;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addRelationToWrapper(
|
private async addRelationshipToWrapper(
|
||||||
relationhipFile: IRelationshipFileInfo,
|
relationhipFile: IRelationshipFileInfo,
|
||||||
zipContent: JSZip,
|
zipContent: JSZip,
|
||||||
wrapper: HeaderWrapper | FooterWrapper,
|
wrapper: HeaderWrapper | FooterWrapper,
|
||||||
|
media: Media,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let wrapperImagesReferences: IRelationshipFileInfo[] = [];
|
|
||||||
let hyperLinkReferences: IRelationshipFileInfo[] = [];
|
|
||||||
const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`];
|
const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`];
|
||||||
if (refFile) {
|
|
||||||
const xmlRef = await refFile.async("text");
|
if (!refFile) {
|
||||||
wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "image");
|
return;
|
||||||
hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "hyperlink");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
for (const r of wrapperImagesReferences) {
|
||||||
const buffer = await zipContent.files[`word/${r.target}`].async("nodebuffer");
|
const buffer = await zipContent.files[`word/${r.target}`].async("nodebuffer");
|
||||||
wrapper.addImageRelationship(buffer, r.id);
|
const mediaData = media.addMedia(buffer);
|
||||||
|
|
||||||
|
wrapper.Relationships.createRelationship(
|
||||||
|
r.id,
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
|
`media/${mediaData.fileName}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const r of hyperLinkReferences) {
|
for (const r of hyperLinkReferences) {
|
||||||
wrapper.addHyperlinkRelationship(r.target, r.id, "External");
|
wrapper.Relationships.createRelationship(
|
||||||
|
r.id,
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||||
|
r.target,
|
||||||
|
TargetModeType.EXTERNAL,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
|
private findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||||
const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
||||||
? xmlObj.Relationships.Relationship
|
? xmlObj.Relationships.Relationship
|
||||||
@ -162,7 +205,7 @@ export class ImportDotx {
|
|||||||
return relationships;
|
return relationships;
|
||||||
}
|
}
|
||||||
|
|
||||||
public extractDocumentRefs(xmlData: string): IDocumentRefs {
|
private extractDocumentRefs(xmlData: string): IDocumentRefs {
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||||
|
|
||||||
@ -208,13 +251,14 @@ export class ImportDotx {
|
|||||||
return { headers, footers };
|
return { headers, footers };
|
||||||
}
|
}
|
||||||
|
|
||||||
public titlePageIsDefined(xmlData: string): boolean {
|
private checkIfTitlePageIsDefined(xmlData: string): boolean {
|
||||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||||
|
|
||||||
return sectionProp["w:titlePg"] !== undefined;
|
return sectionProp["w:titlePg"] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseRefId(str: string): number {
|
private parseRefId(str: string): number {
|
||||||
const match = /^rId(\d+)$/.exec(str);
|
const match = /^rId(\d+)$/.exec(str);
|
||||||
if (match === null) {
|
if (match === null) {
|
||||||
throw new Error("Invalid ref id");
|
throw new Error("Invalid ref id");
|
||||||
|
Reference in New Issue
Block a user