generating the content for a table of contents

This commit is contained in:
Sergio Mendonça
2018-09-18 05:24:19 -03:00
parent 0eb36be053
commit 8e911698a5
13 changed files with 168 additions and 38 deletions

View File

@ -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",

View File

@ -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);

View 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();

View File

@ -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();
}
} }

View File

@ -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++;
} }
} }

View File

@ -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,
}), }),
); );
} }

View File

@ -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));
} }
} }

View File

@ -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;
} }
} }

View File

@ -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[];
}
} }

View File

@ -1 +1,2 @@
export * from "./table-of-contents"; export * from "./table-of-contents";
export * from "./page-reference-instruction";

View 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`);
}
}

View File

@ -54,4 +54,8 @@ export class TableOfContentsInstruction extends XmlComponent {
} }
this.root.push(instruction); this.root.push(instruction);
} }
public getHeaderRange(): string {
return this.properties.headerRange;
}
} }

View File

@ -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);
} }
} }