### Run a framework example with pnpm Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/examples/frameworks/README.md Install dependencies and start the React example. Replace 'react' with 'vue', 'svelte', or 'solid' to run other framework examples. ```sh pnpm install pnpm dev:react ``` -------------------------------- ### Start Storybook for user verification Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/docs/docx-layout-context-fragments-implementation-plan.md Start Storybook to allow user verification. Report the selected local port and keep the server running until verification is complete. ```bash pnpm storybook ``` -------------------------------- ### GitHub Copilot example prompt Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Example prompt to extract text from a PowerPoint file. ```text Extract all text from /Users/me/Documents/deck.pptx ``` -------------------------------- ### Codex CLI example prompt Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Example command to ask Codex to show formulas in a sheet. ```bash codex "Show me all formulas in Sheet1 of /Users/me/Documents/model.xlsx" ``` -------------------------------- ### Download and install prebuilt binary Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Bash script to download, verify, and install the ooxml-mcp-server binary. Replace TAG and ASSET with the latest release tag and your platform's asset name. The shasum command must print 'OK' to confirm the download is valid. ```bash TAG=v0.51.0 # replace with the latest tag from the Releases page ASSET=ooxml-mcp-server-aarch64-apple-darwin # pick your platform curl -L -o ooxml-mcp-server "https://github.com/yukiyokotani/office-open-xml-viewer/releases/download/${TAG}/${ASSET}" curl -L -o sums.txt "https://github.com/yukiyokotani/office-open-xml-viewer/releases/download/${TAG}/${ASSET}.sha256" shasum -a 256 -c sums.txt # must print "OK" chmod +x ooxml-mcp-server mv ooxml-mcp-server /usr/local/bin/ ``` -------------------------------- ### Install ooxml-mcp-server from source with cargo Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Builds and installs the MCP server from the GitHub repository. Requires Rust. The binary is placed in ~/.cargo/bin/ooxml-mcp-server, which must be on PATH. Verify with ooxml-mcp-server --version. ```bash # Install Rust if needed: https://rustup.rs cargo install --git https://github.com/yukiyokotani/office-open-xml-viewer.git \ --package ooxml-mcp-server ``` -------------------------------- ### Develop: build WASM and run dev server Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/site/README.md Builds the WASM parsers once, then starts the site dev server. The predev/prebuild scripts copy demo samples from packages/*/public/demo into public/samples/. ```bash pnpm build:wasm # build the parsers first (once) pnpm --filter @silurus/ooxml-site dev ``` -------------------------------- ### Conformance Fixture Setup and Execution Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/docx/tests/visual/conformance-fixture.html Imports required modules and defines helper functions for validating layout. The main function runs a conformance case, loads the document in main and worker modes, and compares their outputs. ```typescript import { DocxDocument } from '/src/index.ts'; import { layoutDocument } from '/src/document-layout.ts'; import { createLayoutServices } from '/src/layout-runtime.ts'; import { assertDocumentLayout, layoutFingerprint, } from '/src/layout/invariants.ts'; import { CONFORMANCE_CASES } from '/src/conformance/cases.ts'; import { generateConformanceDocx } from '/src/conformance/generate.ts'; ``` ```typescript const finiteRun = (run) => ['x', 'y', 'w', 'h', 'fontSize'].every((key) => Number.isFinite(run[key])) && run.w >= 0 && run.h >= 0 && run.fontSize >= 0; ``` ```typescript const normalizeNumber = (value) => { const rounded = Number(value.toFixed(6)); return Object.is(rounded, -0) ? 0 : rounded; }; ``` ```typescript const records = (root) => { const output = []; const visit = (value) => { if (value === null || typeof value !== 'object') return; if (Array.isArray(value)) { value.forEach(visit); return; } output.push(value); Object.values(value).forEach(visit); }; visit(root); return output; }; ``` ```typescript const authoredGeometry = (layout) => { const retained = layout.pages.flatMap((page) => page.layers.roots.flatMap(({ node }) => records(node))); const tables = retained .filter(({ kind, columnWidthsPt, borders }) => kind === 'table' && Array.isArray(columnWidthsPt) && Array.isArray(borders)) .map(({ id, columnWidthsPt, borders }) => ({ id, columnWidthsPt: columnWidthsPt.map(normalizeNumber), borderEndpoints: borders.map(({ from, to }) => [ normalizeNumber(from.xPt), normalizeNumber(from.yPt), normalizeNumber(to.xPt), normalizeNumber(to.yPt), ]), })) .sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0); const drawingRecords = retained .filter(({ kind, flowBounds, bounds }) => (kind === 'drawing' && (flowBounds ?? bounds) && typeof (flowBounds ?? bounds) === 'object') || (kind === 'resource' && bounds && typeof bounds === 'object')) .map(({ id, drawingId, resourceKey, flowBounds, bounds }) => ({ id: id ?? drawingId ?? resourceKey, widthPt: normalizeNumber((flowBounds ?? bounds).widthPt), heightPt: normalizeNumber((flowBounds ?? bounds).heightPt), })); const drawings = [...new Map( drawingRecords.map((record) => [record.id, record]), ).values()] .sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0); return { pageBoxes: layout.pages.map(({ geometry }) => ({ widthPt: normalizeNumber(geometry.widthPt), heightPt: normalizeNumber(geometry.heightPt), contentTopPt: normalizeNumber(geometry.contentTopPt), contentBottomPt: normalizeNumber(geometry.contentBottomPt), })), tables, drawings, }; }; ``` ```typescript window.runConformanceCase = async (index) => { const testCase = CONFORMANCE_CASES[index]; if (!testCase) throw new RangeError(`unknown conformance case ${index}`); const bytes = generateConformanceDocx(testCase); const main = await DocxDocument.load(bytes.slice().buffer, { useGoogleFonts: false }); const worker = await DocxDocument.load(bytes.slice().buffer, { mode: 'worker', useGoogleFonts: false }); try { const services = createLayoutServices(main.document); const layout = layoutDocument(main.document, services, { currentDateMs: 0 }); assertDocumentLayout(layout); const clonedLayout = structuredClone(layout); if (JSON.stringify(clonedLayout) !== JSON.stringify(layout)) { throw new Error('retained layout changed across structuredClone'); } const mainRuns = []; const workerRuns = []; for (let pageIndex = 0; pageIndex < main.pageCount; pageIndex += 1) { mainRuns.push(await main.collectPageRuns(pageIndex, { width: 612, dpr: 1 })); workerRuns.push(await worker.collectPageRuns(pageIndex, { width: 612, dpr: 1 })); } const mainSizes = Array.from( { length: main.pageCount }, (_, pageIndex) => main.pageSize(pageIndex), ); const workerSizes = Array.from( { length: worker.pageCount }, (_, pageIndex) => worker.pageSize(pageIndex), ); const text = mainRuns.flat().map(({ text: runText }) => runText).join(''); return { caseId: testCase.id, expected: testCase.expected, pageCount: main.pageCount, mainSizes, workerSizes, runsFinite: mainRuns.flat().every(finiteRun) && workerRuns.flat().every(finiteRun), targetTextPresent: text.includes(testCase.expected.targetText), cloneSafe: true, layoutFingerprint: layoutFingerprint(layout), mainWorkerParity: { pageCount: main.pageCount === worker.pageCount, sizes: JSON.stringify(mainSizes) === JSON.stringify(workerSizes), runs: JSON.stringify(mainRuns) === JSON.stringify(workerRuns), }, authoredGeometry: authoredGeometry(layout), }; } finally { main.destroy(); worker.destroy(); } }; document.body.dataset.status = 'ready'; document.body.dataset.caseCount = String(CONFORMANCE_CASES.length); ``` -------------------------------- ### Use full path to ooxml-mcp-server in MCP config Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md When the MCP server fails to start with a 'command not found' error because the launcher does not inherit the shell PATH, use the full path to the binary. First find the full path with echo, then set the command in your JSON or TOML MCP configuration. ```bash # Find the full path echo ~/.cargo/bin/ooxml-mcp-server ``` ```json "command": "/Users/you/.cargo/bin/ooxml-mcp-server" ``` ```toml command = "/Users/you/.cargo/bin/ooxml-mcp-server" ``` -------------------------------- ### Development commands: pnpm install, build:wasm, storybook, typecheck, vrt, build Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/README.md Common development commands for the project. Requires pnpm, Rust, and wasm-pack for the WASM build. The visual regression tests (pnpm vrt) are local only and not run in CI; UPDATE_REFS=1 adopts the current rendering as the new reference baseline. ```bash # Install dependencies pnpm install # Build all WASM parsers (requires Rust + wasm-pack) pnpm build:wasm # Start Storybook dev server (port 6006) pnpm storybook # Type-check all packages pnpm typecheck # Run visual regression tests (local only — not run in CI) pnpm vrt # Adopt the current rendering as the new reference baseline UPDATE_REFS=1 pnpm vrt # Build the library pnpm build ``` -------------------------------- ### Claude Code example prompt Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Example prompt to ask Claude Code about sheets in an Excel file. ```text > What sheets are in /Users/me/Documents/budget.xlsx? ``` -------------------------------- ### Run paint, lint, type, and identity tests (Step 6) Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/docs/docx-layout-context-fragments-implementation-plan.md Runs the fragment-paint, layout-lines-reuse-identity, and paginate-paint-line-count tests, followed by lint, test-lint, and typecheck commands. ```bash pnpm vitest run packages/docx/src/fragment-paint.test.ts packages/docx/src/layout-lines-reuse-identity.test.ts packages/docx/src/paginate-paint-line-count.test.ts pnpm lint pnpm lint:test pnpm --filter @silurus/ooxml-docx typecheck ``` -------------------------------- ### Install @silurus/ooxml with npm or pnpm Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/README.md Install the @silurus/ooxml package using either npm or pnpm. The package is ESM-only and ships real .wasm asset files next to the JavaScript. ```bash npm install @silurus/ooxml ``` ```bash pnpm add @silurus/ooxml ``` -------------------------------- ### Handle copySelection result with discriminated status Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/docs/migration-xlsx-selection-0.77.md copySelection() returns a discriminated result instead of hiding failures. Check result.status before proceeding; the example logs a warning when the status is not 'copied'. ```TypeScript const result = await viewer.copySelection(); if (result.status !== 'copied') { console.warn(`Selection was not copied: ${result.status}`); } ``` -------------------------------- ### Run body layout and pagination tests plus typecheck Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/docs/docx-layout-context-fragments-implementation-plan.md Run the full set of body layout and existing pagination tests, then typecheck the package. This verifies the new fragment production and the compatibility adapter. ```bash pnpm vitest run packages/docx/src/document-layout.test.ts packages/docx/src/pagination.test.ts packages/docx/src/paginate-column-anchor.test.ts packages/docx/src/page-anchor-prescan.test.ts pnpm --filter @silurus/ooxml-docx typecheck ``` -------------------------------- ### GitHub Copilot (VS Code) MCP configuration (.vscode/mcp.json) Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/mcp-server/README.md Manual configuration for file tools only, when the Viewer selection bridge is not needed. Create in workspace root. Confirm with MCP: List Servers. MCP tools are only available in Agent mode. ```json { "servers": { "ooxml": { "type": "stdio", "command": "ooxml-mcp-server" } } } ``` -------------------------------- ### Run Playwright tests Source: https://github.com/yukiyokotani/office-open-xml-viewer/blob/main/packages/pptx/CLAUDE.md Runs the Playwright test suite with the list reporter. The example output shows a slide 4 match percentage of 93.8% with a diff of 6.2% (127,638 / 2,073,600 px). ```bash npx playwright test --reporter=list # Example: slide 4: match=93.8% diff=6.2% (127,638 / 2,073,600 px) ```