Как сопоставить {0}, но обеспечить правильный выход?

Я хочу создать простой текстовый шаблон, который позволит определять заполнители с помощью {0}, аналогично тому, что .Net делает с помощью метода string.format.

В основном я хочу этого:

      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

Я пытаюсь поиграть с регулярным выражением и выполнить возврат, чтобы справиться со экранированными фигурными скобками.

 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.info([
      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.info(e.message);
    }

Тем не менее, я изо всех сил пытаюсь правильно заменить экранированные скобки одной скобкой.

Как это исправить?

Что должно произойти с {0}}?

trincot 27.06.2024 13:10

это должно потерпеть неудачу. Разрешены только двойные {{ и }}, если они не являются заполнителем.

Steve B 27.06.2024 13:42

Должен ли он потерпеть неудачу в том смысле, что должен вызвать ошибку? Или он должен просто не соответствовать и заменить его? Но тогда замените }} на один...?

trincot 27.06.2024 13:43

должен бросить. В противном случае произойдет утечка неправильных шаблонов.

Steve B 27.06.2024 13:44

Хорошо, тогда, возможно, это следует упомянуть в вопросе с тестовым примером. Все же я считаю такое поведение несбалансированным, ведь на зеркальный корпус не бросишь {{0}. Разве не было бы более элегантно, если бы это было такое же поведение?

trincot 27.06.2024 13:45

Обновите вопрос, чтобы уточнить. {{0} тоже должно потерпеть неудачу

Steve B 27.06.2024 13:49

Хорошо, думаю, я понял ожидаемую логику. Дайте мне знать, если я неправильно это интерпретировал.

trincot 27.06.2024 14:07
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
7
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать

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

Посмотрите демонстрацию регулярных выражений .

Подробности:

  • (?<=(?<!\{)(?:\{\{)*)\{ - символ {, который не экранирован (не должно быть {, за которым следует ноль или более двойных { символов)
  • \d+
  • }(?!(?:}})*}(?!})) - символ }, который не экранирован (не должно быть }, которому предшествует ноль или более двойных } символов)

Часть .replaceAll('{{','{').replaceAll('}}','}') завершает трансформацию.

Посмотрите демо-версию JS:

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.info([
  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); // throw error because not enough argument are passed
} catch (e) {
  console.error(e);
}

ПРИМЕЧАНИЕ. Вам необходимо «подготовить» значения, передаваемые в функции, так, чтобы фигурные скобки были экранированы, т. е. просто используйте value.replaceAll('{','{{').replaceAll('}','}}').

@trincot format("{0} {1}", 42, "b{ar") дает ожидаемый результат 42 b{ar, так какой вариант использования вы упомянули?

Wiktor Stribiżew 27.06.2024 13:45

Я думал о format("{0}", "{{"), где восстановительная стоимость также будет уменьшена вдвое.

trincot 27.06.2024 13:51

@trincot, это реализация, все, что нужно, это отправить их с экранированными фигурными скобками или просто «подготовить» их с помощью .replaceAll('{','{{').replaceAll('}','}}')

Wiktor Stribiżew 27.06.2024 13:58

Обратите внимание, что существует также проблема (исходящая из кода спрашивающего), из-за которой заполнители с двумя или более цифрами не заменяются правильно: format("{12}", 0,1,2,3,4,5,6,7,8,9,10,11,12).

trincot 27.06.2024 16:02
Ответ принят как подходящий

Я бы предложил заменить двойные скобки в том же вызове replace. Для обнаружения «синтаксических» ошибок я бы предложил, чтобы, когда в качестве остатка обнаруживается только одна скобка (после потенциальной замены пар из них), которая не может сочетаться с заполнителем, выдавать ее как синтаксическую ошибку:

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


const tests = [
  ["{0}", 42], // output `42`
  ["{0} {1}", 42, "bar"], // output `42 bar`
  ["{1} {1}", 42, "bar"], // output `bar bar` ({0} ignored)
  ["{{0", 42], // output `{0` (`{{` is an escaped `{`)
  ["{{{0}", 42], // output `{42` : an escaped brace and the formatted value
  ["Mix {{0}} and {0}", 42], // outputs `Mix {0} and 42`
  ["Invalid closing brace }"], // should fail, since the closing brace does not close an opening one
  ["Invalid placeholder {z}"], // should fail, not an integer
  ["{0}", "With { in value"], // output `With { in value`, inner { should be broke the format
  ["{0} {1}", 42], // throw error because not enough argument are passed
];

for (const [test, ...args] of tests) {
  try {
    console.info(format(test, ...args));
  } catch (e) {
    console.info("Error:", e.message);
  }
}

Нет необходимости в утверждениях обхода, поскольку нет риска, что при возврате эти парные скобки будут разбиты на части неправильно.

Обратная ссылка \2 служит для указания того, что была сопоставлена ​​двойная фигурная скобка, указывая на экранированную фигурную скобку (либо {, либо }). ? является жадным, но если такой повторяющейся скобки нет, мы зафиксировали одну скобку, которая нарушает синтаксис.

Обновил мой ответ в свете обновления вопроса, который указывает на то, что должна быть выдана ошибка, если скобки не экранируются, хотя они на самом деле должны быть.

trincot 27.06.2024 14:07

потрясающий. Спасибо !

Steve B 27.06.2024 15:49

Другие вопросы по теме