bibcite
Version:
Citations with Bibliography
168 lines (147 loc) • 4.54 kB
text/typescript
import { Citation } from "./citation";
type KeyGroup = { index: number; citations: Citation[] };
function docPosComp(a: HTMLElement, b: HTMLElement) {
switch (a.compareDocumentPosition(b)) {
case Node.DOCUMENT_POSITION_FOLLOWING:
return -1;
case Node.DOCUMENT_POSITION_PRECEDING:
return 1;
default:
return 0;
}
}
export class CitationKeyUse {
_used_keys: Map<string, KeyGroup>;
_length: number = 0;
constructor(used_keys: Map<string, KeyGroup>) {
this._used_keys = used_keys;
}
add(ci: Citation): { need_ref_update: boolean } {
if (this._used_keys.has(ci.key)) {
const entry = this._used_keys.get(ci.key);
entry.citations.push(ci); // add citations to entry
ci.bibIndex = entry.index;
return { need_ref_update: false };
} else {
this._length += 1;
const entry = {
index: this._length,
citations: [ci],
};
this._used_keys.set(ci.key, entry);
ci.bibIndex = entry.index;
return { need_ref_update: true };
}
}
remove(ci: Citation): { index: number; need_ref_update: boolean } {
const ci_list = this._used_keys.get(ci.key).citations;
if (ci_list.length <= 1) {
this._used_keys.delete(ci.key);
return { index: 0, need_ref_update: true };
} else {
const idx = ci_list.indexOf(ci);
ci_list.splice(idx, 1); // remove citation from list
return { index: idx, need_ref_update: false };
}
}
has(key: string) {
return this._used_keys.has(key);
}
get citations(): Citation[] {
return Array.from(this._used_keys.values())
.map((keyGroup) => keyGroup.citations)
.flat(1);
}
get() {
return this._used_keys;
}
}
export class BibSortedCitationKeyUse extends CitationKeyUse {
_key_order: Map<string, number>;
constructor(
used_keys: Map<string, KeyGroup>,
key_order: Map<string, number>
) {
super(used_keys);
this._key_order = key_order;
this.sort_used_keys();
}
sort_used_keys() {
const sorted = [...this._used_keys].sort(
([key1, _1], [key2, _2]) =>
this._key_order.get(key1) - this._key_order.get(key2)
);
sorted.forEach(([_, entry], idx) => {
entry.index = idx;
entry.citations.forEach((ci) => (ci.bibIndex = idx));
});
this._used_keys = new Map(sorted);
}
add(ci: Citation) {
const result = super.add(ci);
if (result.need_ref_update) {
this.sort_used_keys();
}
return result;
}
remove(ci: Citation) {
const result = super.remove(ci);
if (result.need_ref_update) {
this.sort_used_keys();
}
return result;
}
}
export class InsertionSortedCitationKeyUse extends CitationKeyUse {
_safe_to_append_key = (_: Citation) => true; // no order issues at first
constructor(used_keys: Map<string, KeyGroup>) {
super(used_keys);
this.sort_used_keys();
}
sort_used_keys() {
// sort lists of citations for every key
this._used_keys.forEach((entry) => entry.citations.sort(docPosComp).at(0));
const sorted = [...this._used_keys].sort(
([_key1, keyGroup1], [_key2, keyGroup2]) =>
docPosComp(
// compare document position of first citation (i.e. .at(0))
keyGroup1.citations.at(0),
keyGroup2.citations.at(0)
)
);
sorted.forEach(([_, entry], idx) => (entry.index = idx));
this._length = sorted.length;
if (this._length == 0) {
this._safe_to_append_key = (_: Citation) => true;
} else {
this._safe_to_append_key = (other) =>
docPosComp(sorted.at(-1)[1].citations.at(0), other) < 0;
}
this._used_keys = new Map(sorted);
// tell citations new index
this._used_keys.forEach((entry) =>
entry.citations.forEach((c) => (c.bibIndex = entry.index))
);
}
add(ci: Citation) {
const update = super.add(ci).need_ref_update;
if (update) {
if (this._safe_to_append_key(ci)) {
// a new reference was added, it was safe to append, so ci was the the
// first usage of it and it came after all other references. The next
// new reference needs to come after to be safe to append.
this._safe_to_append_key = (other) => docPosComp(ci, other) < 0;
} else {
this.sort_used_keys();
}
}
return { need_ref_update: update };
}
remove(ci: Citation) {
const idx = super.remove(ci).index;
if (idx == 0) {
this.sort_used_keys();
}
return { index: idx, need_ref_update: idx == 0 };
}
}