0

How can I replace a string in a paragraph with another string without using the .replace() method of javascript, and the reason I can not use the .replace() method is because it makes the replacement only with the first match of the word in the string, To put in context a little bit more why i can not use .replace(), im trying the create a marking function where the user is gonna be able to marker text in a document, the marker is gonna be storage on the server side, So i request for the markers of the documents, the marker object contain the start and the end position of the selection in the string, so i take those to values to extract the text and wrapped in to a mark tag, after that i need to replace the text already with the mark tag in to the original text, but there is the case where the user can select a word that is more than one time in the document, so i can not use replace, so i need to use the start and end position of the selection to .replace() the text, for example:

Lorem ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus.

and I want to replace the 3th 'Lorem' for 'Space' without touch the rest i can not use .replace(). Is there any other way to this, maybe using start and end position of a cursor selection?

  /**
  * Count the position and the number of the html tags
  * to position the marker.
  * @param {object} The chapter object
  * @param {object} The marker object
  * @return {object} Return the chapter with the marker on it.
  */
  public characterCounter(chapter, marker): any {
    const counters: any = {
      htmlSimbol: null, // < or >
      characters: 0,
      startTags: 0, // count html until the start of the selection.
      endTags: 0 // count html until the end of the selection.
    };

    /* Here  we loop the blocks to find the righ
    *  one to place marker.
    */
    chapter.blocks.some((block) => {
      if (marker.blockId === block.blockId) {
        /*
        * Ones the block is founded start counting the
        * characters from the string.
        */
        const blockText = block.blockElement.text;
        for (let i = 0; i < blockText.length; i++) {
          /* when the loop reach the end of the selection
          * and match with the number of characters counted
          * that are not part of html tag the loop stop.
          */
          if (counters.characters === marker.end) {
            break;
          } else {
            /*
            * if the loop crash wiht < then start counting
            * characters as part of the html tags and stop
            * when crash with >
            */
            if (blockText[i] === '<' || blockText[i] === '>') {
              counters.htmlSimbol = blockText[i];
            } else if (blockText[i] !== '<' && blockText[i] !== '>' && counters.htmlSimbol !== null) {
              if (counters.htmlSimbol === '<') {
                if (counters.characters <= marker.start) {
                  counters.startTags += 1; // count the characters from the open html tags founded
                } else {
                  counters.endTags += 1; // count the characters from the close html tags founded
                }
              } else if (counters.htmlSimbol === '>') {
                if (counters.characters <= marker.start) {
                  counters.startTags += 2;  // count the characters from the open html tags founded
                } else {
                  counters.endTags += 2; // count the characters from the close html tags founded
                }
                counters.htmlSimbol = null; // change to null to know that is the end ot he html tag
              }
            } else if (counters.htmlSimbol === null) {
              counters.characters += 1; // count the characters that are not part of html tag ( only text no html).
            }
          }
        }

        // Here is where i extract the text selected and wrapped in to the mark tag to then replaced on the original block of text

        counters.endTags += counters.startTags;
        marker.start += counters.startTags;
        marker.end += counters.endTags;

        const textSelected = blockText.substring(marker.start, marker.end);

        const markerText = `<mark class="${marker.color}" *ngClass=""  alt="${marker.id}">${textSelected}</mark>`;

        block.blockElement.text = blockText.replace(textSelected, markerText);

        return false;
      }
    });
    return chapter;
  }

  /**
  * Analyze the text block and add the marking
  * to the right block of text.
  * @param {array} The list of markers
  * @param {array} The array of blocks
  * @return {array} the array of block with the markers.
  */
  public addMarking(markersList, chaptersArray): any {
    let index = 0;
    markersList.some((marker) => {
      index = chaptersArray.map((e) => e.id).indexOf(marker.chapterId);
      if (index > -1) {
        this.characterCounter(chaptersArray[index], marker);
        return false;
      } else {
        chaptersArray.some((chapter) => {
          index = chapter.subChapters.map((e) => e.id).indexOf(marker.chapterId);
          if (index > -1) {
            this.characterCounter(chapter.subChapters[index], marker);
            return false;
          }
        });
      }
    });
    return chaptersArray;
  }

the problem is after i have the mark tag applied, after i apply the mark tag to the selected text, i need to replace the original text with the one with mark tag, as you can see in the code im using .replace(), so i have the problem that the user select a word the is more than one time in a long paragraph so the text is replaced on the wrong place.

