Skip to content

Commit

Permalink
Merge pull request #140 from zotero/dynamic-output-format
Browse files Browse the repository at this point in the history
setOutputFormat API
  • Loading branch information
cormacrelf authored Dec 1, 2021
2 parents 792c0e1 + 171bd17 commit 7f67551
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 65 deletions.
24 changes: 18 additions & 6 deletions crates/citeproc/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl HasFetcher for Processor {

impl ImplementationDetails for Processor {
fn get_formatter(&self) -> Markup {
self.formatter.clone()
self.formatter()
}
fn lookup_cluster_id(&self, symbol: ClusterId) -> Option<SmartString> {
let reader = self.interner.read().unwrap();
Expand Down Expand Up @@ -196,8 +196,6 @@ impl Processor {
let fetcher =
fetcher.unwrap_or_else(|| Arc::new(citeproc_db::PredefinedLocales::bundled_en_us()));
let mut db = Processor::safe_default(fetcher);
db.format_options = format_options;
db.formatter = format.make_markup(db.format_options);
let style = Style::parse_with_opts(
&style,
csl::ParseOptions {
Expand All @@ -207,11 +205,25 @@ impl Processor {
},
)?;
db.set_style_with_durability(Arc::new(style), Durability::HIGH);
db.set_output_format(format, format_options);
db.set_default_lang_override_with_durability(locale_override, Durability::HIGH);
db.set_bibliography_no_sort_with_durability(bibliography_no_sort, Durability::HIGH);
Ok(db)
}

/// Sets the output format. Will require nearly everything to be recomputed, so call sparingly.
pub fn set_output_format(&mut self, format: SupportedFormat, options: FormatOptions) {
self.format_options = options;
let formatter = format.make_markup(options);
if self.formatter == formatter {
// Avoid recomputing everything if possible
return;
}
self.formatter = formatter.clone();
self.set_formatter_with_durability(formatter, Durability::HIGH);
}

/// Sets the CSL style to be used. Will require nearly everything to be recomputed, so call sparingly.
pub fn set_style_text(&mut self, style_text: &str) -> Result<(), StyleError> {
let style = Style::parse(style_text)?;
self.set_style_with_durability(Arc::new(style), Durability::HIGH);
Expand Down Expand Up @@ -589,7 +601,7 @@ impl Processor {
csl::style::SecondFieldAlign::Flush => SecondFieldAlign::Flush,
csl::style::SecondFieldAlign::Margin => SecondFieldAlign::Margin,
}),
format_meta: self.formatter.meta(),
format_meta: self.get_formatter().meta(),
}
})
}
Expand Down Expand Up @@ -813,7 +825,7 @@ impl Processor {

let formatter = format
.map(|fmt| fmt.make_markup(self.format_options))
.unwrap_or_else(|| self.formatter.clone());
.unwrap_or_else(|| self.get_formatter());
let markup = citeproc_proc::db::built_cluster_preview(self, id, &formatter);
let cluster_cites_sorted = self.cluster_cites_sorted(id);
let nn = self.cluster_note_number(id);
Expand All @@ -835,7 +847,7 @@ impl Processor {
self.set_reference_input(preview_ref_id.clone(), arc.clone());
let formatter = format
.map(|fmt| fmt.make_markup(self.format_options))
.unwrap_or_else(|| self.formatter.clone());
.unwrap_or_else(|| self.get_formatter().clone());
citeproc_proc::bib_item_preview(self, preview_ref_id.clone(), arc.as_ref(), &formatter)
}

Expand Down
2 changes: 2 additions & 0 deletions crates/db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod cluster;
mod xml;

pub use cite::*;
use citeproc_io::output::markup::Markup;
pub use cluster::*;
pub use xml::*;

Expand All @@ -14,6 +15,7 @@ use salsa::Durability;
pub fn safe_default(db: &mut (impl cite::CiteDatabase + xml::LocaleDatabase + xml::StyleDatabase)) {
use std::sync::Arc;
db.set_style_with_durability(Default::default(), Durability::HIGH);
db.set_formatter_with_durability(Markup::html(), Durability::HIGH);
db.set_all_keys_with_durability(Default::default(), Durability::MEDIUM);
db.set_all_uncited(Default::default());
db.set_all_cluster_ids(Arc::new(Default::default()));
Expand Down
4 changes: 4 additions & 0 deletions crates/db/src/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use std::io;
use std::sync::Arc;

use citeproc_io::output::markup::Markup;
use csl::{
locale::{Lang, Locale, LocaleSource, EN_US},
style::{Name, Style, TextElement, TextSource},
Expand All @@ -24,6 +25,9 @@ pub trait StyleDatabase {
#[salsa::input]
fn style(&self) -> Arc<Style>;

#[salsa::input]
fn formatter(&self) -> Markup;

/// Grabs the Name options from `<style>` + `<citation>` elements
/// First one is the inherited names-delimiter
fn name_info_citation(&self) -> (Option<SmartString>, Arc<Name>);
Expand Down
20 changes: 20 additions & 0 deletions crates/wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,3 +638,23 @@ if (parentMeta.independentMeta.hasBibliography) {
// ...
driver.free();
```

### `setOutputFormat` and `setStyle`

If you wish to change the output format of the entire driver, you can use
`setOutputFormat(format, formatOptions)`. The format is a string, one of `"html" |
"rtf" | "plain"` just like the `Driver.new` method. The options is an optional
argument with the same value as `formatOptions` in `Driver.new`.

`setStyle(xmlString)` will change the CSL style used by the driver.

Both of these methods will require throwing out almost all cached computation,
so use sparingly.

If you need to render a preview in a different format, there is an argument on
`previewCitationCluster` for doing just that. It does not throw out all the
computation. `citeproc-rs`' disambiguation procedures do take formatting into
account, so `<i>Title</i>` can be distinct from `<b>Title</b>` in HTML and RTF,
but not if the whole driver's output format is `"plain"`, since they both look
identical in plain text. `previewCitationCluster` will simply translate the
formatting into another format, without re-computing all the disambiguation.
81 changes: 81 additions & 0 deletions crates/wasm/js-tests/node/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { withDriver, oneOneOne, mkNoteStyle, mkInTextStyle, checkUpdatesLen } from './utils';
import { UpdateSummary, Driver } from '@citeproc-rs/wasm';

let italicStyle = mkNoteStyle(
`
<text variable="title" font-style="italic" />
<text variable="URL" prefix=" " />
`,
);
let boldStyle = mkNoteStyle(
`
<text variable="title" font-weight="bold" />
<text variable="URL" prefix=" " />
`,
);

describe("Driver", () => {

test('boots', () => {
Expand All @@ -20,6 +33,28 @@ describe("Driver", () => {
expect(res).toBe("TEST_TITLE");
});
});


test("can setOutputFormat", () => {
withDriver({ style: italicStyle, format: "html" }, driver => {
const one = "one";
oneOneOne(driver, { title: "Italicised", URL: "https://google.com" }, one);
expect(driver.builtCluster(one).unwrap())
.toBe("<i>Italicised</i> <a href=\"https://google.com/\">https://google.com</a>");
driver.setOutputFormat("html", {}).unwrap();
expect(driver.builtCluster(one).unwrap())
.toBe("<i>Italicised</i> <a href=\"https://google.com/\">https://google.com</a>");
driver.setOutputFormat("html", { linkAnchors: false }).unwrap();
expect(driver.builtCluster(one).unwrap())
.toBe("<i>Italicised</i> https://google.com");
driver.setOutputFormat("rtf", { linkAnchors: false }).unwrap();
expect(driver.builtCluster(one).unwrap())
.toBe("{\\i Italicised} https://google.com");
driver.setOutputFormat("plain").unwrap();
expect(driver.builtCluster(one).unwrap())
.toBe("Italicised https://google.com");
})
});
});

describe("batchedUpdates", () => {
Expand Down Expand Up @@ -100,6 +135,51 @@ describe("batchedUpdates", () => {
})
});

test("produces updates when style or output format change", () => {
withDriver({ style: italicStyle, format: "html" }, driver => {
const one = "one";
let once: UpdateSummary, twice: UpdateSummary;
oneOneOne(driver, { title: "Italicised", URL: "https://a.com" }, one);

expect(driver.builtCluster(one).unwrap()).toBe('<i>Italicised</i> <a href="https://a.com/">https://a.com</a>');
driver.batchedUpdates().unwrap();

// no change
driver.setOutputFormat("html", {}).unwrap();
once = driver.batchedUpdates().unwrap(); twice = driver.batchedUpdates().unwrap();
expect(once).toEqual(twice);
checkUpdatesLen(once, 0, 0); checkUpdatesLen(twice, 0, 0);

// change part of FormatOptions
driver.setOutputFormat("html", { linkAnchors: false }).unwrap();
once = driver.batchedUpdates().unwrap(); twice = driver.batchedUpdates().unwrap();
expect(once).not.toEqual(twice);
expect(once.clusters).toContainEqual([one, '<i>Italicised</i> https://a.com']);
checkUpdatesLen(twice, 0, 0);

// change to rtf
driver.setOutputFormat("rtf", { linkAnchors: false }).unwrap();
once = driver.batchedUpdates().unwrap(); twice = driver.batchedUpdates().unwrap();
expect(once).not.toEqual(twice);
expect(once.clusters).toContainEqual([one, '{\\i Italicised} https://a.com']);
checkUpdatesLen(twice, 0, 0);

// change style to bold instead of italic
driver.setStyle(boldStyle);
once = driver.batchedUpdates().unwrap(); twice = driver.batchedUpdates().unwrap();
expect(once).not.toEqual(twice);
expect(once.clusters).toContainEqual([one, '{\\b Italicised} https://a.com']);
checkUpdatesLen(twice, 0, 0);

// back to html
driver.setOutputFormat("html", { linkAnchors: false }).unwrap();
once = driver.batchedUpdates().unwrap(); twice = driver.batchedUpdates().unwrap();
expect(once).not.toEqual(twice);
expect(once.clusters).toContainEqual([one, "<b>Italicised</b> https://a.com"]);
checkUpdatesLen(twice, 0, 0);
})
});

});

describe("previewCitationCluster", () => {
Expand Down Expand Up @@ -295,3 +375,4 @@ describe("initialiser", () => {
});
});
});

Loading

0 comments on commit 7f67551

Please sign in to comment.