Citations Plugin: How to access editor-field?

I’m using the Citations Plugin (current version) together with Zotero and BibLaTeX.
What I would like to do is to access the editor-field of my .bib-file with a handlebar in my Literature note content template.

This is how the .bib looks like:

@incollection{somecitekey,
...
editor = {some, editor},
...
}

What did not work

  • First I’ve tried to access the editor-field directly with {{entry.editor}}. While e.g. {{entry.title}} can be accessed directly, this does not work for the editor-field.

  • Second I’ve tried to manipulate main.js by adding variations of the following snippet at different locations.

      Objet.defineProperty(EntryCSLAdapter.prototype, "editor", {
    	get: function () {
    	    return this.data.editor;
    	},
    	enumerable: false,
    	configurable: true
    });
    

    However, I still faild to access the field. I tried brute-forth with {{entry.prototype.creators.editor}}, {{entry.creators.editor}}, and similar.


In fact, there is a hint in the plugin-settings on {{entry}}. But the documentation is not really clear on the syntax on how precisely to access the fields… Hence, a clarification would be much appreciated!

2 Likes

Oy that was painful! Summary: if you are using a bib (not CSL) as your library file, you can get the editor list by combing through data left from the bibtex parsing library the plugin uses. For example, this worked in my Literature Note Content Template to get the last name of the first editor. No guarantees on the behavior if there isn’t an editor for the item, and see the last of the “Notes” for details on the name fields.

Last name of the first editor: {{entry.data.creators.editor.[0].lastName}}

Notes:

  • entry.data Only in bibtex entries, there will be an additional data field in the entry containing the info as provided by the bibtex parser. The suggested template fields in entry are then created by the citations plugin by processing this data. Here’s a useful example of this: parsing entry.data.creators to produce entry.authorString.
  • creators.editor I had to go look in the bibtex parsing library used by the Citations plugin to go see what the field for editors was called. Here is the entire list of creator types in that parsing library.
  • editor.[0] the documentation for either the Citations plugin or the parsing library will tell us that the value of the editor field is an array of names. Normally I would access the first element of an array with arrayName[0]; however the Handlebars templating language used by the citations plugin has problems with this notation so the extra . is needed.
  • [0].lastName Back to the parsing library now that Handlebars is happy, each element in the array is a Name, whose fields are in the documentation. Note the literal field: either the entire name will be there and the other fields will be empty or it will be in pieces under the other fields. Honestly, I just looked at my entry in the Console in Obsidian to see which format it had. To do this I typed the following (replacing citekey with the actual citekey of the entry in the bib file): app.plugins.plugins['obsidian-citation-plugin'].library.entries['citekey'].data.creators.editor[0].

Where to go from here

  • If you know Handlebars, it looks like you can probably loop through the editor array and process out the names without hard-coding in something like this that will cause the literature note to fail if there isn’t an editor field!
  • If you’d rather do this in Javascript/Typescript, take a look at the way the citations plugin makes the authorString(), and do something similar? I think you can use Obsidian plugins like QuickAdd to make literature notes with extra Javascript in them.
  • Is there an Issue on the citations plugin GitHub repo about putting this functionality straight into the plugin? If you end up writing code like authorString() for editors, it’d be great to see it in the plugin for the rest of us to use! I am sure I would use it in the future.

Great question, would love to hear how it goes for your bib file!

2 Likes

Wow, what an amazing answer. Thank you so much @scholarInTraining, especially for pointing to the ability to access the js-Objects in the Console.

I’ve tried various things in the meantime. Below what worked and what not.

Success: hardcoding editorString directly in main.js:

I found that it is sufficient to just add the following blocks of code at the given positions (each, right after the authorString).

Comment: There are more occurences of authorString (at lines 85116 ff.) whose purpose I do not understand exactly. However, in my case it was not necessary to add editorString down there to make it work.

  • line 49486:
    editorString: 'Comma-separated list of editor names',
  • line 49523:
    editorString: entry.editorString,
  • line 49831:
Object.defineProperty(EntryBibLaTeXAdapter.prototype, "editorString", {
        get: function () {
            var _a;
            if (this.data.creators.editor) {
                var names = this.data.creators.editor.map(function (name) {
                    if (name.literal)
                        return name.literal;
                    var parts = [name.firstName, name.prefix, name.lastName, name.suffix];
                    // Drop any null parts and join
                    return parts.filter(function (x) { return x; }).join(' ');
                });
                return names.join(', ');
            }
            else {
                return (_a = this.data.fields.editor) === null || _a === void 0 ? void 0 : _a.join(', ');
            }
        },
        enumerable: false,
        configurable: true
    });

