Text Replacer v3

Powerful string template processor with variables, control structures, and transformation pipes

text-replacertemplatesvariablespipes

Text Replacer v3 Documentation

The Text Replacer v3 is a powerful, extensible string template processor that supports advanced template features including variable replacement, control structures, and transformation pipes.

Table of Contents

  1. Overview
  2. Basic Usage
  3. Template Syntax
  4. Variable Replacement
  5. Control Structures
  6. Transformation Pipes
  7. Advanced Features
  8. Migration from v2
  9. API Reference

Overview

The Text Replacer v3 uses a new template syntax with #{...} delimiters and supports:

  • Variable replacement with nested property access
  • Control structures (if/unless/each loops)
  • Transformation pipes for data formatting
  • Recursive variable resolution
  • Extensible architecture for custom pipes

Key Features

  • πŸ”§ Extensible pipe system - Easy to add custom transformations
  • 🎯 Type-safe - Full TypeScript support
  • πŸš€ Performance optimized - Efficient template processing
  • πŸ”„ Recursive resolution - Handle nested variable references
  • πŸ“Š Rich data handling - Support for objects, arrays, and complex data structures

Basic Usage

import { replaceVariables } from "./utils/text-replacer-v3";

const context = { name: "John", age: 30 };
const template = "Hello #{show name}, you are #{show age} years old!";
const result = replaceVariables(template, context);
console.log(result); // "Hello John, you are 30 years old!"

Shortcut Syntax

For convenience, you can omit the show keyword when accessing variables. This shortcut syntax makes templates more concise and easier to read:

import { replaceVariables } from "./utils/text-replacer-v3";

const context = { name: "John", age: 30 };
// Both syntaxes work identically:
const template1 = "Hello #{show name}, you are #{show age} years old!";
const template2 = "Hello #{name}, you are #{age} years old!"; // Shortcut syntax
const result = replaceVariables(template2, context);
console.log(result); // "Hello John, you are 30 years old!"

Key Points:

  • #{property} is automatically converted to #{show property}
  • Works with all features: nested properties, arrays, pipes, etc.
  • Control structures (if, unless, each) still require explicit keywords
  • You can mix both syntaxes in the same template

Template Syntax

Variable Expressions

Variables can be accessed using either the explicit #{show ...} syntax or the shortcut #{...} syntax:

// Explicit syntax (traditional)
#{show userName}
#{show user.name}
#{show user.profile.email}

// Shortcut syntax (recommended for simplicity)
#{userName}
#{user.name}
#{user.profile.email}

// Both work with nested properties
#{show address.street}
#{address.street}  // Shortcut - cleaner and easier to read

// Both work with array access
#{show users.0.name}
#{users.0.name}  // Shortcut

// Both work with transformation pipes
#{show price | round:2}
#{price | round:2}  // Shortcut

#{show date | format:dd.MM.yyyy}
#{date | format:dd.MM.yyyy}  // Shortcut

Control Structures

Conditional Blocks

Control structures still use explicit keywords, but you can use shortcut syntax for variables inside them:

// If condition - using shortcut syntax for variables
#{if user.isActive}
  Welcome back, #{user.name}!
#{/if}

// Unless condition - mixing both syntaxes is fine
#{unless user.isBlocked}
  You have access to this content, #{show user.email}.
#{/unless}

Loops

// Each loop - using shortcut syntax
#{each items as item}
  - #{item.name}: #{item.price}
#{/each}

// With index access
#{each users as user}
  #{userIndex}: #{user.name}
#{/each}

Variable Replacement

Basic Variables

const context = { greeting: "Hello", name: "World" };

// Using traditional syntax
const template1 = "#{show greeting} #{show name}!";

// Using shortcut syntax (recommended)
const template2 = "#{greeting} #{name}!";

const result = replaceVariables(template2, context);
// Output: "Hello World!"

Nested Properties

The shortcut syntax really shines with nested properties:

const context = {
  user: {
    personal: {
      firstName: "John",
      lastName: "Doe"
    }
  }
};

