Improves FreeTextProcessor.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 25 Sep 2014 22:58:27 +0000 (23:58 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 10 Oct 2014 20:22:44 +0000 (21:22 +0100)
* Removes module wrapper.
* Adds basic tests.
* Adds very ugly normalization (pending refactoring).

lib/text/free-text-processor.js [new file with mode: 0644]
lib/text/free.js [deleted file]
test/text/free-text-processor-test.js [new file with mode: 0644]

diff --git a/lib/text/free-text-processor.js b/lib/text/free-text-processor.js
new file mode 100644 (file)
index 0000000..a7cfe91
--- /dev/null
@@ -0,0 +1,72 @@
+var EscapeStore = require('./escape-store');
+var QuoteScanner = require('./quote-scanner');
+
+var FreeTextProcessor = function FreeTextProcessor() {
+  this.matches = new EscapeStore('FREE_TEXT');
+};
+
+// Strip content tags by replacing them by the a special
+// marker for further restoring. It's done via string scanning
+// instead of regexps to speed up the process.
+FreeTextProcessor.prototype.escape = function(data) {
+  var self = this;
+
+  return new QuoteScanner(data).each(function(match, store) {
+    var placeholder = self.matches.store(match);
+    store.push(placeholder);
+  });
+};
+
+function normalize(text, data, cursor) {
+  // FIXME: this is a hack
+  var lastSemicolon = data.lastIndexOf(';', cursor);
+  var lastOpenBrace = data.lastIndexOf('{', cursor);
+  var lastOne = 0;
+
+  if (lastSemicolon > -1 && lastOpenBrace > -1)
+    lastOne = Math.max(lastSemicolon, lastOpenBrace);
+  else if (lastSemicolon == -1)
+    lastOne = lastOpenBrace;
+  else
+    lastOne = lastSemicolon;
+
+  var context = data.substring(lastOne + 1, cursor);
+
+  if (/\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context))
+    text = text.replace(/\\\n|\\\r\n/g, '');
+
+  if (/^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/.test(text) && !/format\($/.test(context)) {
+    var isFont = /^(font|font\-family):/.test(context);
+    var isAttribute = /\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context);
+    var isKeyframe = /@(-moz-|-o-|-webkit-)?keyframes /.test(context);
+    var isAnimation = /^(-moz-|-o-|-webkit-)?animation(-name)?:/.test(context);
+
+    if (isFont || isAttribute || isKeyframe || isAnimation)
+      text = text.substring(1, text.length - 1);
+  }
+
+  return text;
+}
+
+FreeTextProcessor.prototype.restore = function(data) {
+  var tempData = [];
+  var cursor = 0;
+
+  for (; cursor < data.length;) {
+    var nextMatch = this.matches.nextMatch(data, cursor);
+    if (nextMatch.start < 0)
+      break;
+
+    tempData.push(data.substring(cursor, nextMatch.start));
+    var text = normalize(this.matches.restore(nextMatch.match), data, nextMatch.start);
+    tempData.push(text);
+
+    cursor = nextMatch.end;
+  }
+
+  return tempData.length > 0 ?
+    tempData.join('') + data.substring(cursor, data.length) :
+    data;
+};
+
+module.exports = FreeTextProcessor;
diff --git a/lib/text/free.js b/lib/text/free.js
deleted file mode 100644 (file)
index 97f24e5..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-(function() {
-  var EscapeStore = require('./escape-store');
-  var QuoteScanner = require('./quote-scanner');
-
-  var Free = function Free() {
-    this.matches = new EscapeStore('FREE_TEXT');
-  };
-
-  // Strip content tags by replacing them by the a special
-  // marker for further restoring. It's done via string scanning
-  // instead of regexps to speed up the process.
-  Free.prototype.escape = function(data) {
-    var self = this;
-
-    return new QuoteScanner(data).each(function(match, store) {
-      var placeholder = self.matches.store(match);
-      store.push(placeholder);
-    });
-  };
-
-  Free.prototype.restore = function(data) {
-    return data.replace(this.matches.placeholderRegExp, this.matches.restore);
-  };
-
-  module.exports = Free;
-})();
diff --git a/test/text/free-text-processor-test.js b/test/text/free-text-processor-test.js
new file mode 100644 (file)
index 0000000..0fea779
--- /dev/null
@@ -0,0 +1,69 @@
+var vows = require('vows');
+var assert = require('assert');
+var FreeTextProcessor = require('../../lib/text/free-text-processor');
+
+function processorContext(context) {
+  var vowContext = {};
+
+  function escaped (targetCSS) {
+    return function (sourceCSS) {
+      var result = new FreeTextProcessor().escape(sourceCSS);
+      assert.equal(result, targetCSS);
+    };
+  }
+
+  function restored (targetCSS) {
+    return function (sourceCSS) {
+      var processor = new FreeTextProcessor();
+      var result = processor.restore(processor.escape(sourceCSS));
+      assert.equal(result, targetCSS);
+    };
+  }
+
+  for (var key in context) {
+    vowContext[key] = {
+      topic: context[key][0],
+      escaped: escaped(context[key][1]),
+      restored: restored(context[key][2])
+    };
+  }
+
+  return vowContext;
+}
+
+vows.describe(FreeTextProcessor)
+  .addBatch(
+    processorContext({
+      'no quotes': [
+        'a{color:red;display:block}',
+        'a{color:red;display:block}',
+        'a{color:red;display:block}'
+      ],
+      'single quoted': [
+        'a{color:red;content:\'1234\';display:block}',
+        'a{color:red;content:__ESCAPED_FREE_TEXT_CLEAN_CSS0__;display:block}',
+        'a{color:red;content:\'1234\';display:block}'
+      ],
+      'double quoted': [
+        'a{color:red;content:"1234";display:block}',
+        'a{color:red;content:__ESCAPED_FREE_TEXT_CLEAN_CSS0__;display:block}',
+        'a{color:red;content:"1234";display:block}'
+      ],
+      'inside format': [
+        '@font-face{font-family:X;src:X.ttf format(\'opentype\')}',
+        '@font-face{font-family:X;src:X.ttf format(__ESCAPED_FREE_TEXT_CLEAN_CSS0__)}',
+        '@font-face{font-family:X;src:X.ttf format(\'opentype\')}'
+      ],
+      'attribute': [
+        'a[data-type="search"]{}',
+        'a[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{}',
+        'a[data-type=search]{}'
+      ],
+      'font name': [
+        'a{font-family:"Times","Times New Roman",serif}',
+        'a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0__,__ESCAPED_FREE_TEXT_CLEAN_CSS1__,serif}',
+        'a{font-family:Times,"Times New Roman",serif}'
+      ]
+    })
+  )
+  .export(module);