Failed: create anyString with Javascript and Templater-Plugin:

Instead of manipulating the main.js I prefer to create the editorString with the use of the Templater plugin and some custom javascript. This is my preferred solution, (1) since I don’t want to risk loosing the script by e.g. updating the plugin and (2) since I plan to create many variations, especially of authorString for aliasing, which I definitely don’t want to hard code anywhere.

I got inspired to manipulate the authorString by an answer from cbr9 on github. In their Literature Note Content Template they use something like

<% `${tp.user.authorShort("{{authorString}}")}, ${"{{year}}"}` %>

where tp.user.authorShort() is a templater user script and {{authorString}} is the authorString generated by Citations-Plugin.

Unfortunately, this approach is very limited, since it relies on authors[0].split(" ") – which makes it impossible to distinguish between multi-word firstnames and lastnames (as in “Emma C. Spary” and “Anke te Heesen”).

Hence, it would be amazing to be able to inject the full author-js-object into the templater-script like so: {{entry.data.creators.author}}. However this failed:

Templater Error: Template parsing error, aborting.
Bad template syntax

Unexpected identifier

I’m not familiar with handlebars, but shouldn’t it be possible to pass full objects, too, instead of strings only?!

As an alternative I’ve also tried to access the citations-plugin directly from within the templater-script, like so: console.log(this.app.plugins.plugins['obsidian-citation-plugin'].library);

However, this ends up in the following error:

“Templater Error: Template parsing error, aborting.
Cannot read properties of undefined (reading ‘plugins’)”*

Thanks in advance!

@lab I agree that a templater user script sounds like a great approach! I’ve been using this one from @Christian, the maker of the QuickAdd and other plugins as a reference. It calls the citation plugin directly, bypassing the citation plugin’s normal way of creating a literature note and ignoring whatever’s in the Literature Note Content Template box in the Citations settings, so it’s a different style of approach, but I’ve been able to look through and use bits of it.

Cannot read properties of undefined is one of my least favorite error messages - it happens a lot with my dataview queries too when I’ve guessed wrong about what something is called, or made a typo, or assumed that some variable existed and it didn’t.
Investigation strategy for such a message
The error message said it tried to read “plugins” off of undefined so we’re looking in the code for something just to the left of .plugins. That gives us two candidates in this particular line of code. this.app or this.app.plugins. Start with the shorter one and console.log() it, or take one more .something off the end and console.log(this). this doesn’t have a variable called app, so that’s why this.app is undefined (which would be some useful info to just have in the error message, in my opinion).
Solution to this particular error message
Take out this from the front! app is a global variable all on its own, anywhere in Obsidian as far as I can tell.

Hopefully this gets you unstuck while I think about the other parts of your message! I’ll edit this post when I come up with anything there.


EDIT:

Hmmm… oh now that I think about it, I ran into this while making my first post! As an experiment, turn off your templater stuff and just plop that object into your Literature Note Content Template. It shows up as {object Object} or something like that, right?! No wonder your Templater user script can’t do anything with that!

Why? Conceptually, the way the Citations-Handlebars-Template and Templater pieces are working together right now is by passing information via writing it down onto your note. So they have to both agree exactly about how to write down (Citations) and read (Templater) each piece of information in order to make things work correctly.

What to do?
You probably could add code to correctly write down the object (e.g. as JSON) to your note and then more code to parse it back in during your Templater script. But why have the writing-down-to-your-note as the intermediary? I suggest looking into something like the script I linked to at the top of this. You could replace the use of the QuickAddAPI’s suggestor with the one provided in tp.system, if you want to keep this Templater only. (They are both wrappers on top of a Suggester Modal provided by the Obsidian API and thus the code is pretty similar. Yeah, I was curious and got distracted.)

Good luck!

1 Like

Thank you @scholarInTraining!

It was indeed the this.app, which needed to be changed to app. Actually, I was so sure that I’ve tried this earlier, but apparently I got confused with the error messages…

Below I post my templater user scripts. They will change in future, but already now they can handle already quite a few use cases. Also they should be fairly easy to modify.

Currently I run the scripts via the literature note content template of the citations plugin. This approach, unfortunately, has the downside that tp.user can only be triggered on file creation. However, since the function argument is simply the citekey, it should be easy to integrate the functions in a normal Templater-template, as you suggested. (In this case, the citekey could be read e.g. from the note title). Like so it should be possible to load the Strings into a pre-existing file, too.

So thank you very much for the nice co-working on this feature! If you happen to write an implementation for QuickAdd, I’ll be very interested, since I might shift from Templater to QuickAdd in future.

EDIT: I may edit the functions below in future.


