Created
October 19, 2025 20:59
-
-
Save danmackinlay/c81d6bb62e0935067c45130c48edb03a to your computer and use it in GitHub Desktop.
zotero bibliography with tags
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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