| title | Bibliography (.bbl) Sorting Tool | |||||
|---|---|---|---|---|---|---|
| model | misc | |||||
| author | Leo Petrov | |||||
| layout | sim_page | |||||
| code |
|
|||||
| a11y-description | Text utility for sorting LaTeX bibliography (.bbl) and amsrefs files. Paste bibliography entries into the input area, click Sort, and the tool reorders them alphabetically by author surname then chronologically. Supports standard bibitem and amsrefs bib formats with accent normalization. |
This tool sorts bibliography entries alphabetically by author names, and—for identical author lists—chronologically from oldest to newest. It supports both standard LaTeX bibliography (.bbl) and amsrefs formats, handling complex author formats including initials, surname prefixes (de, von, van), and multiple authors.
<section>
<div class="section-title">Input (.bbl file content)</div>
<textarea id="inputArea" aria-label="Input bibliography content" placeholder="Paste your .bbl file content here..."></textarea>
<div class="stats" id="inputStats" role="status" aria-live="polite"></div>
</section>
<div class="button-group">
<button class="btn btn-primary" onclick="sortBibliography()">Sort Bibliography</button>
<button class="btn btn-secondary" onclick="clearAll()">Clear All</button>
<button class="btn btn-secondary" onclick="loadExample()">Load Standard Example</button>
<button class="btn btn-secondary" onclick="loadAmsrefsExample()">Load Amsrefs Example</button>
</div>
<div id="message" role="status" aria-live="polite"></div>
<section>
<div class="section-title">Sorted Output</div>
<textarea id="outputArea" aria-label="Sorted bibliography output" placeholder="Sorted bibliography will appear here..." readonly></textarea>
<div class="stats" id="outputStats" role="status" aria-live="polite"></div>
</section>
Please enter bibliography content to sort.
';
return;
}
const isAmsrefs = isAmsrefsFormat(input);
const lines = input.split(/\r?\n/);
const header = [];
const entries = [];
let curr = null;
let footer = [];
let footerStarted = false;
let inHeader = true;
for (const line of lines) {
// Handle amsrefs format
if (isAmsrefs) {
if (line.trim().startsWith('\\begin{bibdiv}') || line.trim().startsWith('\\begin{biblist}')) {
header.push(line);
inHeader = false;
continue;
}
if (inHeader) {
header.push(line);
continue;
}
if (line.trim().startsWith('\\bib{')) {
if (curr) entries.push(curr);
curr = [line];
} else if (line.trim() === '}' && curr && curr.length > 0) {
// End of amsrefs entry
curr.push(line);
entries.push(curr);
curr = null;
} else if (line.trim().startsWith('\\end{biblist}') || line.trim().startsWith('\\end{bibdiv}')) {
if (curr) {
entries.push(curr);
curr = null;
}
footerStarted = true;
footer.push(line);
} else if (footerStarted) {
footer.push(line);
} else if (curr) {
curr.push(line);
}
} else {
// Handle standard format
if (line.trim().startsWith('\\begin{thebibliography}')) {
header.push(line);
inHeader = false;
continue;
}
if (inHeader) {
header.push(line);
continue;
}
if (line.trim().startsWith('\\bibitem')) {
if (curr) entries.push(curr);
curr = [line];
} else if (line.trim().startsWith('\\end{thebibliography}')) {
if (curr) entries.push(curr);
footer.push(line);
curr = null;
} else if (curr) {
curr.push(line);
}
}
}
if (curr) entries.push(curr);
entries.sort((a, b) => {
const ka = entryKey(a, isAmsrefs);
const kb = entryKey(b, isAmsrefs);
if (ka !== kb) // primary: authors A-Z
return ka < kb ? -1 : 1;
// secondary: year ↑ within identical‐author group
const ya = extractYear(a, isAmsrefs);
const yb = extractYear(b, isAmsrefs);
return ya - yb;
});
const outLines = [
...header,
...entries.flatMap((e, i) => {
if (isAmsrefs) {
// For amsrefs, entries already include their closing }
// Add blank line after each entry except the last
return i < entries.length - 1 ? [...e, ''] : e;
} else {
// For standard format, add blank line after each entry
return [...e, ''];
}
}),
...footer
];
const output = outLines.join('\n');
document.getElementById('outputArea').value = output;
document.getElementById('message').innerHTML = 'Bibliography sorted successfully!
';
updateStats();
}
// Example bibliography for testing
const exampleBbl = `\\begin{thebibliography}{99}
\\bibitem{EinBoh1955}
A. Einstein and N. Bohr.
\\newblock Unified field theory and the proper way to organize one's sock drawer.
\\newblock \\emph{Princeton Advanced Studies Quarterly}, 12(3):42--108, 1955.
\\bibitem{EinMax1900}
A. Einstein and M. Planck.
\\newblock On blackbody radiation and why my office is always too cold.
\\newblock \\emph{Ann. Physik}, 4(3):553--563, 1900.
\\bibitem{Ein1905}
A. Einstein.
\\newblock On the electrodynamics of moving bodies, with special attention to my wild hair.
\\newblock \\emph{Ann. Physik}, 17(10):891--921, 1905.
\\bibitem{EinNewt1687}
A. Einstein and I. Newton.
\\newblock Principia mathematica: Now with 100% more relativity and significantly better notation.
\\newblock \\emph{Philosophiae Naturalis Principia Mathematica (Revised Edition)}, Royal Society of London, 1687.
\\bibitem{EinPod1935}
A. Einstein and B. Podolsky.
\\newblock Can quantum-mechanical description of physical reality be considered complete? Also, why is my hair like this?
\\newblock \\emph{Phys. Rev.}, 47(10):777--780, 1935.
\\end{thebibliography}`;
const exampleAmsrefs = `% \\bib, bibdiv, biblist are defined by the amsrefs package.
\\begin{bibdiv}
\\begin{biblist}
\\bib{aggarwal2019universality}{article}{
author={Aggarwal, A.},
title={{Universality for Lozenge Tiling Local Statistics}},
date={2023},
journal={Ann. of Math. (2)},
volume={198},
number={3},
pages={881\\ndash 1012},
note={arXiv:1907.09991 [math.PR]},
}
\\bib{aggarwal2022gaussian}{article}{
author={Aggarwal, A.},
author={Gorin, V.},
title={{Gaussian unitary ensemble in random lozenge tilings}},
date={2022},
journal={Prob. Theory Relat. Fields},
volume={184},
number={4},
pages={1139\\ndash 1166},
note={arXiv:2106.07589 [math.PR]},
}
\\bib{BG2011non}{article}{
author={Borodin, A.},
author={Gorin, V.},
title={{Markov processes of infinitely many nonintersecting random walks}},
date={2013},
journal={Probab. Theory Relat. Fields},
volume={155},
number={3-4},
pages={935\\ndash 997},
note={arXiv:1106.1299 [math.PR]},
}
\\bib{borodin-gr2009q}{article}{
author={Borodin, A.},
author={Gorin, V.},
author={Rains, E.},
title={{q-Distributions on boxed plane partitions}},
date={2010},
journal={Selecta Math.},
volume={16},
number={4},
pages={731\\ndash 789},
note={arXiv:0905.0679 [math-ph]},
}
\\end{biblist}
\\end{bibdiv}`;
function loadExample() {
document.getElementById('inputArea').value = exampleBbl;
updateStats();
}
function loadAmsrefsExample() {
document.getElementById('inputArea').value = exampleAmsrefs;
updateStats();
}
function clearAll() {
document.getElementById('inputArea').value = '';
document.getElementById('outputArea').value = '';
document.getElementById('message').innerHTML = '';
updateStats();
}
function updateStats() {
const input = document.getElementById('inputArea').value;
const output = document.getElementById('outputArea').value;
// Count entries for both formats
const inputBibitemCount = (input.match(/\\\\bibitem/g) || []).length;
const inputBibCount = (input.match(/\\\\bib\{/g) || []).length;
const inputEntries = inputBibitemCount + inputBibCount;
document.getElementById('inputStats').textContent = inputEntries ? `${inputEntries} entries` : '';
const outputBibitemCount = (output.match(/\\\\bibitem/g) || []).length;
const outputBibCount = (output.match(/\\\\bib\{/g) || []).length;
const outputEntries = outputBibitemCount + outputBibCount;
document.getElementById('outputStats').textContent = outputEntries ? `${outputEntries} entries sorted` : '';
}
// Update stats on input
document.getElementById('inputArea').addEventListener('input', updateStats);
// Initialize
updateStats();
{% endraw %}
</script>