diff --git a/BibLaTeX.js b/BibLaTeX.js index 805f21d936d..3679e485d3d 100644 --- a/BibLaTeX.js +++ b/BibLaTeX.js @@ -17,7 +17,7 @@ "exportFileData": false, "useJournalAbbreviation": false }, - "lastUpdated": "2022-10-12 19:26:00" + "lastUpdated": "2023-10-17 15:35:00" } /* @@ -78,12 +78,11 @@ var fieldMap = { */ // Exported in BibTeX and BibLaTeX var revExtraIds = { - LCCN: 'lccn', - MR: 'mrnumber', - Zbl: 'zmnumber', - PMCID: 'pmcid', - PMID: 'pmid', - DOI: 'doi' + lccn: 'lccn', + mr: 'mrnumber', + zbl: 'zmnumber', + pmcid: 'pmcid', + pmid: 'pmid', }; // Imported by BibTeX. Exported by BibLaTeX only @@ -98,19 +97,205 @@ var revEprintIds = { GoogleBooksID: 'googlebooks' }; -function parseExtraFields(extra) { - var lines = extra.split(/[\r\n]+/); - var fields = []; - for (var i = 0; i < lines.length; i++) { - var rec = { raw: lines[i] }; - var line = lines[i].trim(); - var splitAt = line.indexOf(':'); - if (splitAt > 1) { - rec.field = line.substr(0, splitAt).trim(); - rec.value = line.substr(splitAt + 1).trim(); - } - fields.push(rec); +const EXTRA_FIELDS = { // fields picked up in structured key-value format in the same way as citeproc would + field: { // english-text label or CSL field => Zotero fields + 'application number': ['applicationNumber'], + 'archive id': ['archiveID'], + 'loc in archive': ['archiveLocation'], + 'artwork size': ['artworkSize'], + assignee: ['assignee'], + 'file type': ['audioFileType'], + format: ['audioRecordingFormat', 'format', 'videoRecordingFormat'], + 'bill number': ['billNumber'], + 'blog title': ['blogTitle'], + 'book title': ['bookTitle'], + 'call number': ['callNumber'], + 'case name': ['caseName'], + 'citation key': ['citationKey'], + code: ['code'], + 'code number': ['codeNumber'], + 'code pages': ['codePages'], + 'code volume': ['codeVolume'], + committee: ['committee'], + company: ['company'], + 'conference name': ['conferenceName'], + country: ['country'], + court: ['court'], + date: ['date', 'dateDecided', 'dateEnacted', 'issueDate'], + 'date added': ['dateAdded'], + 'date decided': ['dateDecided'], + 'date enacted': ['dateEnacted'], + modified: ['dateModified'], + 'dictionary title': ['dictionaryTitle'], + distributor: ['distributor'], + 'docket number': ['docketNumber'], + 'document number': ['documentNumber'], + doi: ['DOI'], + 'encyclopedia title': ['encyclopediaTitle'], + 'episode number': ['episodeNumber'], + 'filing date': ['filingDate'], + 'first page': ['firstPage'], + 'forumlistserv title': ['forumTitle'], + history: ['history'], + identifier: ['identifier'], + institution: ['institution'], + isbn: ['ISBN'], + issn: ['ISSN'], + 'issue date': ['issueDate'], + 'issuing authority': ['issuingAuthority'], + 'item type': ['itemType'], + 'journal abbr': ['journalAbbreviation'], + label: ['label'], + 'legal status': ['legalStatus'], + 'legislative body': ['legislativeBody'], + type: ['genre', 'letterType', 'manuscriptType', 'mapType', 'postType', 'presentationType', 'reportType', 'thesisType', 'type', 'websiteType'], + 'library catalogue': ['libraryCatalog'], + 'meeting name': ['meetingName'], + 'name of act': ['nameOfAct'], + network: ['network'], + 'nr of volumes': ['numberOfVolumes'], + '# of volumes': ['numberOfVolumes'], + 'nr of pages': ['numPages'], + '# of pages': ['numPages'], + organization: ['organization'], + pages: ['codePages', 'firstPage', 'pages'], + 'patent number': ['patentNumber'], + place: ['place', 'repositoryLocation'], + 'post type': ['postType'], + 'priority numbers': ['priorityNumbers'], + 'proceedings title': ['proceedingsTitle'], + 'prog language': ['programmingLanguage'], + 'program title': ['programTitle'], + publication: ['blogTitle', 'bookTitle', 'dictionaryTitle', 'encyclopediaTitle', 'forumTitle', 'proceedingsTitle', 'programTitle', 'publicationTitle', 'websiteTitle'], + 'public law number': ['publicLawNumber'], + reporter: ['reporter'], + 'reporter volume': ['reporterVolume'], + 'report number': ['reportNumber'], + 'report type': ['reportType'], + repository: ['repository'], + 'repo location': ['repositoryLocation'], + rights: ['rights'], + 'running time': ['runningTime'], + series: ['series'], + 'series number': ['seriesNumber'], + 'series text': ['seriesText'], + 'series title': ['seriesTitle'], + session: ['session'], + 'short title': ['shortTitle'], + studio: ['studio'], + subject: ['subject'], + system: ['system'], + university: ['university'], + url: ['url'], + 'website title': ['websiteTitle'], + 'website type': ['websiteType'], + abstract: ['abstractNote'], + archive: ['archive'], + archivelocation: ['archiveLocation'], + authority: ['authority'], + 'call-number': ['applicationNumber', 'callNumber'], + 'chapter-number': ['session'], + 'collection-number': ['seriesNumber'], + 'collection-title': ['series', 'seriesTitle'], + 'container-title': ['code', 'publicationTitle', 'reporter'], + dimensions: ['artworkSize', 'runningTime'], + edition: ['edition'], + 'event-place': ['place'], + 'event-title': ['conferenceName', 'meetingName'], + genre: ['programmingLanguage', 'type'], + issue: ['issue', 'priorityNumbers'], + journalabbreviation: ['journalAbbreviation'], + language: ['language'], + license: ['rights'], + medium: ['medium', 'system'], + number: ['number'], + 'number-of-pages': ['numPages'], + 'number-of-volumes': ['numberOfVolumes'], + page: ['pages'], + publisher: ['publisher'], + 'publisher-place': ['place'], + references: ['history', 'references'], + scale: ['scale'], + section: ['committee', 'section'], + shorttitle: ['shortTitle'], + source: ['libraryCatalog'], + status: ['status'], + title: ['title'], + 'title-short': ['shortTitle'], + version: ['versionNumber'], + volume: ['codeNumber', 'volume'], + accessed: ['accessDate'], + issued: ['date'], + submitted: ['filingDate'] + }, + creator: { // CSL creator => Zotero creator + author: 'author', + 'container-author': 'bookAuthor', + performer: 'castMember', + composer: 'composer', + contributor: 'contributor', + director: 'director', + editor: 'editor', + guest: 'guest', + interviewer: 'interviewer', + producer: 'producer', + recipient: 'recipient', + 'reviewed-author': 'reviewedAuthor', + 'collection-editor': 'seriesEditor', + 'script-writer': 'scriptwriter', + translator: 'translator' } +}; + +/* + * this function parses structured key-value lines from the `extra` field as citeproc does, and + * stores them into the item as regular item fields. This way, the extra information present is + * taken into account when generating the biblatex output. The recognized fields are removed + * from the `extra` field in the process as their purpose is not general-purpose item notes. + */ +function parseAndConvertExtraFields(item) { + if (!item.extra) return null; + + const fields = []; + item.extra = item.extra.split('\n').filter((line) => { // discard lines we're consuming into the object + const rec = { raw: line }; + const kv = line.match(/^([^:]+)\s*:\s*(.+)/); + if (kv) { + const label = kv[1].toLowerCase(); + const value = kv[2]; + for (const field of (EXTRA_FIELDS.field[label] || [])) { + rec.field = label; + rec.value = value; + item[field] = item[field] || rec.value; + } + + const creatorType = EXTRA_FIELDS.creator[label]; + if (creatorType) { + rec.field = label; + + const name = value.split('||').map(n => n.trim()); + if (name.length === 2) { + item.creators.push(rec.value = { creatorType, firstName: name[1], lastName: name[0] }); + } + else { + item.creators.push(rec.value = { creatorType, name: value }); + } + } + + if (revExtraIds[label]) { + rec.field = label; + rec.value = value; + } + + if (rec.field) { + fields.push(rec); + return false; + } + } + + return true; + }).join('\n'); + return fields; } @@ -526,7 +711,7 @@ function doExport() { if (!type) type = "misc"; - var extraFields = item.extra ? parseExtraFields(item.extra) : null; + var extraFields = parseAndConvertExtraFields(item); var citekey = buildCiteKey(item, extraFields, citekeys); // write citation key (removed the comma) @@ -805,8 +990,7 @@ function doExport() { if (extraFields) { // Export identifiers // Dedicated fields - for (let i = 0; i < extraFields.length; i++) { - var rec = extraFields[i]; + for (const rec of extraFields) { if (!rec.field) continue; if (!revExtraIds[rec.field] && !revEprintIds[rec.field]) continue; @@ -825,13 +1009,9 @@ function doExport() { writeField('eprint', '{' + value + '}', true); } } - extraFields.splice(i, 1); - i--; } - - var extra = extraFieldsToString(extraFields); - if (extra && !noteused) writeField("note", extra); } + if (item.extra && !noteused) writeField("note", item.extra); if (item.tags && item.tags.length) { var tagString = ""; diff --git a/BibTeX.js b/BibTeX.js index 7aef0de5d41..7a7fec45a8d 100644 --- a/BibTeX.js +++ b/BibTeX.js @@ -18,7 +18,7 @@ }, "inRepository": true, "translatorType": 3, - "lastUpdated": "2023-04-09 18:35:07" + "lastUpdated": "2023-10-17 15:35:00" } /* @@ -41,25 +41,25 @@ function detectImport() { var maxChars = 1048576; // 1MB - + var inComment = false; var block = ""; var buffer = ""; var chr = ""; var charsRead = 0; - + var re = /^\s*@[a-zA-Z]+[\(\{]/; while ((buffer = Zotero.read(4096)) && charsRead < maxChars) { Zotero.debug("Scanning " + buffer.length + " characters for BibTeX"); charsRead += buffer.length; for (var i=0; i 1) { - rec.field = line.substr(0,splitAt).trim(); - rec.value = line.substr(splitAt + 1).trim(); - } - fields.push(rec); +const EXTRA_FIELDS = { // fields picked up in structured key-value format in the same way as citeproc would + field: { + 'application number': ['applicationNumber'], + 'archive id': ['archiveID'], + 'loc in archive': ['archiveLocation'], + 'artwork size': ['artworkSize'], + assignee: ['assignee'], + 'file type': ['audioFileType'], + format: ['audioRecordingFormat', 'format', 'videoRecordingFormat'], + 'bill number': ['billNumber'], + 'blog title': ['blogTitle'], + 'book title': ['bookTitle'], + 'call number': ['callNumber'], + 'case name': ['caseName'], + 'citation key': ['citationKey'], + code: ['code'], + 'code number': ['codeNumber'], + 'code pages': ['codePages'], + 'code volume': ['codeVolume'], + committee: ['committee'], + company: ['company'], + 'conference name': ['conferenceName'], + country: ['country'], + court: ['court'], + date: ['date', 'dateDecided', 'dateEnacted', 'issueDate'], + 'date added': ['dateAdded'], + 'date decided': ['dateDecided'], + 'date enacted': ['dateEnacted'], + modified: ['dateModified'], + 'dictionary title': ['dictionaryTitle'], + distributor: ['distributor'], + 'docket number': ['docketNumber'], + 'document number': ['documentNumber'], + doi: ['DOI'], + 'encyclopedia title': ['encyclopediaTitle'], + 'episode number': ['episodeNumber'], + 'filing date': ['filingDate'], + 'first page': ['firstPage'], + 'forumlistserv title': ['forumTitle'], + history: ['history'], + identifier: ['identifier'], + institution: ['institution'], + isbn: ['ISBN'], + issn: ['ISSN'], + 'issue date': ['issueDate'], + 'issuing authority': ['issuingAuthority'], + 'item type': ['itemType'], + 'journal abbr': ['journalAbbreviation'], + label: ['label'], + 'legal status': ['legalStatus'], + 'legislative body': ['legislativeBody'], + type: ['genre', 'letterType', 'manuscriptType', 'mapType', 'postType', 'presentationType', 'reportType', 'thesisType', 'type', 'websiteType'], + 'library catalogue': ['libraryCatalog'], + 'meeting name': ['meetingName'], + 'name of act': ['nameOfAct'], + network: ['network'], + 'nr of volumes': ['numberOfVolumes'], + '# of volumes': ['numberOfVolumes'], + 'nr of pages': ['numPages'], + '# of pages': ['numPages'], + organization: ['organization'], + pages: ['codePages', 'firstPage', 'pages'], + 'patent number': ['patentNumber'], + place: ['place', 'repositoryLocation'], + 'post type': ['postType'], + 'priority numbers': ['priorityNumbers'], + 'proceedings title': ['proceedingsTitle'], + 'prog language': ['programmingLanguage'], + 'program title': ['programTitle'], + publication: ['blogTitle', 'bookTitle', 'dictionaryTitle', 'encyclopediaTitle', 'forumTitle', 'proceedingsTitle', 'programTitle', 'publicationTitle', 'websiteTitle'], + 'public law number': ['publicLawNumber'], + reporter: ['reporter'], + 'reporter volume': ['reporterVolume'], + 'report number': ['reportNumber'], + 'report type': ['reportType'], + repository: ['repository'], + 'repo location': ['repositoryLocation'], + rights: ['rights'], + 'running time': ['runningTime'], + series: ['series'], + 'series number': ['seriesNumber'], + 'series text': ['seriesText'], + 'series title': ['seriesTitle'], + session: ['session'], + 'short title': ['shortTitle'], + studio: ['studio'], + subject: ['subject'], + system: ['system'], + university: ['university'], + url: ['url'], + 'website title': ['websiteTitle'], + 'website type': ['websiteType'], + abstract: ['abstractNote'], + archive: ['archive'], + archivelocation: ['archiveLocation'], + authority: ['authority'], + 'call-number': ['applicationNumber', 'callNumber'], + 'chapter-number': ['session'], + 'collection-number': ['seriesNumber'], + 'collection-title': ['series', 'seriesTitle'], + 'container-title': ['code', 'publicationTitle', 'reporter'], + dimensions: ['artworkSize', 'runningTime'], + edition: ['edition'], + 'event-place': ['place'], + 'event-title': ['conferenceName', 'meetingName'], + genre: ['programmingLanguage', 'type'], + issue: ['issue', 'priorityNumbers'], + journalabbreviation: ['journalAbbreviation'], + language: ['language'], + license: ['rights'], + medium: ['medium', 'system'], + number: ['number'], + 'number-of-pages': ['numPages'], + 'number-of-volumes': ['numberOfVolumes'], + page: ['pages'], + publisher: ['publisher'], + 'publisher-place': ['place'], + references: ['history', 'references'], + scale: ['scale'], + section: ['committee', 'section'], + shorttitle: ['shortTitle'], + source: ['libraryCatalog'], + status: ['status'], + title: ['title'], + 'title-short': ['shortTitle'], + version: ['versionNumber'], + volume: ['codeNumber', 'volume'], + accessed: ['accessDate'], + issued: ['date'], + submitted: ['filingDate'] + }, + creator: { + author: 'author', + 'container-author': 'bookAuthor', + performer: 'castMember', + composer: 'composer', + contributor: 'contributor', + director: 'director', + editor: 'editor', + guest: 'guest', + interviewer: 'interviewer', + producer: 'producer', + recipient: 'recipient', + 'reviewed-author': 'reviewedAuthor', + 'collection-editor': 'seriesEditor', + 'script-writer': 'scriptwriter', + translator: 'translator' } +}; + +/* + * this function parses structured key-value lines from the `extra` field as citeproc does, and + * stores them into the item as regular item fields. This way, the extra information present is + * taken into account when generating the biblatex output. The recognized fields are removed + * from the `extra` field in the process as their purpose is not general-purpose item notes. + */ +function parseAndConvertExtraFields(item) { + if (!item.extra) return null; + + const fields = []; + item.extra = item.extra.split('\n').filter((line) => { // discard lines we're consuming into the object + const rec = { raw: line }; + const kv = line.match(/^([^:]+)\s*:\s*(.+)/); + if (kv) { + const label = kv[1].toLowerCase(); + const value = kv[2]; + for (const field of (EXTRA_FIELDS.field[label] || [])) { + rec.field = label; + rec.value = value; + item[field] = item[field] || rec.value; + } + + const creatorType = EXTRA_FIELDS.creator[label]; + if (creatorType) { + rec.field = label; + + const name = value.split('||').map(n => n.trim()); + if (name.length === 2) { + item.creators.push(rec.value = { creatorType, firstName: name[1], lastName: name[0] }); + } + else { + item.creators.push(rec.value = { creatorType, name: value }); + } + } + + if (revExtraIgnore.includes(label)) { + return false; + } + + if (rec.field) { + fields.push(rec); + return false; + } + } + + return true; + }).join('\n'); + return fields; } @@ -199,7 +391,7 @@ function extraFieldsToString(extra) { str += '\n' + extra[i].raw; } } - + return str.substr(1); } @@ -315,7 +507,7 @@ function setKeywordDelimRe( val, flags ) { flags = val.slice(val.lastIndexOf('/')+1); val = val.slice(1, val.lastIndexOf('/')); } - + keywordDelimRe = new RegExp(val, flags); } @@ -327,7 +519,7 @@ function processField(item, field, value, rawValue) { item._extraFields.push({field: "DOI", value: ZU.cleanDOI(value)}); } if (field == "url") { // pass raw values for URL - item.url = rawValue; + item.url = rawValue; } else { item[fieldMap[field]] = value; @@ -338,7 +530,7 @@ function processField(item, field, value, rawValue) { if (!item.title) item.title = ''; item.title = item.title.trim(); value = value.trim(); - + if (!/[-–—:!?.;]$/.test(item.title) && !/^[-–—:.;¡¿]/.test(value) ) { @@ -346,7 +538,7 @@ function processField(item, field, value, rawValue) { } else if (item.title.length) { item.title += ' '; } - + item.title += value; } else if (field == "journal") { if (item.publicationTitle) { @@ -367,7 +559,7 @@ function processField(item, field, value, rawValue) { var name = names[i]; // skip empty names if (!name) continue; - + // Names in BibTeX can have three commas var pieces = splitUnprotected(name, /\s*,\s*/g); var creator = {}; @@ -415,7 +607,7 @@ function processField(item, field, value, rawValue) { if (monthIndex != -1) { value = Zotero.Utilities.formatDate({month:monthIndex}); } - + item.month = value; } else if (field == "year") { item.year = value; @@ -450,7 +642,7 @@ function processField(item, field, value, rawValue) { item._extraFields.push({field: 'Published', value: value}); } } - + } //accept lastchecked or urldate for access date. These should never both occur. //If they do we don't know which is better so we might as well just take the second one @@ -482,24 +674,24 @@ function processField(item, field, value, rawValue) { start = i+1; } } - + attachment = parseFilePathRecord(rawValue.slice(start)); if (attachment) item.attachments.push(attachment); } else if (field == "eprint" || field == "eprinttype") { // Support for IDs exported by BibLaTeX if (field == 'eprint') item._eprint = value; else item._eprinttype = value; - + var eprint = item._eprint; var eprinttype = item._eprinttype; // If we don't have both yet, continue if (!eprint || !eprinttype) return; - + var label = eprintIds[eprinttype.trim().toLowerCase()]; if (!label) return; - + item._extraFields.push({field: label, value: eprint.trim()}); - + delete item._eprinttype; delete item._eprint; } else if (extraIdentifiers[field]) { @@ -518,7 +710,7 @@ function splitUnprotected(str, delim) { delim.lastIndex = 0; // In case we're reusing a regexp var nextPossibleSplit = delim.exec(str); if (!nextPossibleSplit) return [str]; - + var parts = [], open = 0, nextPartStart = 0; for (var i=0; inextPossibleSplit.index) { @@ -529,26 +721,26 @@ function splitUnprotected(str, delim) { return parts; } } - + if (str[i] == '\\') { // Skip next character i++; continue; } - + if (str[i] == '{') { open++; continue; } - + if (str[i] == '}') { open--; if (open < 0) open = 0; // Shouldn't happen, but... continue; } - + if (open) continue; - + if (i == nextPossibleSplit.index) { parts.push(str.substring(nextPartStart, i)); i += nextPossibleSplit[0].length - 1; // We can jump past the split delim @@ -560,12 +752,12 @@ function splitUnprotected(str, delim) { } } } - + // I don't think we should ever get here*, but just to be safe // *we should always be returning from the for loop var last = str.substr(nextPartStart).trim(); if (last) parts.push(last); - + return parts; } @@ -581,14 +773,14 @@ function parseFilePathRecord(record) { start = i+1; } } - + fields.push(decodeFilePathComponent(record.slice(start))); - + if (fields.length != 3 && fields.length != 1) { Zotero.debug("Unknown file path record format: " + record); return; } - + var attachment = {}; if (fields.length == 3) { attachment.title = fields[0].trim() || 'Attachment'; @@ -601,10 +793,10 @@ function parseFilePathRecord(record) { attachment.title = 'Attachment'; attachment.path = fields[0]; } - + attachment.path = attachment.path.trim(); if (!attachment.path) return; - + return attachment; } @@ -620,13 +812,13 @@ function getFieldValue(read) { nextAsLiteral = false; continue; } - + if (read == "\\") { value += read; nextAsLiteral = true; continue; } - + if (read == "{") { openBraces++; value += "{"; @@ -641,7 +833,7 @@ function getFieldValue(read) { value += read; } } - + } else if (read == '"') { var openBraces = 0; while (read = Zotero.read(1)) { @@ -664,7 +856,7 @@ function getFieldValue(read) { function unescapeBibTeX(value) { if (value.length < 2) return value; - + // replace accented characters (yucky slow) value = value.replace(/{?(\\[`"'^~=]){?\\?([A-Za-z])}/g, "{$1$2}"); // normalize some special characters, e.g. caron \v{c} -> {\v c} @@ -685,13 +877,13 @@ function unescapeBibTeX(value) { } } value = value.replace(/\$([^$]+)\$/g, '$1') - + // kill braces value = value.replace(/([^\\])[{}]+/g, "$1"); if (value[0] == "{") { value = value.substr(1); } - + // chop off backslashes value = value.replace(/([^\\])\\([#$%&~_^\\{}])/g, "$1$2"); value = value.replace(/([^\\])\\([#$%&~_^\\{}])/g, "$1$2"); @@ -703,17 +895,17 @@ function unescapeBibTeX(value) { } value = value.replace(/\\\\/g, "\\"); value = value.replace(/\s+/g, " "); - + // Unescape HTML entities coming from web translators if (Zotero.parentTranslator && value.includes('&')) { value = value.replace(/&#?\w+;/g, function(entity) { var char = ZU.unescapeHTML(entity); if (char == entity) char = ZU.unescapeHTML(entity.toLowerCase()); // Sometimes case can be incorrect and entities are case-sensitive - + return char; }); } - + return value; } @@ -871,26 +1063,26 @@ function beginRecord(type, closeChar) { } var item = new Zotero.Item(zoteroType); item._extraFields = []; - } + } else if (type == "preamble") { // Preamble (keeping separate in case we want to do something with these) Zotero.debug("discarded preamble from BibTeX"); return; } - + // For theses write the thesisType determined by the BibTeX type. if (type == "mastersthesis" && item) item.type = "Master's Thesis"; if (type == "phdthesis" && item) item.type = "PhD Thesis"; var field = ""; - + // by setting dontRead to true, we can skip a read on the next iteration // of this loop. this is useful after we read past the end of a string. var dontRead = false; - + var value, rawValue; while (dontRead || (read = Zotero.read(1))) { dontRead = false; - + // the equal sign indicate the start of the value // which will be handled in the following part // possible formats are: @@ -910,25 +1102,25 @@ function beginRecord(type, closeChar) { while (" \n\r\t".includes(read)) { read = Zotero.read(1); } - + if (keyRe.test(read)) { // read numeric data here, since we might get an end bracket // that we should care about value = ""; value += read; - + // character is a number or part of a string name while ((read = Zotero.read(1)) && /[a-zA-Z0-9\-:_]/.test(read)) { value += read; } - + // don't read the next char; instead, process the character // we already read past the end of the string dontRead = true; - + // see if there's a defined string if (strings[value.toLowerCase()]) value = strings[value.toLowerCase()]; - + // rawValue has to be set for some fields to process // thus, in this case, we set it equal to value rawValue = value; @@ -936,19 +1128,19 @@ function beginRecord(type, closeChar) { rawValue = getFieldValue(read); value = unescapeBibTeX(rawValue); } - + valueArray.push(value); rawValueArray.push(rawValue); - + while (" \n\r\t".includes(read)) { read = Zotero.read(1); } - + } while (read === "#"); - + value = valueArray.join(''); rawValue = rawValueArray.join(''); - + if (item) { processField(item, field.toLowerCase(), value, rawValue); } else if (type == "string") { @@ -976,17 +1168,17 @@ function beginRecord(type, closeChar) { } delete item.backupLocation; } - + if (!item.date) { item.date = dateFieldsToDate(item.year, item.month, item.day); } delete item.year; delete item.month; delete item.day; - + item.extra = extraFieldsToString(item._extraFields); delete item._extraFields; - + if (!item.publisher && item.backupPublisher){ item.publisher=item.backupPublisher; delete item.backupPublisher; @@ -1022,11 +1214,11 @@ function doImport() { function readString(resolve, reject) { var read = ""; var type = false; - + var next = function () { readString(resolve, reject); }; - + try { while (read = Zotero.read(1)) { if (read == "@") { @@ -1063,7 +1255,7 @@ function readString(resolve, reject) { reject(e); return; } - + resolve(); } @@ -1080,7 +1272,7 @@ function writeField(field, value, isMacro) { if (!isMacro && !(field == "url" || field == "doi" || field == "file" || field == "lccn" )) { // I hope these are all the escape characters! value = escapeSpecialCharacters(value); - + if (caseProtectedFields.includes(field)) { value = ZU.XRegExp.replace(value, protectCapsRE, "$1{$2$3}"); // only $2 or $3 will have a value, not both } @@ -1166,7 +1358,7 @@ var vphantomRe = /\\vphantom{\\}}((?:.(?!\\vphantom{\\}}))*)\\vphantom{\\{}/g; function escapeSpecialCharacters(str) { var newStr = str.replace(/[|\<\>\~\^\\\{\}]/g, function(c) { return alwaysMap[c]; }) .replace(/([\#\$\%\&\_])/g, "\\$1"); - + // We escape each brace in the text by making sure that it has a counterpart, // but sometimes this is overkill if the brace already has a counterpart in // the text. @@ -1179,7 +1371,7 @@ function escapeSpecialCharacters(str) { vphantomRe.lastIndex = 0; // Start over, because the previous replacement could have created a new pair } } - + return newStr; } @@ -1275,9 +1467,9 @@ function buildCiteKey (item, extraFields, citekeys) { const citationKey = extraFields.findIndex(field => field.field && field.value && field.field.toLowerCase() === 'citation key'); if (citationKey >= 0) return extraFields.splice(citationKey, 1)[0].value; } - + if (item.citationKey) return item.citationKey; - + var basekey = ""; var counter = 0; var citeKeyFormatRemaining = citeKeyFormat; @@ -1351,11 +1543,11 @@ function doExport() { + "|^([\\p{Letter}\\d]+\\p{Uppercase_Letter}[\\p{Letter}\\d]*)" // Initial word with capital in non-initial position , 'g'); } - + //Zotero.write("% BibTeX export generated by Zotero "+Zotero.Utilities.getVersion()); // to make sure the BOM gets ignored Zotero.write("\n"); - + var first = true; var citekeys = new Object(); var item; @@ -1383,15 +1575,15 @@ function doExport() { } if (!type) type = "misc"; - + // create a unique citation key - var extraFields = item.extra ? parseExtraFields(item.extra) : null; + var extraFields = parseAndConvertExtraFields(item); var citekey = buildCiteKey(item, extraFields, citekeys); - + // write citation key Zotero.write((first ? "" : "\n\n") + "@"+type+"{"+citekey); first = false; - + for (var field in fieldMap) { if (item[fieldMap[field]]) { writeField(field, item[fieldMap[field]]); @@ -1401,12 +1593,12 @@ function doExport() { if (item.reportNumber || item.issue || item.seriesNumber || item.patentNumber) { writeField("number", item.reportNumber || item.issue || item.seriesNumber|| item.patentNumber); } - + if (item.accessDate){ var accessYMD = item.accessDate.replace(/\s*\d+:\d+:\d+/, ""); writeField("urldate", accessYMD); } - + if (item.publicationTitle) { if (item.itemType == "bookSection" || item.itemType == "conferencePaper") { writeField("booktitle", item.publicationTitle); @@ -1416,7 +1608,7 @@ function doExport() { writeField("journal", item.publicationTitle); } } - + if (item.publisher) { if (item.itemType == "thesis") { writeField("school", item.publisher); @@ -1426,7 +1618,7 @@ function doExport() { writeField("publisher", item.publisher); } } - + if (item.creators && item.creators.length) { // split creators into subcategories var author = ""; @@ -1445,9 +1637,9 @@ function doExport() { } else { creatorString = creator.lastName; } - + creatorString = escapeSpecialCharacters(creatorString); - + if (creator.fieldMode == true) { // fieldMode true, assume corporate author creatorString = "{" + creatorString + "}"; } else { @@ -1464,7 +1656,7 @@ function doExport() { collaborator += " and "+creatorString; } } - + if (author) { writeField("author", "{" + author.substr(5) + "}", true); } @@ -1478,7 +1670,7 @@ function doExport() { writeField("collaborator", "{" + collaborator.substr(5) + "}", true); } } - + if (item.date) { var date = Zotero.Utilities.strToDate(item.date); // need to use non-localized abbreviation @@ -1489,23 +1681,9 @@ function doExport() { writeField("year", date.year); } } - - if (extraFields) { - // Export identifiers - for (var i=0; i" : "\u21D2", // RIGHTWARDS DOUBLE ARROW "<=>" : "\u21D4", // LEFT RIGHT DOUBLE ARROW */ "$\\infty$" : "\u221E", // INFINITY - + /*"||" : "\u2225", // PARALLEL TO "/=" : "\u2260", // NOT EQUAL TO "<=" : "\u2264", // LESS-THAN OR EQUAL TO @@ -2767,7 +2945,7 @@ var reversemappingTable = { "|=" : "\u22A7", // MODELS "|=" : "\u22A8", // TRUE "||-" : "\u22A9", // FORCES */ - + "$\\#$" : "\u22D5", // EQUAL AND PARALLEL TO //"<<<" : "\u22D8", // VERY MUCH LESS-THAN //">>>" : "\u22D9", // VERY MUCH GREATER-THAN