Add more features to the file renaming functionality (#4424)

* New `attachmentTitle` field, returns the title of the current attachment (or
  the future title of the attachment being created)
* New function `match` to enable testing values with a regex.
* New function `start` to enable truncating from the beginning.
* Ignore new line characters in the template for easier editing.
* Avoid repeated characters when changing case (snake/dash)
* Increase the size of the template input field.

Closes #3252
This commit is contained in:
Tom Najdek 2024-07-27 17:39:10 +02:00 committed by Dan Stillman
parent 29f4aece24
commit 00ae8bb9b2
7 changed files with 205 additions and 107 deletions

View file

@ -43,26 +43,27 @@ Zotero_Preferences.FileRenaming = {
this._itemsView.onSelect.removeListener(this._updatePreview);
},
getActiveTopLevelItem() {
const selectedItem = Zotero.getActiveZoteroPane()?.getSelectedItems()?.[0];
getActiveItem() {
let selectedItem = Zotero.getActiveZoteroPane()?.getSelectedItems()?.[0];
if (selectedItem) {
if (selectedItem.isRegularItem() && !selectedItem.parentKey) {
return [selectedItem, this.defaultExt];
return [selectedItem, this.defaultExt, ''];
}
if (selectedItem.isFileAttachment() && selectedItem.parentKey) {
const path = selectedItem.getFilePath();
const ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
return [Zotero.Items.getByLibraryAndKey(selectedItem.libraryID, selectedItem.parentKey), ext ?? this.defaultExt];
if (selectedItem.isFileAttachment()) {
let path = selectedItem.getFilePath();
let ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
let parentItem = Zotero.Items.getByLibraryAndKey(selectedItem.libraryID, selectedItem.parentKey);
return [parentItem, ext ?? this.defaultExt, selectedItem.getField('title')];
}
}
return null;
},
updatePreview() {
const [item, ext] = this.getActiveTopLevelItem() ?? [this.mockItem ?? this.makeMockItem(), this.defaultExt];
const tpl = document.getElementById('file-renaming-format-template').value;
const preview = Zotero.Attachments.getFileBaseNameFromItem(item, tpl);
async updatePreview() {
const [item, ext, attachmentTitle] = this.getActiveItem() ?? [this.mockItem ?? this.makeMockItem(), this.defaultExt, ''];
const formatString = document.getElementById('file-renaming-format-template').value;
const preview = Zotero.Attachments.getFileBaseNameFromItem(item, { formatString, attachmentTitle });
document.getElementById('file-renaming-format-preview').innerText = `${preview}.${ext}`;
},

View file

@ -57,7 +57,7 @@
aria-labelledby="file-renaming-format-template-label"
id="file-renaming-format-template"
preference="extensions.zotero.attachmentRenameTemplate"
rows="3"
rows="8"
/>
<html:label id="file-renaming-format-preview-label">
<html:h2

View file

@ -582,7 +582,7 @@ Zotero.Attachments = new function () {
// Rename attachment
if (renameIfAllowedType && !fileBaseName && this.isRenameAllowedForType(contentType)) {
let parentItem = Zotero.Items.get(parentItemID);
fileBaseName = this.getFileBaseNameFromItem(parentItem);
fileBaseName = this.getFileBaseNameFromItem(parentItem, { attachmentTitle: title });
}
if (fileBaseName) {
let ext = this._getExtensionFromURL(url, contentType);
@ -1769,13 +1769,12 @@ Zotero.Attachments = new function () {
* @return {Zotero.Item|false} - New Zotero.Item, or false if unsuccessful
*/
this.addFileFromURLs = async function (item, urlResolvers, options = {}) {
var fileBaseName = this.getFileBaseNameFromItem(item);
var tmpDir;
var tmpFile;
var attachmentItem = false;
try {
tmpDir = (await this.createTemporaryStorageDirectory()).path;
tmpFile = OS.Path.join(tmpDir, fileBaseName + '.tmp');
tmpFile = OS.Path.join(tmpDir, 'file.tmp');
let { title, mimeType, url, props } = await this.downloadFirstAvailableFile(
urlResolvers,
tmpFile,
@ -1794,13 +1793,15 @@ Zotero.Attachments = new function () {
if (!this.FIND_AVAILABLE_FILE_TYPES.includes(mimeType)) {
throw new Error(`Resolved file is unsupported type ${mimeType}`);
}
let filename = fileBaseName + '.' + (Zotero.MIME.getPrimaryExtension(mimeType) || 'dat');
await IOUtils.move(tmpFile, PathUtils.join(tmpDir, filename));
title = title || _getTitleFromVersion(props.articleVersion);
let fileBaseName = this.getFileBaseNameFromItem(item, { attachmentTitle: title });
let ext = Zotero.MIME.getPrimaryExtension(mimeType) || 'dat';
let filename = await Zotero.File.rename(tmpFile, `${fileBaseName}.${ext}`);
attachmentItem = await this.createURLAttachmentFromTemporaryStorageDirectory({
directory: tmpDir,
libraryID: item.libraryID,
filename,
title: title || _getTitleFromVersion(props.articleVersion),
title,
url,
contentType: mimeType,
parentItemID: item.id
@ -2207,10 +2208,16 @@ Zotero.Attachments = new function () {
* @param {Zotero.Item} item
* @param {String} formatString
*/
this.getFileBaseNameFromItem = function (item, formatString) {
this.getFileBaseNameFromItem = function (item, options = {}) {
if (!(item instanceof Zotero.Item)) {
throw new Error("'item' must be a Zotero.Item");
}
if (typeof options === 'string') {
Zotero.warn("Zotero.Attachments.getFileBaseNameFromItem(item, formatString) is deprecated -- use Zotero.Attachments(item, options)");
options = { formatString: options };
}
let { formatString = null, attachmentTitle = '' } = options;
if (!formatString) {
formatString = Zotero.Prefs.get('attachmentRenameTemplate');
@ -2219,7 +2226,7 @@ Zotero.Attachments = new function () {
let chunks = [];
let protectedLiterals = new Set();
formatString = formatString.trim();
formatString = formatString.replace(/\r?\n|\r/g, "").trim();
const getSlicedCreatorsOfType = (creatorType, slice) => {
let creatorTypeIDs;
@ -2253,7 +2260,7 @@ Zotero.Attachments = new function () {
};
const common = (value, { truncate = false, prefix = '', suffix = '', replaceFrom = '', replaceTo = '', regexOpts = '', case: textCase = '' } = {}) => {
const common = (value, { start = false, truncate = false, prefix = '', suffix = '', match = '', replaceFrom = '', replaceTo = '', regexOpts = 'i', case: textCase = '' } = {}) => {
if (value === '' || value === null || typeof value === 'undefined') {
return '';
}
@ -2266,6 +2273,17 @@ Zotero.Attachments = new function () {
suffix = '';
}
// match overrides all other options and returns immediately
if (match) {
try {
let matchResult = value.match(new RegExp(match, regexOpts));
return matchResult ? matchResult[0] : '';
}
catch (_e) {
return '';
}
}
if (protectedLiterals.size > 0) {
// escape protected literals in the format string with \
value = value.replace(
@ -2274,8 +2292,12 @@ Zotero.Attachments = new function () {
);
}
if (start) {
value = value.substring(start);
}
if (truncate) {
value = value.substr(0, truncate);
value = value.substring(0, truncate);
}
value = value.trim();
@ -2284,7 +2306,12 @@ Zotero.Attachments = new function () {
let affixed = false;
if (replaceFrom) {
value = value.replace(new RegExp(replaceFrom, regexOpts), replaceTo);
try {
value = value.replace(new RegExp(replaceFrom, regexOpts), replaceTo);
}
catch (_e) {
// ignore
}
}
if (prefix && !value.startsWith(prefix)) {
value = prefix + value;
@ -2313,9 +2340,11 @@ Zotero.Attachments = new function () {
value = Zotero.Utilities.capitalizeTitle(value, true);
break;
case 'hyphen':
value = value.replace(/\s+-/g, '-').replace(/-\s+/g, '-');
value = value.toLowerCase().replace(/\s+/g, '-');
break;
case 'snake':
value = value.replace(/\s+_/g, '_').replace(/_\s+/g, '_');
value = value.toLowerCase().replace(/\s+/g, '_');
break;
case 'camel':
@ -2392,8 +2421,9 @@ Zotero.Attachments = new function () {
item.getField('firstCreator', true, true), args
);
const vars = { ...fields, ...creatorFields, firstCreator, itemType, year };
const attachmentTitleFn = args => common(attachmentTitle ?? '', args);
const vars = { ...fields, ...creatorFields, attachmentTitle: attachmentTitleFn, firstCreator, itemType, year };
// Final name is generated twice. In the first pass we collect all affixed values and determine protected literals.
// This is done in order to remove repeated suffixes, except if these appear in the value or the format string itself.
@ -2480,7 +2510,7 @@ Zotero.Attachments = new function () {
if (!this.isRenameAllowedForType(contentType)) {
return false;
}
return this.getFileBaseNameFromItem(parentItem);
return this.getFileBaseNameFromItem(parentItem, { attachmentTitle: PathUtils.filename(file) });
}

View file

@ -294,7 +294,7 @@ Zotero.RecognizeDocument = new function () {
// Rename attachment file to match new metadata
if (Zotero.Attachments.shouldAutoRenameFile(attachment.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE)) {
let ext = Zotero.File.getExtension(path);
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem, { attachmentTitle: originalTitle });
let newName = fileBaseName + (ext ? '.' + ext : '');
let result = await attachment.renameAttachmentFile(newName, false, true);
if (result !== true) {

View file

@ -901,7 +901,7 @@ Zotero.Translate.ItemSaver.prototype = {
let fileBaseName;
if (parentItemID) {
let parentItem = yield Zotero.Items.getAsync(parentItemID);
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem, { attachmentTitle: title });
}
attachment.linkMode = "imported_url";

View file

@ -5665,7 +5665,7 @@ var ZoteroPane = new function()
let parentItemID = item.parentItemID;
let parentItem = await Zotero.Items.getAsync(parentItemID);
var newName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
var newName = Zotero.Attachments.getFileBaseNameFromItem(parentItem, { attachmentTitle: item.getField('title') });
let extRE = /\.[^\.]+$/;
let origFilename = PathUtils.split(file).pop();

View file

@ -1364,7 +1364,8 @@ describe("Zotero.Attachments", function() {
});
describe("#getFileBaseNameFromItem()", function () {
var item, itemManyAuthors, itemPatent, itemIncomplete, itemBookSection, itemSpaces, itemSuffixes, itemKeepDashes;
var item, itemManyAuthors, itemPatent, itemIncomplete, itemBookSection, itemSpaces, itemSuffixes, itemKeepHyphens,
itemNoRepeatedHyphens, itemNoRepeatedUnderscores;
before(() => {
item = createUnsavedDataObject('item', { title: 'Lorem Ipsum', itemType: 'journalArticle' });
@ -1378,7 +1379,9 @@ describe("Zotero.Attachments", function() {
item.setField('issue', '42');
item.setField('pages', '321');
itemBookSection = createUnsavedDataObject('item', { title: 'Book Section', itemType: 'bookSection' });
itemBookSection.setField('bookTitle', 'Book Title');
itemManyAuthors = createUnsavedDataObject('item', { title: 'Has Many Authors', itemType: 'book' });
itemManyAuthors.setCreators([
{ firstName: 'First', lastName: 'Author', creatorType: 'author' },
@ -1403,222 +1406,247 @@ describe("Zotero.Attachments", function() {
itemPatent.setField('number', 'HBK-8539b');
itemPatent.setField('assignee', 'Fast FooBar');
itemIncomplete = createUnsavedDataObject('item', { title: 'Incomplete', itemType: 'preprint' });
itemBookSection = createUnsavedDataObject('item', { title: 'Book Section', itemType: 'bookSection' });
itemBookSection.setField('bookTitle', 'Book Title');
itemSpaces = createUnsavedDataObject('item', { title: ' Spaces! ', itemType: 'book' });
itemSuffixes = createUnsavedDataObject('item', { title: '-Suffixes-', itemType: 'book' });
itemSuffixes.setField('date', "1999-07-15");
itemKeepDashes = createUnsavedDataObject('item', { title: 'keep--dashes', itemType: 'journalArticle' });
itemKeepDashes.setField('publicationTitle', "keep");
itemKeepDashes.setField('issue', 'dashes');
itemKeepDashes.setField('date', "1999-07-15");
itemKeepHyphens = createUnsavedDataObject('item', { title: 'keep--hyphens', itemType: 'journalArticle' });
itemKeepHyphens.setField('publicationTitle', "keep");
itemKeepHyphens.setField('issue', 'hyphens');
itemKeepHyphens.setField('date', "1999-07-15");
itemNoRepeatedHyphens = createUnsavedDataObject('item', { title: 'no - repeated - hyphens', itemType: 'journalArticle' });
itemNoRepeatedHyphens.setField('publicationTitle', "no- repeated- hyphens");
itemNoRepeatedUnderscores = createUnsavedDataObject('item', { title: 'no _ repeated _ underscores', itemType: 'journalArticle' });
itemNoRepeatedUnderscores.setField('publicationTitle', "no_ repeated_ underscores");
});
it('should strip HTML tags from title', function () {
var htmlItem = createUnsavedDataObject('item', { title: 'Foo <i>Bar</i> Foo<br><br/><br />Bar' });
var str = Zotero.Attachments.getFileBaseNameFromItem(htmlItem, '{{ title }}');
var str = Zotero.Attachments.getFileBaseNameFromItem(htmlItem, { formatString: '{{ title }}' });
assert.equal(str, 'Foo Bar Foo Bar');
});
it('should accept basic formating options', function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, 'FOO{{year}}BAR'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: 'FOO{{year}}BAR' }),
'FOO1975BAR'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title truncate="50" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title truncate="50" }}' }),
'Barius and Pixelus - 1975 - Lorem Ipsum'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{firstCreator suffix=" - " replaceFrom=" *and *" replaceTo="&"}}{{year suffix=" - " replaceFrom="(\\d{2})(\\d{2})" replaceTo="$2"}}{{title truncate="50" replaceFrom=".m" replaceTo="a"}} - {{title truncate="50" replaceFrom=".m" replaceTo="a" regexOpts="g"}}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{firstCreator suffix=" - " replaceFrom=" *and *" replaceTo="&"}}{{year suffix=" - " replaceFrom="(\\d{2})(\\d{2})" replaceTo="$2"}}{{title truncate="50" replaceFrom=".m" replaceTo="a"}} - {{title truncate="50" replaceFrom=".m" replaceTo="a" regexOpts="g"}}' }),
'Barius&Pixelus - 75 - Lora Ipsum - Lora Ipsa'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{year suffix="-"}}{{firstCreator truncate="10" suffix="-"}}{{title truncate="5" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{year suffix="-"}}{{firstCreator truncate="10" suffix="-"}}{{title truncate="5" }}' }),
'1975-Barius and-Lorem'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, 'foo {{year}} bar {{year prefix="++" truncate="2" suffix="++"}}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: 'foo {{year}} bar {{year prefix="++" truncate="2" suffix="++"}}' }),
'foo 1975 bar ++19++'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title}}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title}}' }),
'Author et al. - 2000 - Has Many Authors'
);
});
it('should trim whitespaces from a value', function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemSpaces, '{{ title }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemSpaces, { formatString: '{{ title }}' }),
'Spaces!'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{title truncate="6"}}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{title truncate="6"}}' }),
'Lorem'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{firstCreator truncate="7"}}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{firstCreator truncate="7"}}' }),
'Barius'
);
// but preserve if it's configured as a prefix or suffix
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{title prefix=" " suffix=" "}}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{title prefix=" " suffix=" "}}' }),
' Lorem Ipsum '
);
});
it('should offer a range of options for composing creators', function () {
it('should offer a range of options for composing creators', async function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="1" }}' }),
'Barius'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" truncate="3" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="1" truncate="3" }}' }),
'Bar'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="5" join=" " }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="5" join=" " }}' }),
'Barius Pixelus'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="3" join=" " }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ authors max="3" join=" " }}' }),
'Author Creator Person'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ authors }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, { formatString: '{{ authors }}' }),
'AcmeCorp'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="2" name="family" initialize="family" join=" " initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ authors max="2" name="family" initialize="family" join=" " initialize-with="" }}' }),
'A C'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ authors max="2" name="family" initialize="family" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, { formatString: '{{ authors max="2" name="family" initialize="family" initialize-with="" }}' }),
'A'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" name="full" initialize="full" name-part-separator="" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="1" name="full" initialize="full" name-part-separator="" initialize-with="" }}' }),
'FB'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="3" name="full" initialize="full" name-part-separator="" join=" " initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ authors max="3" name="full" initialize="full" name-part-separator="" join=" " initialize-with="" }}' }),
'FA SC TP'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" name="family-given" initialize="given" name-part-separator="" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="1" name="family-given" initialize="given" name-part-separator="" initialize-with="" }}' }),
'BariusF'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="2" name="family-given" initialize="given" join=" " name-part-separator="" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ authors max="2" name="family-given" initialize="given" join=" " name-part-separator="" initialize-with="" }}' }),
'AuthorF CreatorS'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ editors }}test'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ editors }}test' }),
'test'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="1" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ editors max="1" }}' }),
'Editor1'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="5" join=" " }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ editors max="5" join=" " }}' }),
'Editor1 ProEditor2 SuperbEditor3'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="2" name="family" initialize="family" join=" " initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ editors max="2" name="family" initialize="family" join=" " initialize-with="" }}' }),
'E P'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="1" name="full" initialize="full" name-part-separator="" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ editors max="1" name="full" initialize="full" name-part-separator="" initialize-with="" }}' }),
'SE'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="1" name="family-given" initialize="given" name-part-separator="" initialize-with="" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ editors max="1" name="family-given" initialize="given" name-part-separator="" initialize-with="" }}' }),
'Editor1S'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="3" name="full" initialize="given" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ authors max="3" name="full" initialize="given" }}' }),
'F. Barius, B. Pixelus'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ creators case="upper" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ creators case="upper" }}' }),
'BARIUS, PIXELUS'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="2" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ authors max="2" }}' }),
'Author, Creator'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ creators max="3" join=" " name="given" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ creators max="3" join=" " name="given" }}' }),
'First Second Third'
);
});
it('should accept case parameter', async function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="upper" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="upper" }}' }),
'BEST PUBLICATIONS PLACE'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="lower" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="lower" }}' }),
'best publications place'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="title" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="title" }}' }),
'Best Publications Place'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="hyphen" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="hyphen" }}' }),
'best-publications-place'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="camel" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="camel" }}' }),
'bestPublicationsPlace'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="snake" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle case="snake" }}' }),
'best_publications_place'
);
});
it('should accept itemType or any other field', function () {
it('should not create repeated characters when converting case to hyphen or snake', async function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ itemType localize="true" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemNoRepeatedHyphens, { formatString: '{{ title case="hyphen" }}' }),
'no-repeated-hyphens'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemNoRepeatedHyphens, { formatString: '{{ publicationTitle case="hyphen" }}' }),
'no-repeated-hyphens'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemNoRepeatedUnderscores, { formatString: '{{ title case="snake" }}' }),
'no_repeated_underscores'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemNoRepeatedUnderscores, { formatString: '{{ publicationTitle case="snake" }}' }),
'no_repeated_underscores'
);
});
it('should accept itemType, attachmentTitle or any other field', function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ itemType localize="true" }}' }),
'Journal Article'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ publicationTitle }}' }),
'Best Publications Place'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ journalAbbreviation }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ journalAbbreviation }}' }),
'BPP'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ publisher }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ publisher }}' }),
'Awesome House'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ volume }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, { formatString: '{{ volume }}' }),
'3'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ issue }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ issue }}' }),
'42'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ pages }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ pages }}' }),
'321'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ number }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, { formatString: '{{ number }}' }),
'HBK-8539b'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ assignee }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, { formatString: '{{ assignee }}' }),
'Fast FooBar'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ attachmentTitle }}', attachmentTitle: 'Full Text' }),
'Full Text'
);
});
it("should support simple logic in template syntax", function () {
@ -1634,68 +1662,107 @@ describe("Zotero.Attachments", function() {
);
});
it("should skip missing fields", function () {
it("should skip missing fields", async function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemIncomplete, '{{ authors prefix = "a" suffix="-" }}{{ publicationTitle case="hyphen" suffix="-" }}{{ title }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemIncomplete, { formatString: '{{ authors prefix = "a" suffix="-" }}{{ publicationTitle case="hyphen" suffix="-" }}{{ title }}' }),
'Incomplete'
);
});
it("should recognized base-mapped fields", function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, '{{ bookTitle case="snake" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, { formatString: '{{ bookTitle case="snake" }}' }),
'book_title'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, '{{ publicationTitle case="snake" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, { formatString: '{{ publicationTitle case="snake" }}' }),
'book_title'
);
});
it("should trim spaces from template string", function () {
it("should trim spaces and remove new lines from the template string", function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, ' {{ bookTitle case="snake" }} '),
'book_title'
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, { formatString: ' {{ bookTitle case="snake" }}\n{{ bookTitle case="hyphen" prefix="-" }}' }),
'book_title-book-title'
);
});
it("should suppress suffixes where they would create a repeat character", function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ title suffix="-" }}{{ year prefix="-" }}'),
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ title suffix="-" }}{{ year prefix="-" }}' }),
'Lorem Ipsum-1975'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, '{{ title prefix="-" suffix="-" }}{{ year }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, { formatString: '{{ title prefix="-" suffix="-" }}{{ year }}' }),
'-Suffixes-1999'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, '{{ title suffix="-" }}{{ year prefix="-" }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, { formatString: '{{ title suffix="-" }}{{ year prefix="-" }}' }),
'-Suffixes-1999'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemKeepDashes, '{{ title suffix="-" }}{{ year prefix="-" }}'),
'keep--dashes-1999'
Zotero.Attachments.getFileBaseNameFromItem(itemKeepHyphens, { formatString: '{{ title suffix="-" }}{{ year prefix="-" }}' }),
'keep--hyphens-1999'
);
// keep--dashes is a title and should be kept unchanged but "keep" and "dashes" are fields
// keep--hyphens is a title and should be kept unchanged but "keep" and "hyphens" are fields
// separated by prefixes and suffixes where repeated characters should be suppressed
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemKeepDashes, '{{ title suffix="-" }}{{ publicationTitle suffix="-" }}{{ issue prefix="-" }}'),
'keep--dashes-keep-dashes'
Zotero.Attachments.getFileBaseNameFromItem(itemKeepHyphens, { formatString: '{{ title suffix="-" }}{{ publicationTitle suffix="-" }}{{ issue prefix="-" }}' }),
'keep--hyphens-keep-hyphens'
);
// keep--dashes is provided as literal part of the template and should be kept unchanged
// but "keep" and "dashes" are fields separated by prefixes and suffixes where repeated
// characters should be suppressed. Finally "keep--dashes" title is appended at the end
// keep--hyphens is provided as literal part of the template and should be kept unchanged
// but "keep" and "hyphens" are fields separated by prefixes and suffixes where repeated
// characters should be suppressed. Finally "keep--hyphens" title is appended at the end
// which should also be kept as is.
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemKeepDashes, 'keep--dashes-{{ publicationTitle prefix="-" suffix="-" }}{{ issue prefix="-" suffix="-" }}-keep--dashes-{{ publicationTitle suffix="-" }}test{{ title prefix="-" }}'),
'keep--dashes-keep-dashes-keep--dashes-keep-test-keep--dashes'
Zotero.Attachments.getFileBaseNameFromItem(itemKeepHyphens, { formatString: 'keep--hyphens-{{ publicationTitle prefix="-" suffix="-" }}{{ issue prefix="-" suffix="-" }}-keep--hyphens-{{ publicationTitle suffix="-" }}test{{ title prefix="-" }}' }),
'keep--hyphens-keep-hyphens-keep--hyphens-keep-test-keep--hyphens'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, '{{ title prefix="/" suffix="\\" }}{{ year }}'),
Zotero.Attachments.getFileBaseNameFromItem(itemSuffixes, { formatString: '{{ title prefix="/" suffix="\\" }}{{ year }}' }),
'-Suffixes-1999'
);
});
it("should be possible to test attachmentTitle", function () {
const template = `{{ if {{ attachmentTitle match="^(full.*|submitted.*|accepted.*)$" }} }}
{{ firstCreator suffix=" - " }}{{ year suffix=" - " }}{{ title truncate="100" }}
{{ else }}
{{ attachmentTitle replaceFrom="\\.pdf|\\.epub|\\.png" }}
{{ endif }}`;
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: template, attachmentTitle: 'Full Text' }),
'Barius and Pixelus - 1975 - Lorem Ipsum'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, { formatString: template, attachmentTitle: 'Other Attachment.png' }),
'Other Attachment'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, { formatString: `{{ attachmentTitle start = "6" truncate = "4" }}`, attachmentTitle: 'Other Attachment.png' }),
'Atta'
);
});
it("should perform regex in a case-insensitive way, unless configured otherwise", function () {
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ title match="lorem" }}' }),
'Lorem'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ title match="lorem" regexOpts="" }}' }),
'_' // template formatting results in an empty string, "_" is returned to make it a valid file name
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ title replaceFrom="lorem" replaceTo="Foobar" }}' }),
'Foobar Ipsum'
);
assert.equal(
Zotero.Attachments.getFileBaseNameFromItem(item, { formatString: '{{ title replaceFrom="lorem" replaceTo="foobar" regexOpts="" }}' }),
'Lorem Ipsum'
);
});
it("should convert old attachmentRenameFormatString to use new attachmentRenameTemplate syntax", function () {
assert.equal(
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%c - }{%y - }{%t{50}}'),