From 808c5b00a080a46fd1101ad63ffa8d63e2be68e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 25 Sep 2018 01:18:47 -0300 Subject: [PATCH] table of contents with all the options --- src/file/table-of-contents/index.ts | 1 + src/file/table-of-contents/properties.ts | 3 - .../table-of-contents-instruction.ts | 83 ++++++----- .../table-of-contents-properties.ts | 122 +++++++++++++++ .../table-of-contents.spec.ts | 140 ++++++++++++++++-- .../table-of-contents/table-of-contents.ts | 7 +- 6 files changed, 300 insertions(+), 56 deletions(-) delete mode 100644 src/file/table-of-contents/properties.ts create mode 100644 src/file/table-of-contents/table-of-contents-properties.ts diff --git a/src/file/table-of-contents/index.ts b/src/file/table-of-contents/index.ts index 13700e9bfb..f36b16b738 100644 --- a/src/file/table-of-contents/index.ts +++ b/src/file/table-of-contents/index.ts @@ -1 +1,2 @@ export * from "./table-of-contents"; +export * from "./table-of-contents-properties"; diff --git a/src/file/table-of-contents/properties.ts b/src/file/table-of-contents/properties.ts deleted file mode 100644 index 4b452e42b1..0000000000 --- a/src/file/table-of-contents/properties.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class TableOfContentsProperties { - public headingRange = "1-6"; -} diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/table-of-contents-instruction.ts index aaf0df948d..ec8a6f5a94 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/table-of-contents-instruction.ts @@ -1,61 +1,68 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { TableOfContentsProperties } from "./table-of-contents-properties"; class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { protected xmlKeys = { space: "xml:space" }; } -/** - * Options according to this docs: - * http://officeopenxml.com/WPtableOfContents.php - */ - -export class StyleLevel { - public styleName: string; - public level: number; -} -export class TableOfContentsInstructionProperties { - // \b option - public entriesFromSession: string; - // \h option - public hiperlink: true; - // \n option - public entryLevelsRange: string; - // \o option - public headerRange = "1-6"; - // \t option - public styles: StyleLevel[]; - // \z option -} - export class TableOfContentsInstruction extends XmlComponent { - private readonly properties: TableOfContentsInstructionProperties; + private readonly properties: TableOfContentsProperties; - constructor(properties?: TableOfContentsInstructionProperties) { + constructor(properties?: TableOfContentsProperties) { super("w:instrText"); - this.properties = properties || new TableOfContentsInstructionProperties(); + this.properties = properties || new TableOfContentsProperties(); this.root.push(new TextAttributes({ space: "preserve" })); let instruction = "TOC"; - if (this.properties.entriesFromSession) { - instruction = `${instruction} \\b "${this.properties.entriesFromSession}"`; + if (this.properties.captionLabel) { + instruction = `${instruction} \\a "${this.properties.captionLabel}"`; + } + if (this.properties.entriesFromBookmark) { + instruction = `${instruction} \\b "${this.properties.entriesFromBookmark}"`; + } + if (this.properties.captionLabelIncludingNumbers) { + instruction = `${instruction} \\c "${this.properties.captionLabelIncludingNumbers}"`; + } + if (this.properties.sequenceAndPageNumbersSeparator) { + instruction = `${instruction} \\d "${this.properties.sequenceAndPageNumbersSeparator}"`; + } + if (this.properties.tcFieldIdentifier) { + instruction = `${instruction} \\f "${this.properties.tcFieldIdentifier}"`; } if (this.properties.hiperlink) { instruction = `${instruction} \\h`; } - if (this.properties.entryLevelsRange) { - instruction = `${instruction} \\n "${this.properties.entryLevelsRange}"`; + if (this.properties.tcFieldLevelRange) { + instruction = `${instruction} \\l "${this.properties.tcFieldLevelRange}`; } - if (this.properties.headerRange) { - instruction = `${instruction} \\o "${this.properties.headerRange}"`; + if (this.properties.pageNumbersEntryLevelsRange) { + instruction = `${instruction} \\n "${this.properties.pageNumbersEntryLevelsRange}`; } - if (this.properties.styles && this.properties.styles.length) { - const styles = this.properties.styles.map((sl) => `${sl.styleName}, ${sl.level}`).join(", "); + if (this.properties.headingStyleRange) { + instruction = `${instruction} \\o "${this.properties.headingStyleRange}`; + } + if (this.properties.entryAndPageNumberSeparator) { + instruction = `${instruction} \\p "${this.properties.entryAndPageNumberSeparator}`; + } + if (this.properties.seqFieldIdentifierForPrefix) { + instruction = `${instruction} \\s "${this.properties.seqFieldIdentifierForPrefix}`; + } + if (this.properties.stylesWithLevels && this.properties.stylesWithLevels.length) { + const styles = this.properties.stylesWithLevels.map((sl) => `${sl.styleName},${sl.level}`).join(","); instruction = `${instruction} \\t "${styles}"`; } + if (this.properties.useAppliedParagraphOutlineLevel) { + instruction = `${instruction} \\u`; + } + if (this.properties.preserveTabInEntries) { + instruction = `${instruction} \\w`; + } + if (this.properties.preserveNewLineInEntries) { + instruction = `${instruction} \\x`; + } + if (this.properties.hideTabAndPageNumbersInWebView) { + instruction = `${instruction} \\z`; + } this.root.push(instruction); } - - public getHeaderRange(): string { - return this.properties.headerRange; - } } diff --git a/src/file/table-of-contents/table-of-contents-properties.ts b/src/file/table-of-contents/table-of-contents-properties.ts new file mode 100644 index 0000000000..2f50589429 --- /dev/null +++ b/src/file/table-of-contents/table-of-contents-properties.ts @@ -0,0 +1,122 @@ +export class StyleLevel { + public styleName: string; + public level: number; + + constructor(styleName: string, level: number) { + this.styleName = styleName; + this.level = level; + } +} + +/** + * Options according to this docs: + * https://www.ecma-international.org/publications/standards/Ecma-376.htm + * Part 1 - Page 1251 + * + * Short Guide: + * http://officeopenxml.com/WPtableOfContents.php + */ +export class TableOfContentsProperties { + /** + * \a option - Includes captioned items, but omits caption labels and numbers. + * The identifier designated by text in this switch's field-argument corresponds to the caption label. + * Use captionLabelIncludingNumbers (\c) to build a table of captions with labels and numbers. + */ + public captionLabel: string; + + /** + * \b option - Includes entries only from the portion of the document marked by + * the bookmark named by text in this switch's field-argument. + */ + public entriesFromBookmark: string; + + /** + * \c option - Includes figures, tables, charts, and other items that are numbered + * by a SEQ field (§17.16.5.56). The sequence identifier designated by text in this switch's + * field-argument, which corresponds to the caption label, shall match the identifier in the + * corresponding SEQ field. + */ + public captionLabelIncludingNumbers: string; + + /** + * \d option - When used with \s, the text in this switch's field-argument defines + * the separator between sequence and page numbers. The default separator is a hyphen (-). + */ + public sequenceAndPageNumbersSeparator: string; + + /** + * \f option - Includes only those TC fields whose identifier exactly matches the + * text in this switch's field-argument (which is typically a letter). + */ + public tcFieldIdentifier: string; + + /** + * \h option - Makes the table of contents entries hyperlinks. + */ + public hiperlink: boolean; + + /** + * \l option - Includes TC fields that assign entries to one of the levels specified + * by text in this switch's field-argument as a range having the form startLevel-endLevel, + * where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. + * TC fields that assign entries to lower levels are skipped. + */ + public tcFieldLevelRange: string; + + /** + * \n option - Without field-argument, omits page numbers from the table of contents. + * Page numbers are omitted from all levels unless a range of entry levels is specified by + * text in this switch's field-argument. A range is specified as for \l. + */ + public pageNumbersEntryLevelsRange: string; + + /** + * \o option - Uses paragraphs formatted with all or the specified range of builtin + * heading styles. Headings in a style range are specified by text in this switch's + * field-argument using the notation specified as for \l, where each integer corresponds + * to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). + * If no heading range is specified, all heading levels used in the document are listed. + */ + public headingStyleRange: string; + + /** + * \p option - Text in this switch's field-argument specifies a sequence of characters + * that separate an entry and its page number. The default is a tab with leader dots. + */ + public entryAndPageNumberSeparator: string; + + /** + * \s option - For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. + * The prefix depends on the type of entry. text in this switch's field-argument shall match the + * identifier in the SEQ field. + */ + public seqFieldIdentifierForPrefix: string; + + /** + * \t field-argument Uses paragraphs formatted with styles other than the built-in heading styles. + * Text in this switch's field-argument specifies those styles as a set of comma-separated doublets, + * with each doublet being a comma-separated set of style name and table of content level. + * \t can be combined with \o. + */ + public stylesWithLevels: StyleLevel[]; + + /** + * \u Uses the applied paragraph outline level. + */ + public useAppliedParagraphOutlineLevel = false; + + /** + * \w Preserves tab entries within table entries. + */ + public preserveTabInEntries = false; + + /** + * \x Preserves newline characters within table entries. + */ + public preserveNewLineInEntries = false; + + /** + * \z Hides tab leader and page numbers in web page view (§17.18.102). + */ + public hideTabAndPageNumbersInWebView = false; +} diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 9ae3c53da5..b306cceea2 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -1,7 +1,46 @@ import { expect } from "chai"; import { Formatter } from "../../export/formatter"; -import { TableOfContents } from "./"; +import { StyleLevel, TableOfContents, TableOfContentsProperties } from "./"; + +describe("Table of Contents", () => { + describe("#constructor", () => { + it("should construct a TOC without options", () => { + const toc = new TableOfContents(); + const tree = new Formatter().format(toc); + expect(tree).to.be.deep.equal(DEFAULT_TOC); + }); + + it("should construct a TOC with all the options and alias", () => { + const props = new TableOfContentsProperties(); + + props.captionLabel = "A"; + props.entriesFromBookmark = "B"; + props.captionLabelIncludingNumbers = "C"; + props.sequenceAndPageNumbersSeparator = "D"; + props.tcFieldIdentifier = "F"; + props.hiperlink = true; + props.tcFieldLevelRange = "L"; + props.pageNumbersEntryLevelsRange = "N"; + props.headingStyleRange = "O"; + props.entryAndPageNumberSeparator = "P"; + props.seqFieldIdentifierForPrefix = "S"; + + const styles = new Array(); + styles.push(new StyleLevel("SL", 1)); + styles.push(new StyleLevel("SL", 2)); + props.stylesWithLevels = styles; + props.useAppliedParagraphOutlineLevel = true; + props.preserveTabInEntries = true; + props.preserveNewLineInEntries = true; + props.hideTabAndPageNumbersInWebView = true; + + const toc = new TableOfContents("Summary", props); + const tree = new Formatter().format(toc); + expect(tree).to.be.deep.equal(COMPLETE_TOC); + }); + }); +}); const DEFAULT_TOC = { "w:sdt": [ @@ -47,7 +86,7 @@ const DEFAULT_TOC = { "xml:space": "preserve", }, }, - 'TOC \\o "1-6"', + "TOC", ], }, { @@ -91,13 +130,90 @@ const DEFAULT_TOC = { ], }; -describe("Table of Contents", () => { - describe("#constructor", () => { - it("should construct a TOC with default options", () => { - const toc = new TableOfContents(); - const tree = new Formatter().format(toc); - // console.log(JSON.stringify(tree, null, 2)); - expect(tree).to.be.deep.equal(DEFAULT_TOC); - }); - }); -}); +const COMPLETE_TOC = { + "w:sdt": [ + { + "w:sdtPr": [ + { + "w:alias": [ + { + _attr: { + "w:val": "Summary", + }, + }, + ], + }, + ], + }, + { + "w:sdtContent": [ + { + "w:p": [ + { + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "begin", + "w:dirty": true, + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + 'TOC \\a "A" \\b "B" \\c "C" \\d "D" \\f "F" \\h \\l "L \\n "N \\o "O \\p "P \\s "S \\t "SL,1,SL,2" \\u \\w \\x \\z', + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], + }, + ], + }, + ], + }, + { + "w:p": [ + { + "w:pPr": [], + }, + { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index d4a93b57e1..c659ec2ad2 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -6,18 +6,19 @@ import { XmlComponent } from "file/xml-components"; import { SdtContent } from "./sdt-content"; import { SdtProperties } from "./sdt-properties"; import { TableOfContentsInstruction } from "./table-of-contents-instruction"; +import { TableOfContentsProperties } from "./table-of-contents-properties"; export class TableOfContents extends XmlComponent { - constructor() { + constructor(alias: string = "Table of Contents", properties?: TableOfContentsProperties) { super("w:sdt"); - this.root.push(new SdtProperties("Table of Contents")); + this.root.push(new SdtProperties(alias)); const content = new SdtContent(); const beginParagraph = new Paragraph(); const beginRun = new Run(); beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(new TableOfContentsInstruction()); + beginRun.addChildElement(new TableOfContentsInstruction(properties)); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); content.addChildElement(beginParagraph);