10

I have the sample below content in mydiv

<div id="mydiv">
   sample text sample text sample text...
   ......
   <i>inner text </i> <i>sample text </i>
   ......
   <b>sample text </b> <i>sample text </i>
</div>

Now I want to append highlighting div in between the mydiv content. sample is given below.

<div class="highlight">highlight text</div>

I want to insert this div in every 200 words, but the problem is it should not goes inside any of the children tags. for example in case 200th word is inner it should not append like

<div id="mydiv">
   sample text sample text sample text...
   ......
   <i>inner<div class="highlight">highlight text</div> text </i> <i>sample text </i>
   ......
   <b>sample text </b> <i>sample text </i>
</div>

it should append after the inner tags

<div id="mydiv">
   sample text sample text sample text...
   ......
   <i>inner text </i> <div class="highlight">highlight text</div> <i>sample text </i>
   ......
   <b>sample text </b> <i>sample text </i>
</div>

I tried with substring but it goes inside the child tags. Is there any way to achieve this? We can use any js libraries.

5
  • just thinking out loud, but couldn't you use a regex to find the 200th word (for example, simply going by spaces), then use something like jquery's $(this).parent().after(myStuffToInsert) ? Of course, that assumes the nested tag is the appropriate one to insert after. If you wanted a specific parent tag (for example, after the paragraph tag rather than the bold tag), you could do $(this).parents("p").after(myStuffToInsert); Commented Dec 30, 2016 at 19:36
  • sorry I am not getting your point Commented Dec 30, 2016 at 20:01
  • 1 partial solution I have is blindly insert the inner divs at every 200th word then after iterate through all hiighlight class and if the parent is not matching taking him out. but the 200 word count will fail, because it will count inner tags Commented Dec 30, 2016 at 20:29
  • Looking into an interesting starting point: a CSS pseudo-selector written in jquery, for styling :nth-word (codepen.io/FWeinb/pen/djuIx). I'm thinking the logic behind this would allow you to get to the DOM node you're after, then simply check if it's an allowed type (allowed being P or DIV tags, disallowed being B, I, EM, SPAN or other inline tags). At this point, I'm reverse-engineering the javascript, add your eyes and see what you think. Commented Jan 5, 2017 at 20:23
  • When the 200th word is in an unacceptable tag, would you consider a solution which splits that tag and inserts the highlight div between the two resulting tags, like <i>inner</i><div class="highlight">highlight text</div><i> text </i>? That way, a strict interval of 200 words between insertions could be enforced. Commented Jan 8, 2017 at 2:36

9 Answers 9

7

The easiest way of doing this is to loop through the content of your div and insert the highlight in the right spot. Here is the code :

$(document).ready(function () {
    // count of characters (modulo the period) and selected period
    var count = 0, period = 200;

    // iterator and highlight
    var words = '', highlight = '<div class="highlight">highlight text</div>';

    // loop through the contents
    $('#mydiv').contents().each(function () {
        // we only care about text contents
        if (this.nodeType === 3) {
            // get the individual words
            words = $(this).text().split(' ');
            // loop through them
            for (var j = 0; j < words.length; j++) {
                // increase count except if the word is empty (mutiple spaces)
                if (words[j] && words[j] !== '\n') { count++; }
                // if the period is exceeded, add the highlight and reset the count
                if (count === period) {
                    words.splice(1 + j++, 0, highlight);
                    count = 0;
                }
            }
            // replace the text
            $(this).replaceWith(words.join(' '));
        }
    });
});
Sign up to request clarification or add additional context in comments.

Comments

4

You can use JQuery after or insertAfter to insert element after the target.

append method inserts the specified content as the last child of each element in the jQuery collection

$(function(){
    
  // your logic to find position goes here...
  
  // append  text after an element 
  $("#mydiv i:first-child").after("<div class='highlight'>highlight text</div>");
  
});
.highlight{
   color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mydiv">
  sample1 text1 sample1 text1 sample1 text1sample1 tex1 sample1 text1 sample text1 sample2 text2 sample2 text sample2 textsample2 text2 sample2 text2 sample2 text2 sample3 text3 sample3 text3 sample3 textsample3 text3 sample3 text3 sample3 text3 sample
  3 text 3sample 3text sample3 textsample3 text4 sample 4text sample4 text

  <i>1inner text 1 </i>  <i>sample text 1</i>
  <i>2inner text 2</i>  <i>sample text2 </i>
  <i>3inner text 3</i>  <i>sample text 3</i>
  <i>4inner text 4</i>  <i>sample text4 </i>
  <i>5inner text 5</i>  <i>sample text5 </i>
  <i>6inner text 6</i>  <i>sample text6 </i>
  <i>7inner text 7</i>  <i>sample text 7</i>

  <b>8sample text 8</b>  <i>sample text 8</i>
  <b>9sample text 9</b>  <i>sample text 9</i>
  <b>10sample text 10</b>  <i>sample text10 </i>
  <b>11sample text 11</b>  <i>sample text 11</i>
  <b>12sample text 12</b>  <i>sample text 12</i>

</div>

Comments

3

You haven't given specifics on how you arrive at the text within the DOM (and I assume you're using the DOM). But given the text node containing the word of interest, something like this should do. I am using a minimal amount jQuery for convenience, it is hardly necessary.

// use jQuery to find the text node "inner text" from your example
let textNode = $("#mydiv i")
    .first()
    .contents()
    .filter(function () {
        return this.nodeType == 3; /* text node */
    }).get(0);

// find the parent element of the text node
let el = textNode;
while (el.parentNode) {
    if (el.nodeType == 1) break; /* element */
    el = el.parentNode;
}
// append the html after the parent of the text node.
 $(el).after(`<div class="highlight">highlight text</div>`);

You can see this in action at this plnkr.

Basically the code gets the text node of the Nth word of interest, finds it's parent element, then inserts the desired html as the first right sibling of the parent element.

Comments

3

In any way, I hope this helps.

