tecosaur / LaTeX-Utilities

An add-on to LaTeX Workshop that provides some features that go beyond the bare essentials
MIT License
474 stars 28 forks source link

feat(Zotero): generate bib file for the cited references #398

Open jooyoungseo opened 6 months ago

jooyoungseo commented 6 months ago

Type: Feature Request

Using Cite from Zotero command, people can easily insert citations from their Zotero libraries.

Once all the citations are inserted into tex file, could we generate a bib (default "./references.bib") file for the cited references?

I think this is technically possible as demonstrated in other extension, such as "XING.zotero-cite".

However, their extension does not support "vscode"citation method.

I believe this feature would be greatly appreciated by many Zotero+LaTeX users.

Extension version: 0.4.13 VS Code version: Code - Insiders 1.87.0-insider (c11a2dd4d52e38cb92b8c464f47a7b02bb7c8762, 2024-02-24T01:20:40.384Z) OS version: Windows_NT x64 10.0.22631 Modes:

leoleoasd commented 6 months ago

Thank you for your suggestion, this indeed is an interesting and useful feature. However, currently, I don't have time to implement it. I'm putting this on my todo list, and if anyone can help with a PR I'll be happy to review and merge it. In the meantime, in my current setup, I'm using better bibtex's auto export feature, which can automatically export the entire library to a single .bib file.

volatile-static commented 5 months ago

Here is a code sample for citing items in Zotero and adding them to bib. I hope this helps!

import * as vscode from "vscode";
import Client, {
  MultiReadResponse,
  SingleReadResponse,
} from "zotero-api-client";

const apiKey = "KDRPW", userId = 123;
const zotero = new Client(apiKey).library("user", userId);

class Collection {
  constructor(
    public key: string,
    public name: string,
    public parentCollection: string | false
  ) {}
}

class Item {
  constructor(
    public key: string,
    public name: string,
    public doi: string,
    public creators: object[]
  ) {}
}

type ZoteroObject = Collection | Item;

async function getTopCollections() {
  const collections = await zotero.collections().top().get(),
    data = (collections as MultiReadResponse).getData();
  return data.map(
    (c: any) => new Collection(c.key, c.name, c.parentCollection)
  );
}

async function getSubCollections(collectionKey: string) {
  const collections = await zotero
      .collections(collectionKey)
      .subcollections()
      .get(),
    data = (collections as MultiReadResponse).getData();
  return data.map(
    (c: any) => new Collection(c.key, c.name, c.parentCollection)
  );
}

async function getItems(collectionKey: string) {
  const items = await zotero.collections(collectionKey).items().top().get(),
    data = (items as MultiReadResponse).getData();
  return data.map((i: any) => new Item(i.key, i.title, i.doi, i.creators));
}

async function getItemInfo(itemKey: string) {
  const item = await zotero.items(itemKey).get({ include: "bibtex" });
  return (item as SingleReadResponse).getData();
}

function matchCiteKeys(bibtex: string) {
  const regex = /@\S*?{\S*?,/g;
  return bibtex.match(regex);
}

function getCiteKey(bibtex: string) {
  const regex = /@\S*?{(\S*?),/;
  return regex.exec(bibtex)?.[1];
}

async function citeItem(item: Item) {
  const editor = vscode.window.activeTextEditor;
  if (!editor) return;

  const file = await vscode.workspace.findFiles("zotero.bib");
  if (!file.length) return;

  const info = await getItemInfo(item.key),
    bibtex: string = (<any>info).bibtex,
    citeKey = matchCiteKeys(bibtex)!,
    doc = await vscode.workspace.openTextDocument(file[0]),
    citeKeys = matchCiteKeys(doc.getText());

  editor.edit((editBuilder) =>
    editBuilder.insert(editor.selection.active, `\\cite{${getCiteKey(bibtex)}}`)
  );
  if (!citeKeys || !citeKeys.includes(citeKey[0])) {
    const edit = new vscode.WorkspaceEdit(),
      position = new vscode.Position(0, 0);
    edit.insert(file[0], position, bibtex);
    vscode.workspace.applyEdit(edit);
  }
}

class SimpleTreeProvider implements vscode.TreeDataProvider<ZoteroObject> {
  private _onDidChangeTreeData = new vscode.EventEmitter<undefined>();
  readonly onDidChangeTreeData = this._onDidChangeTreeData.event;

  refresh(): void {
    this._onDidChangeTreeData.fire(undefined);
  }

  getTreeItem(element: ZoteroObject): vscode.TreeItem {
    const treeItem = new vscode.TreeItem(element.name);
    treeItem.contextValue = element instanceof Item ? "item" : "collection";
    treeItem.collapsibleState =
      element instanceof Item
        ? vscode.TreeItemCollapsibleState.None
        : vscode.TreeItemCollapsibleState.Collapsed;
    return treeItem;
  }

  getChildren(element?: Collection) {
    if (!element) return getTopCollections();
    else
      return Promise.all([
        getSubCollections(element.key),
        getItems(element.key),
      ]).then((arr) => arr.flat());
  }
}

export function activate(context: vscode.ExtensionContext) {
  const treeDataProvider = new SimpleTreeProvider();
  vscode.window.createTreeView("latero", {
    treeDataProvider,
    showCollapseAll: true,
    canSelectMany: true,
  });
  context.subscriptions.push(
    vscode.commands.registerCommand("latero.cite", citeItem),
    vscode.commands.registerCommand(
      "latero.refresh",
      treeDataProvider.refresh.bind(treeDataProvider)
    )
  );
}
thebluepotato commented 3 months ago

Since Better BibTex is required anyway, you could use the auto-export to your project. Wouldn't that help?