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 19593b62dd..bb774cbc3d 100644 --- a/src/file/paragraph/run/properties.ts +++ b/src/file/paragraph/run/properties.ts @@ -12,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"; @@ -51,6 +52,7 @@ export interface IRunStylePropertiesOptions { readonly emboss?: boolean; readonly imprint?: boolean; readonly revision?: IRunPropertiesChangeOptions; + readonly language?: ILanguageOptions; readonly border?: IBorderOptions; readonly vanish?: boolean; readonly specVanish?: boolean; @@ -240,6 +242,10 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { if (options.scale !== undefined) { this.push(new NumberValueElement("w:w", options.scale)); } + + if (options.language) { + this.push(createLanguageComponent(options.language)); + } } 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/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; } diff --git a/src/file/xml-components/default-attributes.ts b/src/file/xml-components/default-attributes.ts index bf84e95eef..4d1bbab4de 100644 --- a/src/file/xml-components/default-attributes.ts +++ b/src/file/xml-components/default-attributes.ts @@ -1,16 +1,16 @@ 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 = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } }; + 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 +26,16 @@ 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<{ 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 aca3355c28..8f4b3f5e90 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,17 @@ export class StringContainer extends XmlComponent { this.root.push(val); } } + +export class BuilderElement extends XmlComponent { + public constructor(options: { + readonly name: string; + readonly attributes?: AttributePayload; + readonly children?: readonly XmlComponent[]; + }) { + super(options.name); + + if (options.attributes) { + this.root.push(new NextAttributeComponent(options.attributes)); + } + } +}