Skip to content

Commit

Permalink
Make original text smilies selectable (closes #2)
Browse files Browse the repository at this point in the history
* Make smilies a background image of an <abbrev> element with transparent text.
  This leaves the original text in place, so copying and pasting will keep the
  original text smiley
* Updated tests. Now they're probably a bit more fragile than they were, but it
  shouldn't be too bad.
  • Loading branch information
emanchado committed Nov 25, 2011
1 parent f55a571 commit bcdba4a
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 246 deletions.
69 changes: 41 additions & 28 deletions includes/smileyParser.js
@@ -1,33 +1,46 @@
var SmileyParser = function(smileyTable) { this.init(smileyTable); };
(function ()
{
this._smileyTable = {};
this.init = function(smileyTable) {
this._smileyTable = smileyTable || defaultSmileyTable;
this._handlers = [];
for (var smiley in this._smileyTable) {
if (this._smileyTable.hasOwnProperty(smiley)) {
var self = this;
this._handlers.push({pattern: new RegExp('(["a-zA-Z0-9#])?' + smiley, "g"),
replacement: (function (s) {
return function(text, lb) {
if (lb) return document.createTextNode(text);
var img =
document.createElement('img');
img.src = self._smileyTable[s];
img.alt = img.title =
text.replace(/&/g, '&amp;').
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
return img;
};
})(smiley)});
}
}
};
this._smileyTable = {};
this.init = function(smileyTable) {
/* Returns a function that receives the matched text and the
first matched group in the regular expression. The first group
is always a fake lookbehind. If there's anything, we should
return the text back, without creating any graphical smiley */
function replaceFunction(s) {
return function(text, lb) {
if (lb) return document.createTextNode(text);
var smileyInfo = self._smileyTable[s];
var smileyDom = document.createElement('abbrev');
smileyDom.innerHTML = text.replace(/&/g, '&amp;').
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
var url = "data:image/png;base64," + smileyInfo.imageDataBase64;
smileyDom.style.backgroundImage = "url(" + url + ")";
if (smileyInfo.height !== undefined)
smileyDom.style.height = smileyInfo.height + "px";
if (smileyInfo.width !== undefined)
smileyDom.style.width = smileyInfo.width + "px";
smileyDom.style.display = 'inline-block';
smileyDom.style.color = 'transparent';
smileyDom.style.overflow = 'hidden';
return smileyDom;
};
}
this._smileyTable = smileyTable || defaultSmileyTable;
this._handlers = [];
for (var smiley in this._smileyTable) {
if (this._smileyTable.hasOwnProperty(smiley)) {
var self = this;
this._handlers.push({pattern: new RegExp('(["a-zA-Z0-9#])?' +
smiley, "g"),
replacement: replaceFunction(smiley)});
}
}
};

this.parseSmileys = function(element) {
replaceTextWithElements(element, {excludedTags: /textarea/,
handlers: this._handlers});
};
this.parseSmileys = function(element) {
replaceTextWithElements(element, {excludedTags: /textarea/,
handlers: this._handlers});
};
}).call(SmileyParser.prototype);
16 changes: 7 additions & 9 deletions test.html
Expand Up @@ -3,14 +3,6 @@
<head>
<title>Smiley parsing test</title>
<meta charset='utf-8'>
<script src="includes/defaultSmileyTable.js" />
<script src="includes/smileyParser.js" />
<script>
window.addEventListener('load', function() {
var myOwnParser = new SmileyParser();
// alert(myOwnParser.parseSmileys('lol'));
}, false);
</script>
</head>
<body style="background-color: #ddd">
<div class="commentContent">o_o O_O O_o o_O / foo fOo food oO Oo oo OO o-o</div>
Expand All @@ -21,10 +13,16 @@
<div class="commentContent">&lt;/troll&gt; &lt;/trolling&gt; :-7 / -troll- *troll* troll</div>
<div class="commentContent">^_^ ;) ;-)) :-P ;-P :P ;P :) :-) :))) :( :(((( :-( ;p ;P ;d ;D / ;proper ;Proper ;dice ;Dice</div>
<div class="commentContent">:? :-? :??? :-??? ;'( :'( :'(( :C :-C / :c :-c</div>
<div class="commentContent">X'D X-D XD x'D x-D xD :D :-D :DDD / :d :-d</div>
<div class="commentContent">X'D X-D XD x'D x-D xD :D :-D :DDD :-DDDDDDDDDDDDDDDDDDD / :d :-d</div>
<div class="commentContent">lol LOL лол / LoL lOl trolol trolololo лоласо тролололо трололол</div>
<div class="commentContent"><a href=" lol ">link lol</a></div>

<h1>Random test</h1>

<div class="comment">
/me doesn't think @ErikRose is a follower on twitter … I can't dm him ;-) See you in the tomorrow with bike shorts on
</div>

<h1>Addons.opera.com comment test</h1>

<div class="comment">
Expand Down
62 changes: 43 additions & 19 deletions test/lib/htmlComparison.js
@@ -1,28 +1,52 @@
function canonicalElementHtml(element) {
var result = "<" + element.nodeName.toLowerCase();
var attrPairs = [];
for (var ai = 0, alen = element.attributes.length; ai < alen; ++ai) {
attrPairs.push([ element.attributes[ai].name,
element.attributes[ai].value ]);
}
// Order the attributes
var orderedAttrPairs = attrPairs.sort(function(a, b) {
if (a[0] === b[0]) return 0;
return (a[0] > b[0]) ? 1 : -1;
});
for (var ai2 = 0, alen2 = orderedAttrPairs.length; ai2 < alen2; ++ai2) {
result += " " + orderedAttrPairs[ai2][0] + '="' + orderedAttrPairs[ai2][1] + '"';
}
if (element.childNodes.length > 0) {
result += ">";
result += canonicalHtml(element) +
"</" + element.nodeName.toLowerCase() + ">";
} else {
result += " />";
}
return result;
}


function canonicalSmiley(element) {
console.log(element.textContent);
return "<smiley url=\"" + element.style.backgroundImage + "\"" +
((element.style.height !== undefined) ?
" height=\"" + element.style.height + "\"" : "") +
((element.style.width !== undefined) ?
" width=\"" + element.style.width + "\"" : "") +
">" + element.textContent.replace(/&/g, '&amp;').
replace(/</g, '&lt;').
replace(/>/g, '&gt;') +
"</smiley>";
}


function canonicalHtml(element) {
var result = "";
var cn = element.childNodes;
for (var i = 0, len = cn.length; i < len; ++i) {
if (cn[i] instanceof Element) {
result += "<" + cn[i].nodeName.toLowerCase();
var attrPairs = [];
for (var ai = 0, alen = cn[i].attributes.length; ai < alen; ++ai) {
attrPairs.push([ cn[i].attributes[ai].name,
cn[i].attributes[ai].value ]);
}
// Order the attributes
var orderedAttrPairs = attrPairs.sort(function(a, b) {
if (a[0] === b[0]) return 0;
return (a[0] > b[0]) ? 1 : -1;
});
for (var ai2 = 0, alen2 = orderedAttrPairs.length; ai2 < alen2; ++ai2) {
result += " " + orderedAttrPairs[ai2][0] + '="' + orderedAttrPairs[ai2][1] + '"';
}
if (cn[i].childNodes.length > 0) {
result += ">";
result += canonicalHtml(cn[i]) +
"</" + cn[i].nodeName.toLowerCase() + ">";
if (cn[i].nodeName.toLowerCase() === 'abbrev') {
result += canonicalSmiley(cn[i]);
} else {
result += " />";
result += canonicalElementHtml(cn[i]);
}
} else {
result += cn[i].nodeValue.replace(/&/g, '&amp;').
Expand Down
91 changes: 59 additions & 32 deletions test/spec/htmlComparisonSpec.js
@@ -1,41 +1,68 @@
function isHtmlEquivalent(one, two) {
var domElement = document.createElement('div');
var domElement2 = document.createElement('div');
domElement.innerHTML = one;
domElement2.innerHTML = two;
return canonicalHtml(domElement) === canonicalHtml(domElement2);
}

describe("canonicalHtml", function() {
var domElement;
beforeEach(function() {
this.addMatchers({
toBeCanonically: function(expected) {
return isHtmlEquivalent(this.actual, expected);
},
toNotBeCanonically: function(expected) {
return ! isHtmlEquivalent(this.actual, expected);
}
});
});

beforeEach(function() {
domElement = document.createElement('div');
it("should return the same string when there isn't any markup", function() {
var someString = "Some string without any markup";
expect(someString).toBeCanonically(someString);
});

this.addMatchers({
toBeCanonically: function(expected) {
domElement.innerHTML = this.actual;
return this.env.equals_(canonicalHtml(domElement), expected);
}
});
});
it("should return the same string when there are simple elements", function() {
var someString = "Some string with <strong>some</strong> markup";
expect(someString).toBeCanonically(someString);
});

it("should return the same string when there isn't any markup", function() {
var someString = "Some string without any markup";
expect(someString).toBeCanonically(someString);
});
it("should return attributes in alphabetical order", function() {
var source = 'Some <a title="foo" href="http://example.com">link</a>';
var expected = 'Some <a href="http://example.com" title="foo">link</a>';
expect(source).toBeCanonically(expected);
});

it("should return the same string when there are simple elements", function() {
var someString = "Some string with <strong>some</strong> markup";
expect(someString).toBeCanonically(someString);
});
it("should return self-closing tags correctly", function() {
var source = 'An <img src="image.png">';
var expected = 'An <img src="image.png" />';
expect(source).toBeCanonically(expected);
});

it("should return attributes in alphabetical order", function() {
var source = 'Some <a title="foo" href="http://example.com">link</a>';
var expected = 'Some <a href="http://example.com" title="foo">link</a>';
expect(source).toBeCanonically(expected);
});
it("should return HTML entities correctly", function() {
var source = 'A Smith &amp; Wesson weapon';
expect(source).toBeCanonically(source);
});

it("should return self-closing tags correctly", function() {
var source = 'An <img src="image.png">';
var expected = 'An <img src="image.png" />';
expect(source).toBeCanonically(expected);
});
it("should recognise a simple smiley correctly", function() {
var source1 = 'A smiley: <abbrev style="background-image: url(foo.png); width: 30px; height: 25px">some text</abbrev>';
var source2 = 'A smiley: <abbrev style="background-image: url(foo.png); height: 25px; width: 30px">some text</abbrev>';
var source3 = 'A smiley: <abbrev style="background-image: url(foo.png); height: 25px; width: 30px">some other text</abbrev>';
expect(source1).toBeCanonically(source2);
expect(source1).toNotBeCanonically(source3);
expect(source2).toNotBeCanonically(source3);
});

it("should return HTML entities correctly", function() {
var source = 'A Smith &amp; Wesson weapon';
expect(source).toBeCanonically(source);
});
it("should recognise a smiley w/ extra properties as different", function() {
var source1 = 'A smiley: <abbrev style="background-image: url(foo.png); width: 30px">some text</abbrev>';
var source2 = 'A smiley: <abbrev style="background-image: url(foo.png); width: 30px; height: 25px">some text</abbrev>';
expect(source1).toNotBeCanonically(source2);
});

it("should recognise a smiley w/ angle brackets", function() {
var source1 = 'A smiley: <abbrev style="background-image: url(foo.png); height: 25px; width: 30px">&lt;/troll&gt;</abbrev>';
var source2 = 'A smiley: <abbrev style="background-image: url(foo.png); width: 30px; height: 25px">&lt;/troll&gt;</abbrev>';
expect(source1).toBeCanonically(source2);
});
});

0 comments on commit bcdba4a

Please sign in to comment.