Skip to content

Commit 8ba3d4a

Browse files
committed
optimize relativeTo() results - closes #78
2 parents a15d8b2 + 140dea9 commit 8ba3d4a

File tree

3 files changed

+129
-54
lines changed

3 files changed

+129
-54
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ URI.js is published under the [MIT license](http://www.opensource.org/licenses/m
209209

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

212-
* removing obsolete code fragments - ([Issue #100](https://github.com/medialize/URI.js/issues/100))
212+
* optimize [`relativeTo()`](http://medialize.github.com/URI.js/docs.html#relativeto) results - ([Issue #78](https://github.com/medialize/URI.js/issues/78))
213+
* removing obsolete code fragments from `URI.parse()` and `relativeTo()` - ([Issue #100](https://github.com/medialize/URI.js/issues/100))
213214
* 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))
214215
* updating [Punycode.js](https://github.com/bestiejs/punycode.js/) to version 1.2.3
215216
* fixing internal `strictEncodeURIComponent()` to work in Firefox 3.6 - ([Issue #91](https://github.com/medialize/URI.js/issues/91))

src/URI.js

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,69 +1756,60 @@ p.absoluteTo = function(base) {
17561756
return resolved;
17571757
};
17581758
p.relativeTo = function(base) {
1759-
var relative = this.clone();
1760-
var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
1761-
var common, _base, _this, _this_diff;
1759+
var relative = this.clone().normalize();
1760+
var common;
17621761

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

1767-
if (!(base instanceof URI)) {
1768-
base = new URI(base);
1766+
base = new URI(base).normalize();
1767+
1768+
if (relative.path().charAt(0) !== '/') {
1769+
throw new Error('URI is already relative');
17691770
}
17701771

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

1775-
// determine common sub path
1776-
common = URI.commonPath(relative.path(), base.path());
1777-
1778-
// relative paths don't have authority
1779-
for (var i = 0, p; p = properties[i]; i++) {
1780-
relative._parts[p] = null;
1776+
if (relative._parts.protocol === base._parts.protocol) {
1777+
relative._parts.protocol = null;
17811778
}
17821779

1783-
// no relation if there's nothing in common
1784-
if (common === '/') {
1785-
return relative;
1786-
} else if (!common) {
1787-
// there's absolutely nothing in common here
1788-
return this.clone();
1780+
if (relative._parts.username !== base._parts.username ||
1781+
relative._parts.password !== base._parts.password) {
1782+
return relative.build();
17891783
}
1790-
1791-
_base = base.directory();
1792-
_this = relative.directory();
17931784

1794-
// base and this are on the same level
1795-
if (_base === _this) {
1796-
relative._parts.path = relative.filename();
1785+
if (relative._parts.protocol !== null ||
1786+
relative._parts.username !== null ||
1787+
relative._parts.password !== null) {
17971788
return relative.build();
17981789
}
1799-
1800-
_this_diff = _this.substring(common.length);
1801-
1802-
// this is a descendant of base
1803-
if (_base + '/' === common) {
1804-
if (_this_diff) {
1805-
_this_diff += '/';
1806-
}
1807-
1808-
relative._parts.path = _this_diff + relative.filename();
1790+
1791+
if (relative._parts.hostname === base._parts.hostname &&
1792+
relative._parts.port === base._parts.port) {
1793+
relative._parts.hostname = null;
1794+
relative._parts.port = null;
1795+
} else {
18091796
return relative.build();
1810-
}
1797+
}
18111798

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

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

1821-
relative._parts.path = relative._parts.path.replace(_common, parents);
1807+
var parents = base._parts.path.
1808+
substring(common.length).
1809+
replace(/[^\/]*$/, '').
1810+
replace(/.*?\//g, '../');
1811+
relative._parts.path = parents + relative._parts.path.substring(common.length);
1812+
18221813
return relative.build();
18231814
};
18241815

test/test.js

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,11 +1075,6 @@ test("absoluteTo", function() {
10751075
});
10761076
test("relativeTo", function() {
10771077
var tests = [{
1078-
name: 'no relation',
1079-
url: '/relative/path?blubber=1#hash1',
1080-
base: '/path/to/file?some=query#hash',
1081-
result: '/relative/path?blubber=1#hash1'
1082-
}, {
10831078
name: 'same parent',
10841079
url: '/relative/path?blubber=1#hash1',
10851080
base: '/relative/file?some=query#hash',
@@ -1099,6 +1094,11 @@ test("relativeTo", function() {
10991094
url: '/relative/path?blubber=1#hash1',
11001095
base: '/relative/sub/foo/sub/file?some=query#hash',
11011096
result: '../../../path?blubber=1#hash1'
1097+
}, {
1098+
name: 'parent top level',
1099+
url: '/relative/path?blubber=1#hash1',
1100+
base: '/path/to/file?some=query#hash',
1101+
result: '../../relative/path?blubber=1#hash1'
11021102
}, {
11031103
name: 'descendant',
11041104
url: '/base/path/with/subdir/inner.html',
@@ -1108,29 +1108,112 @@ test("relativeTo", function() {
11081108
name: 'absolute /',
11091109
url: 'http://example.org/foo/bar/bat',
11101110
base: 'http://example.org/',
1111-
result: '/foo/bar/bat'
1111+
result: 'foo/bar/bat'
11121112
}, {
11131113
name: 'absolute /foo',
11141114
url: 'http://example.org/foo/bar/bat',
11151115
base: 'http://example.org/foo',
1116-
result: '/foo/bar/bat'
1116+
result: 'foo/bar/bat'
11171117
}, {
11181118
name: 'absolute /foo/',
11191119
url: 'http://example.org/foo/bar/bat',
11201120
base: 'http://example.org/foo/',
11211121
result: 'bar/bat'
1122+
}, {
1123+
name: 'same scheme',
1124+
url: 'http://example.org/foo/bar/bat',
1125+
base: 'http://example.com/foo/',
1126+
result: '//example.org/foo/bar/bat'
1127+
}, {
1128+
name: 'different scheme',
1129+
url: 'http://example.org/foo/bar',
1130+
base: 'https://example.org/foo/',
1131+
result: 'http://example.org/foo/bar'
1132+
}, {
1133+
name: 'base with no scheme or host',
1134+
url: 'http://example.org/foo/bar',
1135+
base: '/foo/',
1136+
result: 'http://example.org/foo/bar'
1137+
}, {
1138+
name: 'base with no scheme',
1139+
url: 'http://example.org/foo/bar',
1140+
base: '//example.org/foo/bar',
1141+
result: 'http://example.org/foo/bar'
1142+
}, {
1143+
name: 'denormalized base',
1144+
url: '/foo/bar/bat',
1145+
base: '/foo/./bar/',
1146+
result: 'bat'
1147+
}, {
1148+
name: 'denormalized url',
1149+
url: '/foo//bar/bat',
1150+
base: '/foo/bar/',
1151+
result: 'bat'
1152+
}, {
1153+
name: 'credentials',
1154+
url: 'http://user:[email protected]/foo/bar',
1155+
base: 'http://example.org/foo/',
1156+
result: '//user:[email protected]/foo/bar'
1157+
}, {
1158+
name: 'base credentials',
1159+
url: 'http://example.org/foo/bar',
1160+
base: 'http://user:[email protected]/foo/bar',
1161+
result: '//example.org/foo/bar'
1162+
}, {
1163+
name: 'same credentials different host',
1164+
url: 'http://user:[email protected]/foo/bar',
1165+
base: 'http://user:[email protected]/foo/bar',
1166+
result: '//user:[email protected]/foo/bar'
1167+
}, {
1168+
name: 'different port 1',
1169+
url: 'http://example.org/foo/bar',
1170+
base: 'http://example.org:8080/foo/bar',
1171+
result: '//example.org/foo/bar'
1172+
}, {
1173+
name: 'different port 2',
1174+
url: 'http://example.org:8081/foo/bar',
1175+
base: 'http://example.org:8080/foo/bar',
1176+
result: '//example.org:8081/foo/bar'
1177+
}, {
1178+
name: 'different port 3',
1179+
url: 'http://example.org:8081/foo/bar',
1180+
base: 'http://example.org/foo/bar',
1181+
result: '//example.org:8081/foo/bar'
1182+
}, {
1183+
name: 'already relative',
1184+
url: 'foo/bar',
1185+
base: '/foo/',
1186+
throws: true
1187+
}, {
1188+
name: 'relative base',
1189+
url: '/foo/bar',
1190+
base: 'foo/',
1191+
throws: true
11221192
}
11231193
];
11241194

11251195
for (var i = 0, t; t = tests[i]; i++) {
11261196
var u = new URI(t.url),
11271197
b = new URI(t.base),
1198+
caught = false;
1199+
var r;
1200+
1201+
try {
11281202
r = u.relativeTo(b);
1203+
} catch (e) {
1204+
caught = true;
1205+
}
11291206

1130-
equal(r + "", t.result, t.name);
1207+
if (t.throws) {
1208+
ok(caught, t.name + " should throw exception");
1209+
} else {
1210+
ok(!caught, t.name + " should not throw exception");
1211+
equal(r + "", t.result, t.name);
11311212

1132-
var a = r.absoluteTo(t.base);
1133-
equal(a + "", t.url, t.name + " reversed");
1213+
var a = r.absoluteTo(t.base);
1214+
var n = u.clone().normalize();
1215+
equal(a.toString(), n.toString(), t.name + " reversed");
1216+
}
11341217
}
11351218
});
11361219

0 commit comments

Comments
 (0)