成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

獲取選擇文本所在的句子

Eidesen / 2527人閱讀

摘要:原理分析獲取選擇文本通過即可獲得一個對象,再利用即可獲得選擇的文本。所以我們還需要遍歷兄弟和父節(jié)點(diǎn)來獲取完整的句子。實(shí)現(xiàn)選擇文本先獲取文本,如果沒有則退出獲取首部對于只考慮節(jié)點(diǎn),通過獲取選擇在的前半段內(nèi)容。

最近收到一個 issue 期望能在劃詞的時候同時保存單詞的上下文和來源網(wǎng)址。這個功能其實(shí)很久之前就想過,但感覺不好實(shí)現(xiàn)一直拖延沒做。真做完發(fā)現(xiàn)其實(shí)并不復(fù)雜,完整代碼在這里,或者繼續(xù)往下閱讀分析。

原理分析 獲取選擇文本

通過 window.getSelection() 即可獲得一個 Selection 對象,再利用 .toString() 即可獲得選擇的文本。

錨節(jié)點(diǎn)與焦節(jié)點(diǎn)

Selection 對象中還保存了兩個重要信息,anchorNodefocusNode,分別代表選擇產(chǎn)生那一刻的節(jié)點(diǎn)和選擇結(jié)束時的節(jié)點(diǎn),而 anchorOffsetfocusOffset 則保存了選擇在這兩個節(jié)點(diǎn)里的偏移值。

這時你可能馬上就想到第一個方案:這不就好辦了么,有了首尾節(jié)點(diǎn)和偏移,就可以獲取句子的頭部和尾部,再把選擇文本作為中間,整個句子不就出來了么。

當(dāng)然不會這么簡單哈。

強(qiáng)調(diào)一下

一般情況下,anchorNodefocusNode 都是 Text 節(jié)點(diǎn)(而且因為這里處理的是文本,所以其它情況也會直接忽略),可以考慮這種情況:

Saladict is awesome!

如果選擇的是“awesome”,那么 anchorNodefocusNode 都是 is awesome!,所以取不到前面的 “Saladict”。

另外還有嵌套的情況,也是同樣的問題。

Saladict is awesome!

所以我們還需要遍歷兄弟和父節(jié)點(diǎn)來獲取完整的句子。

遍歷到哪?

于是接下就是解決遍歷邊界的問題了。遍歷到什么地方為止呢?我的判斷標(biāo)準(zhǔn)是:跳過 inline-level 元素,遇到 block-level 元素為止。而判斷一個元素是 inline-level 還是 block-level 最準(zhǔn)確的方式應(yīng)該是用 window.getComputedStyle()。但我認(rèn)為這么做太重了,也不需要嚴(yán)格的準(zhǔn)確性,所以用了常見的 inline 標(biāo)簽來判斷。

const INLINE_TAGS = new Set([
  // Inline text semantics
  "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i",
  "kbd", "mark", "q", "rp", "rt", "rtc", "ruby", "s", "samp", "small",
  "span", "strong", "sub", "sup", "time", "u", "var", "wbr"
])
原理總結(jié)

句子由三塊組成,選擇文本作為中間,然后遍歷兄弟和父節(jié)點(diǎn)獲取首尾補(bǔ)上。

實(shí)現(xiàn) 選擇文本

先獲取文本,如果沒有則退出

const selection = window.getSelection()
const selectedText = selection.toString()
if (!selectedText.trim()) { return "" }
獲取首部

對于 anchorNode 只考慮 Text 節(jié)點(diǎn),通過 anchorOffset 獲取選擇在 anchorNode 的前半段內(nèi)容。

然后開始補(bǔ)全在 anchorNode 之前的兄弟節(jié)點(diǎn),最后補(bǔ)全在 anchorNode 父元素之前的兄弟元素。注意后面是元素,這樣可以減少遍歷的次數(shù),而且考慮到一些被隱藏的內(nèi)容不需要獲取,用 innerText 而不是 textContent 屬性。

let sentenceHead = ""
const anchorNode = selection.anchorNode
if (anchorNode.nodeType === Node.TEXT_NODE) {
  let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset)
  for (let node = anchorNode.previousSibling; node; node = node.previousSibling) {
    if (node.nodeType === Node.TEXT_NODE) {
      leadingText = node.textContent + leadingText
    } else if (node.nodeType === Node.ELEMENT_NODE) {
      leadingText = node.innerText + leadingText
    }
  }

  for (
    let element = anchorNode.parentElement;
    element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body;
    element = element.parentElement
  ) {
    for (let el = element.previousElementSibling; el; el = el.previousElementSibling) {
      leadingText = el.innerText + leadingText
    }
  }

  sentenceHead = (leadingText.match(sentenceHeadTester) || [""])[0]
}

最后從提取句子首部用的正則是這個

// match head                 a.b is ok    chars that ends a sentence
const sentenceHeadTester = /((.(?![ .]))|[^.?!。?!…
])+$/

前面的 ((.(?![ .])) 主要是為了跳過 a.b 這樣的特別是在技術(shù)文章中常見的寫法。

獲取尾部

跟首部同理,換成往后遍歷。最后的正則保留了標(biāo)點(diǎn)符號

// match tail                                                    for "..."
const sentenceTailTester = /^((.(?![ .]))|[^.?!。?!…
])+(.)3{0,2}/
壓縮換行

拼湊完句子之后壓縮多個換行為一個空白行,以及刪除每行開頭結(jié)尾的空白符

return (sentenceHead + selectedText + sentenceTail)
  .replace(/(^s+)|(s+$)/gm, "
") // allow one empty line & trim each line
  .replace(/(^s+)|(s+$)/g, "") // remove heading or tailing 
完整代碼
const INLINE_TAGS = new Set([
  // Inline text semantics
  "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i",
  "kbd", "mark", "q", "rp", "rt", "rtc", "ruby", "s", "samp", "small",
  "span", "strong", "sub", "sup", "time", "u", "var", "wbr"
])

/**
* @returns {string}
*/
export function getSelectionSentence () {
  const selection = window.getSelection()
  const selectedText = selection.toString()
  if (!selectedText.trim()) { return "" }

  var sentenceHead = ""
  var sentenceTail = ""

  const anchorNode = selection.anchorNode
  if (anchorNode.nodeType === Node.TEXT_NODE) {
    let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset)
    for (let node = anchorNode.previousSibling; node; node = node.previousSibling) {
      if (node.nodeType === Node.TEXT_NODE) {
        leadingText = node.textContent + leadingText
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        leadingText = node.innerText + leadingText
      }
    }

    for (
      let element = anchorNode.parentElement;
      element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body;
      element = element.parentElement
    ) {
      for (let el = element.previousElementSibling; el; el = el.previousElementSibling) {
        leadingText = el.innerText + leadingText
      }
    }

    sentenceHead = (leadingText.match(sentenceHeadTester) || [""])[0]
  }

  const focusNode = selection.focusNode
  if (selection.focusNode.nodeType === Node.TEXT_NODE) {
    let tailingText = selection.focusNode.textContent.slice(selection.focusOffset)
    for (let node = focusNode.nextSibling; node; node = node.nextSibling) {
      if (node.nodeType === Node.TEXT_NODE) {
        tailingText += node.textContent
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        tailingText += node.innerText
      }
    }

    for (
      let element = focusNode.parentElement;
      element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body;
      element = element.parentElement
    ) {
      for (let el = element.nextElementSibling; el; el = el.nextElementSibling) {
        tailingText += el.innerText
      }
    }

    sentenceTail = (tailingText.match(sentenceTailTester) || [""])[0]
  }

  return (sentenceHead + selectedText + sentenceTail)
    .replace(/(^s+)|(s+$)/gm, "
") // allow one empty line & trim each line
    .replace(/(^s+)|(s+$)/g, "") // remove heading or tailing 

}

【完】

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90147.html

相關(guān)文章

  • LSTM分類相關(guān)

    摘要:而檢驗?zāi)P陀玫降脑牧?,包括薛云老師提供的蒙牛牛奶的評論,以及從網(wǎng)絡(luò)購買的某款手機(jī)的評論數(shù)據(jù)見附件。不同行業(yè)某些詞語的詞頻會有比較大的差別,而這些詞有可能是情感分類的關(guān)鍵詞之一。這是由于文本情感分類的本質(zhì)復(fù)雜性所致的。 文本情感分類--傳統(tǒng)模型(轉(zhuǎn)) showImg(https://segmentfault.com/img/bVKjWF?w=2192&h=534); 傳統(tǒng)的基于情感詞典...

    MartinHan 評論0 收藏0
  • 編輯器之神-VIM

    摘要:在這天地間,流傳這兩大神器的故事?lián)f是神的編輯器,而是編輯器之神。正所謂,工欲善其事必先利其器。今天就和大家分享一下關(guān)于編輯器之神的傳說。主要用于用來編寫和查看文本文件。 在這天地間,流傳這兩大神器的故事:據(jù)說Emacs是神的編輯器,而Vim是編輯器之神。正所謂,工欲善其事,必先利其器。今天就和大家分享一下關(guān)于編輯器之神Vim的傳說。 一、Vim的歷史 1.下圖是關(guān)于幾款主流編輯器...

    imingyu 評論0 收藏0
  • 關(guān)于深度學(xué)習(xí)中注意力機(jī)制,這篇文章從實(shí)例到原理都幫你參透了

    摘要:本文以機(jī)器翻譯為例,深入淺出地介紹了深度學(xué)習(xí)中注意力機(jī)制的原理及關(guān)鍵計算機(jī)制,同時也抽象出其本質(zhì)思想,并介紹了注意力模型在圖像及語音等領(lǐng)域的典型應(yīng)用場景。 最近兩年,注意力模型(Attention Model)被廣泛使用在自然語言處理、圖像識別及語音識別等各種不同類型的深度學(xué)習(xí)任務(wù)中,是深度學(xué)習(xí)技術(shù)中最值得關(guān)注與深入了解的核心技術(shù)之一。本文以機(jī)器翻譯為例,深入淺出地介紹了深度學(xué)習(xí)中注意力機(jī)制...

    iliyaku 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<