improve filename creation (#677)

We now allow a much larger range of characters in filenames. We also URL-encode HTML links when necessary, so that everything still works when you upload the files to a web server.
This commit is contained in:
Jeff Williams 2014-06-28 16:58:17 -07:00
parent da7911bcf8
commit 873480a3ed
2 changed files with 38 additions and 37 deletions

View File

@ -4,6 +4,7 @@
*/
'use strict';
var catharsis = require('catharsis');
var dictionary = require('jsdoc/tag/dictionary');
var util = require('util');
@ -52,20 +53,6 @@ function makeFilenameUnique(filename, str) {
return filename;
}
function cleanseFilename(str) {
str = str || '';
// allow for namespace prefix
// TODO: use prefixes in jsdoc/doclet
return str.replace(/^(event|module|external|package):/, '$1-')
// use - instead of ~ to denote 'inner'
.replace(/~/g, '-')
// use _ instead of # to denote 'instance'
.replace(/\#/g, '_')
// remove the variation, if any
.replace(/\([\s\S]*\)$/, '');
}
var htmlsafe = exports.htmlsafe = function(str) {
return str.replace(/&/g, '&')
.replace(/</g, '&lt;');
@ -83,20 +70,24 @@ var htmlsafe = exports.htmlsafe = function(str) {
* @return {string} The filename to use for the string.
*/
var getUniqueFilename = exports.getUniqueFilename = function(str) {
// allow for namespace prefix
var basename = cleanseFilename(str);
var basename = (str || '')
// allow for namespace prefix
// TODO: use prefixes in jsdoc/doclet
.replace(/^(event|module|external|package):/, '$1-')
// replace characters that can cause problems on some filesystems
.replace(/[\\\/?*:|'"<>]/g, '_')
// use - instead of ~ to denote 'inner'
.replace(/~/g, '-')
// use _ instead of # to denote 'instance'
.replace(/\#/g, '_')
// use _ instead of / (for example, in module names)
.replace(/\//g, '_')
// remove the variation, if any
.replace(/\([\s\S]*\)$/, '')
// make sure we don't create hidden files, or files whose names start with a dash
.replace(/^[\.\-]/, '');
// if the basename includes characters that we can't use in a filepath, remove everything up to
// and including the last bad character
var regexp = /[^$a-z0-9._\-](?=[$a-z0-9._\-]*$)/i;
var result = regexp.exec(basename);
if (result && result.index) {
basename = basename.substr(result.index + 1);
}
// make sure we don't create hidden files on POSIX systems
basename = basename.replace(/^\./, '');
// and in case we've now stripped the entire basename (uncommon, but possible):
// in case we've now stripped the entire basename (uncommon, but possible):
basename = basename.length ? basename : '_';
return makeFilenameUnique(basename, str) + exports.fileExtension;
@ -116,7 +107,6 @@ var tutorialLinkMap = {
var longnameToUrl = exports.longnameToUrl = linkMap.longnameToUrl;
function parseType(longname) {
var catharsis = require('catharsis');
var err;
try {
@ -146,6 +136,14 @@ function isComplexTypeExpression(expr) {
return expr.search(/[{(|]/) !== -1 || expr.search(/</) > 0;
}
function fragmentHash(fragmentId) {
if (!fragmentId) {
return '';
}
return '#' + fragmentId;
}
/**
* Build an HTML link to the symbol with the specified longname. If the longname is not
* associated with a URL, this method simply returns the link text, if provided, or the longname.
@ -171,10 +169,8 @@ function isComplexTypeExpression(expr) {
* @return {string} The HTML link, or the link text if the link is not available.
*/
function buildLink(longname, linkText, options) {
var catharsis = require('catharsis');
var classString = options.cssClass ? util.format(' class="%s"', options.cssClass) : '';
var fragmentString = options.fragmentId ? '#' + options.fragmentId : '';
var fragmentString = fragmentHash(options.fragmentId);
var stripped;
var text;
var url;
@ -205,7 +201,8 @@ function buildLink(longname, linkText, options) {
return text;
}
else {
return util.format('<a href="%s%s"%s>%s</a>', url, fragmentString, classString, text);
return util.format('<a href="%s"%s>%s</a>', encodeURI(url + fragmentString), classString,
text);
}
}
@ -727,7 +724,6 @@ exports.createLink = function(doclet) {
var fakeContainer;
var url = '';
var INSTANCE = exports.scopeToPunc.instance;
var longname = doclet.longname;
// handle doclets in which doclet.longname implies that the doclet gets its own HTML file, but
@ -761,7 +757,7 @@ exports.createLink = function(doclet) {
fragment = getNamespace(doclet.kind) + (doclet.name || '');
}
url = fragment ? (filename + INSTANCE + fragment) : filename;
url = encodeURI( filename + fragmentHash(fragment) );
return url;
};

View File

@ -177,9 +177,14 @@ describe("jsdoc/util/templateHelper", function() {
expect(filename).toBe('BackusNaur.html');
});
it('should convert a string with slashes into the text following the last slash plus the default extension', function() {
it('should replace slashes with underscores', function() {
var filename = helper.getUniqueFilename('tick/tock');
expect(filename).toMatch(/^tock\.html$/);
expect(filename).toBe('tick_tock.html');
});
it('should replace other problematic characters with underscores', function() {
var filename = helper.getUniqueFilename('a very strange \\/?*:|\'"<> filename');
expect(filename).toBe('a very strange __________ filename.html');
});
it('should not return the same filename twice', function() {
@ -1332,7 +1337,7 @@ describe("jsdoc/util/templateHelper", function() {
},
url = helper.createLink(mockDoclet);
expect(url).toEqual('_.html#"*foo"');
expect(url).toEqual('ns1._!_.html#%22*foo%22');
});
it('should create a url for a function that is the only symbol exported by a module.',