【问题标题】:How do I check if selected text on a web only consists of words in JavaScript?如何检查网络上的选定文本是否仅包含 JavaScript 中的单词?
【发布时间】:2018-11-26 10:16:19
【问题描述】:

在 vanilla Javascript 中,我试图确定用户在网页上选择的文本是否全部由单词组成(不包括符号)。

举个例子,

假设我们在网页某处有如下文本。

您好,示例文本! (全选时)

应该是['Hello', 'a', 'text', 'for', 'the', 'example']

然而,

Hello,示例文本! (省略前三个字母)

应该导致['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());
};

有没有什么好的方法可以调整我的功能以使其像我提到的那样工作?

【问题讨论】:

  • 正则表达式应该如何知道,'llo' 不是一个完整的单词?正则表达式不懂任何语言。
  • @PoulBak 感谢您指出这一点!我删除了regex 标签!
  • 您需要将所选文本与整个文本进行比较,以确定是否选择了部分作品。或者使用已知单词的字典来比较所有内容。
  • @HereticMonkey 谢谢你的意见!但由于它是任何网页上的随机文本(因为此代码用于 chrome 扩展),我如何获取整个文本而不是整个段落?
  • 当您创建范围时,请尝试同时覆盖它旁边的文本。然后您可以检查是否存在单词边界(例如空格)或用户选择是否从单词中间开始。

标签: javascript html selection


【解决方案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);

这可能需要大量内存。根据快速谷歌搜索,牛津英语词典大约有218632 个单词。平均字长为4.5 个字母,JS 为每个字符存储2 个字节,为我们提供218632 * (4.5 * 2) = 1967688 B = 1.967 MB,在慢速 3G 连接上下载可能需要长达 1 分钟。

更好的方法可能是在每次页面加载时通过收集页面上所有唯一的单词来自己构建单词字典。

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 个部分选择的词,每个选择端都有一个。

注意:下面的方法还通过假设 THIStHiSthis 都是同一个词来处理大写。

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 元素,例如 &lt;span&gt;w&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;,此代码仍然会中断。这种情况打破了我们对单词的定义,要解决它,您还需要包含某种字典来测试单词的有效性,基本上是结合上面的最后两种解决方案。

【讨论】:

  • 感谢您的详细解答!但是,我有一个问题,但有一个例外。如果所选句子是来自I say helloI say hell,但实际上在同一网页上的其他段落中有一个单词hell,该怎么办?这不会在结果中给我hell吗??
  • 你是对的,正确的子字符串仍然会被匹配。
  • 有什么办法可以解决这个问题吗?
  • @Poream3387 查看我的编辑。我为它添加了一个解决方案。
  • 很高兴帮助队友:)。检查if (window.getSelection) 在那里,因为我从你的代码中复制了它,我假设你在其他地方找到了它:)。为什么需要它是因为某些浏览器可能没有这个 API,我们可能需要一个备用解决方案。检查if (selection.anchorNode) 仅存在因为当没有选择文本时,如果您运行getSelectedWords 函数,selection.anchorNodeselection.focusNode 都将是null,因此在它们上调用.textContent 将给出错误。换句话说,该检查只是为了在没有选择文本时进行保护。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-28
  • 2021-07-22
  • 2011-07-20
  • 1970-01-01
  • 1970-01-01
  • 2012-03-26
  • 1970-01-01
相关资源
最近更新 更多