views:

56

answers:

2

I mean, take the following HTML codes as example, ignore the whitespaces.

<p>
  paragraph-text
  <span>
   span text
   <i>it</i>
  </span>
</p>

I have the pNode object and the index of "a" in "span text" within pNode.innerHTML as a whole, that is 23 here since pNode.innerHTML="paragraph-text<span>span text<i>it</i></span>", the open span tag should also be counted.

Is there some existing library that can be used like transform(pElement, 23), so that I can get the expected element and offset of "spanElement and 2"?

Thanks in adavance. Regards,

Steve

+1  A: 

You could perhaps do it by getting the innerHTML, changing the character whose offset you're looking for, and then re-parsing and diffing the resulting text nodes to find the one where the changed data ended up.

eg.

function findTextFromMarkupIndex(el1, ix) {
    // make comparison element
    var el2= document.createElement(el1.tagName);
    // replace any other use of * in its HTML out of the way
    var html= el1.innerHTML.split('*').join('x');
    // insert a * at the given index in HTML
    el2.innerHTML= html.substring(0, ix)+'*'+html.substring(ix);
    // search the resulting DOM for the * and return the matching descandent from the original
    return findTextInComparison(el1, el2, '*');
}

function findTextInComparison(el1, el2, text) {
    for (var i= 0; i<el1.childNodes.length; i++) {
        var child1= el1.childNodes[i];
        var child2= el2.childNodes[i];
        if (child2.nodeType===3) { // TEXT_NODE
            var ix= child2.data.indexOf(text);
            if (ix!==-1)
                return [child1, ix];
        } else if (child2.nodeType===1) { // ELEMENT_NODE
            var result= findTextInComparison(child1, child2, text);
            if (result!==null)
                return result;
        }
    }
    return null;
}

Untested but should work, for characters in text content anyway. You could extend it to also look inside attribute values if you needed to, but you couldn't do index points inside markup such as an element or entity/character reference. For that to work, you'd essentially need to write an entire HTML parser.

This sounds like an unusual requirement, what exactly are you trying to do? Indexes within innerHTML can be quite fragile as the exact format of markup output for this property is not at all standardised.

bobince
Bobince, this is exactly what I'm looking for. Your code works perfectly! Thanks a lot!
Steve
+1  A: 

What you're talking about sounds something like a DOM Range, which is implemented in all the major browsers except Internet Explorer. Specifying a location in terms of character offsets within HTML is not a good idea, since implementations of innerHTML vary significantly. A DOM Range has a start and end boundary, each of which is specified in terms of a node and an offset within that node. In the case of your example, you could create a Range that represented the location:

var span = pNode.childNodes[1];
var spanText = span.firstChild;
var range = document.createRange();

// Next line takes into account whitespace at the start of the text node
range.setStart(spanText, 6); 

// For illustration, create a range that contains the letter "a" and selects it
range.setEnd(spanText, 7);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

Once you have a Range, you can use it for various things, such as inserting nodes, defining user selections and applying formatting to the content encapsulated by the Range.

IE defines an entirely different object called a TextRange that has a fundamentally different text-based approach but serves much the same purpose. To get the equivalent TextRange to the previous example, you could use the following:

var textRange = document.body.createTextRange();
textRange.moveToElementText(pNode);
textRange.moveStart("character", 17);
textRange.collapse();

// For illustration, create a range that contains the letter "a" and selects it
textRange.moveEnd("character", 1);
textRange.select();
Tim Down
Tim, thanks for your feedback, it is appreciated.
Steve