Literature note content template:

<% `${tp.user.authorsString("{{citekey}}", true, true)} (${tp.user.yearString("{{citekey}}")})`%>

<% `${tp.user.authorsString("{{citekey}}", false, true)} (${tp.user.yearString("{{citekey}}")})`%>

<% `${tp.user.authorsString("{{citekey}}", true, false)} (${tp.user.yearString("{{citekey}}")})`%>

<% `${tp.user.authorsString("{{citekey}}", false, false)} (${tp.user.yearString("{{citekey}}")})`%>

Sample output 1:

te Heesen, A.; Spary, E. C. (2001)

te Heesen, Anke; Spary, Emma C. (2001)

te Heesen, A. and Spary, E. C. (2001)

te Heesen, Anke and Spary, Emma C. (2001)

Sample output 2:

Adler, C.; Hirsch Hadorn, G.; Breu, T.; Wiesmann, U.; Pohl, C. (2018)

Adler, Carolina; Hirsch Hadorn, Gertrude; Breu, Thomas; Wiesmann, Urs; Pohl, Christian (2018)

Adler, C. et al. (2018)

Adler, Carolina et al. (2018)

yearString.js

function yearString(citekey) {
// Input: 
//    - citekey (string)
// Output: 
//    - String with year of publication

    let data = app.plugins.plugins['obsidian-citation-plugin'].library.entries[citekey].data;

    // extract Year
    let yearString = data.fields.date[0].split("-")[0];
    return yearString
}

module.exports = yearString;

authorsString.js

function authorsString(citekey, printFirstAsInitials=false, 
printAllAuthors=false) {
// Input:
//    - citekey (string)
//    - printFirstAsInitials (boolean)
//    - printAllAuthors (boolean)
// Output:
//    - String of authors

    let fullAuthorList = [];

    // check .bib for author entry
    let data = app.plugins.plugins['obsidian-citation-plugin'].library.entries[citekey].data;

    let authors = [];
    if (data.creators.author)
        authors = data.creators.author;
    else
        authors = [{literal: "NaN"}];

    // for each author
    authors.forEach(author => {
        const [firstNamesArray, lastNamesString] = findFirstLast(author);
        let str = [];
        str.push(lastNamesString)
        str.push(",");
        firstNamesArray.forEach(name => {
            // print firstname(s) as initials or full names
            if (printFirstAsInitials)
                str.push(` ${name[0]}.`);
            else
                str.push(` ${name}`);
        });
        fullAuthorList.push(str.join(""));
    });

    // join Authors, clean and return
    const authorString = joinAuthors(fullAuthorList, printAllAuthors);
    return replaceIllegalFileNameCharactersInString(authorString);
}

module.exports = authorsString;

//-----------------------------------
// Helper functions
//-----------------------------------

function joinAuthors(fullAuthorList, printAllAuthors) {
/* Join Authors: This will either
    - print all authors (separated by ";")
    - print "the author"
    - print "first and second author"
    - print "first author et al."
*/
    if (printAllAuthors == true) {
        return fullAuthorList.join("; ");
    }
    else {
        const len = fullAuthorList.length;
        if (len == 1)
            return fullAuthorList[0];
        else if (len == 2)
            return fullAuthorList.join("\ and ");
        else if (len >= 3)
            return fullAuthorList[0] + " et al.";
    }
}

function findFirstLast(author) {
/* Note, that literal names need to be worked around. This means that if lastnames consist of multiple names, they will be mixed with first names. To avoid this you should modify the .bib-file (i.e. switch to "two fields" in Zotero).

For example
    author: Array(2)
        0: {literal: 'Anke te Heesen'}
        1: {lastName: 'Spary', firstName: 'Emma C.'}
will be rendered as
    "Heesen, Anke te and Spary, Emma C."
instead of
    "te Heesen, Anke and Spray, Emma C."
*/

    let lastNamesString = "";
    let firstNamesArray = [];
    if (author.lastName) {
        firstNamesArray = author.firstName.split(" ");
        lastNamesString = author.lastName;
    }
    // Workaround for literal names
    else {
        let parts = author.literal.split(" ");
        firstNamesArray = parts.slice(0,parts.length-1);
        lastNamesString = parts[parts.length-1];
    }
    return [firstNamesArray, lastNamesString];
}

function replaceIllegalFileNameCharactersInString(string) {
/* This may be important if the authorString is e.g. used for aliasing.*/

    return string.replace(/[\\\/@]*/g, ''); // less strict for Linux/MacOS
//     return string.replace(/[\\#%&\{\}\/*<>$\'\":@,]*/g, ''); // very strict for Windows
}
1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.