Skip to content

Instantly share code, notes, and snippets.

@danmackinlay
Created October 19, 2025 20:59
Show Gist options
  • Save danmackinlay/c81d6bb62e0935067c45130c48edb03a to your computer and use it in GitHub Desktop.
Save danmackinlay/c81d6bb62e0935067c45130c48edb03a to your computer and use it in GitHub Desktop.
zotero bibliography with tags
#!/bin/bash
# Zotero Better BibTeX URL β†’ DOCX bibliography
# Usage: ./zotero-tagged-bib.sh "http://127.0.0.1:23119/better-bibtex/export?/group;id:6257749/journal_of_alignment.csljson" [output_name]
# Requires:
#
# * Zotero with Better BibTeX extension
# * curl
# * pandoc with citeproc support
# * jq
## You need the following postscript in user script to include tags in the CSL JSON export:
#
# if (Translator.BetterCSL) {
# if (zotero.tags && zotero.tags.length) {
# csl.keyword = zotero.tags.map(t => t.tag).join(', ');
# }
# }
set -euo pipefail
EXPORT_URL="${1:-}"
OUTPUT_NAME="${2:-bibliography}"
TEMP_DIR="$(mktemp -d)"
BBT_HOST_DEFAULT="http://127.0.0.1:23119"
if [[ -z "${EXPORT_URL}" ]]; then
echo "❌ Error: Missing Better BibTeX export URL"
echo "Provide the exact URL from Zotero (right-click β†’ Better BibTeX β†’ Copy export URL)."
echo "Example:"
echo " ./zotero-tagged-bib.sh \"${BBT_HOST_DEFAULT}/better-bibtex/export?/group;id:6257749/journal_of_alignment.json\" my-bib"
exit 1
fi
# Basic sanity checks
if ! command -v curl >/dev/null 2>&1; then
echo "❌ curl is required"; exit 1
fi
if ! command -v pandoc >/dev/null 2>&1; then
echo "❌ pandoc is required (e.g., brew install pandoc or apt install pandoc)"; exit 1
fi
echo "πŸ“š Zotero BBT URL β†’ RTF"
echo "URL: ${EXPORT_URL}"
echo "Out: ${OUTPUT_NAME}.rtf"
# Create CSL style (simple author, title, year, URL, tags)
CSL_FILE="${TEMP_DIR}/simple-with-tags.csl"
cat > "${CSL_FILE}" << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0">
<info>
<title>Simple Bibliography with Tags (Authors+Year, Bold Tags)</title>
<id>http://www.zotero.org/styles/simple-with-tags-robust</id>
<updated>2025-10-18T06:00:00+00:00</updated>
</info>
<!-- Author/editor fallback -->
<macro name="names-or-title">
<choose>
<if variable="author editor" match="any">
<names variable="author">
<name and="text" delimiter=", " name-as-sort-order="first"/>
</names>
<choose>
<if variable="author" match="none">
<names variable="editor">
<name and="text" delimiter=", " name-as-sort-order="first"/>
<label form="short" prefix=" (" suffix=")" strip-periods="true"/>
</names>
</if>
</choose>
</if>
<else>
<text variable="title"/>
</else>
</choose>
</macro>
<!-- Year fallback -->
<macro name="year">
<choose>
<if variable="issued">
<date variable="issued">
<date-part name="year"/>
</date>
</if>
<else>
<text value="n.d."/>
</else>
</choose>
</macro>
<!-- Title italicized -->
<macro name="title-italic">
<text variable="title" font-style="italic"/>
</macro>
<!-- URL, printed if present -->
<macro name="url">
<text variable="URL"/>
</macro>
<!-- Bold tags from keyword field -->
<macro name="tags-bold">
<choose>
<if variable="keyword">
<group delimiter=" ">
<text value="Tags:" font-weight="bold"/>
<text variable="keyword" font-weight="bold"/>
</group>
</if>
</choose>
</macro>
<citation>
<layout>
<group delimiter=" ">
<text macro="names-or-title"/>
<text macro="year" prefix="(" suffix=")"/>
</group>
</layout>
</citation>
<bibliography hanging-indent="false" entry-spacing="1">
<layout suffix=".">
<group delimiter=". ">
<text macro="names-or-title"/>
<group delimiter=" ">
<text macro="title-italic"/>
<text macro="year" prefix="(" suffix=")"/>
</group>
<text macro="url"/>
<text macro="tags-bold"/>
</group>
</layout>
</bibliography>
</style>
EOF
# Minimal markdown that forces full bibliography rendering
MD_FILE="${TEMP_DIR}/bib.md"
cat > "${MD_FILE}" << 'EOF'
---
nocite: |
@*
---
# Bibliography
EOF
# Fetch JSON
JSON_FILE="${TEMP_DIR}/library.json"
echo "πŸ”„ Fetching export..."
HTTP_STATUS=$(curl -sS -w "%{http_code}" -H "User-Agent: zotero-script" "${EXPORT_URL}" -o "${JSON_FILE}" || true)
if [[ "${HTTP_STATUS}" != "200" ]]; then
echo "❌ Export failed (HTTP ${HTTP_STATUS})."
echo "Response snippet:"
head -c 500 "${JSON_FILE}" || true
echo
echo "Tips:"
echo " β€’ Ensure Zotero is running with Better BibTeX enabled."
echo " β€’ Verify the URL in a browser; it should download JSON."
echo " β€’ Use .json in the URL to get CSL JSON output."
exit 1
fi
JSON_FILE="${TEMP_DIR}/library.json"
CSL_FILE="${TEMP_DIR}/simple-with-tags.csl"
MD_FILE="${TEMP_DIR}/bib.md"
# Ensure absolute paths (Pandoc can be picky with cwd)
JSON_ABS="$(cd "$(dirname "$JSON_FILE")" && pwd)/$(basename "$JSON_FILE")"
CSL_ABS="$(cd "$(dirname "$CSL_FILE")" && pwd)/$(basename "$CSL_FILE")"
cat > "${MD_FILE}" << EOF
---
title: Bibliography
bibliography: ${JSON_ABS}
csl: ${CSL_ABS}
link-citations: true
nocite: |
@*
bibliography-format: csljson
---
EOF
# Validate JSON looks like array/object
if ! grep -qE '^[[:space:]]*[\[\{]' "${JSON_FILE}"; then
echo "❌ Export did not return JSON:"
head -c 500 "${JSON_FILE}" || true
exit 1
fi
# Optional: count items if jq exists
if command -v jq >/dev/null 2>&1; then
COUNT=$(jq 'length' "${JSON_FILE}" 2>/dev/null || echo "unknown")
echo "βœ… Export OK (${COUNT} items)"
else
echo "βœ… Export OK"
fi
# Convert to DOCX
echo "πŸ”§ Converting to DOCX (pandoc)..."
pandoc "${MD_FILE}" \
--bibliography="${JSON_FILE}" \
--csl="${CSL_FILE}" \
--citeproc \
--metadata bibliography-format=csljson \
-o "${OUTPUT_NAME}.docx"
echo "βœ… Done: ${OUTPUT_NAME}.docx"
echo "πŸ“‹ Paste into Google Docs as needed."
# Cleanup
rm -rf "${TEMP_DIR}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment