Merge pull request #143 from amitm02/importDotx
use .dotx files as an initial template for a new (document) file
This commit is contained in:
8
.nycrc
8
.nycrc
@ -1,9 +1,9 @@
|
||||
{
|
||||
"check-coverage": true,
|
||||
"lines": 84.56,
|
||||
"functions": 77.50,
|
||||
"branches": 71.40,
|
||||
"statements": 84.30,
|
||||
"lines": 87.17,
|
||||
"functions": 83.12,
|
||||
"branches": 71.22,
|
||||
"statements": 86.95,
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
|
@ -49,10 +49,10 @@
|
||||
"dependencies": {
|
||||
"@types/image-size": "0.0.29",
|
||||
"@types/jszip": "^3.1.4",
|
||||
"fast-xml-parser": "^3.3.6",
|
||||
"image-size": "^0.6.2",
|
||||
"jszip": "^3.1.5",
|
||||
"xml": "^1.0.1"
|
||||
"xml": "^1.0.1",
|
||||
"xml-js": "^1.6.8"
|
||||
},
|
||||
"author": "Dolan Miu",
|
||||
"license": "MIT",
|
||||
|
@ -21,7 +21,7 @@ describe("Body", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addSection", () => {
|
||||
describe("#addSection", () => {
|
||||
it("should add section with options", () => {
|
||||
body.addSection({
|
||||
width: 10000,
|
||||
@ -39,5 +39,93 @@ describe("Body", () => {
|
||||
const newSection = formatted[1]["w:sectPr"];
|
||||
expect(newSection[0]).to.deep.equal({ "w:pgSz": [{ _attr: { "w:h": 10000, "w:w": 10000, "w:orient": "portrait" } }] });
|
||||
});
|
||||
|
||||
it("should add section with default parameters", () => {
|
||||
body.addSection({
|
||||
width: 10000,
|
||||
height: 10000,
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(body);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:body": [
|
||||
{
|
||||
"w:p": [
|
||||
{ "w:pPr": [] },
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:sectPr": [
|
||||
{ "w:pgSz": [{ _attr: { "w:w": 11906, "w:h": 16838, "w:orient": "portrait" } }] },
|
||||
{
|
||||
"w:pgMar": [
|
||||
{
|
||||
_attr: {
|
||||
"w:top": 1440,
|
||||
"w:right": 1440,
|
||||
"w:bottom": 1440,
|
||||
"w:left": 1440,
|
||||
"w:header": 708,
|
||||
"w:footer": 708,
|
||||
"w:gutter": 0,
|
||||
"w:mirrorMargins": false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ "w:cols": [{ _attr: { "w:space": 708 } }] },
|
||||
{ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
|
||||
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:sectPr": [
|
||||
{ "w:pgSz": [{ _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } }] },
|
||||
{
|
||||
"w:pgMar": [
|
||||
{
|
||||
_attr: {
|
||||
"w:top": 1440,
|
||||
"w:right": 1440,
|
||||
"w:bottom": 1440,
|
||||
"w:left": 1440,
|
||||
"w:header": 708,
|
||||
"w:footer": 708,
|
||||
"w:gutter": 0,
|
||||
"w:mirrorMargins": false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ "w:cols": [{ _attr: { "w:space": 708 } }] },
|
||||
{ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
|
||||
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getParagraphs", () => {
|
||||
it("should get no paragraphs", () => {
|
||||
const paragraphs = body.getParagraphs();
|
||||
|
||||
expect(paragraphs).to.be.an.instanceof(Array);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#DefaultSection", () => {
|
||||
it("should get section", () => {
|
||||
const section = body.DefaultSection;
|
||||
|
||||
const tree = new Formatter().format(section);
|
||||
expect(tree["w:sectPr"]).to.be.an.instanceof(Array);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -38,8 +38,6 @@ export class Body extends XmlComponent {
|
||||
public prepForXml(): IXmlableObject | undefined {
|
||||
if (this.sections.length === 1) {
|
||||
this.root.push(this.sections[0]);
|
||||
} else if (this.sections.length > 1) {
|
||||
throw new Error("Invalid usage of sections. At the end of the body element there must be ONE section.");
|
||||
}
|
||||
|
||||
return super.prepForXml();
|
||||
|
@ -103,7 +103,7 @@ describe("Anchor", () => {
|
||||
assert.equal(graphic.rootKey, "a:graphic");
|
||||
});
|
||||
|
||||
it("should create a Drawing with text wrapping", () => {
|
||||
it("should create a Drawing with square text wrapping", () => {
|
||||
anchor = createDrawing({
|
||||
textWrapping: {
|
||||
textWrapStyle: TextWrapStyle.SQUARE,
|
||||
@ -116,5 +116,44 @@ describe("Anchor", () => {
|
||||
const textWrap = newJson.root[6];
|
||||
assert.equal(textWrap.rootKey, "wp:wrapSquare");
|
||||
});
|
||||
|
||||
it("should create a Drawing with no text wrapping", () => {
|
||||
anchor = createDrawing({
|
||||
textWrapping: {
|
||||
textWrapStyle: TextWrapStyle.NONE,
|
||||
},
|
||||
});
|
||||
const newJson = Utility.jsonify(anchor);
|
||||
assert.equal(newJson.root.length, 10);
|
||||
|
||||
const textWrap = newJson.root[6];
|
||||
assert.equal(textWrap.rootKey, "wp:wrapNone");
|
||||
});
|
||||
|
||||
it("should create a Drawing with tight text wrapping", () => {
|
||||
anchor = createDrawing({
|
||||
textWrapping: {
|
||||
textWrapStyle: TextWrapStyle.TIGHT,
|
||||
},
|
||||
});
|
||||
const newJson = Utility.jsonify(anchor);
|
||||
assert.equal(newJson.root.length, 10);
|
||||
|
||||
const textWrap = newJson.root[6];
|
||||
assert.equal(textWrap.rootKey, "wp:wrapTight");
|
||||
});
|
||||
|
||||
it("should create a Drawing with tight text wrapping", () => {
|
||||
anchor = createDrawing({
|
||||
textWrapping: {
|
||||
textWrapStyle: TextWrapStyle.TOP_AND_BOTTOM,
|
||||
},
|
||||
});
|
||||
const newJson = Utility.jsonify(anchor);
|
||||
assert.equal(newJson.root.length, 10);
|
||||
|
||||
const textWrap = newJson.root[6];
|
||||
assert.equal(textWrap.rootKey, "wp:wrapTopAndBottom");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
29
src/file/header-wrapper.spec.ts
Normal file
29
src/file/header-wrapper.spec.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { expect } from "chai";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { HeaderWrapper } from "./header-wrapper";
|
||||
import { Media } from "./media";
|
||||
import { Paragraph } from "./paragraph";
|
||||
import { Table } from "./table";
|
||||
|
||||
describe("HeaderWrapper", () => {
|
||||
describe("#addParagraph", () => {
|
||||
it("should call the underlying header's addParagraph", () => {
|
||||
const file = new HeaderWrapper(new Media(), 1);
|
||||
const spy = sinon.spy(file.Header, "addParagraph");
|
||||
file.addParagraph(new Paragraph());
|
||||
|
||||
expect(spy.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addTable", () => {
|
||||
it("should call the underlying header's addParagraph", () => {
|
||||
const file = new HeaderWrapper(new Media(), 1);
|
||||
const spy = sinon.spy(file.Header, "addTable");
|
||||
file.addTable(new Table(1, 1));
|
||||
|
||||
expect(spy.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
112
src/file/media/media.spec.ts
Normal file
112
src/file/media/media.spec.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { File } from "../file";
|
||||
import { Media } from "./media";
|
||||
|
||||
describe("Media", () => {
|
||||
describe("#addImage", () => {
|
||||
it("Add image", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
const file = new File();
|
||||
const image = Media.addImage(file, "");
|
||||
|
||||
let tree = new Formatter().format(image.Paragraph);
|
||||
expect(tree["w:p"]).to.be.an.instanceof(Array);
|
||||
|
||||
tree = new Formatter().format(image.Run);
|
||||
expect(tree["w:r"]).to.be.an.instanceof(Array);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addMedia", () => {
|
||||
it("should add media", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const image = new Media().addMedia("", 1);
|
||||
expect(image.fileName).to.equal("test.png");
|
||||
expect(image.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
emus: {
|
||||
x: 952500,
|
||||
y: 952500,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return UInt8Array if atob is present", () => {
|
||||
// tslint:disable-next-line
|
||||
((process as any).atob as any) = () => "atob result";
|
||||
// tslint:disable-next-line:no-any
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const image = new Media().addMedia("", 1);
|
||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getMedia", () => {
|
||||
it("should get media", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const media = new Media();
|
||||
media.addMedia("", 1);
|
||||
|
||||
const image = media.getMedia("test.png");
|
||||
|
||||
expect(image.fileName).to.equal("test.png");
|
||||
expect(image.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
emus: {
|
||||
x: 952500,
|
||||
y: 952500,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Get media", () => {
|
||||
const media = new Media();
|
||||
|
||||
expect(() => media.getMedia("test.png")).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("#Array", () => {
|
||||
it("Get images as array", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const media = new Media();
|
||||
media.addMedia("", 1);
|
||||
|
||||
const array = media.Array;
|
||||
expect(array).to.be.an.instanceof(Array);
|
||||
expect(array.length).to.equal(1);
|
||||
|
||||
const image = array[0];
|
||||
expect(image.fileName).to.equal("test.png");
|
||||
expect(image.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
},
|
||||
emus: {
|
||||
x: 952500,
|
||||
y: 952500,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -2,7 +2,7 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { Spacing } from "./spacing";
|
||||
import { ContextualSpacing, Spacing } from "./spacing";
|
||||
|
||||
describe("Spacing", () => {
|
||||
describe("#constructor", () => {
|
||||
@ -23,3 +23,23 @@ describe("Spacing", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContextualSpacing", () => {
|
||||
describe("#constructor", () => {
|
||||
it("should create", () => {
|
||||
const spacing = new ContextualSpacing(true);
|
||||
const tree = new Formatter().format(spacing);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:contextualSpacing": [{ _attr: { "w:val": 1 } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with value of 0 if param is false", () => {
|
||||
const spacing = new ContextualSpacing(false);
|
||||
const tree = new Formatter().format(spacing);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:contextualSpacing": [{ _attr: { "w:val": 0 } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as fastXmlParser from "fast-xml-parser";
|
||||
import { convertToXmlComponent, ImportedRootElementAttributes, parseOptions } from "file/xml-components";
|
||||
import { convertToXmlComponent, ImportedRootElementAttributes, ImportedXmlComponent } from "file/xml-components";
|
||||
import { Element as XMLElement, xml2js } from "xml-js";
|
||||
import { Styles } from "./";
|
||||
|
||||
export class ExternalStylesFactory {
|
||||
@ -25,25 +25,24 @@ export class ExternalStylesFactory {
|
||||
* </w:styles>
|
||||
* @param externalStyles context from styles.xml
|
||||
*/
|
||||
public newInstance(externalStyles: string): Styles {
|
||||
const xmlStyles = fastXmlParser.parse(externalStyles, parseOptions)["w:styles"];
|
||||
// create styles with attributes from the parsed xml
|
||||
const importedStyle = new Styles(new ImportedRootElementAttributes(xmlStyles._attr));
|
||||
public newInstance(xmlData: string): Styles {
|
||||
const xmlObj = xml2js(xmlData, { compact: false }) as XMLElement;
|
||||
|
||||
// convert other elements (not styles definitions, but default styles and so on ...)
|
||||
Object.keys(xmlStyles)
|
||||
.filter((element) => element !== "_attr" && element !== "w:style")
|
||||
.forEach((element) => {
|
||||
const converted = convertToXmlComponent(element, xmlStyles[element]);
|
||||
if (Array.isArray(converted)) {
|
||||
converted.forEach((c) => importedStyle.push(c));
|
||||
} else {
|
||||
importedStyle.push(converted);
|
||||
}
|
||||
});
|
||||
let stylesXmlElement: XMLElement | undefined;
|
||||
for (const xmlElm of xmlObj.elements || []) {
|
||||
if (xmlElm.name === "w:styles") {
|
||||
stylesXmlElement = xmlElm;
|
||||
}
|
||||
}
|
||||
if (stylesXmlElement === undefined) {
|
||||
throw new Error("can not find styles element");
|
||||
}
|
||||
|
||||
// convert the styles one by one
|
||||
xmlStyles["w:style"].map((style) => convertToXmlComponent("w:style", style)).forEach(importedStyle.push.bind(importedStyle));
|
||||
const importedStyle = new Styles(new ImportedRootElementAttributes(stylesXmlElement.attributes));
|
||||
const stylesElements = stylesXmlElement.elements || [];
|
||||
for (const childElm of stylesElements) {
|
||||
importedStyle.push(convertToXmlComponent(childElm) as ImportedXmlComponent);
|
||||
}
|
||||
return importedStyle;
|
||||
}
|
||||
}
|
||||
|
12
src/file/styles/sample/default-style.spec.ts
Normal file
12
src/file/styles/sample/default-style.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { DefaultStyle } from "./default-style";
|
||||
|
||||
describe("DefaultStyle", () => {
|
||||
it("creates an initially empty property object", () => {
|
||||
const style = DefaultStyle();
|
||||
expect(style).to.have.property("w:styles");
|
||||
expect(style["w:styles"].length).to.be.greaterThan(1);
|
||||
expect(style["w:styles"][1]).to.have.property("w:docDefaults");
|
||||
});
|
||||
});
|
211
src/file/styles/sample/default-style.ts
Normal file
211
src/file/styles/sample/default-style.ts
Normal file
@ -0,0 +1,211 @@
|
||||
/* tslint:disable */
|
||||
function createLsdException(name, uiPriority, qFormat?, semiHidden?, unhideWhenUsed?) {
|
||||
"use strict";
|
||||
|
||||
return [
|
||||
{
|
||||
_attr: {
|
||||
"w:name": name,
|
||||
"w:uiPriority": uiPriority,
|
||||
"w:qFormat": qFormat,
|
||||
"w:semiHidden": semiHidden,
|
||||
"w:unhideWhenUsed": unhideWhenUsed,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function DefaultStyle() {
|
||||
var style = {
|
||||
"w:styles": [
|
||||
{
|
||||
_attr: {
|
||||
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
||||
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
|
||||
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
|
||||
"mc:Ignorable": "w14 w15",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:docDefaults": [
|
||||
{
|
||||
"w:rPrDefault": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": [
|
||||
{
|
||||
_attr: {
|
||||
"w:asciiTheme": "minorHAnsi",
|
||||
"w:eastAsiaTheme": "minorHAnsi",
|
||||
"w:hAnsiTheme": "minorHAnsi",
|
||||
"w:cstheme": "minorBidi",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:sz": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "22",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:szCs": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "22",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:lang": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "en-GB",
|
||||
"w:eastAsia": "en-US",
|
||||
"w:bidi": "ar-SA",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:pPrDefault": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:spacing": [
|
||||
{
|
||||
_attr: {
|
||||
"w:after": "160",
|
||||
"w:line": "259",
|
||||
"w:lineRule": "auto",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:latentStyles": [
|
||||
{
|
||||
_attr: {
|
||||
"w:defLockedState": "0",
|
||||
"w:defUIPriority": "99",
|
||||
"w:defSemiHidden": "0",
|
||||
"w:defUnhideWhenUsed": "0",
|
||||
"w:defQFormat": "0",
|
||||
"w:count": "371",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("Normal", 0, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 1", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 2", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 3", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 4", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 5", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 6", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 7", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 8", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 9", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 1", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 2", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 3", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 4", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 5", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 6", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 7", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 8", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 9", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 1", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 2", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 3", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 4", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 5", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 6", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 7", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 8", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 9", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("Normal Indent", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("footnote text", undefined, undefined, 1, 1),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return style;
|
||||
}
|
@ -1,211 +1 @@
|
||||
/* tslint:disable */
|
||||
function createLsdException(name, uiPriority, qFormat?, semiHidden?, unhideWhenUsed?) {
|
||||
"use strict";
|
||||
|
||||
return [
|
||||
{
|
||||
_attr: {
|
||||
"w:name": name,
|
||||
"w:uiPriority": uiPriority,
|
||||
"w:qFormat": qFormat,
|
||||
"w:semiHidden": semiHidden,
|
||||
"w:unhideWhenUsed": unhideWhenUsed,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function DefaultStyle() {
|
||||
var style = {
|
||||
"w:styles": [
|
||||
{
|
||||
_attr: {
|
||||
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
||||
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
|
||||
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
|
||||
"mc:Ignorable": "w14 w15",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:docDefaults": [
|
||||
{
|
||||
"w:rPrDefault": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": [
|
||||
{
|
||||
_attr: {
|
||||
"w:asciiTheme": "minorHAnsi",
|
||||
"w:eastAsiaTheme": "minorHAnsi",
|
||||
"w:hAnsiTheme": "minorHAnsi",
|
||||
"w:cstheme": "minorBidi",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:sz": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "22",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:szCs": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "22",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:lang": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "en-GB",
|
||||
"w:eastAsia": "en-US",
|
||||
"w:bidi": "ar-SA",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:pPrDefault": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:spacing": [
|
||||
{
|
||||
_attr: {
|
||||
"w:after": "160",
|
||||
"w:line": "259",
|
||||
"w:lineRule": "auto",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:latentStyles": [
|
||||
{
|
||||
_attr: {
|
||||
"w:defLockedState": "0",
|
||||
"w:defUIPriority": "99",
|
||||
"w:defSemiHidden": "0",
|
||||
"w:defUnhideWhenUsed": "0",
|
||||
"w:defQFormat": "0",
|
||||
"w:count": "371",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("Normal", 0, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 1", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 2", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 3", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 4", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 5", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 6", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 7", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 8", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("heading 9", 9, 1, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 1", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 2", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 3", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 4", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 5", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 6", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 7", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 8", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("index 9", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 1", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 2", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 3", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 4", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 5", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 6", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 7", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 8", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("toc 9", 39, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("Normal Indent", undefined, undefined, 1, 1),
|
||||
},
|
||||
{
|
||||
"w:lsdException": createLsdException("footnote text", undefined, undefined, 1, 1),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return style;
|
||||
}
|
||||
export * from "./default-style";
|
||||
|
@ -179,7 +179,7 @@ export class TableCellWidth extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
interface ITableCellShadingAttributesProperties {
|
||||
export interface ITableCellShadingAttributesProperties {
|
||||
fill?: string;
|
||||
color?: string;
|
||||
val?: string;
|
||||
@ -197,7 +197,7 @@ class TableCellShadingAttributes extends XmlAttributeComponent<ITableCellShading
|
||||
* Table cell shading element.
|
||||
*/
|
||||
export class TableCellShading extends XmlComponent {
|
||||
constructor(attrs: object) {
|
||||
constructor(attrs: ITableCellShadingAttributesProperties) {
|
||||
super("w:shd");
|
||||
this.root.push(new TableCellShadingAttributes(attrs));
|
||||
}
|
||||
|
64
src/file/table/table-cell/table-cell-properties.spec.ts
Normal file
64
src/file/table/table-cell/table-cell-properties.spec.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { VerticalAlign, VMergeType, WidthType } from "./table-cell-components";
|
||||
import { TableCellProperties } from "./table-cell-properties";
|
||||
|
||||
describe("TableCellProperties", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates an initially empty property object", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addGridSpan", () => {
|
||||
it("adds grid span", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.addGridSpan(1);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:gridSpan": [{ _attr: { "w:val": 1 } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addVerticalMerge", () => {
|
||||
it("adds vertical merge", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.addVerticalMerge(VMergeType.CONTINUE);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vMerge": [{ _attr: { "w:val": "continue" } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setVerticalAlign", () => {
|
||||
it("sets vertical align", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.setVerticalAlign(VerticalAlign.BOTTOM);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vAlign": [{ _attr: { "w:val": "bottom" } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("sets width", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.setWidth(1, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": [{ _attr: { "w:type": "dxa", "w:w": 1 } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setShading", () => {
|
||||
it("sets shading", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.setShading({
|
||||
fill: "test",
|
||||
color: "000",
|
||||
});
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": [{ _attr: { "w:fill": "test", "w:color": "000" } }] }] });
|
||||
});
|
||||
});
|
||||
});
|
@ -2,6 +2,7 @@ import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import {
|
||||
GridSpan,
|
||||
ITableCellShadingAttributesProperties,
|
||||
TableCellBorders,
|
||||
TableCellShading,
|
||||
TableCellWidth,
|
||||
@ -49,7 +50,7 @@ export class TableCellProperties extends XmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setShading(attrs: object): TableCellProperties {
|
||||
public setShading(attrs: ITableCellShadingAttributesProperties): TableCellProperties {
|
||||
this.root.push(new TableCellShading(attrs));
|
||||
|
||||
return this;
|
||||
|
51
src/file/table/table-properties/table-cell-margin.spec.ts
Normal file
51
src/file/table/table-properties/table-cell-margin.spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { WidthType } from "../table-cell";
|
||||
import { TableCellMargin } from "./table-cell-margin";
|
||||
|
||||
describe("TableCellMargin", () => {
|
||||
describe("#constructor", () => {
|
||||
it("should throw an error if theres no child elements", () => {
|
||||
const cellMargain = new TableCellMargin();
|
||||
expect(() => new Formatter().format(cellMargain)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addTopMargin", () => {
|
||||
it("adds a table cell top margin", () => {
|
||||
const cellMargain = new TableCellMargin();
|
||||
cellMargain.addTopMargin(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addLeftMargin", () => {
|
||||
it("adds a table cell left margin", () => {
|
||||
const cellMargain = new TableCellMargin();
|
||||
cellMargain.addLeftMargin(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addBottomMargin", () => {
|
||||
it("adds a table cell bottom margin", () => {
|
||||
const cellMargain = new TableCellMargin();
|
||||
cellMargain.addBottomMargin(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#addRightMargin", () => {
|
||||
it("adds a table cell right margin", () => {
|
||||
const cellMargain = new TableCellMargin();
|
||||
cellMargain.addRightMargin(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] });
|
||||
});
|
||||
});
|
||||
});
|
@ -43,5 +43,14 @@ describe("TableProperties", () => {
|
||||
"w:tblPr": [{ "w:tblCellMar": [{ "w:top": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] }],
|
||||
});
|
||||
});
|
||||
|
||||
it("adds a table cell left margin", () => {
|
||||
const tp = new TableProperties();
|
||||
tp.CellMargin.addLeftMargin(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(tp);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblPr": [{ "w:tblCellMar": [{ "w:left": [{ _attr: { "w:sz": "dxa", "w:w": 1234 } }] }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { expect } from "chai";
|
||||
import { convertToXmlComponent, ImportedXmlComponent } from "./";
|
||||
import { Element, xml2js } from "xml-js";
|
||||
|
||||
import { ImportedXmlComponent } from "./";
|
||||
import { convertToXmlComponent } from "./imported-xml-component";
|
||||
|
||||
const xmlString = `
|
||||
<w:p w:one="value 1" w:two="value 2">
|
||||
@ -15,39 +18,21 @@ const xmlString = `
|
||||
</w:p>
|
||||
`;
|
||||
|
||||
// tslint:disable:object-literal-key-quotes
|
||||
const importedXmlElement = {
|
||||
"w:p": {
|
||||
_attr: { "w:one": "value 1", "w:two": "value 2" },
|
||||
"w:rPr": { "w:noProof": "some value" },
|
||||
"w:r": [{ _attr: { active: "true" }, "w:t": "Text 1" }, { _attr: { active: "true" }, "w:t": "Text 2" }],
|
||||
},
|
||||
};
|
||||
// tslint:enable:object-literal-key-quotes
|
||||
|
||||
const convertedXmlElement = {
|
||||
deleted: false,
|
||||
rootKey: "w:p",
|
||||
root: [
|
||||
{
|
||||
deleted: false,
|
||||
rootKey: "w:rPr",
|
||||
root: [{ deleted: false, rootKey: "w:noProof", root: ["some value"] }],
|
||||
},
|
||||
{
|
||||
deleted: false,
|
||||
rootKey: "w:r",
|
||||
root: [{ deleted: false, rootKey: "w:t", root: ["Text 1"] }],
|
||||
_attr: { active: "true" },
|
||||
},
|
||||
{
|
||||
deleted: false,
|
||||
rootKey: "w:r",
|
||||
root: [{ deleted: false, rootKey: "w:t", root: ["Text 2"] }],
|
||||
_attr: { active: "true" },
|
||||
rootKey: "w:p",
|
||||
root: [
|
||||
{ deleted: false, rootKey: "w:rPr", root: [{ deleted: false, rootKey: "w:noProof", root: ["some value"] }] },
|
||||
{ deleted: false, rootKey: "w:r", root: [{ deleted: false, rootKey: "w:t", root: ["Text 1"] }], _attr: { active: "true" } },
|
||||
{ deleted: false, rootKey: "w:r", root: [{ deleted: false, rootKey: "w:t", root: ["Text 2"] }], _attr: { active: "true" } },
|
||||
],
|
||||
_attr: { "w:one": "value 1", "w:two": "value 2" },
|
||||
},
|
||||
],
|
||||
_attr: { "w:one": "value 1", "w:two": "value 2" },
|
||||
rootKey: undefined,
|
||||
};
|
||||
|
||||
describe("ImportedXmlComponent", () => {
|
||||
@ -88,7 +73,8 @@ describe("ImportedXmlComponent", () => {
|
||||
|
||||
describe("convertToXmlComponent", () => {
|
||||
it("should convert to xml component", () => {
|
||||
const converted = convertToXmlComponent("w:p", importedXmlElement["w:p"]);
|
||||
const xmlObj = xml2js(xmlString, { compact: false }) as Element;
|
||||
const converted = convertToXmlComponent(xmlObj);
|
||||
expect(converted).to.eql(convertedXmlElement);
|
||||
});
|
||||
});
|
||||
|
@ -1,53 +1,30 @@
|
||||
// tslint:disable:no-any
|
||||
import * as fastXmlParser from "fast-xml-parser";
|
||||
import { flatMap } from "lodash";
|
||||
import { Element as XmlElement, xml2js } from "xml-js";
|
||||
import { IXmlableObject, XmlComponent } from ".";
|
||||
|
||||
export const parseOptions = {
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "",
|
||||
attrNodeName: "_attr",
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the given xml element (in json format) into XmlComponent.
|
||||
* Note: If element is array, them it will return ImportedXmlComponent[]. Example for given:
|
||||
* element = [
|
||||
* { w:t: "val 1"},
|
||||
* { w:t: "val 2"}
|
||||
* ]
|
||||
* will return
|
||||
* [
|
||||
* ImportedXmlComponent { rootKey: "w:t", root: [ "val 1" ]},
|
||||
* ImportedXmlComponent { rootKey: "w:t", root: [ "val 2" ]}
|
||||
* ]
|
||||
*
|
||||
* @param elementName name (rootKey) of the XmlComponent
|
||||
* @param element the xml element in json presentation
|
||||
*/
|
||||
export function convertToXmlComponent(elementName: string, element: any): ImportedXmlComponent | ImportedXmlComponent[] {
|
||||
const xmlElement = new ImportedXmlComponent(elementName, element._attr);
|
||||
if (Array.isArray(element)) {
|
||||
const out: any[] = [];
|
||||
element.forEach((itemInArray) => {
|
||||
out.push(convertToXmlComponent(elementName, itemInArray));
|
||||
});
|
||||
return flatMap(out);
|
||||
} else if (typeof element === "object") {
|
||||
Object.keys(element)
|
||||
.filter((key) => key !== "_attr")
|
||||
.map((item) => convertToXmlComponent(item, element[item]))
|
||||
.forEach((converted) => {
|
||||
if (Array.isArray(converted)) {
|
||||
converted.forEach(xmlElement.push.bind(xmlElement));
|
||||
} else {
|
||||
xmlElement.push(converted);
|
||||
|
||||
export function convertToXmlComponent(element: XmlElement): ImportedXmlComponent | string | undefined {
|
||||
switch (element.type) {
|
||||
case undefined:
|
||||
case "element":
|
||||
const xmlComponent = new ImportedXmlComponent(element.name as string, element.attributes);
|
||||
const childElments = element.elements || [];
|
||||
for (const childElm of childElments) {
|
||||
const child = convertToXmlComponent(childElm);
|
||||
if (child !== undefined) {
|
||||
xmlComponent.push(child);
|
||||
}
|
||||
});
|
||||
} else if (element !== "") {
|
||||
xmlElement.push(element);
|
||||
}
|
||||
return xmlComponent;
|
||||
case "text":
|
||||
return element.text as string;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return xmlElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,16 +37,14 @@ export class ImportedXmlComponent extends XmlComponent {
|
||||
* @param importedContent xml content of the imported component
|
||||
*/
|
||||
public static fromXmlString(importedContent: string): ImportedXmlComponent {
|
||||
const imported = fastXmlParser.parse(importedContent, parseOptions);
|
||||
const elementName = Object.keys(imported)[0];
|
||||
|
||||
const converted = convertToXmlComponent(elementName, imported[elementName]);
|
||||
|
||||
if (Array.isArray(converted) && converted.length > 1) {
|
||||
throw new Error("Invalid conversion, input must be one element.");
|
||||
}
|
||||
return Array.isArray(converted) ? converted[0] : converted;
|
||||
const xmlObj = xml2js(importedContent, { compact: false }) as XmlElement;
|
||||
return convertToXmlComponent(xmlObj) as ImportedXmlComponent;
|
||||
}
|
||||
/**
|
||||
* Converts the xml string to a XmlComponent tree.
|
||||
*
|
||||
* @param importedContent xml content of the imported component
|
||||
*/
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
private readonly _attr: any;
|
||||
@ -123,7 +98,7 @@ export class ImportedXmlComponent extends XmlComponent {
|
||||
return result;
|
||||
}
|
||||
|
||||
public push(xmlComponent: XmlComponent): void {
|
||||
public push(xmlComponent: XmlComponent | string): void {
|
||||
this.root.push(xmlComponent);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
export interface IXmlAttribute {
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
export interface IXmlableObject extends Object {
|
||||
_attr?: { [key: string]: string | number | boolean };
|
||||
_attr?: IXmlAttribute;
|
||||
}
|
||||
|
||||
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
|
||||
|
26
src/import-dotx/import-dotx.spec.ts
Normal file
26
src/import-dotx/import-dotx.spec.ts
Normal file
@ -0,0 +1,26 @@
|
||||
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({ currentRelationshipId: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
// 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,20 +1,15 @@
|
||||
import * as fastXmlParser from "fast-xml-parser";
|
||||
import * as JSZip from "jszip";
|
||||
import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js";
|
||||
|
||||
import { FooterReferenceType } from "file/document/body/section-properties/footer-reference";
|
||||
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
|
||||
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
|
||||
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
|
||||
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
|
||||
|
||||
import { Media } from "file/media";
|
||||
import { Styles } from "file/styles";
|
||||
import { ExternalStylesFactory } from "file/styles/external-styles-factory";
|
||||
import { convertToXmlComponent, ImportedXmlComponent, parseOptions } from "file/xml-components";
|
||||
|
||||
const importParseOptions = {
|
||||
...parseOptions,
|
||||
textNodeName: "",
|
||||
trimValues: false,
|
||||
};
|
||||
|
||||
const schemeToType = {
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
|
||||
@ -69,35 +64,48 @@ export class ImportDotx {
|
||||
|
||||
const headers: IDocumentHeader[] = [];
|
||||
for (const headerRef of documentRefs.headers) {
|
||||
const headerKey = "w:hdr";
|
||||
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
|
||||
if (!relationshipFileInfo) {
|
||||
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
|
||||
if (relationFileInfo === null || !relationFileInfo) {
|
||||
throw new Error(`Can not find target file for id ${headerRef.id}`);
|
||||
}
|
||||
|
||||
const xmlData = await zipContent.files[`word/${relationshipFileInfo.target}`].async("text");
|
||||
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
|
||||
|
||||
const importedComp = convertToXmlComponent(headerKey, xmlObj[headerKey]) as ImportedXmlComponent;
|
||||
|
||||
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
|
||||
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
|
||||
let headerXmlElement: XMLElement | undefined;
|
||||
for (const xmlElm of xmlObj.elements || []) {
|
||||
if (xmlElm.name === "w:hdr") {
|
||||
headerXmlElement = xmlElm;
|
||||
}
|
||||
}
|
||||
if (headerXmlElement === undefined) {
|
||||
continue;
|
||||
}
|
||||
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
|
||||
const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp);
|
||||
await this.addRelationToWrapper(relationshipFileInfo, zipContent, header);
|
||||
await this.addRelationToWrapper(relationFileInfo, zipContent, header);
|
||||
headers.push({ type: headerRef.type, header });
|
||||
}
|
||||
|
||||
const footers: IDocumentFooter[] = [];
|
||||
for (const footerRef of documentRefs.footers) {
|
||||
const footerKey = "w:ftr";
|
||||
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
|
||||
if (!relationshipFileInfo) {
|
||||
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/${relationshipFileInfo.target}`].async("text");
|
||||
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
|
||||
const importedComp = convertToXmlComponent(footerKey, xmlObj[footerKey]) as ImportedXmlComponent;
|
||||
|
||||
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(relationshipFileInfo, zipContent, footer);
|
||||
await this.addRelationToWrapper(relationFileInfo, zipContent, footer);
|
||||
footers.push({ type: footerRef.type, footer });
|
||||
}
|
||||
|
||||
@ -134,16 +142,19 @@ export class ImportDotx {
|
||||
}
|
||||
|
||||
public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
|
||||
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
|
||||
const relationshipXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||
const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
||||
? xmlObj.Relationships.Relationship
|
||||
: [xmlObj.Relationships.Relationship];
|
||||
const relationships: IRelationshipFileInfo[] = relationshipXmlArray
|
||||
.map((item) => {
|
||||
const relationships: IRelationshipFileInfo[] = relationXmlArray
|
||||
.map((item: XMLElementCompact) => {
|
||||
if (item._attributes === undefined) {
|
||||
throw Error("relationship element has no attributes");
|
||||
}
|
||||
return {
|
||||
id: this.parseRefId(item._attr.Id),
|
||||
type: schemeToType[item._attr.Type],
|
||||
target: item._attr.Target,
|
||||
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);
|
||||
@ -151,14 +162,11 @@ export class ImportDotx {
|
||||
}
|
||||
|
||||
public extractDocumentRefs(xmlData: string): IDocumentRefs {
|
||||
interface IAttributedXML {
|
||||
_attr: object;
|
||||
}
|
||||
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
|
||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||
|
||||
const headerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:headerReference"];
|
||||
let headersXmlArray: IAttributedXML[];
|
||||
const headerProps: XMLElementCompact = sectionProp["w:headerReference"];
|
||||
let headersXmlArray: XMLElementCompact[];
|
||||
if (headerProps === undefined) {
|
||||
headersXmlArray = [];
|
||||
} else if (Array.isArray(headerProps)) {
|
||||
@ -167,14 +175,17 @@ export class ImportDotx {
|
||||
headersXmlArray = [headerProps];
|
||||
}
|
||||
const headers = headersXmlArray.map((item) => {
|
||||
if (item._attributes === undefined) {
|
||||
throw Error("header referecne element has no attributes");
|
||||
}
|
||||
return {
|
||||
type: item._attr["w:type"],
|
||||
id: this.parseRefId(item._attr["r:id"]),
|
||||
type: item._attributes["w:type"] as HeaderReferenceType,
|
||||
id: this.parseRefId(item._attributes["r:id"] as string),
|
||||
};
|
||||
});
|
||||
|
||||
const footerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:footerReference"];
|
||||
let footersXmlArray: IAttributedXML[];
|
||||
const footerProps: XMLElementCompact = sectionProp["w:footerReference"];
|
||||
let footersXmlArray: XMLElementCompact[];
|
||||
if (footerProps === undefined) {
|
||||
footersXmlArray = [];
|
||||
} else if (Array.isArray(footerProps)) {
|
||||
@ -184,9 +195,12 @@ export class ImportDotx {
|
||||
}
|
||||
|
||||
const footers = footersXmlArray.map((item) => {
|
||||
if (item._attributes === undefined) {
|
||||
throw Error("footer referecne element has no attributes");
|
||||
}
|
||||
return {
|
||||
type: item._attr["w:type"],
|
||||
id: this.parseRefId(item._attr["r:id"]),
|
||||
type: item._attributes["w:type"] as FooterReferenceType,
|
||||
id: this.parseRefId(item._attributes["r:id"] as string),
|
||||
};
|
||||
});
|
||||
|
||||
@ -194,7 +208,7 @@ export class ImportDotx {
|
||||
}
|
||||
|
||||
public titlePageIsDefined(xmlData: string): boolean {
|
||||
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
|
||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||
return sectionProp["w:titlePg"] !== undefined;
|
||||
}
|
||||
|
Reference in New Issue
Block a user