Miguel Frias
  • 2,544
  • 8
  • 32
  • 53
  • 1
    please share your code template. – Harsh Jaswal May 11 '18 at 10:32
  • You have tried to describe your solution in the question. Can you try to describe the exact problem that you are solving in the first place as well? Are you trying to simply add `mark` tag around the user-selected pieces of text, or is there some further replacement you need to do after having the `mark` tags applied? – Freeman Lambda May 11 '18 at 10:50
  • 2
    Check this answer. You can use a regex like this in your replace method. https://stackoverflow.com/a/25312221/5358917 – Kavindra May 11 '18 at 10:52
  • @Kavindra thanks so much, this could work, but i think that in this case i gonna have to count how many time the word repeat´s in the string and check in which one was the selection made, that is a extra work i would like to avoid – Miguel Frias May 11 '18 at 11:01
  • @FreemanLambda the problem is after i have the `mark` tag applied, after i apply the `mark` tag to the selected text, i need to replace the original text with the one with `mark` tag, as you can see in the code im using `.replace()`, so i have the problem that the user select a word the is more than one time in a long paragraph so the text is replaced on the wrong place. – Miguel Frias May 11 '18 at 11:02
  • What is preventing you from applying the mark tag on the original text from the start? – Freeman Lambda May 11 '18 at 11:04
  • The problem is not applying the `mark` on the original text, the problem is that if the word repeats 20 time in the string(paragraph) and i want to replace the number 14 for example because was the one that was highlight for the user i can not do it with `replace()`. – Miguel Frias May 11 '18 at 11:09
  • Something like this but, but no that ugly [Replace string in specific position](https://stackoverflow.com/questions/2236235/how-to-replace-a-string-at-a-particular-position) this is something that can work but is to hard coded. this will be the case, i have start and end position of the selection on the string (20 to 35), so i need to place/replace the marker/string in that position. – Miguel Frias May 11 '18 at 11:23

4 Answers4

1

I tried to implement a solution without using replacement, but rather iterating once over the markers and the original text, and in the meantime building the "new text" with markers applied. For simplicity, I used the tag mark, hard coded in the solution. You could fairly easily parametrize this aspect.

// suppose we are given the original text, and the markers as start-end pairs of selection character positions
const text = `Lorem ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus.`;
const markers = [{start: 77, end: 82}, {start: 40, end: 45}]; // positions defining the 2nd and the 3rd occurence of Lorem, chosen arbitrarily

function markSelections(text, markers) {
  // sort markers just in case the are not sorted from the Storage
  const sortedMarkers = [...markers].sort((m1, m2) => m1.start - m2.start);
  let markedText = '';
  let characterPointer = 0;
  sortedMarkers.forEach(({start, end}) => {
    markedText += text.substring(characterPointer, start);
    markedText += '<mark>';
    markedText += text.substring(start, end);
    markedText += '</mark>';
    characterPointer = end;
  });
  // add the remaining text after markers
  markedText += text.substring(characterPointer);
  return markedText;
}
document.write(markSelections(text, markers));
mark {
  color: red;
}
Freeman Lambda
  • 3,567
  • 1
  • 25
  • 33
  • i found a small issue with this code, because you are not attaching the rest of the string after place the last marker is working, but if the rest of the string is attached then i does not work any more. – Miguel Frias May 11 '18 at 12:29
0

You can do that by using the following regular expression :

/(?!^)lorem/gi

And use a callback function in the String.prototype.replace function. That callback function will increment the previousMatches variable and in case it matches de specificed position, it will return the replacement.

In the snippet, I'm only replacing the 3rd occurence :

var originalContent, searchedWord, replacement, pattern, regExp, newContent, previousMatches = 0;

originalContent = 'Lorem ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus. Lorem ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus.';
document.write("Before : <br/> " + originalContent);
searchedWord = "Lorem";
replacement = "<b>" + searchedWord +"</b>";
pattern = "(?!^)" + searchedWord;    
position = 2; // we will replace the 3rd occurence
regExp = new RegExp(pattern, "gi");
newContent = originalContent.replace(regExp, function (occurence) {
  var toReturn;
  if (previousMatches === position) {
    // we're returning the replacement in this case 
    toReturn = replacement;
  } else {
    // returning searchedWord is similar to not replacing this occurence
    toReturn = searchedWord;
  }
  // incrementing previousMatches
  previousMatches++;
  return toReturn;
});
document.write("<br/><br/> After : <br/> " + newContent);
Guillaume Georges
  • 3,878
  • 3
  • 14
  • 32
  • But here you are replacing all the 'lorem' for **lorem** i need to change only in a specific position, let say only the second 'lorem' – Miguel Frias May 11 '18 at 10:45
0
<!DOCTYPE html>
<html>
<body>

<p>Click the button to set "blue", "house" and "car" to upper-case, in the paragraph below:</p>

<p id="demo">Mr Blue has a blue house and a blue car.</p>

<button onclick="myFunction()">Try it</button>

<script>
function myFunction() {
    var str = document.getElementById("demo").innerHTML; 
    var res = str.replace(/blue|house|car/gi, function (x) {
        return x.toUpperCase();
    });
    document.getElementById("demo").innerHTML = res;
}
</script>

</body>
</html>
0

I would just take the text, split it on spaces to get the exact words and then filter for the word and get the current index. As you have the indexes you are able to exchange a specific word at an index with whatever you desire and afterwards you can just join the array together again.

Something like this:

const text = "Lorem ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus.";

function exchangeWordInText(lookupWord, nr, text, newWord){

  const lookupTable = [];
  const wordArray = text.split(' ');

  wordArray.filter((word, index) => {
    if(word === lookupWord){
      lookupTable.push({ index: index , word: word});
    }
  });

  if(nr > 0 && nr <= lookupTable.length){
      const exchangeWord = lookupTable[nr-1];
      wordArray[exchangeWord.index] = newWord;
  }

  return wordArray.join(' ');
}

// This would exchange the first 'Lorem' in the text with 'CHANGED'
const result= exchangeWordInText('Lorem', 1, text, 'CHANGED');

RESULTS IN:

"CHANGED ipsum dolor sit amet, consectetur Lorem adipiscing elit. Vivamus ipsum Lorem eros, interdum ac cursus et, pulvinar Lorem at est. Integer nec rutrum ligula. In dignissim est a elit porttitor finibus."

It is not the most beautiful solution and it can for sure be enhanced (it's also case sensitive rightnow) but it might work for you! :)

JSBIN for checking: https://jsbin.com/lepuyifele/1/edit?js,console,output

kr, Georg

Burgi0101
  • 66
  • 5