From af3b7f2613a8266904b5d6eaaca998920742a047 Mon Sep 17 00:00:00 2001 From: Dolan Date: Sat, 17 Dec 2022 19:39:27 +0000 Subject: [PATCH 1/3] Remove unused method --- src/file/relationships/relationships.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/file/relationships/relationships.ts b/src/file/relationships/relationships.ts index fab99968dc..b7b0f89ca6 100644 --- a/src/file/relationships/relationships.ts +++ b/src/file/relationships/relationships.ts @@ -12,13 +12,9 @@ export class Relationships extends XmlComponent { ); } - public addRelationship(relationship: Relationship): void { - this.root.push(relationship); - } - public createRelationship(id: number | string, type: RelationshipType, target: string, targetMode?: TargetModeType): Relationship { const relationship = new Relationship(`rId${id}`, type, target, targetMode); - this.addRelationship(relationship); + this.root.push(relationship); return relationship; } From 03d5152e7c27278aa69456466d1eba44d7bea2e7 Mon Sep 17 00:00:00 2001 From: Dolan Date: Sun, 18 Dec 2022 00:52:29 +0000 Subject: [PATCH 2/3] Add BuilderElement and lang --- src/file/paragraph/run/properties.ts | 37 +++++++++++++++++++ src/file/paragraph/run/run.spec.ts | 30 +++++++++++++++ src/file/xml-components/default-attributes.ts | 31 ++++++++++++++-- src/file/xml-components/simple-elements.ts | 12 +++++- 4 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts index 19593b62dd..f5ddc4750b 100644 --- a/src/file/paragraph/run/properties.ts +++ b/src/file/paragraph/run/properties.ts @@ -2,6 +2,7 @@ import { BorderElement, IBorderOptions } from "@file/border"; import { IShadingAttributesProperties, Shading } from "@file/shading"; import { ChangeAttributes, IChangedAttributesProperties } from "@file/track-revision/track-revision"; import { + BuilderElement, HpsMeasureElement, IgnoreIfEmptyXmlComponent, NumberValueElement, @@ -51,6 +52,16 @@ export interface IRunStylePropertiesOptions { readonly emboss?: boolean; readonly imprint?: boolean; readonly revision?: IRunPropertiesChangeOptions; + // + // + // + // + // + readonly language?: { + readonly value?: string; + readonly eastAsia?: string; + readonly bidirectional?: string; + }; readonly border?: IBorderOptions; readonly vanish?: boolean; readonly specVanish?: boolean; @@ -240,6 +251,32 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { if (options.scale !== undefined) { this.push(new NumberValueElement("w:w", options.scale)); } + + if (options.language) { + this.push( + new BuilderElement<{ + readonly value: string; + readonly eastAsia: string; + readonly bidirectional: string; + }>({ + name: "w:lang", + attributes: { + value: { + key: "w:val", + value: options.language.value, + }, + eastAsia: { + key: "w:eastAsia", + value: options.language.eastAsia, + }, + bidirectional: { + key: "w:bidi", + value: options.language.bidirectional, + }, + }, + }), + ); + } } public push(item: XmlComponent): void { diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts index b178536435..2a207a7b87 100644 --- a/src/file/paragraph/run/run.spec.ts +++ b/src/file/paragraph/run/run.spec.ts @@ -580,5 +580,35 @@ describe("Run", () => { }); }); }); + + describe("#language", () => { + it("should correctly set the language", () => { + const run = new Run({ + language: { + value: "en-US", + eastAsia: "zh-CN", + bidirectional: "ar-SA", + }, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:lang": { + _attr: { + "w:val": "en-US", + "w:eastAsia": "zh-CN", + "w:bidi": "ar-SA", + }, + }, + }, + ], + }, + ], + }); + }); + }); }); }); diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts index bf84e95eef..c3fb1b8105 100644 --- a/src/file/xml-components/default-attributes.ts +++ b/src/file/xml-components/default-attributes.ts @@ -3,14 +3,20 @@ import { IXmlableObject } from "./xmlable-object"; export type AttributeMap = { readonly [P in keyof T]: string }; +export type AttributeData = { readonly [key: string]: boolean | number | string }; +export type AttributePayload = Record< + keyof T, + { + readonly key: string; + readonly value?: T[keyof T]; + } +>; + export abstract class XmlAttributeComponent extends BaseXmlComponent { - // tslint:disable-next-line:readonly-keyword - protected readonly root: T; protected readonly xmlKeys?: AttributeMap; - public constructor(properties: T) { + public constructor(private readonly root: T) { super("_attr"); - this.root = properties; } public prepForXml(_: IContext): IXmlableObject { @@ -26,3 +32,20 @@ export abstract class XmlAttributeComponent extends BaseXmlCom return { _attr: attrs }; } } + +export class NextAttributeComponent extends BaseXmlComponent { + public constructor(private readonly root: AttributePayload) { + super("_attr"); + } + + public prepForXml(_: IContext): IXmlableObject { + const attrs = {}; + Object.values(this.root).forEach(({ key, value }) => { + if (value !== undefined) { + // eslint-disable-next-line functional/immutable-data + attrs[key] = value; + } + }); + return { _attr: attrs }; + } +} diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts index aca3355c28..b049244746 100644 --- a/src/file/xml-components/simple-elements.ts +++ b/src/file/xml-components/simple-elements.ts @@ -1,4 +1,4 @@ -import { Attributes, XmlComponent } from "@file/xml-components"; +import { AttributeData, AttributePayload, Attributes, NextAttributeComponent, XmlComponent } from "@file/xml-components"; import { hpsMeasureValue } from "@util/values"; @@ -70,3 +70,13 @@ export class StringContainer extends XmlComponent { this.root.push(val); } } + +export class BuilderElement extends XmlComponent { + public constructor(options: { readonly attributes?: AttributePayload; readonly name: string }) { + super(options.name); + + if (options.attributes) { + this.root.push(new NextAttributeComponent(options.attributes)); + } + } +} From 3282f762dfd3c472fb84173c644bc4d90740afbb Mon Sep 17 00:00:00 2001 From: Dolan Date: Tue, 20 Dec 2022 21:42:26 +0000 Subject: [PATCH 3/3] Add language support --- demo/83-setting-languages.ts | 69 +++++++++++++++++++ src/file/paragraph/run/language.spec.ts | 29 ++++++++ src/file/paragraph/run/language.ts | 35 ++++++++++ src/file/paragraph/run/properties.ts | 37 +--------- src/file/xml-components/default-attributes.ts | 20 ++---- src/file/xml-components/simple-elements.ts | 6 +- 6 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 demo/83-setting-languages.ts create mode 100644 src/file/paragraph/run/language.spec.ts create mode 100644 src/file/paragraph/run/language.ts diff --git a/demo/83-setting-languages.ts b/demo/83-setting-languages.ts new file mode 100644 index 0000000000..f21d4805da --- /dev/null +++ b/demo/83-setting-languages.ts @@ -0,0 +1,69 @@ +// Simple example to add text to a document +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph } from "../build"; + +const doc = new Document({ + styles: { + default: { + document: { + run: { + color: "ff0000", + language: { + value: "es-ES", + }, + }, + }, + }, + paragraphStyles: [ + { + id: "frenchNormal", + name: "French Normal", + basedOn: "Normal", + next: "Normal", + run: { + color: "999999", + italics: true, + language: { + value: "fr-FR", + }, + }, + }, + { + id: "koreanNormal", + name: "Korean Normal", + basedOn: "Normal", + next: "Normal", + run: { + color: "0000ff", + bold: true, + language: { + value: "ko-KR", + }, + }, + }, + ], + }, + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + text: "Yo vivo en Granada, una ciudad pequeña que tiene monumentos muy importantes como la Alhambra. Aquí la comida es deliciosa y son famosos el gazpacho, el rebujito y el salmorejo.", + }), + new Paragraph({ + text: "Toute personne a droit à l'éducation. L'éducation doit être gratuite, au moins en ce qui concerne l'enseignement élémentaire et fondamental. L'enseignement élémentaire est obligatoire. L'enseignement technique et professionnel doit être généralisé; l'accès aux études supérieures doit être ouvert en pleine égalité à tous en fonction de leur mérite.", + style: "frenchNormal", + }), + new Paragraph({ + text: "대법관은 대법원장의 제청으로 국회의 동의를 얻어 대통령이 임명한다. 강화조약. 국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다, 국가는 청원에 대하여 심사할 의무를 진다.", + style: "koreanNormal", + }), + ], + }, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/src/file/paragraph/run/language.spec.ts b/src/file/paragraph/run/language.spec.ts new file mode 100644 index 0000000000..e742c6092c --- /dev/null +++ b/src/file/paragraph/run/language.spec.ts @@ -0,0 +1,29 @@ +import { expect } from "chai"; + +import { Formatter } from "@export/formatter"; + +import { createLanguageComponent } from "./language"; + +describe("Language", () => { + describe("#createLanguageComponent", () => { + it("should create a language component", () => { + const tree = new Formatter().format( + createLanguageComponent({ + value: "en-US", + eastAsia: "zh-CN", + bidirectional: "ar-SA", + }), + ); + + expect(tree).to.deep.equal({ + "w:lang": { + _attr: { + "w:bidi": "ar-SA", + "w:eastAsia": "zh-CN", + "w:val": "en-US", + }, + }, + }); + }); + }); +}); diff --git a/src/file/paragraph/run/language.ts b/src/file/paragraph/run/language.ts new file mode 100644 index 0000000000..b0978b38bc --- /dev/null +++ b/src/file/paragraph/run/language.ts @@ -0,0 +1,35 @@ +import { BuilderElement, XmlComponent } from "@file/xml-components"; + +// +// +// +// +// +export interface ILanguageOptions { + readonly value?: string; + readonly eastAsia?: string; + readonly bidirectional?: string; +} + +export const createLanguageComponent = (options: ILanguageOptions): XmlComponent => + new BuilderElement<{ + readonly value?: string; + readonly eastAsia?: string; + readonly bidirectional?: string; + }>({ + name: "w:lang", + attributes: { + value: { + key: "w:val", + value: options.value, + }, + eastAsia: { + key: "w:eastAsia", + value: options.eastAsia, + }, + bidirectional: { + key: "w:bidi", + value: options.bidirectional, + }, + }, + }); diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts index f5ddc4750b..bb774cbc3d 100644 --- a/src/file/paragraph/run/properties.ts +++ b/src/file/paragraph/run/properties.ts @@ -2,7 +2,6 @@ import { BorderElement, IBorderOptions } from "@file/border"; import { IShadingAttributesProperties, Shading } from "@file/shading"; import { ChangeAttributes, IChangedAttributesProperties } from "@file/track-revision/track-revision"; import { - BuilderElement, HpsMeasureElement, IgnoreIfEmptyXmlComponent, NumberValueElement, @@ -13,6 +12,7 @@ import { import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark"; import { CharacterSpacing, Color, Highlight, HighlightComplexScript } from "./formatting"; +import { createLanguageComponent, ILanguageOptions } from "./language"; import { IFontAttributesProperties, RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; import { Underline, UnderlineType } from "./underline"; @@ -52,16 +52,7 @@ export interface IRunStylePropertiesOptions { readonly emboss?: boolean; readonly imprint?: boolean; readonly revision?: IRunPropertiesChangeOptions; - // - // - // - // - // - readonly language?: { - readonly value?: string; - readonly eastAsia?: string; - readonly bidirectional?: string; - }; + readonly language?: ILanguageOptions; readonly border?: IBorderOptions; readonly vanish?: boolean; readonly specVanish?: boolean; @@ -253,29 +244,7 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { } if (options.language) { - this.push( - new BuilderElement<{ - readonly value: string; - readonly eastAsia: string; - readonly bidirectional: string; - }>({ - name: "w:lang", - attributes: { - value: { - key: "w:val", - value: options.language.value, - }, - eastAsia: { - key: "w:eastAsia", - value: options.language.eastAsia, - }, - bidirectional: { - key: "w:bidi", - value: options.language.bidirectional, - }, - }, - }), - ); + this.push(createLanguageComponent(options.language)); } } diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts index c3fb1b8105..4d1bbab4de 100644 --- a/src/file/xml-components/default-attributes.ts +++ b/src/file/xml-components/default-attributes.ts @@ -1,16 +1,10 @@ import { BaseXmlComponent, IContext } from "./base"; -import { IXmlableObject } from "./xmlable-object"; +import { IXmlableObject, IXmlAttribute } from "./xmlable-object"; export type AttributeMap = { readonly [P in keyof T]: string }; export type AttributeData = { readonly [key: string]: boolean | number | string }; -export type AttributePayload = Record< - keyof T, - { - readonly key: string; - readonly value?: T[keyof T]; - } ->; +export type AttributePayload = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } }; export abstract class XmlAttributeComponent extends BaseXmlComponent { protected readonly xmlKeys?: AttributeMap; @@ -39,13 +33,9 @@ export class NextAttributeComponent extends BaseXmlComp } public prepForXml(_: IContext): IXmlableObject { - const attrs = {}; - Object.values(this.root).forEach(({ key, value }) => { - if (value !== undefined) { - // eslint-disable-next-line functional/immutable-data - attrs[key] = value; - } - }); + const attrs = Object.values<{ readonly key: string; readonly value: string | boolean | number }>(this.root) + .filter(({ value }) => !!value) + .reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {} as IXmlAttribute); return { _attr: attrs }; } } diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts index b049244746..8f4b3f5e90 100644 --- a/src/file/xml-components/simple-elements.ts +++ b/src/file/xml-components/simple-elements.ts @@ -72,7 +72,11 @@ export class StringContainer extends XmlComponent { } export class BuilderElement extends XmlComponent { - public constructor(options: { readonly attributes?: AttributePayload; readonly name: string }) { + public constructor(options: { + readonly name: string; + readonly attributes?: AttributePayload; + readonly children?: readonly XmlComponent[]; + }) { super(options.name); if (options.attributes) {