// Traditional syntax
const template1 = "#{show user.personal.firstName} #{show user.personal.lastName}";

// Shortcut syntax - much cleaner!
const template2 = "#{user.personal.firstName} #{user.personal.lastName}";

const result = replaceVariables(template2, context);
// Output: "John Doe"

Real-World Example: Address Formatting

const context = {
  address: {
    street: "Hauptstraße 123",
    city: "Berlin",
    zip: "10115",
    country: "Germany"
  }
};

// Shortcut syntax makes this very readable
const template = "#{address.street}, #{address.zip} #{address.city}, #{address.country}";
const result = replaceVariables(template, context);
// Output: "Hauptstraße 123, 10115 Berlin, Germany"

Array Access

const context = {
  colors: ["red", "green", "blue"],
  users: [
    { name: "Alice", age: 25 },
    { name: "Bob", age: 30 }
  ]
};

// Shortcut syntax with arrays
const template = "Color: #{colors.1}, User: #{users.0.name}";
const result = replaceVariables(template, context);
// Output: "Color: green, User: Alice"

Recursive Variables

For complex recursive patterns, you may want to use the explicit show keyword:

const context = {
  settings: { theme: "dark", language: "en" },
  currentSetting: "theme",
  prefix: "settings"
};

// For nested recursive variables, explicit 'show' is recommended
const template = "Current theme: #{show #{show prefix}.#{show currentSetting}}";
const result = replaceVariables(template, context);
// Output: "Current theme: dark"

Control Structures

If/Unless Conditions

const context = { user: { isAdmin: true, isActive: false } };

// If condition
const template1 = `
#{if user.isAdmin}
  <div>Admin Panel</div>
#{/if}
`;

// Unless condition
const template2 = `
#{unless user.isActive}
  <div>Account suspended</div>
#{/unless}
`;

Each Loops

const context = {
  products: [
    { name: "Laptop", price: 999 },
    { name: "Mouse", price: 25 },
    { name: "Keyboard", price: 75 }
  ]
};

// Using shortcut syntax inside loops
const template = `
<ul>
#{each products as product}
  <li>#{product.name} - $#{product.price}</li>
#{/each}
</ul>
`;

Nested Control Structures

const context = {
  departments: [
    {
      name: "Engineering",
      employees: [
        { name: "Alice", role: "Developer" },
        { name: "Bob", role: "Designer" }
      ]
    },
    {
      name: "Marketing",
      employees: [
        { name: "Charlie", role: "Manager" }
      ]
    }
  ]
};

// Shortcut syntax works great in nested structures
const template = `
#{each departments as dept}
  <h2>#{dept.name}</h2>
  #{if dept.employees}
    <ul>
    #{each dept.employees as emp}
      <li>#{emp.name} - #{emp.role}</li>
    #{/each}
    </ul>
  #{/if}
#{/each}
`;

Transformation Pipes

Pipes transform data before output. Use the | symbol to apply pipes. The shortcut syntax works seamlessly with pipes:

// Traditional syntax
#{show value | pipeName}
#{show value | pipeName:parameter}
#{show value | pipe1 | pipe2:param}

// Shortcut syntax (recommended)
#{value | pipeName}
#{value | pipeName:parameter}
#{value | pipe1 | pipe2:param}

Text Transformation Pipes

uppercase

Converts text to uppercase.

const context = { name: "john" };
// Using shortcut syntax
const template = "Hello #{name | uppercase}!";
// Output: "Hello JOHN!"

lowercase

Converts text to lowercase.

const context = { name: "JOHN" };
const template = "Hello #{name | lowercase}!";
// Output: "Hello john!"

capitalize

Capitalizes the first letter, lowercases the rest.

const context = { name: "jOHN" };
const template = "Hello #{name | capitalize}!";
// Output: "Hello John!"

Date Formatting Pipes

format

Formats dates using date-fns format strings.

const context = { 
  timestamp: 1734885600000,
  dateString: "2024-12-22"
};

// Default format (yyyy-MM-dd) - shortcut syntax
const template1 = "Date: #{timestamp | format}";
// Output: "Date: 2024-12-22"

