parent
96e2510165
commit
0ba766f2e0
6 changed files with 766 additions and 125 deletions
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Attachments = new function(){
|
||||
Zotero.Attachments = new function () {
|
||||
const { HiddenBrowser } = ChromeUtils.import("chrome://zotero/content/HiddenBrowser.jsm");
|
||||
|
||||
// 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
|
||||
* 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 {String} formatString
|
||||
*/
|
||||
|
@ -2257,79 +2248,160 @@ Zotero.Attachments = new function(){
|
|||
if (!(item instanceof Zotero.Item)) {
|
||||
throw new Error("'item' must be a Zotero.Item");
|
||||
}
|
||||
|
||||
|
||||
if (!formatString) {
|
||||
formatString = Zotero.Prefs.get('attachmentRenameFormatString');
|
||||
}
|
||||
|
||||
// Replaces the substitution marker with the field value,
|
||||
// truncating based on the {[0-9]+} modifier if applicable
|
||||
function rpl(field, str) {
|
||||
if (!str) {
|
||||
str = formatString;
|
||||
}
|
||||
|
||||
switch (field) {
|
||||
case 'creator':
|
||||
field = 'firstCreator';
|
||||
var rpl = '%c';
|
||||
|
||||
const getSlicedCreatorsOfType = (creatorType, slice) => {
|
||||
let creatorTypeIDs;
|
||||
switch (creatorType) {
|
||||
case 'author':
|
||||
case 'authors':
|
||||
creatorTypeIDs = [Zotero.CreatorTypes.getPrimaryIDForType(item.itemTypeID)];
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
var rpl = '%y';
|
||||
case 'editor':
|
||||
case 'editors':
|
||||
creatorTypeIDs = [Zotero.CreatorTypes.getID('editor'), Zotero.CreatorTypes.getID('seriesEditor')];
|
||||
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:
|
||||
value = '' + item.getField(field, false, true);
|
||||
case 'creator':
|
||||
case 'creators':
|
||||
creatorTypeIDs = null;
|
||||
break;
|
||||
}
|
||||
|
||||
var re = new RegExp("\{?([^%\{\}]*)" + rpl + "(\{[0-9]+\})?" + "([^%\{\}]*)\}?");
|
||||
|
||||
// If no value for this field, strip entire conditional block
|
||||
// (within curly braces)
|
||||
if (!value) {
|
||||
if (str.match(re)) {
|
||||
return str.replace(re, '')
|
||||
if (slice === 0) {
|
||||
return [];
|
||||
}
|
||||
const matchingCreators = creatorTypeIDs === null
|
||||
? item.getCreators()
|
||||
: item.getCreators().filter(c => creatorTypeIDs.includes(c.creatorTypeID));
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return str.replace(re, f);
|
||||
}
|
||||
|
||||
formatString = rpl('creator');
|
||||
formatString = rpl('year');
|
||||
formatString = rpl('title');
|
||||
|
||||
return common(value, args);
|
||||
};
|
||||
|
||||
const itemType = ({ localize = false, ...rest }) => common(
|
||||
localize ? Zotero.ItemTypes.getLocalizedString(item.itemType) : item.itemType, rest
|
||||
);
|
||||
|
||||
const creatorFields = ['authors', 'editors', 'creators'].reduce((obj, name) => {
|
||||
obj[name] = (args) => {
|
||||
return common(commonCreators(name, args), args);
|
||||
};
|
||||
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.File.getValidFileName(formatString);
|
||||
return formatString;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.shouldAutoRenameFile = function (isLink) {
|
||||
|
@ -3029,4 +3101,4 @@ Zotero.Attachments = new function(){
|
|||
}
|
||||
throw new Error(`Invalid link mode name '${linkModeName}'`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
Zotero.Prefs = new function(){
|
||||
Zotero.Prefs = new function() {
|
||||
// Privileged methods
|
||||
this.get = get;
|
||||
this.set = set;
|
||||
|
@ -49,7 +49,7 @@ Zotero.Prefs = new function(){
|
|||
if (!fromVersion) {
|
||||
fromVersion = 0;
|
||||
}
|
||||
var toVersion = 7;
|
||||
var toVersion = 8;
|
||||
if (fromVersion < toVersion) {
|
||||
for (var i = fromVersion + 1; i <= toVersion; i++) {
|
||||
switch (i) {
|
||||
|
@ -109,6 +109,14 @@ Zotero.Prefs = new function(){
|
|||
case 7:
|
||||
this.clear('layers.acceleration.disabled', true);
|
||||
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);
|
||||
|
@ -119,7 +127,7 @@ Zotero.Prefs = new function(){
|
|||
/**
|
||||
* Retrieve a preference
|
||||
**/
|
||||
function get(pref, global){
|
||||
function get(pref, global) {
|
||||
try {
|
||||
pref = global ? pref : ZOTERO_CONFIG.PREF_BRANCH + pref;
|
||||
let branch = this.rootBranch;
|
||||
|
@ -554,4 +562,38 @@ Zotero.Prefs = new function(){
|
|||
}
|
||||
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
|
||||
*
|
||||
* - '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:
|
||||
* {
|
||||
|
@ -2121,44 +2157,108 @@ Zotero.Utilities.Internal = {
|
|||
* @returns {String} HTML
|
||||
*/
|
||||
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 parts = template.split(/{{|}}/);
|
||||
const levels = [{ condition: true }];
|
||||
const parts = this.splitByOuterBrackets(template);
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
let part = parts[i];
|
||||
let level = levels[levels.length - 1];
|
||||
if (i % 2 === 1) {
|
||||
let operator = part.split(' ').filter(x => x)[0];
|
||||
// Get arguments that are used for 'if'
|
||||
let args = [];
|
||||
let match = part.match(/(["'][^"|^']+["']|[^\s"']+)/g);
|
||||
if (match) {
|
||||
args = match.map(x => x.replace(/['"](.*)['"]/, '$1')).slice(1);
|
||||
}
|
||||
|
||||
if (part.startsWith('{{')) {
|
||||
const [operator, args] = splitStatement(part);
|
||||
|
||||
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);
|
||||
}
|
||||
if (['if', 'elseif'].includes(operator)) {
|
||||
if (!level.executed) {
|
||||
level.condition = level.parentCondition && (
|
||||
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.condition = level.parentCondition && evaluateCondition(args);
|
||||
level.executed = level.condition;
|
||||
}
|
||||
else {
|
||||
|
@ -2176,18 +2276,8 @@ Zotero.Utilities.Internal = {
|
|||
continue;
|
||||
}
|
||||
if (level.condition) {
|
||||
// Get attributes i.e. join=" #"
|
||||
let attrsRegexp = new RegExp(/((\w*) *=+ *(['"])((\\\3|[^\3])*?)\3)|((\w*) *=+ *(\w*))/g);
|
||||
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]) || '';
|
||||
const attrs = getAttributes(part);
|
||||
html += evaluateIdentifier(operator, attrs);
|
||||
}
|
||||
}
|
||||
else if (level.condition) {
|
||||
|
|
|
@ -36,7 +36,7 @@ pref("extensions.zotero.autoRecognizeFiles", true);
|
|||
pref("extensions.zotero.autoRenameFiles", true);
|
||||
pref("extensions.zotero.autoRenameFiles.linked", false);
|
||||
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.launchNonNativeFiles", false);
|
||||
pref("extensions.zotero.sortNotesChronologically", false);
|
||||
|
|
|
@ -1293,11 +1293,297 @@ describe("Zotero.Attachments", function() {
|
|||
});
|
||||
|
||||
describe("#getFileBaseNameFromItem()", function () {
|
||||
it("should strip HTML tags from title", async function () {
|
||||
var item = createUnsavedDataObject('item', { title: 'Foo <i>Bar</i> Foo<br><br/><br />Bar' });
|
||||
var str = Zotero.Attachments.getFileBaseNameFromItem(item);
|
||||
var item, itemManyAuthors, itemPatent, itemIncomplete, itemBookSection;
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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 () {
|
||||
|
|
|
@ -549,25 +549,176 @@ describe("Zotero.Utilities.Internal", function () {
|
|||
it("should support variables with attributes", function () {
|
||||
var vars = {
|
||||
v1: '1',
|
||||
v2: (pars) => pars.a1 + pars.a2 + pars.a3,
|
||||
v2: pars => `${pars.a1 ?? ''}${pars.a2 ?? ''}${pars.a3 ?? ''}`,
|
||||
v3: () => '',
|
||||
v5: () => 'something',
|
||||
ar1: [],
|
||||
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);
|
||||
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 () {
|
||||
var vars = {
|
||||
v1: '1',
|
||||
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);
|
||||
assert.equal(html, 'yes1yes2 yes3');
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue