A snippet to debug bibliography entries by showing what CSL receives

Here’s a snippet to debug bibliography entries by showing what CSL actually receives.

Example usage

#import "debug-cite.typ": debug-cite

#debug-cite(<key>)

#bibliography("path/to/your.bib") // or *.yaml
Full code
#set page(height: auto, width: 300pt, margin: 15pt)

#import "debug-cite.typ": debug-cite

#debug-cite(<key>)

#bibliography(bytes(
  ```yaml
  key:
    type: video
    title: Title of Episode
    author:
      - Creator
      - Another contributor
    director: Director
    date: 2161
    note: Season 1, Episode 1.
    archive: Streaming Platform
    url: https://example.com
    parent:
      title: Title of TV Series
      author: Production company
  ```.text,
))

Why?

Biliography sources will be converted multiple times. Occasionally it is really difficult to predict which type and what variables the CSL style will receive.

Therefore, it would be helpful if we can inspect the bibliography entry.
The citegeist package tells you what biblatex thinks, and this debug-cite snippet tells you what citationberg thinks.

Let’s take the entry type for an example.

mermaid.js code
flowchart TB

subgraph source
  bib
  yaml
end

subgraph typst
  biblatex
  hayagriva
  citationberg
end

bib["“@type” in *.bib file"]
-->|🔍 citegeist| biblatex["biblatex::EntryType<br>(33 types + 1 Unknown)"]
--> hayagriva["hayagriva::types::EntryType<br>(30 types)"]
-->|🔍 debug-cite| citationberg["citationberg::taxonomy::Kind<br>(45 CSL types)"]
--> output["Output<br>(PDF/SVG/PNG/HTML)"]

yaml["“type: …” in *.yaml file"] <-->|identical| hayagriva
csl[CSL style] --> output
  1. We write a certain type in a BibLaTeX *.bib file.

    Say, @video.

  2. When typst loads the *.bib file using the biblatex crate, the type is classified into one of the 34 biblatex::EntryTypes.

    In this case, @video becomes biblatex::EntryType::Unknown("video").

  3. Now typst uses the hayagriva crate to manage bibliography, and further converts it to one of the 30 hayagriva::types::EntryTypes.

    Here, all biblatex’s unknown type turn into hayagriva’s EntryType::Misc. Although hayagriva does have a EntryType::Video variant, Unknown("video") is no exception.

  4. Finally typst passes data to the citationberg crate, and typesets the biliography with a CSL style. The CSL file might leverage <if type="…"> for conditional rendering. The CSL specification defines 45 types, so citationberg::taxonomy::Kind has 45 variants.

    Guess which citationberg’s type will match hayagriva’s EntryType::Misc? Kind::Document, and also Kind::Webpage if it has url.

    Note that the above only applies to *.bib. If we use *.yaml instead, then there’s always a single match, which is

    • Kind::MotionPicture if the video does not have a parent (not an episode in a series), or
    • Kind::Broadcast if it has.

(@_@)

Test code for the above behaviour
#import "debug-cite.typ": debug-cite

= BibTeX
#debug-cite(<youtube-bib>)
= YAML
#debug-cite(<youtube-yaml>)
= YAML + parent
#debug-cite(<youtube-yaml-parent>)

#bibliography((
  bytes(
    ```yaml
    youtube-yaml:
      type: video
      author: John Doe
      title: How to Use BibLaTeX for Video Citations
      date: 2025-03-15
      url:
        value: https://www.youtube.com/watch?v=abc123def456
        date: 2025-10-23
    youtube-yaml-parent:
      type: video
      author: John Doe
      title: How to Use BibLaTeX for Video Citations
      date: 2025-03-15
      url:
        value: https://www.youtube.com/watch?v=abc123def456
        date: 2025-10-23
      parent:
        title: A list
    ```.text,
  ),
  bytes(
    ```bib
    @video{youtube-bib,
      author       = {John Doe},
      title        = {How to Use BibLaTeX for Video Citations},
      date         = {2025-03-15},
      url          = {https://www.youtube.com/watch?v=abc123def456},
      urldate      = {2025-10-23},
    }
    ```.text,
  ),
))

