Как расставить html теги в тексте по позициям, заданным индексами?

Рейтинг: 0Ответов: 2Опубликовано: 22.08.2023

Есть абзац простого текста, а так же есть отдельно массив объектов, описывающий его форматирование внутри, такого вида:

let text = "Привет, я текст.";
let format = [
   {
      type: "bold",
      start: 0,
      end: 6
   },
   {
      type: "italic",
      start: 0,
      end: 1
   },
   {
      type: "strike",
      start: 9,
      end: 14
   },
];

Соответственно надо применить к тексту форматирование, обернув символы с позиции start по end в соответствующие html теги. Типов больше, но главное принцип. Их можно обозначить, например так:

const types = {
   bold: {
      start: "<b>",
      end: "</b>"
   },
   italic: {
      start: "<i>",
      end: "</i>"
   },
   strike: {
      start: "<del>",
      end: "</del>"
   },
   other: {
      start: "<span class='other'>",
      end: "</span>"
   },
};

Ожидаемый результат:

<b><i>П</i>ривет<b>, я <del>текст</del>.

Проблема в том, что если просто последовательно применять объекты форматирования к одному и тому же тексту, индексы уже на втором шаге съедут. Так же открывающие и закрывающие теги имеют разную переменную длину. Как это сделать?

Ответы

▲ 1Принят

PS: У вас у strike поля start и end надо сдвинуть на 1 символ вправо

Простой вариант:

Переводим format в массив токенов на определённых позициях, после чего сортируем их по индексу вставки и вставляем:

tokens = []
for (var i in format){
    tokens.push({token: types[format[i].type].start, pos: format[i].start})
    tokens.push({token: types[format[i].type].end, pos: format[i].end})
}

// Сортируем токены так, что бы они шли в обратном порядке
tokens = tokens.sort((a, b) => a.pos > b.pos ? -1 : a.pos === b.pos ? 0 : 1)
// Функция для вставки текста
function insertText(original, pos, text){
  return original.slice(0, pos) + text + original.slice(pos);
}

for (var i in tokens){
  text = insertText(text, tokens[i].pos, tokens[i].token)
}

Этот вариант не слишком оптимизированный, так как вставляет текст на каждый токен. Если же вы работаете с ОЧЕНЬ БОЛЬШИМИ массивами данных и вам нужен более сложный и быстрый вариант, то напишите, я могу его рализовать.

Но мне кажется и этого достаточно

▲ 0

Как это сделать?

Предложу такой вариант. Правда исходный массив format я перестроил...

const text = "Привет, я текст.";
const format = [
   {
      type: "bold",
      start: 0,
      end: 6
   },
   {
      type: "italic",
      start: 0,
      end: 1
   },
   {
      type: "strike",
      start: 9,
      end: 14
   },
];
console.log(test(text, format))
//
function test(text, format) {
  const types = {
     bold: {
        start: "<b>",
        end: "</b>"
     },
     italic: {
        start: "<i>",
        end: "</i>"
     },
     strike: {
        start: "<del>",
        end: "</del>"
     },
     other: {
        start: "<span class='other'>",
        end: "</span>"
     },
  };
  const o = format.reduce((obj, o) => {
    if (!obj[o.start]) obj[o.start] = []
    obj[o.start].push({type: o.type, end: o.end})
    return obj
  }, {})
  let res = ''
  let i = 0
  Object.keys(o).forEach(k => {
    o[k].sort((a, b) => a.end - b.end)
    k = +k
    res += text.slice(i, k)
    i = k
    o[k].slice().reverse().forEach(o => {
      res += types[o.type].start
    })
    o[k].forEach(o => {
      res += text.slice(i, o.end)
      res += types[o.type].end
      i = o.end
    })
  })
  return res
}