Update to latest jsonld.js.
authorDave Longley <dlongley@digitalbazaar.com>
Tue, 05 Mar 2013 17:31:58 -0500
changeset 1375 62c32b280fcc
parent 1374 1b51406761a2
child 1376 9654192b6c2d
Update to latest jsonld.js.
playground/jsonld.js
--- a/playground/jsonld.js	Tue Mar 05 17:30:17 2013 -0500
+++ b/playground/jsonld.js	Tue Mar 05 17:31:58 2013 -0500
@@ -2458,7 +2458,6 @@
       }
 
       base = jsonld.url.parse(base || '');
-      base.pathname = base.pathname || '';
       rval['@base'] = base;
       defined['@base'] = true;
     }
@@ -4460,73 +4459,42 @@
   // parse base if it is a string
   if(_isString(base)) {
     base = jsonld.url.parse(base || '');
-    base.pathname = base.pathname || '';
-  }
-
-  // if base is empty, do not change iri
-  if(base.href === '') {
-    return iri;
-  }
-
-  var authority = (base.host || '');
+  }
+
+  // parse given IRI
   var rel = jsonld.url.parse(iri);
-  rel.pathname = (rel.pathname || '');
-
-  // per RFC3986 normalize slashes and dots in path
+
+  // start hierarchical part
+  var hierPart = (base.protocol || '');
+  if(rel.authority) {
+    hierPart += '//' + rel.authority;
+  }
+  else if(base.href !== '') {
+    hierPart += '//' + base.authority;
+  }
+
+  // per RFC3986 normalize
   var path;
 
-  // IRI contains authority
-  if(rel.pathname.indexOf('//') === 0) {
-    path = rel.pathname.substr(2);
-    authority = path.substr(0, path.lastIndexOf('/'));
-    path = path.substr(authority.length);
-  }
   // IRI represents an absolute path
-  else if(rel.pathname.indexOf('/') === 0) {
+  if(rel.pathname.indexOf('/') === 0) {
     path = rel.pathname;
   }
   else {
     path = base.pathname;
 
-    // use up to last directory for base
+    // append relative path to the end of the last directory from base
     if(rel.pathname !== '') {
-      path = path.substr(0, path.lastIndexOf('/') + 1) + rel.pathname;
-    }
-  }
-
-  var segments = path.split('/');
-
-  // remove '.' and '' (do not remove trailing empty path)
-  segments = segments.filter(function(e, i) {
-    return e !== '.' && (e !== '' || i === segments.length - 1);
-  });
-
-  // remove as many '..' as possible
-  for(var i = 0; i < segments.length;) {
-    var segment = segments[i];
-    if(segment === '..') {
-      // too many reverse dots
-      if(i === 0) {
-        var last = segments[segments.length - 1];
-        if(last !== '..') {
-          segments = [last];
-        }
-        else {
-          segments = [];
-        }
-        break;
-      }
-
-      // remove '..' and previous segment
-      segments.splice(i - 1, 2);
-      i -= 1;
-    }
-    else {
-      i += 1;
-    }
-  }
-
-  path = segments.join('/');
+      path = path.substr(0, path.lastIndexOf('/') + 1);
+      if(path.length > 0 && path.lastIndexOf('/') !== path.length - 1) {
+        path += '/';
+      }
+      path += rel.pathname;
+    }
+  }
+
+  // remove slashes and dots in path
+  path = _removeDotSegments(path, hierPart !== '');
 
   // add query and hash
   if(rel.query) {
@@ -4536,11 +4504,11 @@
     path += rel.hash;
   }
 
-  var rval = (base.protocol || '') + '//';
-  if(base.auth) {
-    rval += base.auth + '@';
-  }
-  rval += authority + '/' + path;
+  var rval = hierPart + path;
+
+  if(rval === '') {
+    rval = './';
+  }
 
   return rval;
 }
