#940 - Add positional tab feature

Also add extra run elements as per spec
This commit is contained in:
Dolan Miu
2022-12-24 19:32:44 +00:00
parent 49b4ca67e0
commit 11bebd42ac
15 changed files with 891 additions and 164 deletions

View File

@ -0,0 +1,229 @@
import { expect } from "chai";
import { Formatter } from "@export/formatter";
import {
AnnotationReference,
CarriageReturn,
ContinuationSeparator,
DayLong,
DayShort,
EndnoteReference,
FootnoteReferenceElement,
LastRenderedPageBreak,
MonthLong,
MonthShort,
NoBreakHyphen,
PageNumberElement,
Separator,
SoftHyphen,
Tab,
YearLong,
YearShort,
} from "./empty-children";
// <xsd:element name="noBreakHyphen" type="CT_Empty"/>
// <xsd:element name="softHyphen" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="annotationRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="footnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="endnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="separator" type="CT_Empty" minOccurs="0" />
// <xsd:element name="continuationSeparator" type="CT_Empty" minOccurs="0" />
// ...
// <xsd:element name="pgNum" type="CT_Empty" minOccurs="0" />
// <xsd:element name="cr" type="CT_Empty" minOccurs="0" />
// <xsd:element name="tab" type="CT_Empty" minOccurs="0" />
// ...
// <xsd:element name="lastRenderedPageBreak" type="CT_Empty" minOccurs="0" maxOccurs="1" />
describe("NoBreakHyphen", () => {
describe("#constructor()", () => {
it("should create a NoBreakHyphen with correct root key", () => {
const tree = new Formatter().format(new NoBreakHyphen());
expect(tree).to.deep.equal({
"w:noBreakHyphen": {},
});
});
});
});
describe("SoftHyphen", () => {
describe("#constructor()", () => {
it("should create a SoftHyphen with correct root key", () => {
const tree = new Formatter().format(new SoftHyphen());
expect(tree).to.deep.equal({
"w:softHyphen": {},
});
});
});
});
describe("DayShort", () => {
describe("#constructor()", () => {
it("should create a DayShort with correct root key", () => {
const tree = new Formatter().format(new DayShort());
expect(tree).to.deep.equal({
"w:dayShort": {},
});
});
});
});
describe("MonthShort", () => {
describe("#constructor()", () => {
it("should create a MonthShort with correct root key", () => {
const tree = new Formatter().format(new MonthShort());
expect(tree).to.deep.equal({
"w:monthShort": {},
});
});
});
});
describe("YearShort", () => {
describe("#constructor()", () => {
it("should create a YearShort with correct root key", () => {
const tree = new Formatter().format(new YearShort());
expect(tree).to.deep.equal({
"w:yearShort": {},
});
});
});
});
describe("DayLong", () => {
describe("#constructor()", () => {
it("should create a DayLong with correct root key", () => {
const tree = new Formatter().format(new DayLong());
expect(tree).to.deep.equal({
"w:dayLong": {},
});
});
});
});
describe("MonthLong", () => {
describe("#constructor()", () => {
it("should create a MonthLong with correct root key", () => {
const tree = new Formatter().format(new MonthLong());
expect(tree).to.deep.equal({
"w:monthLong": {},
});
});
});
});
describe("YearLong", () => {
describe("#constructor()", () => {
it("should create a YearLong with correct root key", () => {
const tree = new Formatter().format(new YearLong());
expect(tree).to.deep.equal({
"w:yearLong": {},
});
});
});
});
describe("AnnotationReference", () => {
describe("#constructor()", () => {
it("should create a AnnotationReference with correct root key", () => {
const tree = new Formatter().format(new AnnotationReference());
expect(tree).to.deep.equal({
"w:annotationRef": {},
});
});
});
});
describe("FootnoteReferenceElement", () => {
describe("#constructor()", () => {
it("should create a FootnoteReferenceElement with correct root key", () => {
const tree = new Formatter().format(new FootnoteReferenceElement());
expect(tree).to.deep.equal({
"w:footnoteRef": {},
});
});
});
});
describe("EndnoteReference", () => {
describe("#constructor()", () => {
it("should create a EndnoteReference with correct root key", () => {
const tree = new Formatter().format(new EndnoteReference());
expect(tree).to.deep.equal({
"w:endnoteRef": {},
});
});
});
});
describe("Separator", () => {
describe("#constructor()", () => {
it("should create a Separator with correct root key", () => {
const tree = new Formatter().format(new Separator());
expect(tree).to.deep.equal({
"w:separator": {},
});
});
});
});
describe("ContinuationSeparator", () => {
describe("#constructor()", () => {
it("should create a ContinuationSeparator with correct root key", () => {
const tree = new Formatter().format(new ContinuationSeparator());
expect(tree).to.deep.equal({
"w:continuationSeparator": {},
});
});
});
});
describe("PageNumberElement", () => {
describe("#constructor()", () => {
it("should create a PageNumberElement with correct root key", () => {
const tree = new Formatter().format(new PageNumberElement());
expect(tree).to.deep.equal({
"w:pgNum": {},
});
});
});
});
describe("CarriageReturn", () => {
describe("#constructor()", () => {
it("should create a CarriageReturn with correct root key", () => {
const tree = new Formatter().format(new CarriageReturn());
expect(tree).to.deep.equal({
"w:cr": {},
});
});
});
});
describe("Tab", () => {
describe("#constructor()", () => {
it("should create a Tab with correct root key", () => {
const tree = new Formatter().format(new Tab());
expect(tree).to.deep.equal({
"w:tab": {},
});
});
});
});
describe("LastRenderedPageBreak", () => {
describe("#constructor()", () => {
it("should create a LastRenderedPageBreak with correct root key", () => {
const tree = new Formatter().format(new LastRenderedPageBreak());
expect(tree).to.deep.equal({
"w:lastRenderedPageBreak": {},
});
});
});
});

View File

@ -0,0 +1,127 @@
import { EmptyElement } from "@file/xml-components";
// <xsd:group name="EG_RunInnerContent">
// ...
// <xsd:element name="noBreakHyphen" type="CT_Empty"/>
// <xsd:element name="softHyphen" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="annotationRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="footnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="endnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="separator" type="CT_Empty" minOccurs="0" />
// <xsd:element name="continuationSeparator" type="CT_Empty" minOccurs="0" />
// ...
// <xsd:element name="pgNum" type="CT_Empty" minOccurs="0" />
// <xsd:element name="cr" type="CT_Empty" minOccurs="0" />
// <xsd:element name="tab" type="CT_Empty" minOccurs="0" />
// ...
// <xsd:element name="lastRenderedPageBreak" type="CT_Empty" minOccurs="0" maxOccurs="1" />
// </xsd:choice>
// </xsd:group>
export class NoBreakHyphen extends EmptyElement {
public constructor() {
super("w:noBreakHyphen");
}
}
export class SoftHyphen extends EmptyElement {
public constructor() {
super("w:softHyphen");
}
}
export class DayShort extends EmptyElement {
public constructor() {
super("w:dayShort");
}
}
export class MonthShort extends EmptyElement {
public constructor() {
super("w:monthShort");
}
}
export class YearShort extends EmptyElement {
public constructor() {
super("w:yearShort");
}
}
export class DayLong extends EmptyElement {
public constructor() {
super("w:dayLong");
}
}
export class MonthLong extends EmptyElement {
public constructor() {
super("w:monthLong");
}
}
export class YearLong extends EmptyElement {
public constructor() {
super("w:yearLong");
}
}
export class AnnotationReference extends EmptyElement {
public constructor() {
super("w:annotationRef");
}
}
export class FootnoteReferenceElement extends EmptyElement {
public constructor() {
super("w:footnoteRef");
}
}
export class EndnoteReference extends EmptyElement {
public constructor() {
super("w:endnoteRef");
}
}
export class Separator extends EmptyElement {
public constructor() {
super("w:separator");
}
}
export class ContinuationSeparator extends EmptyElement {
public constructor() {
super("w:continuationSeparator");
}
}
export class PageNumberElement extends EmptyElement {
public constructor() {
super("w:pgNum");
}
}
export class CarriageReturn extends EmptyElement {
public constructor() {
super("w:cr");
}
}
export class Tab extends EmptyElement {
public constructor() {
super("w:tab");
}
}
export class LastRenderedPageBreak extends EmptyElement {
public constructor() {
super("w:lastRenderedPageBreak");
}
}

View File

@ -7,6 +7,7 @@ export * from "./run-fonts";
export * from "./sequential-identifier";
export * from "./underline";
export * from "./emphasis-mark";
export * from "./tab";
export * from "./simple-field";
export * from "./comment-run";
export * from "./empty-children";
export * from "./positional-tab";

View File

@ -0,0 +1,27 @@
import { expect } from "chai";
import { Formatter } from "@export/formatter";
import { PositionalTab, PositionalTabAlignment, PositionalTabLeader, PositionalTabRelativeTo } from "./positional-tab";
describe("PositionalTab", () => {
it("should create a PositionalTab with correct root key", () => {
const tree = new Formatter().format(
new PositionalTab({
alignment: PositionalTabAlignment.CENTER,
relativeTo: PositionalTabRelativeTo.MARGIN,
leader: PositionalTabLeader.DOT,
}),
);
expect(tree).to.deep.equal({
"w:ptab": {
_attr: {
"w:alignment": "center",
"w:relativeTo": "margin",
"w:leader": "dot",
},
},
});
});
});

View File

@ -0,0 +1,80 @@
import { NextAttributeComponent, XmlComponent } from "@file/xml-components";
// <xsd:simpleType name="ST_PTabAlignment">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="left" />
// <xsd:enumeration value="center" />
// <xsd:enumeration value="right" />
// </xsd:restriction>
// </xsd:simpleType>
export enum PositionalTabAlignment {
LEFT = "left",
CENTER = "center",
RIGHT = "right",
}
// <xsd:simpleType name="ST_PTabRelativeTo">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="margin" />
// <xsd:enumeration value="indent" />
// </xsd:restriction>
// </xsd:simpleType>
export enum PositionalTabRelativeTo {
MARGIN = "margin",
INDENT = "indent",
}
// <xsd:simpleType name="ST_PTabLeader">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="none" />
// <xsd:enumeration value="dot" />
// <xsd:enumeration value="hyphen" />
// <xsd:enumeration value="underscore" />
// <xsd:enumeration value="middleDot" />
// </xsd:restriction>
// </xsd:simpleType>
export enum PositionalTabLeader {
NONE = "none",
DOT = "dot",
HYPHEN = "hyphen",
UNDERSCORE = "underscore",
MIDDLE_DOT = "middleDot",
}
export interface PositionalTabOptions {
readonly alignment: PositionalTabAlignment;
readonly relativeTo: PositionalTabRelativeTo;
readonly leader: PositionalTabLeader;
}
// <xsd:complexType name="CT_PTab">
// <xsd:attribute name="alignment" type="ST_PTabAlignment" use="required" />
// <xsd:attribute name="relativeTo" type="ST_PTabRelativeTo" use="required" />
// <xsd:attribute name="leader" type="ST_PTabLeader" use="required" />
// </xsd:complexType>
export class PositionalTab extends XmlComponent {
public constructor(options: PositionalTabOptions) {
super("w:ptab");
this.root.push(
new NextAttributeComponent<{
readonly alignment: PositionalTabAlignment;
readonly relativeTo: PositionalTabRelativeTo;
readonly leader: PositionalTabLeader;
}>({
alignment: {
key: "w:alignment",
value: options.alignment,
},
relativeTo: {
key: "w:relativeTo",
value: options.relativeTo,
},
leader: {
key: "w:leader",
value: options.leader,
},
}),
);
}
}

View File