// Custom format
const template2 = "Date: #{timestamp | format:dd.MM.yyyy}";
// Output: "Date: 22.12.2024"

// Time format
const template3 = "Time: #{timestamp | format:HH:mm:ss}";
// Output: "Time: 14:30:00"

// Real-world example
const template4 = "Created on #{timestamp | format:dd.MM.yyyy} at #{timestamp | format:HH:mm}";
// Output: "Created on 22.12.2024 at 14:30"

humanizeDuration

Converts milliseconds to human-readable duration.

const context = { duration: 3665000 }; // 1 hour, 1 minute, 5 seconds
const template = "Duration: #{duration | humanizeDuration}";
// Output: "Duration: 1h 1m 5s"

Number Formatting Pipes

round

Rounds numbers to specified decimal places.

const context = { price: 12.456789 };

// Default (0 decimal places)
const template1 = "Price: #{price | round}";
// Output: "Price: 12"

// 2 decimal places - shortcut syntax
const template2 = "Price: #{price | round:2}";
// Output: "Price: 12,46" (German format with comma)

// Real-world example
const context2 = { total: 1234.56 };
const template3 = "Total: €#{total | round:2}";
// Output: "Total: €1234,56"

calc

Performs arithmetic operations.

const context = { 
  price: 100,
  taxRate: 0.19,
  quantity: 3
};

// Multiplication - shortcut syntax
const template1 = "Tax: #{price | calc:multiply:0.19}";
// Output: "Tax: 19"

// Addition
const template2 = "Total: #{show price | calc:add:50}";
// Output: "Total: 150"

// Supported operations: add, subtract, multiply, divide

truncateToRange

Constrains numbers to a specified range.

const context = { 
  score: 150,
  temperature: -5
};

// Constrain to 0-100 range
const template1 = "Score: #{show score | truncateToRange:0:100}";
// Output: "Score: 100"

// Constrain temperature
const template2 = "Temp: #{show temperature | truncateToRange:0:50}";
// Output: "Temp: 0"

Array Processing Pipes

sum

Calculates the sum of array elements.

const context = {
  numbers: [1, 2, 3, 4, 5],
  items: [
    { price: 10.50 },
    { price: 25.00 },
    { price: 15.75 }
  ]
};

// Sum simple array
const template1 = "Total: #{show numbers | sum}";
// Output: "Total: 15"

// Sum with path
const template2 = "Total: #{show items | sum:price}";
// Output: "Total: 51.25"

Data Transformation Pipes

mapping

Maps values from one set to another.

const context = { 
  status: "active",
  priority: "high"
};

// Map status codes to labels
const template1 = "Status: #{show status | mapping:active,inactive,pending:Active,Inactive,Pending}";
// Output: "Status: Active"

// With default value
const template2 = "Priority: #{show priority | mapping:low,medium:Low,Medium:Unknown}";
// Output: "Priority: Unknown"

translate

Translates inline translation objects.

const context = {
  message: {
    en: "Hello",
    de: "Hallo",
    fr: "Bonjour"
  }
};

// Default language (de)
const template1 = "#{show message | translate}";
// Output: "Hallo"

// Specific language
const template2 = "#{show message | translate:en}";
// Output: "Hello"

default

Provides fallback values for null/undefined/empty values.

const context = { 
  name: null,
  title: "",
  description: "Some text"
};

// Shortcut syntax with default pipe
const template = `
Name: #{name | default Anonymous}
Title: #{title | default Untitled}
Description: #{description | default No description}
`;
// Output:
// Name: Anonymous
// Title: Untitled
// Description: Some text

Chaining Pipes

Pipes can be chained for complex transformations. The shortcut syntax keeps this very readable:

const context = { 
  price: 123.456,
  name: "premium product"
};

// Chain multiple pipes - shortcut syntax
const template = `
Product: #{name | capitalize}
Price: $#{price | calc:multiply:1.2 | round:2}
`;
// Output:
// Product: Premium Product
// Price: $148,15

More Chaining Examples

// Complex price calculation with tax
const context = {
  basePrice: 99.99,
  taxRate: 0.19
};

const template = "Final Price: €#{basePrice | calc:add:#{basePrice | calc:multiply:0.19} | round:2}";
// Output: "Final Price: €118,99"

// Text transformation chain
const context2 = { userName: "JOHN DOE" };
const template2 = "Welcome, #{userName | lowercase | capitalize}!";
// Output: "Welcome, John Doe!"

Advanced Features

Shortcut Syntax Best Practices

βœ… DO:

  • Use shortcut syntax for simple variable access: #{user.name}
  • Use shortcut syntax with pipes: #{price | round:2}
  • Mix both syntaxes in the same template if needed
  • Use explicit show for very complex recursive patterns

❌ DON'T:

  • Don't use shortcut syntax for control structures: Use #{if condition}, not #{condition} alone
  • Don't worry about consistency - both syntaxes work identically

Error Handling

The template processor gracefully handles errors with both syntaxes:

const context = { user: { name: "John" } };

// Missing property with default - shortcut syntax
const template1 = "Age: #{user.age | default Unknown}";
// Output: "Age: Unknown"

// Invalid pipe parameters
const template2 = "#{user.name | round:invalid}";
// Output: "John" (pipe ignored, original value returned)

// Note: When using shortcut syntax with removeUnmappedContent: false,
// missing variables will show as #{show property} instead of #{property}
const template3 = "Status: #{user.status}";
// With removeUnmappedContent: false β†’ "Status: #{show user.status}"
// With removeUnmappedContent: true β†’ "Status: "

Performance Optimization

  • Lazy evaluation: Only processes templates when needed
  • Efficient parsing: Optimized regex patterns
  • Shortcut preprocessing: Converts #{...} to #{show ...} once, then processes normally
  • Caching: Pipes cache results where possible
  • Loop protection: Prevents infinite recursion

Configuration Options

const options = {
  removeUnmappedContent: true // Remove variables that can't be resolved
};

const result = replaceVariables(template, context, options);

Migration from v2

Syntax Changes

v2 Syntax v3 Syntax Notes
${variable} #{show variable} New explicit show syntax
#{if condition} #{if condition} Unchanged
#{each items} #{each items as item} Requires 'as' keyword

Pipe Changes

v2 Pipe v3 Pipe Changes
format format:pattern Now supports custom patterns
default:value default value Space-separated instead of colon

Migration Example

// v2 Template
const v2Template = `
Hello ${user.name}!
#{if user.isActive}
  ${user.email}
#{/if}
`;

// v3 Template
const v3Template = `
Hello #{show user.name}!
#{if user.isActive}
  #{show user.email}
#{/if}
`;

API Reference

replaceVariables<T>(content: string, context: T, options?: Options): string

Processes a template string with the given context.

Parameters

  • content: string - The template string to process
  • context: T extends Json - The context object containing data
  • options?: Options - Optional configuration

Options

interface Options {
  removeUnmappedContent?: boolean; // Default: false
}

Returns

string - The processed template string

Example

// Using shortcut syntax
const result = replaceVariables(
  "Hello #{name}!",
  { name: "World" },
  { removeUnmappedContent: true }
);

// Using traditional syntax (also works)
const result2 = replaceVariables(
  "Hello #{show name}!",
  { name: "World" },
  { removeUnmappedContent: true }
);

Built-in Pipes

All pipes work with both traditional #{show property | pipe} and shortcut #{property | pipe} syntax:

Pipe Syntax Description
uppercase | uppercase Convert to uppercase
lowercase | lowercase Convert to lowercase
capitalize | capitalize Capitalize first letter
format | format:pattern Format dates
round | round:decimals Round numbers
calc | calc:operation:value Arithmetic operations
humanizeDuration | humanizeDuration Format durations
truncateToRange | truncateToRange:min:max Constrain to range
mapping | mapping:from:to:default Map values
sum | sum:path? Sum array elements
translate | translate:language? Translate i18n objects
default | default value Fallback values

