markup/pandoc: Add citation support

Closes #15062

Co-authored-by: Sebastian Höffner <info@sebastian-hoeffner.de>
This commit is contained in:
Joe Mooring
2026-06-24 10:22:55 -07:00
committed by Bjørn Erik Pedersen
parent e46d37a984
commit 128fb17c2b
4 changed files with 620 additions and 1 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ func (c *pandocConverter) getPandocContent(src []byte, ctx converter.DocumentCon
if binaryName == "" { if binaryName == "" {
return nil, fmt.Errorf("pandoc not found in $PATH, cannot render %q", ctx.DocumentName) return nil, fmt.Errorf("pandoc not found in $PATH, cannot render %q", ctx.DocumentName)
} }
args := []string{"--mathjax"} args := []string{"--mathjax", "--citeproc"}
return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) return internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args)
} }
+97
View File
@@ -0,0 +1,97 @@
// Copyright 2026 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pandoc_test
import (
"testing"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/markup/pandoc"
)
// See issue 15062.
func TestBibliographySupport(t *testing.T) {
if !pandoc.Supports() {
t.Skip("pandoc not installed")
}
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
[security.exec]
allow = ['pandoc']
-- layouts/page.html --
{{ .Content }}
-- content/p1.pdc --
---
title: home
---
---
bibliography: testdata/foo.bib
citation-style: testdata/ieee.csl
link-citations: true
references:
- type: article-journal
id: WatsonCrick1953
author:
- family: Watson
given: J. D.
- family: Crick
given: F. H. C.
issued:
date-parts:
- - 1953
- 4
- 25
title: 'Molecular structure of nucleic acids: a structure for deoxyribose nucleic acid'
title-short: Molecular structure of nucleic acids
container-title: Nature
volume: 171
issue: 4356
page: 737-738
DOI: 10.1038/171737a0
---
This is a citation: @einstein1905physics
This is another citation: [@WatsonCrick1953, p. 33]
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
`<p>This is a citation: <span class="citation"
data-cites="einstein1905physics"><a href="#ref-einstein1905physics"
role="doc-biblioref">[1]</a></span></p>
<p>This is another citation: <span class="citation"
data-cites="WatsonCrick1953"><a href="#ref-WatsonCrick1953"
role="doc-biblioref">[2, p. 33]</a></span></p>
<div id="ref-einstein1905physics" class="csl-entry" role="listitem">
<div class="csl-left-margin">[1] </div><div class="csl-right-inline">A.
Einstein, <span>“Zur elektrodynamik bewegter
k<span>ö</span>rper,”</span> <em>Annalen der Physik</em>, vol. 322, no.
10, pp. 891921, 1905.</div>
</div>
<div id="ref-WatsonCrick1953" class="csl-entry" role="listitem">
<div class="csl-left-margin">[2] </div><div class="csl-right-inline">J.
D. Watson and F. H. C. Crick, <span>“Molecular structure of nucleic
acids: a structure for deoxyribose nucleic acid,”</span>
<em>Nature</em>, vol. 171, no. 4356, pp. 737738, Apr. 1953, doi: <a
href="https://doi.org/10.1038/171737a0">10.1038/171737a0</a>.</div>
</div>
</div>`,
)
}
+10
View File
@@ -0,0 +1,10 @@
@article{einstein1905physics,
author = {Einstein, Albert},
title = {Zur Elektrodynamik bewegter K{\"o}rper},
journal = {Annalen der Physik},
volume = {322},
number = {10},
pages = {891--921},
year = {1905},
publisher= {Wiley Online Library}
}
+512
View File
@@ -0,0 +1,512 @@
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only">
<info>
<title>IEEE</title>
<id>http://www.zotero.org/styles/ieee</id>
<link href="http://www.zotero.org/styles/ieee" rel="self"/>
<!-- <link href="https://ieeeauthorcenter.ieee.org/wp-content/uploads/IEEE-Reference-Guide.pdf" rel="documentation"/> - 2018 guidelines -->
<link href="http://journals.ieeeauthorcenter.ieee.org/wp-content/uploads/sites/7/IEEE_Reference_Guide.pdf" rel="documentation"/>
<link href="https://journals.ieeeauthorcenter.ieee.org/your-role-in-article-production/ieee-editorial-style-manual/" rel="documentation"/>
<author>
<name>Michael Berkowitz</name>
<email>mberkowi@gmu.edu</email>
</author>
<contributor>
<name>Julian Onions</name>
<email>julian.onions@gmail.com</email>
</contributor>
<contributor>
<name>Rintze Zelle</name>
<uri>http://twitter.com/rintzezelle</uri>
</contributor>
<contributor>
<name>Stephen Frank</name>
<uri>http://www.zotero.org/sfrank</uri>
</contributor>
<contributor>
<name>Sebastian Karcher</name>
</contributor>
<contributor>
<name>Giuseppe Silano</name>
<email>g.silano89@gmail.com</email>
<uri>http://giuseppesilano.net</uri>
</contributor>
<contributor>
<name>Patrick O'Brien</name>
</contributor>
<contributor>
<name>Brenton M. Wiernik</name>
</contributor>
<contributor>
<name>Oliver Couch</name>
<email>oliver.couch@gmail.com</email>
</contributor>
<category citation-format="numeric"/>
<category field="engineering"/>
<category field="generic-base"/>
<summary>IEEE style as per the 2023 guidelines, V 11.29.2023.</summary>
<updated>2024-07-15T14:30:07+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<locale xml:lang="en">
<date form="text">
<date-part name="month" form="short" suffix=" "/>
<date-part name="day" form="numeric-leading-zeros" suffix=", "/>
<date-part name="year"/>
</date>
<terms>
<term name="chapter" form="short">ch.</term>
<term name="chapter-number" form="short">ch.</term>
<term name="presented at">presented at the</term>
<term name="available at">available</term>
</terms>
</locale>
<!-- Macros -->
<macro name="status">
<choose>
<if variable="page issue volume" match="none">
<text variable="status" text-case="capitalize-first" suffix="" font-weight="bold"/>
</if>
</choose>
</macro>
<macro name="edition">
<choose>
<if type="bill book chapter graphic legal_case legislation motion_picture paper-conference report song" match="any">
<choose>
<if is-numeric="edition">
<group delimiter=" ">
<number variable="edition" form="ordinal"/>
<text term="edition" form="short"/>
</group>
</if>
<else>
<text variable="edition" text-case="capitalize-first" suffix="."/>
</else>
</choose>
</if>
</choose>
</macro>
<macro name="issued">
<choose>
<if type="article-journal report" match="any">
<date variable="issued">
<date-part name="month" form="short" suffix=" "/>
<date-part name="year" form="long"/>
</date>
</if>
<else-if type="bill book chapter graphic legal_case legislation song thesis" match="any">
<date variable="issued">
<date-part name="year" form="long"/>
</date>
</else-if>
<else-if type="paper-conference" match="any">
<date variable="issued">
<date-part name="month" form="short"/>
<date-part name="year" prefix=" "/>
</date>
</else-if>
<else-if type="motion_picture" match="any">
<date variable="issued" form="text" prefix="(" suffix=")"/>
</else-if>
<else>
<date variable="issued" form="text"/>
</else>
</choose>
</macro>
<macro name="author">
<names variable="author">
<name and="text" et-al-min="7" et-al-use-first="1" initialize-with=". "/>
<label form="short" prefix=", " text-case="capitalize-first"/>
<et-al font-style="italic"/>
<substitute>
<names variable="editor"/>
<names variable="translator"/>
<text macro="director"/>
</substitute>
</names>
</macro>
<macro name="editor">
<names variable="editor">
<name initialize-with=". " delimiter=", " and="text"/>
<label form="short" prefix=", " text-case="capitalize-first"/>
</names>
</macro>
<macro name="director">
<names variable="director">
<name and="text" et-al-min="7" et-al-use-first="1" initialize-with=". "/>
<et-al font-style="italic"/>
</names>
</macro>
<macro name="locators">
<group delimiter=", ">
<text macro="edition"/>
<group delimiter=" ">
<text term="volume" form="short"/>
<number variable="volume" form="numeric"/>
</group>
<group delimiter=" ">
<number variable="number-of-volumes" form="numeric"/>
<text term="volume" form="short" plural="true"/>
</group>
<group delimiter=" ">
<text term="issue" form="short"/>
<number variable="issue" form="numeric"/>
</group>
</group>
</macro>
<macro name="title">
<choose>
<if type="bill book graphic legal_case legislation motion_picture song standard software" match="any">
<text variable="title" font-style="italic"/>
</if>
<else>
<text variable="title" quotes="true"/>
</else>
</choose>
</macro>
<macro name="publisher">
<choose>
<if type="bill book chapter graphic legal_case legislation motion_picture paper-conference song" match="any">
<group delimiter=": ">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</if>
<else>
<group delimiter=", ">
<text variable="publisher"/>
<text variable="publisher-place"/>
</group>
</else>
</choose>
</macro>
<macro name="event">
<choose>
<!-- Published Conference Paper -->
<if type="paper-conference speech" match="any">
<choose>
<if variable="container-title" match="any">
<group delimiter=" ">
<text term="in"/>
<text variable="container-title" font-style="italic"/>
</group>
</if>
<!-- Unpublished Conference Paper -->
<else>
<group delimiter=" ">
<text term="presented at"/>
<text variable="event"/>
</group>
</else>
</choose>
</if>
</choose>
</macro>
<macro name="access">
<choose>
<if type="webpage post post-weblog" match="any">
<!-- https://url.com/ (accessed Mon. DD, YYYY). -->
<choose>
<if variable="URL">
<group delimiter=". " prefix=" ">
<group delimiter=": ">
<text term="accessed" text-case="capitalize-first"/>
<date variable="accessed" form="text"/>
</group>
<text term="online" prefix="[" suffix="]" text-case="capitalize-first"/>
<group delimiter=": ">
<text term="available at" text-case="capitalize-first"/>
<text variable="URL"/>
</group>
</group>
</if>
</choose>
</if>
<else-if match="any" variable="DOI">
<!-- doi: 10.1000/xyz123. -->
<text variable="DOI" prefix=" doi: " suffix="."/>
</else-if>
<else-if variable="URL">
<!-- Accessed: Mon. DD, YYYY. [Medium]. Available: https://URL.com/ -->
<group delimiter=". " prefix=" " suffix=". ">
<!-- Accessed: Mon. DD, YYYY. -->
<group delimiter=": ">
<text term="accessed" text-case="capitalize-first"/>
<date variable="accessed" form="text"/>
</group>
<!-- [Online Video]. -->
<group prefix="[" suffix="]" delimiter=" ">
<choose>
<if variable="medium" match="any">
<text variable="medium" text-case="capitalize-first"/>
</if>
<else>
<text term="online" text-case="capitalize-first"/>
<choose>
<if type="motion_picture">
<text term="video" text-case="capitalize-first"/>
</if>
</choose>
</else>
</choose>
</group>
</group>
<!-- Available: https://URL.com/ -->
<group delimiter=": " prefix=" ">
<text term="available at" text-case="capitalize-first"/>
<text variable="URL"/>
</group>
</else-if>
</choose>
</macro>
<macro name="page">
<choose>
<if type="article-journal" variable="number" match="all">
<group delimiter=" ">
<text value="Art."/>
<text term="issue" form="short"/>
<text variable="number"/>
</group>
</if>
<else>
<group delimiter=" ">
<label variable="page" form="short"/>
<text variable="page"/>
</group>
</else>
</choose>
</macro>
<macro name="citation-locator">
<group delimiter=" ">
<choose>
<if locator="page">
<label variable="locator" form="short"/>
</if>
<else>
<label variable="locator" form="short" text-case="capitalize-first"/>
</else>
</choose>
<text variable="locator"/>
</group>
</macro>
<macro name="geographic-location">
<group delimiter=", " suffix=".">
<choose>
<if variable="publisher-place">
<text variable="publisher-place" text-case="title"/>
</if>
<else-if variable="event-place">
<text variable="event-place" text-case="title"/>
</else-if>
</choose>
</group>
</macro>
<!-- Series -->
<macro name="collection">
<choose>
<if variable="collection-title" match="any">
<text term="in" suffix=" "/>
<group delimiter=", " suffix=". ">
<text variable="collection-title"/>
<text variable="collection-number" prefix="no. "/>
<text variable="volume" prefix="vol. "/>
</group>
</if>
</choose>
</macro>
<!-- Citation -->
<citation>
<sort>
<key variable="citation-number"/>
</sort>
<layout delimiter=", ">
<group prefix="[" suffix="]" delimiter=", ">
<text variable="citation-number"/>
<text macro="citation-locator"/>
</group>
</layout>
</citation>
<!-- Bibliography -->
<bibliography entry-spacing="0" second-field-align="flush">
<layout>
<!-- Citation Number -->
<text variable="citation-number" prefix="[" suffix="]"/>
<!-- Author(s) -->
<text macro="author" suffix=", "/>
<!-- Rest of Citation -->
<choose>
<!-- Specific Formats -->
<if type="article-journal">
<group delimiter=", ">
<text macro="title"/>
<text variable="container-title" font-style="italic" form="short"/>
<text macro="locators"/>
<text macro="page"/>
<text macro="issued"/>
<text macro="status"/>
</group>
<choose>
<if variable="URL DOI" match="none">
<text value="."/>
</if>
<else>
<text value=","/>
</else>
</choose>
<text macro="access"/>
</if>
<else-if type="paper-conference speech" match="any">
<group delimiter=", " suffix=", ">
<text macro="title"/>
<text macro="event"/>
<text macro="editor"/>
</group>
<text macro="collection"/>
<group delimiter=", " suffix=".">
<text macro="publisher"/>
<text macro="issued"/>
<text macro="page"/>
<text macro="status"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="chapter">
<group delimiter=", " suffix=".">
<text macro="title"/>
<group delimiter=" ">
<text term="in" suffix=" "/>
<text variable="container-title" font-style="italic"/>
</group>
<text macro="locators"/>
<text macro="editor"/>
<text macro="collection"/>
<text macro="publisher"/>
<text macro="issued"/>
<group delimiter=" ">
<label variable="chapter-number" form="short"/>
<text variable="chapter-number"/>
</group>
<text macro="page"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="report">
<group delimiter=", " suffix=".">
<text macro="title"/>
<text macro="publisher"/>
<group delimiter=" ">
<text variable="genre"/>
<text variable="number"/>
</group>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="thesis">
<group delimiter=", " suffix=".">
<text macro="title"/>
<text variable="genre"/>
<text macro="publisher"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="software">
<group delimiter=". " suffix=".">
<text macro="title"/>
<text macro="issued" prefix="(" suffix=")"/>
<text variable="genre"/>
<text macro="publisher"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="article">
<group delimiter=", " suffix=".">
<text macro="title"/>
<text macro="issued"/>
<group delimiter=": ">
<text macro="publisher" font-style="italic"/>
<text variable="number"/>
</group>
</group>
<text macro="access"/>
</else-if>
<else-if type="webpage post-weblog post" match="any">
<group delimiter=", " suffix=".">
<text macro="title"/>
<text variable="container-title"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="patent">
<group delimiter=", ">
<text macro="title"/>
<text variable="number"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<!-- Online Video -->
<else-if type="motion_picture">
<text macro="geographic-location" suffix=". "/>
<group delimiter=", " suffix=".">
<text macro="title"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="standard">
<group delimiter=", " suffix=".">
<text macro="title"/>
<group delimiter=" ">
<text variable="genre"/>
<text variable="number"/>
</group>
<text macro="geographic-location"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<!-- Generic/Fallback Formats -->
<else-if type="bill book graphic legal_case legislation report song" match="any">
<group delimiter=", " suffix=". ">
<text macro="title"/>
<text macro="locators"/>
</group>
<text macro="collection"/>
<group delimiter=", " suffix=".">
<text macro="publisher"/>
<text macro="issued"/>
<text macro="page"/>
</group>
<text macro="access"/>
</else-if>
<else-if type="article-magazine article-newspaper broadcast interview manuscript map patent personal_communication song speech thesis webpage" match="any">
<group delimiter=", " suffix=".">
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<text macro="locators"/>
<text macro="publisher"/>
<text macro="page"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else-if>
<else>
<group delimiter=", " suffix=". ">
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<text macro="locators"/>
</group>
<text macro="collection"/>
<group delimiter=", " suffix=".">
<text macro="publisher"/>
<text macro="page"/>
<text macro="issued"/>
</group>
<text macro="access"/>
</else>
</choose>
</layout>
</bibliography>
</style>