diff --git a/Makefile b/Makefile
index d01194b..7426a40 100644
--- a/Makefile
+++ b/Makefile
@@ -36,6 +36,10 @@ docs: test/Manifest.toml ## Build the documentation
$(JULIA) --project=test docs/make.jl
@echo "Done. Consider using 'make devrepl'"
+pdf: test/Manifest.toml ## Build the documentation in PDF format
+ $(JULIA) --project=test docs/makepdf.jl
+ @echo "Done. Consider using 'make devrepl'"
+
servedocs: test/Manifest.toml ## Build (auto-rebuild) and serve documentation at PORT=8000
$(JULIA) --project=test -e 'include("devrepl.jl"); servedocs(port=$(PORT), verbose=true)'
diff --git a/NEWS.md b/NEWS.md
index 41769fe..4f7d705 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* In general (depending on the style and citation syntax), citation links may now render to arbitrarily complex expressions.
* Citation comments can now have inline markdown elements, e.g., `[GoerzQ2022; definition of $J$ in section *Running costs*](@cite)`
* When running in non-strict mode, missing bibliographic references (either because the `.bib` file does not contain an entry with a specific BibTeX key, or because of a missing `@biblography` block) are now handled similarly to missing references in LaTeX: They will show as (unlinked) question marks.
+* Support for bibliographies in PDFs generate via LaTeX (`format=Documenter.LaTeX()`). Citations and references are rendered exactly as in the HTML version. Specifically, the support does not depend on `bibtex`/`biblatex` and supports any style (including custom styles). [[#18][]]
+* Functions `DocumenterCitations.set_latex_options` and `DocumenterCitations.reset_latex_options` to tweak the rendering of bibliographies in PDFs.
+
### Internal Changes
@@ -31,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Exposed the internal function `format_labeled_bibliography_reference` that implements `format_bibliography_reference` for the built-in styles `:numeric` and `:alpha`.
* Exposed the internal function `format_authoryear_bibliography_reference` that implements `format_bibliography_reference` for the built-in style `:authoryear:`.
* The example custom styles `:enumauthoryear` and `:keylabels` have been rewritten using the above internal functions, illustrating that custom styles will usually not have to rely on the undocumented and even more internal functions like `format_names` and `tex2unicode`.
+* Any `@bibliography` block is now internally expanded into an internal `BibliographyNode` instead of a raw HTML node. This `BibliographyNode` can then be translated into the desired output format by `Documenter.HTMLWriter` or `Documenter.LaTeXWriter`. This is how support for bibliographies with `format=Documenter.LaTeX()` can be achieved.
+* The routine `format_bibliography_reference` must now return a markdown string instead of an HTML string.
## [Version 1.2.1][1.2.1] - 2023-09-22
@@ -118,6 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#31]: https://github.com/JuliaDocs/DocumenterCitations.jl/pull/31
[#20]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/20
[#19]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/19
+[#18]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/18
[#16]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/16
[#14]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/14
[#6]: https://github.com/JuliaDocs/DocumenterCitations.jl/issues/6
diff --git a/docs/makepdf.jl b/docs/makepdf.jl
index 748ebec..7342200 100644
--- a/docs/makepdf.jl
+++ b/docs/makepdf.jl
@@ -1,2 +1,46 @@
-# In a future version, this script will generate a PDF version of the package
-# documentation
+using DocumenterCitations
+using Documenter
+using Pkg
+
+
+# Note: Set environment variable `DOCUMENTER_LATEX_DEBUG=1` in order get a copy
+# of the generated tex file (or, add `platform="none"` to the
+# `Documenter.LaTeX` call)
+
+
+PROJECT_TOML = Pkg.TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml"))
+VERSION = PROJECT_TOML["version"]
+NAME = PROJECT_TOML["name"]
+AUTHORS = join(PROJECT_TOML["authors"], ", ") * " and contributors"
+GITHUB = "https://github.com/JuliaDocs/DocumenterCitations.jl"
+
+bib = CitationBibliography(
+ joinpath(@__DIR__, "src", "refs.bib");
+ style=:numeric # default
+)
+
+println("Starting makedocs")
+
+include("custom_styles/enumauthoryear.jl")
+include("custom_styles/keylabels.jl")
+
+withenv("DOCUMENTER_BUILD_PDF" => "1") do
+ makedocs(
+ authors=AUTHORS,
+ linkcheck=true,
+ warnonly=[:linkcheck,],
+ sitename="DocumenterCitations.jl",
+ format=Documenter.LaTeX(; version=VERSION),
+ pages=[
+ "Home" => "index.md",
+ "Syntax" => "syntax.md",
+ "Citation Style Gallery" => "gallery.md",
+ "CSS Styling" => "styling.md",
+ "Internals" => "internals.md",
+ "References" => "references.md",
+ ],
+ plugins=[bib],
+ )
+end
+
+println("Finished makedocs")
diff --git a/docs/src/assets/preamble.tex b/docs/src/assets/preamble.tex
new file mode 100644
index 0000000..ca1908e
--- /dev/null
+++ b/docs/src/assets/preamble.tex
@@ -0,0 +1,36 @@
+\documentclass[oneside]{memoir}
+\usepackage{./documenter}
+\usepackage{./custom}
+
+\renewcommand{\part}[1]{}
+
+
+\AfterPreamble{\hypersetup{
+ pdfauthor={Michael H. Goerz and Contributors},
+ pdftitle={\DocMainTitle{} v\DocVersion{}},
+ pdfsubject={Documentation of Julia package \DocMainTitle{}}
+}}
+
+
+%% Title Page
+\title{%
+ {\HUGE\DocMainTitle{}}\\
+ \vspace{16pt}
+ {\Large version \DocVersion{}}
+}
+\author{Michael H. Goerz and Contributors}
+
+
+\settocdepth{section}
+
+
+%% Main document begin
+\begin{document}
+\frontmatter
+\maketitle
+%\clearpage
+\tableofcontents
+\widowpenalty10000
+\clubpenalty10000
+\raggedright%
+\mainmatter
diff --git a/docs/src/gallery.md b/docs/src/gallery.md
index 0209d81..addec13 100644
--- a/docs/src/gallery.md
+++ b/docs/src/gallery.md
@@ -88,6 +88,10 @@ Style = :alpha
Canonical = false
```
+```@raw latex
+Compared to the HTML version of the documentation, the hanging indent in the above list of references is too small for the longer labels of the \texttt{:alpha} style. This can be remedied by adjusting the \texttt{dl\_hangindent} and \texttt{dl\_labelwidth} parameters with \hyperlinkref{sec:customizing_latex_output}{\texttt{DocumenterCitations.set\_latex\_options}}.
+```
+
Note that the `:alpha` style is able to automatically disambiguate labels:
```@bibliography
@@ -179,3 +183,7 @@ Pages = ["gallery.md"]
Style = :keylabels
Canonical = false
```
+
+```@raw latex
+As with the \texttt{:alpha} style, for \LaTeX{} output, the \texttt{dl\_hangindent} and \texttt{dl\_labelwidth} parameters should be adjusted with \hyperlinkref{sec:customizing_latex_output}{\texttt{DocumenterCitations.set\_latex\_options}} to obtain a more suitable hanging indent that matches the HTML version of this documentation.
+```
diff --git a/docs/src/index.md b/docs/src/index.md
index 011b3d1..32b6abe 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -10,7 +10,20 @@ github_badge = "[![Github](https://img.shields.io/badge/JuliaDocs-DocumenterCita
version_badge = "![v$VERSION](https://img.shields.io/badge/version-v$(replace("$VERSION", "-" => "--"))-green.svg)"
-Markdown.parse("$github_badge $version_badge")
+if get(ENV, "DOCUMENTER_BUILD_PDF", "") == ""
+ Markdown.parse("$github_badge $version_badge")
+else
+ Markdown.parse("""
+ -----
+
+ On Github: [JuliaDocs/DocumenterCitations.jl](https://github.com/JuliaDocs/DocumenterCitations.jl)
+
+ Version: $VERSION
+
+ -----
+
+ """)
+end
```
[DocumenterCitations.jl](https://github.com/JuliaDocs/DocumenterCitations.jl#readme) uses [Bibliography.jl](https://github.com/Humans-of-Julia/Bibliography.jl) to add support for BibTeX citations in documentation pages generated by [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl).
@@ -104,6 +117,10 @@ makedocs(;
deploydocs(; repo="github.com/JuliaDocs/DocumenterCitations.jl.git")
```
+Bibliographies are also supported in [PDFs generated via LaTeX](https://documenter.juliadocs.org/stable/man/other-formats/#pdf-output). All that is required is to replace `format=Documenter.HTML(…)` in the above code with `format=Documenter.LaTeX()`. See [`docs/makepdf.jl`](https://github.com/JuliaDocs/DocumenterCitations.jl/blob/master/docs/makepdf.jl) for an example. The resulting PDF files for the `DocumenterCitations` package are available as attachments to the [Releases](https://github.com/JuliaDocs/DocumenterCitations.jl/releases).
+
+The rendering of the documentation may be fine-tuned using the [`DocumenterCitations.set_latex_options`](@ref) function. Note that the bibliography in LaTeX is directly rendered for the [different styles](@ref gallery) from the same internal representation as the HTML version. In particular, `bibtex`/`biblatex` is not involved in producing the PDF.
+
## How to cite references in your documentation
diff --git a/docs/src/internals.md b/docs/src/internals.md
index dba2ee0..5c8d301 100644
--- a/docs/src/internals.md
+++ b/docs/src/internals.md
@@ -51,6 +51,17 @@ format_labeled_bibliography_reference
format_authoryear_bibliography_reference
```
+### Customizing LaTeX output
+
+```@raw latex
+\hypertarget{sec:customizing_latex_output}{}
+```
+
+```@docs
+set_latex_options
+reset_latex_options
+```
+
### Citation links
The standard citation links described in [Syntax](@ref) are internally parsed into the [`DocumenterCitations.CitationLink`](@ref) data structure:
diff --git a/src/DocumenterCitations.jl b/src/DocumenterCitations.jl
index 94378c2..d724361 100644
--- a/src/DocumenterCitations.jl
+++ b/src/DocumenterCitations.jl
@@ -186,6 +186,8 @@ include("md_ast.jl")
include("citation_link.jl")
include("collect_citations.jl")
include("expand_citations.jl")
+include("latex_options.jl")
+include("bibliography_node.jl")
include("expand_bibliography.jl")
include("formatting.jl")
include("labeled_styles_utils.jl")
@@ -202,6 +204,7 @@ function __init__()
push!(Documenter.ERROR_NAMES, errname)
end
end
+ reset_latex_options()
end
diff --git a/src/bibliography_node.jl b/src/bibliography_node.jl
new file mode 100644
index 0000000..78b3259
--- /dev/null
+++ b/src/bibliography_node.jl
@@ -0,0 +1,190 @@
+"""Representation of a reference within a [`BibliographyNode`}(@ref).
+
+# Properties
+
+* `anchor_key`: the anchor key for the link target as a string (generally the
+ BibTeX key), or `nothing` if the item is in a non-canonical bibliography
+ block.
+* `label`: `MarkdownAST.Node` for the label of the entry. Usually a simple
+ `Text` node, as labels in the default styles do not have inline formatting.
+ or `nothing` if the style does not use labels
+ the rendered bibliography.
+* `reference`: `MarkdownAST.Node` for the paragraph for the fully rendered
+ reference.
+"""
+struct BibliographyItem
+ anchor_key::Union{Nothing,String}
+ label::Union{Nothing,MarkdownAST.Node{Nothing}}
+ reference::MarkdownAST.Node{Nothing}
+end
+
+
+"""Node in `MarkdownAST` corresponding to a `@bibliography` block.
+
+# Properties
+
+* `list_style`: One of `:dl`, `:ul`, `:ol`, cf. [`bib_html_list_style`](@ref)
+* `canonical`: Whether or not the references in the `@bibliography` block are
+ link targets.
+* `items`: A list of [`BibliographyItem`](@ref) objects, one for each reference
+in the block
+"""
+struct BibliographyNode <: Documenter.AbstractDocumenterBlock
+ list_style::Symbol # one of :dl, :ul, :ol
+ canonical::Bool
+ items::Vector{BibliographyItem}
+ function BibliographyNode(list_style, canonical, items)
+ if list_style in (:dl, :ul, :ol)
+ new(list_style, canonical, items)
+ else
+ throw(
+ ArgumentError(
+ "`list_style` must be one of `:dl`, `:ul`, or `:ol`, not `$(repr(list_style))`"
+ )
+ )
+ end
+ end
+end
+
+
+function Documenter.MDFlatten.mdflatten(io, ::MarkdownAST.Node, b::BibliographyNode)
+ for item in b.items
+ Documenter.MDFlatten.mdflatten(io, item.reference)
+ print(io, "\n\n\n\n")
+ end
+end
+
+
+function Documenter.HTMLWriter.domify(
+ dctx::Documenter.HTMLWriter.DCtx,
+ node::Documenter.Node,
+ bibliography::BibliographyNode
+)
+ @assert node.element === bibliography
+ return domify_bib(dctx, bibliography)
+end
+
+
+function domify_bib(dctx::Documenter.HTMLWriter.DCtx, bibliography::BibliographyNode)
+ Documenter.DOM.@tags dl ul ol li div dt dd
+ list_tag = dl
+ if bibliography.list_style == :ul
+ list_tag = ul
+ elseif bibliography.list_style == :ol
+ list_tag = ol
+ end
+ html_list = list_tag()
+ for item in bibliography.items
+ anchor_id = isnothing(item.anchor_key) ? "" : "#$(item.anchor_key)"
+ html_reference = Documenter.HTMLWriter.domify(dctx, item.reference.children)
+ if bibliography.list_style == :dl
+ html_label = Documenter.HTMLWriter.domify(dctx, item.label.children)
+ push!(html_list.nodes, dt(html_label))
+ push!(html_list.nodes, dd(div[anchor_id](html_reference)))
+ else
+ push!(html_list.nodes, li(div[anchor_id](html_reference)))
+ end
+ end
+ class = ".citation"
+ if bibliography.canonical
+ class *= ".canonical"
+ else
+ class *= ".noncanonical"
+ end
+ return div[class](html_list)
+end
+
+
+_hash(x) = string(hash(x))
+
+
+function _wrapblock(f, io, env)
+ if !isnothing(env)
+ println(io, "\\begin{", env, "}")
+ end
+ f()
+ if !isnothing(env)
+ println(io, "\\end{", env, "}")
+ end
+end
+
+
+function _labelbox(f, io; width="0in")
+ local width_val
+ try
+ width_val = parse(Float64, string(match(r"[\d.]+", width).match))
+ catch
+ throw(ArgumentError("width $(repr(width)) must be a valid LaTeX width"))
+ end
+ if width_val == 0.0
+ # do not use a makebox if width is zero
+ print(io, "{")
+ f()
+ print(io, "} ")
+ return
+ end
+ print(
+ io,
+ "\\makebox[{\\ifdim$(width)<\\dimexpr\\width+1ex\\relax\\dimexpr\\width+1ex\\relax\\else$(width)\\fi}][l]{"
+ )
+ f()
+ print(io, "}")
+end
+
+
+function Documenter.LaTeXWriter.latex(
+ lctx::Documenter.LaTeXWriter.Context,
+ node::MarkdownAST.Node,
+ bibliography::BibliographyNode
+)
+
+ if bibliography.list_style == :ol
+ texenv = "enumerate"
+ elseif bibliography.list_style == :ul
+ if _LATEX_OPTIONS[:ul_as_hanging]
+ texenv = nothing
+ else
+ texenv = "itemize"
+ end
+ else
+ @assert bibliography.list_style == :dl
+ # We emulate a definition list manually with hangindent and labelwidth
+ texenv = nothing
+ end
+
+ io = lctx.io
+
+ function tex_item(n, item)
+ if bibliography.list_style == :ul
+ if _LATEX_OPTIONS[:ul_as_hanging]
+ print(io, "\\hangindent=$(_LATEX_OPTIONS[:ul_hangindent]) ")
+ else
+ print(io, "\\item ")
+ end
+ elseif bibliography.list_style == :ol # enumerate
+ print(io, "\\item ")
+ else
+ @assert bibliography.list_style == :dl
+ print(io, "\\hangindent=$(_LATEX_OPTIONS[:dl_hangindent]) {")
+ _labelbox(io; width=_LATEX_OPTIONS[:dl_labelwidth]) do
+ Documenter.LaTeXWriter.latex(lctx, item.label.children)
+ end
+ print(io, "}")
+ end
+ end
+
+ println(io, "{$(_LATEX_OPTIONS[:bib_blockformat])% @bibliography\n")
+ _wrapblock(io, texenv) do
+ for (n, item) in enumerate(bibliography.items)
+ tex_item(n, item)
+ if !isnothing(item.anchor_key)
+ id = _hash(item.anchor_key)
+ print(io, "\\hypertarget{", id, "}{}")
+ end
+ Documenter.LaTeXWriter.latex(lctx, item.reference.children)
+ print(io, "\n\n")
+ end
+ end
+ println(io, "}% end @bibliography")
+
+end
diff --git a/src/expand_bibliography.jl b/src/expand_bibliography.jl
index 1ae79bd..f434d19 100644
--- a/src/expand_bibliography.jl
+++ b/src/expand_bibliography.jl
@@ -86,10 +86,10 @@ end
"""Format the full reference for an entry in a `@bibliography` block.
```julia
-format_bibliography_reference(style, entry)
+mdstr = format_bibliography_reference(style, entry)
```
-produces an HTML string for the full reference of a
+produces a markdown string for the full reference of a
[`Bibliography.Entry`](https://humans-of-julia.github.io/Bibliography.jl/stable/internal/#BibInternal.Entry).
For the default `style=:numeric`, the result is formatted like in
[REVTeX](https://www.ctan.org/tex-archive/macros/latex/contrib/revtex/auguide)
@@ -108,10 +108,11 @@ end
"""Format the label for an entry in a `@bibliography` block.
```julia
-format_bibliography_label(style, entry, citations)
+mdstr = format_bibliography_label(style, entry, citations)
```
-produces a string for the label in the bibliography for the given
+produces a plain text (technically, markdown) string for the label in the
+bibliography for the given
[`Bibliography.Entry`](https://humans-of-julia.github.io/Bibliography.jl/stable/internal/#BibInternal.Entry).
The `citations` argument is a dict that maps citation keys (`entry.id`) to the
order in which citations appear in the documentation, i.e., a numeric citation
@@ -273,10 +274,9 @@ function expand_bibliography(node::MarkdownAST.Node, meta, page, doc)
"Must be one of $(repr(allowed_tags))"
)
end
- html = """
<$tag>"""
- if fields[:Canonical]
- html = """
<$tag>"""
- end
+
+ bibliography_node = BibliographyNode(tag, fields[:Canonical], BibliographyItem[])
+
anchors = bib.anchor_map
entries_to_show = OrderedDict{String,Bibliography.Entry}(
key => bib.entries[key] for key in keys_to_show
@@ -290,6 +290,7 @@ function expand_bibliography(node::MarkdownAST.Node, meta, page, doc)
end
for (key, entry) in entries_to_show
if fields[:Canonical]
+ anchor_key = key
# Add anchor that citations can link to from anywhere in the docs.
if Documenter.anchor_exists(anchors, key)
# Skip entries that already have a canonical bib entry
@@ -303,26 +304,31 @@ function expand_bibliography(node::MarkdownAST.Node, meta, page, doc)
Documenter.anchor_add!(anchors, entry, key, page.build)
end
else
+ anchor_key = nothing
# For non-canonical bibliographies, no anchors are generated, and
# we don't skip any keys. That is, multiple non-canonical
# bibliographies may contain entries for the same keys.
end
@debug "Expanding bibliography entry: $key."
+ reference = MarkdownAST.@ast MarkdownAST.Paragraph()
+ append!(
+ reference.children,
+ Documenter.mdparse(format_bibliography_reference(style, entry); mode=:span)
+ )
if tag == :dl
- html *= """
-
$(format_bibliography_label(style, entry, citations))
-
- $(format_bibliography_reference(style, entry))
- """
+ label = MarkdownAST.@ast MarkdownAST.Paragraph()
+ append!(
+ label.children,
+ Documenter.mdparse(
+ format_bibliography_label(style, entry, citations);
+ mode=:span
+ )
+ )
else
- html *= """
-
- $(format_bibliography_reference(style, entry))
- """
+ label = nothing
end
+ push!(bibliography_node.items, BibliographyItem(anchor_key, label, reference))
end
- html *= "\n$tag>
"
-
- node.element = Documenter.RawNode(:html, html)
+ node.element = bibliography_node
end
diff --git a/src/formatting.jl b/src/formatting.jl
index a099225..935ad7e 100644
--- a/src/formatting.jl
+++ b/src/formatting.jl
@@ -55,7 +55,7 @@ function linkify(text, link)
if isempty(link)
return text
else
- return "
$text"
+ return "[$text]($link)"
end
end
@@ -140,7 +140,8 @@ function format_names(
names=:full,
and=true,
et_al=0,
- et_al_text="et al."
+ et_al_text="*et al.*",
+ nbsp="\u00A0", # non-breaking space
)
# forces the names to be editors' name if the entry are Proceedings
if !editors && entry.type ∈ ["proceedings"]
@@ -168,14 +169,14 @@ function format_names(
formatted_names = String[]
for name in entry_names
last_parts = [name.particle, name.last, name.junior]
- last = join(filter(!isempty, last_parts), " ")
+ last = join(filter(!isempty, last_parts), nbsp)
first_parts = [_initial(name.first), _initial(name.middle)]
- first = join(filter(!isempty, first_parts), " ")
- push!(formatted_names, "$last, $first")
+ first = join(filter(!isempty, first_parts), nbsp)
+ push!(formatted_names, "$last,$nbsp$first")
end
else
formatted_names = map(parts) do s
- return join(filter(!isempty, s), " ")
+ return join(filter(!isempty, s), nbsp)
end
end
@@ -205,12 +206,12 @@ function format_names(
end
-function format_published_in(entry; include_date=true)
+function format_published_in(entry; include_date=true, nbsp="\u00A0")
str = ""
if entry.type == "article"
- str *= entry.in.journal
+ str *= replace(entry.in.journal, " " => nbsp) # non-breaking space
if !isempty(entry.in.volume)
- str *= "
$(entry.in.volume)"
+ str *= " **$(entry.in.volume)**"
end
if !isempty(entry.in.pages)
str *= ", $(entry.in.pages)"
@@ -336,35 +337,42 @@ function format_eprint(entry)
end
-# Not a safe tag stripper (you can't process HTML with regexes), but we
-# generated the input `html` being passed to this function, so we have some
-# control over not having pathological HTML here. Also, at worst we end up with
-# punctuation that isn't quite perfect.
-_strip_tags(html) = replace(html, r"<[^>]*>" => "")
+function _strip_md_formatting(mdstr)
+ try
+ ast = Documenter.mdparse(mdstr; mode=:single)
+ buffer = IOBuffer()
+ Documenter.MDFlatten.mdflatten(buffer, ast)
+ return String(take!(buffer))
+ catch exc
+ @warn "Cannot strip formatting from $(repr(mdstr))" exc
+ return strip(mdstr)
+ end
+end
+
# Intelligently join the parts with appropriate punctuation
function _join_bib_parts(parts)
- html = ""
+ mdstr = ""
if length(parts) == 0
- html = ""
+ mdstr = ""
elseif length(parts) == 1
- html = strip(parts[1])
- if !endswith(_strip_tags(html), r"[.!?]")
- html *= "."
+ mdstr = strip(parts[1])
+ if !endswith(_strip_md_formatting(mdstr), r"[.!?]")
+ mdstr *= "."
end
else
- html = strip(parts[1])
+ mdstr = strip(parts[1])
rest = _join_bib_parts(parts[2:end])
- rest_text = _strip_tags(rest)
- if endswith(_strip_tags(html), r"[,;.!?]") || startswith(rest_text, "(")
- html *= " " * rest
+ rest_text = _strip_md_formatting(rest)
+ if endswith(_strip_md_formatting(mdstr), r"[,;.!?]") || startswith(rest_text, "(")
+ mdstr *= " " * rest
else
if uppercase(rest_text[1]) == rest_text[1]
- html *= ". " * rest
+ mdstr *= ". " * rest
else
- html *= ", " * rest
+ mdstr *= ", " * rest
end
end
end
- return html
+ return mdstr
end
diff --git a/src/labeled_styles_utils.jl b/src/labeled_styles_utils.jl
index 7a147b5..97b1563 100644
--- a/src/labeled_styles_utils.jl
+++ b/src/labeled_styles_utils.jl
@@ -218,7 +218,7 @@ function citation_label end # implemented by various styles
"""Format a bibliography reference as in a "labeled" style.
```julia
-html = format_labeled_bibliography_reference(style, entry; namesfmt=:last)
+mdstr = format_labeled_bibliography_reference(style, entry; namesfmt=:last)
```
# Options
@@ -229,7 +229,7 @@ function format_labeled_bibliography_reference(style, entry; namesfmt=:last)
authors = format_names(entry; names=namesfmt) |> tex2unicode
title = xtitle(entry)
if !isempty(title)
- title = "
" * tex2unicode(title) * ""
+ title = "*" * tex2unicode(title) * "*"
end
linked_title = linkify(title, entry.access.url)
published_in = linkify(tex2unicode(format_published_in(entry)), _doi_link(entry))
diff --git a/src/latex_options.jl b/src/latex_options.jl
new file mode 100644
index 0000000..f4c7c8b
--- /dev/null
+++ b/src/latex_options.jl
@@ -0,0 +1,98 @@
+const _LATEX_OPTIONS = Dict{Symbol,Any}()
+# _LATEX_OPTIONS are initialized with call to reset_latex_options() __init__()
+
+
+@doc raw"""
+Reset the options for how bibliographies are written in LaTeX.
+
+```julia
+DocumenterCitations.reset_latex_options()
+```
+
+is equivalent to the following call to [`set_latex_options`](@ref):
+
+```julia
+set_latex_options(;
+ ul_as_hanging=true,
+ ul_hangindent="0.33in",
+ dl_hangindent="0.33in",
+ dl_labelwidth="0.33in",
+ bib_blockformat="\raggedright",
+)
+```
+"""
+function reset_latex_options()
+ global _LATEX_OPTIONS
+ _LATEX_OPTIONS[:ul_as_hanging] = true
+ _LATEX_OPTIONS[:ul_hangindent] = "0.33in"
+ _LATEX_OPTIONS[:dl_hangindent] = "0.33in"
+ _LATEX_OPTIONS[:dl_labelwidth] = "0.33in"
+ _LATEX_OPTIONS[:bib_blockformat] = "\\raggedright"
+end
+
+
+@doc raw"""
+Set options for how bibliographies are written via `Documenter.LaTeXWriter`.
+
+```julia
+DocumenterCitations.set_latex_options(; options...)
+```
+
+Valid options that can be passed as keyword arguments are:
+
+* `ul_as_hanging`: If `true` (default), format unordered bibliography lists
+ (`:ul` returned by [`DocumenterCitations.bib_html_list_style`](@ref)) as a
+ list of paragraphs with hanging indent. This matches the recommended CSS
+ styling for HTML `:ul` bibliographies, see [CSS Styling](@ref). If `false`,
+ the bibliography will be rendered as a standard bulleted list.
+* `ul_hangindent`: If `ul_as_hanging=true`, the amount of hanging indent. Must
+ be a string that specifies a valid
+ [LaTeX length](https://www.overleaf.com/learn/latex/Lengths_in_LaTeX),
+ e.g., `"0.33in"`
+* `dl_hangindent` : Bibliographies that should render as "definition lists"
+ (`:dl` returned by [`DocumenterCitations.bib_html_list_style`](@ref)) are
+ emulated as a list of paragraphs with a fixed label width and hanging indent.
+ The amount of hanging indent is specified with `dl_hangindent`, cf.
+ `ul_hangindent`.
+* `dl_labelwidth` : The minimum width to use for the "label" in a bibliography
+ rendered in the `:dl` style.
+* `bib_blockformat`: A LaTeX format command to apply for a bibliography block.
+ Defaults to `"\raggedright"`, which avoids hyphenation within the
+ bibliography. If set to an empty string, let LaTeX decide the default, which
+ will generally result in fully justified text, with hyphenation.
+
+These should be considered experimental and not part of the the stable API.
+
+Options that are not specified remain unchanged from the defaults, respectively
+a previous call to `set_latex_options`.
+
+For bibliography blocks rendered in a `:dl` style, setting `dl_hangindent` and
+`dl_labelwidth` to the same value (slightly larger than the width of the longest
+label) produces results similar to the recommended styling in HTML, see
+[CSS Styling](@ref). For very long citation labels, it may look better to have
+a smaller `dl_hangindent`.
+
+Throws an `ArgumentError` if called with invalid options.
+
+The defaults can be reset with
+[`DocumenterCitations.reset_latex_options`](@ref).
+"""
+function set_latex_options(; reset=false, kwargs...)
+ global _LATEX_OPTIONS
+ for (key, val) in kwargs
+ if haskey(_LATEX_OPTIONS, key)
+ required_type = typeof(_LATEX_OPTIONS[key])
+ if typeof(val) == required_type
+ _LATEX_OPTIONS[key] = val
+ else
+ throw(
+ ArgumentError(
+ "`$(repr(val))` for option $key in set_latex_options must be of type $(required_type), not $(typeof(val))"
+ )
+ )
+ end
+ else
+ throw(ArgumentError("$key is not a valid option in set_latex_options."))
+ end
+ end
+end
diff --git a/src/styles/authoryear.jl b/src/styles/authoryear.jl
index b0307db..1c87bf3 100644
--- a/src/styles/authoryear.jl
+++ b/src/styles/authoryear.jl
@@ -110,7 +110,7 @@ end
"""Format a bibliography reference as for the `:authoryear` style.
```julia
-html = format_authoryear_bibliography_reference(
+mdstr = format_authoryear_bibliography_reference(
style, entry; namesfmt=:lastfirst, empty_names="—"
)
```
@@ -136,7 +136,7 @@ function format_authoryear_bibliography_reference(
end
title = xtitle(entry)
if !isempty(title)
- title = "
" * tex2unicode(title) * ""
+ title = "*" * tex2unicode(title) * "*"
end
linked_title = linkify(title, entry.access.url)
published_in = linkify(
diff --git a/test/runtests.jl b/test/runtests.jl
index a1b86fc..94cf695 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -56,6 +56,11 @@ using DocumenterCitations
include("test_doctest.jl")
end
+ println("\n* latex rendering (test_latex_rendering.jl)")
+ @time @safetestset "latex_rendering" begin
+ include("test_latex_rendering.jl")
+ end
+
println("\n* test undefined citations (test_undefined_citations.jl):")
@time @safetestset "undefined_citations" begin
include("test_undefined_citations.jl")
diff --git a/test/test_collect_from_docstrings.jl b/test/test_collect_from_docstrings.jl
index a00792c..a49f28d 100644
--- a/test/test_collect_from_docstrings.jl
+++ b/test/test_collect_from_docstrings.jl
@@ -25,7 +25,8 @@ include("run_makedocs.jl")
index_outfile = joinpath(dir, "build", "index.html")
@test isfile(index_outfile)
html = read(index_outfile, String)
- @test occursin("citing Ref. [
1]", html)
+ nbsp = "\u00A0" # non-breaking space
+ @test occursin("citing Ref.$(nbsp)[
1]", html)
ref_outfile = joinpath(dir, "build", "references", "index.html")
@test isfile(ref_outfile)
diff --git a/test/test_formatting.jl b/test/test_formatting.jl
index 6ed90c8..2f456d4 100644
--- a/test/test_formatting.jl
+++ b/test/test_formatting.jl
@@ -8,7 +8,9 @@ import DocumenterCitations:
alpha_label,
format_citation,
format_bibliography_reference,
- CitationLink
+ CitationLink,
+ _join_bib_parts,
+ _strip_md_formatting
using IOCapture: IOCapture
@testset "text2unicode" begin
@@ -70,27 +72,66 @@ end
end
+@testset "strip md formatting" begin
+ @Test _strip_md_formatting("[arXiv](https://arxiv.org)") == "arXiv"
+ @Test _strip_md_formatting("*Title*. [arXiv](https://arxiv.org)") == "Title. arXiv"
+ @Test _strip_md_formatting("Text with ``x^2`` math") == "Text with x^2 math"
+ @Test _strip_md_formatting("*italics* and **bold**") == "italics and bold"
+ c = IOCapture.capture() do
+ paragraphs = "Multiple\n\nparagraphs\n\nare\n\nnot allowed"
+ @assert _strip_md_formatting(paragraphs) == paragraphs
+ end
+ @test contains(c.output, "Cannot strip formatting")
+end
+
+
+@testset "join_bib_parts" begin
+ @Test _join_bib_parts(["Title", "(2023)"]) == "Title (2023)."
+ @Test _join_bib_parts(["[Title](https://www.aps.org)", "(2023)"]) ==
+ "[Title](https://www.aps.org) (2023)."
+ @Test _join_bib_parts(["Title", "arXiv"]) == "Title, arXiv."
+ @Test _join_bib_parts(["[Title](https://www.aps.org)", "arXiv"]) ==
+ "[Title](https://www.aps.org), arXiv."
+ @Test _join_bib_parts(["Title.", "arXiv"]) == "Title. arXiv."
+ @Test _join_bib_parts(["Title.", "[arXiv](https://arxiv.org)"]) ==
+ "Title. [arXiv](https://arxiv.org)."
+ @Test _join_bib_parts(["Title", "(2023)", "arXiv"]) == "Title (2023), arXiv."
+ @Test _join_bib_parts(["Title", "(2023)", "Special issue."]) ==
+ "Title (2023). Special issue."
+ @Test _join_bib_parts(["Title", "``x^2``"]) == "Title, ``x^2``."
+ @Test _join_bib_parts(["Title", "``X^2``"]) == "Title. ``X^2``."
+ @Test _join_bib_parts(["*Title*", "``x^2``"]) == "*Title*, ``x^2``."
+ @Test _join_bib_parts(["*Title*", "``X^2``"]) == "*Title*. ``X^2``."
+ c = IOCapture.capture() do
+ paragraphs = "Multiple\n\nparagraphs\n\nare\n\nnot allowed"
+ @assert _join_bib_parts(["*Title*", paragraphs]) == "*Title*. $paragraphs."
+ end
+ @test contains(c.output, "Cannot strip formatting")
+end
+
+
@testset "format_bibliography_reference(:numeric)" begin
bib = CitationBibliography(joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),)
- html(key) = format_bibliography_reference(Val(:numeric), bib.entries[key])
- @Test html("GoerzJPB2011") ==
- "M. H. Goerz, T. Calarco and C. P. Koch.
The quantum speed limit of optimal controlled phasegates for trapped neutral atoms.
J. Phys. B 44, 154011 (2011),
arXiv:1103.6050. Special issue on quantum control theory for coherence and information dynamics."
- @Test html("Luc-KoenigEPJD2004") ==
- "E. Luc-Koenig, M. Vatasescu and F. Masnou-Seeuws.
Optimizing the photoassociation of cold atoms by use of chirped laser pulses.
Eur. Phys. J. D 31, 239 (2004),
arXiv:physics/0407112 [physics.atm-clus]."
- @Test html("GoerzNPJQI2017") ==
- "M. H. Goerz, F. Motzoi, K. B. Whaley and C. P. Koch.
Charting the circuit QED design landscape using optimal control theory,
npj Quantum Inf 3, 37 (2017)."
- @Test html("Wilhelm2003.10132") ==
- "F. K. Wilhelm, S. Kirchhoff, S. Machnes, N. Wittler and D. Sugny.
An introduction into optimal control for quantum technologies,
arXiv:2003.10132 (2020)."
- @Test html("Evans1983") ==
- "L. C. Evans.
An Introduction to Mathematical Optimal Control Theory (1983). Lecture Notes, University of California, Berkeley."
- @Test html("Giles2008b") ==
- "M. B. Giles.
An extended collection of matrix derivative results for forward and reverse mode automatic differentiation. Technical Report NA-08-01, Oxford University Computing Laboratory (2008)."
- @Test html("QCRoadmap") ==
- "
Quantum Computation Roadmap (2004). Version 2.0; April 2, 2004."
- @Test html("TedRyd") ==
- "T. Corcovilos and D. S. Weiss.
Rydberg Calculations. Private communication."
- @Test html("jax") ==
- "J. Bradbury, R. Frostig, P. Hawkins, M. J. Johnson, C. Leary, D. Maclaurin, G. Necula, A. Paszke, J. VanderPlas, S. Wanderman-Milne and Q. Zhang.
JAX: composable transformations of Python+NumPy programs."
+ md(key) = format_bibliography_reference(Val(:numeric), bib.entries[key])
+ # Note: the test strings below contain nonbreaking spaces (" " = "\u00A0")
+ @Test md("GoerzJPB2011") ==
+ "M. H. Goerz, T. Calarco and C. P. Koch. *The quantum speed limit of optimal controlled phasegates for trapped neutral atoms*. [J. Phys. B **44**, 154011 (2011)](https://doi.org/10.1088/0953-4075/44/15/154011), [arXiv:1103.6050](https://arxiv.org/abs/1103.6050). Special issue on quantum control theory for coherence and information dynamics."
+ @Test md("Luc-KoenigEPJD2004") ==
+ "E. Luc-Koenig, M. Vatasescu and F. Masnou-Seeuws. *Optimizing the photoassociation of cold atoms by use of chirped laser pulses*. [Eur. Phys. J. D **31**, 239 (2004)](https://doi.org/10.1140/epjd/e2004-00161-8), [arXiv:physics/0407112 [physics.atm-clus]](https://arxiv.org/abs/physics/0407112)."
+ @Test md("GoerzNPJQI2017") ==
+ "M. H. Goerz, F. Motzoi, K. B. Whaley and C. P. Koch. *Charting the circuit QED design landscape using optimal control theory*, [npj Quantum Inf **3**, 37 (2017)](https://doi.org/10.1038/s41534-017-0036-0)."
+ @Test md("Wilhelm2003.10132") ==
+ "F. K. Wilhelm, S. Kirchhoff, S. Machnes, N. Wittler and D. Sugny. *An introduction into optimal control for quantum technologies*, [arXiv:2003.10132 (2020)](https://doi.org/10.48550/ARXIV.2003.10132)."
+ @Test md("Evans1983") ==
+ "L. C. Evans. [*An Introduction to Mathematical Optimal Control Theory*](https://math.berkeley.edu/~evans/control.course.pdf) (1983). Lecture Notes, University of California, Berkeley."
+ @Test md("Giles2008b") ==
+ "M. B. Giles. [*An extended collection of matrix derivative results for forward and reverse mode automatic differentiation*](https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf). Technical Report NA-08-01, Oxford University Computing Laboratory (2008)."
+ @Test md("QCRoadmap") ==
+ "[*Quantum Computation Roadmap*](http://qist.lanl.gov) (2004). Version 2.0; April 2, 2004."
+ @Test md("TedRyd") ==
+ "T. Corcovilos and D. S. Weiss. *Rydberg Calculations*. Private communication."
+ @Test md("jax") ==
+ "J. Bradbury, R. Frostig, P. Hawkins, M. J. Johnson, C. Leary, D. Maclaurin, G. Necula, A. Paszke, J. VanderPlas, S. Wanderman-Milne and Q. Zhang. [*JAX: composable transformations of Python+NumPy programs*](https://github.com/google/jax)."
end
@@ -98,39 +139,41 @@ end
bib = CitationBibliography(joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),)
bib0 = CitationBibliography(joinpath(splitext(@__FILE__)[1], "preprints.bib"))
merge!(bib.entries, bib0.entries)
- html(key) = format_bibliography_reference(Val(:numeric), bib.entries[key])
- @Test html("LarrouyPRX2020") ==
- "A. Larrouy, S. Patsch, R. Richaud, J.-M. Raimond, M. Brune, C. P. Koch and S. Gleyzes.
Fast Navigation in a Large Hilbert Space Using Quantum Optimal Control.
Phys. Rev. X 10, 021058 (2020).
HAL:hal-02887773."
- @Test html("TuriniciHAL00640217") ==
- "G. Turinici.
Quantum control. HAL:hal-00640217 (2012)."
- @Test html("BrionPhd2004") ==
- "E. Brion.
Contrôle Quantique et Protection de la Cohérence par effet Zénon, Applications à l'Informatique Quantique. Phd thesis, Université Pierre et Marie Curie - Paris VI (2014).
HAL:tel-00007910v2."
- @Test html("KatrukhaNC2017") ==
- "E. A. Katrukha, M. Mikhaylova, H. X. van Brakel, P. M. van Bergen en Henegouwen, A. Akhmanova, C. C. Hoogenraad and L. C. Kapitein.
Probing cytoskeletal modulation of passive and active intracellular dynamics using nanobody-functionalized quantum dots.
Nat. Commun. 8, 14772 (2017),
biorXiv:089284."
- @Test html("NonStandardPreprint") ==
- "M. Tomza, M. H. Goerz, M. Musiał, R. Moszynski and C. P. Koch.
Optimized production of ultracold ground-state molecules: Stabilization employing potentials with ion-pair character and strong spin-orbit coupling.
Phys. Rev. A 86, 043424 (2012), xxx-preprint:1208.4331."
+ md(key) = format_bibliography_reference(Val(:numeric), bib.entries[key])
+ # Note: the test strings below contain nonbreaking spaces (" " = "\u00A0")
+ @Test md("LarrouyPRX2020") ==
+ "A. Larrouy, S. Patsch, R. Richaud, J.-M. Raimond, M. Brune, C. P. Koch and S. Gleyzes. *Fast Navigation in a Large Hilbert Space Using Quantum Optimal Control*. [Phys. Rev. X **10**, 021058 (2020)](https://doi.org/10.1103/physrevx.10.021058). [HAL:hal-02887773](https://hal.science/hal-02887773)."
+ @Test md("TuriniciHAL00640217") ==
+ "G. Turinici. [*Quantum control*](https://hal.science/hal-00640217). HAL:hal-00640217 (2012)."
+ @Test md("BrionPhd2004") ==
+ "E. Brion. *Contrôle Quantique et Protection de la Cohérence par effet Zénon, Applications à l'Informatique Quantique*. Phd thesis, Université Pierre et Marie Curie - Paris VI (2014). [HAL:tel-00007910v2](https://hal.science/tel-00007910v2)."
+ @Test md("KatrukhaNC2017") ==
+ "E. A. Katrukha, M. Mikhaylova, H. X. van Brakel, P. M. van Bergen en Henegouwen, A. Akhmanova, C. C. Hoogenraad and L. C. Kapitein. *Probing cytoskeletal modulation of passive and active intracellular dynamics using nanobody-functionalized quantum dots*. [Nat. Commun. **8**, 14772 (2017)](https://doi.org/10.1038/ncomms14772), [biorXiv:089284](https://www.biorxiv.org/content/10.1101/089284)."
+ @Test md("NonStandardPreprint") ==
+ "M. Tomza, M. H. Goerz, M. Musiał, R. Moszynski and C. P. Koch. *Optimized production of ultracold ground-state molecules: Stabilization employing potentials with ion-pair character and strong spin-orbit coupling*. [Phys. Rev. A **86**, 043424 (2012)](https://doi.org/10.1103/PhysRevA.86.043424), xxx-preprint:1208.4331."
end
@testset "format_bibliography_reference(:authoryear)" begin
bib = CitationBibliography(joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),)
- html(key) = format_bibliography_reference(Val(:authoryear), bib.entries[key])
- @Test html("GoerzJPB2011") ==
- "Goerz, M. H.; Calarco, T. and Koch, C. P. (2011).
The quantum speed limit of optimal controlled phasegates for trapped neutral atoms.
J. Phys. B 44, 154011,
arXiv:1103.6050. Special issue on quantum control theory for coherence and information dynamics."
- @Test html("Luc-KoenigEPJD2004") ==
- "Luc-Koenig, E.; Vatasescu, M. and Masnou-Seeuws, F. (2004).
Optimizing the photoassociation of cold atoms by use of chirped laser pulses.
Eur. Phys. J. D 31, 239,
arXiv:physics/0407112 [physics.atm-clus]."
- @Test html("GoerzNPJQI2017") ==
- "Goerz, M. H.; Motzoi, F.; Whaley, K. B. and Koch, C. P. (2017).
Charting the circuit QED design landscape using optimal control theory,
npj Quantum Inf 3, 37."
- @Test html("Wilhelm2003.10132") ==
- "Wilhelm, F. K.; Kirchhoff, S.; Machnes, S.; Wittler, N. and Sugny, D. (2020).
An introduction into optimal control for quantum technologies,
arXiv:2003.10132."
- @Test html("Evans1983") ==
- "Evans, L. C. (1983).
An Introduction to Mathematical Optimal Control Theory. Lecture Notes, University of California, Berkeley."
- @Test html("Giles2008b") ==
- "Giles, M. B. (2008).
An extended collection of matrix derivative results for forward and reverse mode automatic differentiation. Technical Report NA-08-01, Oxford University Computing Laboratory."
- @Test html("QCRoadmap") ==
- "— (2004).
Quantum Computation Roadmap. Version 2.0; April 2, 2004."
- @Test html("TedRyd") ==
- "Corcovilos, T. and Weiss, D. S.
Rydberg Calculations. Private communication."
- @Test html("jax") ==
- "Bradbury, J.; Frostig, R.; Hawkins, P.; Johnson, M. J.; Leary, C.; Maclaurin, D.; Necula, G.; Paszke, A.; VanderPlas, J.; Wanderman-Milne, S. and Zhang, Q.
JAX: composable transformations of Python+NumPy programs."
+ md(key) = format_bibliography_reference(Val(:authoryear), bib.entries[key])
+ # Note: the test strings below contain nonbreaking spaces (" " = "\u00A0")
+ @Test md("GoerzJPB2011") ==
+ "Goerz, M. H.; Calarco, T. and Koch, C. P. (2011). *The quantum speed limit of optimal controlled phasegates for trapped neutral atoms*. [J. Phys. B **44**, 154011](https://doi.org/10.1088/0953-4075/44/15/154011), [arXiv:1103.6050](https://arxiv.org/abs/1103.6050). Special issue on quantum control theory for coherence and information dynamics."
+ @Test md("Luc-KoenigEPJD2004") ==
+ "Luc-Koenig, E.; Vatasescu, M. and Masnou-Seeuws, F. (2004). *Optimizing the photoassociation of cold atoms by use of chirped laser pulses*. [Eur. Phys. J. D **31**, 239](https://doi.org/10.1140/epjd/e2004-00161-8), [arXiv:physics/0407112 [physics.atm-clus]](https://arxiv.org/abs/physics/0407112)."
+ @Test md("GoerzNPJQI2017") ==
+ "Goerz, M. H.; Motzoi, F.; Whaley, K. B. and Koch, C. P. (2017). *Charting the circuit QED design landscape using optimal control theory*, [npj Quantum Inf **3**, 37](https://doi.org/10.1038/s41534-017-0036-0)."
+ @Test md("Wilhelm2003.10132") ==
+ "Wilhelm, F. K.; Kirchhoff, S.; Machnes, S.; Wittler, N. and Sugny, D. (2020). *An introduction into optimal control for quantum technologies*, [arXiv:2003.10132](https://doi.org/10.48550/ARXIV.2003.10132)."
+ @Test md("Evans1983") ==
+ "Evans, L. C. (1983). [*An Introduction to Mathematical Optimal Control Theory*](https://math.berkeley.edu/~evans/control.course.pdf). Lecture Notes, University of California, Berkeley."
+ @Test md("Giles2008b") ==
+ "Giles, M. B. (2008). [*An extended collection of matrix derivative results for forward and reverse mode automatic differentiation*](https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf). Technical Report NA-08-01, Oxford University Computing Laboratory."
+ @Test md("QCRoadmap") ==
+ "— (2004). [*Quantum Computation Roadmap*](http://qist.lanl.gov). Version 2.0; April 2, 2004."
+ @Test md("TedRyd") ==
+ "Corcovilos, T. and Weiss, D. S. *Rydberg Calculations*. Private communication."
+ @Test md("jax") ==
+ "Bradbury, J.; Frostig, R.; Hawkins, P.; Johnson, M. J.; Leary, C.; Maclaurin, D.; Necula, G.; Paszke, A.; VanderPlas, J.; Wanderman-Milne, S. and Zhang, Q. [*JAX: composable transformations of Python+NumPy programs*](https://github.com/google/jax)."
end
diff --git a/test/test_latex_rendering.jl b/test/test_latex_rendering.jl
new file mode 100644
index 0000000..e00600b
--- /dev/null
+++ b/test/test_latex_rendering.jl
@@ -0,0 +1,261 @@
+using DocumenterCitations
+using Documenter
+using Test
+
+include("run_makedocs.jl")
+
+CUSTOM1 = joinpath(@__DIR__, "..", "docs", "custom_styles", "enumauthoryear.jl")
+CUSTOM2 = joinpath(@__DIR__, "..", "docs", "custom_styles", "keylabels.jl")
+
+include(CUSTOM1)
+include(CUSTOM2)
+
+
+function dummy_lctx()
+ doc = Documenter.Document()
+ buffer = IOBuffer()
+ return Documenter.LaTeXWriter.Context(buffer, doc)
+end
+
+
+function md_to_latex(mdstr)
+ lctx = dummy_lctx()
+ ast = Documenter.mdparse(mdstr; mode=:single)[1]
+ Documenter.LaTeXWriter.latex(lctx, ast.children)
+ return String(take!(lctx.io))
+end
+
+
+@testset "Invalid BibliographyNode" begin
+ exc = ArgumentError("`list_style` must be one of `:dl`, `:ul`, or `:ol`, not `:bl`")
+ @test_throws exc begin
+ DocumenterCitations.BibliographyNode(
+ :bl, # :bl (bullet list) doesn't exist, should be :ul
+ true,
+ DocumenterCitations.BibliographyItem[]
+ )
+ end
+end
+
+
+@testset "QCRoadmap" begin
+ reference = "[*Quantum Computation Roadmap*](http://qist.lanl.gov) (2004). Version 2.0; April 2, 2004."
+ result = md_to_latex(reference)
+ @test result ==
+ "\\href{http://qist.lanl.gov}{\\emph{Quantum Computation Roadmap}} (2004). Version 2.0; April 2, 2004."
+ # There seemed to be a problem with the hyperlink for the QCRoadmap
+ # reference. However, it turned out the problem was that we were using
+ # `\hypertarget{id}` instead of `\hypertarget{id}{}` (see below)
+end
+
+
+@testset "LaTeXWriter Integration Test" begin
+
+ bib = CitationBibliography(
+ joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),
+ style=:numeric
+ )
+ run_makedocs(
+ joinpath(@__DIR__, "..", "docs");
+ sitename="DocumenterCitations.jl",
+ plugins=[bib],
+ format=Documenter.LaTeX(platform="none"),
+ pages=[
+ "Home" => "index.md",
+ "Syntax" => "syntax.md",
+ "Citation Style Gallery" => "gallery.md",
+ "CSS Styling" => "styling.md",
+ "Internals" => "internals.md",
+ "References" => "references.md",
+ ],
+ env=Dict("DOCUMENTER_BUILD_PDF" => "1"),
+ check_success=true
+ ) do dir, result, success, backtrace, output
+
+ @test success
+ @test occursin("LaTeXWriter: creating the LaTeX file.", output)
+
+ tex_outfile = joinpath(dir, "build", "DocumenterCitations.jl.tex")
+ @test isfile(tex_outfile)
+ tex = read(tex_outfile, String)
+ tex_contains(str) = contains(tex, str)
+ @test tex_contains(raw"{\raggedright% @bibliography")
+ @test tex_contains(raw"}% end @bibliography")
+ # must use `\hypertarget{id}{}`, not `\hypertarget{id}`
+ @test tex_contains(r"\\hypertarget{\d+}{}")
+ @test tex_contains(
+ r"\\hypertarget{\d+}{}\\href{http://qist\.lanl\.gov}{\\emph{Quantum Computation Roadmap}} \(2004\)"
+ )
+ @test tex_contains(
+ raw"\hangindent=0.33in {\makebox[{\ifdim0.33in<\dimexpr\width+1ex\relax\dimexpr\width+1ex\relax\else0.33in\fi}][l]{[1]}}"
+ )
+ nbsp = "\u00A0" # nonbreaking space
+ @test tex_contains(
+ "\\hangindent=0.33in Brif,$(nbsp)C.; Chakrabarti,$(nbsp)R. and Rabitz,$(nbsp)H. (2010)."
+ ) # authoryear :ul
+
+ end
+
+end
+
+
+@testset "LaTeXWriter – :ul bullet list, justified" begin
+
+ DocumenterCitations.set_latex_options(ul_as_hanging=false, bib_blockformat="")
+ @test DocumenterCitations._LATEX_OPTIONS == Dict{Symbol,Any}(
+ :ul_as_hanging => false,
+ :ul_hangindent => "0.33in",
+ :dl_hangindent => "0.33in",
+ :dl_labelwidth => "0.33in",
+ :bib_blockformat => "",
+ )
+
+ bib = CitationBibliography(
+ joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),
+ style=:numeric
+ )
+ run_makedocs(
+ joinpath(@__DIR__, "..", "docs");
+ sitename="DocumenterCitations.jl",
+ plugins=[bib],
+ format=Documenter.LaTeX(platform="none"),
+ pages=[
+ "Home" => "index.md",
+ "Syntax" => "syntax.md",
+ "Citation Style Gallery" => "gallery.md",
+ "CSS Styling" => "styling.md",
+ "Internals" => "internals.md",
+ "References" => "references.md",
+ ],
+ env=Dict("DOCUMENTER_BUILD_PDF" => "1"),
+ check_success=true
+ ) do dir, result, success, backtrace, output
+
+ @test success
+ @test occursin("LaTeXWriter: creating the LaTeX file.", output)
+
+ tex_outfile = joinpath(dir, "build", "DocumenterCitations.jl.tex")
+ @test isfile(tex_outfile)
+ tex = read(tex_outfile, String)
+ tex_contains(str) = contains(tex, str)
+ @test tex_contains(raw"{% @bibliography")
+ @test tex_contains(raw"}% end @bibliography")
+ nbsp = "\u00A0" # nonbreaking space
+ @test tex_contains(
+ "\\begin{itemize}\n\\item Brif,$(nbsp)C.; Chakrabarti,$(nbsp)R. and Rabitz,$(nbsp)H. (2010)."
+ ) # authoryear :ul
+
+ end
+
+ DocumenterCitations.reset_latex_options()
+ @test DocumenterCitations._LATEX_OPTIONS == Dict{Symbol,Any}(
+ :ul_as_hanging => true,
+ :ul_hangindent => "0.33in",
+ :dl_hangindent => "0.33in",
+ :dl_labelwidth => "0.33in",
+ :bib_blockformat => "\\raggedright",
+ )
+
+end
+
+
+@testset "LaTeXWriter – custom indents" begin
+
+ DocumenterCitations.set_latex_options(
+ ul_hangindent="1cm",
+ dl_hangindent="1.5cm",
+ dl_labelwidth="2.0cm"
+ )
+ @test DocumenterCitations._LATEX_OPTIONS == Dict{Symbol,Any}(
+ :ul_as_hanging => true,
+ :ul_hangindent => "1cm",
+ :dl_hangindent => "1.5cm",
+ :dl_labelwidth => "2.0cm",
+ :bib_blockformat => "\\raggedright",
+ )
+
+ bib = CitationBibliography(
+ joinpath(@__DIR__, "..", "docs", "src", "refs.bib"),
+ style=:numeric
+ )
+ run_makedocs(
+ joinpath(@__DIR__, "..", "docs");
+ sitename="DocumenterCitations.jl",
+ plugins=[bib],
+ format=Documenter.LaTeX(platform="none"),
+ pages=[
+ "Home" => "index.md",
+ "Syntax" => "syntax.md",
+ "Citation Style Gallery" => "gallery.md",
+ "CSS Styling" => "styling.md",
+ "Internals" => "internals.md",
+ "References" => "references.md",
+ ],
+ env=Dict("DOCUMENTER_BUILD_PDF" => "1"),
+ check_success=true
+ ) do dir, result, success, backtrace, output
+
+ @test success
+ @test occursin("LaTeXWriter: creating the LaTeX file.", output)
+
+ tex_outfile = joinpath(dir, "build", "DocumenterCitations.jl.tex")
+ @test isfile(tex_outfile)
+ tex = read(tex_outfile, String)
+ tex_contains(str) = contains(tex, str)
+ @test tex_contains(raw"{\raggedright% @bibliography")
+ @test tex_contains(raw"}% end @bibliography")
+ nbsp = "\u00A0" # nonbreaking space
+ @test tex_contains(
+ "\\hangindent=1cm Brif,$(nbsp)C.; Chakrabarti,$(nbsp)R. and Rabitz,$(nbsp)H. (2010)."
+ ) # authoryear :ul
+ @test tex_contains(
+ raw"\hangindent=1.5cm {\makebox[{\ifdim2.0cm<\dimexpr\width+1ex\relax\dimexpr\width+1ex\relax\else2.0cm\fi}][l]{[BCR10]}}"
+ ) # :alpha style
+ @test tex_contains(
+ raw"\hangindent=1.5cm {\makebox[{\ifdim2.0cm<\dimexpr\width+1ex\relax\dimexpr\width+1ex\relax\else2.0cm\fi}][l]{[1]}}"
+ ) # :numeric style
+
+ end
+
+ DocumenterCitations.reset_latex_options()
+ @test DocumenterCitations._LATEX_OPTIONS == Dict{Symbol,Any}(
+ :ul_as_hanging => true,
+ :ul_hangindent => "0.33in",
+ :dl_hangindent => "0.33in",
+ :dl_labelwidth => "0.33in",
+ :bib_blockformat => "\\raggedright",
+ )
+
+end
+
+
+@testset "invalid latex options" begin
+
+ msg = "dl_as_hanging is not a valid option in set_latex_options."
+ @test_throws ArgumentError(msg) begin
+ DocumenterCitations.set_latex_options(dl_as_hanging=false)
+ # We've confused `dl_as_hanging` with `ul_as_hanging`
+ end
+
+ msg = "`0` for option ul_hangindent in set_latex_options must be of type String, not Int64"
+ @test_throws ArgumentError(msg) begin
+ DocumenterCitations.set_latex_options(ul_hangindent=0)
+ end
+
+ msg = "width \"\" must be a valid LaTeX width"
+ @test_throws ArgumentError(msg) begin
+ # DocumenterCitations.set_latex_options(ul_hangindent="")
+ # actually works, but then we get an error when we try to generate a
+ # label box:
+ DocumenterCitations._labelbox(nothing, nothing; width="")
+ end
+
+ @test DocumenterCitations._LATEX_OPTIONS == Dict{Symbol,Any}(
+ :ul_as_hanging => true,
+ :ul_hangindent => "0.33in",
+ :dl_hangindent => "0.33in",
+ :dl_labelwidth => "0.33in",
+ :bib_blockformat => "\\raggedright",
+ )
+
+end
diff --git a/test/test_parse_citation_link.jl b/test/test_parse_citation_link.jl
index 0e14cee..3809bea 100644
--- a/test/test_parse_citation_link.jl
+++ b/test/test_parse_citation_link.jl
@@ -38,14 +38,14 @@ using IOCapture: IOCapture
@test cit.cmd == :cite
@test cit.style ≡ :authoryear
- cit = CitationLink("[GoerzQ2022; Eq. (1)](@cite)")
+ cit = CitationLink("[GoerzQ2022; Eq.\u00A0(1)](@cite)")
@test cit.cmd == :cite
@test cit.style ≡ nothing
@test cit.keys == ["GoerzQ2022"]
- @test cit.note ≡ "Eq. (1)"
+ @test cit.note ≡ "Eq.\u00A0(1)"
- cit = CitationLink("[GoerzQ2022; Eq. (1)](@cite)")
- @test cit.note ≡ "Eq. (1)"
+ cit = CitationLink("[GoerzQ2022; Eq.\u00A0(1)](@cite)")
+ @test cit.note ≡ "Eq.\u00A0(1)"
cit = CitationLink("[GoerzQ2022,CarrascoPRA2022,GoerzA2023](@cite)")
@test cit.keys == ["GoerzQ2022", "CarrascoPRA2022", "GoerzA2023"]