What is the best way to find a match for {0} while still allowing for proper

I am working on developing a text templating system that allows for defining placeholders using {0}, similar to the functionality of .Net's string.format method.

Here is an example of what I am aiming for:

      format("{0}", 42), // output `42`
      format("{0} {1}", 42, "bar"), // output `42 bar`
      format("{1} {1}", 42, "bar"), // output `bar bar` ({0} ignored)
      format("{{0", 42), // output `{0` (`{{` is an escaped `{`)
      format("{{{0}", 42), // output `{42` : an escaped brace and the formatted value
      format("Mix {{0}} and {0}", 42), // outputs `Mix {0} and 42`
      format("Invalid closing brace }"), // should fail, since the closing brace does close an opening one
      format("Invalid placeholder {z}"), // should fail, not an integer
      format("{0}", "With { in value"), // output `With { in value`, inner { should be broke the format

I am experimenting with regex and backtracking to handle the escaped braces.

 function format(template: string, ...values: unknown[]): string {
      const regex = /(?!({{)+){(\d+)}(?<!(}}))/gm;
      return template.replace(regex, ([, index]) => {
        const valueIndex = parseInt(index, 10);
        if (valueIndex >= values.length) throw new Error("Not enough arguments")
        return String(values[valueIndex]);

      });
    }

    console.log([
      format("{0}", 42), // output `42`
      format("{0} {1}", 42, "bar"), // output `42 bar`
      format("{1} {1}", 42, "bar"), // output `bar bar` ({0} ignored)
      format("{{0", 42), // output `{0` (`{{` is an escaped `{`)
      format("{{{0}", 42), // output `{42` : an escaped brace and the formatted value
      format("Mix {{0}} and {0}", 42), // outputs `Mix {0} and 42`
      format("Invalid closing brace }"), // should fail, since the closing brace does not close an opening one
      format("Invalid placeholder {z}"), // should fail, not an integer
      format("{0}", "With { in value"), // output `With { in value`, inner { should be broke the format
    ]);

    try {
      format("{0} {1}", 42); // throw error because not enough argument are passed
    } catch (e) {
      console.log(e.message);
    }

Despite my efforts, I am struggling to properly replace the escaped braces with a single brace.

How can I resolve this issue?

Answer №1

In my recommendation, it is best to replace the double braces within the same replace call. To handle any potential "syntax" errors, it would be wise to throw a syntax error if only a single brace is left over after replacing pairs and cannot be combined with a placeholder:

function modifyString(text, ...values) {
  const regex = /{(\d+)}|([{}])(\2?)/g;
  return text.replace(regex, (_, index, brace, escaped) => {
    if (escaped) return brace;
    if (brace) throw new Error("Unescaped literal brace");
    if (+index >= values.length) throw new Error("Insufficient arguments provided");
    return values[+index];
  });
}


const testCases = [
  ["{0}", 42], // outcome: `42`
  ["{0} {1}", 42, "bar"], // outcome: `42 bar`
  ["{1} {1}", 42, "bar"], // outcome: `bar bar` ({0} is ignored)
  ["{{0", 42], // outcome: `{0` (`{{` is an escaped `{`)
  ["{{{0}", 42], // outcome: `{42` : escaped brace and the formatted value
  ["Mix {{0}} and {0}", 42], // outcome: `Mix {0} and 42`
  ["Invalid closing brace }"], // should fail, as the closing brace does not match an opening one
  ["Invalid placeholder {z}"], // should fail, as it is not an integer
  ["{0}", "With { in value"], // outcome: `With { in value`, inner { should break the format
  ["{0} {1}", 42], // should throw an error due to insufficient arguments
];

for (const [test, ...args] of testCases) {
  try {
    console.log(modifyString(test, ...args));
  } catch (e) {
    console.log("Error:", e.message);
  }
}

There is no necessity for look-around assertions since there is no risk of these paired braces getting chunked up incorrectly during backtracking.

The \2 backreference indicates that a double of the brace was matched, signifying an escaped brace ({ or }). The ? is greedy, but if there is no duplicate brace, we capture a single brace that violates the syntax.

Answer №2

To utilize this regex pattern:

(?<=(?<!\{)(?:\{\{)*)\{\d+}(?!(?:}})*}(?!}))

Check out the regex demonstration.

Key Points:

  • (?<=(?<!\{)(?:\{\{)*)\{ - identifies a non-escaped { character (no { followed by zero or more double { characters)
  • \d+
  • }(?!(?:}})*}(?!})) - pinpoints a non-escaped } character (no } preceded by zero or more double } characters)

The section

.replaceAll('{{','{').replaceAll('}}','}')
completes the transformation process.

Examine the JS demonstration:

function format(template, ...values) {
  const regex = /(?<=(?<!\{)(?:\{\{)*)\{\d+}(?!(?:}})*}(?!}))/g;
  return template.replace(regex, ([, index]) => {
    const valueIndex = parseInt(index, 10);
    if (valueIndex >= values.length) throw new Error("Not enough arguments")
    return String(values[valueIndex]).replaceAll('{{','{').replaceAll('}}','}');

  });
}

console.log([
  format("{0}", 42), // output `42`
  format("{0} {1}", 42, "bar"), // output `42 bar`
  format("{1} {1}", 42, "bar"), // output `bar bar` ({0} ignored)
  format("{{0", 42), // output `{0` (`{{` is an escaped `{`)
  format("{{{0}", 42), // output `{42` : an escaped brace and the formatted value
  format("Mix {{0}} and {0}", 42), // outputs `Mix {0} and 42`
]);

