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) {