Skip to content

Instantly share code, notes, and snippets.

@dustbringer
Last active March 12, 2023 12:55
Show Gist options
  • Save dustbringer/6675f9aad4ff47725933abeb7affb891 to your computer and use it in GitHub Desktop.
Save dustbringer/6675f9aad4ff47725933abeb7affb891 to your computer and use it in GitHub Desktop.
Relabel all pandoc footnotes
/*
Footnote re-labeller.
Relabels footnotes of the form [^...] to numeric.
Replaces every label.
*/
/*************** Input *****************/
// Replace this with what you want to relabel.
const input = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. In viverra finibus orci, non porttitor dui tempus ut. Vestibulum eros sapien, ornare eget placerat quis, tristique in urna. Aliquam volutpat posuere felis, et malesuada dolor porta faucibus. Nam elementum tempor velit, lacinia volutpat neque auctor vel. Maecenas et ipsum tempus, placerat leo at, ultrices arcu [^1] et [^2]. Donec mattis nunc vel ultricies imperdiet. Integer tellus nibh, consectetur ut nisl sed, pellentesque ultrices tortor.
[^2]: In efficitur tincidunt luctus.
[^1]: Proin consequat libero blandit placerat suscipit.
Suspendisse faucibus, massa id bibendum vehicula, dolor purus maximus odio, sit amet malesuada lacus nisl sit amet turpis. Duis eu elit ut felis mollis molestie. Aenean vel mauris diam. Nunc malesuada interdum lacus, et varius augue cursus condimentum. Cras ac nisi efficitur, venenatis lectus eu, viverra nulla. Vivamus vel tellus risus. Nulla sed magna mi. Sed ut molestie purus, a egestas ex. Phasellus convallis massa est, id rhoncus lorem varius mollis. Etiam vulputate sed metus vel ultricies. Fusce sed consequat erat, vitae luctus tellus. Nulla ipsum ante, ornare posuere vehicula quis, sodales at lacus. Donec sit amet maximus dolor [^donec], non fringilla lectus. Mauris tellus magna, vulputate nec efficitur sed, volutpat eget ligula.
Suspendisse potenti. Mauris non erat eu ante faucibus placerat. Aliquam pulvinar ligula tincidunt urna blandit laoreet. Fusce ultrices, elit id rhoncus consectetur, risus purus pretium augue, at varius nisl velit vel ipsum. Nullam sed elementum turpis, id tincidunt dui. Integer ut eleifend ipsum, quis blandit leo. Maecenas condimentum facilisis neque rhoncus laoreet. Curabitur pellentesque tincidunt sapien, nec varius diam facilisis vitae. Aenean orci augue, luctus id dapibus ut, blandit eget purus. Sed convallis rutrum felis, a convallis sem commodo non. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sed odio in neque vehicula [^3] feugiat non at magna.
Curabitur ac urna enim. Maecenas dignissim lobortis rhoncus. Aliquam erat volutpat. Curabitur rhoncus feugiat neque, in convallis augue. Morbi congue iaculis dolor a varius. Mauris fringilla odio rutrum, lobortis risus et, gravida nibh. Suspendisse vitae libero sed ante maximus rutrum at sed quam. Etiam ultricies dui et dictum finibus.
[^donec]: Donec porttitor diam magna, eu mattis leo [^1] venenatis ut [^0]. Sed et egestas velit.
[^0]: Curabitur ipsum mauris, ultrices quis dui ac, eleifend dignissim neque. Fusce at ornare ex. Morbi iaculis magna non dolor luctus, et venenatis quam dictum.
[^3]: Cf. [^2].`;
/******************** Helper ********************/
const isNum = (s) => Number.isInteger(s) || /^\d+$/.test(s);
// Looks for footnote target in the form [^...]:
// Assuming:
// - no text before it
// - ending with a colon
// - no whitespace in label
// - only symbols in label are "_", ".", and "-"
const getLabels = (s) =>
[...s.matchAll(/^[^\S\r\n]*\[\^([a-zA-Z0-9_.-]+)\]:/gm)].map((m) => m[1]);
const getIntLabels = (s) =>
[...s.matchAll(/^[^\S\r\n]*\[\^(\d+)\]:/gm)].map((m) => m[1]);
// Gets largest integer in the array
const getMaxInt = (matches) =>
Math.max(...matches.filter((m) => isNum(m)).map((m) => parseInt(m, 10)));
/******************** Functions ********************/
/*
Relabels everything with ascending integers
*/
const relabelAll = (text, indexFrom = 1) => {
if (indexFrom < 0) console.error("indexFrom must be non-negative");
let output = text;
/*
Replace all labels with unique number that is not used later.
(We have no clashes between old and new because all previous numbers are smaller)
*/
let matches = getLabels(text);
console.log(matches);
matches.forEach((m, i) => {
// Start indexing from largest unused number that is larger than any final indexes
const start = Math.max(getMaxInt(matches) + 1, matches.length + indexFrom);
output = output.replaceAll(`[^${m}]`, `[^${i + start}]`);
});
/*
Replace all labels with i+indexFrom.
(At this point all labels are "large" numbers in ascending order,
we can replace with number they appear without any problems.)
*/
matches = getLabels(output);
console.log(matches);
matches.forEach((m, i) => {
output = output.replaceAll(`[^${m}]`, `[^${i + indexFrom}]`);
});
return output;
};
/*
Relabels only the integer labels with ascending integers
*/
const relabelInt = (text, indexFrom = 1) => {
if (indexFrom < 0) throw new Error("indexFrom must be non-negative");
let output = text;
/*
Replace all labels with unique number that is not used later.
(We have no clashes between old and new because all previous numbers are smaller)
*/
let matches = getIntLabels(text);
matches.forEach((m, i) => {
// Start indexing from largest unused number that is larger than any final indexes
const start = Math.max(getMaxInt(matches) + 1, matches.length + indexFrom);
output = output.replaceAll(`[^${m}]`, `[^${i + start}]`);
});
/*
Replace all labels with i+indexFrom.
(At this point all labels are "large" numbers in ascending order,
we can replace with number they appear without any problems.)
*/
matches = getIntLabels(output);
matches.forEach((m, i) => {
output = output.replaceAll(`[^${m}]`, `[^${i + indexFrom}]`);
});
return output;
};
console.log(relabelAll(input));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment