parent
96e2510165
commit
0ba766f2e0
6 changed files with 766 additions and 125 deletions
|
@ -23,7 +23,7 @@
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Zotero.Attachments = new function(){
|
Zotero.Attachments = new function () {
|
||||||
const { HiddenBrowser } = ChromeUtils.import("chrome://zotero/content/HiddenBrowser.jsm");
|
const { HiddenBrowser } = ChromeUtils.import("chrome://zotero/content/HiddenBrowser.jsm");
|
||||||
|
|
||||||
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
|
// Keep in sync with Zotero.Schema.integrityCheck() and this.linkModeToName()
|
||||||
|
@ -2241,15 +2241,6 @@ Zotero.Attachments = new function(){
|
||||||
* (Optional) |formatString| specifies the format string -- otherwise
|
* (Optional) |formatString| specifies the format string -- otherwise
|
||||||
* the 'attachmentRenameFormatString' pref is used
|
* the 'attachmentRenameFormatString' pref is used
|
||||||
*
|
*
|
||||||
* Valid substitution markers:
|
|
||||||
*
|
|
||||||
* %c -- firstCreator
|
|
||||||
* %y -- year (extracted from Date field)
|
|
||||||
* %t -- title
|
|
||||||
*
|
|
||||||
* Fields can be truncated to a certain length by appending an integer
|
|
||||||
* within curly brackets -- e.g. %t{50} truncates the title to 50 characters
|
|
||||||
*
|
|
||||||
* @param {Zotero.Item} item
|
* @param {Zotero.Item} item
|
||||||
* @param {String} formatString
|
* @param {String} formatString
|
||||||
*/
|
*/
|
||||||
|
@ -2257,79 +2248,160 @@ Zotero.Attachments = new function(){
|
||||||
if (!(item instanceof Zotero.Item)) {
|
if (!(item instanceof Zotero.Item)) {
|
||||||
throw new Error("'item' must be a Zotero.Item");
|
throw new Error("'item' must be a Zotero.Item");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formatString) {
|
if (!formatString) {
|
||||||
formatString = Zotero.Prefs.get('attachmentRenameFormatString');
|
formatString = Zotero.Prefs.get('attachmentRenameFormatString');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaces the substitution marker with the field value,
|
const getSlicedCreatorsOfType = (creatorType, slice) => {
|
||||||
// truncating based on the {[0-9]+} modifier if applicable
|
let creatorTypeIDs;
|
||||||
function rpl(field, str) {
|
switch (creatorType) {
|
||||||
if (!str) {
|
case 'author':
|
||||||
str = formatString;
|
case 'authors':
|
||||||
}
|
creatorTypeIDs = [Zotero.CreatorTypes.getPrimaryIDForType(item.itemTypeID)];
|
||||||
|
|
||||||
switch (field) {
|
|
||||||
case 'creator':
|
|
||||||
field = 'firstCreator';
|
|
||||||
var rpl = '%c';
|
|
||||||
break;
|
break;
|
||||||
|
case 'editor':
|
||||||
case 'year':
|
case 'editors':
|
||||||
var rpl = '%y';
|
creatorTypeIDs = [Zotero.CreatorTypes.getID('editor'), Zotero.CreatorTypes.getID('seriesEditor')];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'title':
|
|
||||||
var rpl = '%t';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value;
|
|
||||||
switch (field) {
|
|
||||||
case 'title':
|
|
||||||
value = item.getField('title', false, true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'year':
|
|
||||||
value = item.getField('date', true, true);
|
|
||||||
if (value) {
|
|
||||||
value = Zotero.Date.multipartToSQL(value).substr(0, 4);
|
|
||||||
if (value == '0000') {
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
value = '' + item.getField(field, false, true);
|
case 'creator':
|
||||||
|
case 'creators':
|
||||||
|
creatorTypeIDs = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var re = new RegExp("\{?([^%\{\}]*)" + rpl + "(\{[0-9]+\})?" + "([^%\{\}]*)\}?");
|
if (slice === 0) {
|
||||||
|
return [];
|
||||||
// If no value for this field, strip entire conditional block
|
}
|
||||||
// (within curly braces)
|
const matchingCreators = creatorTypeIDs === null
|
||||||
if (!value) {
|
? item.getCreators()
|
||||||
if (str.match(re)) {
|
: item.getCreators().filter(c => creatorTypeIDs.includes(c.creatorTypeID));
|
||||||
return str.replace(re, '')
|
const slicedCreators = slice > 0
|
||||||
|
? matchingCreators.slice(0, slice)
|
||||||
|
: matchingCreators.slice(slice);
|
||||||
|
|
||||||
|
if (slice < 0) {
|
||||||
|
slicedCreators.reverse();
|
||||||
|
}
|
||||||
|
return slicedCreators;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const common = (value, { truncate = false, prefix = '', suffix = '', case: textCase = '' } = {}) => {
|
||||||
|
if (value === '' || value === null || typeof value === 'undefined') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (truncate) {
|
||||||
|
value = value.substr(0, truncate);
|
||||||
|
}
|
||||||
|
if (prefix) {
|
||||||
|
value = prefix + value;
|
||||||
|
}
|
||||||
|
if (suffix) {
|
||||||
|
value += suffix;
|
||||||
|
}
|
||||||
|
switch (textCase) {
|
||||||
|
case 'upper':
|
||||||
|
value = value.toUpperCase();
|
||||||
|
break;
|
||||||
|
case 'lower':
|
||||||
|
value = value.toLowerCase();
|
||||||
|
break;
|
||||||
|
case 'sentence':
|
||||||
|
value = value.slice(0, 1).toUpperCase() + value.slice(1);
|
||||||
|
break;
|
||||||
|
case 'title':
|
||||||
|
value = Zotero.Utilities.capitalizeTitle(value, true);
|
||||||
|
break;
|
||||||
|
case 'hyphen':
|
||||||
|
value = value.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
break;
|
||||||
|
case 'snake':
|
||||||
|
value = value.toLowerCase().replace(/\s+/g, '_');
|
||||||
|
break;
|
||||||
|
case 'camel':
|
||||||
|
value = value.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeFn = (name, shouldInitialize, initializeWith) => (shouldInitialize ? name.slice(0, 1).toUpperCase() + initializeWith : name);
|
||||||
|
|
||||||
|
const transformName = (creator, { name, namePartSeparator, initialize, initializeWith } = {}) => {
|
||||||
|
if (creator.name) {
|
||||||
|
return initializeFn(creator.name, ['full', 'name'].includes(initialize), initializeWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstLast = ['full', 'given-family', 'first-last'];
|
||||||
|
const lastFirst = ['full-reversed', 'family-given', 'last-first'];
|
||||||
|
const first = ['given', 'first'];
|
||||||
|
const last = ['family', 'last'];
|
||||||
|
|
||||||
|
if (firstLast.includes(name)) {
|
||||||
|
return initializeFn(creator.firstName, ['full', ...first].includes(initialize), initializeWith) + namePartSeparator + initializeFn(creator.lastName, ['full', ...last].includes(initialize), initializeWith);
|
||||||
|
}
|
||||||
|
else if (lastFirst.includes(name)) {
|
||||||
|
return initializeFn(creator.lastName, ['full', ...last].includes(initialize), initializeWith) + namePartSeparator + initializeFn(creator.firstName, ['full', ...first].includes(initialize), initializeWith);
|
||||||
|
}
|
||||||
|
else if (first.includes(name)) {
|
||||||
|
return initializeFn(creator.firstName, ['full', ...first].includes(initialize), initializeWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initializeFn(creator.lastName, ['full', ...last].includes(initialize), initializeWith);
|
||||||
|
};
|
||||||
|
|
||||||
|
const commonCreators = (value, { max = Infinity, name = 'family', namePartSeparator = ' ', join = ', ', initialize = '', initializeWith = '.' } = {}) => {
|
||||||
|
return getSlicedCreatorsOfType(value, max)
|
||||||
|
.map(c => transformName(c, { name, namePartSeparator, initialize, initializeWith }))
|
||||||
|
.join(join);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = Zotero.ItemFields.getAll()
|
||||||
|
.map(f => f.name)
|
||||||
|
.reduce((obj, name) => {
|
||||||
|
obj[name] = (args) => {
|
||||||
|
return common(item.getField(name, false, true), args);
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const year = (args) => {
|
||||||
|
let value = item.getField('date', true, true);
|
||||||
|
if (value) {
|
||||||
|
value = Zotero.Date.multipartToSQL(value).substr(0, 4);
|
||||||
|
if (value == '0000') {
|
||||||
|
value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return common(value, args);
|
||||||
var f = function(match, p1, p2, p3) {
|
};
|
||||||
var maxChars = p2 ? p2.replace(/[^0-9]+/g, '') : false;
|
|
||||||
return p1 + (maxChars ? value.substr(0, maxChars) : value) + p3;
|
const itemType = ({ localize = false, ...rest }) => common(
|
||||||
}
|
localize ? Zotero.ItemTypes.getLocalizedString(item.itemType) : item.itemType, rest
|
||||||
|
);
|
||||||
return str.replace(re, f);
|
|
||||||
}
|
const creatorFields = ['authors', 'editors', 'creators'].reduce((obj, name) => {
|
||||||
|
obj[name] = (args) => {
|
||||||
formatString = rpl('creator');
|
return common(commonCreators(name, args), args);
|
||||||
formatString = rpl('year');
|
};
|
||||||
formatString = rpl('title');
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const firstCreator = args => common(
|
||||||
|
// 74492e40 adds \u2068 and \u2069 around names in the `firstCreator` field, which we don't want in the filename
|
||||||
|
// We might actually want to move this replacement to getValidFileName
|
||||||
|
item.getField('firstCreator', false, true).replaceAll('\u2068', '').replaceAll('\u2069', ''), args
|
||||||
|
);
|
||||||
|
|
||||||
|
const vars = { ...fields, ...creatorFields, firstCreator, itemType, year };
|
||||||
|
|
||||||
|
formatString = Zotero.Utilities.Internal.generateHTMLFromTemplate(formatString, vars);
|
||||||
formatString = Zotero.Utilities.cleanTags(formatString);
|
formatString = Zotero.Utilities.cleanTags(formatString);
|
||||||
formatString = Zotero.File.getValidFileName(formatString);
|
formatString = Zotero.File.getValidFileName(formatString);
|
||||||
return formatString;
|
return formatString;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
this.shouldAutoRenameFile = function (isLink) {
|
this.shouldAutoRenameFile = function (isLink) {
|
||||||
|
@ -3029,4 +3101,4 @@ Zotero.Attachments = new function(){
|
||||||
}
|
}
|
||||||
throw new Error(`Invalid link mode name '${linkModeName}'`);
|
throw new Error(`Invalid link mode name '${linkModeName}'`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
*/
|
*/
|
||||||
Zotero.Prefs = new function(){
|
Zotero.Prefs = new function() {
|
||||||
// Privileged methods
|
// Privileged methods
|
||||||
this.get = get;
|
this.get = get;
|
||||||
this.set = set;
|
this.set = set;
|
||||||
|
@ -49,7 +49,7 @@ Zotero.Prefs = new function(){
|
||||||
if (!fromVersion) {
|
if (!fromVersion) {
|
||||||
fromVersion = 0;
|
fromVersion = 0;
|
||||||
}
|
}
|
||||||
var toVersion = 7;
|
var toVersion = 8;
|
||||||
if (fromVersion < toVersion) {
|
if (fromVersion < toVersion) {
|
||||||
for (var i = fromVersion + 1; i <= toVersion; i++) {
|
for (var i = fromVersion + 1; i <= toVersion; i++) {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
|
@ -109,6 +109,14 @@ Zotero.Prefs = new function(){
|
||||||
case 7:
|
case 7:
|
||||||
this.clear('layers.acceleration.disabled', true);
|
this.clear('layers.acceleration.disabled', true);
|
||||||
break;
|
break;
|
||||||
|
// Convert "attachment rename format string" from old format (e.g. {%c - }{%y - }{%t{50}})
|
||||||
|
// to a new format that uses the template engine
|
||||||
|
case 8:
|
||||||
|
if (this.prefHasUserValue('attachmentRenameFormatString')) {
|
||||||
|
this.set('attachmentRenameFormatString', this.convertLegacyAttachmentRenameFormatString(
|
||||||
|
this.get('attachmentRenameFormatString') || ''
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.set('prefVersion', toVersion);
|
this.set('prefVersion', toVersion);
|
||||||
|
@ -119,7 +127,7 @@ Zotero.Prefs = new function(){
|
||||||
/**
|
/**
|
||||||
* Retrieve a preference
|
* Retrieve a preference
|
||||||
**/
|
**/
|
||||||
function get(pref, global){
|
function get(pref, global) {
|
||||||
try {
|
try {
|
||||||
pref = global ? pref : ZOTERO_CONFIG.PREF_BRANCH + pref;
|
pref = global ? pref : ZOTERO_CONFIG.PREF_BRANCH + pref;
|
||||||
let branch = this.rootBranch;
|
let branch = this.rootBranch;
|
||||||
|
@ -554,4 +562,38 @@ Zotero.Prefs = new function(){
|
||||||
}
|
}
|
||||||
Zotero.Prefs.set(prefKey, JSON.stringify(libraries));
|
Zotero.Prefs.set(prefKey, JSON.stringify(libraries));
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Converts a value of a `attachmentRenameFormatString` pref from a legacy format string
|
||||||
|
* with % markers to a new format that uses the template engine
|
||||||
|
*
|
||||||
|
* @param {string} formatString - The legacy format string to convert.
|
||||||
|
* @returns {string} The new format string.
|
||||||
|
*/
|
||||||
|
this.convertLegacyAttachmentRenameFormatString = function (formatString) {
|
||||||
|
const markers = {
|
||||||
|
c: 'firstCreator',
|
||||||
|
y: 'year',
|
||||||
|
t: 'title'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Regexp contains 4 capture groups all wrapped in {}:
|
||||||
|
// * Prefix before the wildcard, can be empty string
|
||||||
|
// * Any recognized marker. % sign marks a wildcard and is required for a match but is
|
||||||
|
// not part of the capture group. Recognized markers are specified in a `markers`
|
||||||
|
// lookup.
|
||||||
|
// * Optionally a maximum number of characters to truncate the value to
|
||||||
|
// * Suffix after the wildcard, can be empty string
|
||||||
|
const re = new RegExp(`{([^%{}]*)%(${Object.keys(markers).join('|')})({[0-9]+})?([^%{}]*)}`, 'ig');
|
||||||
|
|
||||||
|
return formatString.replace(re, (match, prefix, marker, truncate, suffix) => {
|
||||||
|
const field = markers[marker];
|
||||||
|
truncate = truncate ? truncate.replace(/[^0-9]+/g, '') : false;
|
||||||
|
prefix = prefix ? `prefix="${prefix}"` : null;
|
||||||
|
suffix = suffix ? `suffix="${suffix}"` : null;
|
||||||
|
truncate = truncate ? `truncate="${truncate}"` : null;
|
||||||
|
|
||||||
|
return `{{ ${[field, truncate, prefix, suffix].filter(f => f !== null).join(' ')} }}`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -2091,11 +2091,47 @@ Zotero.Utilities.Internal = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a string by outer-most brackets (`{{` and '}}' by default, configurable).
|
||||||
|
*
|
||||||
|
* @param {string} input - The input string to split.
|
||||||
|
* @returns {string[]} An array of strings split by outer-most brackets.
|
||||||
|
*/
|
||||||
|
splitByOuterBrackets: function(input, left = '{{', right = '}}') {
|
||||||
|
const result = [];
|
||||||
|
let startIndex = 0;
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
if (input.slice(i, i + 2) === left) {
|
||||||
|
if (depth === 0) {
|
||||||
|
result.push(input.slice(startIndex, i));
|
||||||
|
startIndex = i;
|
||||||
|
}
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
else if (input.slice(i, i + 2) === right) {
|
||||||
|
depth--;
|
||||||
|
if (depth === 0) {
|
||||||
|
result.push(input.slice(startIndex, i + 2));
|
||||||
|
startIndex = i + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex < input.length) {
|
||||||
|
result.push(input.slice(startIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic templating engine
|
* A basic templating engine
|
||||||
*
|
*
|
||||||
* - 'if' statement does case-insensitive string comparison
|
* - 'if' statement does case-insensitive string comparison
|
||||||
* - Spaces around '==' are necessary in 'if' statement
|
* - functions can be called from if statements but must be wrapped in {{}} if arguments are passed (e.g. {{myFunction arg1="foo" arg2="bar"}})
|
||||||
*
|
*
|
||||||
* Vars example:
|
* Vars example:
|
||||||
* {
|
* {
|
||||||
|
@ -2121,44 +2157,108 @@ Zotero.Utilities.Internal = {
|
||||||
* @returns {String} HTML
|
* @returns {String} HTML
|
||||||
*/
|
*/
|
||||||
generateHTMLFromTemplate: function (template, vars) {
|
generateHTMLFromTemplate: function (template, vars) {
|
||||||
let levels = [{ condition: true }];
|
const hyphenToCamel = varName => varName.replace(/-(.)/g, (_, g1) => g1.toUpperCase());
|
||||||
|
|
||||||
|
const getAttributes = (part) => {
|
||||||
|
let attrsRegexp = new RegExp(/(([\w-]*) *=+ *(['"])((\\\3|[^\3])*?)\3)/g);
|
||||||
|
let attrs = {};
|
||||||
|
let match;
|
||||||
|
while ((match = attrsRegexp.exec(part))) {
|
||||||
|
if (match[1]) { // if first alternative (i.e. argument with value wrapped in " or ') matched, even if value is empty
|
||||||
|
attrs[hyphenToCamel(match[2])] = match[4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const evaluateIdentifier = (ident, args = {}) => {
|
||||||
|
ident = hyphenToCamel(ident);
|
||||||
|
|
||||||
|
if (!(ident in vars)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const identValue = typeof vars[ident] === 'function' ? vars[ident](args) : vars[ident];
|
||||||
|
|
||||||
|
if (Array.isArray(identValue)) {
|
||||||
|
return identValue.length ? identValue.join(',') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof identValue !== 'string') {
|
||||||
|
throw new Error(`Identifier "${ident}" does not evaluate to a string`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return identValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// evaluates extracted (i.e. without brackets) statement (e.g. `sum a="1" b="2"`) into a string value
|
||||||
|
const evaluateStatement = (statement) => {
|
||||||
|
statement = statement.trim();
|
||||||
|
const operator = statement.split(' ', 1)[0].trim();
|
||||||
|
const args = statement.slice(operator.length).trim();
|
||||||
|
|
||||||
|
return evaluateIdentifier(operator, getAttributes(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
// splits raw (i.e. bracketed) statement (e.g. `{{ sum a="1" b="2" }}) into operator and arguments (e.g. ['sum', 'a="1" b="2"'])
|
||||||
|
const splitStatement = (statement) => {
|
||||||
|
statement = statement.slice(2, -2).trim();
|
||||||
|
const operator = statement.split(' ', 1)[0].trim();
|
||||||
|
const args = statement.slice(operator.length).trim();
|
||||||
|
return [operator, args];
|
||||||
|
};
|
||||||
|
|
||||||
|
// evaluates a condition (e.g. `a == "b"`) into a boolean value
|
||||||
|
const evaluateCondition = (condition) => {
|
||||||
|
const comparators = ['==', '!='];
|
||||||
|
condition = condition.trim();
|
||||||
|
|
||||||
|
// match[1] if left is statement, match[3] if left is literal, match[4] if left is identifier
|
||||||
|
// match[6] if right is statement, match[8] if right is literal, match[9] if right is identifier
|
||||||
|
// match[2] and match[7] are used to match the quotes around the literal (and then check that the other quote is the same)
|
||||||
|
const match = condition.match(new RegExp(String.raw`(?:{{(.*?)}}|(?:(['"])(.*?)\2)|([^ ]+)) *(${comparators.join('|')}) *(?:{{(.*?)}}|(?:(['"])(.*?)\7)|([^ ]+))`));
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
// condition is a statement or identifier without a comparator
|
||||||
|
if (condition.startsWith('{{')) {
|
||||||
|
const [operator, args] = splitStatement(condition);
|
||||||
|
return !!evaluateIdentifier(operator, getAttributes(args));
|
||||||
|
}
|
||||||
|
return !!evaluateIdentifier(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = match[1] ? evaluateStatement(match[1]) : match[3] ?? evaluateIdentifier(match[4]) ?? '';
|
||||||
|
const comparator = match[5];
|
||||||
|
const right = match[6] ? evaluateStatement(match[6]) : match[8] ?? evaluateIdentifier(match[9]) ?? '';
|
||||||
|
|
||||||
|
switch (comparator) {
|
||||||
|
default:
|
||||||
|
case '==':
|
||||||
|
return left.toLowerCase() == right.toLowerCase();
|
||||||
|
case '!=':
|
||||||
|
return left.toLowerCase() != right.toLowerCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
let parts = template.split(/{{|}}/);
|
const levels = [{ condition: true }];
|
||||||
|
const parts = this.splitByOuterBrackets(template);
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
let part = parts[i];
|
let part = parts[i];
|
||||||
let level = levels[levels.length - 1];
|
let level = levels[levels.length - 1];
|
||||||
if (i % 2 === 1) {
|
|
||||||
let operator = part.split(' ').filter(x => x)[0];
|
if (part.startsWith('{{')) {
|
||||||
// Get arguments that are used for 'if'
|
const [operator, args] = splitStatement(part);
|
||||||
let args = [];
|
|
||||||
let match = part.match(/(["'][^"|^']+["']|[^\s"']+)/g);
|
|
||||||
if (match) {
|
|
||||||
args = match.map(x => x.replace(/['"](.*)['"]/, '$1')).slice(1);
|
|
||||||
}
|
|
||||||
if (operator === 'if') {
|
if (operator === 'if') {
|
||||||
level = { condition: false, executed: false, parentCondition: levels[levels.length-1].condition };
|
level = { condition: false, executed: false, parentCondition: levels[levels.length - 1].condition };
|
||||||
levels.push(level);
|
levels.push(level);
|
||||||
}
|
}
|
||||||
if (['if', 'elseif'].includes(operator)) {
|
if (['if', 'elseif'].includes(operator)) {
|
||||||
if (!level.executed) {
|
if (!level.executed) {
|
||||||
level.condition = level.parentCondition && (
|
level.condition = level.parentCondition && evaluateCondition(args);
|
||||||
args[2]
|
|
||||||
// If string variable is equal to the provided string
|
|
||||||
? vars[args[0]].toLowerCase() == args[2].toLowerCase()
|
|
||||||
: (
|
|
||||||
Array.isArray(vars[args[0]])
|
|
||||||
// Is array non empty
|
|
||||||
? !!vars[args[0]].length
|
|
||||||
: (
|
|
||||||
typeof vars[args[0]] === 'function'
|
|
||||||
// If function returns a value (only string is supported)
|
|
||||||
// Note: To keep things simple, this doesn't support function attributes
|
|
||||||
? !!vars[args[0]]({})
|
|
||||||
// If string variable exists
|
|
||||||
: !!vars[args[0]]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
level.executed = level.condition;
|
level.executed = level.condition;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -2176,18 +2276,8 @@ Zotero.Utilities.Internal = {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (level.condition) {
|
if (level.condition) {
|
||||||
// Get attributes i.e. join=" #"
|
const attrs = getAttributes(part);
|
||||||
let attrsRegexp = new RegExp(/((\w*) *=+ *(['"])((\\\3|[^\3])*?)\3)|((\w*) *=+ *(\w*))/g);
|
html += evaluateIdentifier(operator, attrs);
|
||||||
let attrs = {};
|
|
||||||
while ((match = attrsRegexp.exec(part))) {
|
|
||||||
if (match[4]) {
|
|
||||||
attrs[match[2]] = match[4];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
attrs[match[7]] = match[8];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html += (typeof vars[operator] === 'function' ? vars[operator](attrs) : vars[operator]) || '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (level.condition) {
|
else if (level.condition) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ pref("extensions.zotero.autoRecognizeFiles", true);
|
||||||
pref("extensions.zotero.autoRenameFiles", true);
|
pref("extensions.zotero.autoRenameFiles", true);
|
||||||
pref("extensions.zotero.autoRenameFiles.linked", false);
|
pref("extensions.zotero.autoRenameFiles.linked", false);
|
||||||
pref("extensions.zotero.autoRenameFiles.fileTypes", "application/pdf");
|
pref("extensions.zotero.autoRenameFiles.fileTypes", "application/pdf");
|
||||||
pref("extensions.zotero.attachmentRenameFormatString", "{%c - }{%y - }{%t{50}}");
|
pref("extensions.zotero.attachmentRenameFormatString", "{{ firstCreator suffix=\" - \" }}{{ year suffix=\" - \" }}{{ title truncate=\"50\" }}");
|
||||||
pref("extensions.zotero.capitalizeTitles", false);
|
pref("extensions.zotero.capitalizeTitles", false);
|
||||||
pref("extensions.zotero.launchNonNativeFiles", false);
|
pref("extensions.zotero.launchNonNativeFiles", false);
|
||||||
pref("extensions.zotero.sortNotesChronologically", false);
|
pref("extensions.zotero.sortNotesChronologically", false);
|
||||||
|
|
|
@ -1293,11 +1293,297 @@ describe("Zotero.Attachments", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#getFileBaseNameFromItem()", function () {
|
describe("#getFileBaseNameFromItem()", function () {
|
||||||
it("should strip HTML tags from title", async function () {
|
var item, itemManyAuthors, itemPatent, itemIncomplete, itemBookSection;
|
||||||
var item = createUnsavedDataObject('item', { title: 'Foo <i>Bar</i> Foo<br><br/><br />Bar' });
|
|
||||||
var str = Zotero.Attachments.getFileBaseNameFromItem(item);
|
before(() => {
|
||||||
|
item = createUnsavedDataObject('item', { title: 'Lorem Ipsum', itemType: 'journalArticle' });
|
||||||
|
item.setCreators([
|
||||||
|
{ firstName: 'Foocius', lastName: 'Barius', creatorType: 'author' },
|
||||||
|
{ firstName: 'Bazius', lastName: 'Pixelus', creatorType: 'author' }
|
||||||
|
]);
|
||||||
|
item.setField('date', "1975-10-15");
|
||||||
|
item.setField('publicationTitle', 'Best Publications Place');
|
||||||
|
item.setField('journalAbbreviation', 'BPP');
|
||||||
|
item.setField('issue', '42');
|
||||||
|
item.setField('pages', '321');
|
||||||
|
|
||||||
|
|
||||||
|
itemManyAuthors = createUnsavedDataObject('item', { title: 'Has Many Authors', itemType: 'book' });
|
||||||
|
itemManyAuthors.setCreators([
|
||||||
|
{ firstName: 'First', lastName: 'Author', creatorType: 'author' },
|
||||||
|
{ firstName: 'Second', lastName: 'Creator', creatorType: 'author' },
|
||||||
|
{ firstName: 'Third', lastName: 'Person', creatorType: 'author' },
|
||||||
|
{ firstName: 'Final', lastName: 'Writer', creatorType: 'author' },
|
||||||
|
{ firstName: 'Some', lastName: 'Editor1', creatorType: 'editor' },
|
||||||
|
{ firstName: 'Other', lastName: 'ProEditor2', creatorType: 'editor' },
|
||||||
|
{ firstName: 'Last', lastName: 'SuperbEditor3', creatorType: 'editor' },
|
||||||
|
]);
|
||||||
|
itemManyAuthors.setField('date', "2000-01-02");
|
||||||
|
itemManyAuthors.setField('publisher', 'Awesome House');
|
||||||
|
itemManyAuthors.setField('volume', '3');
|
||||||
|
|
||||||
|
itemPatent = createUnsavedDataObject('item', { title: 'Retroencabulator', itemType: 'patent' });
|
||||||
|
itemPatent.setCreators([
|
||||||
|
{ name: 'AcmeCorp', creatorType: 'inventor' },
|
||||||
|
{ firstName: 'Wile', lastName: 'E', creatorType: 'contributor' },
|
||||||
|
{ firstName: 'Road', lastName: 'R', creatorType: 'contributor' },
|
||||||
|
]);
|
||||||
|
itemPatent.setField('date', '1952-05-10');
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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 }}');
|
||||||
assert.equal(str, 'Foo Bar Foo Bar');
|
assert.equal(str, 'Foo Bar Foo Bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should accept basic formating options', function () {
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, 'FOO{{year}}BAR'),
|
||||||
|
'FOO1975BAR'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title truncate="50" }}'),
|
||||||
|
'Barius and Pixelus - 1975 - Lorem Ipsum'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{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="++"}}'),
|
||||||
|
'foo 1975 bar ++19++'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{firstCreator suffix=" - "}}{{year suffix=" - "}}{{title}}'),
|
||||||
|
'Author et al. - 2000 - Has Many Authors'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should offer a range of options for composing creators', function () {
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" }}'),
|
||||||
|
'Barius'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="1" truncate="3" }}'),
|
||||||
|
'Bar'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ authors max="5" join=" " }}'),
|
||||||
|
'Barius Pixelus'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="3" join=" " }}'),
|
||||||
|
'Author Creator Person'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ authors }}'),
|
||||||
|
'AcmeCorp'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ 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="" }}'),
|
||||||
|
'A'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ 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="" }}'),
|
||||||
|
'FA SC TP'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ 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="" }}'),
|
||||||
|
'AuthorF CreatorS'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ editors }}test'),
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="1" }}'),
|
||||||
|
'Editor1'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ editors max="5" join=" " }}'),
|
||||||
|
'Editor1 ProEditor2 SuperbEditor3'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ 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="" }}'),
|
||||||
|
'SE'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ 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" }}'),
|
||||||
|
'F. Barius, B. Pixelus'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ creators case="upper" }}'),
|
||||||
|
'BARIUS, PIXELUS'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ authors max="2" }}'),
|
||||||
|
'Author, Creator'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ 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" }}'),
|
||||||
|
'BEST PUBLICATIONS PLACE'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="lower" }}'),
|
||||||
|
'best publications place'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="title" }}'),
|
||||||
|
'Best Publications Place'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="hyphen" }}'),
|
||||||
|
'best-publications-place'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="camel" }}'),
|
||||||
|
'bestPublicationsPlace'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle case="snake" }}'),
|
||||||
|
'best_publications_place'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept itemType or any other field', function () {
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ itemType localize="true" }}'),
|
||||||
|
'Journal Article'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ publicationTitle }}'),
|
||||||
|
'Best Publications Place'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ journalAbbreviation }}'),
|
||||||
|
'BPP'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ publisher }}'),
|
||||||
|
'Awesome House'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, '{{ volume }}'),
|
||||||
|
'3'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ issue }}'),
|
||||||
|
'42'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, '{{ pages }}'),
|
||||||
|
'321'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ number }}'),
|
||||||
|
'HBK-8539b'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, '{{ assignee }}'),
|
||||||
|
'Fast FooBar'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should support simple logic in template syntax", function () {
|
||||||
|
const template = '{{ if itemType == "journalArticle" }}j-{{ publicationTitle case="hyphen" }}{{ elseif itemType == "patent" }}p-{{ number case="hyphen" }}{{ else }}o-{{ title case="hyphen" }}{{ endif }}';
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(item, template), 'j-best-publications-place'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemPatent, template), 'p-hbk-8539b'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemManyAuthors, template), 'o-has-many-authors'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should skip missing fields", function () {
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemIncomplete, '{{ 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" }}'),
|
||||||
|
'book_title'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Attachments.getFileBaseNameFromItem(itemBookSection, '{{ publicationTitle case="snake" }}'),
|
||||||
|
'book_title'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should convert formatString attachmentRenameFormatString to use template syntax", function () {
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%c - }{%y - }{%t{50}}'),
|
||||||
|
'{{ firstCreator suffix=" - " }}{{ year suffix=" - " }}{{ title truncate="50" }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{ - %y - }'),
|
||||||
|
'{{ year prefix=" - " suffix=" - " }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%y{2}00}'),
|
||||||
|
'{{ year truncate="2" suffix="00" }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%c5 - }'),
|
||||||
|
'{{ firstCreator suffix="5 - " }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%c-2 - }'),
|
||||||
|
'{{ firstCreator suffix="-2 - " }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{%t5 - }'),
|
||||||
|
'{{ title suffix="5 - " }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('{++%t{10}--}'),
|
||||||
|
'{{ title truncate="10" prefix="++" suffix="--" }}'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
Zotero.Prefs.convertLegacyAttachmentRenameFormatString('foo{%c}-{%t{10}}-{%y{2}00}'),
|
||||||
|
'foo{{ firstCreator }}-{{ title truncate="10" }}-{{ year truncate="2" suffix="00" }}'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#getBaseDirectoryRelativePath()", function () {
|
describe("#getBaseDirectoryRelativePath()", function () {
|
||||||
|
|
|
@ -549,25 +549,176 @@ describe("Zotero.Utilities.Internal", function () {
|
||||||
it("should support variables with attributes", function () {
|
it("should support variables with attributes", function () {
|
||||||
var vars = {
|
var vars = {
|
||||||
v1: '1',
|
v1: '1',
|
||||||
v2: (pars) => pars.a1 + pars.a2 + pars.a3,
|
v2: pars => `${pars.a1 ?? ''}${pars.a2 ?? ''}${pars.a3 ?? ''}`,
|
||||||
v3: () => '',
|
v3: () => '',
|
||||||
v5: () => 'something',
|
v5: () => 'something',
|
||||||
ar1: [],
|
ar1: [],
|
||||||
ar2: [1, 2]
|
ar2: [1, 2]
|
||||||
};
|
};
|
||||||
var template = `{{ v1}}{{v2 a1= 1 a2 =' 2' a3 = "3 "}}{{v3}}{{v4}}{{if ar1}}ar1{{endif}}{{if ar2}}{{ar2}}{{endif}}{{if v5}}yes{{endif}}{{if v3}}no{{endif}}{{if v2}}no{{endif}}`;
|
var template = `{{ v1}}{{v2 a1= "1" a2 =' 2' a3 = "3 "}}{{v3}}{{v4}}{{if ar1}}ar1{{endif}}{{if ar2}}{{ar2}}{{endif}}{{if v5}}yes{{endif}}{{if v3}}no1{{endif}}{{if v2}}{{v2}}{{endif}}`;
|
||||||
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
assert.equal(html, '11 23 1,2yes');
|
assert.equal(html, '11 23 1,2yes');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should support empty string as attribute value and correctly render returned false-ish values", function () {
|
||||||
|
const vars = {
|
||||||
|
length: ({ string }) => string.length.toString(),
|
||||||
|
};
|
||||||
|
const template = `"" has a length of {{ length string="" }} and "hello" has a length of {{ length string="hello" }}`;
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, '"" has a length of 0 and "hello" has a length of 5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should support functions in comparison statements", function () {
|
||||||
|
const vars = {
|
||||||
|
sum: ({ a, b }) => (parseInt(a) + parseInt(b)).toString(),
|
||||||
|
fooBar: ({ isFoo }) => (isFoo === 'true' ? 'foo' : 'bar'),
|
||||||
|
false: 'false',
|
||||||
|
twoWords: 'two words',
|
||||||
|
onlyOne: 'actually == 1'
|
||||||
|
};
|
||||||
|
const template = `{{if {{ sum a="1" b="2" }} == "3"}}1 + 2 = {{sum a="1" b="2"}}{{else}}no speak math{{endif}}`;
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, '1 + 2 = 3');
|
||||||
|
|
||||||
|
const template2 = '{{if false != "false"}}no{{elseif false == "false"}}yes{{else}}no{{endif}}';
|
||||||
|
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template2, vars);
|
||||||
|
assert.equal(out2, 'yes');
|
||||||
|
|
||||||
|
const template3 = '{{ if twoWords == "two words" }}yes{{else}}no{{endif}}';
|
||||||
|
const out3 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template3, vars);
|
||||||
|
assert.equal(out3, 'yes');
|
||||||
|
|
||||||
|
const template4 = '{{ if onlyOne == \'actually == 1\' }}yes{{else}}no{{endif}}';
|
||||||
|
const out4 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template4, vars);
|
||||||
|
assert.equal(out4, 'yes');
|
||||||
|
|
||||||
|
const template5 = '{{ if "3" == {{ sum a="1" b="2" }} }}yes{{else}}no{{endif}}';
|
||||||
|
const out5 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template5, vars);
|
||||||
|
assert.equal(out5, 'yes');
|
||||||
|
|
||||||
|
const template6 = '{{ if {{ sum a="1" b="2" }} }}yes{{else}}no{{endif}}';
|
||||||
|
const out6 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template6, vars);
|
||||||
|
assert.equal(out6, 'yes');
|
||||||
|
|
||||||
|
const template7 = '{{ if {{ twoWords }} }}yes{{else}}no{{endif}}';
|
||||||
|
const out7 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template7, vars);
|
||||||
|
assert.equal(out7, 'yes');
|
||||||
|
|
||||||
|
const template8 = '{{ if twoWords }}yes{{else}}no{{endif}}';
|
||||||
|
const out8 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template8, vars);
|
||||||
|
assert.equal(out8, 'yes');
|
||||||
|
|
||||||
|
const template9 = '{{ if missing }}no{{else}}yes{{endif}}';
|
||||||
|
const out9 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template9, vars);
|
||||||
|
assert.equal(out9, 'yes');
|
||||||
|
|
||||||
|
const template10 = '{{ if {{ missing foo="bar" }} }}no{{else}}yes{{endif}}';
|
||||||
|
const out10 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template10, vars);
|
||||||
|
assert.equal(out10, 'yes');
|
||||||
|
|
||||||
|
const template11 = '{{ if {{ missing foo="bar" }} == "" }}yes{{else}}no{{endif}}';
|
||||||
|
const out11 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template11, vars);
|
||||||
|
assert.equal(out11, 'yes');
|
||||||
|
|
||||||
|
const template12 = '{{ if fooBar == "bar" }}yes{{else}}no{{endif}}';
|
||||||
|
const out12 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template12, vars);
|
||||||
|
assert.equal(out12, 'yes');
|
||||||
|
|
||||||
|
const template13 = '{{ if {{ fooBar }} == "bar" }}yes{{else}}no{{endif}}';
|
||||||
|
const out13 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template13, vars);
|
||||||
|
assert.equal(out13, 'yes');
|
||||||
|
|
||||||
|
const template14 = `{{if {{ sum a="1" b="2" }}=="3"}}1 + 2 = {{sum a="1" b="2"}}{{else}}no{{endif}}`;
|
||||||
|
const out14 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template14, vars);
|
||||||
|
assert.equal(out14, '1 + 2 = 3');
|
||||||
|
|
||||||
|
const template15 = `{{if "two words"==twoWords}}yes{{else}}no{{endif}}`;
|
||||||
|
const out15 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template15, vars);
|
||||||
|
assert.equal(out15, 'yes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept hyphen-case variables and attributes", function () {
|
||||||
|
const vars = {
|
||||||
|
fooBar: ({ isFoo }) => (isFoo === 'true' ? 'foo' : 'bar'),
|
||||||
|
};
|
||||||
|
const template = '{{ foo-bar is-foo="true" }}{{ if {{ foo-bar is-foo="false" }} == "bar" }}{{ foo-bar is-foo="false" }}{{ endif }}';
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, 'foobar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with a condition in the middle", function () {
|
||||||
|
const vars = {
|
||||||
|
v1: '1',
|
||||||
|
};
|
||||||
|
const template = 'test {{ if v1 == "1" }}yes{{ else }}no{{ endif }} foobar';
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, 'test yes foobar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("missing identifiers are evaluted as empty string", function () {
|
||||||
|
const vars = {
|
||||||
|
foo: 'foo',
|
||||||
|
};
|
||||||
|
const template = '{{bar}}{{ if foo == "" }}no{{elseif foo}}{{foo}}{{else}}no{{endif}}';
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, 'foo');
|
||||||
|
|
||||||
|
const template2 = 'test: {{ if bar == "" }}yes{{else}}no{{endif}}';
|
||||||
|
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template2, vars);
|
||||||
|
assert.equal(out2, 'test: yes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve whitespace outside of brackets", function () {
|
||||||
|
const template = ' starts }} with {{ whitespace {"test"} == \'foobar\' ';
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, {});
|
||||||
|
assert.equal(out, template);
|
||||||
|
const vars = {
|
||||||
|
space: ' ',
|
||||||
|
spaceFn: () => ' ',
|
||||||
|
};
|
||||||
|
|
||||||
|
const whitespace = ' {{if spaceFn}}{{else}} {{endif}}{{space}} {{space-fn}}';
|
||||||
|
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(whitespace, vars);
|
||||||
|
assert.equal(out2, ' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept array values in logic statements", function () {
|
||||||
|
let someTags = ['foo', 'bar'];
|
||||||
|
const vars = {
|
||||||
|
tags: ({ join }) => (join ? someTags.join(join) : someTags),
|
||||||
|
};
|
||||||
|
const template = '{{ if tags }}#{{ tags join=" #" }}{{else}}no tags{{endif}}';
|
||||||
|
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out, '#foo #bar');
|
||||||
|
|
||||||
|
someTags = [];
|
||||||
|
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
|
assert.equal(out2, 'no tags');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should throw if function returns anything else than a string (or an array which is always joined into string)", function () {
|
||||||
|
const vars = {
|
||||||
|
number: () => 1,
|
||||||
|
logic: () => true,
|
||||||
|
array: () => [],
|
||||||
|
fn: () => 1,
|
||||||
|
};
|
||||||
|
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ number }}', vars), /Identifier "number" does not evaluate to a string/);
|
||||||
|
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ logic }}', vars), /Identifier "logic" does not evaluate to a string/);
|
||||||
|
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ if fn }}no{{endif}}', vars), /Identifier "fn" does not evaluate to a string/);
|
||||||
|
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ if {{ fn foo="bar" }} }}no{{endif}}', vars), /Identifier "fn" does not evaluate to a string/);
|
||||||
|
});
|
||||||
|
|
||||||
it("should support nested 'if' statements", function () {
|
it("should support nested 'if' statements", function () {
|
||||||
var vars = {
|
var vars = {
|
||||||
v1: '1',
|
v1: '1',
|
||||||
v2: 'H',
|
v2: 'H',
|
||||||
};
|
};
|
||||||
var template = `{{if v1 == '1'}}yes1{{if x}}no{{elseif v2 == h }}yes2{{endif}}{{elseif v2 == 2}}no{{else}}no{{endif}} {{if v2 == 1}}not{{elseif x}}not{{else}}yes3{{ endif}}`;
|
var template = `{{if v1 == '1'}}yes1{{if x}}no{{elseif v2 == "h" }}yes2{{endif}}{{elseif v2 == "2"}}no{{else}}no{{endif}} {{if v2 == "1"}}not{{elseif x}}not{{else}}yes3{{ endif}}`;
|
||||||
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
|
||||||
assert.equal(html, 'yes1yes2 yes3');
|
assert.equal(html, 'yes1yes2 yes3');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue