generating the content for a table of contents
This commit is contained in:
@ -52,6 +52,7 @@
|
|||||||
"fast-xml-parser": "^3.3.6",
|
"fast-xml-parser": "^3.3.6",
|
||||||
"image-size": "^0.6.2",
|
"image-size": "^0.6.2",
|
||||||
"jszip": "^3.1.5",
|
"jszip": "^3.1.5",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
"xml": "^1.0.1"
|
"xml": "^1.0.1"
|
||||||
},
|
},
|
||||||
"author": "Dolan Miu",
|
"author": "Dolan Miu",
|
||||||
|
@ -33,6 +33,8 @@ export class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async compile(file: File): Promise<JSZip> {
|
public async compile(file: File): Promise<JSZip> {
|
||||||
|
file.generateTablesOfContents();
|
||||||
|
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
const xmlifiedFileMapping = this.xmlifyFile(file);
|
const xmlifiedFileMapping = this.xmlifyFile(file);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||||
import { Paragraph, ParagraphProperties } from "../..";
|
import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
|
||||||
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
|
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
|
||||||
|
|
||||||
export class Body extends XmlComponent {
|
export class Body extends XmlComponent {
|
||||||
@ -53,6 +53,14 @@ export class Body extends XmlComponent {
|
|||||||
return this.defaultSection;
|
return this.defaultSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTablesOfContents(): TableOfContents[] {
|
||||||
|
return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getParagraphs(): Paragraph[] {
|
||||||
|
return this.root.filter((child) => child instanceof Paragraph) as Paragraph[];
|
||||||
|
}
|
||||||
|
|
||||||
private createSectionParagraph(section: SectionProperties): Paragraph {
|
private createSectionParagraph(section: SectionProperties): Paragraph {
|
||||||
const paragraph = new Paragraph();
|
const paragraph = new Paragraph();
|
||||||
const properties = new ParagraphProperties();
|
const properties = new ParagraphProperties();
|
||||||
|
@ -66,4 +66,12 @@ export class Document extends XmlComponent {
|
|||||||
public get Body(): Body {
|
public get Body(): Body {
|
||||||
return this.body;
|
return this.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTablesOfContents(): TableOfContents[] {
|
||||||
|
return this.body.getTablesOfContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getParagraphs(): Paragraph[] {
|
||||||
|
return this.body.getParagraphs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { cloneDeep } from "lodash";
|
||||||
import { AppProperties } from "./app-properties/app-properties";
|
import { AppProperties } from "./app-properties/app-properties";
|
||||||
import { ContentTypes } from "./content-types/content-types";
|
import { ContentTypes } from "./content-types/content-types";
|
||||||
import { CoreProperties, IPropertiesOptions } from "./core-properties";
|
import { CoreProperties, IPropertiesOptions } from "./core-properties";
|
||||||
@ -8,13 +9,15 @@ import { FootNotes } from "./footnotes";
|
|||||||
import { HeaderWrapper } from "./header-wrapper";
|
import { HeaderWrapper } from "./header-wrapper";
|
||||||
import { Image, Media } from "./media";
|
import { Image, Media } from "./media";
|
||||||
import { Numbering } from "./numbering";
|
import { Numbering } from "./numbering";
|
||||||
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
|
import { Bookmark, Hyperlink, Paragraph, Run } from "./paragraph";
|
||||||
|
import { Begin, End, Separate } from "./paragraph/run/field";
|
||||||
|
import { Tab } from "./paragraph/run/tab";
|
||||||
import { Relationships } from "./relationships";
|
import { Relationships } from "./relationships";
|
||||||
import { Styles } from "./styles";
|
import { Styles } from "./styles";
|
||||||
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
||||||
import { DefaultStylesFactory } from "./styles/factory";
|
import { DefaultStylesFactory } from "./styles/factory";
|
||||||
import { Table } from "./table";
|
import { Table } from "./table";
|
||||||
import { TableOfContents } from "./table-of-contents";
|
import { PageReferenceInstruction, TableOfContents } from "./table-of-contents";
|
||||||
|
|
||||||
export class File {
|
export class File {
|
||||||
private readonly document: Document;
|
private readonly document: Document;
|
||||||
@ -32,6 +35,7 @@ export class File {
|
|||||||
private readonly appProperties: AppProperties;
|
private readonly appProperties: AppProperties;
|
||||||
|
|
||||||
private currentRelationshipId: number = 1;
|
private currentRelationshipId: number = 1;
|
||||||
|
private currentTocBookmarkId: number = 1;
|
||||||
|
|
||||||
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
|
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
@ -284,6 +288,65 @@ export class File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public generateTablesOfContents(): void {
|
public generateTablesOfContents(): void {
|
||||||
|
this.document.getTablesOfContents().forEach((child) => this.generateContent(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateContent(toc: TableOfContents): void {
|
||||||
|
// console.log("TOC", JSON.stringify(toc));
|
||||||
|
if (toc.getHeaderRange()) {
|
||||||
|
this.generateContentForHeaderRange(toc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateContentForHeaderRange(toc: TableOfContents): void {
|
||||||
|
const headerRange = toc.getHeaderRange();
|
||||||
|
const hyphenIndex = headerRange.indexOf("-");
|
||||||
|
if (hyphenIndex !== -1) {
|
||||||
|
const rangeBegin = parseInt(headerRange.substring(0, hyphenIndex), 2);
|
||||||
|
const rangeEnd = parseInt(headerRange.substring(hyphenIndex + 1), 2);
|
||||||
|
const styles = new Array<string>();
|
||||||
|
for (let i = rangeBegin; i <= rangeEnd; i++) {
|
||||||
|
styles.push(`Heading${i}`);
|
||||||
|
}
|
||||||
|
// console.log("Find Headers for range ", rangeBegin, " - ", rangeEnd, styles.join(","));
|
||||||
|
this.document
|
||||||
|
.getParagraphs()
|
||||||
|
.filter((paragraph) => this.paragraphContainAnyStyle(paragraph, styles))
|
||||||
|
.forEach((paragraph) => this.generateContentForParagraph(paragraph, toc));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid headerRange: '${headerRange}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private paragraphContainAnyStyle(paragraph: Paragraph, styles: string[]): boolean {
|
||||||
|
return paragraph.getStyles().some((style) => styles.indexOf(style.styleId) !== -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateContentForParagraph(paragraph: Paragraph, toc: TableOfContents): void {
|
||||||
|
const bookmarkId = `_TOC_${this.currentTocBookmarkId}`;
|
||||||
|
// console.log("Generating content for paragraph: ", bookmarkId);
|
||||||
|
|
||||||
|
const generatedParagraph = cloneDeep(paragraph);
|
||||||
|
generatedParagraph.rightTabStop(9016, "dot");
|
||||||
|
|
||||||
|
const tabRun = new Run();
|
||||||
|
tabRun.addChildElement(new Tab());
|
||||||
|
generatedParagraph.addChildElement(tabRun);
|
||||||
|
|
||||||
|
const beginRun = new Run();
|
||||||
|
beginRun.addChildElement(new Begin());
|
||||||
|
beginRun.addChildElement(new PageReferenceInstruction(bookmarkId));
|
||||||
|
beginRun.addChildElement(new Separate());
|
||||||
|
generatedParagraph.addRun(beginRun);
|
||||||
|
|
||||||
|
const endRun = new Run();
|
||||||
|
endRun.addChildElement(new End());
|
||||||
|
generatedParagraph.addRun(endRun);
|
||||||
|
|
||||||
|
toc.addGeneratedContent(generatedParagraph);
|
||||||
|
|
||||||
|
paragraph.addBookmark(this.createBookmark(bookmarkId, ""));
|
||||||
|
// console.log("Paragraph after content generation", JSON.stringify(paragraph, null, 2));
|
||||||
|
this.currentTocBookmarkId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { Attributes, XmlComponent } from "file/xml-components";
|
import { Attributes, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
export class Style extends XmlComponent {
|
export class Style extends XmlComponent {
|
||||||
constructor(type: string) {
|
public readonly styleId: string;
|
||||||
|
|
||||||
|
constructor(styleId: string) {
|
||||||
super("w:pStyle");
|
super("w:pStyle");
|
||||||
|
this.styleId = styleId;
|
||||||
this.root.push(
|
this.root.push(
|
||||||
new Attributes({
|
new Attributes({
|
||||||
val: type,
|
val: styleId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,27 @@
|
|||||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
export class TabStop extends XmlComponent {
|
export class TabStop extends XmlComponent {
|
||||||
constructor(tab: Tab) {
|
constructor(tab: TabStopItem) {
|
||||||
super("w:tabs");
|
super("w:tabs");
|
||||||
this.root.push(tab);
|
this.root.push(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start";
|
export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start";
|
||||||
|
export type LeaderType = "dot" | "hyphen" | "middleDot" | "none" | "underscore";
|
||||||
|
|
||||||
export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number }> {
|
export class TabAttributes extends XmlAttributeComponent<{ val: TabValue; pos: string | number; leader: LeaderType }> {
|
||||||
protected xmlKeys = { val: "w:val", pos: "w:pos" };
|
protected xmlKeys = { val: "w:val", pos: "w:pos", leader: "w:leader" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tab extends XmlComponent {
|
export class TabStopItem extends XmlComponent {
|
||||||
constructor(value: TabValue, position: string | number) {
|
constructor(value: TabValue, position: string | number, leader?: LeaderType) {
|
||||||
super("w:tab");
|
super("w:tab");
|
||||||
this.root.push(
|
this.root.push(
|
||||||
new TabAttributes({
|
new TabAttributes({
|
||||||
val: value,
|
val: value,
|
||||||
pos: position,
|
pos: position,
|
||||||
|
leader: leader || "none",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -28,24 +30,24 @@ export class Tab extends XmlComponent {
|
|||||||
|
|
||||||
export class MaxRightTabStop extends TabStop {
|
export class MaxRightTabStop extends TabStop {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(new Tab("right", 9026));
|
super(new TabStopItem("right", 9026));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LeftTabStop extends TabStop {
|
export class LeftTabStop extends TabStop {
|
||||||
constructor(position: number) {
|
constructor(position: number, leader?: LeaderType) {
|
||||||
super(new Tab("left", position));
|
super(new TabStopItem("left", position, leader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RightTabStop extends TabStop {
|
export class RightTabStop extends TabStop {
|
||||||
constructor(position: number) {
|
constructor(position: number, leader?: LeaderType) {
|
||||||
super(new Tab("right", position));
|
super(new TabStopItem("right", position, leader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CenterTabStop extends TabStop {
|
export class CenterTabStop extends TabStop {
|
||||||
constructor(position: number) {
|
constructor(position: number, leader?: LeaderType) {
|
||||||
super(new Tab("center", position));
|
super(new TabStopItem("center", position, leader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { KeepLines, KeepNext } from "./formatting/keep";
|
|||||||
import { PageBreak, PageBreakBefore } from "./formatting/page-break";
|
import { PageBreak, PageBreakBefore } from "./formatting/page-break";
|
||||||
import { ISpacingProperties, Spacing } from "./formatting/spacing";
|
import { ISpacingProperties, Spacing } from "./formatting/spacing";
|
||||||
import { Style } from "./formatting/style";
|
import { Style } from "./formatting/style";
|
||||||
import { CenterTabStop, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
|
import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
|
||||||
import { NumberProperties } from "./formatting/unordered-list";
|
import { NumberProperties } from "./formatting/unordered-list";
|
||||||
import { Bookmark, Hyperlink } from "./links";
|
import { Bookmark, Hyperlink } from "./links";
|
||||||
import { ParagraphProperties } from "./properties";
|
import { ParagraphProperties } from "./properties";
|
||||||
@ -160,18 +160,18 @@ export class Paragraph extends XmlComponent {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public leftTabStop(position: number): Paragraph {
|
public leftTabStop(position: number, leader?: LeaderType): Paragraph {
|
||||||
this.properties.push(new LeftTabStop(position));
|
this.properties.push(new LeftTabStop(position, leader));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public rightTabStop(position: number): Paragraph {
|
public rightTabStop(position: number, leader?: LeaderType): Paragraph {
|
||||||
this.properties.push(new RightTabStop(position));
|
this.properties.push(new RightTabStop(position, leader));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public centerTabStop(position: number): Paragraph {
|
public centerTabStop(position: number, leader?: LeaderType): Paragraph {
|
||||||
this.properties.push(new CenterTabStop(position));
|
this.properties.push(new CenterTabStop(position, leader));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +232,12 @@ export class Paragraph extends XmlComponent {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get Properties(): ParagraphProperties {
|
public getStyles(): Style[] {
|
||||||
return this.properties;
|
return this.properties.getStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTabStop(run: Run): Paragraph {
|
||||||
|
this.root.splice(1, 0, run);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// http://officeopenxml.com/WPparagraphProperties.php
|
// http://officeopenxml.com/WPparagraphProperties.php
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
import { Border } from "./formatting/border";
|
import { Border } from "./formatting/border";
|
||||||
|
import { Style } from "./formatting/style";
|
||||||
|
|
||||||
export class ParagraphProperties extends XmlComponent {
|
export class ParagraphProperties extends XmlComponent {
|
||||||
public paragraphBorder: Border;
|
public paragraphBorder: Border;
|
||||||
@ -17,4 +18,8 @@ export class ParagraphProperties extends XmlComponent {
|
|||||||
public push(item: XmlComponent): void {
|
public push(item: XmlComponent): void {
|
||||||
this.root.push(item);
|
this.root.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getStyles(): Style[] {
|
||||||
|
return this.root.filter((child) => child instanceof Style) as Style[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./table-of-contents";
|
export * from "./table-of-contents";
|
||||||
|
export * from "./page-reference-instruction";
|
||||||
|
13
src/file/table-of-contents/page-reference-instruction.ts
Normal file
13
src/file/table-of-contents/page-reference-instruction.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
|
class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> {
|
||||||
|
protected xmlKeys = { space: "xml:space" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PageReferenceInstruction extends XmlComponent {
|
||||||
|
constructor(bookmarkId: string) {
|
||||||
|
super("w:instrText");
|
||||||
|
this.root.push(new TextAttributes({ space: "preserve" }));
|
||||||
|
this.root.push(`PAGEREF ${bookmarkId} \h`);
|
||||||
|
}
|
||||||
|
}
|
@ -54,4 +54,8 @@ export class TableOfContentsInstruction extends XmlComponent {
|
|||||||
}
|
}
|
||||||
this.root.push(instruction);
|
this.root.push(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getHeaderRange(): string {
|
||||||
|
return this.properties.headerRange;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,27 +1,42 @@
|
|||||||
// import { TableOfContentsProperties } from "./properties";
|
// import { TableOfContentsProperties } from "./properties";
|
||||||
import { ParagraphProperties } from "file/paragraph";
|
import { Paragraph, ParagraphProperties } from "file/paragraph";
|
||||||
import { Run } from "file/paragraph/run";
|
import { Run } from "file/paragraph/run";
|
||||||
import { Begin, End, Separate } from "file/paragraph/run/field";
|
import { Begin, End, Separate } from "file/paragraph/run/field";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
import { TableOfContentsInstruction } from "./instruction";
|
import { TableOfContentsInstruction } from "./table-of-contents-instruction";
|
||||||
|
|
||||||
export class TableOfContents extends XmlComponent {
|
export class TableOfContents extends XmlComponent {
|
||||||
// private readonly tocProperties: TableOfContentsProperties;
|
// private readonly tocProperties: TableOfContentsProperties;
|
||||||
private readonly properties: ParagraphProperties;
|
private readonly properties: ParagraphProperties;
|
||||||
|
|
||||||
|
private readonly instruction: TableOfContentsInstruction;
|
||||||
|
|
||||||
constructor(/*tocProperties?: TableOfContentsProperties*/) {
|
constructor(/*tocProperties?: TableOfContentsProperties*/) {
|
||||||
super("w:p");
|
super("w:sdt");
|
||||||
this.properties = new ParagraphProperties();
|
this.properties = new ParagraphProperties();
|
||||||
|
this.instruction = new TableOfContentsInstruction();
|
||||||
this.root.push(this.properties);
|
this.root.push(this.properties);
|
||||||
// this.tocProperties = tocProperties || new TableOfContentsProperties();
|
// this.tocProperties = tocProperties || new TableOfContentsProperties();
|
||||||
const firstRun = new Run();
|
const beginParagraph = new Paragraph();
|
||||||
firstRun.addChildElement(new Begin());
|
const beginRun = new Run();
|
||||||
firstRun.addChildElement(new TableOfContentsInstruction());
|
beginRun.addChildElement(new Begin());
|
||||||
firstRun.addChildElement(new Separate());
|
beginRun.addChildElement(this.instruction);
|
||||||
this.root.push(firstRun);
|
beginRun.addChildElement(new Separate());
|
||||||
|
beginParagraph.addRun(beginRun);
|
||||||
|
this.root.push(beginParagraph);
|
||||||
|
|
||||||
const secondRun = new Run();
|
const endParagraph = new Paragraph();
|
||||||
secondRun.addChildElement(new End());
|
const endRun = new Run();
|
||||||
this.root.push(secondRun);
|
endRun.addChildElement(new End());
|
||||||
|
endParagraph.addRun(endRun);
|
||||||
|
this.root.push(endParagraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeaderRange(): string {
|
||||||
|
return this.instruction.getHeaderRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addGeneratedContent(paragraph: Paragraph): void {
|
||||||
|
this.root.splice(this.root.length - 1, 0, paragraph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user