Dive Into Greasemonkey

Teaching an old web new tricks

5.6. Case study: Frownies

Converting graphical smilies to text

Frownies came to be in response to a joke. Someone on the Greasemonkey mailing list announced that they had developed a user script to convert ASCII “smilies” like :-) to their graphical equivalents. Someone else responded, wondering how long it would take for someone to do the reverse: convert graphical smilies back to text.

For the record, it took me about 20 minutes. Most of the time was spent researching publishing software that auto-generated graphical smilies, and compiling a comprehensive list of variations.

This script relies on the fact that most publishing software that generates graphical smilies puts the text equivalent in the alt attribute of the <img> element. So really what this script does is replace images with their ALT text, if the ALT text matches one of a list of pre-defined constants.

Example: frownies.user.js

// ==UserScript==
// @name          Frownies
// @namespace     http://diveintogreasemonkey.org/download/
// @description   convert graphical smilies to their text equivalents
// @include       *
// ==/UserScript==

var smilies, images, img, replacement;
smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
    ":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
    "X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
    ":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
    "I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
    ":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
    "8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
    "/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
    "#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
    "[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
    "B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
    "#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
    ":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
    ":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
    ":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
    ":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
    ":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
    ":MRGREEN:"];
images = document.evaluate(
    '//img[@alt]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
for (var i = 0; i < images.snapshotLength; i++) {
    img = images.snapshotItem(i);
    alt = img.alt.toUpperCase();
    for (var j in smilies) {
        if (alt == smilies[j]) {
            replacement = document.createTextNode(alt);
            img.parentNode.replaceChild(replacement, img);
        }
    }
}

The code breaks down into four steps:

  1. Define a list of smilies (as text strings).
  2. Find all the images on the page that contain an alt attribute.
  3. For each image, check whether the ALT text matches any of the list of ASCII smilies.
  4. If it matches, replace the <img> element with a text node that contains only the ASCII smily.

The first step simply defines a list, using Javascript's [ ] syntax.

smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
    ":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
    "X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
    ":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
    "I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
    ":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
    "8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
    "/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
    "#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
    "[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
    "B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
    "#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
    ":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
    ":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
    ":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
    ":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
    ":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
    ":MRGREEN:"];

Next I search the page for all <img> elements with an alt attribute, using an XPath query. See Doing something for every element with a certain attribute for more details on XPath queries.

images = document.evaluate(
    '//img[@alt]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);

Step 3 loops through all these <img> elements, and checks whether the alt attribute matches any of my defined smilies. Because some smilies contain letters, I use the toUpperCase() method to convert the alt attribute to uppercase before comparing.

for (var i = 0; i < images.snapshotLength; i++) {
    img = images.snapshotItem(i);
    alt = img.alt.toUpperCase())
    for (var j in smilies) {
        if (alt == smilies[j]) {
            // ...
        }
    }
}

Finally, I create a new text node with the text of the smily, and replace the existing <img> element. See Replacing an element with new content for more details.

            replacement = document.createTextNode(alt);
            img.parentNode.replaceChild(replacement, img);

Download

← Case study: Dumb Quotes
Case study: Zoom Textarea →