window.ENTITIES={'/api/snippets/javascript/javascript%20-%20custom%20simple%20template%20engine':[{"result":true,"message":null,"batch":{"type":"javascript","name":"javascript - custom simple template engine","items":[{"id":"1RyNRj","type":"javascript","name":"JavaScript - custom simple template engine","content":"// ONLINE-RUNNER:browser;\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-name-builder-allowed-a-z-A-Z-and-0-9-characters-DnYvq1\n//\nconst PATTERNS_GUARDIAN = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']);\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-name-builder-allowed-a-z-A-Z-and-0-9-characters-DnYvq1\n//\nfunction NameBuilder() {\n let _text = '';\n this.append = (character) => {\n if (PATTERNS_GUARDIAN.has(character)) {\n _text += character;\n } else {\n throw new Error('Name syntax error.');\n }\n };\n this.extract = () => {\n const result = _text;\n if (result === '') {\n throw new Error('Name syntax error.');\n }\n _text = '';\n return result;\n };\n}\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-path-builder-allowed-a-z-A-Z-and-0-9-characters-in-names-and-dot-as-separator-p2KV8j\n//\nfunction PathBuilder() {\n const _builder = new NameBuilder();\n let _parts = new Array();\n this.append = (character) => {\n _builder.append(character);\n };\n this.separate = () => {\n _parts.push(_builder.extract());\n };\n this.extract = () => {\n _parts.push(_builder.extract());\n return _parts.splice(0);\n };\n}\n\nfunction TextTemplate(_template) {\n if (_template == null) { // null or undefined\n throw new Error('Template is not provided.');\n }\n const _root = {\n parent: null,\n levels: new Array()\n };\n let _level = _root;\n let _index = 0;\n let _part = '';\n const _readName = (limiter) => {\n const builder = new NameBuilder();\n while (true) {\n if (_index < _template.length) {\n const entry = _template[_index++];\n try {\n switch (entry) {\n case limiter:\n return builder.extract();\n default:\n builder.append(entry);\n break;\n }\n } catch (e) {\n throw new Error(`Name syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n };\n const _readPath = (limiter) => {\n const builder = new PathBuilder();\n while (true) {\n if (_index < _template.length) {\n const entry = _template[_index++];\n try {\n switch (entry) {\n case '.':\n builder.separate();\n break;\n case limiter:\n return builder.extract();\n default:\n builder.append(entry);\n break;\n }\n } catch (e) {\n throw new Error(`Path syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n };\n const _findValue = (level, proxy) => {\n const path = level.path;\n switch (path.length) {\n case 1:\n level = level.parent;\n if (level) {\n const mixed = path[0];\n while (true) {\n if (mixed === level.base) {\n const data = proxy.data;\n return data == null ? null : data; // null or undefined // OR: value ?? null\n }\n level = level.parent;\n if (level == null) { // null or undefined\n break;\n }\n proxy = proxy.parent;\n }\n const value = proxy.data[mixed];\n return value == null ? null : value; // null or undefined // OR: value ?? null\n }\n return null;\n case 2:\n level = level.parent;\n if (level) {\n const major = path[0];\n const minor = path[1];\n while (true) {\n if (major === level.base) {\n const value = proxy.data[minor];\n return value == null ? null : value; // null or undefined // OR: value ?? null\n }\n level = level.parent;\n if (level == null) { // null or undefined\n break;\n }\n proxy = proxy.parent;\n }\n }\n return null;\n default:\n throw new Error('Template rendering error.');\n }\n };\n while (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '{':\n const levels = _level.levels;\n levels.push({\n type: 1,\n text: _part\n });\n _part = '';\n if (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '=':\n levels.push({\n type: 2,\n parent: _level,\n path: _readPath('}')\n });\n break;\n case '#':\n _level = {\n type: 3,\n parent: _level,\n path: _readPath(':'),\n base: _readName('}'),\n levels: new Array()\n };\n levels.push(_level);\n break;\n case '/':\n if (_index < _template.length) {\n const entry = _template[_index++];\n if (entry !== '}') {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n _level = _level.parent;\n if (_level == null) { // null or undefined\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n break;\n default:\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n break;\n case '}':\n throw new Error(`Template syntax error at position ${_index}.`);\n case '\\\\':\n if (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '{':\n _part += '{';\n break;\n case '}':\n _part += '}';\n break;\n case '\\\\':\n _part += '\\\\';\n break;\n default:\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n break;\n default:\n _part += entry;\n break;\n }\n }\n if (_root !== _level){\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n _level.levels.push({\n type: 1,\n text: _part\n });\n this.render = (...data) => {\n let result = '';\n const process = (level, proxy) => {\n for (const entry1 of level.levels) {\n switch (entry1.type) {\n case 1:\n result += entry1.text;\n break;\n case 2:\n result += _findValue(entry1, proxy);\n break;\n case 3:\n const value = _findValue(entry1, proxy);\n if (value) {\n for (const entry2 of value) {\n const wrapper = {\n parent: proxy,\n data: entry2\n };\n process(entry1, wrapper);\n }\n }\n break;\n default:\n throw new Error('Template rendering error.');\n }\n }\n };\n for (const entry of data) {\n const wrapper = {\n parent: null,\n data: entry\n };\n process(_root, wrapper);\n }\n return result;\n };\n}\n\n\n\n// Usage example:\n\nconst template = new TextTemplate(`\n

Hi {=name}!

\n

\n Your items are:\n

\n

\n`);\n\nconst result = template.render({\n name: 'John',\n items: [\n {name: 'Book', count: 2},\n {name: 'Pencil', count: 4},\n {name: 'Marker', count: 1}\n ]\n});\n\nconsole.log(result);","source":"","author":{"id":"b0Z2y0","name":"a_horse","avatar":"1629125843498__b0Z2y0__w40px_h40px.jpg","points":538,"role":"BASIC"},"creationTime":1776077551000,"updateTime":1777066856000,"removalTime":null},{"id":"DW03xp","type":"javascript","name":"JavaScript - custom simple template engine","content":"// ONLINE-RUNNER:browser;\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-name-builder-allowed-a-z-A-Z-and-0-9-characters-DnYvq1\n//\nconst PATTERNS_GUARDIAN = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']);\n\n// Source: https://dirask.com/snippets/JavaScript-check-if-value-is-collection-Array-Set-Map-etc-jQA7rj\n//\nconst isCollectionValue = (value) => {\n if (value == null) { // null or undefined\n return false;\n }\n return !!value[Symbol.iterator];\n};\n\n// Checks if value can not be used.\n//\nconst isNegativeValue = (value) => {\n if (value == null) { // null or undefined\n return true;\n }\n if (value.length === 0 || value.size === 0) {\n return true;\n }\n return false;\n};\n\n// Checks if value can be used.\n//\nconst isPositiveValue = (value) => {\n if (value == null) { // null or undefined\n return false;\n }\n if (value.length === 0 || value.size === 0) {\n return false;\n }\n return true;\n};\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-name-builder-allowed-a-z-A-Z-and-0-9-characters-DnYvq1\n//\nfunction NameBuilder() {\n let _text = '';\n this.append = (character) => {\n if (PATTERNS_GUARDIAN.has(character)) {\n _text += character;\n } else {\n throw new Error('Name syntax error.');\n }\n };\n this.extract = () => {\n const result = _text;\n if (result === '') {\n throw new Error('Name syntax error.');\n }\n _text = '';\n return result;\n };\n}\n\n// Based on: https://dirask.com/snippets/JavaScript-custom-path-builder-allowed-a-z-A-Z-and-0-9-characters-in-names-and-dot-as-separator-p2KV8j\n//\nfunction PathBuilder() {\n const _builder = new NameBuilder();\n let _parts = new Array();\n this.append = (character) => {\n _builder.append(character);\n };\n this.separate = () => {\n _parts.push(_builder.extract());\n };\n this.extract = () => {\n _parts.push(_builder.extract());\n return _parts.splice(0);\n };\n}\n\nfunction TextTemplate(_config) {\n if (_config == null) { // null or undefined\n throw new Error('Configuration is not provided.');\n }\n const _template = _config.template;\n const _filters = _config.filters;\n const _negativeChecker = _config.negativeChecker || isNegativeValue; // OR: _config.negativeChecker ?? isNegativeValue\n const _positiveChecker = _config.positiveChecker || isPositiveValue; // OR: _config.positiveChecker || isPositiveValue\n if (_template == null) { // null or undefined\n throw new Error('Template is not provided.');\n }\n if (_filters == null) { // null or undefined\n throw new Error('Filters are not provided.');\n }\n const _root = {\n parent: null,\n levels: new Array()\n };\n let _level = _root;\n let _index = 0;\n let _part = '';\n const _readName = (limiter) => {\n const builder = new NameBuilder();\n while (true) {\n if (_index < _template.length) {\n const entry = _template[_index++];\n try {\n switch (entry) {\n case limiter:\n return builder.extract();\n default:\n builder.append(entry);\n break;\n }\n } catch (e) {\n throw new Error(`Name syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n };\n const _readPath = (limiter) => {\n const builder = new PathBuilder();\n while (true) {\n if (_index < _template.length) {\n const entry = _template[_index++];\n try {\n switch (entry) {\n case '.':\n builder.separate();\n break;\n case limiter:\n return builder.extract();\n default:\n builder.append(entry);\n break;\n }\n } catch (e) {\n throw new Error(`Path syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n };\n const _readPipe = (limiter) => {\n const builder = new PathBuilder();\n while (true) {\n if (_index < _template.length) {\n const entry = _template[_index++];\n try {\n switch (entry) {\n case '.':\n builder.separate();\n break;\n case '|':\n const filter = _readName(limiter);\n return {\n filter,\n path: builder.extract()\n };\n case limiter:\n return {\n path: builder.extract()\n };\n default:\n builder.append(entry);\n break;\n }\n } catch (e) {\n throw new Error(`Pipe syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n };\n const _findValue = (level, proxy) => {\n const path = level.path;\n switch (path.length) {\n case 1:\n level = level.parent;\n if (level) {\n const mixed = path[0];\n while (true) {\n if (mixed === level.base) {\n const data = proxy.data;\n return data == null ? null : data; // null or undefined // OR: value ?? null\n }\n level = level.parent;\n if (level == null) { // null or undefined\n break;\n }\n proxy = proxy.parent;\n }\n const value = proxy.data[mixed];\n return value == null ? null : value; // null or undefined // OR: value ?? null\n }\n return null;\n case 2:\n level = level.parent;\n if (level) {\n const major = path[0];\n const minor = path[1];\n while (true) {\n if (major === level.base) {\n const value = proxy.data[minor];\n return value == null ? null : value; // null or undefined // OR: value ?? null\n }\n level = level.parent;\n if (level == null) { // null or undefined\n break;\n }\n proxy = proxy.parent;\n }\n }\n return null;\n default:\n throw new Error('Template rendering error.');\n }\n };\n while (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '{':\n const levels = _level.levels;\n levels.push({\n type: 1,\n text: _part\n });\n _part = '';\n if (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '=':\n {\n const pipe = _readPipe('}');\n levels.push({\n ...pipe,\n type: 2,\n parent: _level\n });\n }\n break;\n case '+':\n {\n const path = _readPath('}');\n _level = {\n path,\n type: 3,\n parent: _level,\n levels: new Array()\n };\n levels.push(_level);\n }\n break;\n case '-':\n {\n const path = _readPath('}');\n _level = {\n path,\n type: 4,\n parent: _level,\n levels: new Array()\n };\n levels.push(_level);\n }\n break;\n case '#':\n {\n const path = _readPath(':');\n const base = _readName('}');\n _level = {\n path,\n base,\n type: 5,\n parent: _level,\n levels: new Array()\n };\n levels.push(_level);\n }\n break;\n case '/':\n {\n if (_index < _template.length) {\n const entry = _template[_index++];\n if (entry !== '}') {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n _level = _level.parent;\n if (_level == null) { // null or undefined\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n }\n break;\n default:\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n break;\n case '}':\n throw new Error(`Template syntax error at position ${_index}.`);\n case '\\\\':\n if (_index < _template.length) {\n const entry = _template[_index++];\n switch (entry) {\n case '{':\n _part += '{';\n break;\n case '}':\n _part += '}';\n break;\n case '\\\\':\n _part += '\\\\';\n break;\n default:\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n } else {\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n break;\n default:\n _part += entry;\n break;\n }\n }\n if (_root !== _level){\n throw new Error(`Template syntax error at position ${_index}.`);\n }\n _level.levels.push({\n type: 1,\n text: _part\n });\n this.render = (...data) => {\n let result = '';\n const process = (level, proxy) => {\n for (const entry of level.levels) {\n switch (entry.type) {\n case 1:\n result += entry.text;\n break;\n case 2:\n {\n const filter = entry.filter;\n if (filter == null) { // null or undefined\n result += _findValue(entry, proxy);\n } else {\n const action = _filters[filter];\n if (action == null) { // null or undefined\n throw new Error(`'${filter}' filter is not provided.`);\n }\n const value = _findValue(entry, proxy);\n result += action.call(null, value);\n }\n }\n break;\n case 3:\n {\n const value = _findValue(entry, proxy);\n if (_positiveChecker.call(null, value)) {\n const wrapper = {\n parent: proxy\n };\n process.call(null, entry, wrapper);\n }\n }\n break;\n case 4:\n {\n const value = _findValue(entry, proxy);\n if (_negativeChecker.call(null, value)) {\n const wrapper = {\n parent: proxy\n };\n process.call(null, entry, wrapper);\n }\n }\n break;\n case 5:\n {\n const value = _findValue(entry, proxy);\n if (isCollectionValue(value)) {\n for (const data of value) {\n const wrapper = {\n data,\n parent: proxy\n };\n process.call(null, entry, wrapper);\n }\n }\n }\n break;\n default:\n throw new Error('Template rendering error.');\n }\n }\n };\n for (const entry of data) {\n const wrapper = {\n data: entry\n };\n process.call(null, _root, wrapper);\n }\n return result;\n };\n}\n\n\n\n// Usage example:\n\nconst hCase1 = document.querySelector('#case-1');\nconst hCase2 = document.querySelector('#case-2');\n\nconst template = new TextTemplate({\n template: `\n

Hi {=name|upper}!

\n

\n {+items}\n Your items are:\n

\n {/}\n {-items}\n You don't have items!\n {/}\n

\n `,\n filters: {\n upper: (value) => {\n return value.toUpperCase();\n },\n lower: (value) => {\n return value.toLowerCase();\n }\n }\n});\n\nconst result1 = template.render({\n name: 'John',\n items: [\n {name: 'Book', count: 2},\n {name: 'Pencil', count: 4},\n {name: 'Marker', count: 1}\n ]\n});\nconst result2 = template.render({\n name: 'Kate',\n});\n\nconsole.log(result1);\nconsole.log(result2);","source":"","author":{"id":"jDX58a","name":"christa","avatar":"1629130298989__jDX58a__w40px_h40px.jpg","points":600,"role":"BASIC"},"creationTime":1776901568000,"updateTime":1777233923000,"removalTime":null}]}}]};