Improves UrlsProcessor.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 26 Sep 2014 11:44:36 +0000 (12:44 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 10 Oct 2014 20:22:44 +0000 (21:22 +0100)
* Adds prototypal OO.
* Adds basic tests.

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

diff --git a/lib/text/urls-processor.js b/lib/text/urls-processor.js
new file mode 100644 (file)
index 0000000..24be977
--- /dev/null
@@ -0,0 +1,70 @@
+var EscapeStore = require('./escape-store');
+
+var UrlsProcessor = function UrlsProcessor() {
+  this.urls = new EscapeStore('URL');
+};
+
+// Strip urls by replacing them by a special
+// marker for further restoring. It's done via string scanning
+// instead of regexps to speed up the process.
+UrlsProcessor.prototype.escape = function (data) {
+  var nextStart = 0;
+  var nextEnd = 0;
+  var cursor = 0;
+  var tempData = [];
+
+  for (; nextEnd < data.length;) {
+    nextStart = data.indexOf('url(', nextEnd);
+    if (nextStart == -1)
+      break;
+
+    nextEnd = data.indexOf(')', nextStart);
+
+    var url = data.substring(nextStart, nextEnd + 1);
+    var placeholder = this.urls.store(url);
+    tempData.push(data.substring(cursor, nextStart));
+    tempData.push(placeholder);
+
+    cursor = nextEnd + 1;
+  }
+
+  return tempData.length > 0 ?
+    tempData.join('') + data.substring(cursor, data.length) :
+    data;
+};
+
+function normalize(url) {
+  url = url
+    .replace(/\\?\n|\\?\r\n/g, '')
+    .replace(/(\s{2,}|\s)/g, ' ')
+    .replace(/^url\((['"])? /, 'url($1')
+    .replace(/ (['"])?\)$/, '$1)');
+
+  if (url.indexOf(' ') == -1 && !/url\(['"]data:[^;]+;charset/.test(url))
+    url = url.replace(/["']/g, '');
+
+  return url;
+}
+
+UrlsProcessor.prototype.restore = function (data) {
+  var tempData = [];
+  var cursor = 0;
+
+  for (; cursor < data.length;) {
+    var nextMatch = this.urls.nextMatch(data, cursor);
+    if (nextMatch.start < 0)
+      break;
+
+    tempData.push(data.substring(cursor, nextMatch.start));
+    var url = normalize(this.urls.restore(nextMatch.match));
+    tempData.push(url);
+
+    cursor = nextMatch.end;
+  }
+
+  return tempData.length > 0 ?
+    tempData.join('') + data.substring(cursor, data.length) :
+    data;
+};
+
+module.exports = UrlsProcessor;
diff --git a/lib/text/urls.js b/lib/text/urls.js
deleted file mode 100644 (file)
index 3e04c4b..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-var EscapeStore = require('./escape-store');
-
-module.exports = function Urls() {
-  var urls = new EscapeStore('URL');
-
-  return {
-    // Strip urls by replacing them by a special
-    // marker for further restoring. It's done via string scanning
-    // instead of regexps to speed up the process.
-    escape: function(data) {
-      var nextStart = 0;
-      var nextEnd = 0;
-      var cursor = 0;
-      var tempData = [];
-
-      for (; nextEnd < data.length;) {
-        nextStart = data.indexOf('url(', nextEnd);
-        if (nextStart == -1)
-          break;
-
-        nextEnd = data.indexOf(')', nextStart);
-
-        var url = data.substring(nextStart, nextEnd + 1);
-        var placeholder = urls.store(url);
-        tempData.push(data.substring(cursor, nextStart));
-        tempData.push(placeholder);
-        cursor = nextEnd + 1;
-      }
-
-      return tempData.length > 0 ?
-        tempData.join('') + data.substring(cursor, data.length) :
-        data;
-    },
-
-    restore: function(data) {
-      return data.replace(urls.placeholderRegExp, function(placeholder) {
-        return urls.restore(placeholder).replace(/\s/g, '');
-      });
-    }
-  };
-};
diff --git a/test/text/urls-processor-test.js b/test/text/urls-processor-test.js
new file mode 100644 (file)
index 0000000..f742b2f
--- /dev/null
@@ -0,0 +1,79 @@
+var vows = require('vows');
+var assert = require('assert');
+var UrlsProcessor = require('../../lib/text/urls-processor');
+
+function processorContext(context) {
+  var vowContext = {};
+
+  function escaped (targetCSS) {
+    return function (sourceCSS) {
+      var result = new UrlsProcessor().escape(sourceCSS);
+      assert.equal(result, targetCSS);
+    };
+  }
+
+  function restored (targetCSS) {
+    return function (sourceCSS) {
+      var processor = new UrlsProcessor();
+      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(UrlsProcessor)
+  .addBatch(
+    processorContext({
+      'no urls': [
+        'a{color:red}',
+        'a{color:red}',
+        'a{color:red}'
+      ],
+      'unquoted': [
+        'div{background:url(some/file.png) repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url(some/file.png) repeat}'
+      ],
+      'single quoted': [
+        'div{background:url(\'some/file.png\') repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url(some/file.png) repeat}'
+      ],
+      'single quoted with whitespace': [
+        'div{background:url(\'some/file name.png\') repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url(\'some/file name.png\') repeat}'
+      ],
+      'double quoted': [
+        'div{background:url("some/file.png") repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url(some/file.png) repeat}'
+      ],
+      'double quoted with whitespace': [
+        'div{background:url("some/file name.png") repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url("some/file name.png") repeat}'
+      ],
+      'multiple': [
+        'div{background:url(one/file.png) repeat}p{background:url(second/file.png)}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}p{background:__ESCAPED_URL_CLEAN_CSS1__}',
+        'div{background:url(one/file.png) repeat}p{background:url(second/file.png)}'
+      ],
+      'whitespace': [
+        'div{background:url(\'  some/\nfile.png  \') repeat}',
+        'div{background:__ESCAPED_URL_CLEAN_CSS0__ repeat}',
+        'div{background:url(some/file.png) repeat}'
+      ]
+    })
+  )
+  .export(module);