Definition of debug-cite

// The lists of types and varieables are copied from the CSL spec.
// https://docs.citationstyles.org/en/v1.0.2/specification.html

#let types = (
  "article",
  "article-journal",
  "article-magazine",
  "article-newspaper",
  "bill",
  "book",
  "broadcast",
  "chapter",
  "classic",
  "collection",
  "dataset",
  "document",
  "entry",
  "entry-dictionary",
  "entry-encyclopedia",
  "event",
  "figure",
  "graphic",
  "hearing",
  "interview",
  "legal_case",
  "legislation",
  "manuscript",
  "map",
  "motion_picture",
  "musical_score",
  "pamphlet",
  "paper-conference",
  "patent",
  "performance",
  "periodical",
  "personal_communication",
  "post",
  "post-weblog",
  "regulation",
  "report",
  "review",
  "review-book",
  "software",
  "song",
  "speech",
  "standard",
  "thesis",
  "treaty",
  "webpage",
)

#let non-number-standard-variables = (
  "abstract",
  "annote",
  "archive",
  "archive_collection",
  "archive_location",
  "archive-place",
  "authority",
  "call-number",
  "citation-key",
  "citation-label",
  "collection-title",
  "container-title",
  "container-title-short",
  "dimensions",
  "division",
  "DOI",
  "event",
  "event-title",
  "event-place",
  "genre",
  "ISBN",
  "ISSN",
  "jurisdiction",
  "keyword",
  "language",
  "license",
  "medium",
  "note",
  "original-publisher",
  "original-publisher-place",
  "original-title",
  "part-title",
  "PMCID",
  "PMID",
  "publisher",
  "publisher-place",
  "references",
  "reviewed-genre",
  "reviewed-title",
  "scale",
  "source",
  "status",
  "title",
  "title-short",
  "URL",
  "volume-title",
  "year-suffix",
)

#let number-standard-variables = (
  "chapter-number",
  "citation-number",
  "collection-number",
  "edition",
  "first-reference-note-number",
  "issue",
  "locator",
  "number",
  "number-of-pages",
  "number-of-volumes",
  "page",
  "page-first",
  "part-number",
  "printing-number",
  "section",
  "supplement-number",
  "version",
  "volume",
)

#let date-variables = (
  "accessed",
  "available-date",
  "event-date",
  "issued",
  "original-date",
  "submitted",
)

#let name-variables = (
  "author",
  "chair",
  "collection-editor",
  "compiler",
  "composer",
  "container-author",
  "contributor",
  "curator",
  "director",
  "editor",
  "editorial-director",
  "editor-translator",
  "executive-producer",
  "guest",
  "host",
  "illustrator",
  "interviewer",
  "narrator",
  "organizer",
  "original-author",
  "performer",
  "producer",
  "recipient",
  "reviewed-author",
  "script-writer",
  "series-creator",
  "translator",
)

#let match-type = (
  ```xml
  <text value='"type": ['/>
  <group delimiter=", ">
    [[inner]]
  </group>
  <text value="], "/>
  ```
    .text
    .replace(
      "[[inner]]",
      {
        let template = ```xml
        <choose><if type="[[type]]">
          <text value="[[type]]" prefix='"' suffix='"' />
        </if></choose>
        ```.text
        types.map(t => template.replace("[[type]]", t)).join()
      },
    )
)

#let match-standard-variable(group, variables) = {
  ```xml
  <text value='"[[group]]": { '/>
  <group delimiter=", ">
    [[inner]]
  </group>
  <text value=" }, "/>
  ```
    .text
    .replace("[[group]]", group)
    .replace("[[inner]]", {
      let template = ```xml
      <text variable="[[variable]]" prefix='"[[variable]]": "' suffix='"' />
      ```.text
      variables.map(v => template.replace("[[variable]]", v)).join()
    })
}

#let match-date-variable = {
  ```xml
  <text value='"date-variable": { '/>
  <group delimiter=", ">
    [[inner]]
  </group>
  <text value=' }, '/>
  ```
    .text
    .replace(
      "[[inner]]",
      date-variables
        .map(
          v => `<date variable="[[v]]" form="numeric" prefix='"[[v]]": "' suffix='"' />`
            .text
            .replace("[[v]]", v),
        )
        .join(),
    )
}

#let match-name-variable = {
  ```xml
  <text value='"name-variable": { '/>
  <group delimiter=", ">
    [[inner]]
  </group>
  <text value='} '/>
  ```
    // There might be a typst bug for the trailing suffix.
    .text
    .replace(
      "[[inner]]",
      name-variables
        .map(
          v => `<names variable="[[v]]" prefix='"[[v]]": "' suffix='"' />`
            .text
            .replace("[[v]]", v),
        )
        .join(),
    )
}


#let csl = (
  ```xml
  <?xml version='1.0' encoding='utf-8'?>
  <style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0">
    <info>
      <title>Which CSL</title>
      <id>https://typst.app/universe/package/which-csl</id>
    </info>
    <macro name="debug">
      <group prefix="{ " suffix=" }">
        [[match-type]]
        [[match-standard-variables]]
        [[match-date-variable]]
        [[match-name-variable]]
      </group>
    </macro>
    <citation>
      <layout>
        <text macro="debug" />
      </layout>
    </citation>
    <bibliography>
      <layout>
        <text value="This CSL style is only intended for citations, not bibliography." />
      </layout>
    </bibliography>
  </style>
  ```
    .text
    .replace("[[match-type]]", match-type)
    .replace(
      "[[match-standard-variables]]",
      (
        match-standard-variable(
          "non-number-standard-variables",
          non-number-standard-variables,
        ),
        match-standard-variable(
          "number-standard-variables",
          number-standard-variables,
        ),
      ).join(),
    )
    .replace("[[match-date-variable]]", match-date-variable)
    .replace("[[match-name-variable]]", match-name-variable)
)

#let debug-cite(key, pretty-print: true, ..args) = {
  show: body => if pretty-print {
    // Convert the URL to plain text
    show link: it => it.body
    // Parse as JSON
    show regex(".+"): it => json(bytes(it.text))
    body
  } else {
    body
  }

  cite(key, style: bytes(csl), ..args)
}

Links and future plan

This snippet is inspired by How to parse BibTex "@incollection" or Hayagriva "type: anthos, parent-type: anthology" entry in CSL? - #3 by DiegoCasas.

It was developed while I was debugging How do I determine which type a legislation bibentry will have in CSL? and Is there a way to cite TV Shows and Episodes with the MLA style?.

I may publish it as a typst package later.
However, I seem to have found a bug of typst for nested <group suffix="}"> in CSL. I won’t publish it until it’s further tested.
Update: Confirmed. Nested group's suffixes may get trimmed · Issue #411 · typst/hayagriva · GitHub

9 Likes

Hi @Y.D.X

That’s exactly what I needed, but I was not clever enough to devise something like that :sweat_smile:

I will certainly use your snippet for writing my dissertation. Since my Uni requires me to follow Brazilian standards (ABNT), I’ve been developing my own CSL style.

1 Like

Glad to know it’s helpful!

But "associacao-brasileira-de-normas-tecnicas" (Associação Brasileira de Normas Técnicas) is one of the built-in bibliography styles. Have you tried that?

(The source CSL is in available in CSL’s official GitHub.)

1 Like

Yes, I tried that one, but (1) it is the author-date form–I’m using the numeric form; and (2) it is translated to Portuguese (e.g., months), while I’m writing my dissertation in English.

1 Like