Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
5be745b081 | |||
b4fcfd6386 | |||
6e371d42a7 | |||
e8564d58c6 | |||
368aa431a0 | |||
afcd1ae060 | |||
244d2b8904 | |||
6dae62e1ab | |||
5810cb5f02 | |||
11c26af3a9 | |||
1b9b815214 | |||
c342137c6f | |||
eefc8bd8a5 | |||
9873861210 | |||
cf7a05e05d | |||
6eb11d8842 | |||
3ad68337e7 | |||
07f363fcb7 | |||
2d2e4cdab2 | |||
0cadec7f58 | |||
e86dbd3398 | |||
021f1b0c4d | |||
5aa2027252 | |||
2fc297ef3c | |||
3fe216846c | |||
649a50f7c5 | |||
dc14123a5a | |||
048a035a5d | |||
f212cf0251 | |||
1f2f5988e1 | |||
f1359a4750 | |||
f2775ed13c | |||
3ae749278e | |||
0bcc6910f4 | |||
c15951550c | |||
8308b6413e | |||
5b75875684 | |||
28029f4c1c | |||
824d7f9893 | |||
b3aea4b9a0 | |||
32377d187d | |||
a80815822d | |||
c198154fdc | |||
618c7a8578 | |||
ef7b930d4d | |||
8296895cc6 | |||
1dce6fee15 | |||
444e7771b4 | |||
962795743c | |||
f98f852a55 | |||
e379a7fe04 |
16
.github/workflows/default.yml
vendored
16
.github/workflows/default.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- uses: "./.github/actions/install-and-build"
|
- uses: "./.github/actions/install-and-build"
|
||||||
- name: Archive Production Artifact
|
- name: Archive Production Artifact
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: build
|
path: build
|
||||||
@ -25,22 +25,24 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Test
|
- name: Test
|
||||||
run: npm run test:ci
|
run: npm run test:ci
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
verbose: true
|
verbose: true
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Lint
|
- name: Lint
|
||||||
@ -50,7 +52,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Prettier
|
- name: Prettier
|
||||||
@ -60,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Prettier
|
- name: Prettier
|
||||||
|
2
.github/workflows/demos.yml
vendored
2
.github/workflows/demos.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
name: Demos
|
name: Demos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v4
|
||||||
- uses: "./.github/actions/install-and-build"
|
- uses: "./.github/actions/install-and-build"
|
||||||
- name: Run Demos
|
- name: Run Demos
|
||||||
run: npm run run-ts -- ./demo/1-basic.ts
|
run: npm run run-ts -- ./demo/1-basic.ts
|
||||||
|
8
.github/workflows/github-pages.yml
vendored
8
.github/workflows/github-pages.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Build 🔧
|
- name: Build 🔧
|
||||||
@ -19,7 +19,7 @@ jobs:
|
|||||||
echo "docx.js.org" > docs/.nojekyll
|
echo "docx.js.org" > docs/.nojekyll
|
||||||
echo "docx.js.org" > docs/CNAME
|
echo "docx.js.org" > docs/CNAME
|
||||||
- name: Archive Production Artifact
|
- name: Archive Production Artifact
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docs
|
name: docs
|
||||||
path: docs
|
path: docs
|
||||||
@ -28,11 +28,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo 🛎️
|
- name: Checkout Repo 🛎️
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@v4
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci --force
|
run: npm ci --force
|
||||||
- name: Download Artifact
|
- name: Download Artifact
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docs
|
name: docs
|
||||||
path: docs
|
path: docs
|
||||||
|
46
.github/workflows/npm-publish.yml
vendored
Normal file
46
.github/workflows/npm-publish.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||||
|
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
||||||
|
|
||||||
|
name: Node.js Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20.x"
|
||||||
|
- run: npm ci --force
|
||||||
|
- run: npm run cspell
|
||||||
|
- run: npm run prettier
|
||||||
|
- run: npm run lint
|
||||||
|
- run: npm run test:ci
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20.x"
|
||||||
|
- run: npm ci --force
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
needs: [test, build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20.x"
|
||||||
|
registry-url: https://registry.npmjs.org/
|
||||||
|
- run: npm ci --force
|
||||||
|
- run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
24
SECURITY.md
Normal file
24
SECURITY.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 9.0.x | :white_check_mark: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
We encourage responsible disclosure of security vulnerabilities. If you believe you have found a security vulnerability in this project, please report it via the [Security Tab](https://github.com/dolanmiu/docx/security/advisories)
|
||||||
|
|
||||||
|
Please include the following information in your report:
|
||||||
|
|
||||||
|
* A description of the vulnerability
|
||||||
|
* Steps to reproduce the vulnerability
|
||||||
|
* Impact of the vulnerability
|
||||||
|
|
||||||
|
We will investigate all reported vulnerabilities and take appropriate action.
|
||||||
|
|
||||||
|
We appreciate your help in keeping this project secure.
|
72
demo/93-template-document.ts
Normal file
72
demo/93-template-document.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Patch a document with patches
|
||||||
|
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { patchDocument, PatchType, TextRun } from "docx";
|
||||||
|
|
||||||
|
patchDocument({
|
||||||
|
outputType: "nodebuffer",
|
||||||
|
data: fs.readFileSync("demo/assets/field-trip.docx"),
|
||||||
|
patches: {
|
||||||
|
todays_date: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: new Date().toLocaleDateString() })],
|
||||||
|
},
|
||||||
|
|
||||||
|
school_name: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
address: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "blah blah" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
city: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
state: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
zip: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
phone: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
first_name: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
last_name: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
email_address: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
ft_dates: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
|
||||||
|
grade: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun({ text: "test" })],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then((doc) => {
|
||||||
|
fs.writeFileSync("My Document.docx", doc);
|
||||||
|
});
|
BIN
demo/assets/field-trip.docx
Normal file
BIN
demo/assets/field-trip.docx
Normal file
Binary file not shown.
@ -22,7 +22,7 @@ const doc = new Document({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
}];
|
}]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -130,6 +130,62 @@ new Paragraph({
|
|||||||
}),
|
}),
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disabling numbering inherited from paragraph style
|
||||||
|
|
||||||
|
If the numbering is set on a paragraph style, you may wish to disable it for a specific paragraph:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const doc = new Document({
|
||||||
|
...
|
||||||
|
numbering: {
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
reference: "my-bullet-points",
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
level: 0,
|
||||||
|
format: LevelFormat.BULLET,
|
||||||
|
text: "\u1F60",
|
||||||
|
alignment: AlignmentType.LEFT,
|
||||||
|
style: {
|
||||||
|
paragraph: {
|
||||||
|
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
paragraphStyles: [
|
||||||
|
{
|
||||||
|
id: 'bullet',
|
||||||
|
name: 'Bullet',
|
||||||
|
basedOn: 'Normal',
|
||||||
|
next: 'Normal',
|
||||||
|
run: {},
|
||||||
|
paragraph: {
|
||||||
|
numbering: {
|
||||||
|
reference: 'my-bullet-points',
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Paragraph({
|
||||||
|
text: "No bullet points!",
|
||||||
|
style: "Bullet",
|
||||||
|
numbering: false,
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include")
|
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include")
|
||||||
|
@ -35,6 +35,9 @@ interface Patch {
|
|||||||
| type | `PatchType` | Required | `DOCUMENT`, `PARAGRAPH` |
|
| type | `PatchType` | Required | `DOCUMENT`, `PARAGRAPH` |
|
||||||
| children | `FileChild[] or ParagraphChild[]` | Required | The contents to replace with. A `FileChild` is a `Paragraph` or `Table`, whereas a `ParagraphChild` is typical `Paragraph` children. |
|
| children | `FileChild[] or ParagraphChild[]` | Required | The contents to replace with. A `FileChild` is a `Paragraph` or `Table`, whereas a `ParagraphChild` is typical `Paragraph` children. |
|
||||||
|
|
||||||
|
|
||||||
|
The patcher also takes in a `keepOriginalStyles` boolean, which will preserve the styles of the patched text when set to true.
|
||||||
|
|
||||||
### How to patch existing document
|
### How to patch existing document
|
||||||
|
|
||||||
1. Open your existing word document in your favorite Word Processor
|
1. Open your existing word document in your favorite Word Processor
|
||||||
|
@ -126,10 +126,10 @@ const doc = new Document({
|
|||||||
next: "Normal",
|
next: "Normal",
|
||||||
quickFormat: true,
|
quickFormat: true,
|
||||||
run: {
|
run: {
|
||||||
size: 26
|
size: 26,
|
||||||
bold: true,
|
bold: true,
|
||||||
color: "999999",
|
color: "999999",
|
||||||
{
|
underline: {
|
||||||
type: UnderlineType.DOUBLE,
|
type: UnderlineType.DOUBLE,
|
||||||
color: "FF0000",
|
color: "FF0000",
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ Then add the table in the `section`
|
|||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
sections: [{
|
sections: [{
|
||||||
children: [table],
|
children: [table],
|
||||||
}];
|
}],
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
8088
package-lock.json
generated
8088
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "docx",
|
"name": "docx",
|
||||||
"version": "8.5.0",
|
"version": "9.0.2",
|
||||||
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
|
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "build/index.umd.js",
|
"main": "build/index.umd.js",
|
||||||
@ -54,7 +54,8 @@
|
|||||||
"clippy"
|
"clippy"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^22.7.5",
|
||||||
|
"hash.js": "^1.1.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"xml": "^1.0.1",
|
"xml": "^1.0.1",
|
||||||
@ -71,34 +72,34 @@
|
|||||||
"@types/prompt": "^1.1.1",
|
"@types/prompt": "^1.1.1",
|
||||||
"@types/unzipper": "^0.10.4",
|
"@types/unzipper": "^0.10.4",
|
||||||
"@types/xml": "^1.0.8",
|
"@types/xml": "^1.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^6.9.1",
|
"@typescript-eslint/parser": "^8.8.1",
|
||||||
"@vitest/coverage-v8": "^1.1.0",
|
"@vitest/coverage-v8": "^1.1.0",
|
||||||
"@vitest/ui": "^1.1.0",
|
"@vitest/ui": "^2.1.2",
|
||||||
"cspell": "^8.2.3",
|
"cspell": "^8.2.3",
|
||||||
"docsify-cli": "^4.3.0",
|
"docsify-cli": "^4.3.0",
|
||||||
"eslint": "^8.23.0",
|
"eslint": "^8.23.0",
|
||||||
"eslint-plugin-functional": "^6.0.0",
|
"eslint-plugin-functional": "^6.0.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsdoc": "^48.0.2",
|
"eslint-plugin-jsdoc": "^50.3.1",
|
||||||
"eslint-plugin-no-null": "^1.0.2",
|
"eslint-plugin-no-null": "^1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
"eslint-plugin-unicorn": "^50.0.1",
|
"eslint-plugin-unicorn": "^56.0.0",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"glob": "^10.2.7",
|
"glob": "^11.0.0",
|
||||||
"inquirer": "^9.2.7",
|
"inquirer": "^9.2.7",
|
||||||
"jsdom": "^23.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"pre-commit": "^1.2.2",
|
"pre-commit": "^1.2.2",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"tsconfig-paths": "^4.0.0",
|
"tsconfig-paths": "^4.0.0",
|
||||||
"tsx": "^4.7.0",
|
"tsx": "^4.7.0",
|
||||||
"typedoc": "^0.25.4",
|
"typedoc": "^0.25.4",
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.3.3",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.12.3",
|
||||||
"vite": "^5.0.10",
|
"vite": "^5.0.10",
|
||||||
"vite-plugin-dts": "^3.3.1",
|
"vite-plugin-dts": "^4.2.4",
|
||||||
"vite-plugin-node-polyfills": "^0.19.0",
|
"vite-plugin-node-polyfills": "^0.19.0",
|
||||||
"vite-tsconfig-paths": "^4.2.0",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
"vitest": "^1.1.0"
|
"vitest": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -7,6 +7,12 @@ export type EffectExtentAttributes = {
|
|||||||
readonly left: number;
|
readonly left: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// <xsd:complexType name="CT_EffectExtent">
|
||||||
|
// <xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
|
||||||
|
// <xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
|
||||||
|
// <xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
|
||||||
|
// <xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
|
||||||
|
// </xsd:complexType>
|
||||||
export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent =>
|
export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent =>
|
||||||
new BuilderElement<EffectExtentAttributes>({
|
new BuilderElement<EffectExtentAttributes>({
|
||||||
name: "wp:effectExtent",
|
name: "wp:effectExtent",
|
||||||
|
@ -24,6 +24,8 @@ class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
|
|||||||
before: "w:before",
|
before: "w:before",
|
||||||
line: "w:line",
|
line: "w:line",
|
||||||
lineRule: "w:lineRule",
|
lineRule: "w:lineRule",
|
||||||
|
beforeAutoSpacing: "w:beforeAutospacing",
|
||||||
|
afterAutoSpacing: "w:afterAutoSpacing",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,36 @@ describe("ParagraphProperties", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create with numbering disabled", () => {
|
||||||
|
const properties = new ParagraphProperties({
|
||||||
|
numbering: false,
|
||||||
|
});
|
||||||
|
const tree = new Formatter().format(properties);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:pPr": [
|
||||||
|
{
|
||||||
|
"w:numPr": [
|
||||||
|
{
|
||||||
|
"w:ilvl": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:numId": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should create with widowControl", () => {
|
it("should create with widowControl", () => {
|
||||||
const properties = new ParagraphProperties({
|
const properties = new ParagraphProperties({
|
||||||
widowControl: true,
|
widowControl: true,
|
||||||
|
@ -37,12 +37,14 @@ export interface ILevelParagraphStylePropertiesOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IParagraphStylePropertiesOptions extends ILevelParagraphStylePropertiesOptions {
|
export interface IParagraphStylePropertiesOptions extends ILevelParagraphStylePropertiesOptions {
|
||||||
readonly numbering?: {
|
readonly numbering?:
|
||||||
|
| {
|
||||||
readonly reference: string;
|
readonly reference: string;
|
||||||
readonly level: number;
|
readonly level: number;
|
||||||
readonly instance?: number;
|
readonly instance?: number;
|
||||||
readonly custom?: boolean;
|
readonly custom?: boolean;
|
||||||
};
|
}
|
||||||
|
| false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions {
|
export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions {
|
||||||
@ -135,6 +137,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level));
|
this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level));
|
||||||
|
} else if (options.numbering === false) {
|
||||||
|
this.push(new NumberProperties(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.border) {
|
if (options.border) {
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { Formatter } from "@export/formatter";
|
import { Formatter } from "@export/formatter";
|
||||||
import { IViewWrapper } from "@file/document-wrapper";
|
import { IViewWrapper } from "@file/document-wrapper";
|
||||||
import { File } from "@file/file";
|
import { File } from "@file/file";
|
||||||
import * as convenienceFunctions from "@util/convenience-functions";
|
|
||||||
|
|
||||||
import { ImageRun } from "./image-run";
|
import { ImageRun } from "./image-run";
|
||||||
|
|
||||||
describe("ImageRun", () => {
|
describe("ImageRun", () => {
|
||||||
beforeAll(() => {
|
|
||||||
vi.spyOn(convenienceFunctions, "uniqueId").mockReturnValue("test-unique-id");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
vi.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#constructor()", () => {
|
describe("#constructor()", () => {
|
||||||
it("should create with Buffer", () => {
|
it("should create with Buffer", () => {
|
||||||
const currentImageRun = new ImageRun({
|
const currentImageRun = new ImageRun({
|
||||||
@ -193,7 +184,8 @@ describe("ImageRun", () => {
|
|||||||
"a:blip": {
|
"a:blip": {
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId{test-unique-id.png}",
|
"r:embed":
|
||||||
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -445,7 +437,8 @@ describe("ImageRun", () => {
|
|||||||
"a:blip": {
|
"a:blip": {
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId{test-unique-id.png}",
|
"r:embed":
|
||||||
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -700,7 +693,8 @@ describe("ImageRun", () => {
|
|||||||
"a:blip": {
|
"a:blip": {
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId{test-unique-id.png}",
|
"r:embed":
|
||||||
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -955,7 +949,8 @@ describe("ImageRun", () => {
|
|||||||
"a:blip": {
|
"a:blip": {
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId{test-unique-id.png}",
|
"r:embed":
|
||||||
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1090,7 +1085,8 @@ describe("ImageRun", () => {
|
|||||||
{
|
{
|
||||||
_attr: {
|
_attr: {
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
"r:embed": "rId{test-unique-id.png}",
|
"r:embed":
|
||||||
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1106,7 +1102,7 @@ describe("ImageRun", () => {
|
|||||||
"asvg:svgBlip": {
|
"asvg:svgBlip": {
|
||||||
_attr: expect.objectContaining({
|
_attr: expect.objectContaining({
|
||||||
"r:embed":
|
"r:embed":
|
||||||
"rId{test-unique-id.svg}",
|
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.svg}",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1131,5 +1127,55 @@ describe("ImageRun", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("using same data twice should use same media key", () => {
|
||||||
|
const imageRunStringData = new ImageRun({
|
||||||
|
type: "png",
|
||||||
|
data: "DATA",
|
||||||
|
transformation: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
rotation: 42,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageRunBufferData = new ImageRun({
|
||||||
|
type: "png",
|
||||||
|
data: Buffer.from("DATA"),
|
||||||
|
transformation: {
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
rotation: 45,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const addImageSpy = vi.fn();
|
||||||
|
const context = {
|
||||||
|
file: {
|
||||||
|
Media: {
|
||||||
|
addImage: addImageSpy,
|
||||||
|
},
|
||||||
|
} as unknown as File,
|
||||||
|
viewWrapper: {} as unknown as IViewWrapper,
|
||||||
|
stack: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
new Formatter().format(imageRunStringData, context);
|
||||||
|
new Formatter().format(imageRunBufferData, context);
|
||||||
|
|
||||||
|
const expectedHash = "580393f5a94fb469585f5dd2a6859a4aab899f37";
|
||||||
|
|
||||||
|
expect(addImageSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(addImageSpy).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
`${expectedHash}.png`,
|
||||||
|
expect.objectContaining({ fileName: `${expectedHash}.png` }),
|
||||||
|
);
|
||||||
|
expect(addImageSpy).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
`${expectedHash}.png`,
|
||||||
|
expect.objectContaining({ fileName: `${expectedHash}.png` }),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { uniqueId } from "@util/convenience-functions";
|
import { hashedId } from "@util/convenience-functions";
|
||||||
|
|
||||||
import { IContext, IXmlableObject } from "@file/xml-components";
|
import { IContext, IXmlableObject } from "@file/xml-components";
|
||||||
import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties";
|
import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties";
|
||||||
@ -76,20 +76,19 @@ const createImageData = (options: IImageOptions, key: string): Pick<IMediaData,
|
|||||||
});
|
});
|
||||||
|
|
||||||
export class ImageRun extends Run {
|
export class ImageRun extends Run {
|
||||||
private readonly key: string;
|
|
||||||
private readonly fallbackKey = `${uniqueId()}.png`;
|
|
||||||
private readonly imageData: IMediaData;
|
private readonly imageData: IMediaData;
|
||||||
|
|
||||||
public constructor(options: IImageOptions) {
|
public constructor(options: IImageOptions) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.key = `${uniqueId()}.${options.type}`;
|
const hash = hashedId(options.data);
|
||||||
|
const key = `${hash}.${options.type}`;
|
||||||
|
|
||||||
this.imageData =
|
this.imageData =
|
||||||
options.type === "svg"
|
options.type === "svg"
|
||||||
? {
|
? {
|
||||||
type: options.type,
|
type: options.type,
|
||||||
...createImageData(options, this.key),
|
...createImageData(options, key),
|
||||||
fallback: {
|
fallback: {
|
||||||
type: options.fallback.type,
|
type: options.fallback.type,
|
||||||
...createImageData(
|
...createImageData(
|
||||||
@ -97,13 +96,13 @@ export class ImageRun extends Run {
|
|||||||
...options.fallback,
|
...options.fallback,
|
||||||
transformation: options.transformation,
|
transformation: options.transformation,
|
||||||
},
|
},
|
||||||
this.fallbackKey,
|
`${hashedId(options.fallback.data)}.${options.fallback.type}`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: options.type,
|
type: options.type,
|
||||||
...createImageData(options, this.key),
|
...createImageData(options, key),
|
||||||
};
|
};
|
||||||
const drawing = new Drawing(this.imageData, {
|
const drawing = new Drawing(this.imageData, {
|
||||||
floating: options.floating,
|
floating: options.floating,
|
||||||
@ -115,10 +114,10 @@ export class ImageRun extends Run {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public prepForXml(context: IContext): IXmlableObject | undefined {
|
public prepForXml(context: IContext): IXmlableObject | undefined {
|
||||||
context.file.Media.addImage(this.key, this.imageData);
|
context.file.Media.addImage(this.imageData.fileName, this.imageData);
|
||||||
|
|
||||||
if (this.imageData.type === "svg") {
|
if (this.imageData.type === "svg") {
|
||||||
context.file.Media.addImage(this.fallbackKey, this.imageData.fallback);
|
context.file.Media.addImage(this.imageData.fallback.fileName, this.imageData.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.prepForXml(context);
|
return super.prepForXml(context);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { BaseXmlComponent, IContext } from "./base";
|
import { BaseXmlComponent, IContext } from "./base";
|
||||||
import { IXmlableObject, IXmlAttribute } from "./xmlable-object";
|
import { IXmlableObject, IXmlAttribute } from "./xmlable-object";
|
||||||
|
|
||||||
export type AttributeMap<T> = { readonly [P in keyof T]: string };
|
type AttributeMap<T> = Record<keyof T, string>;
|
||||||
|
|
||||||
export type AttributeData = { readonly [key: string]: boolean | number | string };
|
export type AttributeData = { readonly [key: string]: boolean | number | string };
|
||||||
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
|
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
|
||||||
|
|
||||||
export abstract class XmlAttributeComponent<T extends object> extends BaseXmlComponent {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export abstract class XmlAttributeComponent<T extends { readonly [key: string]: any }> extends BaseXmlComponent {
|
||||||
protected readonly xmlKeys?: AttributeMap<T>;
|
protected readonly xmlKeys?: AttributeMap<T>;
|
||||||
|
|
||||||
public constructor(private readonly root: T) {
|
public constructor(private readonly root: T) {
|
||||||
@ -16,12 +17,11 @@ export abstract class XmlAttributeComponent<T extends object> extends BaseXmlCom
|
|||||||
public prepForXml(_: IContext): IXmlableObject {
|
public prepForXml(_: IContext): IXmlableObject {
|
||||||
// eslint-disable-next-line functional/prefer-readonly-type
|
// eslint-disable-next-line functional/prefer-readonly-type
|
||||||
const attrs: { [key: string]: string } = {};
|
const attrs: { [key: string]: string } = {};
|
||||||
Object.keys(this.root).forEach((key) => {
|
Object.entries(this.root).forEach(([key, value]) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const value = (this.root as any)[key];
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const newKey = (this.xmlKeys && (this.xmlKeys as any)[key]) || key;
|
const newKey = (this.xmlKeys && this.xmlKeys[key]) || key;
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
attrs[newKey] = value;
|
attrs[newKey] = value;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import { appendRelationship, getNextRelationshipIndex } from "./relationship-man
|
|||||||
import { appendContentType } from "./content-types-manager";
|
import { appendContentType } from "./content-types-manager";
|
||||||
|
|
||||||
// eslint-disable-next-line functional/prefer-readonly-type
|
// eslint-disable-next-line functional/prefer-readonly-type
|
||||||
type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream;
|
export type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream;
|
||||||
|
|
||||||
export const PatchType = {
|
export const PatchType = {
|
||||||
DOCUMENT: "file",
|
DOCUMENT: "file",
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./from-docx";
|
export * from "./from-docx";
|
||||||
|
export * from "./patch-detector";
|
||||||
|
@ -273,5 +273,65 @@ describe("paragraph-split-inject", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create an empty end element if it is at the end", () => {
|
||||||
|
const output = splitRunElement(
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
|
||||||
|
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
|
||||||
|
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
|
||||||
|
{ type: "element", name: "w:br" },
|
||||||
|
{ type: "element", name: "w:t", elements: [{ type: "text", text: "ɵ" }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ɵ",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).to.deep.equal({
|
||||||
|
left: {
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
|
||||||
|
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
|
||||||
|
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
|
||||||
|
{ type: "element", name: "w:br" },
|
||||||
|
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly
|
|||||||
runElement.elements
|
runElement.elements
|
||||||
?.map((e, i) => {
|
?.map((e, i) => {
|
||||||
if (e.type === "element" && e.name === "w:t") {
|
if (e.type === "element" && e.name === "w:t") {
|
||||||
const text = (e.elements?.[0].text as string) ?? "";
|
const text = (e.elements?.[0]?.text as string) ?? "";
|
||||||
const splitText = text.split(token);
|
const splitText = text.split(token);
|
||||||
const newElements = splitText.map((t) => ({
|
const newElements = splitText.map((t) => ({
|
||||||
...e,
|
...e,
|
||||||
|
393
src/patcher/patch-detector.spec.ts
Normal file
393
src/patcher/patch-detector.spec.ts
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import JSZip from "jszip";
|
||||||
|
import { patchDetector } from "./patch-detector";
|
||||||
|
|
||||||
|
const MOCK_XML = `
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
|
||||||
|
xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
|
||||||
|
xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
|
||||||
|
xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex"
|
||||||
|
xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex"
|
||||||
|
xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex"
|
||||||
|
xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex"
|
||||||
|
xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex"
|
||||||
|
xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex"
|
||||||
|
xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink"
|
||||||
|
xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
xmlns:oel="http://schemas.microsoft.com/office/2019/extlst"
|
||||||
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||||
|
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||||
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
|
||||||
|
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||||
|
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
||||||
|
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"
|
||||||
|
xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
|
||||||
|
xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
|
||||||
|
xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
|
||||||
|
xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"
|
||||||
|
xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
|
||||||
|
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
|
||||||
|
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
|
||||||
|
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
|
||||||
|
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
|
||||||
|
<w:body>
|
||||||
|
<w:p w14:paraId="2499FE9F" w14:textId="0A3D130F" w:rsidR="00B51233"
|
||||||
|
w:rsidRDefault="007B52ED" w:rsidP="007B52ED">
|
||||||
|
<w:pPr>
|
||||||
|
<w:pStyle w:val="Title" />
|
||||||
|
</w:pPr>
|
||||||
|
<w:r>
|
||||||
|
<w:t>Hello World</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p w14:paraId="6410D9A0" w14:textId="7579AB49" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED" />
|
||||||
|
<w:p w14:paraId="57ACF964" w14:textId="315D7A05" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED">
|
||||||
|
<w:r>
|
||||||
|
<w:t>Hello {{name}},</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r w:rsidR="008126CB">
|
||||||
|
<w:t xml:space="preserve"> how are you?</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p w14:paraId="38C7DF4A" w14:textId="66CDEC9A" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED" />
|
||||||
|
<w:p w14:paraId="04FABE2B" w14:textId="3DACA001" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED">
|
||||||
|
<w:r>
|
||||||
|
<w:t>{{paragraph_replace}}</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p w14:paraId="7AD7975D" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
<w:p w14:paraId="3BD6D75A" w14:textId="19AE3121" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F">
|
||||||
|
<w:r>
|
||||||
|
<w:t>{{table}}</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p w14:paraId="76023962" w14:textId="4E606AB9" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED" />
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr>
|
||||||
|
<w:tblStyle w:val="TableGrid" />
|
||||||
|
<w:tblW w:w="0" w:type="auto" />
|
||||||
|
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1"
|
||||||
|
w:lastColumn="0" w:noHBand="0" w:noVBand="1" />
|
||||||
|
</w:tblPr>
|
||||||
|
<w:tblGrid>
|
||||||
|
<w:gridCol w:w="3003" />
|
||||||
|
<w:gridCol w:w="3003" />
|
||||||
|
<w:gridCol w:w="3004" />
|
||||||
|
</w:tblGrid>
|
||||||
|
<w:tr w:rsidR="00EF161F" w14:paraId="1DEC5955" w14:textId="77777777" w:rsidTr="00EF161F">
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="54DA5587" w14:textId="625BAC60" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F">
|
||||||
|
<w:r>
|
||||||
|
<w:t>{{table_heading_1}}</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="57100910" w14:textId="71FD5616" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3004" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="1D388FAB" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr w:rsidR="00EF161F" w14:paraId="0F53D2DC" w14:textId="77777777" w:rsidTr="00EF161F">
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="0F2BCCED" w14:textId="3C3B6706" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F">
|
||||||
|
<w:r>
|
||||||
|
<w:t>Item: {{item_1}}</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="1E6158AC" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3004" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="17937748" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr w:rsidR="00EF161F" w14:paraId="781DAC1A" w14:textId="77777777" w:rsidTr="00EF161F">
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="1DCD0343" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3003" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="5D02E3CD" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3004" w:type="dxa" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p w14:paraId="52EA0DBB" w14:textId="77777777" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="00EF161F" />
|
||||||
|
</w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
<w:p w14:paraId="47CD1FBC" w14:textId="23474CBC" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED" />
|
||||||
|
<w:p w14:paraId="0ACCEE90" w14:textId="67907499" w:rsidR="00EF161F"
|
||||||
|
w:rsidRDefault="0077578F">
|
||||||
|
<w:r>
|
||||||
|
<w:t>{{image_test}}</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p w14:paraId="23FA9862" w14:textId="77777777" w:rsidR="0077578F"
|
||||||
|
w:rsidRDefault="0077578F" />
|
||||||
|
<w:p w14:paraId="01578F2F" w14:textId="3BDC6C85" w:rsidR="007B52ED"
|
||||||
|
w:rsidRDefault="007B52ED">
|
||||||
|
<w:r>
|
||||||
|
<w:t>Thank you</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:sectPr w:rsidR="007B52ED" w:rsidSect="0072043F">
|
||||||
|
<w:headerReference w:type="default" r:id="rId6" />
|
||||||
|
<w:footerReference w:type="default" r:id="rId7" />
|
||||||
|
<w:pgSz w:w="11900" w:h="16840" />
|
||||||
|
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708"
|
||||||
|
w:footer="708" w:gutter="0" />
|
||||||
|
<w:cols w:space="708" />
|
||||||
|
<w:docGrid w:linePitch="360" />
|
||||||
|
</w:sectPr>
|
||||||
|
</w:body>
|
||||||
|
</w:document>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// cspell:disable
|
||||||
|
const MOCK_XML_2 = `
|
||||||
|
<w:body>
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr>
|
||||||
|
<w:tblStyle w:val="TableGrid" />
|
||||||
|
<w:tblW w:w="9350" w:type="dxa" />
|
||||||
|
<w:jc w:val="left" />
|
||||||
|
<w:tblInd w:w="0" w:type="dxa" />
|
||||||
|
<w:tblLayout w:type="fixed" />
|
||||||
|
<w:tblCellMar>
|
||||||
|
<w:top w:w="0" w:type="dxa" />
|
||||||
|
<w:left w:w="108" w:type="dxa" />
|
||||||
|
<w:bottom w:w="0" w:type="dxa" />
|
||||||
|
<w:right w:w="108" w:type="dxa" />
|
||||||
|
</w:tblCellMar>
|
||||||
|
<w:tblLook w:firstRow="1" w:noVBand="1" w:lastRow="0" w:firstColumn="1"
|
||||||
|
w:lastColumn="0" w:noHBand="0" w:val="04a0" />
|
||||||
|
</w:tblPr>
|
||||||
|
<w:tblGrid>
|
||||||
|
<w:gridCol w:w="3119" />
|
||||||
|
<w:gridCol w:w="3141" />
|
||||||
|
<w:gridCol w:w="3090" />
|
||||||
|
</w:tblGrid>
|
||||||
|
<w:tr>
|
||||||
|
<w:trPr></w:trPr>
|
||||||
|
<w:tc>
|
||||||
|
<w:tcPr>
|
||||||
|
<w:tcW w:w="3119" w:type="dxa" />
|
||||||
|
<w:tcBorders>
|
||||||
|
<w:right w:val="nil" />
|
||||||
|
</w:tcBorders>
|
||||||
|
<w:shd w:color="auto" w:fill="D9D9D9" w:themeFill="background1"
|
||||||
|
w:themeFillShade="d9" w:val="clear" />
|
||||||
|
</w:tcPr>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr>
|
||||||
|
<w:pStyle w:val="NormalSpaceAboveandBelow" />
|
||||||
|
<w:widowControl />
|
||||||
|
<w:spacing w:before="120" w:after="120" />
|
||||||
|
<w:jc w:val="left" />
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
</w:pPr>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>{{</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>s</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>chool_</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>n</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>ame}}</w:t>
|
||||||
|
<w:br />
|
||||||
|
<w:t>{{</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>a</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>ddr</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>ess</w:t>
|
||||||
|
</w:r>
|
||||||
|
<w:r>
|
||||||
|
<w:rPr>
|
||||||
|
<w:rFonts w:eastAsia="Times New Roman" />
|
||||||
|
<w:kern w:val="0" />
|
||||||
|
<w:sz w:val="20" />
|
||||||
|
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
|
||||||
|
</w:rPr>
|
||||||
|
<w:t>}}</w:t>
|
||||||
|
<w:br />
|
||||||
|
<w:t>{{</w:t>
|
||||||
|
</w:r>
|
||||||
|
</w:p>
|
||||||
|
</w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
</w:body>
|
||||||
|
`;
|
||||||
|
// cspell:enable
|
||||||
|
|
||||||
|
describe("patch-detector", () => {
|
||||||
|
describe("patchDetector", () => {
|
||||||
|
describe("document.xml and [Content_Types].xml", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
|
||||||
|
new Promise<JSZip>((resolve) => {
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
zip.file("word/document.xml", MOCK_XML);
|
||||||
|
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
|
||||||
|
resolve(zip);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should patch the document", async () => {
|
||||||
|
const output = await patchDetector({
|
||||||
|
data: Buffer.from(""),
|
||||||
|
});
|
||||||
|
expect(output).toMatchObject(["name", "paragraph_replace", "table", "image_test", "table_heading_1", "item_1"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("patchDetector", () => {
|
||||||
|
describe("document.xml and [Content_Types].xml", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
|
||||||
|
new Promise<JSZip>((resolve) => {
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
zip.file("word/document.xml", MOCK_XML_2);
|
||||||
|
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
|
||||||
|
resolve(zip);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should patch the document", async () => {
|
||||||
|
const output = await patchDetector({
|
||||||
|
data: Buffer.from(""),
|
||||||
|
});
|
||||||
|
expect(output).toMatchObject(["school_name", "address"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
30
src/patcher/patch-detector.ts
Normal file
30
src/patcher/patch-detector.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import JSZip from "jszip";
|
||||||
|
import { toJson } from "./util";
|
||||||
|
import { traverse } from "./traverser";
|
||||||
|
import { InputDataType } from "./from-docx";
|
||||||
|
|
||||||
|
type PatchDetectorOptions = {
|
||||||
|
readonly data: InputDataType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Detects which patches are needed/present in a template */
|
||||||
|
export const patchDetector = async ({ data }: PatchDetectorOptions): Promise<readonly string[]> => {
|
||||||
|
const zipContent = await JSZip.loadAsync(data);
|
||||||
|
const patches = new Set<string>();
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(zipContent.files)) {
|
||||||
|
if (!key.endsWith(".xml") && !key.endsWith(".rels")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key.startsWith("word/") && !key.endsWith(".xml.rels")) {
|
||||||
|
const json = toJson(await value.async("text"));
|
||||||
|
traverse(json).forEach((p) => findPatchKeys(p.text).forEach((patch) => patches.add(patch)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(patches);
|
||||||
|
};
|
||||||
|
|
||||||
|
const findPatchKeys = (text: string): readonly string[] => {
|
||||||
|
const pattern = /(?<=\{\{).+?(?=\}\})/gs;
|
||||||
|
return text.match(pattern) ?? [];
|
||||||
|
};
|
@ -200,5 +200,454 @@ describe("replacer", () => {
|
|||||||
|
|
||||||
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
|
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should replace", () => {
|
||||||
|
// cspell:disable
|
||||||
|
const output = replacer({
|
||||||
|
json: {
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:hdr",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:p",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "{{" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "s" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "chool_" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "n" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "{{" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "a" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "ddr" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "ess" }],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rFonts",
|
||||||
|
attributes: { "w:eastAsia": "Times New Roman" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:kern",
|
||||||
|
attributes: { "w:val": "0" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:sz",
|
||||||
|
attributes: { "w:val": "20" },
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:lang",
|
||||||
|
attributes: {
|
||||||
|
"w:val": "en-US",
|
||||||
|
"w:eastAsia": "en-US",
|
||||||
|
"w:bidi": "ar-SA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "text", text: "\n " },
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "}}" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// cspell:enable
|
||||||
|
patch: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new Paragraph("Lorem ipsum paragraph")],
|
||||||
|
},
|
||||||
|
patchText: "{{address}}",
|
||||||
|
context: {
|
||||||
|
file: {} as unknown as File,
|
||||||
|
viewWrapper: {
|
||||||
|
Relationships: {},
|
||||||
|
} as unknown as IViewWrapper,
|
||||||
|
stack: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -67,7 +67,7 @@ export const replacer = ({
|
|||||||
|
|
||||||
if (keepOriginalStyles) {
|
if (keepOriginalStyles) {
|
||||||
const runElementNonTextualElements = runElementToBeReplaced.elements!.filter(
|
const runElementNonTextualElements = runElementToBeReplaced.elements!.filter(
|
||||||
(e) => e.type === "element" && e.name !== "w:t",
|
(e) => e.type === "element" && e.name !== "w:t" && e.name !== "w:br",
|
||||||
);
|
);
|
||||||
|
|
||||||
newRunElements = textJson.map((e) => ({
|
newRunElements = textJson.map((e) => ({
|
||||||
|
@ -15,7 +15,7 @@ const elementsToWrapper = (wrapper: ElementWrapper): readonly ElementWrapper[] =
|
|||||||
parent: wrapper,
|
parent: wrapper,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
export const findLocationOfText = (node: Element, text: string): readonly IRenderedParagraphNode[] => {
|
export const traverse = (node: Element): readonly IRenderedParagraphNode[] => {
|
||||||
let renderedParagraphs: readonly IRenderedParagraphNode[] = [];
|
let renderedParagraphs: readonly IRenderedParagraphNode[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line functional/prefer-readonly-type
|
// eslint-disable-next-line functional/prefer-readonly-type
|
||||||
@ -41,5 +41,8 @@ export const findLocationOfText = (node: Element, text: string): readonly IRende
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderedParagraphs.filter((p) => p.text.includes(text));
|
return renderedParagraphs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const findLocationOfText = (node: Element, text: string): readonly IRenderedParagraphNode[] =>
|
||||||
|
traverse(node).filter((p) => p.text.includes(text));
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
convertMillimetersToTwip,
|
convertMillimetersToTwip,
|
||||||
docPropertiesUniqueNumericIdGen,
|
docPropertiesUniqueNumericIdGen,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
|
hashedId,
|
||||||
uniqueNumericIdCreator,
|
uniqueNumericIdCreator,
|
||||||
uniqueUuid,
|
uniqueUuid,
|
||||||
} from "./convenience-functions";
|
} from "./convenience-functions";
|
||||||
@ -72,6 +73,26 @@ describe("Utility", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#hashedId", () => {
|
||||||
|
it("should generate a hex string", () => {
|
||||||
|
expect(hashedId("")).to.equal("da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with string, Uint8Array, Buffer and ArrayBuffer", () => {
|
||||||
|
const stringInput = "DATA";
|
||||||
|
const uint8ArrayInput = new Uint8Array(new TextEncoder().encode(stringInput));
|
||||||
|
const bufferInput = Buffer.from(uint8ArrayInput);
|
||||||
|
const arrayBufferInput = uint8ArrayInput.buffer;
|
||||||
|
|
||||||
|
const expectedHash = "580393f5a94fb469585f5dd2a6859a4aab899f37";
|
||||||
|
|
||||||
|
expect(hashedId(stringInput)).to.equal(expectedHash);
|
||||||
|
expect(hashedId(uint8ArrayInput)).to.equal(expectedHash);
|
||||||
|
expect(hashedId(bufferInput)).to.equal(expectedHash);
|
||||||
|
expect(hashedId(arrayBufferInput)).to.equal(expectedHash);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#uniqueUuid", () => {
|
describe("#uniqueUuid", () => {
|
||||||
it("should generate a unique pseudorandom ID", () => {
|
it("should generate a unique pseudorandom ID", () => {
|
||||||
expect(uniqueUuid()).to.not.be.empty;
|
expect(uniqueUuid()).to.not.be.empty;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { nanoid, customAlphabet } from "nanoid/non-secure";
|
import { nanoid, customAlphabet } from "nanoid/non-secure";
|
||||||
|
import hash from "hash.js";
|
||||||
|
|
||||||
// Twip - twentieths of a point
|
// Twip - twentieths of a point
|
||||||
export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20);
|
export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20);
|
||||||
@ -24,6 +25,12 @@ export const bookmarkUniqueNumericIdGen = (): UniqueNumericIdCreator => uniqueNu
|
|||||||
|
|
||||||
export const uniqueId = (): string => nanoid().toLowerCase();
|
export const uniqueId = (): string => nanoid().toLowerCase();
|
||||||
|
|
||||||
|
export const hashedId = (data: Buffer | string | Uint8Array | ArrayBuffer): string =>
|
||||||
|
hash
|
||||||
|
.sha1()
|
||||||
|
.update(data instanceof ArrayBuffer ? new Uint8Array(data) : data)
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
const generateUuidPart = (count: number): string => customAlphabet("1234567890abcdef", count)();
|
const generateUuidPart = (count: number): string => customAlphabet("1234567890abcdef", count)();
|
||||||
export const uniqueUuid = (): string =>
|
export const uniqueUuid = (): string =>
|
||||||
`${generateUuidPart(8)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(12)}`;
|
`${generateUuidPart(8)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(12)}`;
|
||||||
|
@ -7,7 +7,9 @@ import { nodePolyfills } from "vite-plugin-node-polyfills";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
dts(),
|
dts({
|
||||||
|
rollupTypes: true
|
||||||
|
}),
|
||||||
nodePolyfills({
|
nodePolyfills({
|
||||||
exclude: [],
|
exclude: [],
|
||||||
globals: {
|
globals: {
|
||||||
|
Reference in New Issue
Block a user