@@ -4556,20 +4524,17 @@
 function _removeBase(base, iri) {
   if(_isString(base)) {
     base = jsonld.url.parse(base || '');
-    base.pathname = base.pathname || '';
-  }
-
-  // base is empty
-  if(base.href === '') {
-    return iri;
   }
 
   // establish base root
-  var root = (base.protocol || '') + '//';
-  if(base.auth) {
-    root += base.auth + '@';
-  }
-  root += (base.host || '');
+  var root = '';
+  if(base.href !== '') {
+    root += (base.protocol || '') + '//' + base.authority;
+  }
+  // scheme-relative root with empty base
+  else if(iri.indexOf('//')) {
+    root += '//';
+  }
 
   // IRI not relative to base
   if(iri.indexOf(root) !== 0) {
@@ -4578,11 +4543,11 @@
 
   // remove root from IRI and parse remainder
   var rel = jsonld.url.parse(iri.substr(root.length));
-  rel.pathname = rel.pathname || '';
 
   // remove path segments that match
-  var baseSegments = base.pathname.split('/');
-  var iriSegments = rel.pathname.split('/');
+  var baseSegments = base.normalizedPath.split('/');
+  var iriSegments = rel.normalizedPath.split('/');
+
   while(baseSegments.length > 0 && iriSegments.length > 0) {
     if(baseSegments[0] !== iriSegments[0]) {
       break;
@@ -4595,7 +4560,8 @@
   var rval = '';
   if(baseSegments.length > 0) {
     // do not count the last segment if it isn't a path (doesn't end in '/')
-    if(base.pathname.indexOf('/', base.pathname.length - 1) === -1) {
+    if(base.normalizedPath.indexOf(
+      '/', base.normalizedPath.length - 1) === -1) {
       baseSegments.pop();
     }
     for(var i = 0; i < baseSegments.length; ++i) {
@@ -4635,7 +4601,6 @@
     namer = new UniqueNamer('_:b');
   }
   var base = jsonld.url.parse(options.base || '');
-  base.pathname = base.pathname || '';
   return {
     '@base': base,
     mappings: {},
@@ -6171,7 +6136,15 @@
 // define URL parser
 jsonld.url = {};
 if(_nodejs) {
-  jsonld.url.parse = require('url').parse;
+  var parse = require('url').parse;
+  jsonld.url.parse = function(url) {
+    var parsed = parse(url);
+    parsed.pathname = parsed.pathname || '';
+    _parseAuthority(parsed);
+    parsed.normalizedPath = _removeDotSegments(
+      parsed.pathname, parsed.authority !== '');
+    return parsed;
+  };
 }
 else {
   // parseUri 1.2.2
@@ -6194,7 +6167,9 @@
     if(uri.host && uri.path === '') {
       uri.path = '/';
     }
-    uri.pathname = uri.path;
+    uri.pathname = uri.path || '';
+    _parseAuthority(uri);
+    uri.normalizedPath = _removeDotSegments(uri.pathname, uri.authority !== '');
     if(uri.query) {
       uri.path = uri.path + '?' + uri.query;
     }
@@ -6206,6 +6181,74 @@
   };
 }
 
+/**
+ * Parses the authority for the pre-parsed given URL.
+ *
+ * @param parsed the pre-parsed URL.
+ */
+function _parseAuthority(parsed) {
+  // parse authority for network-path reference
+  if(parsed.href.indexOf('//') === 0) {
+    parsed.pathname = parsed.pathname.substr(2);
+    var idx = parsed.pathname.indexOf('/');
+    if(idx === -1) {
+      parsed.authority = parsed.pathname;
+      parsed.pathname = '';
+    }
+    else {
+      parsed.authority = parsed.pathname.substr(0, idx);
+      parsed.pathname = parsed.pathname.substr(idx);
+    }
+  }
+  else {
+    // construct authority
+    parsed.authority = '';
+    if(parsed.auth) {
+      parsed.authority += parsed.auth + '@';
+    }
+    parsed.authority += (parsed.host || '');
+  }
+}
+
+/**
+ * Removes dot segments from a URL path.
+ *
+ * @param path the path to remove dot segments from.
+ * @param hasAuthority true if the URL has an authority, false if not.
+ */
+function _removeDotSegments(path, hasAuthority) {
+  var rval = '';
+
+  if(path.indexOf('/') === 0) {
+    rval = '/';
+  }
+
+  // RFC 3986 5.2.4 (reworked)
+  var input = path.split('/');
+  var output = [];
+  while(input.length > 0) {
+    if(input[0] === '.' || (input[0] === '' && input.length > 1)) {
+      input.shift();
+      continue;
+    }
+    if(input[0] === '..') {
+      input.shift();
+      if(hasAuthority ||
+        (output.length > 0 && output[output.length - 1] !== '..')) {
+        output.pop();
+      }
+      // leading relative URL '..'
+      else {
+        output.push('..');
+      }
+      continue;
+    }
+    output.push(input.shift());
+  }
+
+  return rval + output.join('/');
+}
+
 if(_nodejs) {
   // use node context loader by default
   jsonld.useContextLoader('node');