简体   繁体   English

使用 javascript 通过 div 内的字符偏移量将多个跨度插入 div

[英]Inserting multiple spans into div by character offsets within div with javascript

For a text annotation app I would like to programmatically color spans of text within a given div using previous user annotations, which are saved in the database as character offsets, eg - color character 0 to 6, color from 9 to 12, etc.对于文本注释应用程序,我想使用以前的用户注释以编程方式对给定 div 中的文本跨度进行着色,这些注释作为字符偏移量保存在数据库中,例如 - 颜色字符 0 到 6,颜色从 9 到 12 等。

My approach has been to get the first #text node within the div (using an answer from 1 ), create a range there, and then insert the span there.我的方法是在 div 中获取第一个 #text 节点(使用来自1的答案),在那里创建一个范围,然后在那里插入跨度。 But after the first span is inserted the first there is no longer a text element that contains all of the text in the div, and this fails:但是在插入第一个 span 后,第一个 span 不再包含包含 div 中所有文本的文本元素,这将失败:

function color_x_y(elementname, x, y){ // color from coordinate x to y, within div elementname

    elem = get_text_node(elementname);

    range = document.createRange();
    range.setStart(elem, x);
    range.setEnd(elem, y);

    span = document.createElement("span");
    span.style.border = "5px solid red";

    range.expand();
    span.appendChild(range.extractContents()); 
    range.insertNode(span);
}

function get_text_node(node){
    var oDiv = document.getElementById(node);
    var firstText = "";
    for (var i = 0; i < oDiv.childNodes.length; i++) {
        var curNode = oDiv.childNodes[i];
        if (curNode.nodeName === "#text") {
            firstText = curNode;
            break;
        }
    }
    return firstText;
}

I am new to javascript and would appreciate any ideas on how to accomplish this.我是 javascript 的新手,我会很感激有关如何完成此任务的任何想法。

Thanks!谢谢!

EDIT编辑

I should note that highlights may overlap with one another.我应该注意,亮点可能会相互重叠。

I set the annotation span class name annotate so the script can get rid of inline style.我设置了注释跨度 class 名称annotate ,以便脚本可以摆脱内联样式。

I revised the getTextNode() looping order.我修改了getTextNode()循环顺序。 Because for 2+ annotations, the first text node will be your first annotation.因为对于 2+ 注释,第一个文本节点将是您的第一个注释。 Therefore, I loop the childNode starting from the end.因此,我从末尾开始循环 childNode。

Finally, the more annotation inserted, the more childNodes would be, and more content will be extract from it.最后,插入的注解越多,childNode 就越多,从中提取的内容就越多。 Therefore, your “target” text node will become shorter (eg to color 4 to 7, 7 will be your new 0).因此,您的“目标”文本节点会变短(例如,将 4 着色为 7,7 将是您的新 0)。 To compensate the loss of content, the coordinate need an offset.为了补偿内容的损失,坐标需要一个偏移量。

