Skip to content

Commit

Permalink
Prevent duplicate labels in :alpha style
Browse files Browse the repository at this point in the history
This implements a stateful `AlphaStyle` that disambiguates the labels
for the bibliography by appending a suffix "a", "b", etc. This emulates
the behavior of the numeric citation style in LaTeX.

The existing `:alpha` style is automatically upgraded to the new
`AlphaStyle`. It's a lot easier for users to pass `style=:alpha` than to
instantiate the full `AlphaStyle`, in particular because `AlphaStyle`
needs access to all the entries of the `.bib` file (so instantiating in
manually would end up reading the `.bib` file twice). Moreover, there's
no reason anyone would ever want to use the old "dumb" `:alpha` style.

In addition to the new `AlphaStyle`, this also fixes some
inconsistencies relative to the alphabetic citation style in LaTeX:

* Up to four names are included in the label, not 3 (but 5 or more names
  results in 3 names and "+")
* The first letter of a "particle" of the name is included in the label,
  e.g. "vWB08" for a first author "von Winckel"
  • Loading branch information
goerz committed Jul 31, 2023
1 parent 04ed974 commit 45c694d
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 19 deletions.
8 changes: 7 additions & 1 deletion docs/latex/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: all clean

ALL = numeric.pdf authoryear.pdf rmp.pdf prb.pdf
ALL = numeric.pdf authoryear.pdf rmp.pdf prb.pdf alpha.pdf

all: $(ALL)

Expand Down Expand Up @@ -28,6 +28,12 @@ prb.pdf: prb.tex
pdflatex $<
pdflatex $<

alpha.pdf: alpha.tex
pdflatex $<
bibtex $(basename $<)
pdflatex $<
pdflatex $<

clean:
@rm -f ${ALL}
@rm -f *.aux
Expand Down
63 changes: 63 additions & 0 deletions docs/latex/alpha.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
\documentclass[aps,pra,onecolumn,noshowpacs,superscriptaddress,preprintnumbers,%
kamsmath,amssymb,notitlepage,letterpaper]{revtex4-2}

\def\Author{Michael Goerz}
\def\Title{Demo of the standard RevTeX numeric citation style}

\usepackage{natbib}
\usepackage[utf8]{inputenc}
\usepackage{caption}
\captionsetup{justification=raggedright, singlelinecheck=true}
\usepackage[
pdftitle={\Title},
pdfauthor={\Author},
colorlinks=true, linkcolor=black, urlcolor=black, citecolor=black,
bookmarksopen=false, breaklinks=true, plainpages=false, pdfpagelabels
]{hyperref}

\begin{document}

\title{\Title}
\author{\Author}
\date{\today}