NOTE: HTML used is given sample content.
Try splitting your div content and work from that. See comments for explanations:

        //1.Get mydiv content
        //2. Split spaces and newlines
        //3. Remove empty array values
        var div =     $("#mydiv").html().toString().split(/[\s+\n]/).filter(String);

        var allowAdd = true, appendNow;
        for(var a=0; a < div.length ; a++){
            if(div[a].match(/^</) && div[a].match(/>$/)){ //search for end tags ie. </i>, </b>
                if(div[a].match(/<\//)){ //if end tag, 
                    allowAdd = true;    //allow append
                }
            }else if (div[a].match(/</)){ //if start stag,
                allowAdd = false;   //disallow append (inside block)

            }

            if((a+1)%200 == 0){
                //every 200 words
                appendNow = true;
            }

            //append if 200th word and end tag is passed
            if(appendNow && allowAdd){
                div[a] += ' <div class="highlight">highlight text </div> ';
                appendNow = false;
            }
        }

        //join array values and assign value to mydiv content
        $("#mydiv").html(div.join(" ")); 

Comments

3

Here's a code that will give you your desired result. The advantage of this code is that it doesn't check every element whether it's a tag or not by checking if it contains < or </, which in my opinion is good, as our code is not as complicated as it would be, and we don't need to check through all values, whether or not they contain < or </. (Less calculation, should in theory run faster.)

var elements = $("#mydiv").contents(); //Get all contents of #mydiv
$("#mydiv").html(null); //Delete all elements from #mydiv

var count = 0; //This will be counting our elements

for(var i = 0; i < elements.length; i++){ //Go through all elements of #mydiv
    if(elements[i].nodeName == "#text"){ //If the element is a text block, then:

        var textElements = $(elements[i]).text().split(/\s+/g); //Split the text by all the spaces

        for(var j = 0; j < textElements.length; j++){ //Go through all elements inside the text block

            if(textElements[j] == "") continue; //The splitting of the text-block elements is not perfect, so we have to skip some elements manually

            count++; //Add to our count

            $("#mydiv").append(textElements[j] + " "); //Add this element to our cleared #mydiv

            if(count != 0 && count % 200 == 0){ //Every 200 elements, do this:
                $("#mydiv").append(" <span class='highlight'>highlight</span> "); //Add our highlight
            }
        }
    }else{ //If the element is not a text block, then:

        $("#mydiv").append(elements[i]); //Add the non-text element

        count++; //Add to our counter

        if(count != 0 && count % 200 == 0){ //Every 200 elements, do this:
            $("#mydiv").append(" <span class='highlight'>highlight</span> "); //Add our highlight

        }
    }
}

Comments

3

Provided that you are ok with manipulating the HTML of #mydiv as a string, one solution is to use string.replace with a regular expression and a replacement function, such as:

function insertInHTMLEveryNWords(n, htmlString, insertString) {
  var wordCounter = 0,
      tagDepth = 0;
  return htmlString.replace(
    /<(\/?)([^>]+)>|[^<\s]+/g,
    function(match, forwardSlash, tagContent) {
      if (tagContent) { //matched a tag
        tagDepth += forwardSlash ? -1 : 1;
      } else { //matched a word
        wordCounter++;
      }
      if (!tagDepth && wordCounter >= n) {
        //if not inside tag and words are at least n,
        wordCounter = 0;
        return match + insertString;
      } else {
        return match;
      }
    });
}

The following snippet contains a working demo of the approach, simplified to insertion after every 2 words (if not in any tag, or directly after the next possible closing tag, as stated by the OP):

var htmlOriginal = document.getElementById('mydiv').innerHTML;

var htmlWithInsertions = insertInHTMLEveryNWords(
  2,
  htmlOriginal,
  '<div>[Inserted DIV after every 2 words or when not in any tag]</div>'
);

//inspect result in console
console.log(htmlWithInsertions);

//replace html in #mydiv
document.getElementById('mydiv').innerHTML = htmlWithInsertions;

function insertInHTMLEveryNWords(n, htmlString, insertString) {
  var wordCounter = 0,
      tagDepth = 0;
  return htmlString.replace(
    /<(\/?)([^>]+)>|[^<\s]+/g,
    function(match, forwardSlash, tagContent) {
      if (tagContent) { //matched a tag
        tagDepth += forwardSlash ? -1 : 1;
      } else { //matched a word
        wordCounter++;
      }
      if (!tagDepth && wordCounter >= n) {
        //if not inside tag and words are at least n,
        wordCounter = 0;
        return match + insertString;
      } else {
        return match;
      }
    });
}
<div id="mydiv">
  Some text, word3,
  <em>emphasized and <strong>strong</strong></em> text.
</div>

A potential limitation is that manipulating the HTML replaces the existing DOM elements in #mydiv and breaks any previous references to them. If that does not pose any problem, this is a straightforward working solution.

Comments

3
+50

UPDATED The div that I will be adding after every 2nd word.

var some_div = '<div style="display:inline-block;color:red;">some_text</div>';

var text = $('#mydiv').text().match(/\w+/g);

Secondly, traverse through all the words and prefixed these words in the html of the div with a unique identifier text.

Here, I am add a string <index>$$ where <index> increments on each traversal.

var i = 1;
var count = 1;
var html = $('#mydiv').html();

text.forEach(function(word, index) {

  var offset = html.indexOf(word);
  while (html[offset - 1] == '$' && html[offset - 2] == '$') {
    offset = html.indexOf(word, offset + 1);
  }

  if ((count % up_index) == 0) {
    html = html.slice(0, offset) + (i++) + '$$' + html.slice(offset)
    $('#mydiv').html(html);
  }

  count++;
});

Finally, loop through all the unique tokens and replace them with your html.

to find tokens use $('#mydiv').find(':contains(' + j + '$$)'); of jquery.

for (var j = 1; j < i; j++) {
  var elm = $('#mydiv').find(':contains(' + j + '$$)');
  if (elm.length == 0) {
    console.log('inroot>>' + ':contains(' + j + '$$)');
    var offset = $(':contains(' + j + '$$)').last().html().indexOf(j + '$$');
    var t_html = $(':contains(' + j + '$$)').last().html().slice(0, (offset + (("" + j + '$$').length))).replace(/[0-9]\$\$/ig, '');
    t_html += some_div;
    t_html += $(':contains(' + j + '$$)').last().html().slice(offset + (("" + j + '$$').length));
    $('#mydiv').html(t_html);

  } else {
    console.log('not inroot>>' + ':contains(' + j + '$$)');
    $(some_div).insertAfter(elm.last());
  }
}

Here is an Example where I have added div after every 2nd word

Firstly, I am fetching all the words inside the interested container as follows,

var some_div = '<div style="display:inline-block;color:red;">some text</div>';

var up_index = 2; // Word index that will be updated every 2nd word.

var text = $('#mydiv').text().match(/\w+/g);

var i = 1;
var count = 1;
var html = $('#mydiv').html();

text.forEach(function(word, index) {

  var offset = html.indexOf(word);
  while (html[offset - 1] == '$' && html[offset - 2] == '$') {
    offset = html.indexOf(word, offset + 1);
  }

  if ((count % up_index) == 0) {
    html = html.slice(0, offset) + (i++) + '$$' + html.slice(offset)
    $('#mydiv').html(html);
  }

  count++;
});

for (var j = 1; j < i; j++) {
  var elm = $('#mydiv').find(':contains(' + j + '$$)');
  if (elm.length == 0) {
    console.log('inroot>>' + ':contains(' + j + '$$)');
    var offset = $(':contains(' + j + '$$)').last().html().indexOf(j + '$$');
    var t_html = $(':contains(' + j + '$$)').last().html().slice(0, (offset + (("" + j + '$$').length))).replace(/[0-9]\$\$/ig, '');
    t_html += some_div;
    t_html += $(':contains(' + j + '$$)').last().html().slice(offset + (("" + j + '$$').length));
    $('#mydiv').html(t_html);

  } else {
    console.log('not inroot>>' + ':contains(' + j + '$$)');
    $(some_div).insertAfter(elm.last());
  }
}

$('#mydiv').html($('#mydiv').html().replace(/[0-9]\$\$/ig, ''));
.highlight {
  color: red;
  display: inline-block;
}
b {
  background-color: blue;
}
i {
  background-color: yellow;
}
i,
b {
  border: 1px solid green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mydiv">
  sample text
  <b><a>sample text</a></b>  <i>sample text </i>
  ......
  <i>inner text </i>  <i>sample text </i>
  ......
  <b>sample text </b>  <i>sample text </i>
</div>

4 Comments

Sorry I dont want to highlight any words, I want to insert multiple highlight word/sentence in between the current html content. I am expecting insertions.
You can modify the highlight_div string in the code with any HTML you want to insert by replacing the word.
there is no text to replace, we have only the words count
You overlooked a major requirement. "it should not goes inside any of the children tags."
-1

The i tag is an inline element and it isn't possible to insert a block element, as div, inside it. And "highlight" is not a valid attribute for div.

To achieve the insertion, use a span instead of a div. And check which attribute are needed for your goals. The result will be (with id as attribute):

<div id="mydiv">
   sample text sample text sample text...
   ......
   <i>inner<span id="highlight">highlight text</span> text </i> <i>sample text </i>
   ......
   <b>sample text </b> <i>sample text </i>
</div>

Comments

-1

I assume you are already finding the word 200 and appending your div after it, something like this maybe?

varUntilSpace200 + '<div "highlight">highlight text</div>' + restOfInnerHTML;

Then all you need to do is check in restOfInnerHTML if after space 200 there is a

"</"

if so, just append everything from that position to the first

">"

var indof1 = restOfInnerHTML.indexOf("</");
var indof2 = restOfInnerHTML.indexOf("<");
var indof3 = restOfInnerHTML.indexOf(">");

if (indof1 < indof2) {
  varUntilSpace200 += restOfInnerHTML.substring(indof1,indof3);
  restOfInnerHTML = restOfInnerHTML.substring(indof3+1,restOfInnerHTML.length);
}

2 Comments

I tried to find out 200th word by taking the content as string, so it includes tags so I am getting wrong word count too. In your solution indof1 & indof2 will equal when there is a closing tag and it will not enter into the condition, am I right?
sorry, i didn't explain that, the condition is to know if we have steeped into a html tag or if the encountered closing tag belongs to another tag placed ahead of the point where you have to add your div

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.