Last active
March 13, 2026 18:10
-
-
Save minimarimo3/c341778865e0561af33feb2e8090d795 to your computer and use it in GitHub Desktop.
Minimalist Hayagriva Translator for Zotero A simple script to export Zotero items to .yml for use with Typst.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "translatorID": "b8a08333-55b2-4b12-b75d-80457b8bcff6", | |
| "label": "Hayagriva", | |
| "creator": "minimarimo3 + ChatGPT", | |
| "target": "yaml", | |
| "minVersion": "5.0", | |
| "maxVersion": "", | |
| "priority": 100, | |
| "inRepository": false, | |
| "translatorType": 2, | |
| "lastUpdated": "2026-03-13 00:00:00" | |
| } | |
| // Hayagriva export translator for Zotero | |
| // References: | |
| // - Zotero translator docs: https://www.zotero.org/support/dev/translators | |
| // - Hayagriva file format: https://github.com/typst/hayagriva/blob/main/docs/file-format.md | |
| function doExport() { | |
| var seenKeys = Object.create(null); | |
| var item; | |
| while ((item = Zotero.nextItem())) { | |
| if (!item || item.itemType === "note" || item.itemType === "attachment") continue; | |
| var entry = buildEntry(item); | |
| var key = makeUniqueCitationKey(generateCitationKey(item), seenKeys); | |
| Zotero.write(key + ":\n"); | |
| Zotero.write(serializeMapping(entry, 2)); | |
| Zotero.write("\n"); | |
| } | |
| } | |
| function buildEntry(item) { | |
| var entryType = getHayagrivaType(item); | |
| var parent = buildParent(item, entryType); | |
| var creatorData = buildCreators(item, entryType, parent); | |
| var identifiers = collectSerialNumbers(item); | |
| var entry = {}; | |
| entry.type = entryType; | |
| assign(entry, "title", cleanText(item.title)); | |
| assign(entry, "author", creatorData.author); | |
| assign(entry, "date", normalizeDate(item.date)); | |
| assign(entry, "editor", creatorData.editor); | |
| assign(entry, "affiliated", creatorData.affiliated); | |
| assign(entry, "publisher", buildTopLevelPublisher(item, entryType, !!parent)); | |
| assign(entry, "organization", buildOrganization(item, entryType)); | |
| assign(entry, "location", buildTopLevelLocation(item, entryType, !!parent)); | |
| assign(entry, "serial-number", identifiers); | |
| assign(entry, "language", cleanValue(item.language)); | |
| assign(entry, "edition", shouldKeepTopLevelNumbering(entryType, parent) ? cleanValue(item.edition) : null); | |
| assign(entry, "genre", buildGenre(item, entryType)); | |
| assign(entry, "issue", shouldKeepTopLevelNumbering(entryType, parent) ? cleanValue(item.issue) : null); | |
| assign(entry, "volume", shouldKeepTopLevelNumbering(entryType, parent) ? cleanValue(item.volume) : null); | |
| assign(entry, "volume-total", shouldKeepTopLevelNumbering(entryType, parent) ? toNumberIfPossible(item.numberOfVolumes) : null); | |
| assign(entry, "chapter", shouldKeepTopLevelNumbering(entryType, parent) ? cleanValue(item.section) : null); | |
| assign(entry, "page-range", normalizePageRange(item.pages)); | |
| assign(entry, "page-total", toNumberIfPossible(item.numPages)); | |
| assign(entry, "url", buildUrl(item.url, item.accessDate)); | |
| assign(entry, "archive", cleanValue(item.archive)); | |
| assign(entry, "archive-location", cleanValue(item.archiveLocation)); | |
| assign(entry, "call-number", cleanValue(item.callNumber)); | |
| assign(entry, "runtime", cleanValue(item.runningTime)); | |
| assign(entry, "abstract", cleanText(item.abstractNote)); | |
| assign(entry, "note", buildNote(item)); | |
| assign(entry, "parent", parent); | |
| return entry; | |
| } | |
| function buildCreators(item, entryType, parent) { | |
| var authors = []; | |
| var editors = []; | |
| var affiliatedByRole = Object.create(null); | |
| var parentEditors = []; | |
| var creators = item.creators || []; | |
| for (var i = 0; i < creators.length; i++) { | |
| var creator = creators[i]; | |
| var name = formatCreator(creator); | |
| if (!name) continue; | |
| if (creator.creatorType === "author") { | |
| authors.push(name); | |
| continue; | |
| } | |
| if (creator.creatorType === "editor") { | |
| if (shouldAttachEditorsToParent(item, entryType, parent)) { | |
| parentEditors.push(name); | |
| } | |
| else { | |
| editors.push(name); | |
| } | |
| continue; | |
| } | |
| if (creator.creatorType === "bookAuthor") { | |
| if (!parent) parent = {}; | |
| if (!parent.author) parent.author = []; | |
| parent.author.push(name); | |
| continue; | |
| } | |
| if (creator.creatorType === "seriesEditor") { | |
| if (parent) { | |
| if (!parent.editor) parent.editor = []; | |
| parent.editor.push(name); | |
| } | |
| else { | |
| editors.push(name); | |
| } | |
| continue; | |
| } | |
| // Reasonable fallbacks for creator-heavy item types | |
| if (item.itemType === "interview" && creator.creatorType === "interviewee") { | |
| authors.push(name); | |
| continue; | |
| } | |
| if ((item.itemType === "presentation" || item.itemType === "podcast") && creator.creatorType === "presenter") { | |
| authors.push(name); | |
| continue; | |
| } | |
| var role = mapRole(creator.creatorType); | |
| if (!role) continue; | |
| if (!affiliatedByRole[role]) affiliatedByRole[role] = []; | |
| affiliatedByRole[role].push(name); | |
| } | |
| if (parent && parentEditors.length) { | |
| if (!parent.editor) parent.editor = []; | |
| parent.editor = parent.editor.concat(parentEditors); | |
| } | |
| var affiliated = []; | |
| for (var role in affiliatedByRole) { | |
| affiliated.push({ | |
| role: role, | |
| names: affiliatedByRole[role].length === 1 ? affiliatedByRole[role][0] : affiliatedByRole[role] | |
| }); | |
| } | |
| return { | |
| author: authors.length ? authors : null, | |
| editor: editors.length ? editors : null, | |
| affiliated: affiliated.length ? affiliated : null | |
| }; | |
| } | |
| function shouldAttachEditorsToParent(item, entryType, parent) { | |
| if (!parent) return false; | |
| if (["article", "chapter", "entry", "anthos"].indexOf(entryType) !== -1) return true; | |
| if (item.itemType === "conferencePaper") return true; | |
| return false; | |
| } | |
| function buildParent(item, entryType) { | |
| var parent = {}; | |
| if (["journalArticle", "magazineArticle", "newspaperArticle"].indexOf(item.itemType) !== -1) { | |
| parent.type = item.itemType === "newspaperArticle" ? "newspaper" : "periodical"; | |
| assign(parent, "title", cleanText(item.publicationTitle)); | |
| assign(parent, "volume", cleanValue(item.volume)); | |
| assign(parent, "issue", cleanValue(item.issue)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "bookSection") { | |
| parent.type = entryType === "anthos" ? "anthology" : "book"; | |
| assign(parent, "title", cleanText(item.bookTitle || item.publicationTitle)); | |
| assign(parent, "volume", cleanValue(item.volume)); | |
| assign(parent, "volume-total", toNumberIfPossible(item.numberOfVolumes)); | |
| assign(parent, "edition", cleanValue(item.edition)); | |
| assign(parent, "publisher", buildPublisher(item.publisher, item.place)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "encyclopediaArticle" || item.itemType === "dictionaryEntry") { | |
| parent.type = "reference"; | |
| assign(parent, "title", cleanText(item.encyclopediaTitle || item.dictionaryTitle || item.publicationTitle)); | |
| assign(parent, "volume", cleanValue(item.volume)); | |
| assign(parent, "edition", cleanValue(item.edition)); | |
| assign(parent, "publisher", buildPublisher(item.publisher, item.place)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "conferencePaper") { | |
| parent.type = item.publicationTitle ? "proceedings" : "conference"; | |
| assign(parent, "title", cleanText(item.publicationTitle || item.conferenceName)); | |
| assign(parent, "organization", cleanValue(item.proceedingsTitle ? null : item.conferenceName && !item.publicationTitle ? item.publisher : null)); | |
| assign(parent, "publisher", buildPublisher(item.publisher, item.place)); | |
| assign(parent, "volume", cleanValue(item.volume)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "blogPost") { | |
| parent.type = "blog"; | |
| assign(parent, "title", cleanText(item.blogTitle || item.websiteTitle || item.publicationTitle)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "webpage") { | |
| parent.type = "web"; | |
| assign(parent, "title", cleanText(item.websiteTitle || item.publicationTitle)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| if (item.itemType === "forumPost") { | |
| parent.type = "thread"; | |
| assign(parent, "title", cleanText(item.forumTitle || item.websiteTitle || item.publicationTitle)); | |
| assign(parent, "url", buildUrl(item.url, item.accessDate)); | |
| return hasContent(parent) ? parent : null; | |
| } | |
| return null; | |
| } | |
| function getHayagrivaType(item) { | |
| if (item.itemType === "bookSection") { | |
| var hasBookAuthor = hasCreatorType(item, "bookAuthor"); | |
| var hasEditor = hasCreatorType(item, "editor") || hasCreatorType(item, "seriesEditor"); | |
| return hasEditor && !hasBookAuthor ? "anthos" : "chapter"; | |
| } | |
| var map = { | |
| book: "book", | |
| journalArticle: "article", | |
| magazineArticle: "article", | |
| newspaperArticle: "article", | |
| thesis: "thesis", | |
| letter: "misc", | |
| manuscript: "manuscript", | |
| interview: "misc", | |
| film: "video", | |
| artwork: "artwork", | |
| webpage: "web", | |
| conferencePaper: "article", | |
| report: "report", | |
| bill: "legislation", | |
| case: "case", | |
| hearing: "misc", | |
| patent: "patent", | |
| statute: "legislation", | |
| email: "misc", | |
| map: "misc", | |
| blogPost: "article", | |
| instantMessage: "misc", | |
| forumPost: "thread", | |
| audioRecording: "audio", | |
| podcast: "audio", | |
| presentation: "misc", | |
| videoRecording: "video", | |
| tvBroadcast: "video", | |
| radioBroadcast: "audio", | |
| computerProgram: "repository", | |
| document: "misc", | |
| encyclopediaArticle: "entry", | |
| dictionaryEntry: "entry", | |
| dataset: "misc", | |
| preprint: "article", | |
| standard: "report" | |
| }; | |
| return map[item.itemType] || "misc"; | |
| } | |
| function buildTopLevelPublisher(item, entryType, hasParent) { | |
| if (hasParent) { | |
| if (["book", "report", "thesis", "manuscript", "patent", "legislation", "case", "misc", "repository", "artwork", "audio", "video", "web"].indexOf(entryType) === -1) { | |
| return null; | |
| } | |
| } | |
| if (entryType === "report" && !item.publisher && item.institution) return null; | |
| if (entryType === "thesis" && !item.publisher && item.university) return null; | |
| return buildPublisher(item.publisher, needsPublisherLocation(entryType, hasParent) ? item.place : null); | |
| } | |
| function needsPublisherLocation(entryType, hasParent) { | |
| if (hasParent && ["article", "chapter", "entry", "anthos"].indexOf(entryType) !== -1) return false; | |
| return ["book", "report", "thesis", "manuscript", "patent", "legislation", "case", "repository", "misc"].indexOf(entryType) !== -1; | |
| } | |
| function buildOrganization(item, entryType) { | |
| if (entryType === "thesis") return cleanValue(item.university || item.institution); | |
| if (entryType === "report") return cleanValue(item.institution || item.organization); | |
| if (entryType === "repository") return cleanValue(item.company || item.organization || item.publisher); | |
| if (entryType === "patent") return cleanValue(item.assignee); | |
| return cleanValue(item.organization); | |
| } | |
| function buildTopLevelLocation(item, entryType, hasParent) { | |
| if (!item.place) return null; | |
| if (needsPublisherLocation(entryType, hasParent)) return null; | |
| if (["video", "audio", "artwork", "misc", "performance"].indexOf(entryType) !== -1) return cleanValue(item.place); | |
| return null; | |
| } | |
| function buildGenre(item, entryType) { | |
| if (entryType === "thesis") return cleanValue(item.thesisType || "thesis"); | |
| if (entryType === "report") return cleanValue(item.reportType); | |
| if (item.itemType === "webpage") return cleanValue(item.websiteType); | |
| if (item.itemType === "artwork") return cleanValue(item.artworkMedium || item.type); | |
| if (item.itemType === "interview") return "interview"; | |
| if (item.itemType === "letter") return "letter"; | |
| if (item.itemType === "presentation") return cleanValue(item.presentationType || "presentation"); | |
| if (item.itemType === "standard") return "standard"; | |
| return null; | |
| } | |
| function buildNote(item) { | |
| var parts = []; | |
| var extraNote = stripStructuredExtra(item.extra); | |
| if (extraNote) parts.push(cleanText(extraNote)); | |
| if (item.medium && item.itemType !== "artwork") parts.push("Medium: " + cleanText(item.medium)); | |
| if (item.libraryCatalog) parts.push("Catalog: " + cleanText(item.libraryCatalog)); | |
| var note = parts.filter(Boolean).join("\n\n"); | |
| return note || null; | |
| } | |
| function shouldKeepTopLevelNumbering(entryType, parent) { | |
| if (!parent) return true; | |
| return ["book", "report", "thesis", "manuscript", "case", "legislation", "patent", "repository", "misc", "artwork", "audio", "video", "web"].indexOf(entryType) !== -1; | |
| } | |
| function stripStructuredExtra(extra) { | |
| if (!extra) return null; | |
| var lines = String(extra).split(/\r?\n/); | |
| var kept = []; | |
| for (var i = 0; i < lines.length; i++) { | |
| var line = lines[i].trim(); | |
| if (!line) { | |
| kept.push(lines[i]); | |
| continue; | |
| } | |
| if (/^(DOI|ISBN|ISSN|PMID|PMCID|arXiv|LCCN|MR|Zbl|Version|Version Number|Report Number|Patent Number|Docket Number)\s*:/i.test(line)) continue; | |
| kept.push(lines[i]); | |
| } | |
| var joined = kept.join("\n").trim(); | |
| return joined || null; | |
| } | |
| function buildPublisher(name, location) { | |
| name = cleanValue(name); | |
| location = cleanValue(location); | |
| if (!name) return null; | |
| if (location) return { name: name, location: location }; | |
| return name; | |
| } | |
| function buildUrl(url, accessDate) { | |
| url = cleanValue(url); | |
| if (!url) return null; | |
| var date = normalizeDate(accessDate); | |
| if (date) { | |
| return { | |
| value: url, | |
| date: date | |
| }; | |
| } | |
| return url; | |
| } | |
| function collectSerialNumbers(item) { | |
| var serials = {}; | |
| assign(serials, "doi", cleanValue(item.DOI)); | |
| assign(serials, "isbn", cleanValue(item.ISBN)); | |
| assign(serials, "issn", cleanValue(item.ISSN)); | |
| assign(serials, "pmid", cleanValue(item.PMID)); | |
| assign(serials, "pmcid", cleanValue(item.PMCID)); | |
| if (item.itemType === "report") assign(serials, "serial", cleanValue(item.reportNumber)); | |
| if (item.itemType === "patent") assign(serials, "serial", cleanValue(item.patentNumber)); | |
| if (item.itemType === "computerProgram") assign(serials, "version", cleanValue(item.versionNumber || item.version)); | |
| if (item.itemType === "case") assign(serials, "serial", cleanValue(item.docketNumber)); | |
| var extraFields = parseExtraFields(item.extra); | |
| mergePreferExisting(serials, extraFields); | |
| return hasContent(serials) ? serials : null; | |
| } | |
| function parseExtraFields(extra) { | |
| var serials = {}; | |
| if (!extra) return serials; | |
| var lines = String(extra).split(/\r?\n/); | |
| for (var i = 0; i < lines.length; i++) { | |
| var line = lines[i].trim(); | |
| if (!line) continue; | |
| var m = line.match(/^(DOI|ISBN|ISSN|PMID|PMCID|arXiv|LCCN|MR|Zbl|Version|Version Number|Report Number|Patent Number|Docket Number)\s*:\s*(.+)$/i); | |
| if (!m) continue; | |
| var label = m[1].toLowerCase(); | |
| var value = cleanValue(m[2]); | |
| if (!value) continue; | |
| if (label === "version" || label === "version number") serials.version = value; | |
| else if (label === "report number" || label === "patent number" || label === "docket number") serials.serial = value; | |
| else serials[label] = value; | |
| } | |
| return serials; | |
| } | |
| function mapRole(creatorType) { | |
| var map = { | |
| translator: "translator", | |
| contributor: "collaborator", | |
| interviewee: "collaborator", | |
| interviewer: "collaborator", | |
| director: "director", | |
| scriptwriter: "writer", | |
| producer: "producer", | |
| castMember: "cast-member", | |
| sponsor: "organizer", | |
| counsel: "collaborator", | |
| inventor: "holder", | |
| artist: "illustrator", | |
| performer: "cast-member", | |
| composer: "composer", | |
| wordsBy: "writer", | |
| cartographer: "illustrator", | |
| programmer: "writer", | |
| podcaster: "executive-producer", | |
| presenter: "cast-member", | |
| guest: "collaborator", | |
| host: "collaborator" | |
| }; | |
| return map[creatorType] || "collaborator"; | |
| } | |
| function formatCreator(creator) { | |
| if (!creator) return null; | |
| if (creator.fieldMode === 1 && creator.lastName) return cleanValue(creator.lastName); | |
| if (creator.name) return cleanValue(creator.name); | |
| var last = cleanValue(creator.lastName); | |
| var first = cleanValue(creator.firstName); | |
| if (last && first) return last + ", " + first; | |
| return last || first || null; | |
| } | |
| function generateCitationKey(item) { | |
| var parts = []; | |
| var authorish = getPrimaryKeyPart(item); | |
| var year = extractYear(item.date); | |
| var title = slugify(cleanText(item.title) || "untitled"); | |
| if (authorish) parts.push(slugify(authorish)); | |
| if (year) parts.push(year); | |
| if (title) parts.push(title); | |
| var key = parts.filter(Boolean).join("_") || "item"; | |
| if (key.length > 40) key = key.slice(0, 40).replace(/_+$/g, ""); | |
| return key || "item"; | |
| } | |
| function getPrimaryKeyPart(item) { | |
| if (item.creators && item.creators.length) { | |
| for (var i = 0; i < item.creators.length; i++) { | |
| var creator = item.creators[i]; | |
| if (creator.creatorType === "author" || creator.creatorType === "inventor" || creator.creatorType === "director" || creator.creatorType === "presenter") { | |
| return creator.lastName || creator.name || creator.firstName; | |
| } | |
| } | |
| var first = item.creators[0]; | |
| if (first) return first.lastName || first.name || first.firstName; | |
| } | |
| return item.publisher || item.institution || item.organization || item.university || item.websiteTitle || domainFromUrl(item.url) || "item"; | |
| } | |
| function extractYear(date) { | |
| var iso = normalizeDate(date) || cleanValue(date); | |
| if (!iso) return null; | |
| var m = String(iso).match(/^-?\d{4}/); | |
| return m ? m[0] : null; | |
| } | |
| function makeUniqueCitationKey(base, seenKeys) { | |
| base = base || "item"; | |
| if (!seenKeys[base]) { | |
| seenKeys[base] = 1; | |
| return base; | |
| } | |
| seenKeys[base]++; | |
| var suffix = "_" + seenKeys[base]; | |
| var trimmed = base; | |
| if (trimmed.length + suffix.length > 40) { | |
| trimmed = trimmed.slice(0, 40 - suffix.length).replace(/_+$/g, ""); | |
| } | |
| return trimmed + suffix; | |
| } | |
| function slugify(str) { | |
| str = cleanText(str || ""); | |
| if (!str) return ""; | |
| str = str.replace(/[^\p{L}\p{N}_-]+/gu, "_"); | |
| str = str.replace(/^_+|_+$/g, ""); | |
| return str.toLowerCase(); | |
| } | |
| function normalizeDate(value) { | |
| if (!value) return null; | |
| var iso = Zotero.Utilities.strToISO(value); | |
| return iso || cleanValue(value); | |
| } | |
| function normalizePageRange(value) { | |
| value = cleanValue(value); | |
| if (!value) return null; | |
| return value.replace(/--+/g, "-"); | |
| } | |
| function toNumberIfPossible(value) { | |
| if (value === undefined || value === null || value === "") return null; | |
| if (typeof value === "number") return value; | |
| var str = String(value).trim(); | |
| if (/^-?\d+$/.test(str)) return parseInt(str, 10); | |
| return str; | |
| } | |
| function cleanText(str) { | |
| if (str === undefined || str === null || str === "") return null; | |
| str = String(str); | |
| str = str.replace(/<\s*br\s*\/?\s*>/gi, "\n"); | |
| str = str.replace(/<\/p\s*>/gi, "\n"); | |
| str = str.replace(/<[^>]+>/g, ""); | |
| str = str.replace(/\u00A0/g, " "); | |
| str = str.replace(/[ \t\f\v]+/g, " "); | |
| str = str.replace(/\n\s+/g, "\n"); | |
| str = str.replace(/\n{3,}/g, "\n\n"); | |
| str = str.trim(); | |
| return str || null; | |
| } | |
| function cleanValue(value) { | |
| if (value === undefined || value === null || value === "") return null; | |
| value = String(value).trim(); | |
| return value || null; | |
| } | |
| function domainFromUrl(url) { | |
| url = cleanValue(url); | |
| if (!url) return null; | |
| var m = url.match(/^https?:\/\/([^\/]+)/i); | |
| if (!m) return null; | |
| return m[1].replace(/^www\./i, ""); | |
| } | |
| function hasCreatorType(item, creatorType) { | |
| var creators = item.creators || []; | |
| for (var i = 0; i < creators.length; i++) { | |
| if (creators[i].creatorType === creatorType) return true; | |
| } | |
| return false; | |
| } | |
| function assign(obj, key, value) { | |
| if (value === undefined || value === null) return; | |
| if (typeof value === "string" && value === "") return; | |
| if (Array.isArray(value) && !value.length) return; | |
| if (isPlainObject(value) && !hasContent(value)) return; | |
| obj[key] = value; | |
| } | |
| function mergePreferExisting(target, source) { | |
| for (var key in source) { | |
| if (target[key] === undefined || target[key] === null || target[key] === "") { | |
| target[key] = source[key]; | |
| } | |
| } | |
| } | |
| function hasContent(value) { | |
| if (value === undefined || value === null) return false; | |
| if (typeof value === "string") return value !== ""; | |
| if (Array.isArray(value)) return value.length > 0; | |
| if (isPlainObject(value)) { | |
| for (var key in value) { | |
| if (hasContent(value[key])) return true; | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| function isPlainObject(value) { | |
| return Object.prototype.toString.call(value) === "[object Object]"; | |
| } | |
| function serializeMapping(obj, indent) { | |
| var lines = []; | |
| for (var key in obj) { | |
| serializeField(lines, key, obj[key], indent); | |
| } | |
| return lines.join(""); | |
| } | |
| function serializeField(lines, key, value, indent) { | |
| if (!hasContent(value)) return; | |
| var pad = repeat(" ", indent); | |
| if (Array.isArray(value)) { | |
| lines.push(pad + key + ":\n"); | |
| for (var i = 0; i < value.length; i++) { | |
| serializeListItem(lines, value[i], indent + 2); | |
| } | |
| return; | |
| } | |
| if (isPlainObject(value)) { | |
| lines.push(pad + key + ":\n"); | |
| for (var subKey in value) { | |
| serializeField(lines, subKey, value[subKey], indent + 2); | |
| } | |
| return; | |
| } | |
| lines.push(pad + key + ": " + yamlScalar(value) + "\n"); | |
| } | |
| function serializeListItem(lines, value, indent) { | |
| var pad = repeat(" ", indent); | |
| if (Array.isArray(value)) { | |
| lines.push(pad + "-\n"); | |
| for (var i = 0; i < value.length; i++) { | |
| serializeListItem(lines, value[i], indent + 2); | |
| } | |
| return; | |
| } | |
| if (isPlainObject(value)) { | |
| var keys = []; | |
| for (var key in value) { | |
| if (hasContent(value[key])) keys.push(key); | |
| } | |
| if (!keys.length) return; | |
| var firstKey = keys[0]; | |
| var firstValue = value[firstKey]; | |
| if (!Array.isArray(firstValue) && !isPlainObject(firstValue)) { | |
| lines.push(pad + "- " + firstKey + ": " + yamlScalar(firstValue) + "\n"); | |
| for (var i = 1; i < keys.length; i++) { | |
| serializeField(lines, keys[i], value[keys[i]], indent + 2); | |
| } | |
| } | |
| else { | |
| lines.push(pad + "-\n"); | |
| for (var j = 0; j < keys.length; j++) { | |
| serializeField(lines, keys[j], value[keys[j]], indent + 2); | |
| } | |
| } | |
| return; | |
| } | |
| lines.push(pad + "- " + yamlScalar(value) + "\n"); | |
| } | |
| function yamlScalar(value) { | |
| if (typeof value === "number") return String(value); | |
| if (typeof value === "boolean") return value ? "true" : "false"; | |
| return yamlString(value); | |
| } | |
| function yamlString(value) { | |
| return '"' + String(value) | |
| .replace(/\\/g, "\\\\") | |
| .replace(/\r/g, "\\r") | |
| .replace(/\n/g, "\\n") | |
| .replace(/\t/g, "\\t") | |
| .replace(/"/g, '\\"') + '"'; | |
| } | |
| function repeat(str, times) { | |
| var out = ""; | |
| for (var i = 0; i < times; i++) out += str; | |
| return out; | |
| } |
Author
Yep, that seems to be working great. Thank you!
FWIW, the hayagriva cli also seems to convert from .bib just fine:
hayagriva literature.bib > converted.yaml
But your plugin allows users to skip a step by just going straight from zotero to hayagriva.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@fronesis47
Thanks for letting me know! To be honest, I had completely forgotten about this script.
I just made some updates, and the publisher and place information should be exported correctly now.
(If it still doesn't work properly, could you please send me the .bib file for reference?)
Good luck with your research! ヾ(^-^)ノ