\maketitle

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{itemize}
\item \verb|\cite{GoerzQ2022}| renders as ``\cite{GoerzQ2022}''.
\item \verb|\citet{GoerzQ2022}| renders as ``\citet{GoerzQ2022}''.
\item \verb|\citep{GoerzQ2022}| renders as ``\citep{GoerzQ2022}''.
\item \verb|\cite[Eq.~(1)]{GoerzQ2022}| renders as ``\cite[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citet[Eq.~(1)]{GoerzQ2022}| renders as ``\citet[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citep[Eq.~(1)]{GoerzQ2022}| renders as ``\citep[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citet*{GoerzQ2022}| renders as ``\citet*{GoerzQ2022}''.
\item \verb|\citep*{GoerzQ2022}| renders as ``\citep*{GoerzQ2022}''.
\item \verb|\citet*[Eq.~(1)]{GoerzQ2022}| renders as ``\citet*[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citep*[Eq.~(1)]{GoerzQ2022}| renders as ``\citep*[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citet{WinckelIP2008}| renders as ``\citet{WinckelIP2008}''.
\item \verb|\Citet{WinckelIP2008}| renders as ``\Citet{WinckelIP2008}''.
\item \cite{GraceJMO2007}, \cite{GraceJPB2007}, \cite{GrondPRA2009a}, and \cite{GrondPRA2009b}
\end{itemize}

Further commands that we do not support in markdown:

\begin{itemize}
\item \verb|\citenum{GoerzQ2022}| renders as ``\citenum{GoerzQ2022}''.
\item \verb|\citealt{GoerzQ2022}| renders as ``\citealt{GoerzQ2022}''.
\item \verb|\citealp{GoerzQ2022}| renders as ``\citealp{GoerzQ2022}''.
\item \verb|\citealt*{GoerzQ2022}| renders as ``\citealt*{GoerzQ2022}''.
\item \verb|\citealp*{GoerzQ2022}| renders as ``\citealp*{GoerzQ2022}''.
\item \verb|\citealt*[Eq.~(1)]{GoerzQ2022}| renders as ``\citealt*[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\citealp*[Eq.~(1)]{GoerzQ2022}| renders as ``\citealp*[Eq.~(1)]{GoerzQ2022}''.
\item \verb|\Citealt{WinckelIP2008}| renders as ``\Citealt{WinckelIP2008}''.
\item \verb|\Citealp{WinckelIP2008}| renders as ``\Citealp{WinckelIP2008}''.
\end{itemize}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\bibliographystyle{alpha}
\bibliography{../src/refs}

\end{document}
18 changes: 18 additions & 0 deletions docs/src/gallery.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ Style = :alpha
Canonical = false
```

Note that the `:alpha` style is able to automatically disambiguate labels:

```@bibliography
Pages = []
Style = :alpha
Canonical = false
GraceJMO2007
GraceJPB2007
```

This is achieved by automatically upgrading `style=:alpha` to the internal

```@docs
DocumenterCitations.AlphaStyle
```


## [Custom styles](@id custom_styles)

In the following, we show two examples for user-defined styles. See the [notes on customization](@ref customization) on how to generally define a custom style.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ExpandCitations

## [Customization](@id customization)

A custom style can be created by defining methods for functions listed below that specialize for a user-defined `style` argument to [`CitationBibliography`](@ref). If the `style` is identified by a simple name, e.g. `:mystyle`, the method should specialize on `Val{:mystyle}`, see the [examples for custom styles](@ref custom_styles). Beyond that, e.g., if the `style` needs to implement options or needs to maintain internal state to manage unique citation labels, `style` can be an object of a custom type.
A custom style can be created by defining methods for functions listed below that specialize for a user-defined `style` argument to [`CitationBibliography`](@ref). If the `style` is identified by a simple name, e.g. `:mystyle`, the method should specialize on `Val{:mystyle}`, see the [examples for custom styles](@ref custom_styles). Beyond that, e.g., if the `style` needs to implement options or needs to maintain internal state to manage unique citation labels, `style` can be an object of a custom type. The builtin [`DocumenterCitations.AlphaStyle`](@ref) is an example for such a "stateful" style.


```@docs
Expand Down
58 changes: 58 additions & 0 deletions docs/src/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ @article{GoerzQ2022
Doi = {10.22331/q-2022-12-07-871},
Pages = {871},
Volume = {6},
eprint = {2205.15044},
Archiveprefix = {arXiv},
}

@article{RaithelQST2022,
Expand Down Expand Up @@ -427,3 +429,59 @@ @manual{SciPy
year = {2001--},
url = {https://docs.scipy.org/doc/scipy/},
}


@article{LapertPRA09,
Author = {Lapert, M. and Tehini, R. and Turinici, G. and Sugny, D.},
Title = {Monotonically Convergent Optimal Control Theory of Quantum Systems with Spectral Constraints on the Control Field},
Journal = pra,
Year = {2009},
Doi = {10.1103/PhysRevA.79.063411},
Pages = {063411},
Volume = {79},
}


@article{GraceJMO2007,
Author = {Grace, Matthew D. and Brif, Constantin and Rabitz, Herschel and Lidar, Daniel A. and Walmsley, Ian A. and Kosut, Robert L.},
Title = {Fidelity of optimally controlled quantum gates with randomly coupled multiparticle environments},
Journal = jmo,
Year = {2007},
Doi = {10.1080/09500340701639615},
Pages = {2339},
Volume = {54},
}


@article{GraceJPB2007,
Author = {Grace, Matthew and Brif, Constantin and Rabitz, Herschel and Walmsley, Ian A and Kosut, Robert L and Lidar, Daniel A},
Title = {Optimal control of quantum gates and suppression of decoherence in a system of interacting two-level particles},
Journal = jpb,
Year = {2007},
Doi = {10.1088/0953-4075/40/9/s06},
Pages = {S103},
Volume = {40},
}


@article{GrondPRA2009b,
Author = {Grond, Julian and von Winckel, Gregory and Schmiedmayer, J\"org and Hohenester, Ulrich},
Title = {Optimal control of number squeezing in trapped {Bose-Einstein} condensates},
Journal = pra,
Year = {2009},
Doi = {10.1103/PhysRevA.80.053625},
Pages = {053625},
Volume = {80},
}


@article{GrondPRA2009a,
Author = {Grond, Julian and Schmiedmayer, J\"org and Hohenester, Ulrich},
Title = {Optimizing number squeezing when splitting a mesoscopic condensate},
Journal = pra,
Year = {2009},
Doi = {10.1103/PhysRevA.79.021603},
Pages = {021603},
Volume = {79},
}

84 changes: 81 additions & 3 deletions src/DocumenterCitations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ using Documenter.Expanders
using Documenter.Writers.HTMLWriter

using Markdown
using Bibliography
using Bibliography: xyear, xlink, xtitle
using Bibliography: Bibliography, xyear, xlink, xtitle
using OrderedCollections: OrderedDict, OrderedSet
using Unicode

Expand Down Expand Up @@ -80,7 +79,7 @@ function CitationBibliography(bibfile::AbstractString=""; style=nothing, cached=
# in 1.0. It can be removed in any future 1.1 release.
style = :numeric
end
entries = import_bibtex(bibfile)
entries = Bibliography.import_bibtex(bibfile)
if length(bibfile) > 0
if !isfile(bibfile)
error("bibfile $bibfile does not exist")
Expand All @@ -89,6 +88,11 @@ function CitationBibliography(bibfile::AbstractString=""; style=nothing, cached=
@warn "No entries loaded from $bibfile"
end
end
if style == :alpha
# Upgrade the "dumb" alpha style to a smart style that disambiguates
# duplicate labels
style = AlphaStyle(entries)

Check warning on line 94 in src/DocumenterCitations.jl

View check run for this annotation

Codecov / codecov/patch

src/DocumenterCitations.jl#L94

Added line #L94 was not covered by tests
end
citations = Dict{String,Int64}()
page_citations = Dict{String,Set{String}}()
if cached
Expand All @@ -115,6 +119,80 @@ in its docstring.
"""
struct Example end


"""
"Smart" alphabetic citation style (relative to the "dumb" `:alpha`).
```julia
style = AlphaStyle(entries)
```
with a dict of BibTeX `entries` as returned, e.g., by
`Bibliography.import_bibtex`, instantiates a style for
[`CitationBibliography`](@ref) that avoids duplicate labels.
Any of the entries that would result in the same label will be
disambiguated by appending the suffix "a", "b", etc.
Any bibliography that cites a subset of the given `entries` is guaranteed to
have unique labels.
"""
struct AlphaStyle

# BibTeX key (entry.id) => rendered label, e.g. "GraceJMO2007" => "GBR+07b"
label_for_key::Dict{String,String}

function AlphaStyle(entries)

entries = OrderedDict{String,Bibliography.Entry}(
# best not to mutate the input, so we'll create a copy before
# sorting (as well as ensuring it's an OrderedDict)
key => entry for (key, entry) in entries
)
Bibliography.sort_bibliography!(entries, :nyt)

# pass 1 - collect dumb labels, identify duplicates
keys_for_label = Dict{String,Vector{String}}()
for (key, entry) in entries
label = alpha_label(entry) # dumb label (no suffix)
if label in keys(keys_for_label)
push!(keys_for_label[label], key)
else
keys_for_label[label] = String[key,]
end
end

# pass 2 - disambiguate duplicates (append suffix to dumb labels)
label_for_key = Dict{String,String}()
for (key, entry) in entries
label = alpha_label(entry)
if length(keys_for_label[label]) > 1
i = findfirst(isequal(key), keys_for_label[label])
label *= _alpha_suffix(i)
end
label_for_key[key] = label
end

return new(label_for_key)

end

end


function _alpha_suffix(i)
if i <= 25
return string(Char(96 + i)) # 1 -> "a", 2 -> "b", etc.
else
# 26 -> "za", 27 -> "zb", etc.
# I couldn't find any information on (and I was too lazy to test) how
# LaTeX handles disambiguation of more than 25 identical labels, but
# this seems sensible. But also: Seriously? I don't think we'll ever
# run into this in real life.
return "z" * _alpha_suffix(i - 25)
end
end


include("citations.jl")
include("bibliography.jl")
include("formatting.jl")
Expand Down
21 changes: 21 additions & 0 deletions src/bibliography.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,17 @@ function format_bibliography_reference(::Val{:authoryear}, entry)
return "$authors ($year). <i>$title</i>. $(linkify(published_in, link))."
end


function format_bibliography_reference(::Val{:alpha}, entry)
return format_bibliography_reference(:numeric, entry)
end


function format_bibliography_reference(::AlphaStyle, entry)
return format_bibliography_reference(:numeric, entry)
end


"""Format the label for an entry in a `@bibliography` block.
```julia
Expand Down Expand Up @@ -125,6 +131,15 @@ function format_bibliography_label(
end


function format_bibliography_label(
alpha::AlphaStyle,
entry,
citations::OrderedDict{String,Int64}
)
return "[$(alpha.label_for_key[entry.id])]"
end


"""Identify the type of HTML list associated with a bibliographic style.
```julia
Expand All @@ -143,6 +158,7 @@ bib_html_list_style(style::Symbol) = bib_html_list_style(Val(style))
bib_html_list_style(::Val{:numeric}) = :dl
bib_html_list_style(::Val{:authoryear}) = :ul
bib_html_list_style(::Val{:alpha}) = :dl
bib_html_list_style(::AlphaStyle) = :dl


"""Identify the sorting associated with a bibliographic style.
Expand All @@ -159,6 +175,7 @@ bib_sorting(style::Symbol) = bib_sorting(Val(style))
bib_sorting(::Val{:numeric}) = :citation
bib_sorting(::Val{:authoryear}) = :nyt
bib_sorting(::Val{:alpha}) = :nyt
bib_sorting(::AlphaStyle) = :nyt


function parse_bibliography_block(block, doc, page)
Expand Down Expand Up @@ -215,6 +232,10 @@ function Selectors.runner(::Type{BibliographyBlock}, x, page, doc)
# Local styles are for the Gallery in the documentation only.
@assert fields[:Style] isa Symbol
style = fields[:Style]
if style == :alpha
# same automatic upgrade as in CitationsBibliography
style = AlphaStyle(bib.entries)
end
@debug "Overriding local style with $repr($style)"
end

Expand Down
13 changes: 10 additions & 3 deletions src/citations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,18 +320,25 @@ end


function format_citation(
style::Val{:alpha},
style::Union{Val{:alpha},AlphaStyle},
entry,
citations; # OrderedDict{String,Int64}
note::Union{Nothing,String}=nothing,
cite_cmd::Symbol=:cite,
capitalize::Bool=false,
starred::Bool=false
)
if style == Val(:alpha)
# dumb style
label = alpha_label(entry)
else
# smart style
label = style.label_for_key[entry.id]
end
if isnothing(note)
link_text = "[$(alpha_label(entry))]"
link_text = "[$label]"
else
link_text = "[$(alpha_label(entry)), $note]"
link_text = "[$label, $note]"
end
if cite_cmd [:citealt, :citealp, :citenum]
@warn "$cite_cmd citations are not supported in the default styles."
Expand Down
Loading

0 comments on commit 45c694d

Please sign in to comment.