typst icon indicating copy to clipboard operation
typst copied to clipboard

Multiple bibliographies

Open mareikep opened this issue 1 year ago • 19 comments

I am really excited to get to use typst, it looks great so far!

However, for larger documents it would be really helpful to have multiple bibliographies, e.g. to categorize publications into books, journals, etc. or to separate own prior publications from other peoples' works. This would be something like the bibunit or chapterbib packages in LaTeX. Unfortunately I cannot assign labels, but the [proposed Label] would be feature request

mareikep avatar May 04 '23 09:05 mareikep

we need it for PhD too

ayoubelmhamdi avatar Jun 10 '23 10:06 ayoubelmhamdi

I too have discovered that there can be only one #bibliography, but its path argument can be an array, so you can include multiple bibliographies. However, there can't be any duplicate keys (if there are duplicate keys, I think it would probably make sense for later definitions to override earlier ones; then you could list the "most definitive" bibliographies last).

wlupton avatar Jul 03 '23 14:07 wlupton

Something like that (in latex) would be nice indeed:

        \printbibliography[heading=subbibliography, title={Articles}, type=article]
        \printbibliography[heading=subbibliography, title={Reports}, nottype=online, nottype=article]
        \printbibliography[heading=subbibliography, title={Internet}, type=online]

HROMANO avatar Jul 12 '23 14:07 HROMANO

One more vote for this! I got 80% of the way through rebuilding my CV in typst, which is a dream compared to LaTeX, but this might kill the effort :-/. I'm looking into manually building building bibliographies from the yaml, but I am not quite confident enough in the syntax to have high hopes there.

An interim solution (though I'm not sure if it's actually easier to implement) would be to enable the style of a #cite call to be the full reference. In other words, instead of

== Some header

@label1
@label2

== Other header

@label3

#bibliography[...]

Giving image

Maybe one could do something like

== Some header

#cite("label1", "label2", style: "full")

== Other Header

#cite("label3", style: "full")

To get

Some header

[1] M. C. Woodruff, K. S. Bonham, et al., “Chronic inflammation, neutrophil activity, and autoreactivity splits long Covid,” Nature Commun., vol. 14, no. 1, Jul. 2023, doi: 10.1038/ s41467-023-40012-7. [Online]. Available: https://doi.org/10.1038/s41467-023-40012-7

[2] A. A. Schoenborn, S. M. Yannarell, et al., “Microclimate is a strong predictor of the native and invasive plant-associated soil microbiome on san cristóbal island, galápagos archipelago,” Environmental Microbiology, Mar. 2023, doi: 10.1111/1462-2920.16361. [Online]. Available: https://doi.org/10.1111/1462-2920.16361

Other Header

[3] L. Tso, K. S. Bonham, A. Fishbein, S. Rowland, and V. Klepac-Ceraj, “Targeted high-resolution taxonomic identification of Bifidobacterium longum subsp. infantis using human milk oligosaccharide metabolizing genes,” Nutrients, vol. 13, no. 8, p. 2833, Aug. 2021, doi: 10.3390/ nu13082833. [Online]. Available: https://doi.org/10.3390/nu13082833

kescobo avatar Jul 15 '23 14:07 kescobo

Is there a way to call hayagriva within typst? Something like my workaround is already possible with the hayagriva CLI:

❯ hayagriva testcite.yaml reference
Joe Schmoe, and Jane Doe. 2042. “This Is a Really Fascinating Title.” FEBS Journal. https://doi.org/10.1111/j.1742-4658.2010.07623.x.
Me Myself, and And Eye. 2001. “Boring Title,” https://doi.org/10.2222/j.x.

❯ hayagriva testcite.yaml reference --select "!misc"
Joe Schmoe, and Jane Doe. 2042. “This Is a Really Fascinating Title.” FEBS Journal. https://doi.org/10.1111/j.1742-4658.2010.07623.x.

❯ hayagriva testcite.yaml reference --select "!misc" --style ieee
Joe Schmoe, and Jane Doe, “This is a really fascinating title,” FEBS J., 2042, doi: 10.1111/j.1742-4658.2010.07623.x. [Online]. Available: https://doi.org/10.1111/j.1742-4658.2010.07623.x

kescobo avatar Jul 15 '23 16:07 kescobo

I just hacked this together using the #yaml function:

#let refs(contents) = {
    for (tag, fields) in contents {
        let auth_n = fields.author.len()
        let auths = ()
        if auth_n > 4 {
            auths = fields.author.slice(0, count:4)
            [#auths.join(", ") _et. al._]
        } else {
            auths = fields.author
            auths.join(", ", last: " and ")
        }
        
        [, "#fields.title.value"]
        [. #emph(fields.parent.at(0).title)]
        [. (#fields.date)]
        [ doi: #link(fields.url, fields.doi)]
        linebreak()
    }
}

#refs(
  yaml("citations.yaml")
)

gives

image

kescobo avatar Jul 16 '23 14:07 kescobo

OK, last one - this allows you to pass an array of citation keys, and pass a tag to include:

#let refs(file, entries: (), tag: none) = {
    if entries.len() == 0 {
        entries = yaml(file).keys()
    }

    for (entry, fields) in yaml(file) {
        if entry not in entries {
            continue
        }
        if not tag == none {
            if "tags" not in fields or tag not in fields.tags {
                continue
            }
        }

        if "tags" in fields and "cofirst" in fields.tags {
            [\* ]
        }

        if "tags" in fields and "corresponding" in fields.tags {
            [#sym.dagger ]
        }

        let auth_n = fields.author.len()
        let auths = ()
        if auth_n > 4 {
            auths = fields.author.slice(0, count:4)
            [#auths.join(", ") _et. al._]
        } else {
            auths = fields.author
            auths.join(", ", last: " and ")
        }
        
        [, "#eval("[" + fields.title.value + "]")"]
        [. #emph(fields.parent.at(0).title)]
        [. (#fields.date)]
        [ doi: #link(fields.url, fields.doi)]
        linebreak()
    }
}

So you can do eg #refs("citations.yaml", tag: "preprint") or #refs("citations.yaml", entries: ("entry1", "entry2")) (or a combination.

It also has some logic to add a star at the beginning if you have the tag "cofirst", or a dagger if you have the tag "corresponding"

kescobo avatar Jul 16 '23 17:07 kescobo

we need it for PhD too

Just wanted to confirm.

I am trying to reproduce my PhD thesis LaTex template in typst, but it was a necessity to have bibliographies for each chapter and numbering of references needed to start from 1 again.

Would be really nice feature to have.

camattelaer avatar Jul 25 '23 07:07 camattelaer

Multiple bibliographies could meaning:

  • the using of the different files.
  • or by having a bibliography for each chapter with reference numbers that start from 1 in each chapter

so what's means Multiple bibliographies ?

ayoubelmhamdi avatar Jul 27 '23 22:07 ayoubelmhamdi

@ayoubelmhamdi I think you can already use multiple bibliography files by passing an array to #bibliography.

kescobo avatar Jul 28 '23 14:07 kescobo

I currently do this using the lua filters for multiple bibliographies in Quarto v1.3. I haven't tested them in Typst/Quarto v1.4 yet, but I hope they'll work here, too.

  • https://github.com/pandoc-ext/multibib to include multiple bib files as inputs
  • https://github.com/pandoc-ext/section-bibliographies to print one bibliography per chapter/section

jadebarclay avatar Aug 06 '23 03:08 jadebarclay

I wanted to add a page just with personal publications using the full: true and adding a multiple bib files as an array will list out everything that is not part of my publications.

akshaybabloo avatar Dec 04 '23 02:12 akshaybabloo

Is this being considered by the development team? Multiple bibliographies are a basic feature of any academic work.

bcdavasconcelos avatar Dec 23 '23 21:12 bcdavasconcelos

@bcdavasconcelos It will be supported in the future, but I can't yet say when.

laurmaedje avatar Dec 25 '23 19:12 laurmaedje

Hey all,

Seeing as I want to also be able to write my PhD dissertation in Typst, I figured I might come up with a way to allow multiple bibliographies in Typst, by using a Typst library. With multiple bibliographies, I intend to mean that every chapter has their own set of references and citations throughout the manuscript can correctly link to the desired bibliography. The project is still very young, but works relatively well. It only supports rudimentary APA style citation, but one can easily add their preferred citation style in the library itself.

https://github.com/jrihon/multi-bibs

Hopefully this can be of use to someone else too, until the real deal comes along!

jrihon avatar Feb 02 '24 20:02 jrihon

Hey all,

Seeing as I want to also be able to write my PhD dissertation in Typst, I figured I might come up with a way to allow multiple bibliographies in Typst, by using a Typst library. With multiple bibliographies, I intend to mean that every chapter has their own set of references and citations throughout the manuscript can correctly link to the desired bibliography. The project is still very young, but works relatively well. It only supports rudimentary APA style citation, but one can easily add their preferred citation style in the library itself.

https://github.com/jrihon/multi-bibs

Hopefully this can be of use to someone else too, until the real deal comes along!

It's great to have a bibliography on every chapter, but it seems that you control the user on how to organize our project, ~that's a bad idea~.

rootdirectory/ 
    - chapters/
        - CHAPTER_X/
            mod.typ, bibliography_X.yml
        - CHAPTER_Y/
            mod.typ, bibliography_Y.yml
    - lib/
        multi-bibs.typ

ayoubelmhamdi avatar Feb 03 '24 06:02 ayoubelmhamdi

It's great to have a bibliography on every chapter, but it seems that you control the user on how to organize our project, that's a bad idea.

rootdirectory/ 
    - chapters/
        - CHAPTER_X/
            mod.typ, bibliography_X.yml
        - CHAPTER_Y/
            mod.typ, bibliography_Y.yml
    - lib/
        multi-bibs.typ

As stated as a disclaimer and in the TODO section, I am very much aware of the rigidity of the project. One of the key problems is that the #yaml() function infers a path to your .yml file and prepends it to the given path. I've figured out that by passing an absolute path to the .yml directly, typst prepend the path with the path to the root directory of the project. This Path inference is generally quite annoying, but I understand its place in the Typst atm. This path inference is also very variable and changes on the type of path you pass into the argument of the function, hence its fickle nature. That is why, for now, I decided to make a bit rigid, just to get it working.

But hey, it's open source, feel free to change it yourself if you don't like it. I only figured out a relatively viable solution to a problem I have, and replying with a that's a bad idea to a glaring issue I acknowledged myself is just not productive.

jrihon avatar Feb 03 '24 09:02 jrihon

If you use IEEE reference format (or something similar that goes by number), this will work. The only downside is it won't merge adjacent references, but you can accommodate this by using cite rules instead of ref rules.

#show bibliography: none
#bibliography("refs.bib")
// Keep track of all references, clearing every time a new heading is shown
#let section-refs = state("section-refs", ())

// Add bibliography references to the current section's state
#show ref: it => {
  if it.element != none {
    // Citing a document element like a figure, not a bib key
    // So don't update refs
    it
    return
  }
  section-refs.update(old => {
    if it.target not in old {
      old.push(it.target)
    }
    old
  })
  locate(loc => {
    let idx = section-refs.at(loc).position(el => el == it.target)
    "[" + str(idx + 1) + "]"
  })
}

// Print the "per-section" bibliography
#let section-bib() = locate(loc => {
  let ref-counter = counter("section-refs")
  ref-counter.update(1)
  show regex("^\[(\d+)\]\s"): it => [
    [#ref-counter.display()]
  ]
  for target in section-refs.at(loc) {
    block(cite(target, form: "full"))
    ref-counter.step()
  }
})

// Clear the previously stored references every time a level 1 heading
// is created.
#show heading.where(level: 1): it => {
  section-refs.update(())
  it
}



= First Section
My reference @plucked-string and another @plot

#section-bib()

= Second Section
Another reference @plucked-string-extensions @plot @plucked-string-extensions @plucked-string

#section-bib()

image

ntjess avatar Feb 05 '24 23:02 ntjess

if you use IEEE reference format (or something similar that goes by number), this will work. The only downside is it won't merge adjacent references, but you can accommodate this by using cite rules instead of ref rules.

One of the big utilities is being able to link the citation to the references, by making hyperlinks between them (link, label etc.) . What I am wondering here is the following : what if you are citing a specific reference, sourced from a single bibliography, and using that citation in multiple chapters, thereby creating the multiple instances of the same label. Wouldn't that create ambiguous hyperlinks? Typst will not compile if that is the case .. I ran into this issue, so I created unique labels in the Referencelist, by splitting the bibliography per chapter for my specific issue.

jrihon avatar Feb 06 '24 15:02 jrihon

basically we need to reproduce the biblatex behaviour here https://www.overleaf.com/learn/latex/Questions/Creating_multiple_bibliographies_in_the_same_document

braindevices avatar Feb 16 '24 17:02 braindevices

we need 1 new concept refsection. Then a #bibliography() only print ref list inside the refsection. We must to have this to make typst really useful. If I only write a few page document, latex is not that slow.

In more advanced version, the refsection should be able to nested. So at the end we can also have a global ref list. But I think it is ok to not support this option. Usually it is quite redundant.

braindevices avatar Feb 16 '24 17:02 braindevices

#show heading.where(level: 1): it => {
  section-refs.update(())
  it
}

There could be some improvements:

  1. the section-refs.update action can be merged into each section-bib() function.
  2. There seems to be a bug, the ref id cannot be obtained by using len, rather, it should be obtained by .position.

I made some modification as

//modified based on 
//https://github.com/typst/typst/issues/1097#issuecomment-1928525350
#show bibliography: none
#bibliography("ref.bib")
// Keep track of all references, clearing every time a new heading is shown
#let section-refs = state("section-refs", ())

// Add bibliography references to the current section's state
#show ref: it => {
  if it.element != none {
    // Citing a document element like a figure, not a bib key
    // So don't update refs
    it
    return
  }
  section-refs.update(old => {
    if it.target not in old {
      old.push(it.target)
    }
    old
  })

//I think the following is how the reference index should be obtained correctly.

  let get_ref_id(loc)={
    //str(section-refs.at(loc).len())
    str(section-refs.at(loc).position(x=>(x==it.target))+1)
  }
  
  
  locate(loc => {
    "[" + get_ref_id(loc) + "]"
  })
}

// Print the "per-section" bibliography
#let section-bib() = locate(loc => {
  //https://github.com/typst/typst/issues/1097

  let ref-counter = counter("section-refs")
  ref-counter.update(1)
  show regex("^\[(\d+)\]\s"): it => [
    [#ref-counter.display()]
  ]
  for target in section-refs.at(loc) {
    block(cite(target, form: "full"))
    ref-counter.step()
  }
  section-refs.update(())
})

astrojhgu avatar Feb 17 '24 18:02 astrojhgu

@astrojhgu

the section-refs.update action can be merged into each section-bib() function

I thought of this too; the reason it is separated is because people often see the code and ask "how can I modify it so that references are per-section", or "how can I incorporate a call to section-bib automatically"? This type of answer plants the seed of how to make your own adjustments as needed (in our case, a small introduction of other concepts beyond the answer itself, like tying into other show rules).

There seems to be a bug

You're absolutely right! I've updated my answer above to include these adjustments and verified with a new screenshot.

Would you consider surrounding your code block inside a <details> ... </details> wrapper now that the code block is updated? It will help ensure there is just one community-maintained snippet.

ntjess avatar Feb 19 '24 14:02 ntjess

Would you consider surrounding your code block inside a <details> ... </details> wrapper now that the code block is updated? It will help ensure there is just one community-maintained snippet.

Sure, I have modified the code block. (not sure if I did it correctly, as I'm not very familiar with the <details /> tag).

astrojhgu avatar Feb 21 '24 09:02 astrojhgu

Sure, I have modified the code block. (not sure if I did it correctly, as I'm not very familiar with the <details /> tag).

Just type /d at the start of a new line and then press Enter (in the web version of GitHub). It's called a "slash command". For convenience.

https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections

https://docs.github.com/en/issues/tracking-your-work-with-issues/about-slash-commands

Andrew15-5 avatar Feb 21 '24 09:02 Andrew15-5

(I fixed the formatting of the <details /> block. It needs a newline between <details> and the code block.)

laurmaedje avatar Feb 21 '24 09:02 laurmaedje

For the peace of mind, I add the newline above and below the code block.

(The /details slash command also adds an unnecessary in my opinion p tag and a whitespace after </details>, so it's kinda useful, and I hate it at the same time.)

Andrew15-5 avatar Feb 21 '24 09:02 Andrew15-5

If you use IEEE reference format (or something similar that goes by number), this will work. The only downside is it won't merge adjacent references, but you can accommodate this by using cite rules instead of ref rules.

#show bibliography: none
#bibliography("refs.bib")
// Keep track of all references, clearing every time a new heading is shown
#let section-refs = state("section-refs", ())

// Add bibliography references to the current section's state
#show ref: it => {
  if it.element != none {
    // Citing a document element like a figure, not a bib key
    // So don't update refs
    it
    return
  }
  section-refs.update(old => {
    if it.target not in old {
      old.push(it.target)
    }
    old
  })
  locate(loc => {
    let idx = section-refs.at(loc).position(el => el == it.target)
    "[" + str(idx + 1) + "]"
  })
}

// Print the "per-section" bibliography
#let section-bib() = locate(loc => {
  let ref-counter = counter("section-refs")
  ref-counter.update(1)
  show regex("^\[(\d+)\]\s"): it => [
    [#ref-counter.display()]
  ]
  for target in section-refs.at(loc) {
    block(cite(target, form: "full"))
    ref-counter.step()
  }
})

// Clear the previously stored references every time a level 1 heading
// is created.
#show heading.where(level: 1): it => {
  section-refs.update(())
  it
}



= First Section
My reference @plucked-string and another @plot

#section-bib()

= Second Section
Another reference @plucked-string-extensions @plot @plucked-string-extensions @plucked-string

#section-bib()

image

Does anyone know how to modify it for apa references and adding page numbers?

I tried adjusting it but I can't figure out a way to make page numbers work. I currently cite works like this: @cognitive-neuroscience[s.~110]

#show bibliography: none
#bibliography("bibliography.yml", title: "Referenser", style: "apa")
// Keep track of all references, clearing every time a new heading is shown
#let section-refs = state("section-refs", ())

// Add bibliography references to the current section's state
#show ref: it => {
  if it.element != none {
    // Citing a document element like a figure, not a bib key
    // So don't update refs
    it
    return
  }
  section-refs.update(old => {
    if it.target not in old {
      old.push(it.target)
    }
    old
  })
  locate(loc => {
    let ref_pos = section-refs.at(loc).position(el => el == it.target)
    let ref_target = section-refs.at(loc).at(ref_pos)
    let apa_normal_citation = cite(ref_target, form: "normal")

    apa_normal_citation
  })
}

// Print the "per-section" bibliography
#let section-bib() = locate(loc => {
  let ref-counter = counter("section-refs")
  ref-counter.update(1)
  show regex("^\[(\d+)\]\s"): it => [
    [#ref-counter.display()]
  ]
  for target in section-refs.at(loc) {
    block(cite(target, form: "full"))
    ref-counter.step()
  }
})

// Clear the previously stored references every time a level 1 heading
// is created.
#show heading.where(level: 1): it => {
  section-refs.update(())
  it
}

cascading-jox avatar May 29 '24 10:05 cascading-jox

This would be very helpful for dissertations, where in some cases references and (own) publications are required to be listed separately. It would be wonderful if this worked as expected:

#bibliography("references.bib")
#pagebreak(weak: true)
#bibliography("publications.bib", full: true, title: "List of Publications")

oaken-source avatar Jun 12 '24 21:06 oaken-source

I want to flag up a repo with a minimal working example for an approach that basically solves this issue (at least for my needs).

https://github.com/isometricneko/typst-example

I'm not fully understanding how it works, but it allows me to see the bibliography in chapter-level and document-level previews, everything compiles as expected, and VS Code does not complain about using citations without a bibliography either.

The basic idea (from the author) is:

you can use state to store the #bibliography for each of the subfiles and update the value of the state to none in the main file

So well done to @isometricneko !

The only situation I have found this approach creating problems is when using pandoc to transpile to .tex.

jordantgh avatar Jun 15 '24 20:06 jordantgh