Initial commit
This commit is contained in:
commit
e49726988c
5445 files changed
+1089914
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Alon Bar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+826
@@ -0,0 +1,826 @@
|
||||
# easy-template-x
|
||||
|
||||
Generate docx documents from templates, in Node or in the browser.
|
||||
|
||||
[](https://github.com/alonrbar/easy-template-x/actions/workflows/ci.yaml)
|
||||
[](https://www.npmjs.com/package/easy-template-x)
|
||||
[](https://www.npmjs.com/package/easy-template-x)
|
||||
|
||||
> ---
|
||||
>
|
||||
> 🚀 **Looking for a managed solution?**
|
||||
>
|
||||
> Check out [templatedocs.io](https://templatedocs.io) - a cloud platform for document generation with:
|
||||
>
|
||||
> ✓ PDF support
|
||||
> ✓ REST API integration
|
||||
> ✓ Zapier and Make integration
|
||||
> ✓ Live preview functionality
|
||||
> ✓ Easy-to-use UI
|
||||
>
|
||||
> And more...
|
||||
>
|
||||
> ---
|
||||
|
||||
- [Node Example](#node-example)
|
||||
- [Browser Example](#browser-example)
|
||||
- [Live Demo](#live-demo)
|
||||
- [Plugins](#plugins)
|
||||
- [Text plugin](#text-plugin)
|
||||
- [Loop plugin](#loop-plugin)
|
||||
- [Conditions](#conditions)
|
||||
- [Nested Conditions](#nested-conditions)
|
||||
- [Controlling loop behavior](#controlling-loop-behavior)
|
||||
- [Image plugin](#image-plugin)
|
||||
- [Link plugin](#link-plugin)
|
||||
- [Chart plugin](#chart-plugin)
|
||||
- [Raw xml plugin](#raw-xml-plugin)
|
||||
- [Writing custom plugins](#writing-your-own-plugins)
|
||||
- [Listing tags](#listing-tags)
|
||||
- [Scope resolution](#scope-resolution)
|
||||
- [Extensions](#extensions)
|
||||
- [Community Extensions](#community-extensions)
|
||||
- [Data Binding Extension](#data-binding-extension)
|
||||
- [Template handler options](#template-handler-options)
|
||||
- [Custom tag delimiters](#custom-tag-delimiters)
|
||||
- [Advanced syntax and custom resolvers](#advanced-syntax-and-custom-resolvers)
|
||||
- [Supported Binary Formats](#supported-binary-formats)
|
||||
- [Internal API](#note---internal-api)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Changelog](#changelog)
|
||||
|
||||
## Node Example
|
||||
|
||||
```typescript
|
||||
import * as fs from 'fs';
|
||||
import { TemplateHandler } from 'easy-template-x';
|
||||
|
||||
// 1. read template file
|
||||
const templateFile = fs.readFileSync('myTemplate.docx');
|
||||
|
||||
// 2. process the template
|
||||
const data = {
|
||||
posts: [
|
||||
{ author: 'Alon Bar', text: 'Very important\ntext here!' },
|
||||
{ author: 'Alon Bar', text: 'Forgot to mention that...' }
|
||||
]
|
||||
};
|
||||
|
||||
const handler = new TemplateHandler();
|
||||
const doc = await handler.process(templateFile, data);
|
||||
|
||||
// 3. save output
|
||||
fs.writeFileSync('myTemplate - output.docx', doc);
|
||||
```
|
||||
|
||||
Input:
|
||||
|
||||

|
||||
|
||||
Output:
|
||||
|
||||

|
||||
|
||||
## Browser Example
|
||||
|
||||
The following example produces the same output while running in the browser.
|
||||
Notice that the actual template processing (step 2) is exactly the same as in the previous Node example.
|
||||
|
||||
```typescript
|
||||
import { TemplateHandler } from 'easy-template-x';
|
||||
|
||||
// 1. read template file
|
||||
|
||||
// (in this example we're loading the template by performing
|
||||
// an AJAX call using the fetch API, another common way to
|
||||
// get your hand on a Blob is to use an HTML File Input)
|
||||
const response = await fetch('http://somewhere.com/myTemplate.docx');
|
||||
const templateFile = await response.blob();
|
||||
|
||||
// 2. process the template
|
||||
const data = {
|
||||
posts: [
|
||||
{ author: 'Alon Bar', text: 'Very important\ntext here!' },
|
||||
{ author: 'Alon Bar', text: 'Forgot to mention that...' }
|
||||
]
|
||||
};
|
||||
|
||||
const handler = new TemplateHandler();
|
||||
const doc = await handler.process(templateFile, data);
|
||||
|
||||
// 3. save output
|
||||
saveFile('myTemplate - output.docx', doc);
|
||||
|
||||
function saveFile(filename, blob) {
|
||||
|
||||
// see: https://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link
|
||||
|
||||
// get downloadable url from the blob
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// create temp link element
|
||||
let link = document.createElement("a");
|
||||
link.download = filename;
|
||||
link.href = blobUrl;
|
||||
|
||||
// use the link to invoke a download
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// remove the link
|
||||
setTimeout(() => {
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
link = null;
|
||||
}, 0);
|
||||
}
|
||||
```
|
||||
|
||||
## Live Demo
|
||||
|
||||
Checkout this [live demo](https://codesandbox.io/p/sandbox/easy-template-x-demo-x4ppu?file=%2Findex.ts) on CodeSandbox 😎
|
||||
|
||||
## Plugins
|
||||
|
||||
`easy-template-x` uses a plugin model to support it's various template manipulation capabilities. There are some built-in plugins and you can also write your own custom plugins if required.
|
||||
|
||||
These are the plugins that comes bundled with `easy-template-x`:
|
||||
|
||||
- [Text plugin](#text-plugin) - For simple text replacement.
|
||||
- [Loop plugin](#loop-plugin) - For iterating text, table rows, table columns and list rows and for simple conditions.
|
||||
- [Image plugin](#image-plugin) - For embedding images.
|
||||
- [Link plugin](#link-plugin) - For hyperlinks creation.
|
||||
- [Chart plugin](#chart-plugin) - For handling charts.
|
||||
- [Raw xml plugin](#raw-xml-plugin) - For custom xml insertion.
|
||||
|
||||
### Text plugin
|
||||
|
||||
The most basic plugin. Replaces a single tag with custom text. Preserves the original text style.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```json
|
||||
{
|
||||
"First Tag": "Quis et ducimus voluptatum\nipsam id.",
|
||||
"Second Tag": "Dolorem sit voluptas magni dolorem molestias."
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
### Loop plugin
|
||||
|
||||
Iterates text, table rows, table columns and lists.
|
||||
Requires an opening tag that starts with `#` and a closing tag that starts with `/` ([configurable](#custom-tag-delimiters)).
|
||||
|
||||
**Note**: The closing tag does not need to have the same name as the opening tag, or a name at all. This will work `{#loop}{/loop}`, but also this `{#loop}{/}` and even this `{#loop}{/something else}`.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```json
|
||||
{
|
||||
"Beers": [
|
||||
{ "Brand": "Carlsberg", "Price": 1 },
|
||||
{ "Brand": "Leaf Blonde", "Price": 2 },
|
||||
{ "Brand": "Weihenstephan", "Price": 1.5 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
#### Conditions
|
||||
|
||||
You can render content conditionally depending on a boolean value using the same syntax used for loops.
|
||||
|
||||
The example below shows two lines being rendered, each with different content depending on the truthy value.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
lines: [
|
||||
{ visible: true },
|
||||
{ invisible: true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
#### Nested Conditions
|
||||
|
||||
Nested conditions are also supported, so you can nest other tags including loop tags and even other conditions in them. When doing so remember to format your data accordingly. See the example below for clarification:
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
Notice how even though `name` and `members` are nested in the template under the `show` condition their values are adjacent to it in the input data.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"teams": [
|
||||
{
|
||||
show: true,
|
||||
name: "A-Team",
|
||||
members: [
|
||||
{ name: "Hannibal" },
|
||||
{ name: "Face" },
|
||||
{ name: "Murdock" },
|
||||
{ name: "Baracus" },
|
||||
]
|
||||
},
|
||||
{
|
||||
show: false,
|
||||
name: "B-Team",
|
||||
members: [
|
||||
{ name: "Alice" },
|
||||
{ name: "Bob" },
|
||||
{ name: "Charlie" },
|
||||
{ name: "Dave" },
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
_If you are looking for a yet more powerful conditional syntax see the [alternative syntax](#advanced-syntax-and-custom-resolvers) section._
|
||||
|
||||
#### Controlling loop behavior
|
||||
|
||||
The loop plugin uses some heuristics to determine the right behavior in each
|
||||
case (i.e. when to loop over rows, columns, paragraphs, etc.). You can control this behavior
|
||||
explicitly through the `loopOver` option as explain below.
|
||||
|
||||
##### Default behavior
|
||||
|
||||
The default heuristics are as follows:
|
||||
|
||||
1. If both loop tags are inside the same table cell - the plugin assumes you want to repeat the **cell content**, not the row or column.
|
||||
2. If both loop tags are in the same column - the plugin will repeat the **column**.
|
||||
3. If both loop tags are inside a table - the plugin will repeat the relevant table **rows**.
|
||||
4. If both loop tags are in a list - the plugin will repeat the relevant **list items**.
|
||||
5. Otherwise - the plugin will use a naive approach for repeating the **content** in between the loop tags.
|
||||
|
||||
##### Changing the default
|
||||
|
||||
To use a different behavior than the default one you can use the `loopOver`
|
||||
option. Supported values are: `row`, `column` and `content`.
|
||||
|
||||
**Note:** This option controls conditions too.
|
||||
|
||||
For instance, given this data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
students: [
|
||||
{ name: "Alice" },
|
||||
{ name: "Bob" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can use this syntax:
|
||||
|
||||

|
||||
|
||||
Or this syntax:
|
||||
|
||||

|
||||
|
||||
Or this syntax:
|
||||
|
||||

|
||||
|
||||
The first will produce this document:
|
||||
|
||||

|
||||
|
||||
The second will produce this document:
|
||||
|
||||

|
||||
|
||||
And the third will produce this document:
|
||||
|
||||

|
||||
|
||||
### Image plugin
|
||||
|
||||
Embed images into the document.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"Kung Fu Hero": {
|
||||
_type: "image",
|
||||
source: fs.readFileSync("hero.png"),
|
||||
format: MimeType.Png,
|
||||
width: 200,
|
||||
height: 200,
|
||||
altText: "Kung Fu Hero", // Optional
|
||||
transparencyPercent: 80 // Optional
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
### Link plugin
|
||||
|
||||
Inserts hyperlinks into the document.
|
||||
Like text tags link tags also preserve their original style.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"easy": {
|
||||
_type: 'link',
|
||||
text: 'super easy', // Optional - if not specified the `target` property will be used
|
||||
target: 'https://github.com/alonrbar/easy-template-x'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
### Chart plugin
|
||||
|
||||
To use charts in templates, put a placeholder chart and place a tag in its title, like so:
|
||||
|
||||

|
||||
|
||||
Generally speaking, the charts preserve their original settings and style with
|
||||
some configurations also available through the input json data.
|
||||
|
||||
`easy-template-x` try to keep the same terminology used by MS Word. Therefore,
|
||||
while the exact input data schema depends on the chart type, the main terms
|
||||
are common to most:
|
||||
|
||||
- **Categories** are the values of the X axis.
|
||||
- **Series** are the values of the Y axis.
|
||||
|
||||
Take a look at the examples below to get a better understanding of how chart
|
||||
tags behave.
|
||||
|
||||
#### Line, bar & column charts
|
||||
|
||||
Line, bar & column charts all uses the same input data format.
|
||||
|
||||
The example below shows 4 charts in the same template: Line chart, Column chart, Bar chart and Stacked Column chart. In this example all of the charts use the same input data (the `MyChart` tag data) but you can of course use a different tag for each chart.
|
||||
|
||||
Notice how the end result ignores the placeholder data and matches our input data instead.
|
||||
|
||||
Note that you are not limited to use the same number of categories or series as the placeholder chart. This specific example uses less series than the placeholder (2 instead of 3) but you can just as easily add more series or change the number of categories.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
MyChart: {
|
||||
_type: "chart",
|
||||
title: "Easy Chart", // Optional
|
||||
categories: {
|
||||
names: ["Q1", "Q2", "Q3", "Q4"]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "Earnings", // Optional
|
||||
color: "#34d399", // Optional
|
||||
values: [100, 210, 150, 170]
|
||||
},
|
||||
{
|
||||
name: "Expenses",
|
||||
color: "#f87171",
|
||||
values: [170, 165, 169, 155]
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
#### Pie & doughnut charts
|
||||
|
||||
Pie & doughnut uses the same input data format as line, bar and column chart,
|
||||
except they expect a single series.
|
||||
|
||||
The example below shows 4 charts in the same template: 2 pie charts and 2 doughnut charts, each with a different style.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
Chart1: {
|
||||
_type: "chart",
|
||||
title: "Easy Chart", // Optional
|
||||
categories: {
|
||||
names: ["Q1", "Q2", "Q3", "Q4"]
|
||||
},
|
||||
series: [
|
||||
{ values: [100, 210, 150, 170] },
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
#### Scatter chart
|
||||
|
||||
Scatter chart do not have a `categories` property. Instead, each of their values requires an `x` and a `y` properties.
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
scatter: {
|
||||
_type: "chart",
|
||||
title: "Easy Scatter Chart", // Optional
|
||||
series: [
|
||||
{
|
||||
name: "Earnings", // Optional
|
||||
color: "#34d399", // Optional
|
||||
values: [
|
||||
{ x: 1, y: 310 },
|
||||
{ x: 3, y: 450 },
|
||||
{ x: 4, y: 200 },
|
||||
{ x: 6, y: 200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Expenses",
|
||||
color: "#f87171",
|
||||
values: [
|
||||
{ x: 1, y: 410 },
|
||||
{ x: 2, y: 450 },
|
||||
{ x: 3, y: 200 },
|
||||
{ x: 5, y: 350 },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
#### Bubble chart
|
||||
|
||||
Bubble charts are very similar to scatter charts but they have an additional
|
||||
`size` property (notice the placeholder chart should be a bubble chart, not a
|
||||
scatter chart).
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
bubble: {
|
||||
_type: "chart",
|
||||
title: "Bubble Chart", // Optional
|
||||
series: [
|
||||
{
|
||||
name: "Earnings", // Optional
|
||||
color: "#34d399", // Optional
|
||||
values: [
|
||||
{ x: 1, y: 10, size: 10 },
|
||||
{ x: 2, y: 10, size: 20 },
|
||||
{ x: 3, y: 8, size: 40 },
|
||||
{ x: 4, y: 8, size: 30 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Expenses",
|
||||
color: "#f87171",
|
||||
values: [
|
||||
{ x: 1, y: 4, size: 40 },
|
||||
{ x: 2, y: 4, size: 20 },
|
||||
{ x: 3, y: 3, size: 30 },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
### Raw xml plugin
|
||||
|
||||
Add custom xml into the document to be interpreted by Word.
|
||||
|
||||
**Tip**:
|
||||
You can add page breaks using this plugin and the following xml markup:
|
||||
`<w:br w:type="page"/>`
|
||||
|
||||
Input template:
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"Dont worry be happy": {
|
||||
_type: 'rawXml',
|
||||
xml: '<w:sym w:font="Wingdings" w:char="F04A"/>',
|
||||
replaceParagraph: false, // Optional - should the plugin replace an entire paragraph or just the tag itself
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
### Writing your own plugins
|
||||
|
||||
To write a plugin inherit from the [TemplatePlugin](./src/plugins/templatePlugin.ts) class.
|
||||
The base class provides two methods you can implement and a set of [utilities](./src/plugins/templatePlugin.ts) to
|
||||
make it easier to do the actual xml modification.
|
||||
|
||||
_To better understand the internal structure of Word documents check out [this excellent source](http://officeopenxml.com/WPcontentOverview.php)._
|
||||
|
||||
Example plugin implementation ([source](./src/plugins/rawXml/rawXmlPlugin.ts)):
|
||||
|
||||
```typescript
|
||||
import { officeMarkup, xml } from "easy-template-x";
|
||||
|
||||
/**
|
||||
* A plugin that inserts raw xml to the document.
|
||||
*/
|
||||
export class RawXmlPlugin extends TemplatePlugin {
|
||||
|
||||
// Declare the unique "content type" this plugin handles
|
||||
public readonly contentType = 'rawXml';
|
||||
|
||||
// Plugin logic goes here:
|
||||
public simpleTagReplacements(tag: Tag, data: ScopeData): void {
|
||||
|
||||
// Get the value to use from the input data.
|
||||
const value = data.getScopeData<RawXmlContent>();
|
||||
if (value && typeof value.xml === 'string') {
|
||||
|
||||
// Tag.xmlTextNode always reference the actual xml text node.
|
||||
// In MS Word each text node is wrapped by a <w:t> node so we retrieve that.
|
||||
const wordTextNode = officeMarkup.query.containingTextNode(tag.xmlTextNode);
|
||||
|
||||
// If the input data contains an "xml" string property, parse it and insert
|
||||
// the content next to the placeholder tag.
|
||||
const newNode = xml.parser.parse(value.xml);
|
||||
xml.modify.insertBefore(newNode, wordTextNode);
|
||||
}
|
||||
|
||||
// Remove the placeholder tag.
|
||||
officeMarkup.modify.removeTag(tag.xmlTextNode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The content type that this plugin expects to see is:
|
||||
|
||||
```typescript
|
||||
export interface RawXmlContent extends PluginContent {
|
||||
_type: 'rawXml';
|
||||
xml: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Listing tags
|
||||
|
||||
You can get the list of [tags](https://github.com/alonrbar/easy-template-x/blob/8a88535ef090fc357cf3523411bef0d0729d10c8/src/compilation/tag.ts) in a template by calling the `parseTags` method as follows:
|
||||
|
||||
```typescript
|
||||
import { TemplateHandler } from 'easy-template-x';
|
||||
|
||||
const templateFile = fs.readFileSync('myTemplate.docx');
|
||||
|
||||
const handler = new TemplateHandler();
|
||||
const tags = await handler.parseTags(templateFile);
|
||||
```
|
||||
|
||||
## Scope resolution
|
||||
|
||||
`easy-template-x` supports tag data scoping. That is, you can reference
|
||||
"shallow" data from within deeper in the hierarchy similarly to how you can
|
||||
reference an outer scope variables from within a function in JavaScript. You can
|
||||
leverage this property to declare "top level" data (your logo and company name
|
||||
or some useful xml snippets like page breaks, etc.) to be used anywhere in the
|
||||
document.
|
||||
|
||||
Input template:
|
||||
|
||||
(notice that we are using the "Company" tag inside the "Employees" loop)
|
||||
|
||||

|
||||
|
||||
Input data:
|
||||
|
||||
(notice that the "Company" data is declared outside the "Employees" loop, in it's so
|
||||
called "outer scope")
|
||||
|
||||
```javascript
|
||||
{
|
||||
"Company": "Contoso Ltd.",
|
||||
"Employees": [
|
||||
{ "Surname": "Gates", "Given name": "William" },
|
||||
{ "Surname": "Nadella", "Given name": "Satya" },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output document:
|
||||
|
||||

|
||||
|
||||
## Extensions
|
||||
|
||||
While most document manipulation can be achieved using plugins, there are some cases where a more powerful tool is required. In order to extend the document manipulation process you can specify extensions that will be run before and/or after the standard template processing.
|
||||
|
||||
To write an extension inherit from the [TemplateExtension](./src/extensions/templateExtension.ts) class.
|
||||
|
||||
By default no extension is loaded. Extensions and the order they run in are specified via the `TemplateHandlerOptions`.
|
||||
|
||||
```typescript
|
||||
const handler = new TemplateHandler({
|
||||
extensions: {
|
||||
afterCompilation: [
|
||||
new DataBindingExtension()
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Community Extensions
|
||||
|
||||
The following extensions were developed by the community.
|
||||
Want to see your extension here? Submit a pull request or [open an issue](https://github.com/alonrbar/easy-template-x/issues).
|
||||
|
||||
#### Data Binding Extension
|
||||
|
||||
The [easy-template-x-data-binding](https://github.com/sebastianrogers/easy-template-x-data-binding) extension supports updating [custom XML files](https://docs.microsoft.com/en-gb/archive/blogs/modonovan/word-2007-content-controls-and-xml-part-1-the-basics) inside Word documents.
|
||||
|
||||
This allows using `easy-template-x` to automatically fill [Word forms](https://support.office.com/en-us/article/create-forms-that-users-complete-or-print-in-word-040c5cc1-e309-445b-94ac-542f732c8c8b) that uses content controls.
|
||||
|
||||
## Template handler options
|
||||
|
||||
You can configure the template handler behavior by passing an options object to it's constructor.
|
||||
|
||||
Below is the list of options along with their types and default values:
|
||||
|
||||
```typescript
|
||||
const handler = new TemplateHandler({
|
||||
|
||||
plugins: createDefaultPlugins(), // TemplatePlugin[]
|
||||
|
||||
defaultContentType: TEXT_CONTENT_TYPE, // string
|
||||
|
||||
containerContentType: LOOP_CONTENT_TYPE, // string
|
||||
|
||||
delimiters: { // Partial<Delimiters>
|
||||
tagStart: "{",
|
||||
tagEnd: "}",
|
||||
containerTagOpen: "#",
|
||||
containerTagClose: "/",
|
||||
tagOptionsStart: "[",
|
||||
tagOptionsEnd: "]"
|
||||
},
|
||||
|
||||
maxXmlDepth: 20,
|
||||
|
||||
extensions: { // ExtensionOptions
|
||||
beforeCompilation: undefined, // TemplateExtension[]
|
||||
afterCompilation: undefined // TemplateExtension[]
|
||||
},
|
||||
|
||||
scopeDataResolver: undefined // ScopeDataResolver
|
||||
})
|
||||
```
|
||||
|
||||
### Custom tag delimiters
|
||||
|
||||
To use custom tag delimiters and container marks (used for loops and conditions) specify the `delimiters` option of the template handler.
|
||||
|
||||
For instance, to change from `{#open loop}` and `{/close loop}` to `{{>>open loop}}` and `{{<<close loop}}` do the following:
|
||||
|
||||
```typescript
|
||||
const handler = new TemplateHandler({
|
||||
delimiters: {
|
||||
tagStart: "{{",
|
||||
tagEnd: "}}",
|
||||
containerTagOpen: ">>",
|
||||
containerTagClose: "<<"
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced syntax and custom resolvers
|
||||
|
||||
Custom [scope data resolvers](https://github.com/alonrbar/easy-template-x/blob/master/src/compilation/scopeData.ts#L18) gives the developer a way to hook into `easy-template-x` in order to change how it interprets the tag syntax.
|
||||
|
||||
For instance, to use [Angular](https://angular.io/)-like expressions you can import [easy-template-x-angular-expressions](https://github.com/alonrbar/easy-template-x-angular-expressions) by doing the following:
|
||||
|
||||
```typescript
|
||||
import { createResolver } from "easy-template-x-angular-expressions"
|
||||
|
||||
const handler = new TemplateHandler({
|
||||
scopeDataResolver: createResolver()
|
||||
})
|
||||
```
|
||||
|
||||
This allows the use of advanced syntax expressions such as:
|
||||
|
||||

|
||||
|
||||
## Supported Binary Formats
|
||||
|
||||
The library supports the following binary formats:
|
||||
|
||||
- [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) (browser)
|
||||
- [Buffer](https://nodejs.org/api/buffer.html) (node)
|
||||
- [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) (browser and node)
|
||||
|
||||
## Note - Internal API
|
||||
|
||||
In addition to what's described here in the readme file the library exports many
|
||||
more types and functions. While you are free to use them as you see fit please note
|
||||
that anything not documented in the readme file is considered an internal
|
||||
implementation detail and may break between minor versions, use at your own
|
||||
risk.
|
||||
|
||||
## Philosophy
|
||||
|
||||
This library was originally developed as part of an app for non-English speaking k-12 teachers. As such it assumes the template editors do not necessarily have technical background and certainly no programming experience.
|
||||
|
||||
In order to stay friendly for such potential users it keeps the template syntax as simple as possible, limiting the required knowledge to `{tags}` and `{#loop tags}{/loop tags}` alone (and can be customized to support an alternative, potentially simpler syntax, such as `{>>loop tags}{<<loop tags}`).
|
||||
|
||||
For the same reason it supports tags with whitespace and any unicode supported alphabet such as `{שם התלמיד}` or `{اسم المعلم}`.
|
||||
|
||||
If your template does not need to meet such requirements, especially if they are meant to be edited by developers you can adopt a more sophisticated [alternative syntax](#advanced-syntax-and-custom-resolvers).
|
||||
|
||||
## Changelog
|
||||
|
||||
The change log can be found [here](https://github.com/alonrbar/easy-template-x/blob/master/CHANGELOG.md).
|
||||
+4902
File diff suppressed because it is too large.
Load diff
+4834
File diff suppressed because it is too large.
Load diff
+8
@@ -0,0 +1,8 @@
|
||||
import { OpenXmlPart } from "src/office";
|
||||
import { XmlTextNode } from "src/xml";
|
||||
export interface DelimiterMark {
|
||||
xmlTextNode: XmlTextNode;
|
||||
openXmlPart?: OpenXmlPart;
|
||||
index: number;
|
||||
isOpen: boolean;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { XmlNode } from "src/xml";
|
||||
import { DelimiterMark } from "./delimiterMark";
|
||||
export declare class DelimiterSearcher {
|
||||
maxXmlDepth: number;
|
||||
startDelimiter: string;
|
||||
endDelimiter: string;
|
||||
findDelimiters(node: XmlNode): DelimiterMark[];
|
||||
private noMatch;
|
||||
private fullMatch;
|
||||
private shouldSearchNode;
|
||||
private createDelimiterMark;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export * from './delimiterMark';
|
||||
export * from './delimiterSearcher';
|
||||
export * from './scopeData';
|
||||
export * from './tag';
|
||||
export * from './tagParser';
|
||||
export * from './templateCompiler';
|
||||
export * from './templateContext';
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { TemplateContent, TemplateData } from '../templateData';
|
||||
import { Tag } from './tag';
|
||||
export type PathPart = Tag | number;
|
||||
export interface ScopeDataArgs {
|
||||
path: PathPart[];
|
||||
strPath: string[];
|
||||
data: TemplateData;
|
||||
}
|
||||
export type ScopeDataResolver = (args: ScopeDataArgs) => TemplateContent | TemplateData[];
|
||||
export declare class ScopeData {
|
||||
static defaultResolver(args: ScopeDataArgs): TemplateContent | TemplateData[];
|
||||
scopeDataResolver: ScopeDataResolver;
|
||||
allData: TemplateData;
|
||||
private readonly path;
|
||||
private readonly strPath;
|
||||
constructor(data: TemplateData);
|
||||
pathPush(pathPart: PathPart): void;
|
||||
pathPop(): Tag | number;
|
||||
pathString(): string;
|
||||
getScopeData<T extends TemplateContent | TemplateData[]>(): T;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { IMap } from "src/types";
|
||||
import { XmlTextNode } from "src/xml";
|
||||
export declare const TagDisposition: Readonly<{
|
||||
readonly Open: "Open";
|
||||
readonly Close: "Close";
|
||||
readonly SelfClosed: "SelfClosed";
|
||||
}>;
|
||||
export type TagDisposition = typeof TagDisposition[keyof typeof TagDisposition];
|
||||
export interface Tag {
|
||||
name: string;
|
||||
options?: IMap<any>;
|
||||
rawText: string;
|
||||
disposition: TagDisposition;
|
||||
xmlTextNode: XmlTextNode;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Delimiters } from "src/delimiters";
|
||||
import { DelimiterMark } from "./delimiterMark";
|
||||
import { Tag } from "./tag";
|
||||
export declare class TagParser {
|
||||
private readonly tagRegex;
|
||||
private readonly delimiters;
|
||||
constructor(delimiters: Delimiters);
|
||||
parse(delimiters: DelimiterMark[]): Tag[];
|
||||
private normalizeTagNodes;
|
||||
private processTag;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { TemplatePlugin } from '../plugins';
|
||||
import { XmlNode } from '../xml';
|
||||
import { DelimiterSearcher } from './delimiterSearcher';
|
||||
import { ScopeData } from './scopeData';
|
||||
import { Tag } from './tag';
|
||||
import { TagParser } from './tagParser';
|
||||
import { TemplateContext } from './templateContext';
|
||||
export interface TemplateCompilerOptions {
|
||||
defaultContentType: string;
|
||||
containerContentType: string;
|
||||
skipEmptyTags?: boolean;
|
||||
}
|
||||
export declare class TemplateCompiler {
|
||||
private readonly pluginsLookup;
|
||||
private readonly delimiterSearcher;
|
||||
private readonly tagParser;
|
||||
private readonly options;
|
||||
constructor(delimiterSearcher: DelimiterSearcher, tagParser: TagParser, plugins: TemplatePlugin[], options: TemplateCompilerOptions);
|
||||
compile(node: XmlNode, data: ScopeData, context: TemplateContext): Promise<void>;
|
||||
parseTags(node: XmlNode): Tag[];
|
||||
private doTagReplacements;
|
||||
private detectContentType;
|
||||
private simpleTagReplacements;
|
||||
private findCloseTagIndex;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { Docx, OpenXmlPart } from '../office';
|
||||
export interface TemplateContext {
|
||||
docx: Docx;
|
||||
currentPart: OpenXmlPart;
|
||||
pluginContext: Record<string, any>;
|
||||
options: TemplateOptions;
|
||||
}
|
||||
export interface TemplateOptions {
|
||||
maxXmlDepth: number;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
export declare class Delimiters {
|
||||
tagStart: string;
|
||||
tagEnd: string;
|
||||
containerTagOpen: string;
|
||||
containerTagClose: string;
|
||||
tagOptionsStart: string;
|
||||
tagOptionsEnd: string;
|
||||
constructor(initial?: Partial<Delimiters>);
|
||||
private encodeAndValidate;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export * from './internalArgumentMissingError';
|
||||
export * from './internalError';
|
||||
export * from './malformedFileError';
|
||||
export * from './maxXmlDepthError';
|
||||
export * from './missingCloseDelimiterError';
|
||||
export * from './missingStartDelimiterError';
|
||||
export * from './tagOptionsParseError';
|
||||
export * from './templateDataError';
|
||||
export * from './templateSyntaxError';
|
||||
export * from './unclosedTagError';
|
||||
export * from './unidentifiedFileTypeError';
|
||||
export * from './unknownContentTypeError';
|
||||
export * from './unopenedTagError';
|
||||
export * from './unsupportedFileTypeError';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { InternalError } from "./internalError";
|
||||
export declare class InternalArgumentMissingError extends InternalError {
|
||||
readonly argName: string;
|
||||
constructor(argName: string);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class InternalError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export declare class MalformedFileError extends Error {
|
||||
readonly expectedFileType: string;
|
||||
constructor(expectedFileType: string);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export declare class MaxXmlDepthError extends Error {
|
||||
readonly maxDepth: number;
|
||||
constructor(maxDepth: number);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TemplateSyntaxError } from "./templateSyntaxError";
|
||||
export declare class MissingCloseDelimiterError extends TemplateSyntaxError {
|
||||
readonly openDelimiterText: string;
|
||||
constructor(openDelimiterText: string);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TemplateSyntaxError } from "./templateSyntaxError";
|
||||
export declare class MissingStartDelimiterError extends TemplateSyntaxError {
|
||||
readonly closeDelimiterText: string;
|
||||
constructor(closeDelimiterText: string);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { TemplateSyntaxError } from "./templateSyntaxError";
|
||||
export declare class TagOptionsParseError extends TemplateSyntaxError {
|
||||
readonly tagRawText: string;
|
||||
readonly parseError: Error;
|
||||
constructor(tagRawText: string, parseError: Error);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class TemplateDataError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class TemplateSyntaxError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TemplateSyntaxError } from "./templateSyntaxError";
|
||||
export declare class UnclosedTagError extends TemplateSyntaxError {
|
||||
readonly tagName: string;
|
||||
constructor(tagName: string);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class UnidentifiedFileTypeError extends Error {
|
||||
constructor();
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { TemplateDataError } from "./templateDataError";
|
||||
export declare class UnknownContentTypeError extends TemplateDataError {
|
||||
readonly tagRawText: string;
|
||||
readonly contentType: string;
|
||||
readonly path: string;
|
||||
constructor(contentType: string, tagRawText: string, path: string);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TemplateSyntaxError } from "./templateSyntaxError";
|
||||
export declare class UnopenedTagError extends TemplateSyntaxError {
|
||||
readonly tagName: string;
|
||||
constructor(tagName: string);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export declare class UnsupportedFileTypeError extends Error {
|
||||
readonly fileType: string;
|
||||
constructor(fileType: string);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TemplateExtension } from "./templateExtension";
|
||||
export interface ExtensionOptions {
|
||||
beforeCompilation?: TemplateExtension[];
|
||||
afterCompilation?: TemplateExtension[];
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './extensionOptions';
|
||||
export * from './templateExtension';
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { ScopeData, TagParser, TemplateCompiler, TemplateContext } from 'src/compilation';
|
||||
export interface ExtensionUtilities {
|
||||
compiler: TemplateCompiler;
|
||||
tagParser: TagParser;
|
||||
}
|
||||
export declare abstract class TemplateExtension {
|
||||
protected utilities: ExtensionUtilities;
|
||||
setUtilities(utilities: ExtensionUtilities): void;
|
||||
abstract execute(data: ScopeData, context: TemplateContext): void | Promise<void>;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export * from './compilation';
|
||||
export * from './extensions';
|
||||
export * from './errors';
|
||||
export * from './office';
|
||||
export * from './plugins';
|
||||
export * from './utils';
|
||||
export * from './xml';
|
||||
export * from './zip';
|
||||
export * from './delimiters';
|
||||
export * from './mimeType';
|
||||
export * from './templateData';
|
||||
export * from './templateHandler';
|
||||
export * from './templateHandlerOptions';
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
export declare const MimeType: Readonly<{
|
||||
readonly Png: "image/png";
|
||||
readonly Jpeg: "image/jpeg";
|
||||
readonly Gif: "image/gif";
|
||||
readonly Bmp: "image/bmp";
|
||||
readonly Svg: "image/svg+xml";
|
||||
}>;
|
||||
export type MimeType = typeof MimeType[keyof typeof MimeType];
|
||||
export declare class MimeTypeHelper {
|
||||
static getDefaultExtension(mime: MimeType): string;
|
||||
static getOfficeRelType(mime: MimeType): string;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { MimeType } from "src/mimeType";
|
||||
import { Zip } from "src/zip";
|
||||
export declare class ContentTypesFile {
|
||||
private static readonly contentTypesFilePath;
|
||||
private addedNew;
|
||||
private root;
|
||||
private contentTypes;
|
||||
private readonly zip;
|
||||
constructor(zip: Zip);
|
||||
ensureContentType(mime: MimeType): Promise<void>;
|
||||
count(): Promise<number>;
|
||||
save(): Promise<void>;
|
||||
private parseContentTypesFile;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Constructor } from "src/types";
|
||||
import { Binary } from "src/utils";
|
||||
import { Zip } from "src/zip";
|
||||
import { ContentTypesFile } from "./contentTypesFile";
|
||||
import { MediaFiles } from "./mediaFiles";
|
||||
import { OpenXmlPart } from "./openXmlPart";
|
||||
export declare class Docx {
|
||||
static load(file: Binary): Promise<Docx>;
|
||||
static open(zip: Zip): Promise<Docx>;
|
||||
private static getMainDocumentPath;
|
||||
readonly mainDocument: OpenXmlPart;
|
||||
readonly mediaFiles: MediaFiles;
|
||||
readonly contentTypes: ContentTypesFile;
|
||||
private readonly zip;
|
||||
get rawZipFile(): Zip;
|
||||
private constructor();
|
||||
getContentParts(): Promise<OpenXmlPart[]>;
|
||||
export<T extends Binary>(outputType?: Constructor<T>): Promise<T>;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export * from './docx';
|
||||
export * from './omlNode';
|
||||
export * from './officeMarkup';
|
||||
export * from './openXmlPart';
|
||||
export * from './relationship';
|
||||
export * from './xlsx';
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { MimeType } from '../mimeType';
|
||||
import { Binary } from '../utils';
|
||||
import { Zip } from '../zip';
|
||||
export declare class MediaFiles {
|
||||
private static readonly mediaDir;
|
||||
private hashes;
|
||||
private readonly files;
|
||||
private nextFileId;
|
||||
private readonly zip;
|
||||
constructor(zip: Zip);
|
||||
add(mediaFile: Binary, mime: MimeType): Promise<string>;
|
||||
count(): Promise<number>;
|
||||
private hashMediaFiles;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { XmlGeneralNode, XmlNode, XmlTextNode } from "src/xml";
|
||||
export declare class OfficeMarkup {
|
||||
readonly query: Query;
|
||||
readonly modify: Modify;
|
||||
}
|
||||
declare class Query {
|
||||
isTextNode(node: XmlNode): boolean;
|
||||
isRunNode(node: XmlNode): boolean;
|
||||
isRunPropertiesNode(node: XmlNode): boolean;
|
||||
isTableCellNode(node: XmlNode): boolean;
|
||||
isParagraphNode(node: XmlNode): boolean;
|
||||
isParagraphPropertiesNode(node: XmlNode): boolean;
|
||||
isListParagraph(paragraphNode: XmlNode): boolean;
|
||||
findParagraphPropertiesNode(paragraphNode: XmlNode): XmlNode;
|
||||
firstTextNodeChild(node: XmlNode): XmlNode;
|
||||
containingTextNode(node: XmlTextNode): XmlGeneralNode;
|
||||
containingRunNode(node: XmlNode): XmlNode;
|
||||
containingParagraphNode(node: XmlNode): XmlNode;
|
||||
containingTableRowNode(node: XmlNode): XmlNode;
|
||||
containingTableCellNode(node: XmlNode): XmlNode;
|
||||
containingTableNode(node: XmlNode): XmlNode;
|
||||
isEmptyTextNode(node: XmlNode): boolean;
|
||||
isEmptyRun(node: XmlNode): boolean;
|
||||
}
|
||||
declare class Modify {
|
||||
splitTextNode(textNode: XmlTextNode, splitIndex: number, addBefore: boolean): XmlTextNode;
|
||||
splitParagraphByTextNode(paragraph: XmlNode, textNode: XmlTextNode, removeTextNode: boolean): [XmlNode, XmlNode];
|
||||
joinTextNodesRange(from: XmlTextNode, to: XmlTextNode): void;
|
||||
joinParagraphs(first: XmlNode, second: XmlNode): void;
|
||||
setSpacePreserveAttribute(node: XmlGeneralNode): void;
|
||||
removeTag(textNode: XmlTextNode): void;
|
||||
}
|
||||
export declare const officeMarkup: OfficeMarkup;
|
||||
export {};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
declare class W {
|
||||
readonly Paragraph = "w:p";
|
||||
readonly ParagraphProperties = "w:pPr";
|
||||
readonly Run = "w:r";
|
||||
readonly RunProperties = "w:rPr";
|
||||
readonly Text = "w:t";
|
||||
readonly Table = "w:tbl";
|
||||
readonly TableRow = "w:tr";
|
||||
readonly TableCell = "w:tc";
|
||||
readonly NumberProperties = "w:numPr";
|
||||
}
|
||||
declare class A {
|
||||
readonly Paragraph = "a:p";
|
||||
readonly ParagraphProperties = "a:pPr";
|
||||
readonly Run = "a:r";
|
||||
readonly RunProperties = "a:rPr";
|
||||
readonly Text = "a:t";
|
||||
}
|
||||
export declare class OmlNode {
|
||||
static readonly W: W;
|
||||
static readonly A: A;
|
||||
}
|
||||
export declare class OmlAttribute {
|
||||
static readonly SpacePreserve = "xml:space";
|
||||
}
|
||||
export {};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { Constructor } from "src/types";
|
||||
import { Binary } from "src/utils";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { Zip } from "src/zip";
|
||||
import { RelsFile } from "./relsFile";
|
||||
export declare class OpenXmlPart {
|
||||
readonly rels: RelsFile;
|
||||
readonly path: string;
|
||||
private root;
|
||||
private readonly openedParts;
|
||||
private readonly zip;
|
||||
constructor(path: string, zip: Zip);
|
||||
xmlRoot(): Promise<XmlNode>;
|
||||
getText(): Promise<string>;
|
||||
getContentBinary<T extends Binary>(outputType?: Constructor<T>): Promise<T>;
|
||||
getPartById(relId: string): Promise<OpenXmlPart>;
|
||||
getFirstPartByType(type: string): Promise<OpenXmlPart>;
|
||||
getPartsByType(type: string): Promise<OpenXmlPart[]>;
|
||||
save(binaryContent?: Binary): Promise<void>;
|
||||
private openPart;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { XmlGeneralNode } from "src/xml";
|
||||
export declare const RelType: Readonly<{
|
||||
readonly Package: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
|
||||
readonly MainDocument: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
||||
readonly Header: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
||||
readonly Footer: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
|
||||
readonly Styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
||||
readonly SharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
||||
readonly Link: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
||||
readonly Image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
|
||||
readonly Chart: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
|
||||
readonly ChartColors: "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
|
||||
readonly Worksheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
||||
readonly Table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
|
||||
}>;
|
||||
export type RelTargetMode = 'Internal' | 'External';
|
||||
export declare class Relationship {
|
||||
static fromXml(xml: XmlGeneralNode): Relationship;
|
||||
static normalizeRelTarget(target: string): string;
|
||||
id: string;
|
||||
type: string;
|
||||
target: string;
|
||||
targetMode: RelTargetMode;
|
||||
constructor(initial?: Partial<Relationship>);
|
||||
toXml(): XmlGeneralNode;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Zip } from "src/zip";
|
||||
import { Relationship, RelTargetMode } from "./relationship";
|
||||
export declare class RelsFile {
|
||||
private rels;
|
||||
private relTargets;
|
||||
private nextRelId;
|
||||
private readonly partDir;
|
||||
private readonly relsFilePath;
|
||||
private readonly zip;
|
||||
constructor(partPath: string, zip: Zip);
|
||||
add(relTarget: string, relType: string, relTargetMode?: RelTargetMode): Promise<string>;
|
||||
list(): Promise<Relationship[]>;
|
||||
absoluteTargetPath(relTarget: string): string;
|
||||
save(): Promise<void>;
|
||||
private getNextRelId;
|
||||
private parseRelsFile;
|
||||
private getRelTargetKey;
|
||||
private createRootNode;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Constructor } from "src/types";
|
||||
import { Binary } from "src/utils";
|
||||
import { Zip } from "src/zip";
|
||||
import { OpenXmlPart } from "./openXmlPart";
|
||||
export declare class Xlsx {
|
||||
static load(file: Binary): Promise<Xlsx>;
|
||||
static open(zip: Zip): Promise<Xlsx>;
|
||||
private static getMainDocumentPath;
|
||||
readonly mainDocument: OpenXmlPart;
|
||||
private readonly _parts;
|
||||
private readonly zip;
|
||||
get rawZipFile(): Zip;
|
||||
private constructor();
|
||||
export<T extends Binary>(outputType?: Constructor<T>): Promise<T>;
|
||||
private saveXmlChanges;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { OpenXmlPart } from "src/office/openXmlPart";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { ChartType } from "./chartData";
|
||||
export interface SeriesAccentColor {
|
||||
name: string;
|
||||
lumMod: string;
|
||||
lumOff: string;
|
||||
}
|
||||
export declare class ChartColors {
|
||||
static load(chartPart: OpenXmlPart): Promise<ChartColors>;
|
||||
private accents;
|
||||
private variations;
|
||||
private initialized;
|
||||
private readonly chartPart;
|
||||
private constructor();
|
||||
setSeriesColor(chartType: ChartType, seriesNode: XmlNode, isNewSeries: boolean, color: string | number): void;
|
||||
private recurseSetColor;
|
||||
private setAccentColor;
|
||||
private getAccentColorConfig;
|
||||
private init;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { PluginContent } from "src/plugins/pluginContent";
|
||||
import { ChartData } from "./chartData";
|
||||
export type ChartContent = ChartPluginContent & ChartData;
|
||||
interface ChartPluginContent extends PluginContent {
|
||||
_type: 'chart';
|
||||
title?: string;
|
||||
}
|
||||
export {};
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
export type ChartData = StandardChartData | ScatterChartData | BubbleChartData;
|
||||
export interface StandardChartData {
|
||||
categories: Categories;
|
||||
series: Series[];
|
||||
}
|
||||
export interface ScatterChartData {
|
||||
series: ScatterSeries[];
|
||||
}
|
||||
export interface BubbleChartData {
|
||||
series: BubbleSeries[];
|
||||
}
|
||||
export type Categories = NumericCategories | StringCategories | DateCategories;
|
||||
export interface NumericCategories {
|
||||
formatCode?: NumberingFormat;
|
||||
names: number[];
|
||||
}
|
||||
export interface StringCategories {
|
||||
names: string[];
|
||||
}
|
||||
export interface DateCategories {
|
||||
formatCode?: DateTimeFormat;
|
||||
names: Date[];
|
||||
}
|
||||
export interface Series {
|
||||
name?: string;
|
||||
color?: string;
|
||||
values: number[];
|
||||
}
|
||||
export interface ScatterSeries {
|
||||
name?: string;
|
||||
color?: string;
|
||||
values: ScatterValue[];
|
||||
}
|
||||
export interface ScatterValue {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export interface BubbleSeries {
|
||||
name?: string;
|
||||
color?: string;
|
||||
values: BubbleValue[];
|
||||
}
|
||||
export interface BubbleValue extends ScatterValue {
|
||||
size: number;
|
||||
}
|
||||
export declare const chartTypes: Readonly<{
|
||||
readonly area3DChart: "c:area3DChart";
|
||||
readonly areaChart: "c:areaChart";
|
||||
readonly bar3DChart: "c:bar3DChart";
|
||||
readonly barChart: "c:barChart";
|
||||
readonly line3DChart: "c:line3DChart";
|
||||
readonly lineChart: "c:lineChart";
|
||||
readonly doughnutChart: "c:doughnutChart";
|
||||
readonly ofPieChart: "c:ofPieChart";
|
||||
readonly pie3DChart: "c:pie3DChart";
|
||||
readonly pieChart: "c:pieChart";
|
||||
readonly scatterChart: "c:scatterChart";
|
||||
readonly bubbleChart: "c:bubbleChart";
|
||||
}>;
|
||||
export type ChartType = typeof chartTypes[keyof typeof chartTypes];
|
||||
export declare const NumberingFormatValues: Readonly<{
|
||||
readonly General: "General";
|
||||
readonly Integer: "0";
|
||||
readonly Decimal_2Places: "0.00";
|
||||
readonly ThousandsSeparator: "#,##0";
|
||||
readonly ThousandsSeparator_2DecimalPlaces: "#,##0.00";
|
||||
readonly ThousandsSeparator_ParenthesesForNegatives: "#,##0 ;(#,##0)";
|
||||
readonly ThousandsSeparator_RedNegatives: "#,##0 ;[Red](#,##0)";
|
||||
readonly ThousandsSeparator_2DecimalPlaces_ParenthesesForNegatives: "#,##0.00;(#,##0.00)";
|
||||
readonly ThousandsSeparator_2DecimalPlaces_RedNegatives: "#,##0.00;[Red](#,##0.00)";
|
||||
readonly Percentage: "0%";
|
||||
readonly Percentage_2DecimalPlaces: "0.00%";
|
||||
readonly Scientific: "0.00E+00";
|
||||
readonly Scientific_1DecimalPlace: "##0.0E+0";
|
||||
readonly Fraction_SingleDigit: "# ?/?";
|
||||
readonly Fraction_DoubleDigit: "# ??/?";
|
||||
readonly Text: "@";
|
||||
}>;
|
||||
export type NumberingFormat = typeof NumberingFormatValues[keyof typeof NumberingFormatValues];
|
||||
export declare const DateTimeFormatValues: Readonly<{
|
||||
readonly General: "General";
|
||||
readonly Date_Short: "mm-dd-yy";
|
||||
readonly Date_DayMonthYear: "d-mmm-yy";
|
||||
readonly Date_DayMonth: "d-mmm";
|
||||
readonly Date_MonthYear: "mmm-yy";
|
||||
readonly Time_12Hour: "h:mm AM/PM";
|
||||
readonly Time_12Hour_WithSeconds: "h:mm:ss AM/PM";
|
||||
readonly Time_24Hour: "h:mm";
|
||||
readonly Time_24Hour_WithSeconds: "h:mm:ss";
|
||||
readonly DateTime_Short: "m/d/yy h:mm";
|
||||
readonly Time_MinutesSeconds: "mm:ss";
|
||||
readonly Time_ElapsedHoursMinutesSeconds: "[h]:mm:ss";
|
||||
readonly Time_CompactMinutesSecondsWithDecimal: "mmss.0";
|
||||
readonly Text: "@";
|
||||
}>;
|
||||
export type DateTimeFormat = typeof DateTimeFormatValues[keyof typeof DateTimeFormatValues];
|
||||
export declare const formatIds: Readonly<{
|
||||
General: 0;
|
||||
"0": 1;
|
||||
"0.00": 2;
|
||||
"#,##0": 3;
|
||||
"#,##0.00": 4;
|
||||
"0%": 9;
|
||||
"0.00%": 10;
|
||||
"0.00E+00": 11;
|
||||
"# ?/?": 12;
|
||||
"# ??/?": 13;
|
||||
"mm-dd-yy": 14;
|
||||
"d-mmm-yy": 15;
|
||||
"d-mmm": 16;
|
||||
"mmm-yy": 17;
|
||||
"h:mm AM/PM": 18;
|
||||
"h:mm:ss AM/PM": 19;
|
||||
"h:mm": 20;
|
||||
"h:mm:ss": 21;
|
||||
"m/d/yy h:mm": 22;
|
||||
"#,##0 ;(#,##0)": 37;
|
||||
"#,##0 ;[Red](#,##0)": 38;
|
||||
"#,##0.00;(#,##0.00)": 39;
|
||||
"#,##0.00;[Red](#,##0.00)": 40;
|
||||
"mm:ss": 45;
|
||||
"[h]:mm:ss": 46;
|
||||
"mmss.0": 47;
|
||||
"##0.0E+0": 48;
|
||||
"@": 49;
|
||||
}>;
|
||||
export type FormatCode = NumberingFormat | DateTimeFormat;
|
||||
export declare function chartFriendlyName(chartType: ChartType): string;
|
||||
export declare function isStandardChartType(chartType: ChartType): boolean;
|
||||
export declare function isScatterChartType(chartType: ChartType): boolean;
|
||||
export declare function isBubbleChartType(chartType: ChartType): boolean;
|
||||
export declare function isStandardChartData(chartData: ChartData): chartData is StandardChartData;
|
||||
export declare function isScatterChartData(chartData: ChartData): chartData is ScatterChartData;
|
||||
export declare function isBubbleChartData(chartData: ChartData): chartData is BubbleChartData;
|
||||
export declare function isStringCategories(categories: Categories): categories is StringCategories;
|
||||
export declare function isDateCategories(categories: Categories): categories is DateCategories;
|
||||
export declare function formatCode(categories: Categories): FormatCode;
|
||||
export declare function scatterXValues(series: ScatterSeries[]): number[];
|
||||
export declare function scatterYValues(xValues: number[], series: ScatterSeries): number[];
|
||||
export declare function bubbleSizeValues(xValues: number[], series: BubbleSeries): number[];
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import { ChartData, ChartType } from "./chartData";
|
||||
export declare function validateChartData(chartType: ChartType, chartData: ChartData): void;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { ScopeData, Tag, TemplateContext } from "src/compilation";
|
||||
import { TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare class ChartPlugin extends TemplatePlugin {
|
||||
readonly contentType = "chart";
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData, context: TemplateContext): Promise<void>;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './chartContent';
|
||||
export * from './chartPlugin';
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { OpenXmlPart } from "src/office";
|
||||
import { ChartData } from "./chartData";
|
||||
export declare function updateChart(chartPart: OpenXmlPart, chartData: ChartData): Promise<void>;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import { TemplatePlugin } from "./templatePlugin";
|
||||
export declare function createDefaultPlugins(): TemplatePlugin[];
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { MimeType } from '../../mimeType';
|
||||
import { Binary } from '../../utils';
|
||||
import { PluginContent } from '../pluginContent';
|
||||
export type ImageFormat = typeof MimeType.Jpeg | typeof MimeType.Png | typeof MimeType.Gif | typeof MimeType.Bmp | typeof MimeType.Svg;
|
||||
export interface ImageContent extends PluginContent {
|
||||
_type: 'image';
|
||||
source: Binary;
|
||||
format: ImageFormat;
|
||||
width: number;
|
||||
height: number;
|
||||
altText?: string;
|
||||
transparencyPercent?: number;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { ScopeData, Tag, TemplateContext } from "src/compilation";
|
||||
import { TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare class ImagePlugin extends TemplatePlugin {
|
||||
readonly contentType = "image";
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData, context: TemplateContext): Promise<void>;
|
||||
private getNextImageId;
|
||||
private createMarkup;
|
||||
private docProperties;
|
||||
private pictureMarkup;
|
||||
private transparencyMarkup;
|
||||
private pixelsToEmu;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './imageContent';
|
||||
export * from './imagePlugin';
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export * from './image';
|
||||
export * from './link';
|
||||
export * from './loop';
|
||||
export * from './rawXml';
|
||||
export * from './text';
|
||||
export * from './defaultPlugins';
|
||||
export * from './pluginContent';
|
||||
export * from './templatePlugin';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './linkContent';
|
||||
export * from './linkPlugin';
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { PluginContent } from '../pluginContent';
|
||||
export interface LinkContent extends PluginContent {
|
||||
_type: 'link';
|
||||
text?: string;
|
||||
target: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { ScopeData, Tag, TemplateContext } from "src/compilation";
|
||||
import { TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare class LinkPlugin extends TemplatePlugin {
|
||||
readonly contentType = "link";
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData, context: TemplateContext): Promise<void>;
|
||||
private generateMarkup;
|
||||
private insertHyperlinkNode;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './loopPlugin';
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { ScopeData, Tag, TemplateContext } from "src/compilation";
|
||||
import { PluginUtilities, TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare const LOOP_CONTENT_TYPE = "loop";
|
||||
export declare class LoopPlugin extends TemplatePlugin {
|
||||
readonly contentType = "loop";
|
||||
private readonly loopStrategies;
|
||||
setUtilities(utilities: PluginUtilities): void;
|
||||
containerTagReplacements(tags: Tag[], data: ScopeData, context: TemplateContext): Promise<void>;
|
||||
private repeat;
|
||||
private compile;
|
||||
private updatePathBefore;
|
||||
private updatePathAfter;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export declare const LoopOver: Readonly<{
|
||||
readonly Row: "row";
|
||||
readonly Column: "column";
|
||||
readonly Content: "content";
|
||||
}>;
|
||||
export type LoopOver = typeof LoopOver[keyof typeof LoopOver];
|
||||
export declare class LoopTagOptions {
|
||||
loopOver: LoopOver;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { Tag } from "src/compilation";
|
||||
import { XmlNode } from "src/xml";
|
||||
export interface ILoopStrategy {
|
||||
isApplicable(openTag: Tag, closeTag: Tag, isCondition: boolean): boolean;
|
||||
splitBefore(openTag: Tag, closeTag: Tag): SplitBeforeResult;
|
||||
mergeBack(compiledNodes: XmlNode[][], firstNode: XmlNode, lastNode: XmlNode): void;
|
||||
}
|
||||
export interface SplitBeforeResult {
|
||||
firstNode: XmlNode;
|
||||
nodesToRepeat: XmlNode[];
|
||||
lastNode: XmlNode;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export * from './iLoopStrategy';
|
||||
export * from './loopListStrategy';
|
||||
export * from './loopParagraphStrategy';
|
||||
export * from './loopTableColumnsStrategy';
|
||||
export * from './loopTableRowsStrategy';
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { Tag } from "src/compilation";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
||||
export declare class LoopListStrategy implements ILoopStrategy {
|
||||
isApplicable(openTag: Tag, closeTag: Tag, isCondition: boolean): boolean;
|
||||
splitBefore(openTag: Tag, closeTag: Tag): SplitBeforeResult;
|
||||
mergeBack(paragraphGroups: XmlNode[][], firstParagraph: XmlNode, lastParagraphs: XmlNode): void;
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { Tag } from "src/compilation";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
||||
export declare class LoopParagraphStrategy implements ILoopStrategy {
|
||||
isApplicable(openTag: Tag, closeTag: Tag, isCondition: boolean): boolean;
|
||||
splitBefore(openTag: Tag, closeTag: Tag): SplitBeforeResult;
|
||||
mergeBack(middleParagraphs: XmlNode[][], firstParagraph: XmlNode, lastParagraph: XmlNode): void;
|
||||
}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import { Tag } from "src/compilation";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
||||
export declare class LoopTableColumnsStrategy implements ILoopStrategy {
|
||||
isApplicable(openTag: Tag, closeTag: Tag, isCondition: boolean): boolean;
|
||||
splitBefore(openTag: Tag, closeTag: Tag): SplitBeforeResult;
|
||||
mergeBack(columnsWrapperGroups: XmlNode[][], firstCell: XmlNode, lastCell: XmlNode): void;
|
||||
private extractColumns;
|
||||
private insertColumnAfterIndex;
|
||||
private removeColumn;
|
||||
private getColumnIndex;
|
||||
private getColumnByIndex;
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { Tag } from "src/compilation";
|
||||
import { XmlNode } from "src/xml";
|
||||
import { ILoopStrategy, SplitBeforeResult } from "./iLoopStrategy";
|
||||
export declare class LoopTableRowsStrategy implements ILoopStrategy {
|
||||
isApplicable(openTag: Tag, closeTag: Tag, isCondition: boolean): boolean;
|
||||
splitBefore(openTag: Tag, closeTag: Tag): SplitBeforeResult;
|
||||
mergeBack(rowGroups: XmlNode[][], firstRow: XmlNode, lastRow: XmlNode): void;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export interface PluginContent {
|
||||
_type: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
export declare const PluginContent: {
|
||||
isPluginContent(content: unknown): content is PluginContent;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './rawXmlContent';
|
||||
export * from './rawXmlPlugin';
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { PluginContent } from '../pluginContent';
|
||||
export interface RawXmlContent extends PluginContent {
|
||||
_type: 'rawXml';
|
||||
xml: string;
|
||||
replaceParagraph?: boolean;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { ScopeData, Tag } from "src/compilation";
|
||||
import { TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare class RawXmlPlugin extends TemplatePlugin {
|
||||
readonly contentType = "rawXml";
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData): void;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { ScopeData, Tag, TemplateCompiler, TemplateContext } from "../compilation";
|
||||
export interface PluginUtilities {
|
||||
compiler: TemplateCompiler;
|
||||
}
|
||||
export declare abstract class TemplatePlugin {
|
||||
abstract readonly contentType: string;
|
||||
protected utilities: PluginUtilities;
|
||||
setUtilities(utilities: PluginUtilities): void;
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData, context: TemplateContext): void | Promise<void>;
|
||||
containerTagReplacements(tags: Tag[], data: ScopeData, context: TemplateContext): void | Promise<void>;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from './textPlugin';
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { ScopeData, Tag } from "src/compilation";
|
||||
import { TemplatePlugin } from "src/plugins/templatePlugin";
|
||||
export declare const TEXT_CONTENT_TYPE = "text";
|
||||
export declare class TextPlugin extends TemplatePlugin {
|
||||
readonly contentType = "text";
|
||||
simpleTagReplacements(tag: Tag, data: ScopeData): void;
|
||||
private replaceSingleLine;
|
||||
private replaceMultiLine;
|
||||
private getLineBreak;
|
||||
private createOfficeTextNode;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { PluginContent } from './plugins';
|
||||
export type PrimitiveTemplateContent = string | number | boolean;
|
||||
export type TemplateContent = PrimitiveTemplateContent | PluginContent;
|
||||
export interface TemplateData {
|
||||
[tagName: string]: TemplateContent | TemplateData | TemplateData[];
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { Tag } from "./compilation";
|
||||
import { OpenXmlPart } from "./office";
|
||||
import { TemplateData } from "./templateData";
|
||||
import { TemplateHandlerOptions } from "./templateHandlerOptions";
|
||||
import { Binary } from "./utils";
|
||||
import { XmlNode } from "./xml";
|
||||
export declare class TemplateHandler {
|
||||
readonly version: string;
|
||||
private readonly compiler;
|
||||
private readonly options;
|
||||
constructor(options?: TemplateHandlerOptions);
|
||||
process<T extends Binary>(templateFile: T, data: TemplateData): Promise<T>;
|
||||
parseTags(templateFile: Binary): Promise<Tag[]>;
|
||||
getText(docxFile: Binary, relType?: string): Promise<string>;
|
||||
getXml(docxFile: Binary, relType?: string): Promise<XmlNode>;
|
||||
getParts(docxFile: Binary, relType: string): Promise<OpenXmlPart[]>;
|
||||
private callExtensions;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ScopeDataResolver } from './compilation';
|
||||
import { Delimiters } from './delimiters';
|
||||
import { ExtensionOptions } from './extensions';
|
||||
import { TemplatePlugin } from './plugins';
|
||||
export declare class TemplateHandlerOptions {
|
||||
plugins?: TemplatePlugin[];
|
||||
skipEmptyTags?: boolean;
|
||||
defaultContentType?: string;
|
||||
containerContentType?: string;
|
||||
delimiters?: Partial<Delimiters>;
|
||||
maxXmlDepth?: number;
|
||||
extensions?: ExtensionOptions;
|
||||
scopeDataResolver?: ScopeDataResolver;
|
||||
constructor(initial?: Partial<TemplateHandlerOptions>);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type IMap<T> = Record<string, T>;
|
||||
export interface Constructor<T> {
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { IMap } from '../types';
|
||||
export type ItemMapper<TIn, TOut = string> = (item: TIn, index: number) => TOut;
|
||||
export declare function pushMany<T>(destArray: T[], items: T[]): void;
|
||||
export declare function first<T>(array: T[]): T;
|
||||
export declare function last<T>(array: T[]): T;
|
||||
export declare function toDictionary<TIn, TOut = TIn>(array: TIn[], keySelector: ItemMapper<TIn>, valueSelector?: ItemMapper<TIn, TOut>): IMap<TOut>;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class Base64 {
|
||||
static encode(str: string): string;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Constructor } from '../types';
|
||||
export type Binary = Blob | Buffer | ArrayBuffer;
|
||||
export declare const Binary: {
|
||||
isBlob(binary: unknown): binary is Blob;
|
||||
isArrayBuffer(binary: unknown): binary is ArrayBuffer;
|
||||
isBuffer(binary: unknown): binary is Buffer;
|
||||
isBlobConstructor(binaryType: Constructor<unknown>): binaryType is Constructor<Blob>;
|
||||
isArrayBufferConstructor(binaryType: Constructor<unknown>): binaryType is Constructor<ArrayBuffer>;
|
||||
isBufferConstructor(binaryType: Constructor<unknown>): binaryType is Constructor<Buffer>;
|
||||
toBase64(binary: Binary): Promise<string>;
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export * from './array';
|
||||
export * from './base64';
|
||||
export * from './binary';
|
||||
export * from './number';
|
||||
export * from './path';
|
||||
export * from './regex';
|
||||
export * from './sha1';
|
||||
export * from './txt';
|
||||
export * from './types';
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function isNumber(value: unknown): value is number;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export declare class Path {
|
||||
static getFilename(path: string): string;
|
||||
static getDirectory(path: string): string;
|
||||
static combine(...parts: string[]): string;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare class Regex {
|
||||
static escape(str: string): string;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function sha1(msg: string): string;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export declare function stringValue(val: unknown): string;
|
||||
export declare function normalizeDoubleQuotes(text: string): string;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { Constructor } from '../types';
|
||||
export declare function inheritsFrom(derived: Constructor<any>, base: Constructor<any>): boolean;
|
||||
export declare function isPromiseLike<T>(candidate: unknown): candidate is PromiseLike<T>;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export * from "./xml";
|
||||
export * from "./xmlDepthTracker";
|
||||
export * from "./xmlNode";
|
||||
export * from "./xmlTreeIterator";
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { XmlGeneralNode, XmlNode } from "./xmlNode";
|
||||
import { XmlCommentNode } from "./xmlNode";
|
||||
import { XmlTextNode } from "./xmlNode";
|
||||
import type { IMap } from "src/types";
|
||||
export type XmlNodePredicate = (node: XmlNode) => boolean;
|
||||
export declare class XmlUtils {
|
||||
readonly parser: Parser;
|
||||
readonly create: Create;
|
||||
readonly query: Query;
|
||||
readonly modify: Modify;
|
||||
}
|
||||
declare class Parser {
|
||||
private static xmlFileHeader;
|
||||
private static readonly parser;
|
||||
parse(str: string): XmlNode;
|
||||
domParse(str: string): Document;
|
||||
encodeValue(str: string): string;
|
||||
serializeNode(node: XmlNode): string;
|
||||
serializeFile(xmlNode: XmlNode): string;
|
||||
}
|
||||
interface XmlGeneralNodeInit {
|
||||
attributes?: IMap<string>;
|
||||
childNodes?: XmlNode[];
|
||||
}
|
||||
declare class Create {
|
||||
textNode(text?: string): XmlTextNode;
|
||||
generalNode(name: string, init?: XmlGeneralNodeInit): XmlGeneralNode;
|
||||
commentNode(text?: string): XmlCommentNode;
|
||||
cloneNode<T extends XmlNode>(node: T, deep: boolean): T;
|
||||
fromDomNode(domNode: Node): XmlNode;
|
||||
}
|
||||
declare class Query {
|
||||
isTextNode(node: XmlNode): node is XmlTextNode;
|
||||
isGeneralNode(node: XmlNode): node is XmlGeneralNode;
|
||||
isCommentNode(node: XmlNode): node is XmlCommentNode;
|
||||
lastTextChild(node: XmlNode, createIfMissing?: boolean): XmlTextNode;
|
||||
findParent(node: XmlNode, predicate: (node: XmlNode) => boolean): XmlNode;
|
||||
findParentByName(node: XmlNode, nodeName: string): XmlNode;
|
||||
findChild(node: XmlNode, predicate: (node: XmlNode) => boolean): XmlNode;
|
||||
findChildByName(node: XmlNode, childName: string): XmlNode;
|
||||
siblingsInRange(firstNode: XmlNode, lastNode: XmlNode): XmlNode[];
|
||||
descendants(node: XmlNode, maxDepth: number, predicate: XmlNodePredicate): XmlNode[];
|
||||
}
|
||||
declare class Modify {
|
||||
insertBefore(newNode: XmlNode, referenceNode: XmlNode): void;
|
||||
insertAfter(newNode: XmlNode, referenceNode: XmlNode): void;
|
||||
insertChild(parent: XmlNode, child: XmlNode, childIndex: number): void;
|
||||
appendChild(parent: XmlNode, child: XmlNode): void;
|
||||
remove(node: XmlNode): void;
|
||||
removeChild(parent: XmlNode, child: XmlNode): XmlNode;
|
||||
removeChild(parent: XmlNode, childIndex: number): XmlNode;
|
||||
removeChildren(parent: XmlNode, predicate: (child: XmlNode) => boolean): void;
|
||||
removeSiblings(from: XmlNode, to: XmlNode): XmlNode[];
|
||||
splitByChild(parent: XmlNode, child: XmlNode, removeChild: boolean): [XmlNode, XmlNode];
|
||||
removeEmptyTextNodes(node: XmlNode): void;
|
||||
}
|
||||
export declare const xml: XmlUtils;
|
||||
export {};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export declare class XmlDepthTracker {
|
||||
private depth;
|
||||
private readonly maxDepth;
|
||||
constructor(maxDepth: number);
|
||||
increment(): void;
|
||||
decrement(): void;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { IMap } from "src/types";
|
||||
export declare const XmlNodeType: Readonly<{
|
||||
readonly Text: "Text";
|
||||
readonly General: "General";
|
||||
readonly Comment: "Comment";
|
||||
}>;
|
||||
export type XmlNodeType = typeof XmlNodeType[keyof typeof XmlNodeType];
|
||||
export type XmlNode = XmlTextNode | XmlGeneralNode | XmlCommentNode;
|
||||
export interface XmlNodeBase {
|
||||
nodeType: XmlNodeType;
|
||||
nodeName: string;
|
||||
parentNode?: XmlNode;
|
||||
childNodes?: XmlNode[];
|
||||
nextSibling?: XmlNode;
|
||||
}
|
||||
export declare const TEXT_NODE_NAME = "#text";
|
||||
export declare const COMMENT_NODE_NAME = "#comment";
|
||||
export interface XmlTextNode extends XmlNodeBase {
|
||||
nodeType: typeof XmlNodeType.Text;
|
||||
nodeName: typeof TEXT_NODE_NAME;
|
||||
textContent: string;
|
||||
}
|
||||
export interface XmlCommentNode extends XmlNodeBase {
|
||||
nodeType: typeof XmlNodeType.Comment;
|
||||
nodeName: typeof COMMENT_NODE_NAME;
|
||||
commentContent: string;
|
||||
}
|
||||
export interface XmlGeneralNode extends XmlNodeBase {
|
||||
nodeType: typeof XmlNodeType.General;
|
||||
attributes?: IMap<string>;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { XmlNode } from "./xmlNode";
|
||||
export declare class XmlTreeIterator<T extends XmlNode = XmlNode> {
|
||||
get node(): T;
|
||||
private _current;
|
||||
private readonly depthTracker;
|
||||
constructor(initial: XmlNode, maxDepth: number);
|
||||
next(): XmlNode;
|
||||
setCurrent(node: XmlNode): void;
|
||||
private findNextNode;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export * from './zip';
|
||||
export * from './zipObject';
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import JSZip from 'jszip';
|
||||
import { Constructor } from '../types';
|
||||
import { Binary } from '../utils';
|
||||
export declare class JsZipHelper {
|
||||
static toJsZipOutputType(binary: Binary): JSZip.OutputType;
|
||||
static toJsZipOutputType(binaryType: Constructor<Binary>): JSZip.OutputType;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { Constructor } from '../types';
|
||||
import { Binary } from '../utils';
|
||||
import { ZipObject } from './zipObject';
|
||||
export declare class Zip {
|
||||
static load(file: Binary): Promise<Zip>;
|
||||
private readonly zip;
|
||||
private readonly binaryFormat;
|
||||
private constructor();
|
||||
getFile(path: string): ZipObject;
|
||||
setFile(path: string, content: string | Binary): void;
|
||||
isFileExist(path: string): boolean;
|
||||
listFiles(): string[];
|
||||
export<T extends Binary>(outputType?: Constructor<T>): Promise<T>;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import JSZip from 'jszip';
|
||||
import { Constructor } from '../types';
|
||||
import { Binary } from '../utils';
|
||||
export declare class ZipObject {
|
||||
get name(): string;
|
||||
set name(value: string);
|
||||
get isDirectory(): boolean;
|
||||
private readonly zipObject;
|
||||
private readonly binaryFormat;
|
||||
constructor(zipObject: JSZip.JSZipObject, binaryFormat: Constructor<Binary>);
|
||||
getContentText(): Promise<string>;
|
||||
getContentBase64(): Promise<string>;
|
||||
getContentBinary<T extends Binary>(outputType?: Constructor<T>): Promise<T>;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../json5/lib/cli.js
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "easy-template-x",
|
||||
"version": "6.2.1",
|
||||
"description": "Generate docx documents from templates, in Node or in the browser.",
|
||||
"keywords": [
|
||||
"docx",
|
||||
"template",
|
||||
"word",
|
||||
"office",
|
||||
"microsoft",
|
||||
"ms"
|
||||
],
|
||||
"author": "Alon Bar",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/alonrbar/easy-template-x",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alonrbar/easy-template-x.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/alonrbar/easy-template-x/issues"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/cjs/easy-template-x.cjs",
|
||||
"module": "dist/es/easy-template-x.mjs",
|
||||
"exports": {
|
||||
"require": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"default": "./dist/cjs/easy-template-x.cjs"
|
||||
},
|
||||
"import": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"default": "./dist/es/easy-template-x.mjs"
|
||||
}
|
||||
},
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf .tmp dist test-reports && yarn clean-dist-verify",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint \"./{src,test}/**/!(*.d).ts\"",
|
||||
"test": "jest --verbose",
|
||||
"quality": "yarn typecheck && yarn lint && yarn test",
|
||||
"build-src": "rollup -c",
|
||||
"build-types": "tsc -p tsconfig.types.json --emitDeclarationOnly",
|
||||
"build": "yarn build-types && yarn build-src",
|
||||
"release": "yarn clean && yarn quality && yarn build && yarn dist-verify",
|
||||
"dist-verify": "yarn dist-verify-cjs && yarn dist-verify-es",
|
||||
"dist-verify-cjs": "cd dist-verify/cjs && npm install && node main.js",
|
||||
"dist-verify-es": "cd dist-verify/es && npm install && node main.js",
|
||||
"clean-dist-verify": "yarn clean-dist-verify-cjs && yarn clean-dist-verify-es",
|
||||
"clean-dist-verify-cjs": "rimraf dist-verify/cjs/node_modules dist-verify/cjs/package-lock.json",
|
||||
"clean-dist-verify-es": "rimraf dist-verify/es/node_modules dist-verify/es/package-lock.json"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "0.8.10",
|
||||
"json5": "2.2.3",
|
||||
"jszip": "3.10.1",
|
||||
"lodash.get": "4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.10",
|
||||
"@babel/preset-env": "7.26.9",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@eslint/js": "9.22.0",
|
||||
"@rollup/plugin-alias": "5.1.1",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@types/babel__preset-env": "7.10.0",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/jszip": "3.4.1",
|
||||
"@types/node": "22.13.10",
|
||||
"@types/ts-nameof": "4.2.5",
|
||||
"babel-jest": "29.7.0",
|
||||
"babel-loader": "10.0.0",
|
||||
"babel-plugin-ts-nameof": "4.2.1",
|
||||
"eslint": "9.22.0",
|
||||
"globals": "16.0.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-html-reporters": "3.1.7",
|
||||
"jest-junit": "16.0.0",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "4.35.0",
|
||||
"rollup-plugin-auto-external": "2.0.0",
|
||||
"rollup-plugin-babel": "4.4.0",
|
||||
"rollup-plugin-node-resolve": "5.2.0",
|
||||
"typescript": "5.8.2",
|
||||
"typescript-eslint": "8.26.1"
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Version number of the `easy-template-x` library.
|
||||
*/
|
||||
const EASY_VERSION: string;
|
||||
Loaded 100 of 5445 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user