Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support LaTeX output and linking to bibliography #42

Merged
merged 1 commit into from
Nov 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ from reference providers (like Zotero), in which case the item from the provider

![animation of migrating from cite2c and from DOIs][migrate-gif]

### Exporting to LaTeX

Citation Manager supports LaTeX as one of the output formats. For now the citations are embedded as plain-text,
while the bibliography uses semantic `thebibliography` LaTeX environment.
To configure notebook to export citations to LaTeX:

1. Go to `Advanced Settings Editor` -> `Citation Manager` and change the default `outputFormat` to `latex`.
2. Insert bibliography in a cell, but instead of using `Markdown` cell, use `Raw` cell
3. Open `Property Inspector` sidebar and change `Raw NBConvert Format` to `LaTeX` (you need to have the `Raw` cell selected for the dropdown to appear)
4. From `File` menu select `Save and Export Notebook As…` and choose `LaTeX`
5. Compile the resulting `.tex` to desired output such as PDF with preferred tool (e.g. `pdflatex` on Linux)

![LaTeX setup overview][latex-setup]

[bookshelf]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/style/icons/bookshelf.svg?sanitize=true
[book-open-variant]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/style/icons/book-open-variant.svg?sanitize=true
[book-plus]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/style/icons/book-plus.svg?sanitize=true
Expand All @@ -90,10 +104,11 @@ from reference providers (like Zotero), in which case the item from the provider
[add-bibliography]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/docs/images/add-bibliography.gif
[change-style]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/docs/images/change-style.gif
[migrate-gif]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/docs/images/migrate-cite2c-and-doi.gif
[latex-setup]: https://raw.githubusercontent.com/krassowski/jupyterlab-citation-manager/main/docs/images/latex.png

## Requirements

* JupyterLab >= 3.1
* JupyterLab >= 3.2
* Modern browser (with ES 2019 support, e.g. Firefox 64+, or Chrome 73+)
* [Zotero account](https://www.zotero.org/user/register)

Expand Down
Binary file added docs/images/latex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@
"default": "apa.csl",
"description": "The default citation style, one of the styles in the Citation Style Language (CSL) repository."
},
"linkToBibliography": {
"title": "Link citations to bibliography",
"type": "boolean",
"default": true,
"description": "Should citations link to the bibliography? Only available for HTML output format."
},
"outputFormat": {
"title": "Format of citations and bibliography",
"title": "The default format of citations and bibliography",
"type": "string",
"default": "html",
"description": "One of: html, text, rtf, asciidoc, fo, latex",
"description": "One of: html, text, rtf, asciidoc, fo, latex. Only `html` and `latex` are supported.",
"enum": ["html", "text", "rtf", "asciidoc", "fo", "latex"]
}
},
Expand Down
96 changes: 81 additions & 15 deletions src/adapters/notebook.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
CitationInsertData,
CitationQuerySubset,
CiteProc,
CommandIDs,
ICitableItemRecords,
ICitableItemRecordsBySource,
ICitation,
ICitationFormattingOptions,
ICitationManager,
ICitationMap,
IDocumentAdapter
Expand All @@ -18,6 +20,8 @@ import { JupyterFrontEnd } from '@jupyterlab/application';
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
import ICellMetadata = NotebookAdapter.ICellMetadata;
import type { Cell } from '@jupyterlab/cells';
import OutputMode = CiteProc.OutputMode;
import { itemIdToPrimitive } from '../index';

export namespace NotebookAdapter {
export interface INotebookMetadata extends ReadonlyPartialJSONObject {
Expand All @@ -29,6 +33,10 @@ export namespace NotebookAdapter {
* Mapping of citable items used in this document, grouped by the source.
*/
items: ICitableItemRecordsBySource;
/**
* The output format (default `html`).
*/
format?: OutputMode;
}

export interface ICellMetadata extends ReadonlyPartialJSONObject {
Expand All @@ -45,7 +53,10 @@ export const cellMetadataKey = 'citation-manager';
export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
citations: ICitation[];

constructor(public document: NotebookPanel) {
constructor(
public document: NotebookPanel,
public options: ICitationFormattingOptions
) {
this.citations = [];
}

Expand Down Expand Up @@ -126,6 +137,14 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
return metadata.style;
}

get outputFormat(): OutputMode {
const metadata = this.notebookMetadata;
if (!metadata) {
return this.options.defaultFormat;
}
return metadata.format || this.options.defaultFormat;
}

setCitationStyle(newStyle: string): void {
if (!this.document.model) {
console.warn('Cannot set style on', this.document, ' - no model');
Expand All @@ -141,11 +160,29 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
}

formatBibliography(bibliography: string): string {
console.log('format', this.outputFormat);
if (this.outputFormat === 'latex') {
return bibliography;
}
return `<!-- BIBLIOGRAPHY START -->${bibliography}<!-- BIBLIOGRAPHY END -->`;
}

formatCitation(citation: CitationInsertData): string {
return `<cite id="${citation.citationId}">${citation.text}</cite>`;
// note: not using `wrapCitationEntry` as that was causing more problems
// (itemID undefined).
let text = citation.text;
if (this.outputFormat === 'html' && this.options.linkToBibliography) {
// link to the first mentioned element
const first = citation.items[0];
const firstID = itemIdToPrimitive(first);
text = `<a href="#${firstID}">${text}</a>`;
} else if (this.outputFormat === 'latex') {
// this does not work well with MathJax - we need to figure out something else!
// but it might still be useful (without $) for text editor adapter
// const citationIDs = citation.items.map(itemIdToPrimitive).join(',');
// text = `$$\\cite{${citationIDs}}$$`;
}
return `<cite id="${citation.citationId}">${text}</cite>`;
}

insertCitation(citation: CitationInsertData): void {
Expand All @@ -169,7 +206,7 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
`<cite id=["']${citation.citationId}["'][^>]*?>([\\s\\S]*?)<\\/cite>`
);
let matches = 0;
this.markdownCells.forEach(cell => {
this.nonCodeCells.forEach(cell => {
const oldText = cell.model.value.text;
const matchIndex = oldText.search(pattern);
if (matchIndex !== -1) {
Expand All @@ -196,13 +233,38 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
}

updateBibliography(bibliography: string): void {
const pattern =
const htmlPattern =
/(?<=<!-- BIBLIOGRAPHY START -->)([\s\S]*?)(?=<!-- BIBLIOGRAPHY END -->)/;
this.markdownCells.forEach(cell => {
const htmlFullyCapturingPattern =
/(<!-- BIBLIOGRAPHY START -->[\s\S]*?<!-- BIBLIOGRAPHY END -->)/;
const latexPattern =
/(\\begin{thebibliography}[\s\S]*?\\end{thebibliography})/;

this.nonCodeCells.forEach(cell => {
const oldText = cell.model.value.text;
if (oldText.match(/<!-- BIBLIOGRAPHY START -->/)) {
cell.model.value.text = oldText.replace(pattern, bibliography);
if (oldText.search(pattern) === -1) {
cell.model.value.text = oldText.replace(
this.outputFormat === 'latex'
? htmlFullyCapturingPattern
: htmlPattern,
this.outputFormat === 'latex' ? bibliography.trim() : bibliography
);
if (oldText.search(htmlPattern) === -1) {
console.warn(
'Failed to update bibliography',
bibliography,
'in',
oldText
);
}
} else if (oldText.match(/\\begin{thebibliography}/)) {
cell.model.value.text = oldText.replace(
latexPattern,
this.outputFormat !== 'latex'
? this.formatBibliography(bibliography)
: bibliography.trim()
);
if (oldText.search(latexPattern) === -1) {
console.warn(
'Failed to update bibliography',
bibliography,
Expand All @@ -217,15 +279,15 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
private chooseCells(subset: CitationQuerySubset) {
switch (subset) {
case 'all':
return this.markdownCells;
return this.nonCodeCells;
case 'after-cursor':
// TODO check for off by one
return this.selectMarkdownCells(
return this.selectNonCodeCells(
this.document.content.activeCellIndex,
Infinity
);
case 'before-cursor':
return this.selectMarkdownCells(
return this.selectNonCodeCells(
0,
this.document.content.activeCellIndex
);
Expand Down Expand Up @@ -275,19 +337,23 @@ export class NotebookAdapter implements IDocumentAdapter<NotebookPanel> {
}
}

private get markdownCells() {
/**
* We want to insert citation/bibliography in Markdown and Raw cells
* (raw cells so that LaTeX can be exported as-is).
*/
private get nonCodeCells() {
return this.document.content.widgets.filter(
cell => cell.model.type === 'markdown'
cell => cell.model.type !== 'code'
);
}

private selectMarkdownCells(min: number, max: number) {
private selectNonCodeCells(min: number, max: number) {
return this.document.content.widgets
.slice(min, max)
.filter(cell => cell.model.type === 'markdown');
.filter(cell => cell.model.type !== 'code');
}

addCitationMetadata(cell: Cell, citationsInCell: ICitation[]) {
addCitationMetadata(cell: Cell, citationsInCell: ICitation[]): void {
let metadata: ICellMetadata = cell.model.metadata.get(
cellMetadataKey
) as ICellMetadata;
Expand Down
Loading