feat: add inject_citations tool for Word .docx citation injection#85
Open
mlenburg wants to merge 3 commits into
Open
feat: add inject_citations tool for Word .docx citation injection#85mlenburg wants to merge 3 commits into
mlenburg wants to merge 3 commits into
Conversation
Adds a new `inject_citations` MCP tool that replaces `<zcite key="ITEMKEY"/>` placeholders in a Word .docx with native Zotero field codes (ADDIN ZOTERO_ITEM CSL_CITATION), producing a `_cited.docx` that Zotero's Word Plugin can refresh and reformat. Addresses all feedback from PR review: - Fix entity-unescape order in parseZciteTag (& last to avoid double-unescape) - Fail loudly on fallback instead of emitting invalid OOXML inside <w:t> - Path validation: require absolute path, reject _cited.docx as input - Overwrite protection: refuse to clobber existing output without overwrite=true - Opt-in preference gate (enableCitationInjection) with UI toggle in Preferences - Populate uris[] with zotero://select URIs so citations link on Zotero refresh - Randomise citationID to avoid collisions with existing fields - Bibliography inserted before <w:sectPr> (OOXML requires it as last body child) - <w:r[^>]*> in all run patterns to match Word's attribute-bearing runs Pure functions extracted to citationInjector.pure.ts (no Gecko deps) and tested with 36 Mocha+Chai unit tests covering parseZciteTag, formatCitationText, generateCitationFieldCode, validateDocxPath, xmlEscape, and the preprocessing regex. Run with: npm test Note: fflate (zip read/write) still runs synchronously on the main thread; async decompression deferred per maintainer guidance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace `import { PathUtils } from "resource://gre/modules/PathUtils.sys.mjs"`
with `declare const PathUtils: any` / `declare const IOUtils: any` — esbuild
cannot resolve resource:// URLs at bundle time; they are ambient globals in
the Zotero/Gecko runtime
- Cast response.body to string in callInjectCitations to satisfy strict TS
- Add generated i10n type entries for pref-citation-injection-* FTL keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zotero.Prefs.get() without a default returns undefined for unset prefs, causing !enabled to throw even when the toggle is visibly on in the UI. The gate in streamableMCPServer.ts (which passes true as default) is sufficient — remove the duplicate check from citationInjector.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new
inject_citationsMCP tool that replaces<zcite key="ITEMKEY"/>placeholders in a Word.docxfile with native Zotero field codes (ADDIN ZOTERO_ITEM CSL_CITATION), producing a_cited.docxthat the Zotero Word Plugin can refresh and reformat in any citation style.This addresses all feedback from the initial PR review (except the
zipSyncthreading issue, which is deferred per your guidance):&is now replaced last inparseZciteTag, preventing&quot;from double-unescaping<w:t>(corrupt OOXML), the injector now throws a descriptive error if a placeholder cannot be matched within a valid<w:r>run<w:r>attribute matching: all run patterns use<w:r[^>]*>to match Word's attribute-bearing runs (e.g.w:rsidR)_cited.docxas input; refuses to overwrite existing output withoutoverwrite=trueenableCitationInjectionpref with toggle in the Preferences UI;inject_citationsis hidden from the tool list when disableduris[]populated: each citation item carries azotero://select/library/items/<KEY>URI so Zotero relinks citations on refresh rather than treating them as unlinked/embeddedMath.random().toString(36)instead of sequentialcit1, cit2…to avoid collisions with existing fields<w:sectPr>(OOXML requires it as the last body child)Tests
Pure functions extracted to
citationInjector.pure.ts(no Gecko dependencies) and covered by 36 Mocha + Chai unit tests:Tests cover
parseZciteTag,formatCitationText,generateCitationFieldCode,validateDocxPath,xmlEscape, and the preprocessing regex (including the non-greedy fix and<w:r>attribute matching).Note on zipSync
unzipSync/zipSync(fflate) still run synchronously on the main thread. For typical manuscript sizes this completes in well under a second. Async decompression is deferred as noted in your review.