try {
  format("{0} {1}", 42); // throws an error because not enough arguments are provided
} catch (e) {
  console.error(e);
}

IMPORTANT: Make sure to "prep" the passed values by escaping the braces, i.e. use

value.replaceAll('{','{{').replaceAll('}','}}')
.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieve variables from an external JavaScript file

I need to fetch the currentID variable from renderermain.js and utilize it in renderermem.js. However, I am looking for a way to achieve this without modifying mem.html: <script src="renderermain.js"></script> <script src="renderermem.js"& ...

Creating a conditional class property requirement in TypeScriptAnother way to implement conditionally required class

I am currently in the process of creating an Event class with a data property that is typed based on the type of the event. Here is the code snippet I have been working on: export interface EventTypes { localeChange: { locale: string; }; translat ...

Issue with locating JavaScript controller in Angular HTML file [Node.js/Angular]

Here are all the files and folders in my project located within the same directory. If you want to check out the project on Github, you can find it here: https://github.com/JohnsCurry/learnAngular. Just to confirm, they are all in the same folder. Unfortu ...

Pressing "Enter" initiates the submission of the form

My webpage contains a stylish GIF displayed as a button within a div. The script inside the div triggers a click event when the ENTER key is pressed, using $(this).click(). This click action has its own functionality. Everything functions smoothly in Fire ...

Can you explain the distinction between using src/**/* and 'src/**/*'?

Starting my project with nodemon is my goal. "scripts": { "start": "tsc && node build/index.js", "watch-server1": "nodemon --watch src/**/* -e ts,tsx --exec ts-node ./src/index.ts", "watc ...

By default, the HTML table will highlight the specific column based on the current month using either AngularJS or JavaScript upon loading

I am working with a table of 10 products and their monthly sales data. Using Angular JS, I am looking to highlight the entire column based on the current month and year. Additionally, we will also be incorporating future expected sales data into the table. ...

Exploring point clouds with three.js and NSF OpenTopography data: What's the best way to configure the camera and implement a controls library for seamless data navigation?

Currently, I am engaged in a small-scale project aimed at showcasing NSF OpenTopography data through a point cloud visualization using three.js. While I have successfully generated the point cloud, I am encountering significant challenges with setting up t ...

difficulty with parsing JSON in jQuery when referencing an external file containing the same data

Looking for help with parsing a json file using jquery? Check out this working sample: http://jsfiddle.net/bw85zeea/ I'm encountering an issue when trying to load "data2" from an external file. The browser keeps complaining that it's an invalid ...

In the realm of JavaScript, removing a DOM element can sometimes feel like an

For my project, I am using JavaScript, DOM, and AJAX to create pages. I am trying to figure out how to check if an element already exists and, if it does, remove it. This is what I have tried: var elementL = document.getElementById('divLogin'); ...

Instructions for downloading a file using Front End technology in Java and HTML

I am looking to initiate a file download upon clicking a button on the front-end interface. Front End <td><a name="${flow.name}" data-toggle="tooltip" title="Report" class="generateReport"><span class="far fa-file-excel"></span>&l ...

Transform the Standard class into a generic one in typescript

I've created a class that can take JSON objects and transform them into the desired class. Here's the code: import {plainToClass} from "class-transformer"; import UserDto from "../../auth/dto/user.dto"; class JsonConverter { ...

Use JavaScript to switch the h1 title when selecting an option from the dropdown menu

As a beginner in coding, I am struggling to find a solution using an if statement for this particular problem. Although I can achieve the desired result with HTML code and options, this time I need to use arrays and an if function. My goal is to create a ...

PHP Ajax login form to instantly authenticate without redirecting to a new page

After attempting to implement Ajax in my login form by following two different tutorials, I am still facing the issue of error messages redirecting to another page instead of displaying on the same page. I have tried to troubleshoot and compare my code wit ...

having difficulty placing 3 pop-up windows on a single page

Struggling to implement multiple popups on my page, each with a unique ID assigned. Any assistance would be greatly appreciated. Here is the code snippet: .fa { font-size: 50px; cursor: pointer; user-select: none; } .fa:hover { font-size:20px; t ...

The ideal version of firebase for showing messages in the foreground

Looking to instantly display notifications as soon as they are received? I recently watched a video on Angular 8 + Firebase where version 7.6.0 was discussed. While the notification is displayed in the foreground for me, I find that I need to click on some ...

Tips for arranging datasets in React Chart.js 2 to create stacked bar charts

Currently, I am facing an issue with sorting the datasets displayed in each stacked bar chart in descending order as they are showing up randomly. Here is the snippet of my code: import React from 'react'; import { Chart as ChartJS, CategoryS ...

Discord bot struggling to reach every user in the server

After creating a discord bot with Discord.js, I was able to retrieve all the users from my server. However, lately I noticed that it is only returning 59 members out of the 300+ in total. I want to ensure that I am able to access all the users as before. ...

Error occurs during server to server mutation with Apollo Client (query successful)

Currently, I am using apollo-client on my web server to interact with my graphql server (which is also apollo). While I have successfully implemented a query that retrieves data correctly, I encounter new ApolloError messages when attempting a mutation. Od ...

Can PHP be used to create a new page whenever Javascript history.go(-1) is triggered?

A PHP file (a.php) is currently sending the following headers: <?php header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Pragma: no-cache'); ? ...