Skip to content

Commit

Permalink
optimize relativeTo() results - closes #78
Browse files Browse the repository at this point in the history
  • Loading branch information
rodneyrehm committed Aug 4, 2013
2 parents a15d8b2 + 140dea9 commit 8ba3d4a
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 54 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ URI.js is published under the [MIT license](http://www.opensource.org/licenses/m

### `[dev-version]` (master branch) ###

* removing obsolete code fragments - ([Issue #100](https://github.com/medialize/URI.js/issues/100))
* optimize [`relativeTo()`](http://medialize.github.com/URI.js/docs.html#relativeto) results - ([Issue #78](https://github.com/medialize/URI.js/issues/78))
* removing obsolete code fragments from `URI.parse()` and `relativeTo()` - ([Issue #100](https://github.com/medialize/URI.js/issues/100))
* adding setting `URI.escapeQuerySpace` to control if query string should escape spaces using `+` or `%20` - ([Issue #74](https://github.com/medialize/URI.js/issues/74))
* updating [Punycode.js](https://github.com/bestiejs/punycode.js/) to version 1.2.3
* fixing internal `strictEncodeURIComponent()` to work in Firefox 3.6 - ([Issue #91](https://github.com/medialize/URI.js/issues/91))
Expand Down
77 changes: 34 additions & 43 deletions src/URI.js
Original file line number Diff line number Diff line change
Expand Up @@ -1756,69 +1756,60 @@ p.absoluteTo = function(base) {
return resolved;
};
p.relativeTo = function(base) {
var relative = this.clone();
var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
var common, _base, _this, _this_diff;
var relative = this.clone().normalize();
var common;

if (relative._parts.urn) {
throw new Error('URNs do not have any generally defined hierarchical components');
}

if (!(base instanceof URI)) {
base = new URI(base);
base = new URI(base).normalize();

if (relative.path().charAt(0) !== '/') {
throw new Error('URI is already relative');
}

if (relative.path().charAt(0) !== '/' || base.path().charAt(0) !== '/') {
throw new Error('Cannot calculate common path from non-relative URLs');
if (base.path().charAt(0) !== '/') {
throw new Error('Cannot calculate a URI relative to another relative URI');
}

// determine common sub path
common = URI.commonPath(relative.path(), base.path());

// relative paths don't have authority
for (var i = 0, p; p = properties[i]; i++) {
relative._parts[p] = null;
if (relative._parts.protocol === base._parts.protocol) {
relative._parts.protocol = null;
}

// no relation if there's nothing in common
if (common === '/') {
return relative;
} else if (!common) {
// there's absolutely nothing in common here
return this.clone();
if (relative._parts.username !== base._parts.username ||
relative._parts.password !== base._parts.password) {
return relative.build();
}

_base = base.directory();
_this = relative.directory();

// base and this are on the same level
if (_base === _this) {
relative._parts.path = relative.filename();
if (relative._parts.protocol !== null ||
relative._parts.username !== null ||
relative._parts.password !== null) {
return relative.build();
}

_this_diff = _this.substring(common.length);

// this is a descendant of base
if (_base + '/' === common) {
if (_this_diff) {
_this_diff += '/';
}

relative._parts.path = _this_diff + relative.filename();

if (relative._parts.hostname === base._parts.hostname &&
relative._parts.port === base._parts.port) {
relative._parts.hostname = null;
relative._parts.port = null;
} else {
return relative.build();
}
}

// this is a descendant of base
var parents = '../';
var _common = new RegExp('^' + escapeRegEx(common));
var _parents = _base.replace(_common, '/').match(/\//g).length -1;
// determine common sub path
common = URI.commonPath(relative.path(), base.path());

while (_parents--) {
parents += '../';
// If the paths have nothing in common, return a relative URL with the absolute path.
if (!common) {
return relative.build();
}

relative._parts.path = relative._parts.path.replace(_common, parents);
var parents = base._parts.path.
substring(common.length).
replace(/[^\/]*$/, '').
replace(/.*?\//g, '../');
relative._parts.path = parents + relative._parts.path.substring(common.length);

return relative.build();
};

Expand Down
103 changes: 93 additions & 10 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1075,11 +1075,6 @@ test("absoluteTo", function() {
});
test("relativeTo", function() {
var tests = [{
name: 'no relation',
url: '/relative/path?blubber=1#hash1',
base: '/path/to/file?some=query#hash',
result: '/relative/path?blubber=1#hash1'
}, {
name: 'same parent',
url: '/relative/path?blubber=1#hash1',
base: '/relative/file?some=query#hash',
Expand All @@ -1099,6 +1094,11 @@ test("relativeTo", function() {
url: '/relative/path?blubber=1#hash1',
base: '/relative/sub/foo/sub/file?some=query#hash',
result: '../../../path?blubber=1#hash1'
}, {
name: 'parent top level',
url: '/relative/path?blubber=1#hash1',
base: '/path/to/file?some=query#hash',
result: '../../relative/path?blubber=1#hash1'
}, {
name: 'descendant',
url: '/base/path/with/subdir/inner.html',
Expand All @@ -1108,29 +1108,112 @@ test("relativeTo", function() {
name: 'absolute /',
url: 'http://example.org/foo/bar/bat',
base: 'http://example.org/',
result: '/foo/bar/bat'
result: 'foo/bar/bat'
}, {
name: 'absolute /foo',
url: 'http://example.org/foo/bar/bat',
base: 'http://example.org/foo',
result: '/foo/bar/bat'
result: 'foo/bar/bat'
}, {
name: 'absolute /foo/',
url: 'http://example.org/foo/bar/bat',
base: 'http://example.org/foo/',
result: 'bar/bat'
}, {
name: 'same scheme',
url: 'http://example.org/foo/bar/bat',
base: 'http://example.com/foo/',
result: '//example.org/foo/bar/bat'
}, {
name: 'different scheme',
url: 'http://example.org/foo/bar',
base: 'https://example.org/foo/',
result: 'http://example.org/foo/bar'
}, {
name: 'base with no scheme or host',
url: 'http://example.org/foo/bar',
base: '/foo/',
result: 'http://example.org/foo/bar'
}, {
name: 'base with no scheme',
url: 'http://example.org/foo/bar',
base: '//example.org/foo/bar',
result: 'http://example.org/foo/bar'
}, {
name: 'denormalized base',
url: '/foo/bar/bat',
base: '/foo/./bar/',
result: 'bat'
}, {
name: 'denormalized url',
url: '/foo//bar/bat',
base: '/foo/bar/',
result: 'bat'
}, {
name: 'credentials',
url: 'http://user:pass@example.org/foo/bar',
base: 'http://example.org/foo/',
result: '//user:pass@example.org/foo/bar'
}, {
name: 'base credentials',
url: 'http://example.org/foo/bar',
base: 'http://user:pass@example.org/foo/bar',
result: '//example.org/foo/bar'
}, {
name: 'same credentials different host',
url: 'http://user:pass@example.org/foo/bar',
base: 'http://user:pass@example.com/foo/bar',
result: '//user:pass@example.org/foo/bar'
}, {
name: 'different port 1',
url: 'http://example.org/foo/bar',
base: 'http://example.org:8080/foo/bar',
result: '//example.org/foo/bar'
}, {
name: 'different port 2',
url: 'http://example.org:8081/foo/bar',
base: 'http://example.org:8080/foo/bar',
result: '//example.org:8081/foo/bar'
}, {
name: 'different port 3',
url: 'http://example.org:8081/foo/bar',
base: 'http://example.org/foo/bar',
result: '//example.org:8081/foo/bar'
}, {
name: 'already relative',
url: 'foo/bar',
base: '/foo/',
throws: true
}, {
name: 'relative base',
url: '/foo/bar',
base: 'foo/',
throws: true
}
];

for (var i = 0, t; t = tests[i]; i++) {
var u = new URI(t.url),
b = new URI(t.base),
caught = false;
var r;

try {
r = u.relativeTo(b);
} catch (e) {
caught = true;
}

equal(r + "", t.result, t.name);
if (t.throws) {
ok(caught, t.name + " should throw exception");
} else {
ok(!caught, t.name + " should not throw exception");
equal(r + "", t.result, t.name);

var a = r.absoluteTo(t.base);
equal(a + "", t.url, t.name + " reversed");
var a = r.absoluteTo(t.base);
var n = u.clone().normalize();
equal(a.toString(), n.toString(), t.name + " reversed");
}
}
});

Expand Down

0 comments on commit 8ba3d4a

Please sign in to comment.