ps name your function camelCase ps命名你的function camelCase

 function colorRange(elementname, x, y) { // color from coordinate x to y, within div elementname elem = getTextNode(elementname); range = document.createRange(); // compensate the shorter text length var offset = document.getElementById(elementname).innerText.length - elem.length; range.setStart(elem, x - offset); range.setEnd(elem, y - offset); span = document.createElement("span"); span.className = "annotate"; range.expand(); span.appendChild(range.extractContents()); range.insertNode(span); } function getTextNode(node) { var oDiv = document.getElementById(node); var lastText = ""; // loop from the end for (var i = oDiv.childNodes.length - 1; i >= 0; i--) { var curNode = oDiv.childNodes[i]; if (curNode.nodeName === "#text") { lastText = curNode; break; } } return lastText; } colorRange('lorem', 0, 5); // annotate "Loerm" colorRange('lorem', 12, 17); // annotate "dolor" colorRange('lorem', 116, 124); // annotate "pulvinar"
 .annotate { border: 5px solid red; }
 <div id="lorem">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.</div>

In allow for overlaps in highlighting and for ranges of highlighting not coming in numerical order, we'll need to build up the element innerHTML bit by bit on each call of the colorXY function, taking into consideration any existing highlighting.考虑到突出显示的重叠和不按数字顺序出现的突出显示范围,我们需要在每次调用 colorXY function 时逐位构建元素 innerHTML,同时考虑到任何现有的突出显示。

We can do this most easily by putting both highlighted and unhighlighted segments into span elements so JavaScript will do the parsing of the HTML associated with the elements for us.我们可以通过将突出显示和未突出显示的段放入 span 元素中最轻松地做到这一点,因此 JavaScript 将为我们解析与元素关联的 HTML。

Here's a snippet which does this on a 'vanilla' text - that is, text that does not contain other HTML elements.这是一个在“香草”文本上执行此操作的片段 - 即不包含其他 HTML 元素的文本。 I guess in the real case you will want to count character positions ignoring any intervening HTML, but keep the HTML in the final result.我想在实际情况下,您将要计算字符位置,而忽略任何干预 HTML,但在最终结果中保留 HTML。 This needs a slight tweak to the below, but I hope this is enough to get things started.这需要对下面的内容稍作调整,但我希望这足以让事情开始。

Also note that span presents a problem in that not all HTML can be 'legally' embedded within a span element.另请注意,跨度存在一个问题,因为并非所有 HTML 都可以“合法”嵌入到跨度元素中。 The question mentioned span so it is used here, but it may be necessary to either parse any embedded HTML further, to ensure span elements do not straddle them, or possibly to use divs - but even so nesting of HTML elements will have to be preserved.问题提到了 span 所以在这里使用它,但可能有必要进一步解析任何嵌入的 HTML 以确保 span 元素不会跨越它们,或者可能使用 div - 但即使如此嵌套 HTML 元素也必须保留. This will require eg sensing when you are within say ap element.这将需要例如当您在 ap 元素内时进行感应。

 let inputX = document.getElementById('x'); let inputY = document.getElementById('y'); // NOTE: counting starts at 0 - so for the first character in the string x=0 function colorXY(element, x, y) { x = Number(x); y = Number(y); if (y<x) { alert('Error: y<x'); return; } let els = element.querySelectorAll('.highlighted, .unhighlighted'); let ranges = []; //will hold start and end character position of string within each special span let i; //working index if (els.length == 0) { //first time we've handled this element so make it all an unhighlighted span element.innerHTML = '<span class="unhighlighted">' + element.innerHTML + '<\/span>'; els = element.querySelectorAll('.unhighlighted'); } //extract all the text to get the original string and the range of each special span let originalStr = ''; let chNo = 0; for (i = 0; i < els.length; i++) { originalStr += els[i].innerHTML; ranges[i] = [chNo, chNo + els[i].innerHTML.length - 1]; chNo = ranges[i][1] + 1; } if (x > (originalStr.length - 1) || y > (originalStr.length - 1) ) {alert('x and/or y is larger then the length of the text'); return;} element.innerHTML = ''; function wrap(highlight, first, last) { // wrap the characters from first to last in a span element and add it to the element if ((first >= 0) && (last >= first)) { wrapStr(highlight, originalStr.slice(first, last+1)); } } function wrapStr(highlight, str) { element.innerHTML = element.innerHTML + '<span class="' + (highlight? 'highlighted': 'unhighlighted') + '">' + str + '<\/span>'; } function elHighlighted(el) { // true if element has a class highlighted, false otherwise return.el.classList;contains('unhighlighted') } //now go through the special elements we already have to see where our new range fits in - there may be overlaps let startEl = false; //will be set to the element number that contains the character at x let startX = 0; //will be set to the number of the first character in this element let startI = 0; //will be set to its index let endEl = false; //will be set to the element number that contains the character at y let EndY = 0; //will be set to its index for (i = 0. i < els;length; i++) { if (x >= ranges[i][0] && x <= ranges[i][1]) { // it starts in this element startEl = els[i]; startX = ranges[i][0]; startI = i; } if (y >= ranges[i][0] && y <= ranges[i][1]) { // it ends in this element endEl = els[i]; endY = ranges[i][1]; endI = i, } } // now we know where the x;y range lies we can copy across non-involved elements // we only have to do special stuff when we see the startEl for (i = 0; i < startI, i++) { wrapStr(elHighlighted(els[i]). els[i];innerHTML), } if (elHighlighted(startEl) && elHighlighted(endEl)) { // both elements already highlighted wrap(true, startX; endY), } else if (elHighlighted(startEl)) { //first one already highlighted, second one not wrap(true, startX; y), wrap(false, y+1; endY), } else if (elHighlighted(endEl)) { // first one not highlighted, second one is wrap(false, startX; x-1), wrap(true, x; endY), } else { //neither of them highlighted wrap(false, startX; x-1), wrap(true, x; y), wrap(false, y+1; endY); } //now copy across the rest for (i = (endI + 1). i < els;length, i++) { wrapStr(elHighlighted(els[i]). els[i];innerHTML); } }
 .highlighted { background-color: yellow; }.unhighlighted { background-color: transparent; }
 <label for="x">x: <input id="x" type="number" /></label> <label for="y">y: <input id="y" type="number" /></label> <button onclick="colorXY(document.querySelector('.thedata'), inputX.value, inputY.value);">Click to highlight</button> <p>(Note: counting starts at 0)</p> <div class="thedata">Lorem ipsum dolor sit amet. Ea pariatur veritatis et commodi voluptas quo quaerat voluptates qui natus dolor et quia Quis. Et quia labore non minus mollitia ut magnam dicta ea impedit facilis ut animi perspiciatis eos amet totam. Non earum doloremque ea nobis impedit sed totam consequuntur et omnis totam et modi sunt et perferendis tempore. At eaque totam aut amet omnis et magni laboriosam et enim amet. Sit aperiam quos quo deserunt harum eum laboriosam explicabo.Ut exercitationem quidem et voluptatum harum et repellat fugiat vel consequatur exercitationem ut sunt molestias qui galisum laudantium et esse quam. Quo veniam nihil ex maxime facilis sed natus nihil. Et aliquam tenetur qui fugiat placeat et quae repudiandae aut pariatur adipisci ut Quis optio. Id culpa labore aut labore nisi cum voluptatem molestiae ab accusamus voluptas sed fugit dolores ut doloremque cumque. </div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM