Abraxas
Abraxas

Reputation: 104

DOM - Find text between arbitrarily nested elements

Suppose my DOM looks something like this (purely fictional example):

<div>
  <img id="img1" />
  Text 1
</div>

<img id="img2" />

<div>
  <p>Text 2</p>
  <div>
    <img id="img3" />
  </div>
  <img id="img4" />
</div>

What I'm attempting to do is to find all text (if any) between consecutive elements independent of nesting level (so in this case, I'd find Text1 between #img1 and #img2, Text 2 between #img2 and #img3, and nothing/an empty string between #img3 and #img4)

I don't know ahead of time how the dom is going to be structured.

I've tried using JQuery's nextUntil(), but that only seems to work for sibling nodes.

Upvotes: 2

Views: 147

Answers (2)

Gabriele Petrioli
Gabriele Petrioli

Reputation: 195992

You can use the contents() method which returns all child nodes including textnodes.
This way, doing $('body *').contents().addBack() returns a flattened representation of the DOM.

Now, you can iterate between img (or whatever tag you want) elements and get the textnodes (having a nodeType of 3)

function textBetweenTags(tag){
  var contents = $('body *').contents().addBack(),
      allOfType = contents.filter(tag),
      count = allOfType.length,
      map = allOfType.map(function(){return contents.index( this );}),
      texts = [];
  
  
  for (var i = 0, l = map.length-1; i < l; i++){
    var start = map[i],
        end = map[i+1],
        textnodes = contents.slice(start,end).get().filter(function(item,index){return item.nodeType===3;});
    texts.push( {
      start: contents[start],
      end: contents[end],
      text: $.trim($(textnodes).text()) 
    });
  }
  return texts.filter(function(item,index){return item.text !== '';});
}

var texts = textBetweenTags('img');

console.log(texts);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <img id="img1" />
  Text 1
</div>

<img id="img2" />

<div>
  <p>Text 2</p>
  <div>
    <img id="img3" />
  </div>
  <img id="img4" />
</div>

Upvotes: 1

zord
zord

Reputation: 4783

I think my solution is more closely achieves what you specified. This is also using contents() but does the traversing/filtering too, based on the ids.

function textBetweenIds(firstId, lastId) {
    var texts = [],
        betweenIds = false,
        recurse = function ($item) {
            $item.each(function() {
                var text,
                    $item = $(this),
                    itemId = $item.attr("id"),
                    contents = $item.contents();
                if (itemId == firstId) {
                    betweenIds = true;
                } else if (itemId == lastId) {
                    betweenIds = false;
                }
                if (contents.length == 0 && betweenIds) {
                    text = $item.text().trim();
                    if (text != undefined && text.length > 0) {
                        texts.push(text);
                    }
                }
                recurse(contents);
            });
            return texts;
        };
    recurse($("body"));
    return texts;
}

result = textBetweenIds("img1", "img3");
// result: ["Text 1", "Text 2"] 

Upvotes: 0

Related Questions