From bec4c8bf4cc62206ab5d75ef969e0ca4c37d5bd6 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Fri, 21 Aug 2026 22:46:44 +0100 Subject: [PATCH] Enhance EJS formatting functionality with new placeholder handling and text tag collapsing This commit introduces several improvements to the EJS formatting logic in the `templateformatter.js` file. It adds new regex patterns for inline placeholders and text-only tags, enhancing the ability to manage EJS content. The `maskEjsBlocks` function is updated to differentiate between control and inline EJS tags, while the `restoreEjsBlocks` function now handles both comment and inline placeholders. Additionally, a new function, `collapseTextOnlyTags`, is introduced to ensure that text content within tags is properly formatted without unnecessary whitespace. Corresponding tests have been added to verify these enhancements, improving the overall template content formatting capabilities. --- .../__tests__/templateformatter.test.js | 21 ++++++++++ src/templates/templateformatter.js | 39 ++++++++++++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/templates/__tests__/templateformatter.test.js b/src/templates/__tests__/templateformatter.test.js index 5a8b655..8c4c745 100644 --- a/src/templates/__tests__/templateformatter.test.js +++ b/src/templates/__tests__/templateformatter.test.js @@ -59,4 +59,25 @@ describe('formatTemplateContent', () => { expect(result.content).not.toMatch(/\(\s+<%/); expect(result.content).not.toMatch(/%>\s+\)/); }); + + it('should not add a space next to a colon before EJS output', () => { + const input = + 'INV: <%= _reference %>\n '; + const result = formatTemplateContent(input); + + expect(result.content).toBe( + 'INV:<%= _reference %>' + ); + }); + + it('should keep barcode content and the closing tag on one line', () => { + const input = + 'INV:<%= _reference %>'; + const result = formatTemplateContent(input); + + expect(result.content).toBe( + 'INV:<%= _reference %>' + ); + expect(result.content).not.toMatch(/%>\s*\n\s*<\/Barcode>/); + }); }); diff --git a/src/templates/templateformatter.js b/src/templates/templateformatter.js index 6182ff9..055492e 100644 --- a/src/templates/templateformatter.js +++ b/src/templates/templateformatter.js @@ -4,7 +4,9 @@ const { html: beautifyHtml, js: beautifyJs } = beautify; const EJS_BLOCK_REGEX = /<%[\s\S]*?%>/g; const EJS_TAG_REGEX = /^<%([=#-]?)([\s\S]*?)%>/; -const EJS_PLACEHOLDER_REGEX = //g; +const EJS_COMMENT_PLACEHOLDER_REGEX = //g; +const EJS_INLINE_PLACEHOLDER_REGEX = /___EJS_PH_(\d+)___/g; +const TEXT_ONLY_TAG_REGEX = /<([A-Za-z][\w:-]*)(\s[^>]*)?>([^<]*)<\/\1>/g; const HTML_BEAUTIFY_OPTIONS = { indent_size: 2, @@ -47,28 +49,53 @@ function formatEjsTag(tag) { } } +function isControlEjs(tag) { + const match = tag.match(EJS_TAG_REGEX); + if (!match) { + return false; + } + + const [, modifier] = match; + return !modifier; +} + function maskEjsBlocks(content) { const ejsBlocks = []; const masked = content.replace(EJS_BLOCK_REGEX, (match) => { const index = ejsBlocks.length; ejsBlocks.push(match); - return `\n\n`; + if (isControlEjs(match)) { + return `\n\n`; + } + return `___EJS_PH_${index}___`; }); return { masked, ejsBlocks }; } function restoreEjsBlocks(content, ejsBlocks) { - return content.replace(EJS_PLACEHOLDER_REGEX, (_, index) => - formatEjsTag(ejsBlocks[Number(index)]) - ); + const restore = (_, index) => formatEjsTag(ejsBlocks[Number(index)]); + return content + .replace(EJS_COMMENT_PLACEHOLDER_REGEX, restore) + .replace(EJS_INLINE_PLACEHOLDER_REGEX, restore); +} + +function collapseTextOnlyTags(content) { + return content.replace(TEXT_ONLY_TAG_REGEX, (_, tagName, attrs, inner) => { + const collapsedInner = inner.replace(/^\s+|\s+$/g, ''); + return `<${tagName}${attrs || ''}>${collapsedInner}`; + }); } function tightenParenthesesAroundEjs(content) { return content.replace(/\(\s+(<%)/g, '($1').replace(/(%>)\s+\)/g, '$1)'); } +function tightenColonsAroundEjs(content) { + return content.replace(/:\s+(<%)/g, ':$1'); +} + export function formatTemplateContent(content) { if (content == null || typeof content !== 'string') { return { error: 'Content is required and must be a string.', code: 400 }; @@ -81,8 +108,10 @@ export function formatTemplateContent(content) { try { const { masked, ejsBlocks } = maskEjsBlocks(content); let formatted = beautifyHtml(masked, HTML_BEAUTIFY_OPTIONS); + formatted = collapseTextOnlyTags(formatted); formatted = restoreEjsBlocks(formatted, ejsBlocks); formatted = tightenParenthesesAroundEjs(formatted); + formatted = tightenColonsAroundEjs(formatted); return { content: formatted.trimEnd() }; } catch (error) { return { error: error.message || 'Failed to format template content.', code: 400 };