3

I have a song lyrics and I'm using a function to highlight the parts of the lyrics. The code looks like this:

var span        = {
    intro           : '<span style="color: Magenta ;">',
    verse           : '<span style="color: DodgerBlue;">',
    prechorus       : '<span style="color: DeepPink;">',
    chorus          : '<span style="color: Tomato;">',
    bridge          : '<span style="color: LimeGreen;">',
    outro           : '<span style="color: Magenta ;">',
    repeat          : '<span style="color: Silver;" title="Repeat">',
    close           : '</span>',
};

function highlight(lyrics) {
    var highlighted = lyrics.replace(/\[.*\]/g, function(match) {
        switch(match) {
            case "[Intro]":         return span.io              + match + span.close;   break;
            case "[Verse 1]":       return span.verse           + match + span.close;   break;
            case "[Verse 2]":       return span.verse           + match + span.close;   break;
            case "[Verse 3]":       return span.verse           + match + span.close;   break;
            case "[Verse 4]":       return span.verse           + match + span.close;   break;
            case "[Verse 5]":       return span.verse           + match + span.close;   break;
            case "[Pre-Chorus 1]":  return span.prechorus       + match + span.close;   break;
            case "[Pre-Chorus 2]":  return span.prechorus       + match + span.close;   break;
            case "[Pre-Chorus 3]":  return span.prechorus       + match + span.close;   break;
            case "[Pre-Chorus 4]":  return span.prechorus       + match + span.close;   break;
            case "[Chorus 1]":      return span.chorus          + match + span.close;   break;
            case "[Chorus 2]":      return span.chorus          + match + span.close;   break;
            case "[Chorus 3]":      return span.chorus          + match + span.close;   break;
            case "[Chorus 4]":      return span.chorus          + match + span.close;   break;
            case "[Chorus 5]":      return span.chorus          + match + span.close;   break;
            case "[Bridge 1]":      return span.bridge          + match + span.close;   break;
            case "[Bridge 2]":      return span.bridge          + match + span.close;   break;
            case "[Outro]":         return span.io              + match + span.close;   break;
        }
    });
    return highlighted.replace(/\(R.{0,3}\)/g, span.repeat + "$&" + span.close);
}

highlight function expects a text. First, it finds the strings (headers of lyrics parts) that are enclosed by brackets, then using switch, it checks the found matches and wraps them in a corresponding span tag.

My problem is that I don't want to use five different cases to replace verses, or any other repeating headers. Instead I want to use a regexp. Something like:

case /\[Verse.*?\]/g: return span.verse + match + span.close; break;

Fiddle

akinuri
  • 10,690
  • 10
  • 65
  • 102
  • Is there a reason for using both CSS classes and inline styles? Do you not have access to the CSS, and a CSP blocks a custom CSS source? – brianary Aug 16 '14 at 01:03
  • @brianary Nope. My mistake. That is an old script. I used to use CSS to style the headers. Not anymore. I forgot to remove them. Updated the question. – akinuri Aug 16 '14 at 01:11
  • This gets much easier if you can move the inline CSS into a stylesheet, or even programmatically add the styles. Inline styles are less efficient and maintainable anyway. – brianary Aug 16 '14 at 01:17
  • Yea, I know. I'm planing to store the styles in an object and add them with JS. I'll do that once the application I'm working on is done. It's just that it's too easy to do, that I ignore it for now =) – akinuri Aug 16 '14 at 01:23

6 Answers6

3

Use an object holding the regular expressions, instead of a switch statement.

var highlighted = lyrics.replace(/\[.*\]/g, function (match) {

    var regex = {
        intro: /\[Intro.*?\]/g,
        verse: /\[Verse.*?\]/g,
        prechorus: /\[Pre-Chorus.*?\]/g,
        chorus: /\[Chorus.*?\]/g,
        bridge: /\[Bridge.*?\]/g,
        outro: /\[Outro.*?\]/g
    };

    for (var k in regex) {
        if (regex.hasOwnProperty(k)) {
            if (regex[k].test(match)) {
                return span[k] + match + span.close;
            }

        }
    }

});

JSFiddle example

http://jsfiddle.net/unwthvns/1/

Switch statements can usually always be replaced with an object in JavaScript. No need for them.

Miguel Mota
  • 20,135
  • 5
  • 45
  • 64
  • I thought about using an obj to to hold the regular expressions, but I still used a switch statement, and used a function (as a case) to do the comparison. Though I solved my problem, I didn't want to delete the question in case I find a better approach. I like your method. It's much better. But I have a question. If I move the regexp object outside of `highlight` function (make it global), some headers doesn't highlight. Any idea why? Example: [2nd chorus after verse 2 (fiddle)](http://jsfiddle.net/unwthvns/2/) – akinuri Aug 16 '14 at 01:09
  • 1
    `test` sets an index on the regex object keeping track of the last match. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex To get around it, remove the `g` flag, or reset `lastIndex` to zero, or use `!!match.match(regex)`. http://jsfiddle.net/moogs/unwthvns/3/ – Miguel Mota Aug 16 '14 at 05:03
  • Having the object inside the function worked because it's a fresh object every time so the lastIndex is always zero. I recommend just removing the g flag since all you really care is if matches at least once. – Miguel Mota Aug 16 '14 at 05:05
1

You could also use the string match method. This would have each case match the header against a regex. If the header does not match the regex, the case will fail and move on. For example:

case header.match(/\[Verse.*?\]/): return ...

Check out this post for some examples of how to use regular expressions in switch cases.

Community
  • 1
  • 1
Reggie
  • 413
  • 2
  • 9
  • 19
1

You may not need a case statement or even a lookup object here.

If you could modify the CSS or provide your own to change the selectors and move the inline styles into the CSS (where they really belong anyway), you could drastically simplify to:

function highlight(lyrics) {
    return lyrics.replace(/(\[([-a-z]+\b)(?: \d)?\])/gi,'<span class="$2">$1</span>');
}

You can tighten up the regex as neccessary if A-Z and dash happen to catch too much.

You could use a callback function similar to this replacement string if you want all-lowercase class names, want to coalesce the intro and outro into a single class, or remove dashes from the class names.

brianary
  • 8,996
  • 2
  • 35
  • 29
0

You can simply let the case fall through:

case "[Verse 1]":
case "[Verse 2]":
case "[Verse 3]":
case "[Verse 4]":
case "[Verse 5]": return span.verse + match + span.close; break;

or you have to use an if...else statement.

case values can be any expression, but the result is compared using strict comparison with the value passed to the switch statement, so you'd end up doing /\[Verse.*?\]/g === match.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
0

I would do it as

var html = format( s ,
  /\[Verse.*?\]/g,      "<span style='color: limegreen;'>@</span>",
  /\[Pre-Chorus.*?\]/g, "<span style='color: Magenta;'>@</span>",
  /\[Chorus.*?\]/g,     "<span style='color: DeepPink;'>@</span>"
 );

where format() is as simple as

function format(s) {
    for( var i = 1; i < arguments.length; i += 2 )
      if( var m = s.match( arguments[ i ] ) )
        return arguments[ i + 1 ].replace("@", m[ 1 ]);
    return "";
 }

The above is not tested but idea should be clear I think.

c-smile
  • 26,734
  • 7
  • 59
  • 86
0
switch (myVar) {
   case 'case1':
      /...do work
      break
   case /[a-z]*/.test(myVar) && myVar:
      /...regex match, do work
      break
}
Mime
  • 1,142
  • 1
  • 9
  • 20
Cory Robinson
  • 4,616
  • 4
  • 36
  • 53