我需要从选择中提取文本并将其发送到TTS服务。TTS服务将为每个单词返回一个流URL和一组索引,指示它们的开始和结束位置(在时间和文本中)。
当用户播放流时,我希望在读取每个单词时突出显示它们。要做到这一点,我不能只对每个单词使用文本索引,因为它们不能让我返回到原始的HTML节点--因此我不能使用严格意义上的文本的toString()。
到目前为止,我要做的是使用range对象的开始和结束容器创建一个TreeWalker,并使用它提取范围中的所有文本节点。
问题: window.getSelection().toString()本质上忽略未显示的节点。其中包括<script>节点、<style>节点、具有display: none;等的节点。使用TreeWalker不是。
我知道我可以手动跳过TreeWalker中的所有这些节点(就像getSelection中没有alt属性和脚本吗?中建议的那样),但是它会变得非常复杂(特别是检查每个节点的可见性)。
在讨论这个问题之前,我想问一下,自从我所链接的问题被回答后,是否出现了新的方法或库?
我不打算使用跨浏览器的代码,我使用的是普通的Javascript (即不使用jQuery)。
发布于 2013-05-28 15:31:00
首先,我建议不要使用window.getSelection().toString()。它的行为因浏览器而异,而且有目前没有针对它的规范。有一个HTML5规范的草案版本,要求它在每个选择范围上返回调用toString()的结果,这就是IE9所实现的;WebKit和Mozilla都做了一些更复杂的事情。此外,WebKit和Mozilla之间也有不同之处,它们可以随时更改它们的实现。
冒着提升我自己的东西的风险,您可能可以使用我的兰迪库的兰迪库,该库试图提供方法来导航DOM并将其范围作为用户看到的文本。另一种方法是自己做很多类似的工作,或者限制代码可以使用的HTML。
发布于 2013-05-31 14:23:23
在等待答案的同时,我开始编写自己的解析器。这有点粗糙,因为有,没有跨浏览器支持,我也不对文本做任何修改-这意味着任何行间隔和其他空白将被保留。
还有很多我还没有清理过的冗余,比如遍历我已经知道隐藏的节点的子节点。
总之,密码是:
function ParsedRange(range){
this.text = "";
this.nodeIndices = [];
this.highlight = function(startIndex, endIndex){
var selection = window.getSelection();
var startNode = this.nodeIndices[startIndex].node;
var endNode = this.nodeIndices[endIndex].node;
var startOffset = startIndex - this.nodeIndices[startIndex].startIndex;
var endOffset = endIndex - this.nodeIndices[endIndex].startIndex + 1;
// Scroll into view
startNode.parentNode.scrollIntoViewIfNeeded();
// Highlight
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
selection.removeAllRanges();
selection.addRange(range);
};
// Parsing starts here
var startIndex;
var rootNode = range.commonAncestorContainer;
var startNode = range.startContainer;
var endNode = range.endContainer;
var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, false); // Only walk text and element nodes
var currentNode = treeWalker.currentNode;
// Move to start node
while (currentNode && currentNode != startNode) currentNode = treeWalker.nextNode();
// Extract text
var nodeText;
while (currentNode && currentNode != endNode){ // Handle end node separately
// Continue to next node if current node is hidden
if (isHidden(currentNode)){
currentNode = treeWalker.nextNode();
continue;
}
// Extract text if text node
if (currentNode.nodeType == 3){
if (currentNode == startNode) nodeText = currentNode.nodeValue.substring(range.startOffset); // Extract from start of selection if first node
else nodeText = currentNode.nodeValue; // Else extra entire node
this.text += nodeText;
if (currentNode == startNode) startIndex = range.startOffset * -1;
else startIndex = this.nodeIndices.length;
for (var i=0; i<nodeText.length; i++){
this.nodeIndices.push({
startIndex: startIndex,
node: currentNode
});
}
}
// Continue to next node
currentNode = treeWalker.nextNode();
}
// Extract text from end node if it's a text node
if (currentNode == endNode && currentNode.nodeType == 3 && !isHidden(currentNode)){
if (endNode == startNode) nodeText = currentNode.nodeValue.substring(range.startOffset, range.endOffset); // Extract only selected part if end and start nodes are the same
else nodeText = currentNode.nodeValue.substring(0, range.endOffset); // Else extract up to where the selection ends in the end node
this.text += nodeText;
if (currentNode == startNode) startIndex = range.startOffset*-1;
else startIndex = this.nodeIndices.length;
for (var i=0; i<nodeText.length; i++){
this.nodeIndices.push({
startIndex: startIndex,
node: currentNode
});
}
}
return this;
}
ParsedRange.removeHighlight = function(){
window.getSelection().removeAllRanges();
};
function isHidden(element){
// Get parent node if element is a text node
if (element.nodeType == 3) element = element.parentNode;
// Only check visibility of the element itself
if (window.getComputedStyle(element, null).getPropertyValue("visibility") == "hidden") return true;
// Check display and dimensions for element and its parents
while (element){
if (element.nodeType == 9) return false; // Document
if (element.tagName == "NOSCRIPT") return true;
if (window.getComputedStyle(element, null).getPropertyValue("display") == "none") return true;
if (element.offsetWidth == 0 || element.offsetHeight == 0){ // If element does not have overflow:visible it is hidden
if (window.getComputedStyle(element, null).getPropertyValue("overflow") != "visible"){
return true;
}
}
element = element.parentNode;
}
return false;
}由于在我的项目中集成它的方式,我已经将它作为一个类(除了isHidden()助手函数)。
除此之外,类通过传递一个有效的范围来工作,然后它将提取范围内的文本,并保存对所有节点的引用。这些引用在highlight()函数中使用,它使用浏览器选择来根据开始和结束字符索引突出显示。
在nodeIndices属性上添加一个额外的注释(因为这可能没有意义)。nodeIndices是一个数组,它包含具有以下表单的对象:
{
startIndex: // Int
node: // Reference to text node
}对于我在结果文本中提取的每一个字符,我在nodeIndices上推送其中一个对象,node属性只是对文本节点的引用,文本来自该节点。startIndex定义节点从整个文本开始的字符。
使用此数组,我可以将ParsedParagraph.text中的字符索引转换为HTML和该节点内相应字符的索引。
使用示例:
// Get start/end nodes and offsets for range
var startNode = // Code to get start node here, can be a text node or an element node
var startOffset = // Offset into the start node
var endNode = // Code to get end node here, can be a text node or an element node
var endOffset = // Offset into the end node
// Create the range
var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
// Parse the range using the ParsedRange class
var parsedRange = new ParsedRange(range);
parsedRange.text; // Contains visible text with whitespaces preserved.
parsedRange.highlight(startIndex, endIndex); // Will highlight the corresponding text inside parsedRange.text using browser selectionhttps://stackoverflow.com/questions/16776060
复制相似问题