@codejamboree/replace-tags
A lightweight utility for replacing tags in a text string with values from an object.
Replaces {{tag.paths}}
in templates by default, with many styles available and customizable.
Installation
You can install the package via npm:
npm install @codejamboree/replace-tags
Or using yarn:
yarn add @codejamboree/replace-tags
Or using a CDN (Content Delivery Network)
<script src="https://cdn.jsdelivr.net/npm/@codejamboree/replace-tags@1.2.2/dist/index.min.js"></script>
Usage
const { replaceTags } = require("@codejamboree/replace-tags");
const text = "Hello {{user.name}}, welcome to {{website}}!";
const values = {
user: {
name: "John Doe",
},
website: "example.com",
};
const replacedText = replaceTags(text, values);
console.log(replacedText);
Via CDN
<script src="https://cdn.jsdelivr.net/npm/@codejamboree/replace-tags@1.2.2/dist/index.min.js"></script>
<script>
var replaceTags = window["@codejamboree/replace-tags"].replaceTags;
var text = replaceTags("Hello {{name}}!", { name: "World" });
console.log(text);
</script>
Configuration Interface
A page is available to try out replaceTags by changing various configuration settings and review its performance.
Unresolved Tags
If a tags property path cannot be resolved, the tag remains unchanged.
const { replaceTags } = require("@codejamboree/replace-tags");
console.log(
replaceTags("Hello {{missing.path}}!", { user: "John Doe" }),
);
Usage with Provided Tag Patterns
const {
replaceTags,
PercentSigns,
} = require("@codejamboree/replace-tags");
console.log(
replaceTags(
"Hello %{name}%",
{
name: "John Doe",
},
PercentSigns,
),
);
Usage with Custom Tag Patterns
const { replaceTags } = require("@codejamboree/replace-tags");
const values = {
user: {
name: "John Doe",
},
};
const options = {
tagPattern: /tag_start->.*?<-tag_end/g,
tagStartPattern: /^tag_start->/,
tagEndPattern: /<-tag_end$/,
};
const text = "Hello tag_start-> user.name <-tag_end!";
console.log(replaceTags(text, values, options));
An error will be thrown if the following conditions are not met:
- The
tagPattern
must end with the /g
flag. - The
tagStartPattern
must begin with /^
. - The
tagEndpattern
must end with non-escaped $/
.
Tag Styles
Style | Template |
---|
AngleBrackets | Hello << user.name >> |
AngleBracketsWithPercentSigns | Hello <% user.name %> |
Backticks | Hello `` user.name `` |
Chevrons | See AngleBrackets |
CurlyBraces | Hello { user.name } |
CurlyBracesWithDollarSigns | Hello {$ user.name $} |
CurlyBracesWithExclamationMarks | Hello {! user.name !} |
CurlyBracesWithHashSymbols | Hello {# user.name #} |
DollarSignsWithSquareBrackets | Hello $[ user.name ]$ |
DollarSignWithCurlyBraces | Hello ${ user.name } |
DoubleAngle | Hello « user.name » |
DoubleAtSigns | Hello @@ user.name @@ |
DoubleCaretsWithBraces | Hello ^^{ user.name }^^ |
DoubleColonsWithBraces | Hello ::{ user.name }:: |
DoubleCurlyBraces (default) | Hello {{ user.name }} |
DoubleCurlyBracesWithPercentSign | Hello {{% user.name %}} |
DoubleQuestionMarks | Hello ?? user.name ?? |
DoubleSquareBrackets | Hello [[ user.name ]] |
DoubleSquareBracketsWithDollarSigns | Hello [[$ user.name $]] |
DoubleUnderscores | Hello __ user.name __ |
Dunders | See DoubleUnderscores |
ExclamationMarks | Hello !{ user.name }! |
HandleBars | See CurlyBraces |
HashSymbolsWithCurlyBraces | Hello #{ user.name }# |
HTMLComments | Hello <!-- user.name --!> |
Mustache | See DoubleCurlyBraces |
Parentheses | Hello ( user.name ) |
PercentBrackets | See AngleBracketsWithPercentSigns |
PercentSigns | Hello %{ user.name }% |
Pointy | Hello 👉 user.name 👈 |
SquareBrackets | Hello [ user.name ] |
SquareBracketsWithColons | Hello [: user.name :] |
SquareBracketsWithHyphens | Hello [- user.name -] |
TripleCurlyBraces | Hello {{{ user.name }}} |
VerticalBars | Hello | user.name | |
Property Access Notation
The paths that may appear within tags allows for object traversal along the object to find the properties value in complex data structures. It can navigate through nested objects and nested arrays.
Ideally, paths can have multiple segments delimited by .
. Each segment may have a part at the beginning that doesn't have a [
character, and they can end with multiple sets of brackets [
and ]
before a .
indicating the next segment of the path.
Since each segment and index are evaluted against object keys and arrray indexes, Unconventional paths that wouldn't work accessing properties in JavaScript will still work in the path resolution.
values | path | unconventional |
---|
{"key": "value"} | key | [key] |
["value"] | [0] | 0 |
{"key": ["value"]} | key[0] | key.0 |
{"key": [["value"]]} | key[0][0] | key.0.0 |
{"parent": {"child": "value"}} | parent.child | parent[child] |
{"key": () => "value" } | key | [key] |
{"parent": () => ({"child": "value"})} | parent.child | [parent][child] |
The paths within the tags also allow for whitespace. You can either specify {{key}}
or {{ key }}
for easier readability, but you may not have whitespace within the path such as {{parent. child .name}}
.
API
For detailed API documentation, pleae refer to the documentation.
replaceTags(text: string, values: object, options?: ReplaceTagsOptions): string
Replaces tags in the provided text with values from the values object.
text
: The text string containing tags to replace.values
: An object or JSON object containing values to replace the tags.options
(optional): An object specifying options for tag parsing. Default is DoubleCurlyBraces. It can include the following properties:
tagPattern
(optional): A regular expression pattern to find tags in the text. Default is DoubleCurlyBraces.tagPattern.tagStartPattern
(optional): A regular expression pattern to find the start of a tag. Default is DoubleCurlyBraces.tagStartPattern.tagEndPattern
(optional): A regular expression pattern to find the end of a tag. Default is DoubleCurlyBraces.tagEndPattern.cache
(optional): Flag indicating if values are to be cached for subsequent calls. Default is false.onMissingPath
(optional): A callback to resolve values for paths that found nothing. Default is to retain the tag. (path, tag) => tag
Returns the modified text string with tags replaced.
Unconventional paths
0
The same as [0]
users.0.name
The same as users[0].name
user[name]
The same as users.name
Missing Values
If a value is not found for a given tag, the tag will remain in its original form. This behavior can be overridden to replace it with empty text, throw an error, or other custom logic by setting the onMissingPath
callback. It receives both the path
and tag
that was unable to be resolved. The returned value is used in replacing the tag in the original template.
const { replaceTags } = require("@codejamboree/replace-tags");
const values = {};
const template = "Hello {{ this.tag.is.missing }}!";
const onMissingPath = function (path, tag) {
console.log(path);
console.log(tag);
return "Unknown";
};
const options = { onMissingPath };
console.log(replaceTags(template, values, options));
Object Values
Any value that resolves as an object will result as that object being converted to JSON.
const { replaceTags } = require("@codejamboree/replace-tags");
const values = {
user: {
name: "John Doe",
},
};
const text = "user = {{user}}";
console.log(replaceTags(text, values));
Dynamic Values
The replaceTags
function supports dynamic behavior by allowing functions to be called during property resolution. If a value is found to be a function, it will be called. The value returned is then used to continue resolving the property path.
valueGetter(key: string, currentPath: string, fullPath: string): unknown
const { replaceTags } = require("@codejamboree/replace-tags");
const values = {
user: {
birthYear: 1990,
getDecade: function (key, currentPath, fullPath) {
if (key === "getDecade") {
return Math.floor((this.birthYear % 100) / 10);
} else {
return undefined;
}
},
},
};
const text = "How was the {{user.getDecade}}0's?";
console.log(replaceTags(text, values));
Cached Values
Values along a path are cached for optimization. Even when the path has differing whitespace within the tags. Tag paths used multiple times will resolve to the same value. Using functions as values demonstrates this behavior.
const { replaceTags } = require("@codejamboree/replace-tags");
let count = 0;
const getCount = () => {
console.log("Getting Count");
return ++count;
};
const values = { getCount };
const text = "Count 1: {{getCount}}; Count 2: {{ getCount }}";
console.log(replaceTags(text, values));
Note: Even when the cache
flag is set to false for subsequent calls to replaceTags
, the internal cache is still used during each individual call.
Caching Option
The cache
flag may be set to true to cache resolved lookup values betweeen subsequent calls. This is optimal when reusing the same values
between each call.
const {
replaceTags,
DoubleAngle,
} = require("@codejamboree/replace-tags");
const original = { name: "John Doe" };
const newValues = { name: "Jane Smith" };
console.log(replaceTags("{{name}}", original));
console.log(replaceTags("{{name}}", newValues, { cache: true }));
console.log(replaceTags("{{name}}", original, { cache: true }));
console.log(
replaceTags("«name»", original, {
...DoubleAngle,
cache: true,
}),
);
console.log(replaceTags("{{name}}", original));
Passing Values as JSON
You may pass a JSON string as the values to be evaluated.
const { replaceTags } = require("@codejamboree/replace-tags");
const text = "Hello {{user.name}}!";
const values = `{"user": {"name": "John Doe"}}`;
const replacedText = replaceTags(text, values);
console.log(replacedText);
Find Value By Path
A method is also provided for you to retrieve the resolved path values.
findValueByPath(source: object, path: string): unknown
const { findValueByPath } = require("@codejamboree/replace-tags");
console.log(
findValueByPath({ user: { name: "John Doe" } }, "user.name"),
);
Caching
Subsequent calls to findValueByPath
will return cached values. You may use clearCache
to clear the cache.
const {
findValueByPath,
clearCache,
} = require("@codejamboree/replace-tags");
const original = { name: "John Doe" };
const newValues = { name: "Jane Smith" };
console.log(findValueByPath(original, "name"));
console.log(findValueByPath(newValues, "name"));
clearCache();
console.log(findValueByPath(newValues, "name"));
Known Issues
Paired Delimiters
Tag Styles that have a paried tag with the same opening and closing tags (ie __
in __dunder__
) are sometimes unable to parse tags directly following one after another. (ie __key1____key2__
) It is recomended to have some whitespace between these tags. (ie __key1__ __key2__
).
const {
replaceTags,
DoubleUnderscores,
} = require("@codejamboree/replace-tags");
const values = {
key1: "## 1 ##",
key2: "-- 2 --",
};
console.log(
replaceTags("__key1____key2__", values, DoubleUnderscores),
);
console.log(
replaceTags("__key1__ __key2__", values, DoubleUnderscores),
);
Changelog
For the latest changes, updates, and improvements to this project, please refer to the Changelog.
License
Copyright (c) 2024 Code Jamboree LLC
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
Portions of the source code were generated with the assistance of