From ef4bfa96443130638f723d8dd336bbeeeb421431 Mon Sep 17 00:00:00 2001 From: Michael Goerz Date: Thu, 12 Oct 2023 01:58:32 -0400 Subject: [PATCH] Add support for citations in PDFs/LaTeX Co-authored-by: Sukera <11753998+Seelengrab@users.noreply.github.com> --- Makefile | 4 + NEWS.md | 6 + docs/makepdf.jl | 48 ++++- docs/src/assets/preamble.tex | 36 ++++ docs/src/gallery.md | 8 + docs/src/index.md | 19 +- docs/src/internals.md | 11 ++ src/DocumenterCitations.jl | 3 + src/bibliography_node.jl | 190 +++++++++++++++++++ src/expand_bibliography.jl | 46 +++-- src/formatting.jl | 60 +++--- src/labeled_styles_utils.jl | 4 +- src/latex_options.jl | 98 ++++++++++ src/styles/authoryear.jl | 4 +- test/runtests.jl | 5 + test/test_collect_from_docstrings.jl | 3 +- test/test_formatting.jl | 143 ++++++++++----- test/test_latex_rendering.jl | 261 +++++++++++++++++++++++++++ test/test_parse_citation_link.jl | 8 +- 19 files changed, 849 insertions(+), 108 deletions(-) create mode 100644 docs/src/assets/preamble.tex create mode 100644 src/bibliography_node.jl create mode 100644 src/latex_options.jl create mode 100644 test/test_latex_rendering.jl 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
    " - - 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"]