diff --git a/docs/language/template.md b/docs/language/template.md index ecec9fa5..86988482 100644 --- a/docs/language/template.md +++ b/docs/language/template.md @@ -49,6 +49,16 @@ POML supports various JavaScript expressions within the double curly brackets. T The `` tag allows you to define variables, import data from external files, and set values within your POML template. +To provide a fallback value that only applies when a variable hasn't been set yet, add `default="true"` to the `` element. When used without a `name`, only properties that are not already defined will be merged into the context. + +```xml + + + +

{{greeting}}

+
+``` + ### Syntax 1: Setting a variable from a value ```xml diff --git a/packages/poml/file.tsx b/packages/poml/file.tsx index e01e4909..b2119905 100644 --- a/packages/poml/file.tsx +++ b/packages/poml/file.tsx @@ -510,10 +510,14 @@ export class PomlFile { const type = xmlAttribute(element, 'type')?.value; const name = xmlAttribute(element, 'name')?.value; const value = xmlAttribute(element, 'value')?.value; + const isDefault = xmlAttribute(element, 'default')?.value?.toLowerCase() === 'true'; // Case 1: , case insensitive // or , case insensitive if (source) { + if (name && isDefault && Object.prototype.hasOwnProperty.call(contextOut, name)) { + return true; + } let content: any; try { content = readSource( @@ -531,14 +535,22 @@ export class PomlFile { } if (!name) { if (content && typeof content === 'object') { - Object.assign(contextOut, content); + if (isDefault) { + for (const [key, value] of Object.entries(content)) { + if (!Object.prototype.hasOwnProperty.call(contextOut, key)) { + contextOut[key] = value; + } + } + } else { + Object.assign(contextOut, content); + } } else { this.reportError( 'name attribute is expected when the source is not an object.', this.xmlElementRange(element), ); } - } else { + } else if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) { contextOut[name] = content; } return true; @@ -553,13 +565,15 @@ export class PomlFile { ); return true; } - const evaluated = this.evaluateExpression( - value, - contextIn, - this.xmlAttributeValueRange(xmlAttribute(element, 'value')!), - true, - ); - contextOut[name] = evaluated; + if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) { + const evaluated = this.evaluateExpression( + value, + contextIn, + this.xmlAttributeValueRange(xmlAttribute(element, 'value')!), + true, + ); + contextOut[name] = evaluated; + } return true; } @@ -582,14 +596,22 @@ export class PomlFile { } if (!name) { if (content && typeof content === 'object') { - Object.assign(contextOut, content); + if (isDefault) { + for (const [key, value] of Object.entries(content)) { + if (!Object.prototype.hasOwnProperty.call(contextOut, key)) { + contextOut[key] = value; + } + } + } else { + Object.assign(contextOut, content); + } } else { this.reportError( 'name attribute is expected when the source is not an object.', this.xmlElementRange(element), ); } - } else { + } else if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) { contextOut[name] = content; } diff --git a/packages/poml/tests/file.test.tsx b/packages/poml/tests/file.test.tsx index dd381b69..168dad94 100644 --- a/packages/poml/tests/file.test.tsx +++ b/packages/poml/tests/file.test.tsx @@ -314,6 +314,27 @@ describe('templateEngine', () => { expect(await poml(text)).toBe('2\n\n4'); expect(ErrorCollection.empty()).toBe(true); }); + + test('letDefaultSetsWhenUnset', async () => { + const text = '

{{greeting}}

'; + expect(await poml(text)).toBe('Hi'); + expect(ErrorCollection.empty()).toBe(true); + }); + + test('letDefaultDoesNotOverride', async () => { + const text = + '' + + '' + + '

{{greeting}}

'; + expect(await poml(text)).toBe('Hello'); + expect(ErrorCollection.empty()).toBe(true); + }); + + test('letDefaultObjectMerge', async () => { + const text = '{ "a": 1 }{ "a": 2, "b": 3 }

{{a}} {{b}}

'; + expect(await poml(text)).toBe('1 3'); + expect(ErrorCollection.empty()).toBe(true); + }); }); describe('expressionEvaluation', () => {