@ -9,10 +9,91 @@ import { Begin, End, Separate } from "./field";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { IRunPropertiesOptions, RunProperties } from "./properties";
import { Text } from "./run-components/text";
import { Tab } from "./tab";
import {
AnnotationReference,
CarriageReturn,
ContinuationSeparator,
DayLong,
DayShort,
EndnoteReference,
FootnoteReferenceElement,
LastRenderedPageBreak,
MonthLong,
MonthShort,
NoBreakHyphen,
PageNumberElement,
Separator,
SoftHyphen,
Tab,
YearLong,
YearShort,
} from "./empty-children";
import { PositionalTab } from "./positional-tab";
export interface IRunOptions extends IRunPropertiesOptions {
readonly children?: readonly (Begin | FieldInstruction | Separate | End | PageNumber | FootnoteReferenceRun | Tab | string)[];
// <xsd:choice>
// <xsd:element name="br" type="CT_Br" />
// <xsd:element name="t" type="CT_Text" />
// <xsd:element name="contentPart" type="CT_Rel" />
// <xsd:element name="delText" type="CT_Text" />
// <xsd:element name="instrText" type="CT_Text" />
// <xsd:element name="delInstrText" type="CT_Text" />
// <xsd:element name="noBreakHyphen" type="CT_Empty" />
// <xsd:element name="softHyphen" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearShort" type="CT_Empty" minOccurs="0" />
// <xsd:element name="dayLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="monthLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="yearLong" type="CT_Empty" minOccurs="0" />
// <xsd:element name="annotationRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="footnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="endnoteRef" type="CT_Empty" minOccurs="0" />
// <xsd:element name="separator" type="CT_Empty" minOccurs="0" />
// <xsd:element name="continuationSeparator" type="CT_Empty" minOccurs="0" />
// <xsd:element name="sym" type="CT_Sym" minOccurs="0" />
// <xsd:element name="pgNum" type="CT_Empty" minOccurs="0" />
// <xsd:element name="cr" type="CT_Empty" minOccurs="0" />
// <xsd:element name="tab" type="CT_Empty" minOccurs="0" />
// <xsd:element name="object" type="CT_Object" />
// <xsd:element name="pict" type="CT_Picture" />
// <xsd:element name="fldChar" type="CT_FldChar" />
// <xsd:element name="ruby" type="CT_Ruby" />
// <xsd:element name="footnoteReference" type="CT_FtnEdnRef" />
// <xsd:element name="endnoteReference" type="CT_FtnEdnRef" />
// <xsd:element name="commentReference" type="CT_Markup" />
// <xsd:element name="drawing" type="CT_Drawing" />
// <xsd:element name="ptab" type="CT_PTab" minOccurs="0" />
// <xsd:element name="lastRenderedPageBreak" type="CT_Empty" minOccurs="0" maxOccurs="1" />
// </xsd:choice>
readonly children?: readonly (
| Begin
| FieldInstruction
| Separate
| End
| PageNumber
| FootnoteReferenceRun
| Break
| AnnotationReference
| CarriageReturn
| ContinuationSeparator
| DayLong
| DayShort
| EndnoteReference
| FootnoteReferenceElement
| LastRenderedPageBreak
| MonthLong
| MonthShort
| NoBreakHyphen
| PageNumberElement
| Separator
| SoftHyphen
| Tab
| YearLong
| YearShort
| PositionalTab
| string
)[];
readonly break?: number;
readonly text?: string;
}

View File

@ -1,22 +0,0 @@
import { expect } from "chai";
import { Formatter } from "@export/formatter";
import { Tab } from "./tab";
describe("Tab", () => {
let tab: Tab;
beforeEach(() => {
tab = new Tab();
});
describe("#constructor()", () => {
it("should create a Tab with correct root key", () => {
const tree = new Formatter().format(tab);
expect(tree).to.deep.equal({
"w:tab": {},
});
});
});
});

View File

@ -1,14 +0,0 @@
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_tab_topic_ID0EM6AO.html
import { XmlComponent } from "@file/xml-components";
// <xsd:group name="EG_RunInnerContent">
// ...
// <xsd:element name="tab" type="CT_Empty" minOccurs="0"/>
//
// TODO: this is unused and undocumented currently.
// I think the intended use was for users to import, and insert as a child of `Run`.
export class Tab extends XmlComponent {
public constructor() {
super("w:tab");
}
}

View File

@ -35,6 +35,11 @@ export class HpsMeasureElement extends XmlComponent {
// This represents element type CT_String, which indicate a string value.
//
// <xsd:complexType name="CT_Empty"/>
export class EmptyElement extends XmlComponent {}
// This represents element type CT_Empty, which indicate aan empty element.
//
// <xsd:complexType name="CT_String">
// <xsd:attribute name="val" type="s:ST_String" use="required"/>
// </xsd:complexType>