В ванильном Javascript я пытаюсь определить, состоит ли текст, выбранный пользователем на веб-странице, из слов (за исключением символов).

Чтобы взять пример,

Допустим, у нас есть тексты, подобные приведенным ниже, где-то на веб-странице.

Здравствуйте, текст для примера! (при выделении всех)

Должен привести к ['Hello', 'a', 'text', 'for', 'the', 'example']

Однако,

Хел вот, текст для примера! (без первых трех букв)

Должен привести к ['a', 'text', 'for', 'the', 'example'], поскольку Hello не был полностью выделен как слово.

Пока у меня есть функция getSelectionText, которая выводит весь выделенный текст.

function getSelectionText() {
    var text = "";
    if (window.getSelection) {
        text = window.getSelection().toString();
    } else if (document.selection && document.selection.type !== "Control") {
        text = document.selection.createRange().text;
    }
    return text;
}

// Just adding the function as listeners.
document.onmouseup = document.onkeyup = function() {
    console.log(getSelectionText());
};

Есть ли хороший способ настроить мою функцию, чтобы она работала, как я уже упоминал?

4
Backrub32 24 Ноя 2018 в 22:01

1 ответ

Лучший ответ

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

Один из способов - иметь полный словарь всех английских слов.

const setOfAllEnglishWords = new Set([
  "Hello",
  "a",
  "text",
  "for",
  "the",
  "example"
  // ... many many more
]);

const selection = "lo, a text for the example!";
const result = selection
  .replace(/[^A-Za-z0-9\s]/g, "") // remove punctuation by replacing anything that is not a letter or a digit with the empty string
  .split(/\s+/)                   // split text into words by using 1 or more whitespace as the break point
  .filter(word => setOfAllEnglishWords.has(word));

console.log(result);

Для этого может потребоваться много памяти. Оксфордский словарь английского языка, основанный на быстром поиске в Google, содержит примерно 218632 слов. Средняя длина слова составляет 4.5 букв, а в JS-хранилище 2 байтов на символ, что дает нам 218632 * (4.5 * 2) = 1967688 B = 1.967 MB, загрузка которого может занять до 1 минуты при медленном соединении 3G.

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

function getSetOfWordsOnPage() {
  const walk = document.createTreeWalker(
    document.body,
    NodeFilter.SHOW_TEXT
  );

  const dict = new Set();
  let n;
  while ((n = walk.nextNode())) {
    for (const word of n.textContent
      .replace(/[^A-Za-z0-9\s]/g, "")
      .split(/\s+/)
      .map(word => word.trim())
      .filter(word => !!word)) {
      dict.add(word);
    }
  }
  return dict;
}

const setOfWordsOnThePage = getSetOfWordsOnPage();

function getSelectionText() {
  if (window.getSelection) {
    return window.getSelection().toString();
  } else if (document.selection && document.selection.type !== "Control") {
    return document.selection.createRange().text;
  }
  return "";
}

// Just adding the function as listeners.
document.querySelector("#button").addEventListener("click", () => {
  const result = getSelectionText()
    .replace(/[^A-Za-z0-9\s]/g, "") // remove punctuation
    .split(/\s+/) // split text into words
    .filter(word => setOfWordsOnThePage.has(word));
  console.log(result);
});
<button id="button">Show result</button>
<p>this is some text</p>
<p>again this is a text!!!!!</p>
<p>another,example,of,a,sentence</p>

Может быть, мы сможем пойти еще дальше. Нам вообще нужно запоминать слова? Кажется, достаточно определения «слово - это текст, окруженный пробелами».

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

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

Предположение, которое мы делаем здесь, состоит в том, что для любого произвольного выделения текста у нас может быть не более 2 частично выбранных слов, по одному на каждом конце выделения.

Примечание . В описанном ниже подходе также учитывается использование заглавных букв, предполагая, что THIS, tHiS и this - это одно и то же слово.

function removePunctuation(string) {
  return string.replace(/[^A-Za-z0-9\s]/g, " ");
}

function splitIntoWords(string) {
  return removePunctuation(string)
    .split(/\s+/)
    .map(word => word.toLowerCase().trim())
    .filter(word => !!word);
}

function getSelectedWords() {
  const selection = window.getSelection();
  const words = splitIntoWords(selection.toString());

  if (selection.anchorNode) {
    const startingsWords = splitIntoWords(selection.anchorNode.textContent);
    if (words[0] !== startingsWords[0]) {
      words.shift(); // remove the start since it's not a whole word
    }
  }

  if (selection.focusNode) {
    const endingWords = splitIntoWords(selection.focusNode.textContent);
    if (words[words.length - 1] !== endingWords[endingWords.length - 1]) {
      words.pop(); // remove the end since it's not a whole word
    }
  }

  return words;
}

// Just adding the function as listeners.
document.querySelector("#button").addEventListener("click", () => {
  console.log(getSelectedWords());
});
<button id="button">Show result</button>
<p><div>this is</div> <div>some text</div></p>
<p><span>again</span><span> </span><span>this</span><span> </span><span>is</span><span> </span><span>a</span> <span>text</span><span>!!!!!</span></p>
<p>another,example,of,a,sentence</p>

Примечание. этот код все равно не работает, если слова разбиты на несколько элементов html, например <span>w</span><span>o</span><span>r</span><span>d</span>. Этот сценарий нарушает наше определение слова, и для его решения вам потребуется включить какой-то словарь, а также проверить правильность слова, по существу объединяя последние 2 решения выше.

4
nem035 25 Ноя 2018 в 18:48