Skip to content

fix(ast): unwrap mc:AlternateContent so shapes reach the AST - #5

Closed
Ryujiyasu wants to merge 1 commit into
kklimuk:mainfrom
Ryujiyasu:fix/mc-alternate-content
Closed

Ryujiyasu wants to merge 1 commit into
kklimuk:mainfrom
Ryujiyasu:fix/mc-alternate-content

Conversation

@Ryujiyasu

Copy link
Copy Markdown

Fixes the reader half of #4.

Word writes every modern shape and text box as <mc:AlternateContent> — a wps <mc:Choice> holding the <w:drawing>, beside an <mc:Fallback> holding a legacy <w:pict>. Neither tag is matched by readRun or walkRunContainer, so the whole subtree is dropped before it reaches the AST, including the ChartRun placeholder the reader intends to emit ("surface as ChartRun placeholders so callers know 'something visual lives here'").

The placeholder contract already held for the branch almost nobody writes — strip a document to its mc:Fallback and read prints `[drawing]` — and was missed for the one Word always writes. This makes it hold for both.

$ docx read fixture-with-a-textbox.docx
-`[shape]`Acme Corporation shall deliver the goods by Friday. <!-- p0 -->
+Acme Corporation shall deliver the goods by Friday. <!-- p0 -->

What this is and is not

This is option (a) from the issue: it removes the silent part. An agent can now see that a shape is there and reach for raw. It does not make the text inside a text box readable — docx find tests/fixtures/multi-column.docx "Permission to make digital" still returns no matches, because that needs w:txbxContent paragraphs to become addressable blocks, and a text box is a separate story like a header, so it needs a locator decision that is yours to make. Happy to do that too if you tell me what shape you want the locators to take.

Shape of the change

One helper plus one call site in each of the two walkers — 26 lines. resolveAlternateContent returns its input unchanged when there is no wrapper, which is the overwhelmingly common case, so the hot path is one Array.some over a run's children.

Choice is preferred over Fallback because it is what Word itself reads; Fallback is taken when a producer wrote only that.

Tests

Four cases in tests/core/ast/read.test.ts, all against synthetic XML so they need no new fixture: a Choice branch reaching the run walker, a Fallback-only wrapper, Choice winning when both are present (taking the wrong branch is silent, so it is worth pinning), and text either side of a wrapper coming through untouched.

bun run check passes. bun test tests/core tests/cli gives 1625 pass; the three failures on my machine (docx info skill SKILL.md sync, the compiled-binary upgrade test, and the LibreOffice integration test) fail identically on unpatched main here — no soffice, no compiled binary. I skipped the pre-commit hook for that reason; CI should have a better environment than mine.

Windows 11, bun 1.4.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JsWGQLZuWdd5aksp63VeVM

Word writes every modern shape and text box as <mc:AlternateContent> with a
wps <mc:Choice> and a legacy <w:pict> <mc:Fallback>. Neither tag is matched by
readRun or walkRunContainer, so the whole subtree is dropped before it reaches
the AST -- including the ChartRun placeholder the reader intends to emit.

Resolve the wrapper to its Choice branch (Fallback when a producer wrote only
that) in both walkers. The placeholder contract already held for the branch
almost nobody writes; this makes it hold for the one Word always writes.

Refs kklimuk#4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01JsWGQLZuWdd5aksp63VeVM
@kklimuk kklimuk closed this in 8e0e2bf Sep 4, 2026
@kklimuk

kklimuk commented Sep 4, 2026

Copy link
Copy Markdown
Owner

@Ryujiyasu I've pushed a commit and a new version that solves your problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants