利用Javascript获取选择文本所在的句子详解(2)

选择文本

先获取文本,如果没有则退出

const selection = window.getSelection()
const selectedText = selection.toString()
if (!selectedText.trim()) { return '' }

获取首部

对于 anchorNode 只考虑 Text 节点,通过 anchorOffset 获取选择在 anchorNode 的前半段内容。

然后开始补全在 anchorNode 之前的兄弟节点,最后补全在 anchorNode 父元素之前的兄弟元素。注意后面是元素,这样可以减少遍历的次数,而且考虑到一些被隐藏的内容不需要获取,用 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 = /((\.(?![ .]))|[^.?!。?!…\r\n])+$/

前面的 ((\.(?![ .])) 主要是为了跳过 a.b 这样的特别是在技术文章中常见的写法。

获取尾部

跟首部同理,换成往后遍历。最后的正则保留了标点符号

// match tail       for "..."
const sentenceTailTester = /^((\.(?![ .]))|[^.?!。?!…\r\n])+(.)\3{0,2}/

压缩换行

拼凑完句子之后压缩多个换行为一个空白行,以及删除每行开头结尾的空白符

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

完整代码

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, '\n') // allow one empty line & trim each line
 .replace(/(^\s+)|(\s+$)/g, '') // remove heading or tailing \n
}
      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/533.html