Как получить узлы, лежащие внутри диапазона с помощью JavaScript? - PullRequest
15 голосов
/ 20 марта 2009

Я пытаюсь получить все узлы DOM, которые находятся внутри объекта диапазона, каков наилучший способ сделать это?

var selection = window.getSelection(); //what the user has selected
var range = selection.getRangeAt(0); //the first range of the selection
var startNode = range.startContainer;
var endNode = range.endContainer;
var allNodes = /*insert magic*/;

Последние несколько часов я думал о пути и придумал:

var getNextNode = function(node, skipChildren){
    //if there are child nodes and we didn't come from a child node
    if (node.firstChild && !skipChildren) {
        return node.firstChild;
    }
    if (!node.parentNode){
        return null;
    }
    return node.nextSibling 
        || getNextNode(node.parentNode, true);
};

var getNodesInRange = function(range){
    var startNode = range.startContainer.childNodes[range.startOffset]
            || range.startContainer;//it's a text node
    var endNode = range.endContainer.childNodes[range.endOffset]
            || range.endContainer;

    if (startNode == endNode && startNode.childNodes.length === 0) {
        return [startNode];
    };

    var nodes = [];
    do {
        nodes.push(startNode);
    }
    while ((startNode = getNextNode(startNode)) 
            && (startNode != endNode));
    return nodes;
};

Однако, когда конечный узел является родителем начального узла, он возвращает все на странице. Я уверен, что я пропускаю что-то очевидное? Или, может быть, все происходит совершенно неправильно.

MDC / DOM / диапазон

Ответы [ 8 ]

11 голосов
/ 28 октября 2011

Вот реализация, которую я придумал, чтобы решить эту проблему:

function getNextNode(node)
{
    if (node.firstChild)
        return node.firstChild;
    while (node)
    {
        if (node.nextSibling)
            return node.nextSibling;
        node = node.parentNode;
    }
}

function getNodesInRange(range)
{
    var start = range.startContainer;
    var end = range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode)
    {
        nodes.push(node);
        if (node == commonAncestor)
            break;
    }
    nodes.reverse();

    // walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node))
    {
        nodes.push(node);
        if (node == end)
            break;
    }

    return nodes;
}
11 голосов
/ 21 марта 2009

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

Вместо этого выполните проверку условного прерывания внутри getNextNode:

var getNextNode = function(node, skipChildren, endNode){
  //if there are child nodes and we didn't come from a child node
  if (endNode == node) {
    return null;
  }
  if (node.firstChild && !skipChildren) {
    return node.firstChild;
  }
  if (!node.parentNode){
    return null;
  }
  return node.nextSibling 
         || getNextNode(node.parentNode, true, endNode); 
};

и оператор while:

while (startNode = getNextNode(startNode, false , endNode));
2 голосов
/ 15 октября 2013

Библиотека Rangy имеет функцию Range.getNodes([Array nodeTypes[, Function filter]]) .

1 голос
/ 26 января 2015

Я сделал 2 дополнительных исправления, основанных на ответе MikeB, чтобы улучшить точность выбранных узлов.

Я особенно проверяю это при выборе всех операций, кроме выбора диапазона, сделанного перетаскиванием курсора по тексту, охватывающему несколько элементов.

В Firefox нажатие кнопки select all (CMD + A) возвращает диапазон, где его startContainer & endContainer - это contenteditable div, а разница - в startOffset & endOffset, где это соответственно индекс первого и последнего дочернего узла.

В Chrome нажатие кнопки select all (CMD + A) возвращает диапазон, в котором startContainer является первым дочерним узлом contenteditable div, а endContainer является последним дочерним узлом contenteditable div.

Модификации, которые я добавил, устраняют расхождения между ними. Вы можете увидеть комментарии в коде для дополнительного объяснения.

function getNextNode(node) {
    if (node.firstChild)
        return node.firstChild;

    while (node) {
        if (node.nextSibling) return node.nextSibling;
        node = node.parentNode;
    }
}

function getNodesInRange(range) {

    // MOD #1
    // When the startContainer/endContainer is an element, its
    // startOffset/endOffset basically points to the nth child node
    // where the range starts/ends.
    var start = range.startContainer.childNodes[range.startOffset] || range.startContainer;
    var end = range.endContainer.childNodes[range.endOffset] || range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode)
    {
        nodes.push(node);
        if (node == commonAncestor)
            break;
    }
    nodes.reverse();

    // walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node))
    {
        // MOD #2
        // getNextNode might go outside of the range
        // For a quick fix, I'm using jQuery's closest to determine
        // when it goes out of range and exit the loop.
        if (!$(node.parentNode).closest(commonAncestor)[0]) break;

        nodes.push(node);
        if (node == end)
            break;
    }

    return nodes;
};
1 голос
/ 04 марта 2014

код ниже решит вашу проблему

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>payam jabbari</title>
<script src="http://code.jquery.com/jquery-2.0.2.min.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function(){
    var startNode = $('p.first').contents().get(0);
var endNode = $('span.second').contents().get(0);
var range = document.createRange();
range.setStart(startNode, 0);
range.setEnd(endNode, 5);
var selection = document.getSelection();
selection.addRange(range);
// below code return all nodes in selection range. this code work in all browser
var nodes = range.cloneContents().querySelectorAll("*");
for(var i=0;i<nodes.length;i++)
{
   alert(nodes[i].innerHTML);
}
});
</script>
</head>

<body>
<div>

<p class="first">Even a week ago, the idea of a Russian military intervention in Ukraine seemed far-fetched if not totally alarmist. But the arrival of Russian troops in Crimea over the weekend has shown that he is not averse to reckless adventures, even ones that offer little gain. In the coming days and weeks</p>

<ol>
    <li>China says military will respond to provocations.</li>
    <li >This Man Has Served 20 <span class="second"> Years—and May Die—in </span> Prison for Marijuana.</li>
    <li>At White House, Israel's Netanyahu pushes back against Obama diplomacy.</li>
</ol>
</div>
</body>
</html>
0 голосов
/ 14 июня 2017

здесь функция возвращает вам массив поддиапазонов

function getSafeRanges(range) {

var doc = document;

var commonAncestorContainer = range.commonAncestorContainer;
var startContainer = range.startContainer;
var endContainer = range.endContainer;
var startArray = new Array(0),
    startRange = new Array(0);
var endArray = new Array(0),
    endRange = new Array(0);
// @@@@@ If start container and end container is same
if (startContainer == endContainer) {
    return [range];
} else {
    for (var i = startContainer; i != commonAncestorContainer; i = i.parentNode) {
        startArray.push(i);
    }
    for (var i = endContainer; i != commonAncestorContainer; i = i.parentNode) {
        endArray.push(i);
    }
}
if (0 < startArray.length) {
    for (var i = 0; i < startArray.length; i++) {
        if (i) {
            var node = startArray[i - 1];
            while ((node = node.nextSibling) != null) {
                startRange = startRange.concat(getRangeOfChildNodes(node));
            }
        } else {
            var xs = doc.createRange();
            var s = startArray[i];
            var offset = range.startOffset;
            var ea = (startArray[i].nodeType == Node.TEXT_NODE) ? startArray[i] : startArray[i].lastChild;
            xs.setStart(s, offset);
            xs.setEndAfter(ea);
            startRange.push(xs);
        }
    }
}
if (0 < endArray.length) {
    for (var i = 0; i < endArray.length; i++) {
        if (i) {
            var node = endArray[i - 1];
            while ((node = node.previousSibling) != null) {
                endRange = endRange.concat(getRangeOfChildNodes(node));
            }
        } else {
            var xe = doc.createRange();
            var sb = (endArray[i].nodeType == Node.TEXT_NODE) ? endArray[i] : endArray[i].firstChild;
            var end = endArray[i];
            var offset = range.endOffset;
            xe.setStartBefore(sb);
            xe.setEnd(end, offset);
            endRange.unshift(xe);
        }
    }
}
var topStartNode = startArray[startArray.length - 1];
var topEndNode = endArray[endArray.length - 1];
var middleRange = getRangeOfMiddleElements(topStartNode, topEndNode);
startRange = startRange.concat(middleRange);
response = startRange.concat(endRange);
return response;

}

0 голосов
/ 10 июня 2016

боб. функция возвращает только startNode и endNode. промежуточные узлы не помещаются в массив.

кажется, что цикл while возвращает значение null для getNextNode (), следовательно, этот блок никогда не выполняется.

0 голосов
/ 26 июня 2015

Аннон, отличная работа. Я изменил оригинал плюс включенные модификации Стефана в следующем.

Кроме того, я убрал зависимость от Range, который преобразует функцию в общий алгоритм обхода между двумя узлами. Кроме того, я все обернул в одну функцию.

Мысли о других решениях:

  • Не заинтересован в использовании jquery
  • Использование cloneNode переводит результаты во фрагмент, что предотвращает многие операции, которые можно выполнить во время фильтрации.
  • Использование querySelectAll на клонированном фрагменте является проблематичным, поскольку начальный или конечный узлы могут находиться в узле обтекания, следовательно, синтаксический анализатор может не иметь закрывающего тега?

Пример:

<div>
    <p>A</p>
    <div>
        <p>B</p>
        <div>
            <p>C</p>
        </div>
    </div>
</div>

Предположим, что начальный узел - это абзац "A", а конечный узел - это абзац "C" , Полученный клонированный фрагмент будет иметь вид:

<p>A</p>
    <div>
        <p>B</p>
        <div>
            <p>C</p>

а нам не хватает закрывающих тегов? в результате чего в фанки структуры DOM?

В любом случае, вот функция, которая включает опцию фильтра, которая должна возвращать ИСТИНА или ЛОЖЬ для включения / исключения из результатов.

var getNodesBetween = function(startNode, endNode, includeStartAndEnd, filter){
    if (startNode == endNode && startNode.childNodes.length === 0) {
        return [startNode];
    };

    var getNextNode = function(node, finalNode, skipChildren){
        //if there are child nodes and we didn't come from a child node
        if (finalNode == node) {
            return null;
        }
        if (node.firstChild && !skipChildren) {
            return node.firstChild;
        }
        if (!node.parentNode){
            return null;
        }
        return node.nextSibling || getNextNode(node.parentNode, endNode, true);
    };

    var nodes = [];

    if(includeStartAndEnd){
        nodes.push(startNode);
    }

    while ((startNode = getNextNode(startNode, endNode)) && (startNode != endNode)){
        if(filter){
            if(filter(startNode)){
                nodes.push(startNode);
            }
        } else {
            nodes.push(startNode);
        }
    }

    if(includeStartAndEnd){
        nodes.push(endNode);
    }

    return nodes;
};
...