Как заменить фразу, а не слово внутри фразы?

Я разрабатываю функцию в своем приложении, которая выполняет поиск по тексту и сокращает слова/фразы, пока пользователь вводит текст.

let dictionary = {
  "M": {
    "Mobile telephone number": "M/TEL"
  },
  "T": {
    "Telephone": "TEL"
  }
};

function abbreviateText() {
  const input = document.getElementById("input").value;
  let output = input;

  for (const groupKey in dictionary) {
    const group = dictionary[groupKey];

    for (const key in group) {
      const regex = new RegExp(`\\b${key}\\b`, "gi");
      const replacement = `<span class = "tooltip" data-tooltip = "${key}">${group[key]}</span>`;
      output = output.replace(regex, replacement);
    }
  }

  document.getElementById("output").innerHTML = output;
}
<textarea id = "input" placeholder = "Enter message" rows = "7" oninput = "abbreviateText()"></textarea>
<p id = "output"></p>

При вводе mobile telephone number должно выводиться M/TEL вместо TEL number">M/TEL.

Исходный вывод должен быть <span class = "tooltip" data-tooltip = "Mobile telephone number">M/TEL"</span>. Обратите внимание, что нет тега span, охватывающего TEL.

У меня есть другие случаи этого, поэтому решение должно быть общим.

🤔 А знаете ли вы, что...
JavaScript поддерживает работу с куки и хранилищем веб-браузера для сохранения данных на клиентской стороне.


1
50
2

Ответы:

Решено

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

  1. Запустите первый цикл, чтобы заменить значения некоторыми заполнителями.
  2. Запустите второй цикл, чтобы заменить заполнители.

Также вам нужно сначала убедиться, что у вас есть «более длинные» замены.

let dictionary = {
  "M": {
    "Mobile telephone number": "M/TEL"
  },
  "T": {
    "Telephone": "TEL"
  }
};

const replacements = Object
  .values(dictionary)
  .flatMap(obj => Object.entries(obj))
  .sort((a, b) => a[0] < b[0])


function abbreviateText() {
  const input = document.getElementById("input").value;
  let output = input;
  
  const found = []

  for (const [key, value] of replacements) {
    const regex = new RegExp(`\\b${key}\\b`, "gi");
    
    if (regex.test(output)) {
      output = output.replace(regex, `__replacement:${found.length}`);
      found.push([found.length, `<span class = "tooltip" data-tooltip = "${key}">${value}</span>`])
    }
  }
  
  for (const [index, replacement] of found) {
    output = output.replace(new RegExp(`__replacement:${index}`, 'gi'), replacement)
  }

  document.getElementById("output").innerHTML = output;
}
<textarea id = "input" placeholder = "Enter message" rows = "7" oninput = "abbreviateText()"></textarea>
<p id = "output"></p>

Вы можете токенизировать весь вывод, а затем обрабатывать токены один за другим:

// Note the outer parentheses! These form a capturing group,
// whose match's content will be pushed back to the splitted array at odd indices.
const tokenizer = new RegExp(`(${words.join('|')})`, 'i');

input.addEventListener('input', function() {
  const value = this.value;
  const tokens = value.split(tokenizer);
  
  let joined = '';
  
  for (const [index, token] of tokens.entries()) {
    const isRegisteredWord = index % 2 === 1;
    
    if (isRegisteredWord) {
      const { abbreviated, tooltip } = wordToAbbreviation[token.toLowerCase()];
      joined += `<abbr title = "${tooltip}">${abbreviated}</abbr>`;
    } else {
      joined += token;
    }
  }
  
  output.innerHTML = joined;
});

Чтобы избежать ненужных итераций, важно изменить структуру данных на что-то вроде этого:

const dictionary = {
  M: {
    'mobile telephone number': {
      abbreviated: 'M/TEL',
      tooltip: 'Mobile telephone number'
    }
  },
  T: {
    telephone: {
      abbreviated: 'TEL',
      tooltip: 'Telephone'
    }
  }
};

// Flattening is required, however.
const wordToAbbreviation = Object.assign(
  Object.create(null),
  ...Object.values(dictionary)
);

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

const dictionary = {
  'mobile telephone number': {
    abbreviated: 'M/TEL',
    tooltip: 'Mobile telephone number'
  }
  telephone: {
    abbreviated: 'TEL',
    tooltip: 'Telephone'
  }
};

Попробуй это:

const dictionary = {
  M: {
    'mobile telephone number': {
      abbreviated: 'M/TEL',
      tooltip: 'Mobile telephone number'
    }
  },
  T: {
    telephone: {
      abbreviated: 'TEL',
      tooltip: 'Telephone'
    }
  }
};

const wordToAbbreviation = Object.assign(
  Object.create(null),
  ...Object.values(dictionary)
);

const input = document.getElementById('input');
const output = document.getElementById('output');

const words = Object.keys(wordToAbbreviation);
const tokenizer = new RegExp(`(${words.join('|')})`, 'i');

input.addEventListener('input', function() {
  const value = this.value;
  const tokens = value.split(tokenizer);
  
  let joined = '';
  
  for (const [index, token] of tokens.entries()) {
    const isRegisteredWord = index % 2 === 1;
    
    if (isRegisteredWord) {
      const { abbreviated, tooltip } = wordToAbbreviation[token.toLowerCase()];
      joined += `<abbr title = "${tooltip}">${abbreviated}</abbr>`;
    } else {
      joined += token;
    }
  }
  
  output.innerHTML = joined;
});

input.dispatchEvent(new InputEvent('input'));
<textarea id = "input" placeholder = "Enter message">mobile TelePhone nuMbeR</textarea>
<p id = "output"></p>