Aaron Silber
Aaron Silber

Reputation: 81

Replace string when it matches a key in a key/value pair (with its corresponding value)

I'm trying to use javascript/jQuery to wrap any abbreviations in a paragraph in a <abbr title=""> tag.

For example, in a sentence like, The WHO eLENA clarifies guidance on life-saving nutrition interventions, and assists in scaling up action against malnutrition, WHO and eLENA would both be wrapped in an <abbr> tag. I'd like the title attribute to display the extended version of the abbreviation; i.e. WHO = World Health Organization.

Whats the best way of accomplishing this? I'm a bit new to javascript/jQuery so I'm fiddling in the dark here. So far I've created a variable that contains all the abbreviations as key/value pairs, and I can replace a specific instance of an abbreviation, but not much else.

Upvotes: 4

Views: 643

Answers (3)

RobG
RobG

Reputation: 147473

Firstly, this should really be done on the server, doing it on the client is very inefficient and much more prone to error. But having said that...

You can try processing the innerHTML of the element, but javascript and regular expressions are really bad at that.

The best way is to use DOM methods and parse the text of each element. When a matching word is found, replace it with an abbr element. This requires that where a match is found in a text node, the entire node is replaced because what was one text node will now be two text nodes (or more) either side of an abbr element.

Here is a simple function that goes close, but it likely has foibles that you need to address. It works on simple text strings, but you'll need to test it thoroughly on more complex strings. Naturally it should only ever be run once on a particular node or abbreviations will be doubly wrapped.

var addAbbrHelp = (function() {
  var abbrs = {
      'WHO': 'World Health Organisation', 
      'NATO': 'North Atlantic Treaty Organisation'
  };

  return function(el) {
    var node, nodes = el.childNodes;
    var word, words;
    var adding, text, frag;
    var abbr, oAbbr = document.createElement('abbr');
    var frag, oFrag = document.createDocumentFragment()

    for (var i=0, iLen=nodes.length; i<iLen; i++) {
      node = nodes[i];

      if (node.nodeType == 3) { // if text node
        words = node.data.split(/\b/);
        adding = false;
        text = '';
        frag = oFrag.cloneNode(false);

        for (var j=0, jLen=words.length; j<jLen; j++) {
          word = words[j];

          if (word in abbrs) {
            adding = true;

            // Add the text gathered so far
            frag.appendChild(document.createTextNode(text));
            text = '';

            // Add the wrapped word
            abbr = oAbbr.cloneNode(false);
            abbr.title = abbrs[word];
            abbr.appendChild(document.createTextNode(word));
            frag.appendChild(abbr);

          // Otherwise collect the words processed so far
          } else {
            text += word;
          }
        }

        // If found some abbrs, replace the text 
        // Otherwise, do nothing
        if (adding) {
         frag.appendChild(document.createTextNode(text));
         node.parentNode.replaceChild(frag, node);
        }

      // If found another element, add abbreviation help
      // to its content too
      } else if (node.nodeType == 1) {
        addAbbrHelp(node);
      }
    }
  }
}());

For the markup:

<div id="d0">
  <p>This is the WHO and NATO string.</p>
  <p>Some non-NATO forces were involved.</p>
</div>

and calling:

addAbbrHelp(document.getElementById('d0'));

results in (my formatting):

<div id="d0">
  <p>This is the<abbr title="World Health Organisation">WHO</abbr>
  and <abbr title="North Atlantic Treaty Organisation">NATO</abbr>
  string.</p>
  <p>Some non-<abbr title="North Atlantic Treaty Organisation">NATO</abbr> forces were involved.</p>
</div>

Using the word break pattern to split words is interesting because in strings like "with non–NATO forces", the word NATO will still get wrapped but not the "non–" part. However, if the abbreviation is split across a text node or by a hyphen, it will not be recognised unless the same pattern is included as a property name in the abbrs object.

Upvotes: 1

Sig
Sig

Reputation: 5188

Check out the javascript replace method.

I'd use JQuery to pull out all the text in the paragraph

var text = $(p#paragraphId).html()

Use a for loop to loop through the list of abbreviations you have and then use the replace() method mentioned above to swap out the abbreviation for the tag you need.

Finally use JQuery to set the html of the paragraph back to your newly updated string.

Upvotes: 0

mgiuca
mgiuca

Reputation: 21357

First you must decide exactly what criteria you will use for selecting a replacement -- I would suggest doing it on a word boundary, such that "I work with WHO" will wrap "WHO" in an abbr, but "WHOEVER TOUCHED MY BIKE WILL REGRET IT" won't abbreviate "WHO". You should also decide if you are going to be case sensitive (probably you want to be, so that "The guy who just came in" doesn't abbreviate "who".)

  1. Use jQuery to recurse over all of the text in the document. This can be done using the .children selector and stepping through elements and reading all the text.
  2. For each text node, split the text into words.
  3. For each word, look it up in your key value store to see if it matches a key. If so, get the value, and construct a new element <abbr title="value">key</abbr>.
  4. Break up the text node into a) the text before the abbreviation (a text node), b) the abbreviation itself (an element), and c) the text after the abbreviation (a text node). Insert all three as child nodes of the original text node's parent, replacing the original text node.

Each of these steps will require a bit of work and looking up some API docs, but that is the basic process.

Upvotes: 1

Related Questions