Error Handling

The template processor handles errors gracefully:

  • Missing variables: Returns empty string or original template (based on options)
  • Invalid pipes: Ignores the pipe and returns original value
  • Malformed expressions: Leaves expression unchanged
  • Infinite recursion: Protected by iteration limits
  • Shortcut syntax: Unmapped variables show as #{show property} when removeUnmappedContent: false

Best Practices

  1. Use shortcut syntax for cleaner, more readable templates: #{user.name} instead of #{show user.name}
  2. Use meaningful variable names in your context objects
  3. Test edge cases with null/undefined values
  4. Chain pipes logically from general to specific transformations
  5. Use default pipes for optional values
  6. Keep templates readable with proper indentation
  7. Validate context data before processing templates
  8. Mix syntaxes freely - both work identically, choose based on readability

Common Patterns

Form Templates

const context = {
  user: { name: "John", email: "john@example.com" },
  errors: { name: null, email: "Invalid email" }
};

// Using shortcut syntax for cleaner forms
const template = `
<div>
  <label>Name: #{user.name}</label>
  #{if errors.name}
    <span class="error">#{errors.name}</span>
  #{/if}
</div>
<div>
  <label>Email: #{user.email}</label>
  #{if errors.email}
    <span class="error">#{errors.email}</span>
  #{/if}
</div>
`;

Email Templates

const context = {
  user: { name: "John", orders: [{ id: 1, total: 99.99 }] },
  company: { name: "ACME Corp" }
};

// Shortcut syntax makes email templates very readable
const template = `
Dear #{user.name | capitalize},

Thank you for your recent orders:
#{each user.orders as order}
- Order ##{order.id}: $#{order.total | round:2}
#{/each}

Best regards,
#{company.name}
`;

Address Formatting Template

const context = {
  customer: {
    name: "Alice Johnson",
    address: {
      street: "Hauptstraße 123",
      city: "Berlin",
      zip: "10115",
      country: "Germany"
    }
  }
};

// Real-world address template with shortcut syntax
const template = `
Shipping Address:
#{customer.name}
#{customer.address.street}
#{customer.address.zip} #{customer.address.city}
#{customer.address.country}
`;
// Output:
// Shipping Address:
// Alice Johnson
// Hauptstraße 123
// 10115 Berlin
// Germany

Report Templates

const context = {
  report: {
    title: "Sales Report",
    period: "2024-Q1",
    data: [
      { month: "Jan", sales: 10000 },
      { month: "Feb", sales: 12000 },
      { month: "Mar", sales: 15000 }
    ]
  }
};

// Shortcut syntax for reports
const template = `
# #{report.title}
Period: #{report.period}

## Monthly Sales
#{each report.data as item}
- #{item.month}: $#{item.sales | round:0}
#{/each}

Total: $#{report.data | sum:sales | round:0}
`;

Invoice Template Example

const context = {
  invoice: {
    number: "INV-2024-001",
    date: 1734885600000,
    customer: {
      name: "ABC Company",
      address: "Main Street 45, 12345 Berlin"
    },
    items: [
      { description: "Consulting Services", quantity: 8, rate: 150 },
      { description: "Software License", quantity: 1, rate: 500 }
    ],
    taxRate: 0.19
  }
};

// Complete invoice template using shortcut syntax
const template = `
INVOICE #{invoice.number}
Date: #{invoice.date | format:dd.MM.yyyy}

Bill To:
#{invoice.customer.name}
#{invoice.customer.address}

Items:
#{each invoice.items as item}
#{item.description}
  Quantity: #{item.quantity} x €#{item.rate} = €#{item.quantity | calc:multiply:#{item.rate}}
#{/each}

Subtotal: €#{invoice.items | sum:rate}
Tax (19%): €#{invoice.items | sum:rate | calc:multiply:0.19 | round:2}
Total: €#{invoice.items | sum:rate | calc:multiply:1.19 | round:2}
`;

This documentation provides a comprehensive guide to using the Text Replacer v3 system effectively with both traditional #{show property} and shortcut #{property} syntax.