2013-05-27 3 views
0

Мне нужно извлечь текст из выделения и отправить его в службу TTS. Служба TTS вернет URL-адрес потока и набор индексов для каждого слова, указывая, где они начинаются и заканчиваются (как во времени, так и в тексте).getSelection.toString() - без использования toString()?

Когда пользователь играет в потоке, я хочу выделить каждое слово при чтении. Для этого я не могу просто использовать текстовые индексы для каждого слова, потому что они не могут вернуть меня к исходным узлам HTML, поэтому я не могу использовать toString(), который является строго текстовым.

То, что я делаю до сих пор, заключается в создании TreeWalker с использованием начального и конечного контейнеров объекта диапазона и использования этого для извлечения всех текстовых узлов в диапазоне.

Проблема: window.getSelection().toString() по своей сути игнорирует узлы, которые не отображаются. Это включает в себя <script> узлов, <style> узел, узлы с display: none; и тому подобное. Использование TreeWalker этого не делает.

Я знаю, что могу вручную пропустить все эти узлы в TreeWalker (как предлагается в getSelection without alt attribute and scripts in it?), но он может стать довольно сложным очень быстро (особенно проверять видимость каждого узла).

Прежде чем вступить в это, я хотел спросить, есть ли какие-либо новые методы или библиотеки, которые появились после ответа на вопрос, на который я ссылался.

Я не предполагаю, что код должен быть перекрестным браузером, и я использую простой Javascript (т. Е. JQuery).

ответ

2

Во-первых, я бы рекомендовал не использовать window.getSelection().toString(). Его поведение варьируется между браузерами, и есть currently no spec for it. Был проект версии спецификации HTML5, в которой было указано, что она должна возвращать согласование результатов вызова toString() по каждому диапазону выбора, что и было реализовано в IE 9; WebKit и Mozilla делают что-то более сложное. Кроме того, существуют различия между тем, что делают WebKit и Mozilla, и они могут изменить свои реализации в любое время.

С риском продвижения моих собственных материалов вы можете использовать TextRange module в моей библиотеке Rangy, которая пытается предоставить способы навигации по DOM и диапазонам внутри него в виде текста, который видит пользователь. Альтернатива делает много подобной работы самостоятельно или ограничивает HTML, с которым может работать ваш код.

+0

Код используется только в расширении Chrome, поэтому отсутствие перекрестной поддержки браузера не такая уж большая проблема. Во всяком случае, есть ли не измененная версия модуля Rangy/TextRange? Теперь у меня есть рабочее решение, но мне интересно узнать, как вы это сделали. – Woodgnome

+0

@Woodgnome: Отсутствие спецификации все еще является проблемой на долгий срок: реализация Chrome может легко измениться в будущем. Источник для модуля TextRange находится здесь: http://rangy.googlecode.com/svn/trunk/src/js/modules/rangy-textrange.js, а также сборки Rangy 1.3 имеют уменьшенные и незавершенные версии всего. –

+0

Хороший вопрос в отношении реализации - это было просто временное решение, поэтому должно быть хорошо в любом случае. – Woodgnome

0

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

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

Во всяком случае, код:

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 selection 
Смежные вопросы