Compare commits

...
Author SHA1 Message Date
ZacharyZhang-NY e6759d5eef Limit the snapshot rerun to Linux uploads
Windows artifacts are already available in the temporary draft release and macOS assets were published from the local notarized run. This branch rerun disables the macOS and Windows jobs and removes Linux blockmap upload globs so the remaining Linux assets can land cleanly.

Constraint: The temporary draft release already has the required Windows uploads
Rejected: Rerun the full matrix again | duplicates keep failing mac uploads and waste runner time
Confidence: medium
Scope-risk: narrow
Directive: This workflow shape is for the snapshot branch only; restore the full matrix after the temporary release transport is no longer needed
Tested: prior rerun uploaded Windows x64-ia32 and Windows arm64 successfully
Not-tested: Linux-only rerun before execution
2026-04-04 03:58:55 -04:00
ZacharyZhang-NY 4df7f600d8 Bypass Actions artifact quota for the snapshot build branch
The native runners successfully package every requested platform, but Actions artifact upload is blocked by the repository storage quota. This workflow revision sends build outputs to a temporary draft GitHub release instead so the binaries remain downloadable for Cloudflare publication.

Constraint: GitHub Actions artifact storage quota is exhausted for this repository
Rejected: Continue using actions/upload-artifact | every build finished packaging and then failed on upload
Confidence: medium
Scope-risk: narrow
Directive: This release-upload path is for the temporary snapshot branch; do not keep it on the main workflow without deciding how permanent release transport should work
Tested: prior run packaged macOS, Windows x64-ia32, Windows arm64, Linux x64, and Linux arm64 before upload failure
Not-tested: Draft-release upload path before rerun
2026-04-04 03:40:59 -04:00
ZacharyZhang-NY b772e1c758 Build the current workspace snapshot on native CI runners
This branch snapshots the in-progress desktop workspace so GitHub-hosted Windows and Linux runners can package the same source tree that already passes local verification on macOS.

Constraint: Local Apple Silicon Docker/Wine packaging crashes before Windows artifacts are produced
Rejected: Publish from the incomplete base repo state | remote typecheck failed because current source additions were missing
Confidence: medium
Scope-risk: broad
Directive: Treat this branch as a build snapshot only; do not merge it without reviewing the unrelated workspace changes it contains
Tested: local typecheck, lint, workspace typecheck, unit tests, integration tests, e2e smoke, macOS signed release upload
Not-tested: Native Windows/Linux artifacts before this CI run
2026-04-04 03:28:14 -04:00
ZacharyZhang-NY 0e497cd892 Align release artifacts with the published platform matrix
Constraint: Local Apple Silicon Docker/Wine builds are not stable for Windows packaging
Rejected: Keep auto-bumping build versions at packaging time | website and artifact versions drifted
Confidence: medium
Scope-risk: moderate
Directive: Keep package.json, generated download links, and packaged artifact versions identical for every public release
Tested: typecheck, lint, workspace typecheck, unit tests, integration tests, e2e smoke locally
Not-tested: GitHub-hosted Windows/Linux runner outputs before workflow execution
2026-04-04 03:24:06 -04:00
ZacharyZhang-NYandClaude Sonnet 4.6 66158e222e update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 22:02:28 -04:00
ZacharyZhang-NY 16d8426119 update 2026-03-30 21:48:44 -04:00
ZacharyZhang-NY aed312124b update 2026-03-30 16:24:29 -04:00
ZacharyZhang-NY 400417a6af Update .gitignore 2026-03-30 10:50:25 -04:00
ZacharyZhang-NY 28740faf27 update 2026-03-30 10:45:53 -04:00
ZacharyZhang-NY 6f1de8047e update 2026-03-30 00:15:21 -04:00
ZacharyZhang-NY 65e85135c1 large update 2026-03-29 14:08:15 -04:00
ZacharyZhang-NY 2ed952f19c Merge branch 'worktree-agent-afbb9437' 2026-03-28 03:26:56 -04:00
ZacharyZhang-NY 29e4f9f430 Merge branch 'worktree-agent-a8526086'
# Conflicts:
#	.planning/STATE.md
2026-03-28 03:19:28 -04:00
ZacharyZhang-NYandClaude Opus 4.6 758bc077ff fix(quick-260328-48g): fix heartbeat task duplication with Paperclip-style coalescing
- Remove self-defeating heartbeat task filter that excluded heartbeat tasks from candidates
- Add Paperclip-style task reuse: recycle existing done/in_review heartbeat tasks instead of creating new ones
- Fix status bug: new heartbeat tasks created as "todo" instead of "in_progress" so CAS checkout succeeds
- Wire one-time duplicate cleanup sweep into heartbeat scheduler startup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 03:17:17 -04:00
ZacharyZhang-NYandClaude Opus 4.6 4b5492ea34 feat(quick-260328-48g): add database methods for heartbeat task reuse and cleanup
- Add findReusableHeartbeatTask() to find most recent done/in_review heartbeat task per agent
- Add cleanupDuplicateHeartbeatTasks() to cancel old duplicates using ROW_NUMBER window function
- Add 12 unit tests covering coalescing behavior, cross-agent isolation, status filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 03:14:40 -04:00
ZacharyZhang-NY bc1c7aefe5 feat(quick-260327-x64): implement listSkills/syncSkills on Claude, Codex, and Gemini adapters
- Add skills.ts to Claude adapter: ephemeral mode, listClaudeSkills/syncClaudeSkills
- Add skills.ts to Codex adapter: ephemeral mode, listCodexSkills/syncCodexSkills
- Add skills.ts to Gemini adapter: persistent mode with symlink sync, listGeminiSkills/syncGeminiSkills
- Re-export skill functions from each adapter's server/index.ts
2026-03-28 00:01:39 -04:00
ZacharyZhang-NY ed4988c5aa feat(quick-260327-x64): add skill sync types and resolution utilities to adapter-utils
- Add AdapterSkillSyncMode, AdapterSkillState, AdapterSkillOrigin, AdapterSkillEntry, AdapterSkillSnapshot, AdapterSkillContext types to types.ts
- Add optional listSkills/syncSkills methods to ServerAdapterModule interface
- Export new skill types from index.ts
- Add skill resolution helpers to server-utils.ts: resolveAgentCompanySkillsDir, listAgentCompanySkillEntries, readInstalledSkillTargets, buildPersistentSkillSnapshot, readAgentCompanyRuntimeSkillEntries, readAgentCompanySkillSyncPreference, resolveAgentCompanyDesiredSkillNames, writeAgentCompanySkillSyncPreference, ensureAgentCompanySkillSymlink, removeMaintainerOnlySkillSymlinks
2026-03-27 23:59:28 -04:00
ZacharyZhang-NY 80fa4221dc feat(260327-w3o): add CEO skill instructions to prompt-builder
- Step 1: review available skills before designing org structure
- Step 3: assign skills to agents after each hire is approved
- Priority 3: check/assign agent skills before delegating tasks
2026-03-27 23:11:16 -04:00
ZacharyZhang-NY 74d31f8365 feat(260327-w3o): add 5 skill REST API endpoints for agent access
- GET /api/companies/:companyId/skills (list company skills)
- GET /api/skills/:skillId (skill detail with markdown)
- POST /api/agents/:agentId/skills (assign skill to agent)
- DELETE /api/agents/:agentId/skills/:skillId (remove skill)
- GET /api/agents/:agentId/skills (list agent's skills)
2026-03-27 23:10:31 -04:00
ZacharyZhang-NYandClaude Opus 4.6 702f560b87 feat(260327-vvv): expand CEO Team Building Phase Step 1 with workspace exploration
- Step 1 now instructs codebase exploration (ls, read key files, understand structure)
- Added company identity consideration guidance for org design decisions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:00:06 -04:00
ZacharyZhang-NYandClaude Opus 4.6 4bb9c5ce9f feat(260327-vvv): add codebase scan and company context to CEO kickoff task
- Kickoff task now instructs CEO to explore workspace codebase before planning
- Company name and description interpolated into both activeGoal and fallback branches

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:59:45 -04:00
ZacharyZhang-NY 72848fcb00 feat(11): scan ~/.agents/skills/ and ~/.claude/skills/ for auto-import
Skills from user home directories are now auto-imported when listing
company skills. Deduplication handled by canonical key uniqueness.
Scans: ~/.agents/skills/, ~/.claude/skills/, and workspace .agents/skills/.
Cross-platform: uses HOME or USERPROFILE env var.
2026-03-27 22:49:44 -04:00
ZacharyZhang-NY 6480b7f49f fix(11): repair broken color patterns from CSS variable migration
- ChannelSidebar: replace inline color-mix style with Tailwind classes for task discussion icons
- MessageItem: replace broken priority config (color-mix inline) with Tailwind utility classes
- MessageItem: replace broken department badge hex-alpha pattern with Tailwind classes
- automation-config: restore ACCENT/LINE as hex values (used in template literal alpha suffixes)
- desktop-shell-service: set nativeTheme.themeSource to sync Windows title bar with app theme
2026-03-27 22:44:14 -04:00
ZacharyZhang-NY 3b756fd30d feat: scan workspace .agents/skills/ directories for skill imports
Add scanWorkspaceSkills method to skill service that iterates all
workspace local paths for a company and imports SKILL.md files from
.agents/skills/ subdirectories. Uses path.join for cross-platform
compatibility (Windows/macOS/Linux). The scan runs automatically
when skills are listed via IPC, with graceful error handling per
workspace so one bad path does not block the entire listing.

Also adds listWorkspaceLocalPaths to database for retrieving workspace
paths by company.
2026-03-27 22:38:02 -04:00
ZacharyZhang-NY 831acc3b06 fix: use tinted backgrounds for message priority badges
Priority badges had identical bg and text colors (both solid destructive
red for urgent), creating oversized solid-color blocks. Use 15% tinted
backgrounds so text remains readable and badges appear as subtle pills.
2026-03-27 22:36:35 -04:00
ZacharyZhang-NY c780fb7a1c fix: correct task discussion channel colors in sidebar
The task discussion avatar used solid var(--primary) background making a
dark square. Use 12% tinted primary instead (matching other channel icons).
Also change selected state from bg-muted to bg-accent for consistency
with regular channel items.
2026-03-27 22:36:19 -04:00
ZacharyZhang-NY dc05c97ce7 fix: use light background color for Electron window chrome
Change BrowserWindow backgroundColor from dark #13110f (leftover from
old design tokens) to #ffffff so the title bar area matches light mode.
The old dark color was visible in light mode as a dark title bar strip.
2026-03-27 22:35:57 -04:00
ZacharyZhang-NY 259c0a279d fix: hide cancel button on first-launch onboarding
Add isFirstLaunch check to cancel button visibility so it only shows
when user is creating additional companies, not during initial setup.
2026-03-27 22:34:52 -04:00
ZacharyZhang-NY 0a0b2fdce4 fix: prevent onboarding page scrollbar by constraining to viewport height
Change outer container from min-h-[100dvh] to h-[100dvh] and hero panel
from overflow-y-auto to overflow-hidden to prevent unwanted scrollbar
caused by ASCII animation background content.
2026-03-27 22:34:38 -04:00
ZacharyZhang-NY 2106ee1feb fix(11-02): repair broken text strings from mass CSS variable replacement
The mass find-and-replace incorrectly replaced the word 'surface' in
human-readable text strings and type literals, not just CSS class contexts.
Fixed: App.tsx control surface text, DocumentCenter knowledge base text,
i18n en.ts, and Panel tone prop type literal.
2026-03-27 22:24:43 -04:00
ZacharyZhang-NYandClaude Opus 4.6 17cebc277a fix(11-03): add missing @tailwindcss/typography dependency and verify design token migration
- Install @tailwindcss/typography required by styles.css @plugin directive
- All 9 automated verification checks pass: zero old variables, zero old border radius, zero data-theme, 70 OKLCH tokens, key tokens present, status-colors exports verified, no fontsource packages
- Zero renderer type errors, build succeeds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:18:32 -04:00
ZacharyZhang-NY 06ea4c6aec feat(11-02): replace 1113 old CSS variable references with Paperclip design tokens
- Replace all bg-[color:var(--old)] with bg-background/bg-card/bg-muted/bg-primary etc.
- Replace all text-[color:var(--old)] with text-foreground/text-muted-foreground/text-primary etc.
- Replace all border-[color:var(--line)] with border-border
- Replace all inline var(--old) with var(--new) equivalents
- Replace all rounded-[6px]/rounded-[8px] with rounded-lg
- Replace .surface class with bg-card border border-border shadow-sm
- Replace .focus-ring class with focus-visible Tailwind utilities
- Replace .mono class with font-mono
- Replace data-theme attribute with class-based dark mode
- 99 files updated, ~1265 lines changed, zero old references remaining
2026-03-27 22:12:00 -04:00
ZacharyZhang-NY 2ed5dd3388 chore: merge wave 1 (plan 11-01) with conflict resolution 2026-03-27 22:08:16 -04:00
ZacharyZhang-NY 22314876b3 feat(11-01): create status-colors.ts module and remove font packages
- Add status-colors.ts with Paperclip semantic status color mappings
- Export statusBadge, issueStatusIcon, issueStatusText, agentStatusDot, priorityColor
- Remove @fontsource-variable/inter, @fontsource-variable/jetbrains-mono, @fontsource/instrument-serif
2026-03-27 22:06:02 -04:00
ZacharyZhang-NY 02426bc861 feat(11-01): replace hex CSS vars with Paperclip OKLCH design tokens
- Replace entire styles.css with OKLCH token system (light + dark modes)
- Switch dark mode from data-theme attribute to .dark CSS class
- Update App.tsx theme toggle to use classList.toggle with system preference detection
- Switch fonts from Inter/JetBrains Mono to Maple Mono/Maple Mono NF CN
- Add chart tokens, sidebar tokens, 0px border radius
- Preserve sidebar-drag, ascii-scroll-track, reduced-motion support
- Remove fontsource font imports from main.tsx
2026-03-27 22:05:10 -04:00
ZacharyZhang-NY 047b09eae4 test(10-01): add cross-subsystem pipeline integration tests
- 6 tests exercising full 5-subsystem pipeline (D-04)
- Quality gate -> CAS checkout -> budget gate -> session -> completion
- Budget exhaustion blocks subsequent runs correctly
- Session token accumulation across multiple runs verified
- CAS double-checkout prevention in pipeline context
- Uses real AppDatabase, budget enforcement, session management services
2026-03-27 20:32:54 -04:00
ZacharyZhang-NY c395dbe8bf test(10-02): session rotation during execution edge case tests (D-05.3)
- Rotation triggered when run count exceeds maxRuns threshold
- Rotation triggered when token count exceeds maxTokens threshold
- Handoff markdown generated with task title and run history
- New session after rotation has clean counters (0 runs, 0 tokens)
- Claude connector exempt from rotation (nativeContextManagement)
- Codex connector exempt from rotation (nativeContextManagement)
- Handoff markdown available for next run context after rotation
2026-03-27 20:32:45 -04:00
ZacharyZhang-NY 2f80c319f9 test(10-02): budget exhaustion mid-run edge case tests (D-05.2)
- In-flight run completes normally when budget exhausted by another run
- Budget gate blocks next run after exhaustion
- BlockedBy returns detailed policy/scope/spent/limit info
- Agent-scope exhaustion blocks even when company budget has room
- Budget gate transitions from allowed to blocked after spending
- Cost entry recorded even when budget would be exceeded
2026-03-27 20:31:44 -04:00
ZacharyZhang-NY 4f5a14671d test(10-01): add process recovery integration tests
- 10 tests covering worker crash detection, lock release, agent reset
- Session handoff markdown generation with real DB
- Context preservation (D-02): agent assignment retained after crash
- DB consistency verification: no running runs/agents, no orphaned locks
- Multiple concurrent runs all interrupted on crash
- Uses real AppDatabase with temp dirs, gemini_local connector
2026-03-27 20:31:17 -04:00
ZacharyZhang-NY e14964ade3 test(10-02): concurrent checkout edge case tests (D-05.1)
- CAS rejects second claimer when two agents attempt same task
- Losing agent can checkout a different task
- In-progress status guard blocks new checkouts
- Lock release clears fields but CAS still checks status
- Stale lock recovery frees locks and resets status to todo
- Recovered task can be re-checked-out by another agent
- STALE_LOCK_TIMEOUT_MS validated at 15 minutes
2026-03-27 20:30:34 -04:00
ZacharyZhang-NYandClaude Opus 4.6 41c5dccae4 feat(09-02): wire App.tsx granular event routing with batch handler
- Replace createEventBatcher with createDomainEventBatcher in App.tsx
- Add handleGranularBatch for task, run, approval, agent, and budget events
- Task/run/approval events trigger targeted IPC fetch and Zustand patch
- Run completion and budget changes trigger full refresh (cross-entity)
- Failed entity fetches fall back to removeTask or full refresh
- Unknown event types trigger full refresh (PERF-03 safety)
- Sidebar badges refresh after every successful granular batch
- Add 12 integration tests for handleGranularBatch in selective-ipc.test.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:49:55 -04:00
ZacharyZhang-NYandClaude Opus 4.6 ffab9888ed chore(09-02): merge main to get plan 01 infrastructure
- Resolve merge conflicts in worker-service.ts and ipc-core-work-graph-handlers.ts
- Keep granular emitEvent calls from plan 02 while preserving session management and task validation from main
- Fix duplicate createSkillService import in service-graph.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:47:33 -04:00
ZacharyZhang-NYandClaude Opus 4.6 5124209ddb feat(09-02): convert high-frequency callsites to granular emitEvent
- Replace publishDomainChanged() with granular events at 9 high-frequency callsites
- saveTask/deleteTask emit task-updated events in ipc-core-work-graph-handlers
- decideApproval emits approval-updated, addComment emits task-updated
- worker-service emits run-updated for status changes and completion
- worker-service emits agent-updated for budget hard-stop pause
- orchestration-service emits agent-updated for auto-pause, run-updated for dispatch
- Wire emitEvent dependency to orchestration-service via service-graph
- Keep publishDomainChanged for low-frequency and multi-entity paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:45:30 -04:00
ZacharyZhang-NY 039c1d117f Merge branch 'worktree-agent-a2f30682'
# Conflicts:
#	.planning/STATE.md
#	src/main/ipc-core-read-handlers.ts
#	src/shared/contracts.ts
2026-03-27 19:40:29 -04:00
ZacharyZhang-NYandClaude Opus 4.6 f172a3d9b3 feat(09-01): build enhanced event batcher with granular event coalescing
- Add createDomainEventBatcher with entity type+ID dedup within 50ms window
- Domain-changed overrides all granular events in same batch
- More than 10 unique entities triggers full refresh fallback
- Delete action preserved during coalescing (last-write-wins otherwise)
- Unknown event types trigger full refresh as safety fallback
- Legacy createEventBatcher preserved for backward compatibility
- Add 12 comprehensive tests for all coalescing behaviors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:35:05 -04:00
ZacharyZhang-NYandClaude Opus 4.6 920a764856 feat(09-01): define granular event types, IPC channels, entity getters, and Zustand patch methods
- Add 5 granular event interfaces (TaskUpdated, RunUpdated, ApprovalUpdated, AgentUpdated, BudgetChanged) to DesktopEvent union
- Add 4 entity getter IPC channels (getTask, getAgent, getRun, getApproval) with Zod validation and company ownership checks
- Add getRun method to AppDatabase following existing getTask pattern
- Add 4 preload bridge methods for entity getters
- Add 8 patch/remove methods and refreshBadges to CompanySlice
- Add selective-ipc.test.ts with exhaustive union type tests
- Extend store.test.ts with 14 patch method tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:33:33 -04:00
ZacharyZhang-NY 76a69494a1 fix(08): use getBudgetStatus IPC for company/agent/project scope budget utilization 2026-03-27 18:45:32 -04:00
ZacharyZhang-NY 907a5dfe2f Merge branch 'worktree-agent-a5b08020'
# Conflicts:
#	.planning/STATE.md
#	package.json
#	pnpm-lock.yaml
2026-03-27 09:04:16 -04:00
ZacharyZhang-NYandClaude Opus 4.6 b6c5b2a9ee feat(08-02): create 5 dashboard chart components with ChartSection container
- Add RunActivityChart (area chart), SuccessRateChart (line chart), CostTrendChart (area chart), AgentWorkloadChart (bar chart) using Recharts
- Add BudgetUtilization with CSS progress bars, color thresholds (success/warn/danger), and empty state
- Create ChartSection container orchestrating all charts with Framer Motion entrance animation
- Integrate ChartSection into Dashboard.tsx between MetricsGrid and NeedsAttention
- Add companyId prop to DashboardProps and pass from App.tsx
- Include Plan 01 infrastructure: chart-utils.ts, ChartCard.tsx, ChartTooltip.tsx, recharts dependency
- All charts respect reduced motion via useReducedMotion hook
- 16 unit tests pass for aggregation functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:59:59 -04:00
ZacharyZhang-NY 40b01f24e9 Merge branch 'worktree-agent-a1f54db6'
# Conflicts:
#	.planning/STATE.md
2026-03-27 08:51:34 -04:00
ZacharyZhang-NYandClaude Opus 4.6 31328d5a3b feat(08-01): create ChartCard wrapper and ChartTooltip components
- ChartCard: shared chart card with title, period badge, empty state, role="img" accessibility
- ChartTooltip: custom Recharts tooltip with design system tokens, color dots, mono font
- Both use CSS custom properties for automatic dark mode support
- TypeScript compiles cleanly with Recharts TooltipContentProps type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:47:07 -04:00
ZacharyZhang-NYandClaude Opus 4.6 a45e6bc10c feat(08-01): install Recharts and create tested data aggregation functions
- Install recharts@3.8.1 as dependency
- Create chart-utils.ts with 4 exported pure functions: aggregateRunsByDay, aggregateCostsByDay, aggregateAgentWorkload, formatShortDate
- Create dashboard-charts.test.ts with 16 unit tests covering all aggregation behaviors
- All tests pass: 14-day bucketing with zero-fill, timed_out/interrupted as failed, null cost handling, top-8 + Others grouping, name truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:44:49 -04:00
ZacharyZhang-NY 2ffdfdc0b5 fix: remove extra closing brace from merge conflict resolution 2026-03-27 00:49:10 -04:00
ZacharyZhang-NY fd3f5accc1 Merge branch 'worktree-agent-a205edfb'
# Conflicts:
#	.planning/STATE.md
#	src/main/orchestration-service.ts
#	src/main/task-quality-gates.ts
#	tests/unit/task-quality-gates.test.ts
2026-03-27 00:47:58 -04:00
ZacharyZhang-NYandClaude Opus 4.6 c4eb4423db feat(07-02): wire sweepStaleReviews into heartbeat timer
- Import sweepStaleReviews, resolveApprovalDecision, dispatchAgentMessage
- Insert stale review sweep into per-company heartbeat loop after budget reset
- Sweep runs on every 15-second heartbeat cycle for each active company
- Local callback stubs for handleTaskStatusChange and handleApprovalResolved
- Error handling prevents sweep failures from crashing the heartbeat loop
- Fix test type casts to use as-unknown-as for vi.fn mock compatibility
- TypeScript compiles cleanly, 249/249 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:43:08 -04:00
ZacharyZhang-NYandClaude Opus 4.6 0bfbdec09b feat(07-02): implement sweepStaleReviews with escalation chain
- sweepStaleReviews queries tasks in in_review status exceeding 30-min timeout
- Escalation dispatched to reviewer's manager (manager's manager in chain)
- When reviewer is CEO (no higher manager), desktop notification sent to human
- Pending approvals auto-approved with documented timeout note
- Task status advanced from in_review to done after escalation
- Race condition with manual approval handled via try-catch (Pitfall 3)
- Tasks without assigneeAgentId skipped gracefully
- Exports REVIEW_TIMEOUT_MS (1800000ms) and SweepStaleReviewsDeps interface
- 8 comprehensive unit tests covering all TASK-04 behaviors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:39:41 -04:00
ZacharyZhang-NYandClaude Opus 4.6 e1f8a3c9ab test(07-02): add failing tests for sweepStaleReviews escalation
- 8 tests covering TASK-04 stale review escalation logic
- Escalation to manager's manager, CEO fallback notification
- Auto-approve pending approvals, race condition handling
- Task status advancement, no-assignee skip, timeout constant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:38:57 -04:00
ZacharyZhang-NYandClaude Opus 4.6 4d1085fc04 chore(07-02): bring in plan 01 prerequisites
Cherry-pick plan 01 task quality gate validation module and its
integration into IPC, API server, and automation service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:37:36 -04:00
ZacharyZhang-NY 8ef70259e0 Merge branch 'worktree-agent-a3712f2b'
# Conflicts:
#	.planning/STATE.md
2026-03-27 00:34:55 -04:00
ZacharyZhang-NY a9ce25e6dd feat(07-01): wire task quality gates into all creation paths
- Add SaveTaskResult type to contracts.ts (id + optional capabilityWarning)
- IPC handler: validate new tasks, reject with TASK_VALIDATION_FAILED, check capability match
- API server: validate POST /issues and meeting action items before db.saveTask
- Automation service: validate create_task action, skip with warning if invalid
- CapabilityMatchDb interface uses optional skill methods for graceful degradation
- TypeScript compiles cleanly across all process boundaries
- All 241 tests pass with zero regressions
2026-03-27 00:31:27 -04:00
ZacharyZhang-NY 025920bc12 feat(07-01): implement task quality gate validation module
- validateTaskCreation: min 50 char description, heartbeat bypass, acceptance criteria check
- checkAcceptanceCriteria: 12 keyword patterns for structured task definitions
- checkCapabilityMatch: word-overlap matching with skill fallback and graceful degradation
- All 25 unit tests passing across TASK-01, TASK-02, TASK-03 requirements
2026-03-27 00:27:06 -04:00
ZacharyZhang-NY d38b59255c test(07-01): add failing tests for task quality gate validation
- validateTaskCreation: description length, heartbeat bypass, acceptance criteria
- checkAcceptanceCriteria: keyword detection for 12 patterns
- checkCapabilityMatch: capability overlap, skill fallback, graceful degradation
2026-03-27 00:26:25 -04:00
ZacharyZhang-NYandClaude Opus 4.6 a1d361b947 feat(06-02): wire session handoff injection into prompt builder and orchestration dispatch
- Add sessionHandoff field to PromptContext interface and Session Handoff section in buildSystemPrompt
- Add getSessionHandoff dependency to orchestration service, load handoff before prompt build
- Update orchestration to prefer task_sessions table over legacy tasks.session_json (SESS-01)
- Wire getSessionHandoff through ServiceGraph into createOrchestrationService

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:05:16 -04:00
ZacharyZhang-NYandClaude Opus 4.6 5271f7ee5d feat(06-02): wire token usage tracking, session accumulation, and rotation into worker pipeline
- Add inputTokens/outputTokens fields to worker run-finished messages and workerMessageSchema
- Wire sessionService into WorkerServiceDependencies and ServiceGraph lazy slot
- Add token accumulation and rotation evaluation in run-finished handler
- Add 7 wiring integration tests verifying accumulation, rotation, and guard paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:03:23 -04:00
ZacharyZhang-NYandClaude Opus 4.6 e54c632fd6 feat(06-01): add session management service with rotation, thresholds, and delta tracking
- Create createSessionManagementService factory with accumulateTokenUsage,
  evaluateRotation (configurable thresholds per D-01), generateHandoff,
  rotateSession, getHandoffContext, snapshotUsage, getUsageDelta
- Export DEFAULT_THRESHOLDS (maxRuns: 20, maxTokens: 500k, maxAgeHours: 72) and RotationThresholds type
- Adapter nativeContextManagement exemption skips rotation for Claude/Codex (D-04)
- Handoff markdown capped at 2000 chars with structured sections (D-06)
- Add 14 unit tests for service: rotation thresholds, adapter exemption, configurable
  thresholds, token accumulation, handoff generation, session rotation, usage delta

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:56:18 -04:00
ZacharyZhang-NYandClaude Opus 4.6 2c671d0011 feat(06-01): add task_sessions CRUD, nativeContextManagement flag, TaskSessionRecord type
- Add getActiveTaskSession, incrementSessionUsage, archiveSession, createNewSession,
  getRecentRunsForTask, getRecentActivityForTask methods to AppDatabase
- Add nativeContextManagement optional boolean to ServerAdapterModule interface
- Set nativeContextManagement: true for claude_local and codex_local, false for gemini_local
- Add TaskSessionRecord interface to shared types
- Add unit tests for task_sessions CRUD and adapter exemption flags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:53:39 -04:00
ZacharyZhang-NY e55ddeffd8 docs(phase-05): complete phase execution 2026-03-26 21:28:02 -04:00
ZacharyZhang-NY 1c462d571c feat(05-03): policy-level BudgetSummary, incident-based BudgetAlertBanner, AgentEditor policy wiring
- BudgetSummary shows policy-level utilization bars when companyId provided, falls back to agent-level
- BudgetAlertBanner loads open incidents for policy-based alerts (warn_threshold/hard_stop), falls back to agent thresholds
- AgentEditor reads budget from budget_policies and writes budget as policy on save (D-11)
- Dashboard passes companyId through to BudgetAlertBanner
2026-03-26 21:17:02 -04:00
ZacharyZhang-NY 3a8e4c40ad feat(05-03): PolicyList, PolicyFormModal, IncidentList and costs tab navigation
- Create PolicyList with utilization bars, edit/delete, empty state
- Create PolicyFormModal with scope selector, dollar amount, warn/hard-stop toggles
- Create IncidentList with status filtering and manual resolve action
- Add tabbed navigation (Cost Entries | Policies | Incidents) to costs section
- Export new components from execution barrel file
2026-03-26 21:13:17 -04:00
ZacharyZhang-NY 4e5429929a feat(05-02): replace flat budget check with policy-based gate, wire incident checks into run completion and heartbeat
- Replace flat budgetMonthlyUsd check with evaluateBudgetGate in getRunEligibilityFailure
- Add projectId parameter to getRunEligibilityFailure for project-scope policy evaluation
- Update all 3 call sites to pass projectId where task context is available
- Wire checkAndCreateIncidents into worker-service run-finished handler for post-run incident creation
- Wire autoCloseExpiredIncidents into heartbeat for window rollover cleanup (D-06)
- Pass budgetService dependency to orchestration-service and worker-service via service-graph
- Mark autoPauseBudgetExceededAgent as deprecated (superseded by BudgetEnforcementService)
2026-03-26 21:02:12 -04:00
ZacharyZhang-NY feea5c1fe9 feat(05-02): IPC handlers, preload bridge, and ServiceGraph wiring for budget enforcement
- Create ipc-budget-handlers.ts with 6 budget channel handlers (save/delete/list policies, list/resolve incidents, get status)
- Add budgetService to RegisterDomainHandlersDependencies interface
- Register budget handlers in ipc-domain-handlers.ts composition
- Add BudgetEnforcementService lazy slot in service-graph.ts with getOrCreateBudgetEnforcementService
- Pass budgetService to domain handler registration in main-runtime-support.ts
- Fix preload bridge to pass {companyId} object for listBudgetPolicies and getBudgetStatus
- D-06: checkAutoResolve called on saveBudgetPolicy to auto-resolve incidents on limit increase
2026-03-26 20:57:52 -04:00
ZacharyZhang-NYandClaude Opus 4.6 72cb921069 feat(05-01): BudgetEnforcementService factory with policy evaluation and incident lifecycle
- evaluateBudgetGate: strictest-wins blocking (D-01), no-policy=unlimited (D-02)
- checkAndCreateIncidents: warn + hard-stop thresholds with dedup (D-07, D-08)
- checkAutoResolve: auto_resolved_limit_increase when limit increases (D-06)
- autoCloseExpiredIncidents: window rollover cleanup (D-06)
- getBudgetStatus: summary of all policies with current spend
- Notifications with budget_warning and budget_exhaustion batch keys
- Factory pattern matching createAutomationService convention

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:48:37 -04:00
ZacharyZhang-NYandClaude Opus 4.6 3a68cfa707 feat(05-01): budget enforcement types, schemas, IPC channels, and database CRUD
- Add BudgetPolicyRecord, BudgetIncidentRecord, BudgetStatusSummary types
- Add 6 IPC channels for budget operations in contracts.ts
- Add Zod schemas for all budget payloads
- Add DesktopApi entries for budget methods
- Add database CRUD: saveBudgetPolicy, deleteBudgetPolicy, listBudgetPolicies,
  getBudgetPolicy, getActivePoliciesForRun, getSpentInWindow (cents aggregation),
  upsertBudgetIncident (D-08 dedup), resolveIncidentsForPolicy,
  resolveWarnIncidentsForPolicy, autoCloseExpiredIncidents
- Update recordRunCompletion to include project_id in cost_entries INSERT
- Add migration 18: convert agent budgetMonthlyUsd to budget_policies rows
- Add preload bridge stubs for budget IPC channels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:47:20 -04:00
ZacharyZhang-NYandClaude Opus 4.6 245eb4ccf3 test(05-01): add tests for budget policy evaluation and incident lifecycle
- Budget policy CRUD (save, update, delete)
- getActivePoliciesForRun with multi-scope matching
- getSpentInWindow with cents aggregation per scope (company/agent/project)
- evaluateBudgetGate strictest-wins logic (D-01)
- upsertBudgetIncident with deduplication (D-08)
- resolveIncidentsForPolicy and autoCloseExpiredIncidents (D-06)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:43:50 -04:00
ZacharyZhang-NY 2be909b67c fix(04): add active_session_id and project_id to ensureOptionalColumns
Migration 13 columns were skipped when ensureOptionalColumns added
checkout_run_id first, triggering 'duplicate column' catch that marked
the entire migration as applied without running remaining ALTERs.
2026-03-26 19:22:26 -04:00
ZacharyZhang-NY 930273ce87 feat(04-02): worker lock release, wake promotion, IPC wakeup channel, and preload bridge
- Add releaseTaskLock to worker crash recovery after finishRun (EXEC-02)
- Track affected agent company IDs for targeted wake promotion
- Replace listWakeupRequests+loop with promoteWakeupRequests+wakeAgentIfPossible in run completion
- Track hadDeferredWakes flag for circuit breaker self-wake decision
- Add listWakeupRequests IPC channel to IPC_CHANNELS
- Add wakeupQuerySchema with Zod validation and WakeupRequestRecord interface
- Add listWakeupRequests to DesktopApi type
- Add listWakeupRequests preload bridge method
- Add registerHandle for listWakeupRequests IPC handler with company validation
2026-03-26 19:16:17 -04:00
ZacharyZhang-NY 8a424822a5 feat(04-02): wire CAS checkout and stale recovery into orchestration-service
- Add randomUUID import for pre-generated runId in CAS checkout flow
- Replace task-level hasActiveRun checks with db.checkoutTask CAS in startAgentHeartbeatRun and queueTaskRun
- Pass pre-generated runId to createRun (database.ts accepts optional id field)
- Log execution.checkout_rejected activity on CAS failure (D-04)
- Return fail("CHECKOUT_RACE_LOST") in queueTaskRun on CAS failure
- Rewrite wakeAgentIfPossible with proper lifecycle tracking via enqueueWakeupRequest
- Add stale lock recovery sweep at startup (resumeQueuedRuns) and each heartbeat tick
- Log execution.lock_recovered activity entries for recovered stale locks
2026-03-26 19:13:09 -04:00
ZacharyZhang-NY 63ff0f2625 Merge branch 'worktree-agent-a1383b58'
# Conflicts:
#	.planning/STATE.md
#	src/main/database.ts
2026-03-26 19:07:13 -04:00
ZacharyZhang-NY 4d47712669 fix(04-01): resolve type errors and update integration tests for wakeup API
- Fix SQLInputValue type in listWakeupRequests params
- Fix leftover pendingWakes reference in worker-service.ts
- Fix RunStatus 'completed' -> 'succeeded' in atomic-checkout test
- Update autonomous-chain.spec.ts to use new wakeup lifecycle API
2026-03-26 19:00:51 -04:00
ZacharyZhang-NY f647a4fdf1 test(04-01): add wakeup lifecycle unit tests covering all CRUD operations
- 10 test cases: enqueue, coalesce (queued + deferred), claim, complete, defer, promote, merge, list with filters
- Validates D-06 trigger_detail JSON array coalescing
- Validates D-07 per-agent scope deduplication
- Validates full state machine: queued -> claimed -> completed, queued -> deferred -> queued
2026-03-26 18:58:18 -04:00
ZacharyZhang-NY 08935b3379 feat(04-01): implement CAS checkout, lock release, stale recovery, and migration 16
- Rewrite checkoutTask to use runId-based CAS with checkout_run_id IS NULL guard
- Add releaseTaskLock and recoverStaleLocks methods
- Add lock release in finishRun and cancelRunForDeletion exit paths
- Add migration 16: pending_wakes to wakeup_requests cutover with DROP TABLE
- Replace all pending_wakes references with wakeup_requests
- Add wakeup lifecycle CRUD: enqueue, claim, complete, defer, promote, list
- Update api-server, orchestration-service, worker-service callers
- Update existing database.test.ts for new checkoutTask signature
2026-03-26 18:56:54 -04:00
ZacharyZhang-NY 02e87dc06d test(04-01): add failing tests for CAS checkout, lock release, stale recovery
- 8 test cases covering CAS semantics, lock release on finishRun/deleteAgent, stale recovery
- Tests expect STALE_LOCK_TIMEOUT_MS export, checkout_run_id/execution_locked_at columns
- RED phase: 5 of 8 tests fail as expected before implementation
2026-03-26 18:51:46 -04:00
ZacharyZhang-NY 2ade6c0cbc feat(03-02): add agent-centric skill toggles in AgentEditor for D-04 dual access
- Import SkillAgentToggles and Zustand skill state into AgentEditor
- Add lazy skill loading for navigation paths that bypass Skills section
- Render Skills section with agent-centric toggle mode between heartbeat and save
2026-03-26 02:03:11 -04:00
ZacharyZhang-NY e71d2e5529 feat(03-02): add SkillDetail pane, SkillAgentToggles, and assignment query IPC
- Add listSkillAgents and listAgentSkills IPC channels with Zod schemas
- Create SkillDetail component with metadata, file viewer, update check, delete
- Create SkillAgentToggles with discriminated union for skill/agent-centric modes
- Wire SkillDetail into SkillsSection right column replacing placeholder
- Remove duplicate skill method declarations from DesktopApi type
2026-03-26 02:01:51 -04:00
ZacharyZhang-NY 6397c118ea merge: resolve wave 1 worktree conflicts (keep Phase 02 schema validation) 2026-03-26 01:54:08 -04:00
ZacharyZhang-NYandClaude Opus 4.6 49b75257fb feat(03-01): add SkillsSection layout, SkillList, SkillFileTree, and SkillImportModal
- Create SkillsSection with two-column grid layout (19rem + flex) matching Paperclip pattern
- Create SkillList with source icons, expandable file trees, CSS grid animation
- Create SkillFileTree with recursive rendering, dir/file icons, indent levels
- Create SkillImportModal with 4 tabs: GitHub, Local Path, HTTP URL, skills.sh
- Wire listSkills IPC for skill loading and importSkill IPC for import flow
- Add skill IPC channels, Zod schemas, and DesktopApi methods to contracts.ts
- Add skill IPC bridge methods to preload/index.ts
- All Phosphor icons, all ui.tsx components, no external dependencies

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:51:06 -04:00
ZacharyZhang-NYandClaude Opus 4.6 e8d642941f feat(03-01): add skill section type wiring, Zustand slice, i18n, and sidebar nav entry
- Add "skills" to SectionId union in shared types
- Add SkillRecord, SkillImportResult, SkillUpdateStatus types to shared/types.ts
- Create SkillSlice with skills list, selection, file path, and import modal state
- Wire SkillSlice into Zustand store composition
- Add "nav.skills" i18n keys for English and Chinese
- Add Skills sidebar nav entry with PuzzlePiece icon between Communication and Documents
- Add section === "skills" rendering route in App.tsx
- Clear skill state on company switch
- Add skills case to navigateTo in NavigationSlice

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:45:32 -04:00
ZacharyZhang-NY b949187356 merge: wave 3a executor (02-03 IPC) into main 2026-03-26 00:51:00 -04:00
ZacharyZhang-NY 14a7f4468e feat(02-04): update all 5 adapters to use managed skills instead of static directories
- Claude: buildSkillsDir() accepts managedSkills, uses createManagedSkillSymlinks
- Codex: ensureCodexSkillsInjected() prefers managed skills over static scan
- Gemini: ensureGeminiSkillsInjected() prefers managed skills over static scan
- OpenCode: ensureOpenCodeSkillsInjected() prefers managed skills over static scan
- Kimi: new skill injection via ensureKimiSkillsInjected() (first-time support)
- All adapters fall back to static SKILLS_CANDIDATES when no managed skills provided
2026-03-26 00:49:07 -04:00
ZacharyZhang-NY bbdabbe675 feat(02-03): wire SkillService into ServiceGraph and preload bridge
- Add lazy SkillService creation in ServiceGraph with getOrCreateSkillService getter
- Pass skillService to registerDomainHandlers in main-runtime-support
- Add 7 skill operations to preload bridge (importSkill, listSkills, getSkillDetail, deleteSkill, assignSkillToAgent, removeSkillFromAgent, checkSkillUpdate)
- Add skillService cleanup in app lifecycle shutdown
2026-03-26 00:46:34 -04:00
ZacharyZhang-NY 3fb9ab86d9 feat(02-04): add shared skill symlink helper and wire orchestration-to-worker pipeline
- Add ManagedSkillRef type to adapter-utils for cross-package use
- Add createManagedSkillSymlinks() with Windows junction support (D-13)
- Add managedSkills field to worker RunJob and connector config
- Add listRuntimeSkillEntries dependency to orchestration service
- Wire SkillService lazy init in service-graph for runtime skill resolution
- Include prerequisite skill system files from plans 02-01/02-02
2026-03-26 00:46:19 -04:00
ZacharyZhang-NY b5ad164338 feat(02-03): add 7 skill IPC channels with Zod schemas and register handlers
- Add importSkill, listSkills, getSkillDetail, deleteSkill, assignSkillToAgent, removeSkillFromAgent, checkSkillUpdate IPC channels
- Add Zod validation schemas for all skill payloads in contracts.ts
- Create ipc-skill-handlers.ts with company existence checks and domain-changed events on mutations
- Add SkillRecord, SkillImportResult, SkillUpdateStatus types to shared/types.ts
- Wire registerSkillHandlers into ipc-domain-handlers.ts
- Add skillService to RegisterDomainHandlersDependencies interface
2026-03-26 00:45:03 -04:00
ZacharyZhang-NYandClaude Opus 4.6 c9111d2f6d feat(02-02): create SkillService with import, CRUD, assignment, token budget, update detection
- Factory pattern following createOrchestrationService with DesktopResult returns
- Import from 4 sources: GitHub REST API, local path, HTTP URL, skills.sh registry
- Per-agent skill assignment via agent_skill_assignments join table
- Token budget enforcement skips oversized skills with logger.warn
- GitHub update detection compares stored vs latest commit SHAs
- File materialization at profileDir/skills/{companyId}/ per D-10
- Deletion follows D-12 order: files from disk, then DB record (FK cascades assignments)
- Upsert semantics prevent duplicate imports via canonical key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:38:19 -04:00
ZacharyZhang-NYandClaude Opus 4.6 cae241a978 feat(02-02): create GitHub REST API skill fetcher and database skill CRUD
- Add skill-github.ts with resolveGitHubCommitSha and fetchGitHubSkills
- Add migration 16 for agent_skill_assignments table with FKs and cascade delete
- Add 9 skill CRUD methods to AppDatabase (upsert, list, get, delete, assign, remove)
- Add agent_skill_assignments cleanup to deleteCompany cascade

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:35:43 -04:00
ZacharyZhang-NY 3a1698195d chore: remove accidental worktree submodule 2026-03-26 00:31:09 -04:00
ZacharyZhang-NY e3a5e20d90 merge: wave 1 executor (02-01) into main 2026-03-26 00:31:03 -04:00
ZacharyZhang-NY a8db40180c feat(02-01): create canonical key derivation and skill source resolution
- deriveCanonicalSkillKey handles all 5 source types deterministically
- normalizeSkillSlug with lowercase, hyphen-delimited, edge case handling
- hashSkillValue produces 10-char SHA-256 hex digests
- parseSkillImportSource resolves GitHub, skills.sh, URL, local path inputs
2026-03-26 00:27:39 -04:00
ZacharyZhang-NY 77480374fd test(02-01): add failing test for canonical key derivation
- Tests normalizeSkillSlug for edge cases (empty, null, special chars)
- Tests hashSkillValue determinism and 10-char hex output
- Tests deriveCanonicalSkillKey for all 5 source types
- Tests parseSkillImportSource for GitHub, skills.sh, URL, local paths
2026-03-26 00:26:56 -04:00
ZacharyZhang-NY b1008be0e4 feat(02-01): create SKILL.md format parser and trust level derivation
- parseFrontmatterMarkdown with CRLF normalization and minimal YAML parser
- classifyInventoryKind with directory prefix and extension-based classification
- deriveTrustLevel deriving trust from file inventory (script > asset > markdown)
- estimateTokens with 1:4 char-to-token ratio for budget enforcement
2026-03-26 00:26:19 -04:00
ZacharyZhang-NY 58d3f4a039 test(02-01): add failing test for SKILL.md format parser
- Tests parseFrontmatterMarkdown with frontmatter, no frontmatter, CRLF normalization
- Tests classifyInventoryKind for all file type classifications
- Tests deriveTrustLevel for script/asset/markdown-only derivation
- Tests estimateTokens for token estimation logic
2026-03-26 00:24:53 -04:00
ZacharyZhang-NY e6d5cd82eb feat(02-01): create skill type definitions and constants
- SkillSourceType union with 5 source kinds (github, local_path, url, skills_sh, managed)
- TrustLevel union with 3 levels (markdown_only, assets, scripts_executables)
- FileInventoryEntry with 6 kind classifications
- SkillRecord interface matching company_skills table columns
- TOKEN_BUDGETS constant with per-adapter token caps
- RuntimeSkillEntry, ManagedSkillRef, SkillAssignment, SkillUpdateStatus interfaces
2026-03-26 00:24:16 -04:00
ZacharyZhang-NY f16ddc44f9 test(02-01): add failing test for skill type definitions
- Tests TOKEN_BUDGETS constant values for 5 adapter types
- Tests type shapes for SkillRecord, RuntimeSkillEntry, ManagedSkillRef
- Tests SkillSourceType (5 members), TrustLevel (3 members), FileInventoryEntry (6 kinds)
2026-03-26 00:23:45 -04:00
ZacharyZhang-NYandClaude Opus 4.6 122cf8db04 feat(01-02): session data migration and backward compatibility verification
Adds migration 15 - eager copy session_json to task_sessions table.
Handles corrupt rows gracefully, maintains idempotency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:46:33 -04:00
ZacharyZhang-NYandClaude Opus 4.6 aa1a78d7d9 merge: wave 2 executor (01-02) into main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:46:16 -04:00
ZacharyZhang-NYandClaude Opus 4.6 a5b8fcf9d3 merge: wave 1 executor (01-01) into main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:38:02 -04:00
ZacharyZhang-NY ec2e0528d4 feat(01-01): add migrations 13-14 and update deleteCompany cleanup
- Migration 13: add checkout_run_id, execution_locked_at, active_session_id to tasks; add project_id with index to cost_entries
- Migration 14: create company_skills, budget_policies, budget_incidents, task_sessions, wakeup_requests tables with indexes
- Update deleteCompany() to delete from all 5 new tables in FK-safe order (budget_incidents before budget_policies)
2026-03-25 23:35:22 -04:00
ZacharyZhang-NY a4bd40c2b0 feat(01-01): set busy_timeout and extend migration runner type
- Set 5000ms busy_timeout via DatabaseSync constructor option in openConnection()
- Extend migration runner type to support both sql string and fn callback migrations
- fn-based migrations execute inside existing transaction for atomicity
2026-03-25 23:33:59 -04:00
ZacharyZhang-NY 981b4dd19e docs: create roadmap (10 phases) 2026-03-25 23:11:25 -04:00
735 changed files with 135117 additions and 19605 deletions

No files matched your search

Submodule .claude/worktrees/agent-a7fcd1c8 added at da96a90511.
Submodule .claude/worktrees/agent-a997a4a7 added at 447c50fb2d.
+33 -111
View File
@@ -19,11 +19,9 @@ concurrency:
env:
PNPM_VERSION: '10.24.0'
NODE_VERSION: '22'
CI_RELEASE_TAG: ci-build-${{ github.run_id }}
jobs:
# ─────────────────────────────────────────────
# Stage 1: Lint & Typecheck (fast feedback)
# ─────────────────────────────────────────────
check:
name: Lint & Typecheck
runs-on: ubuntu-latest
@@ -37,12 +35,10 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm exec tsc --noEmit --project tsconfig.check.json
- run: pnpm typecheck:all
# ─────────────────────────────────────────────
# Stage 2: Unit Tests
# ─────────────────────────────────────────────
test:
name: Unit Tests
runs-on: ubuntu-latest
@@ -57,16 +53,16 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
# ─────────────────────────────────────────────
# Stage 3: Build matrix (parallel per platform)
# ─────────────────────────────────────────────
- run: pnpm test:unit
- run: pnpm test:integration
build-macos:
if: false
name: macOS (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
needs: test
permissions:
contents: write
strategy:
fail-fast: false
matrix:
@@ -79,82 +75,31 @@ jobs:
builder_args: --mac --x64
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Decode signing certificate
if: env.CERT_B64 != ''
env:
CERT_B64: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 }}
run: echo "$CERT_B64" | base64 --decode > /tmp/cert.p12
- name: Build electron-vite
run: pnpm exec electron-vite build
- name: Package & sign
run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never
env:
CSC_LINK: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 != '' && '/tmp/cert.p12' || '' }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: ${{ secrets.MAC_CERTIFICATE_P12_BASE64 != '' && 'true' || 'false' }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- uses: actions/upload-artifact@v4
with:
name: macos-${{ matrix.arch }}
path: |
build/release/*.dmg
build/release/*.zip
build/release/*.blockmap
build/release/latest-mac.yml
if-no-files-found: error
build-windows:
if: false
name: Windows (${{ matrix.arch }})
runs-on: windows-latest
needs: test
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- arch: x64
builder_args: --win --x64
- arch: x64-ia32
builder_args: --win --x64 --ia32
- arch: arm64
builder_args: --win --arm64
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Build electron-vite
run: pnpm exec electron-vite build
- name: Package
run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never
env:
CSC_LINK: ${{ secrets.WIN_CERTIFICATE_P12_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
- uses: actions/upload-artifact@v4
with:
name: windows-${{ matrix.arch }}
path: |
build/release/*.exe
build/release/*.blockmap
build/release/latest.yml
if-no-files-found: error
build-linux:
name: Linux (${{ matrix.arch }})
runs-on: ubuntu-latest
needs: test
permissions:
contents: write
strategy:
fail-fast: false
matrix:
@@ -180,53 +125,30 @@ jobs:
run: pnpm exec electron-vite build
- name: Package
run: pnpm exec electron-builder ${{ matrix.builder_args }} --config electron-builder.yml --publish never
- uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.arch }}
path: |
build/release/*.AppImage
build/release/*.deb
build/release/*.rpm
build/release/*.snap
build/release/*.blockmap
build/release/latest-linux.yml
if-no-files-found: warn
- name: Upload release files
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release view "$CI_RELEASE_TAG" >/dev/null 2>&1 || \
gh release create "$CI_RELEASE_TAG" \
--draft \
--prerelease \
--title "CI Build $GITHUB_RUN_ID" \
--notes "Temporary build artifact transport for Cloudflare publication." \
--target "$GITHUB_SHA"
gh release upload "$CI_RELEASE_TAG" \
build/release/*.AppImage \
build/release/*.deb \
build/release/*.rpm \
build/release/latest-linux.yml \
--clobber
# ─────────────────────────────────────────────
# Stage 4: Publish to GitHub Releases
# ─────────────────────────────────────────────
release:
name: Publish Release
runs-on: ubuntu-latest
needs: [build-macos, build-windows, build-linux]
needs: [build-linux]
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: false
- name: Collect release files
run: |
mkdir -p release-files
find artifacts -type f \( \
-name "*.dmg" -o -name "*.zip" -o -name "*.exe" \
-o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \
-o -name "*.snap" -o -name "*.blockmap" \
-o -name "latest*.yml" \
\) -exec cp {} release-files/ \;
ls -lh release-files/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
draft: true
generate_release_notes: true
files: release-files/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+18 -5
View File
@@ -9,7 +9,14 @@ coverage/
*.tsbuildinfo
win-unpacked
.blockmap
.claude
build
.omc
.omx
.planning
paperclip-reference
Reference
.pnpm-store
# Test artifacts
test-results/
playwright-report/
@@ -25,8 +32,11 @@ playwright-report/
# Environment files
.env
.env.*
.env.local
.env.*.local
!.env.example
!.env.release.example
# Editor and OS files
.DS_Store
@@ -41,6 +51,8 @@ oldreference/
*.md
!/README.md
!/docs/*.md
!/CLAUDE.md
!/.planning/**/*.md
*.zip
*.blockmap
*.exe
@@ -48,16 +60,17 @@ oldreference/
# Local design scratch files
homepagedesign.html
# Landing page (deployed separately to Cloudflare Pages)
landing/
# Landing page is part of the repo and should be versioned
# Boss edition build (contains credentials — never commit)
boss-build/
# Package manager logs
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
CLAUDE.md
build/release/AgentCompany-0.4.61-arm64.dmg
CLAUDE.md
CLAUDE.md
build/release/AgentCompany-0.4.90-arm64.dmg
local-release/
+339
View File
@@ -108,6 +108,7 @@ Root app + `packages/adapter-{claude,gemini,codex,kimi,opencode}-local/` + `pack
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks
- `/gsd:debug` for investigation and bug fixing
- `/gsd:execute-phase` for planned phase work
@@ -153,3 +154,341 @@ Builders and operators who want to spin up autonomous AI companies without manag
3. **Real, not theatrical** — Agent activity should feel like watching a real company operate, not a sci-fi simulation. Status over spectacle.
4. **Desktop-native craft** — Keyboard-first, focus rings, platform-aware shortcuts, proper window management. No web-app-in-a-wrapper feel.
5. **Accessible by default** — Reduced motion, color contrast, semantic HTML, focus traps. Not an afterthought.
<!-- GSD:project-start source:PROJECT.md -->
## Project
**AgentCompany — Match Paperclip Quality Milestone**
AgentCompany is a cross-platform Electron desktop app that lets users create autonomous AI companies. A user provides a company name, description, goal, and deliverable. A CEO agent self-organizes other AI agents, delegates tasks, and the company runs end-to-end while the human observes through a Slack-style hub. This milestone closes the quality gap with the Paperclip reference implementation — bringing agent skill injection, task execution integrity, budget enforcement, session management, and UI maturity to production quality.
**Core Value:** Agents produce real, usable output — not garbage. Every run has domain knowledge (skills), validated tasks, quality gates on output, cost controls, and session continuity.
### Constraints
- **Runtime**: Electron with strict main/preload/renderer separation — no architectural changes
- **Database**: SQLite (`node:sqlite`) — implement all features within SQLite's capabilities
- **Adapters**: Preserve existing 5-adapter modular npm package structure — extend, don't replace
- **Backward compatibility**: Existing companies/agents/tasks must continue to work after migration
- **Performance**: Skill injection must not add >2s to run startup; IPC events must be <100ms
<!-- GSD:project-end -->
<!-- GSD:stack-start source:codebase/STACK.md -->
## Technology Stack
## Languages
- TypeScript 5.9.3 - All application code (main, preload, renderer, shared, packages)
- JavaScript - Build scripts and some config files
- Python - Asset generation scripts (`scripts/generate-icons.py`)
## Runtime
- Electron 39.8.0 - Cross-platform desktop framework
- Node.js 22+ - Server runtime (main process)
- pnpm 10.24.0
- Lockfile: `pnpm-lock.yaml` (present)
## Frameworks
- Electron 39.8.0 - Desktop runtime with IPC, main/preload/renderer architecture
- React 19.2.4 - UI framework in renderer process
- TailwindCSS 4.2.1 - Utility-first CSS in renderer
- Zustand 5.0.12 - Lightweight state management (renderer)
- Immer 11.1.4 - Immutable state updates
- Framer Motion 12.36.0 - Animation and motion primitives in React components
- Vite 7.0.0 - Module bundler and dev server
- electron-vite 5.0.0 - Vite config wrapper for Electron main/preload/renderer
- @vitejs/plugin-react 5.0.0 - React JSX support in Vite
- @tailwindcss/vite 4.2.1 - TailwindCSS Vite integration
- Vitest 4.1.0 - Unit/integration test runner (Node environment)
- @playwright/test 1.58.2 - E2E testing framework (`tests/e2e/playwright.config.ts`)
- ESLint 9.38.0 - Code linting with flat config
- @typescript-eslint 8.46.1 - TypeScript linting support
- eslint-plugin-react-hooks 7.0.0 - React hooks rule enforcement
- eslint-plugin-react-refresh 0.4.24 - Fast refresh support
- TypeScript 5.9.3 - Type checking (`pnpm typecheck`)
## Key Dependencies
- zod 4.3.6 - Schema validation at IPC boundaries (main, preload, renderer share contracts)
- electron-log 5.4.3 - Structured logging in main process (no console.log in production)
- electron-updater 6.8.3 - Auto-update via electron-updater (when configured)
- node:sqlite - Built-in SQLite bindings for local database (no external DB driver)
- electron.safeStorage - OS keychain integration for credential encryption
- @phosphor-icons/react 2.1.10 - Icon library (16-18px standard)
- @tanstack/react-virtual 3.13.23 - Virtual scrolling for large lists
- clsx 2.1.1 - Conditional className utility
- tailwind-merge 3.5.0 - TailwindCSS className merging
- @fontsource-variable/inter - System sans-serif for UI
- @fontsource-variable/jetbrains-mono 5.2.8 - Monospace for code/data display
- @fontsource/instrument-serif - Serif font support
- @agentcompany/adapter-claude-local - Claude Code CLI adapter (workspace:*)
- @agentcompany/adapter-codex-local - Codex CLI adapter (workspace:*)
- @agentcompany/adapter-gemini-local - Gemini CLI adapter (workspace:*)
- @agentcompany/adapter-utils - Shared adapter utilities (workspace:*)
## Configuration
- Files: `electron.vite.config.ts`, `tsconfig.json`, `vitest.config.ts`, `tests/e2e/playwright.config.ts`
- Profile directory: `{userData}/profile` (default) or `AGENTCOMPANY_PROFILE_DIR` env var
- Database: `{profileDir}/profile.sqlite` (WAL mode, foreign keys enabled)
- Window state: `{profileDir}/window-state.json`
- Artifacts: `{profileDir}/artifacts/`
- Logs: `{profileDir}/logs/`
- Backups: `{profileDir}/backups/`
- Secrets vault: `{profileDir}/vault.key` (600 permissions)
- `electron.vite.config.ts` - Main/preload/renderer entry points and aliases
- `electron-builder.yml` - Platform-specific build targets (macOS dmg/zip, Windows NSIS/portable, Linux AppImage/deb/rpm)
- `tsconfig.json` - Path aliases: `@shared`, `@main`, `@renderer`, `@preload`
- `eslint.config.mjs` - ESLint 9 flat config
- `pnpm-workspace.yaml` - Monorepo with `packages/*`
## Platform Targets
- Formats: dmg (installer), zip (portable)
- Architectures: x64, arm64
- Signing: `hardenedRuntime: true`, Gatekeeper assessment disabled
- Code sign config via: `CSC_LINK`, `CSC_KEY_PASSWORD` env vars
- Formats: NSIS (installer), portable exe
- Architectures: x64, arm64
- Code sign config via: `WIN_CSC_LINK`, `WIN_CSC_KEY_PASSWORD` env vars
- NSIS: oneClick false, perMachine false, allow custom install dir
- Formats: AppImage, deb, rpm, snap
- Architectures: x64, arm64 (AppImage/deb/snap); x64 (rpm)
- Desktop category: Development
- deb dependencies: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0
- Snap plugs: default, removable-media
## Monorepo Structure
- `packages/adapter-claude-local/` - Claude Code CLI integration
- `packages/adapter-codex-local/` - Codex CLI integration
- `packages/adapter-gemini-local/` - Gemini CLI integration
- `packages/adapter-kimi-local/` - Kimi CLI integration (not bundled in main build)
- `packages/adapter-opencode-local/` - OpenCode CLI integration (not bundled in main build)
- `packages/adapter-utils/` - Shared adapter types and utilities
## Key Scripts
- Main: `out/main/index.js` + `out/main/worker.js` (entry points in electron.vite.config.ts)
- Preload: `out/preload/index.js` (CJS format, chunked)
- Renderer: `out/renderer/` (Vite SPA bundle)
- Desktop installers: `build/release/` (platform-specific per electron-builder.yml)
## Adapter System
- `execute()` - Run code in CLI session
- `sessionCodec` - Serialize/deserialize session state
- `testEnvironment()` - Check CLI availability
- `models` - List available models
<!-- GSD:stack-end -->
<!-- GSD:conventions-start source:CONVENTIONS.md -->
## Conventions
## Naming Patterns
- kebab-case for all source files: `connector-auth-service.ts`, `ipc-core-handlers.ts`, `automation-service.ts`
- React components: PascalCase when exporting components: `RuleEditor.tsx`, `CompanyOnboarding.tsx`
- Test files: kebab-case with `.test.ts` or `.spec.ts` suffix: `database.test.ts`, `autonomous-chain.spec.ts`, `notification-service.test.ts`
- camelCase with descriptive verbs: `saveCompany()`, `handleApprovalResolved()`, `checkoutTask()`, `wakeAgentIfPossible()`
- Event handlers: prefix with `handle` or `on`: `handleSave()`, `handleTaskCreated()`, `onClick()`
- Prefix boolean-returning functions with `is` or `has`: `isAgentPendingApproval()`, `hasCompany()`, `belongsToCompany()`
- camelCase for all variables: `companyId`, `workspaceId`, `agentId`, `tempDir`
- Boolean variables prefixed with `is`, `has`, or `can`: `isEditing`, `hasError`, `canApprove`
- Collections in plural form: `agents`, `tasks`, `workspaces`, `tempDirs`
- PascalCase for all types: `AppDatabase`, `DesktopResult<T>`, `CompanyRecord`, `AgentRecord`
- Database entity types suffixed with `Record`: `CompanyRecord`, `AgentRecord`, `TaskRecord`, `ApprovalRecord`, `RunRecord`
- Zod schemas suffixed with `Schema`: `companyInputSchema`, `connectorInputSchema`, `workspaceInputSchema`, `approvalDecisionSchema`
- Generic types: `DesktopResult<T>` for IPC return types, `RuleFormState` for form state interfaces
- UPPERCASE for true constants defined at module level: `BOARD_READER_ID`, `ACCENT`, `LINE`
- camelCase for derived constants/configs: `IPC_CHANNELS`, `sidebarNavDefs`, `defaultRuleForm`
## Code Style
- 2-space indentation (enforced by ESLint configuration)
- No Prettier config in repository — style guided by ESLint rules
- Line length: no hard limit enforced, but prefer reasonable wrapping
- Semicolons: required (enforced by `@typescript-eslint` defaults)
- ESLint 9 with flat config in `eslint.config.mjs`
- TypeScript ESLint rules enabled in recommended configuration
- `@typescript-eslint/no-explicit-any` set to `off` — `any` is allowed for database interop and bridge code
- `@typescript-eslint/no-unused-vars`: warn with patterns `^_` for unused parameters/variables
- Unused variables can be ignored by prefixing with underscore: `_event`, `_prevStatus`
- React Hooks: `exhaustive-deps` set to warn (not error), allowing intentional deviations
- ESLint covers: `src/**/*.{ts,tsx}`, `tests/**/*.{ts,tsx}`, `scripts/**/*.{ts,mjs}`, `packages/**/*.{ts,tsx}`
- Excluded: `node_modules/`, `out/`, `build/`, `dist/`, `coverage/`, `playwright-report/`, `test-results/`
## Import Organization
- `@shared/*` → `src/shared/` — Zod schemas, types, contract definitions, localization
- `@main/*` → `src/main/` — Database, services, IPC handlers, API server
- `@renderer/*` → `src/renderer/` — React components, hooks, UI utilities
- `@preload/*` → `src/preload/` — Context-isolated bridge APIs
- Use named exports for all utilities, services, handlers, and hooks
- Default exports only for React components (required for route-based splitting patterns)
- Example: `export function saveCompany()` not `export default saveCompany`
- Example: `export default function CompanyOnboarding()` for React components
- Use `import type` exclusively for TypeScript-only imports to enable proper tree-shaking
- Example: `import type { ProfileSnapshot, DesktopEvent } from "@shared/types";`
## Error Handling
- All IPC handlers return `DesktopResult<T>` = `{ ok: true, data: T } | { ok: false, error: { code, message } }`
- Use helper functions `ok(data)` and `fail(code, message)` from `ipc-core-common.ts`
- Example from `ipc-core-admin-handlers.ts`:
- Zod is used for all IPC payloads: `const parsed = companyInputSchema.parse(payload);`
- Zod parsing happens at the IPC boundary (main process side)
- Parse before any business logic, catch validation errors and return `fail()` with descriptive message
- Example: `const parsed = approvalDecisionSchema.parse(payload);`
- All entity operations check company ownership with `ensureCompanyExists()` and `belongsToCompany()`
- Pattern from `ipc-core-operations-handlers.ts`:
- Never throw exceptions across IPC. Always catch and return `fail(code, message)`
- Main ↔ Renderer communication must use `DesktopResult` envelope
- Database errors are caught and wrapped: `catch (error) { return fail("APPROVAL_INVALID", error instanceof Error ? error.message : "...") }`
- Use SCREAMING_SNAKE_CASE for error codes: `INVALID_COMPANY_ID`, `CONNECTOR_COMMAND_LOCKED`, `TASK_NOT_FOUND`, `APPROVAL_NOT_FOUND`
- Error codes are used by renderer to determine UI response (e.g., showing specific error message)
## Logging
- No `console.log()` in production code. Use `electron-log` instead.
- Logger injected as dependency to services: `logger: { info, error }`
- Example from `notification-service.test.ts`:
- Critical events: service initialization, crashes, recoveries
- Errors: validation failures, database errors, connectivity issues
- Debug: trace flow through complex orchestration (in dev mode)
## Comments
- Only for non-obvious logic or business rules
- Prefer clear naming and types over comments
- Example: comments explaining why a decision was made, not what the code does
- Minimal use — TypeScript types are the primary documentation
- Use JSDoc for public APIs and exported functions if return type is complex
- Example for complex types:
## Function Design
- Use object parameters (destructuring) for >3 parameters
- Example: `saveAgent({ companyId, name, role, ... })`
- Avoid parameter lists with many flags
- Explicit return types for all exported functions
- Return early for error conditions: `if (error) return fail("CODE", "message");`
- Consistent use of `DesktopResult<T>` for IPC handlers
- Use `async/await`, not promises chains
- Always handle errors in handlers: `try/catch` around database calls
- Example pattern:
## Module Design
- Named exports for utilities, services, handlers
- Default exports ONLY for React components
- Example good pattern: `export function registerCoreAdminHandlers({ ... })`
- Used in `src/renderer/components/` for organizing imports
- Example: `export { AgentCreator, AgentEditor, AgentRoster } from "./..."`
- Database and major services are class-based (e.g., `AppDatabase`, `ServiceGraph`)
- Other services are functional: `createNotificationService()`, `createEventBatcher()`
- Classes own initialization (constructor) and lifecycle (init/close methods)
## React-Specific Patterns
- All React components are functional with hooks
- No class components
- Example: `export function RuleFormModal({ open, editingRule, companyId, onClose, onSaved }: Props)`
- `useCallback()` for stable function references passed to event handlers
- `useEffect()` for side effects with proper dependency arrays
- `useMemo()` for expensive calculations and string translations
- Example from `RuleEditor.tsx`:
- Zustand store (`useAppStore`) for global UI state (sidebar collapsed, selected entities, locale)
- Local `useState()` for component-specific form state
- No Redux, Context API used only for i18n provider
- Explicit interface or inline type for all props
- Example: `function RuleFormModal({ open, editingRule, companyId, onClose, onSaved }: { open: boolean; editingRule: AutomationRuleRecord | null; ... })`
<!-- GSD:conventions-end -->
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
## Architecture
## Pattern Overview
- Process boundary enforcement: main (Node.js) → preload (context-isolated) ← renderer (React)
- Service graph: loosely-coupled async services coordinated by bootstrap
- Data flow: SQLite → IPC domain-changed events → Zustand store → React UI
- Agent execution: orchestration service queues runs to worker process (child utility process)
- Automation: event-driven triggers (task created, status changed, approval resolved) dispatch workflows
- IPC pattern: `ipcRenderer.invoke()` → Zod validation → DesktopResult<T> pattern
## Layers
- Purpose: Database operations, service coordination, IPC handler registration, external API communication, worker management
- Location: `src/main/`
- Contains: Database class, service factories, IPC handlers, agent orchestration logic, API server, automation rules
- Depends on: Electron main process APIs, SQLite, node:fs, child processes via worker service
- Used by: Preload process (via ipcMain handlers), worker process (via message passing)
- Purpose: Context-isolated bridge exposing minimal DesktopApi to renderer via contextBridge
- Location: `src/preload/index.ts`
- Contains: ~75 typed IPC channel invocations, subscription to desktop events
- Depends on: Electron contextBridge, ipcRenderer, shared contracts
- Used by: Renderer process (via window.agentCompany global)
- Purpose: Display company state, handle user interactions, visualize agent execution
- Location: `src/renderer/`
- Contains: React components, Zustand store (no Redux/Redux-like), hooks, i18n context, UI utilities
- Depends on: React 19, window.agentCompany preload API, Zustand, Framer Motion
- Used by: User directly through Electron window
- Purpose: Execute agent commands in isolated child process (Electron utilityProcess)
- Location: Built from agent adapters in `packages/adapter-*/`
- Contains: Connector CLI execution, run log streaming, status reporting
- Depends on: `EnqueueRunPayload` message contract, agent connector CLIs (Claude, Codex, Gemini, Kimi, OpenCode)
- Used by: Worker service via message passing
## Data Flow
- Zustand store in `src/renderer/state/store.ts` holds:
- No Redux, no global context beyond locale/translation
- Derived state computed via hooks (e.g., `selectedAgent = agents.find(a => a.id === selectedAgentId)`)
- Snapshot updates trigger full re-computation (no partial updates)
## Key Abstractions
- Purpose: All SQLite operations, snapshot generation, business logic for data consistency
- Examples: `src/main/database.ts`
- Pattern: Class with ~100 public methods, synchronous SQLite.DatabaseSync, no ORM
- Responsibilities: CRUD for all entities, filtering/searching, metrics calculation, backup/restore, secret vault integration
- Purpose: Dependency injection and lifecycle management for 9 coordinated services
- Examples: `src/main/service-graph.ts`
- Pattern: Factory function returning object with service getters, lazy initialization via `init()`
- Services: DesktopShell, ConnectorHealth, Worker, Orchestration, Automation, Onboarding, BrowserAction, Updater, AppLifecycle
- Purpose: Agent task execution, workspace resolution, budget tracking, run queuing
- Examples: `src/main/orchestration-service.ts`
- Pattern: Factory with internal state (heartbeat timer, run cache), public methods for task/agent operations
- Coordinates with: Worker service (enqueue), Automation service (dispatch on status change), Database (persist)
- Purpose: Event-driven trigger evaluation and workflow execution
- Examples: `src/main/automation-service.ts`
- Pattern: Factory managing active execution state, async/await for workflow steps
- Triggers: task.created, task.status_changed, approval.resolved, comment.posted, document.created, run.completed
- Actions: assign_task, notify_agent, send_message, create_approval, trigger_heartbeat, escalate, cross_department_notify, auto_approve
- Purpose: Electron window lifecycle, platform integration, keyboard shortcuts, native menus
- Examples: `src/main/desktop-shell-service.ts`
- Pattern: Factory managing BrowserWindow instance, event listeners for menu/shortcut actions
- Responsibilities: window creation, state persistence, platform-specific behaviors
- Purpose: Child process lifecycle, run execution, log streaming
- Examples: `src/main/worker-service.ts`
- Pattern: Factory managing Electron utilityProcess, retry logic, consecutive wake detection
- Message types: enqueue-run (inbound), run-log/run-status/run-finished (outbound)
- Purpose: Handler registration, payload validation, sender URL verification, error sanitization
- Examples: `src/main/ipc-support.ts`
- Pattern: Factory with `registerHandle()` method for each channel, returns `DesktopResult<T>`
- Security: Verifies senderFrame.url matches trusted renderer origin (dev or app://-) before processing
## Entry Points
- Location: Electron main configuration in `electron.vite.config.ts` or `build/` entry
- Triggers: App launch via `electron .` or packaged app
- Responsibilities: Create main runtime, await bootstrap, setup single-instance lock, handle app lifecycle events
- Location: `src/renderer/main.tsx` (React root)
- Triggers: Main process creates BrowserWindow pointing to renderer dist
- Responsibilities: Mount React App component, initialize Zustand store, hydrate snapshot on first load
- Location: `src/main/ipc-core-handlers.ts` dispatches to specialized handler modules
- Handlers split across: `ipc-core-read-handlers.ts`, `ipc-core-operations-handlers.ts`, `ipc-core-work-graph-handlers.ts`, `ipc-agent-coordination-handlers.ts`, etc.
- Each handler: validates input with Zod, calls DB or service method, returns DesktopResult, publishes domain-changed on write
## Error Handling
- IPC validation: Zod schema parsing at handler entry → `sanitizeHandlerError()` converts ZodError to `IPC_INVALID_PAYLOAD`
- DB errors: Throws logged to electron-log, caught by IPC handler, returned as DesktopResult error
- Worker errors: Run status set to "failed" with errorMessage, logged to run log file
- Automation errors: Try-catch blocks log errors but continue processing other rules
- Async cleanup: `appLifecycleService` waits 3 seconds for worker child process to exit before app.exit(0)
## Cross-Cutting Concerns
<!-- GSD:architecture-end -->
## 核心原则 (Core Persona)
1. **​第一性原理​**: 从原始需求出发。动机不清立刻停,路径非最优直接纠正。
2. **​极简沟通​**: 用简单直白的中文一次性输出,把用户当高中生。拒绝角色扮演,拒绝分段分口吻,对话中已解决的问题后续绝不再提。不要用 P0/P1/P2 这种术语。
3. **​Let it crash​**: 发现问题尽早暴露。严禁使用任何降级、兜底、后发式补丁或非严谨通用算法的后处理补救。
4. **​禁止擅自开分支​**: 严禁私自创建新 worktree。可以给建议,但必须征得用户明确同意后方可操作。
5. **​自检与精简​**: 每次改动后,严格执行「Review查Bug然后第一性原理分析」流程,思考是否有更简单、更稳健的实现。
## 开发工作流 (Development Workflow)
1. **​分析层​**: 文字、图标、颜色的UI修改,直接操作执行层并落地archive。重大重构/多任务才走规划层。
2. **​规划层​**: 使用 using-superpowers 编排流程并产出/更新全局流程图。
3. **​任务层​**: 使用 planning-with-files 维护 task_plan.md / progress.md / findings.md。
4. **​执行层​**: OpenSpec 四步闭环(propose -> 用户确认 -> apply -> archive)。
5. **​粒度控制​**: 动手前用 gsd-method-guide 拆解为 `<files>/<action>/<verify>/<done>`。
## 工程规范 (Engineering Constraints)
1. **​数据处理​**: 不可捏造数据。生产代码严禁 Mock。Mock 仅限本地调试(统一入口: `127.0.0.1:xxxx/mock`),必须在 `.gitignore` 中排除。
2. **​自动化执行​**: `curl`、`cat`、`git` 等命令直接运行免确认;Playwright 脚本在终端持续会话,禁止无意义的暂停。
3. **​子代理分流​**: 复杂问题(多于1个、需 Review/研究/并行分析)必须拆解并使用子代理(5.4 模型),保持主上下文纯净。
4. **​API 接入​**: 参考 `/Users/ifengz/CodingCase/002_DevelopSpec/1.Doc/`中的现成方案,并不断补充新内容。
5. **​自我进化​**: 用户指正后立即更新 `lessons.md`。开始新任务前必须回顾 `lessons.md`。
## 运维安全守则 (Operations Constraints)
1. **​排障顺序​**: 遇网络/证书/代理异常,优先排查入口及反代配置。严禁使用临时IP:端口判定数据库损坏,必须寻找固定入口(域名/面板地址)。
## 输出规范 (Output Specs - 拒绝啰嗦)
1. **​禁止陈述式汇报​**: 严禁复读背景,严禁分“证据/分析/结论”等多维度拆解简单问题。
2. **​结论先行​**: 直接给结论和修补方案。解释必须是短小精悍的中文大白话,不显示P0/P1等级。
3. **​表格化输出​**: 多数内容(尤其是评审、对比、多项任务)必须以Markdown表格输出。
4. **​强制收口​**: 结束对话必须明确告知用到的 skill。
+2
View File
@@ -126,12 +126,14 @@ pnpm build:verify
pnpm build # Compile + package for current platform → /build/release
pnpm build:dir # Compile + unpackaged directory
pnpm build:all # Build for all platforms
pnpm release:local # Signed/uploading release pipeline (see docs/release/local-r2.md)
```
- **Targets:** macOS (`.dmg`), Windows (`.exe` / NSIS), Linux (`.AppImage`)
- Packaging uses `electron-builder`
- Local builds use ad-hoc signing (`CSC_IDENTITY_AUTO_DISCOVERY=false`)
- Defaults to `--publish never`; override with `AGENTCOMPANY_ELECTRON_PUBLISH`
- Cloudflare R2 release hosting + auto-update pipeline: `docs/release/local-r2.md`
---
+1 -1
View File
@@ -4,7 +4,7 @@
"output": "/Users/zacharyzhang/Documents/Github/AgentCompany/build/release"
},
"extraMetadata": {
"version": "0.4.95"
"version": "0.4.136"
},
"mac": {
"hardenedRuntime": false,
-8
View File
@@ -1,8 +0,0 @@
version: 0.4.95
files:
- url: AgentCompany-0.4.95-arm64-mac.zip
sha512: /Ew0ls0nISOD/65MHxVbc23TRmgP/bbCWR89j/FhoAwKkE0Vmexjsp6IxY22X8tXnwuMgP+A/DJgqenNlKslzQ==
size: 115753870
path: AgentCompany-0.4.95-arm64-mac.zip
sha512: /Ew0ls0nISOD/65MHxVbc23TRmgP/bbCWR89j/FhoAwKkE0Vmexjsp6IxY22X8tXnwuMgP+A/DJgqenNlKslzQ==
releaseDate: '2026-03-24T20:37:33.571Z'
Binary file not shown.
@@ -0,0 +1,173 @@
import {loadFont} from '@remotion/google-fonts/Inter';
import {AbsoluteFill, spring, useCurrentFrame, useVideoConfig} from 'remotion';
const {fontFamily} = loadFont();
const COLOR_BAR = '#D4AF37';
const COLOR_TEXT = '#ffffff';
const COLOR_MUTED = '#888888';
const COLOR_BG = '#0a0a0a';
const COLOR_AXIS = '#333333';
// Ideal composition size: 1280x720
const Title: React.FC<{children: React.ReactNode}> = ({children}) => (
<div style={{textAlign: 'center', marginBottom: 40}}>
<div style={{color: COLOR_TEXT, fontSize: 48, fontWeight: 600}}>
{children}
</div>
</div>
);
const YAxis: React.FC<{steps: number[]; height: number}> = ({
steps,
height,
}) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height,
paddingRight: 16,
}}
>
{steps
.slice()
.reverse()
.map((step) => (
<div
key={step}
style={{
color: COLOR_MUTED,
fontSize: 20,
textAlign: 'right',
}}
>
{step.toLocaleString()}
</div>
))}
</div>
);
const Bar: React.FC<{
height: number;
progress: number;
}> = ({height, progress}) => (
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
}}
>
<div
style={{
width: '100%',
height,
backgroundColor: COLOR_BAR,
borderRadius: '8px 8px 0 0',
opacity: progress,
}}
/>
</div>
);
const XAxis: React.FC<{
children: React.ReactNode;
labels: string[];
height: number;
}> = ({children, labels, height}) => (
<div style={{flex: 1, display: 'flex', flexDirection: 'column'}}>
<div
style={{
display: 'flex',
alignItems: 'flex-end',
gap: 16,
height,
borderLeft: `2px solid ${COLOR_AXIS}`,
borderBottom: `2px solid ${COLOR_AXIS}`,
paddingLeft: 16,
}}
>
{children}
</div>
<div
style={{
display: 'flex',
gap: 16,
paddingLeft: 16,
marginTop: 12,
}}
>
{labels.map((label) => (
<div
key={label}
style={{
flex: 1,
textAlign: 'center',
color: COLOR_MUTED,
fontSize: 20,
}}
>
{label}
</div>
))}
</div>
</div>
);
export const MyAnimation = () => {
const frame = useCurrentFrame();
const {fps, height} = useVideoConfig();
const data = [
{month: 'Jan', price: 2039},
{month: 'Mar', price: 2160},
{month: 'May', price: 2327},
{month: 'Jul', price: 2426},
{month: 'Sep', price: 2634},
{month: 'Nov', price: 2672},
];
const minPrice = 2000;
const maxPrice = 2800;
const priceRange = maxPrice - minPrice;
const chartHeight = height - 280;
const yAxisSteps = [2000, 2400, 2800];
return (
<AbsoluteFill
style={{
backgroundColor: COLOR_BG,
padding: 60,
display: 'flex',
flexDirection: 'column',
fontFamily,
}}
>
<Title>Gold Price 2024</Title>
<div style={{display: 'flex', flex: 1}}>
<YAxis steps={yAxisSteps} height={chartHeight} />
<XAxis height={chartHeight} labels={data.map((d) => d.month)}>
{data.map((item, i) => {
const progress = spring({
frame: frame - i * 5 - 10,
fps,
config: {damping: 18, stiffness: 80},
});
const barHeight =
((item.price - minPrice) / priceRange) * chartHeight * progress;
return (
<Bar key={item.month} height={barHeight} progress={progress} />
);
})}
</XAxis>
</div>
</AbsoluteFill>
);
};
@@ -0,0 +1,100 @@
import {
AbsoluteFill,
interpolate,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
const COLOR_BG = '#ffffff';
const COLOR_TEXT = '#000000';
const FULL_TEXT = 'From prompt to motion graphics. This is Remotion.';
const PAUSE_AFTER = 'From prompt to motion graphics.';
const FONT_SIZE = 72;
const FONT_WEIGHT = 700;
const CHAR_FRAMES = 2;
const CURSOR_BLINK_FRAMES = 16;
const PAUSE_SECONDS = 1;
// Ideal composition size: 1280x720
const getTypedText = ({
frame,
fullText,
pauseAfter,
charFrames,
pauseFrames,
}: {
frame: number;
fullText: string;
pauseAfter: string;
charFrames: number;
pauseFrames: number;
}): string => {
const pauseIndex = fullText.indexOf(pauseAfter);
const preLen =
pauseIndex >= 0 ? pauseIndex + pauseAfter.length : fullText.length;
let typedChars = 0;
if (frame < preLen * charFrames) {
typedChars = Math.floor(frame / charFrames);
} else if (frame < preLen * charFrames + pauseFrames) {
typedChars = preLen;
} else {
const postPhase = frame - preLen * charFrames - pauseFrames;
typedChars = Math.min(
fullText.length,
preLen + Math.floor(postPhase / charFrames),
);
}
return fullText.slice(0, typedChars);
};
const Cursor: React.FC<{
frame: number;
blinkFrames: number;
symbol?: string;
}> = ({frame, blinkFrames, symbol = '\u258C'}) => {
const opacity = interpolate(
frame % blinkFrames,
[0, blinkFrames / 2, blinkFrames],
[1, 0, 1],
{extrapolateLeft: 'clamp', extrapolateRight: 'clamp'},
);
return <span style={{opacity}}>{symbol}</span>;
};
export const MyAnimation = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const pauseFrames = Math.round(fps * PAUSE_SECONDS);
const typedText = getTypedText({
frame,
fullText: FULL_TEXT,
pauseAfter: PAUSE_AFTER,
charFrames: CHAR_FRAMES,
pauseFrames,
});
return (
<AbsoluteFill
style={{
backgroundColor: COLOR_BG,
}}
>
<div
style={{
color: COLOR_TEXT,
fontSize: FONT_SIZE,
fontWeight: FONT_WEIGHT,
fontFamily: 'sans-serif',
}}
>
<span>{typedText}</span>
<Cursor frame={frame} blinkFrames={CURSOR_BLINK_FRAMES} />
</div>
</AbsoluteFill>
);
};
@@ -0,0 +1,103 @@
import {loadFont} from '@remotion/google-fonts/Inter';
import React from 'react';
import {AbsoluteFill, spring, useCurrentFrame, useVideoConfig} from 'remotion';
/*
* Highlight a word in a sentence with a spring-animated wipe effect.
*/
// Ideal composition size: 1280x720
const COLOR_BG = '#ffffff';
const COLOR_TEXT = '#000000';
const COLOR_HIGHLIGHT = '#A7C7E7';
const FULL_TEXT = 'This is Remotion.';
const HIGHLIGHT_WORD = 'Remotion';
const FONT_SIZE = 72;
const FONT_WEIGHT = 700;
const HIGHLIGHT_START_FRAME = 30;
const HIGHLIGHT_WIPE_DURATION = 18;
const {fontFamily} = loadFont();
const Highlight: React.FC<{
word: string;
color: string;
delay: number;
durationInFrames: number;
}> = ({word, color, delay, durationInFrames}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const highlightProgress = spring({
fps,
frame,
config: {damping: 200},
delay,
durationInFrames,
});
const scaleX = Math.max(0, Math.min(1, highlightProgress));
return (
<span style={{position: 'relative', display: 'inline-block'}}>
<span
style={{
position: 'absolute',
left: 0,
right: 0,
top: '50%',
height: '1.05em',
transform: `translateY(-50%) scaleX(${scaleX})`,
transformOrigin: 'left center',
backgroundColor: color,
borderRadius: '0.18em',
zIndex: 0,
}}
/>
<span style={{position: 'relative', zIndex: 1}}>{word}</span>
</span>
);
};
export const MyAnimation = () => {
const highlightIndex = FULL_TEXT.indexOf(HIGHLIGHT_WORD);
const hasHighlight = highlightIndex >= 0;
const preText = hasHighlight ? FULL_TEXT.slice(0, highlightIndex) : FULL_TEXT;
const postText = hasHighlight
? FULL_TEXT.slice(highlightIndex + HIGHLIGHT_WORD.length)
: '';
return (
<AbsoluteFill
style={{
backgroundColor: COLOR_BG,
alignItems: 'center',
justifyContent: 'center',
fontFamily,
}}
>
<div
style={{
color: COLOR_TEXT,
fontSize: FONT_SIZE,
fontWeight: FONT_WEIGHT,
}}
>
{hasHighlight ? (
<>
<span>{preText}</span>
<Highlight
word={HIGHLIGHT_WORD}
color={COLOR_HIGHLIGHT}
delay={HIGHLIGHT_START_FRAME}
durationInFrames={HIGHLIGHT_WIPE_DURATION}
/>
<span>{postText}</span>
</>
) : (
<span>{FULL_TEXT}</span>
)}
</div>
</AbsoluteFill>
);
};
Submodule builtin-skills/basic-for-all/my-writing-style added at d94826e1cd.
@@ -0,0 +1,158 @@
// Complete implementation of condition-based waiting utilities
// From: Lace test infrastructure improvements (2025-10-03)
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
import type { ThreadManager } from '~/threads/thread-manager';
import type { LaceEvent, LaceEventType } from '~/threads/types';
/**
* Wait for a specific event type to appear in thread
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param eventType - Type of event to wait for
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to the first matching event
*
* Example:
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
*/
export function waitForEvent(
threadManager: ThreadManager,
threadId: string,
eventType: LaceEventType,
timeoutMs = 5000
): Promise<LaceEvent> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const event = events.find((e) => e.type === eventType);
if (event) {
resolve(event);
} else if (Date.now() - startTime > timeoutMs) {
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
} else {
setTimeout(check, 10); // Poll every 10ms for efficiency
}
};
check();
});
}
/**
* Wait for a specific number of events of a given type
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param eventType - Type of event to wait for
* @param count - Number of events to wait for
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to all matching events once count is reached
*
* Example:
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
*/
export function waitForEventCount(
threadManager: ThreadManager,
threadId: string,
eventType: LaceEventType,
count: number,
timeoutMs = 5000
): Promise<LaceEvent[]> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const matchingEvents = events.filter((e) => e.type === eventType);
if (matchingEvents.length >= count) {
resolve(matchingEvents);
} else if (Date.now() - startTime > timeoutMs) {
reject(
new Error(
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
)
);
} else {
setTimeout(check, 10);
}
};
check();
});
}
/**
* Wait for an event matching a custom predicate
* Useful when you need to check event data, not just type
*
* @param threadManager - The thread manager to query
* @param threadId - Thread to check for events
* @param predicate - Function that returns true when event matches
* @param description - Human-readable description for error messages
* @param timeoutMs - Maximum time to wait (default 5000ms)
* @returns Promise resolving to the first matching event
*
* Example:
* // Wait for TOOL_RESULT with specific ID
* await waitForEventMatch(
* threadManager,
* agentThreadId,
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
* 'TOOL_RESULT with id=call_123'
* );
*/
export function waitForEventMatch(
threadManager: ThreadManager,
threadId: string,
predicate: (event: LaceEvent) => boolean,
description: string,
timeoutMs = 5000
): Promise<LaceEvent> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
const events = threadManager.getEvents(threadId);
const event = events.find(predicate);
if (event) {
resolve(event);
} else if (Date.now() - startTime > timeoutMs) {
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
} else {
setTimeout(check, 10);
}
};
check();
});
}
// Usage example from actual debugging session:
//
// BEFORE (flaky):
// ---------------
// const messagePromise = agent.sendMessage('Execute tools');
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
// agent.abort();
// await messagePromise;
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
// expect(toolResults.length).toBe(2); // Fails randomly
//
// AFTER (reliable):
// ----------------
// const messagePromise = agent.sendMessage('Execute tools');
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
// agent.abort();
// await messagePromise;
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
// expect(toolResults.length).toBe(2); // Always succeeds
//
// Result: 60% pass rate → 100%, 40% faster execution
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Bisection script to find which test creates unwanted files/state
# Usage: ./find-polluter.sh <file_or_dir_to_check> <test_pattern>
# Example: ./find-polluter.sh '.git' 'src/**/*.test.ts'
set -e
if [ $# -ne 2 ]; then
echo "Usage: $0 <file_to_check> <test_pattern>"
echo "Example: $0 '.git' 'src/**/*.test.ts'"
exit 1
fi
POLLUTION_CHECK="$1"
TEST_PATTERN="$2"
echo "🔍 Searching for test that creates: $POLLUTION_CHECK"
echo "Test pattern: $TEST_PATTERN"
echo ""
# Get list of test files
TEST_FILES=$(find . -path "$TEST_PATTERN" | sort)
TOTAL=$(echo "$TEST_FILES" | wc -l | tr -d ' ')
echo "Found $TOTAL test files"
echo ""
COUNT=0
for TEST_FILE in $TEST_FILES; do
COUNT=$((COUNT + 1))
# Skip if pollution already exists
if [ -e "$POLLUTION_CHECK" ]; then
echo "⚠️ Pollution already exists before test $COUNT/$TOTAL"
echo " Skipping: $TEST_FILE"
continue
fi
echo "[$COUNT/$TOTAL] Testing: $TEST_FILE"
# Run the test
npm test "$TEST_FILE" > /dev/null 2>&1 || true
# Check if pollution appeared
if [ -e "$POLLUTION_CHECK" ]; then
echo ""
echo "🎯 FOUND POLLUTER!"
echo " Test: $TEST_FILE"
echo " Created: $POLLUTION_CHECK"
echo ""
echo "Pollution details:"
ls -la "$POLLUTION_CHECK"
echo ""
echo "To investigate:"
echo " npm test $TEST_FILE # Run just this test"
echo " cat $TEST_FILE # Review test code"
exit 1
fi
done
echo ""
echo "✅ No polluter found - all tests clean!"
exit 0
@@ -0,0 +1,30 @@
© 2025 Anthropic, PBC. All rights reserved.
LICENSE: Use of these materials (including all code, prompts, assets, files,
and other components of this Skill) is governed by your agreement with
Anthropic regarding use of Anthropic's services. If no separate agreement
exists, use is governed by Anthropic's Consumer Terms of Service or
Commercial Terms of Service, as applicable:
https://www.anthropic.com/legal/consumer-terms
https://www.anthropic.com/legal/commercial-terms
Your applicable agreement is referred to as the "Agreement." "Services" are
as defined in the Agreement.
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
contrary, users may not:
- Extract these materials from the Services or retain copies of these
materials outside the Services
- Reproduce or copy these materials, except for temporary copies created
automatically during authorized use of the Services
- Create derivative works based on these materials
- Distribute, sublicense, or transfer these materials to any third party
- Make, offer to sell, sell, or import any inventions embodied in these
materials
- Reverse engineer, decompile, or disassemble these materials
The receipt, viewing, or possession of these materials does not convey or
imply any license or right beyond those expressly granted above.
Anthropic retains all right, title, and interest in these materials,
including all copyrights, patents, and other intellectual property rights.
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,135 @@
"""Accept all tracked changes in a DOCX file using LibreOffice.
Requires LibreOffice (soffice) to be installed.
"""
import argparse
import logging
import shutil
import subprocess
from pathlib import Path
from office.soffice import get_soffice_env
logger = logging.getLogger(__name__)
LIBREOFFICE_PROFILE = "/tmp/libreoffice_docx_profile"
MACRO_DIR = f"{LIBREOFFICE_PROFILE}/user/basic/Standard"
ACCEPT_CHANGES_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
Sub AcceptAllTrackedChanges()
Dim document As Object
Dim dispatcher As Object
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(document, ".uno:AcceptAllTrackedChanges", "", 0, Array())
ThisComponent.store()
ThisComponent.close(True)
End Sub
</script:module>"""
def accept_changes(
input_file: str,
output_file: str,
) -> tuple[None, str]:
input_path = Path(input_file)
output_path = Path(output_file)
if not input_path.exists():
return None, f"Error: Input file not found: {input_file}"
if not input_path.suffix.lower() == ".docx":
return None, f"Error: Input file is not a DOCX file: {input_file}"
try:
output_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(input_path, output_path)
except Exception as e:
return None, f"Error: Failed to copy input file to output location: {e}"
if not _setup_libreoffice_macro():
return None, "Error: Failed to setup LibreOffice macro"
cmd = [
"soffice",
"--headless",
f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
"--norestore",
"vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
str(output_path.absolute()),
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
check=False,
env=get_soffice_env(),
)
except subprocess.TimeoutExpired:
return (
None,
f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
)
if result.returncode != 0:
return None, f"Error: LibreOffice failed: {result.stderr}"
return (
None,
f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
)
def _setup_libreoffice_macro() -> bool:
macro_dir = Path(MACRO_DIR)
macro_file = macro_dir / "Module1.xba"
if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text():
return True
if not macro_dir.exists():
subprocess.run(
[
"soffice",
"--headless",
f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
"--terminate_after_init",
],
capture_output=True,
timeout=10,
check=False,
env=get_soffice_env(),
)
macro_dir.mkdir(parents=True, exist_ok=True)
try:
macro_file.write_text(ACCEPT_CHANGES_MACRO)
return True
except Exception as e:
logger.warning(f"Failed to setup LibreOffice macro: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Accept all tracked changes in a DOCX file"
)
parser.add_argument("input_file", help="Input DOCX file with tracked changes")
parser.add_argument(
"output_file", help="Output DOCX file (clean, no tracked changes)"
)
args = parser.parse_args()
_, message = accept_changes(args.input_file, args.output_file)
print(message)
if "Error" in message:
raise SystemExit(1)
+318
View File
@@ -0,0 +1,318 @@
"""Add comments to DOCX documents.
Usage:
python comment.py unpacked/ 0 "Comment text"
python comment.py unpacked/ 1 "Reply text" --parent 0
Text should be pre-escaped XML (e.g., &amp; for &, &#x2019; for smart quotes).
After running, add markers to document.xml:
<w:commentRangeStart w:id="0"/>
... commented content ...
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
"""
import argparse
import random
import shutil
import sys
from datetime import datetime, timezone
from pathlib import Path
import defusedxml.minidom
TEMPLATE_DIR = Path(__file__).parent / "templates"
NS = {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
"w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex",
}
COMMENT_XML = """\
<w:comment w:id="{id}" w:author="{author}" w:date="{date}" w:initials="{initials}">
<w:p w14:paraId="{para_id}" w14:textId="77777777">
<w:r>
<w:rPr><w:rStyle w:val="CommentReference"/></w:rPr>
<w:annotationRef/>
</w:r>
<w:r>
<w:rPr>
<w:color w:val="000000"/>
<w:sz w:val="20"/>
<w:szCs w:val="20"/>
</w:rPr>
<w:t>{text}</w:t>
</w:r>
</w:p>
</w:comment>"""
COMMENT_MARKER_TEMPLATE = """
Add to document.xml (markers must be direct children of w:p, never inside w:r):
<w:commentRangeStart w:id="{cid}"/>
<w:r>...</w:r>
<w:commentRangeEnd w:id="{cid}"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
REPLY_MARKER_TEMPLATE = """
Nest markers inside parent {pid}'s markers (markers must be direct children of w:p, never inside w:r):
<w:commentRangeStart w:id="{pid}"/><w:commentRangeStart w:id="{cid}"/>
<w:r>...</w:r>
<w:commentRangeEnd w:id="{cid}"/><w:commentRangeEnd w:id="{pid}"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{pid}"/></w:r>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
def _generate_hex_id() -> str:
return f"{random.randint(0, 0x7FFFFFFE):08X}"
SMART_QUOTE_ENTITIES = {
"\u201c": "&#x201C;",
"\u201d": "&#x201D;",
"\u2018": "&#x2018;",
"\u2019": "&#x2019;",
}
def _encode_smart_quotes(text: str) -> str:
for char, entity in SMART_QUOTE_ENTITIES.items():
text = text.replace(char, entity)
return text
def _append_xml(xml_path: Path, root_tag: str, content: str) -> None:
dom = defusedxml.minidom.parseString(xml_path.read_text(encoding="utf-8"))
root = dom.getElementsByTagName(root_tag)[0]
ns_attrs = " ".join(f'xmlns:{k}="{v}"' for k, v in NS.items())
wrapper_dom = defusedxml.minidom.parseString(f"<root {ns_attrs}>{content}</root>")
for child in wrapper_dom.documentElement.childNodes:
if child.nodeType == child.ELEMENT_NODE:
root.appendChild(dom.importNode(child, True))
output = _encode_smart_quotes(dom.toxml(encoding="UTF-8").decode("utf-8"))
xml_path.write_text(output, encoding="utf-8")
def _find_para_id(comments_path: Path, comment_id: int) -> str | None:
dom = defusedxml.minidom.parseString(comments_path.read_text(encoding="utf-8"))
for c in dom.getElementsByTagName("w:comment"):
if c.getAttribute("w:id") == str(comment_id):
for p in c.getElementsByTagName("w:p"):
if pid := p.getAttribute("w14:paraId"):
return pid
return None
def _get_next_rid(rels_path: Path) -> int:
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
max_rid = 0
for rel in dom.getElementsByTagName("Relationship"):
rid = rel.getAttribute("Id")
if rid and rid.startswith("rId"):
try:
max_rid = max(max_rid, int(rid[3:]))
except ValueError:
pass
return max_rid + 1
def _has_relationship(rels_path: Path, target: str) -> bool:
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
for rel in dom.getElementsByTagName("Relationship"):
if rel.getAttribute("Target") == target:
return True
return False
def _has_content_type(ct_path: Path, part_name: str) -> bool:
dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
for override in dom.getElementsByTagName("Override"):
if override.getAttribute("PartName") == part_name:
return True
return False
def _ensure_comment_relationships(unpacked_dir: Path) -> None:
rels_path = unpacked_dir / "word" / "_rels" / "document.xml.rels"
if not rels_path.exists():
return
if _has_relationship(rels_path, "comments.xml"):
return
dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
root = dom.documentElement
next_rid = _get_next_rid(rels_path)
rels = [
(
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
"comments.xml",
),
(
"http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
"commentsExtended.xml",
),
(
"http://schemas.microsoft.com/office/2016/09/relationships/commentsIds",
"commentsIds.xml",
),
(
"http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible",
"commentsExtensible.xml",
),
]
for rel_type, target in rels:
rel = dom.createElement("Relationship")
rel.setAttribute("Id", f"rId{next_rid}")
rel.setAttribute("Type", rel_type)
rel.setAttribute("Target", target)
root.appendChild(rel)
next_rid += 1
rels_path.write_bytes(dom.toxml(encoding="UTF-8"))
def _ensure_comment_content_types(unpacked_dir: Path) -> None:
ct_path = unpacked_dir / "[Content_Types].xml"
if not ct_path.exists():
return
if _has_content_type(ct_path, "/word/comments.xml"):
return
dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
root = dom.documentElement
overrides = [
(
"/word/comments.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
),
(
"/word/commentsExtended.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml",
),
(
"/word/commentsIds.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml",
),
(
"/word/commentsExtensible.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml",
),
]
for part_name, content_type in overrides:
override = dom.createElement("Override")
override.setAttribute("PartName", part_name)
override.setAttribute("ContentType", content_type)
root.appendChild(override)
ct_path.write_bytes(dom.toxml(encoding="UTF-8"))
def add_comment(
unpacked_dir: str,
comment_id: int,
text: str,
author: str = "Claude",
initials: str = "C",
parent_id: int | None = None,
) -> tuple[str, str]:
word = Path(unpacked_dir) / "word"
if not word.exists():
return "", f"Error: {word} not found"
para_id, durable_id = _generate_hex_id(), _generate_hex_id()
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
comments = word / "comments.xml"
first_comment = not comments.exists()
if first_comment:
shutil.copy(TEMPLATE_DIR / "comments.xml", comments)
_ensure_comment_relationships(Path(unpacked_dir))
_ensure_comment_content_types(Path(unpacked_dir))
_append_xml(
comments,
"w:comments",
COMMENT_XML.format(
id=comment_id,
author=author,
date=ts,
initials=initials,
para_id=para_id,
text=text,
),
)
ext = word / "commentsExtended.xml"
if not ext.exists():
shutil.copy(TEMPLATE_DIR / "commentsExtended.xml", ext)
if parent_id is not None:
parent_para = _find_para_id(comments, parent_id)
if not parent_para:
return "", f"Error: Parent comment {parent_id} not found"
_append_xml(
ext,
"w15:commentsEx",
f'<w15:commentEx w15:paraId="{para_id}" w15:paraIdParent="{parent_para}" w15:done="0"/>',
)
else:
_append_xml(
ext,
"w15:commentsEx",
f'<w15:commentEx w15:paraId="{para_id}" w15:done="0"/>',
)
ids = word / "commentsIds.xml"
if not ids.exists():
shutil.copy(TEMPLATE_DIR / "commentsIds.xml", ids)
_append_xml(
ids,
"w16cid:commentsIds",
f'<w16cid:commentId w16cid:paraId="{para_id}" w16cid:durableId="{durable_id}"/>',
)
extensible = word / "commentsExtensible.xml"
if not extensible.exists():
shutil.copy(TEMPLATE_DIR / "commentsExtensible.xml", extensible)
_append_xml(
extensible,
"w16cex:commentsExtensible",
f'<w16cex:commentExtensible w16cex:durableId="{durable_id}" w16cex:dateUtc="{ts}"/>',
)
action = "reply" if parent_id is not None else "comment"
return para_id, f"Added {action} {comment_id} (para_id={para_id})"
if __name__ == "__main__":
p = argparse.ArgumentParser(description="Add comments to DOCX documents")
p.add_argument("unpacked_dir", help="Unpacked DOCX directory")
p.add_argument("comment_id", type=int, help="Comment ID (must be unique)")
p.add_argument("text", help="Comment text")
p.add_argument("--author", default="Claude", help="Author name")
p.add_argument("--initials", default="C", help="Author initials")
p.add_argument("--parent", type=int, help="Parent comment ID (for replies)")
args = p.parse_args()
para_id, msg = add_comment(
args.unpacked_dir,
args.comment_id,
args.text,
args.author,
args.initials,
args.parent,
)
print(msg)
if "Error" in msg:
sys.exit(1)
cid = args.comment_id
if args.parent is not None:
print(REPLY_MARKER_TEMPLATE.format(pid=args.parent, cid=cid))
else:
print(COMMENT_MARKER_TEMPLATE.format(cid=cid))
Whitespace-only changes.
@@ -0,0 +1,199 @@
"""Merge adjacent runs with identical formatting in DOCX.
Merges adjacent <w:r> elements that have identical <w:rPr> properties.
Works on runs in paragraphs and inside tracked changes (<w:ins>, <w:del>).
Also:
- Removes rsid attributes from runs (revision metadata that doesn't affect rendering)
- Removes proofErr elements (spell/grammar markers that block merging)
"""
from pathlib import Path
import defusedxml.minidom
def merge_runs(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
_remove_elements(root, "proofErr")
_strip_run_rsid_attrs(root)
containers = {run.parentNode for run in _find_elements(root, "r")}
merge_count = 0
for container in containers:
merge_count += _merge_runs_in(container)
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Merged {merge_count} runs"
except Exception as e:
return 0, f"Error: {e}"
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def _get_child(parent, tag: str):
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
return child
return None
def _get_children(parent, tag: str) -> list:
results = []
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(child)
return results
def _is_adjacent(elem1, elem2) -> bool:
node = elem1.nextSibling
while node:
if node == elem2:
return True
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return False
def _remove_elements(root, tag: str):
for elem in _find_elements(root, tag):
if elem.parentNode:
elem.parentNode.removeChild(elem)
def _strip_run_rsid_attrs(root):
for run in _find_elements(root, "r"):
for attr in list(run.attributes.values()):
if "rsid" in attr.name.lower():
run.removeAttribute(attr.name)
def _merge_runs_in(container) -> int:
merge_count = 0
run = _first_child_run(container)
while run:
while True:
next_elem = _next_element_sibling(run)
if next_elem and _is_run(next_elem) and _can_merge(run, next_elem):
_merge_run_content(run, next_elem)
container.removeChild(next_elem)
merge_count += 1
else:
break
_consolidate_text(run)
run = _next_sibling_run(run)
return merge_count
def _first_child_run(container):
for child in container.childNodes:
if child.nodeType == child.ELEMENT_NODE and _is_run(child):
return child
return None
def _next_element_sibling(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
return sibling
sibling = sibling.nextSibling
return None
def _next_sibling_run(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
if _is_run(sibling):
return sibling
sibling = sibling.nextSibling
return None
def _is_run(node) -> bool:
name = node.localName or node.tagName
return name == "r" or name.endswith(":r")
def _can_merge(run1, run2) -> bool:
rpr1 = _get_child(run1, "rPr")
rpr2 = _get_child(run2, "rPr")
if (rpr1 is None) != (rpr2 is None):
return False
if rpr1 is None:
return True
return rpr1.toxml() == rpr2.toxml()
def _merge_run_content(target, source):
for child in list(source.childNodes):
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name != "rPr" and not name.endswith(":rPr"):
target.appendChild(child)
def _consolidate_text(run):
t_elements = _get_children(run, "t")
for i in range(len(t_elements) - 1, 0, -1):
curr, prev = t_elements[i], t_elements[i - 1]
if _is_adjacent(prev, curr):
prev_text = prev.firstChild.data if prev.firstChild else ""
curr_text = curr.firstChild.data if curr.firstChild else ""
merged = prev_text + curr_text
if prev.firstChild:
prev.firstChild.data = merged
else:
prev.appendChild(run.ownerDocument.createTextNode(merged))
if merged.startswith(" ") or merged.endswith(" "):
prev.setAttribute("xml:space", "preserve")
elif prev.hasAttribute("xml:space"):
prev.removeAttribute("xml:space")
run.removeChild(curr)
@@ -0,0 +1,197 @@
"""Simplify tracked changes by merging adjacent w:ins or w:del elements.
Merges adjacent <w:ins> elements from the same author into a single element.
Same for <w:del> elements. This makes heavily-redlined documents easier to
work with by reducing the number of tracked change wrappers.
Rules:
- Only merges w:ins with w:ins, w:del with w:del (same element type)
- Only merges if same author (ignores timestamp differences)
- Only merges if truly adjacent (only whitespace between them)
"""
import xml.etree.ElementTree as ET
import zipfile
from pathlib import Path
import defusedxml.minidom
WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
def simplify_redlines(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
merge_count = 0
containers = _find_elements(root, "p") + _find_elements(root, "tc")
for container in containers:
merge_count += _merge_tracked_changes_in(container, "ins")
merge_count += _merge_tracked_changes_in(container, "del")
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Simplified {merge_count} tracked changes"
except Exception as e:
return 0, f"Error: {e}"
def _merge_tracked_changes_in(container, tag: str) -> int:
merge_count = 0
tracked = [
child
for child in container.childNodes
if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
]
if len(tracked) < 2:
return 0
i = 0
while i < len(tracked) - 1:
curr = tracked[i]
next_elem = tracked[i + 1]
if _can_merge_tracked(curr, next_elem):
_merge_tracked_content(curr, next_elem)
container.removeChild(next_elem)
tracked.pop(i + 1)
merge_count += 1
else:
i += 1
return merge_count
def _is_element(node, tag: str) -> bool:
name = node.localName or node.tagName
return name == tag or name.endswith(f":{tag}")
def _get_author(elem) -> str:
author = elem.getAttribute("w:author")
if not author:
for attr in elem.attributes.values():
if attr.localName == "author" or attr.name.endswith(":author"):
return attr.value
return author
def _can_merge_tracked(elem1, elem2) -> bool:
if _get_author(elem1) != _get_author(elem2):
return False
node = elem1.nextSibling
while node and node != elem2:
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return True
def _merge_tracked_content(target, source):
while source.firstChild:
child = source.firstChild
source.removeChild(child)
target.appendChild(child)
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
if not doc_xml_path.exists():
return {}
try:
tree = ET.parse(doc_xml_path)
root = tree.getroot()
except ET.ParseError:
return {}
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
try:
with zipfile.ZipFile(docx_path, "r") as zf:
if "word/document.xml" not in zf.namelist():
return {}
with zf.open("word/document.xml") as f:
tree = ET.parse(f)
root = tree.getroot()
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
except (zipfile.BadZipFile, ET.ParseError):
return {}
def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str:
modified_xml = modified_dir / "word" / "document.xml"
modified_authors = get_tracked_change_authors(modified_xml)
if not modified_authors:
return default
original_authors = _get_authors_from_docx(original_docx)
new_changes: dict[str, int] = {}
for author, count in modified_authors.items():
original_count = original_authors.get(author, 0)
diff = count - original_count
if diff > 0:
new_changes[author] = diff
if not new_changes:
return default
if len(new_changes) == 1:
return next(iter(new_changes))
raise ValueError(
f"Multiple authors added new changes: {new_changes}. "
"Cannot infer which author to validate."
)
+159
View File
@@ -0,0 +1,159 @@
"""Pack a directory into a DOCX, PPTX, or XLSX file.
Validates with auto-repair, condenses XML formatting, and creates the Office file.
Usage:
python pack.py <input_directory> <output_file> [--original <file>] [--validate true|false]
Examples:
python pack.py unpacked/ output.docx --original input.docx
python pack.py unpacked/ output.pptx --validate false
"""
import argparse
import sys
import shutil
import tempfile
import zipfile
from pathlib import Path
import defusedxml.minidom
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def pack(
input_directory: str,
output_file: str,
original_file: str | None = None,
validate: bool = True,
infer_author_func=None,
) -> tuple[None, str]:
input_dir = Path(input_directory)
output_path = Path(output_file)
suffix = output_path.suffix.lower()
if not input_dir.is_dir():
return None, f"Error: {input_dir} is not a directory"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file"
if validate and original_file:
original_path = Path(original_file)
if original_path.exists():
success, output = _run_validation(
input_dir, original_path, suffix, infer_author_func
)
if output:
print(output)
if not success:
return None, f"Error: Validation failed for {input_dir}"
with tempfile.TemporaryDirectory() as temp_dir:
temp_content_dir = Path(temp_dir) / "content"
shutil.copytree(input_dir, temp_content_dir)
for pattern in ["*.xml", "*.rels"]:
for xml_file in temp_content_dir.rglob(pattern):
_condense_xml(xml_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
for f in temp_content_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(temp_content_dir))
return None, f"Successfully packed {input_dir} to {output_file}"
def _run_validation(
unpacked_dir: Path,
original_file: Path,
suffix: str,
infer_author_func=None,
) -> tuple[bool, str | None]:
output_lines = []
validators = []
if suffix == ".docx":
author = "Claude"
if infer_author_func:
try:
author = infer_author_func(unpacked_dir, original_file)
except ValueError as e:
print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr)
validators = [
DOCXSchemaValidator(unpacked_dir, original_file),
RedliningValidator(unpacked_dir, original_file, author=author),
]
elif suffix == ".pptx":
validators = [PPTXSchemaValidator(unpacked_dir, original_file)]
if not validators:
return True, None
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
output_lines.append(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
output_lines.append("All validations PASSED!")
return success, "\n".join(output_lines) if output_lines else None
def _condense_xml(xml_file: Path) -> None:
try:
with open(xml_file, encoding="utf-8") as f:
dom = defusedxml.minidom.parse(f)
for element in dom.getElementsByTagName("*"):
if element.tagName.endswith(":t"):
continue
for child in list(element.childNodes):
if (
child.nodeType == child.TEXT_NODE
and child.nodeValue
and child.nodeValue.strip() == ""
) or child.nodeType == child.COMMENT_NODE:
element.removeChild(child)
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception as e:
print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr)
raise
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Pack a directory into a DOCX, PPTX, or XLSX file"
)
parser.add_argument("input_directory", help="Unpacked Office document directory")
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
parser.add_argument(
"--original",
help="Original file for validation comparison",
)
parser.add_argument(
"--validate",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Run validation with auto-repair (default: true)",
)
args = parser.parse_args()
_, message = pack(
args.input_directory,
args.output_file,
original_file=args.original,
validate=args.validate,
)
print(message)
if "Error" in message:
sys.exit(1)
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:complexType name="CT_ShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Shape">
<xsd:sequence>
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_ConnectorNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Connector">
<xsd:sequence>
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence>
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrameNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrame">
<xsd:sequence>
<xsd:element name="nvGraphicFramePr" type="CT_GraphicFrameNonVisual" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GroupShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupShape">
<xsd:sequence>
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_ObjectChoices">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_MarkerCoordinate">
<xsd:restriction base="xsd:double">
<xsd:minInclusive value="0.0"/>
<xsd:maxInclusive value="1.0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Marker">
<xsd:sequence>
<xsd:element name="x" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
<xsd:element name="y" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RelSizeAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:group ref="EG_ObjectChoices"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AbsSizeAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_Anchor">
<xsd:choice>
<xsd:element name="relSizeAnchor" type="CT_RelSizeAnchor"/>
<xsd:element name="absSizeAnchor" type="CT_AbsSizeAnchor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Drawing">
<xsd:sequence>
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:element name="lockedCanvas" type="a:CT_GvmlGroupShape"/>
</xsd:schema>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/picture">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:schema>
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:import schemaLocation="shared-relationshipReference.xsd"
namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:complexType name="CT_AnchorClientData">
<xsd:attribute name="fLocksWithSheet" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPrintsWithSheet" type="xsd:boolean" use="optional" default="true"/>
</xsd:complexType>
<xsd:complexType name="CT_ShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Shape">
<xsd:sequence>
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_ConnectorNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Connector">
<xsd:sequence>
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_PictureNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Picture">
<xsd:sequence>
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicalObjectFrameNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GraphicalObjectFrame">
<xsd:sequence>
<xsd:element name="nvGraphicFramePr" type="CT_GraphicalObjectFrameNonVisual" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GroupShapeNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupShape">
<xsd:sequence>
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_ObjectChoices">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="sp" type="CT_Shape"/>
<xsd:element name="grpSp" type="CT_GroupShape"/>
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
<xsd:element name="cxnSp" type="CT_Connector"/>
<xsd:element name="pic" type="CT_Picture"/>
<xsd:element name="contentPart" type="CT_Rel"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:complexType name="CT_Rel">
<xsd:attribute ref="r:id" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_ColID">
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RowID">
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Marker">
<xsd:sequence>
<xsd:element name="col" type="ST_ColID"/>
<xsd:element name="colOff" type="a:ST_Coordinate"/>
<xsd:element name="row" type="ST_RowID"/>
<xsd:element name="rowOff" type="a:ST_Coordinate"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_EditAs">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="twoCell"/>
<xsd:enumeration value="oneCell"/>
<xsd:enumeration value="absolute"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_TwoCellAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="to" type="CT_Marker"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
</xsd:complexType>
<xsd:complexType name="CT_OneCellAnchor">
<xsd:sequence>
<xsd:element name="from" type="CT_Marker"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AbsoluteAnchor">
<xsd:sequence>
<xsd:element name="pos" type="a:CT_Point2D"/>
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
<xsd:group ref="EG_ObjectChoices"/>
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_Anchor">
<xsd:choice>
<xsd:element name="twoCellAnchor" type="CT_TwoCellAnchor"/>
<xsd:element name="oneCellAnchor" type="CT_OneCellAnchor"/>
<xsd:element name="absoluteAnchor" type="CT_AbsoluteAnchor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Drawing">
<xsd:sequence>
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="wsDr" type="CT_Drawing"/>
</xsd:schema>
@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:dpct="http://schemas.openxmlformats.org/drawingml/2006/picture"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
schemaLocation="dml-main.xsd"/>
<xsd:import schemaLocation="wml.xsd"
namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/picture"
schemaLocation="dml-picture.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:complexType name="CT_EffectExtent">
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_WrapDistance">
<xsd:restriction base="xsd:unsignedInt"/>
</xsd:simpleType>
<xsd:complexType name="CT_Inline">
<xsd:sequence>
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="0" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_WrapText">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="bothSides"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="largest"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_WrapPath">
<xsd:sequence>
<xsd:element name="start" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
<xsd:element name="lineTo" type="a:CT_Point2D" minOccurs="2" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="edited" type="xsd:boolean" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapNone"/>
<xsd:complexType name="CT_WrapSquare">
<xsd:sequence>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapTight">
<xsd:sequence>
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapThrough">
<xsd:sequence>
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_WrapTopBottom">
<xsd:sequence>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_WrapType">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="wrapNone" type="CT_WrapNone" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapSquare" type="CT_WrapSquare" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapTight" type="CT_WrapTight" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapThrough" type="CT_WrapThrough" minOccurs="1" maxOccurs="1"/>
<xsd:element name="wrapTopAndBottom" type="CT_WrapTopBottom" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_PositionOffset">
<xsd:restriction base="xsd:int"/>
</xsd:simpleType>
<xsd:simpleType name="ST_AlignH">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RelFromH">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="column"/>
<xsd:enumeration value="character"/>
<xsd:enumeration value="leftMargin"/>
<xsd:enumeration value="rightMargin"/>
<xsd:enumeration value="insideMargin"/>
<xsd:enumeration value="outsideMargin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_PosH">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_AlignV">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="top"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RelFromV">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="paragraph"/>
<xsd:enumeration value="line"/>
<xsd:enumeration value="topMargin"/>
<xsd:enumeration value="bottomMargin"/>
<xsd:enumeration value="insideMargin"/>
<xsd:enumeration value="outsideMargin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_PosV">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Anchor">
<xsd:sequence>
<xsd:element name="simplePos" type="a:CT_Point2D"/>
<xsd:element name="positionH" type="CT_PosH"/>
<xsd:element name="positionV" type="CT_PosV"/>
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
<xsd:group ref="EG_WrapType"/>
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="0" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="simplePos" type="xsd:boolean"/>
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_TxbxContent">
<xsd:group ref="w:EG_BlockLevelElts" minOccurs="1" maxOccurs="unbounded"/>
</xsd:complexType>
<xsd:complexType name="CT_TextboxInfo">
<xsd:sequence>
<xsd:element name="txbxContent" type="CT_TxbxContent" minOccurs="1" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:unsignedShort" use="optional" default="0"/>
</xsd:complexType>
<xsd:complexType name="CT_LinkedTextboxInformation">
<xsd:sequence>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:unsignedShort" use="required"/>
<xsd:attribute name="seq" type="xsd:unsignedShort" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingShape">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="cNvCnPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
maxOccurs="1"/>
</xsd:choice>
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="txbx" type="CT_TextboxInfo" minOccurs="1" maxOccurs="1"/>
<xsd:element name="linkedTxbx" type="CT_LinkedTextboxInformation" minOccurs="1"
maxOccurs="1"/>
</xsd:choice>
<xsd:element name="bodyPr" type="a:CT_TextBodyProperties" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="normalEastAsianFlow" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
<xsd:complexType name="CT_GraphicFrame">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvFrPr" type="a:CT_NonVisualGraphicFrameProperties" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingContentPartNonVisual">
<xsd:sequence>
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:element name="cNvContentPartPr" type="a:CT_NonVisualContentPartProperties" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingContentPart">
<xsd:sequence>
<xsd:element name="nvContentPartPr" type="CT_WordprocessingContentPartNonVisual" minOccurs="0" maxOccurs="1"/>
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="0" maxOccurs="1"/>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="bwMode" type="a:ST_BlackWhiteMode" use="optional"/>
<xsd:attribute ref="r:id" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingGroup">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
maxOccurs="1"/>
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="wsp"/>
<xsd:element name="grpSp" type="CT_WordprocessingGroup"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
<xsd:element ref="dpct:pic"/>
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
</xsd:choice>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_WordprocessingCanvas">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element name="bg" type="a:CT_BackgroundFormatting" minOccurs="0" maxOccurs="1"/>
<xsd:element name="whole" type="a:CT_WholeE2oFormatting" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="wsp"/>
<xsd:element ref="dpct:pic"/>
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
<xsd:element ref="wgp"/>
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
</xsd:choice>
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="wpc" type="CT_WordprocessingCanvas"/>
<xsd:element name="wgp" type="CT_WordprocessingGroup"/>
<xsd:element name="wsp" type="CT_WordprocessingShape"/>
<xsd:element name="inline" type="CT_Inline"/>
<xsd:element name="anchor" type="CT_Anchor"/>
</xsd:schema>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
elementFormDefault="qualified">
<xsd:complexType name="CT_AdditionalCharacteristics">
<xsd:sequence>
<xsd:element name="characteristic" type="CT_Characteristic" minOccurs="0"
maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Characteristic">
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="relation" type="ST_Relation" use="required"/>
<xsd:attribute name="val" type="xsd:string" use="required"/>
<xsd:attribute name="vocabulary" type="xsd:anyURI" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_Relation">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ge"/>
<xsd:enumeration value="le"/>
<xsd:enumeration value="gt"/>
<xsd:enumeration value="lt"/>
<xsd:enumeration value="eq"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="additionalCharacteristics" type="CT_AdditionalCharacteristics"/>
</xsd:schema>
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:simpleType name="ST_SourceType">
<xsd:restriction base="s:ST_String">
<xsd:enumeration value="ArticleInAPeriodical"/>
<xsd:enumeration value="Book"/>
<xsd:enumeration value="BookSection"/>
<xsd:enumeration value="JournalArticle"/>
<xsd:enumeration value="ConferenceProceedings"/>
<xsd:enumeration value="Report"/>
<xsd:enumeration value="SoundRecording"/>
<xsd:enumeration value="Performance"/>
<xsd:enumeration value="Art"/>
<xsd:enumeration value="DocumentFromInternetSite"/>
<xsd:enumeration value="InternetSite"/>
<xsd:enumeration value="Film"/>
<xsd:enumeration value="Interview"/>
<xsd:enumeration value="Patent"/>
<xsd:enumeration value="ElectronicSource"/>
<xsd:enumeration value="Case"/>
<xsd:enumeration value="Misc"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NameListType">
<xsd:sequence>
<xsd:element name="Person" type="CT_PersonType" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PersonType">
<xsd:sequence>
<xsd:element name="Last" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="First" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="Middle" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NameType">
<xsd:sequence>
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NameOrCorporateType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="Corporate" minOccurs="1" maxOccurs="1" type="s:ST_String"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AuthorType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="Artist" type="CT_NameType"/>
<xsd:element name="Author" type="CT_NameOrCorporateType"/>
<xsd:element name="BookAuthor" type="CT_NameType"/>
<xsd:element name="Compiler" type="CT_NameType"/>
<xsd:element name="Composer" type="CT_NameType"/>
<xsd:element name="Conductor" type="CT_NameType"/>
<xsd:element name="Counsel" type="CT_NameType"/>
<xsd:element name="Director" type="CT_NameType"/>
<xsd:element name="Editor" type="CT_NameType"/>
<xsd:element name="Interviewee" type="CT_NameType"/>
<xsd:element name="Interviewer" type="CT_NameType"/>
<xsd:element name="Inventor" type="CT_NameType"/>
<xsd:element name="Performer" type="CT_NameOrCorporateType"/>
<xsd:element name="ProducerName" type="CT_NameType"/>
<xsd:element name="Translator" type="CT_NameType"/>
<xsd:element name="Writer" type="CT_NameType"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SourceType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="AbbreviatedCaseNumber" type="s:ST_String"/>
<xsd:element name="AlbumTitle" type="s:ST_String"/>
<xsd:element name="Author" type="CT_AuthorType"/>
<xsd:element name="BookTitle" type="s:ST_String"/>
<xsd:element name="Broadcaster" type="s:ST_String"/>
<xsd:element name="BroadcastTitle" type="s:ST_String"/>
<xsd:element name="CaseNumber" type="s:ST_String"/>
<xsd:element name="ChapterNumber" type="s:ST_String"/>
<xsd:element name="City" type="s:ST_String"/>
<xsd:element name="Comments" type="s:ST_String"/>
<xsd:element name="ConferenceName" type="s:ST_String"/>
<xsd:element name="CountryRegion" type="s:ST_String"/>
<xsd:element name="Court" type="s:ST_String"/>
<xsd:element name="Day" type="s:ST_String"/>
<xsd:element name="DayAccessed" type="s:ST_String"/>
<xsd:element name="Department" type="s:ST_String"/>
<xsd:element name="Distributor" type="s:ST_String"/>
<xsd:element name="Edition" type="s:ST_String"/>
<xsd:element name="Guid" type="s:ST_String"/>
<xsd:element name="Institution" type="s:ST_String"/>
<xsd:element name="InternetSiteTitle" type="s:ST_String"/>
<xsd:element name="Issue" type="s:ST_String"/>
<xsd:element name="JournalName" type="s:ST_String"/>
<xsd:element name="LCID" type="s:ST_Lang"/>
<xsd:element name="Medium" type="s:ST_String"/>
<xsd:element name="Month" type="s:ST_String"/>
<xsd:element name="MonthAccessed" type="s:ST_String"/>
<xsd:element name="NumberVolumes" type="s:ST_String"/>
<xsd:element name="Pages" type="s:ST_String"/>
<xsd:element name="PatentNumber" type="s:ST_String"/>
<xsd:element name="PeriodicalTitle" type="s:ST_String"/>
<xsd:element name="ProductionCompany" type="s:ST_String"/>
<xsd:element name="PublicationTitle" type="s:ST_String"/>
<xsd:element name="Publisher" type="s:ST_String"/>
<xsd:element name="RecordingNumber" type="s:ST_String"/>
<xsd:element name="RefOrder" type="s:ST_String"/>
<xsd:element name="Reporter" type="s:ST_String"/>
<xsd:element name="SourceType" type="ST_SourceType"/>
<xsd:element name="ShortTitle" type="s:ST_String"/>
<xsd:element name="StandardNumber" type="s:ST_String"/>
<xsd:element name="StateProvince" type="s:ST_String"/>
<xsd:element name="Station" type="s:ST_String"/>
<xsd:element name="Tag" type="s:ST_String"/>
<xsd:element name="Theater" type="s:ST_String"/>
<xsd:element name="ThesisType" type="s:ST_String"/>
<xsd:element name="Title" type="s:ST_String"/>
<xsd:element name="Type" type="s:ST_String"/>
<xsd:element name="URL" type="s:ST_String"/>
<xsd:element name="Version" type="s:ST_String"/>
<xsd:element name="Volume" type="s:ST_String"/>
<xsd:element name="Year" type="s:ST_String"/>
<xsd:element name="YearAccessed" type="s:ST_String"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="Sources" type="CT_Sources"/>
<xsd:complexType name="CT_Sources">
<xsd:sequence>
<xsd:element name="Source" type="CT_SourceType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="SelectedStyle" type="s:ST_String"/>
<xsd:attribute name="StyleName" type="s:ST_String"/>
<xsd:attribute name="URI" type="s:ST_String"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
elementFormDefault="qualified">
<xsd:simpleType name="ST_Lang">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_HexColorRGB">
<xsd:restriction base="xsd:hexBinary">
<xsd:length value="3" fixed="true"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Panose">
<xsd:restriction base="xsd:hexBinary">
<xsd:length value="10"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CalendarType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="gregorian"/>
<xsd:enumeration value="gregorianUs"/>
<xsd:enumeration value="gregorianMeFrench"/>
<xsd:enumeration value="gregorianArabic"/>
<xsd:enumeration value="hijri"/>
<xsd:enumeration value="hebrew"/>
<xsd:enumeration value="taiwan"/>
<xsd:enumeration value="japan"/>
<xsd:enumeration value="thai"/>
<xsd:enumeration value="korea"/>
<xsd:enumeration value="saka"/>
<xsd:enumeration value="gregorianXlitEnglish"/>
<xsd:enumeration value="gregorianXlitFrench"/>
<xsd:enumeration value="none"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_AlgClass">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="hash"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CryptProv">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="rsaAES"/>
<xsd:enumeration value="rsaFull"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_AlgType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="typeAny"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ColorType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_Guid">
<xsd:restriction base="xsd:token">
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OnOff">
<xsd:union memberTypes="xsd:boolean ST_OnOff1"/>
</xsd:simpleType>
<xsd:simpleType name="ST_OnOff1">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="on"/>
<xsd:enumeration value="off"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_String">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_XmlName">
<xsd:restriction base="xsd:NCName">
<xsd:minLength value="1"/>
<xsd:maxLength value="255"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TrueFalse">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TrueFalseBlank">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
<xsd:enumeration value=""/>
<xsd:enumeration value="True"/>
<xsd:enumeration value="False"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_UnsignedDecimalNumber">
<xsd:restriction base="xsd:decimal">
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_TwipsMeasure">
<xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
</xsd:simpleType>
<xsd:simpleType name="ST_VerticalAlignRun">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="baseline"/>
<xsd:enumeration value="superscript"/>
<xsd:enumeration value="subscript"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Xstring">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_XAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_YAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="inline"/>
<xsd:enumeration value="top"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="inside"/>
<xsd:enumeration value="outside"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConformanceClass">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="strict"/>
<xsd:enumeration value="transitional"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_UniversalMeasure">
<xsd:restriction base="xsd:string">
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositiveUniversalMeasure">
<xsd:restriction base="ST_UniversalMeasure">
<xsd:pattern value="[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Percentage">
<xsd:restriction base="xsd:string">
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FixedPercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="-?((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositivePercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="[0-9]+(\.[0-9]+)?%"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PositiveFixedPercentage">
<xsd:restriction base="ST_Percentage">
<xsd:pattern value="((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:complexType name="CT_DatastoreSchemaRef">
<xsd:attribute name="uri" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_DatastoreSchemaRefs">
<xsd:sequence>
<xsd:element name="schemaRef" type="CT_DatastoreSchemaRef" minOccurs="0" maxOccurs="unbounded"
/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DatastoreItem">
<xsd:sequence>
<xsd:element name="schemaRefs" type="CT_DatastoreSchemaRefs" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="itemID" type="s:ST_Guid" use="required"/>
</xsd:complexType>
<xsd:element name="datastoreItem" type="CT_DatastoreItem"/>
</xsd:schema>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
targetNamespace="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xsd:complexType name="CT_Schema">
<xsd:attribute name="uri" type="xsd:string" default=""/>
<xsd:attribute name="manifestLocation" type="xsd:string"/>
<xsd:attribute name="schemaLocation" type="xsd:string"/>
<xsd:attribute name="schemaLanguage" type="xsd:token"/>
</xsd:complexType>
<xsd:complexType name="CT_SchemaLibrary">
<xsd:sequence>
<xsd:element name="schema" type="CT_Schema" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="schemaLibrary" type="CT_SchemaLibrary"/>
</xsd:schema>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
blockDefault="#all" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:element name="Properties" type="CT_Properties"/>
<xsd:complexType name="CT_Properties">
<xsd:sequence>
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="CT_Property"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Property">
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
<xsd:element ref="vt:array"/>
<xsd:element ref="vt:blob"/>
<xsd:element ref="vt:oblob"/>
<xsd:element ref="vt:empty"/>
<xsd:element ref="vt:null"/>
<xsd:element ref="vt:i1"/>
<xsd:element ref="vt:i2"/>
<xsd:element ref="vt:i4"/>
<xsd:element ref="vt:i8"/>
<xsd:element ref="vt:int"/>
<xsd:element ref="vt:ui1"/>
<xsd:element ref="vt:ui2"/>
<xsd:element ref="vt:ui4"/>
<xsd:element ref="vt:ui8"/>
<xsd:element ref="vt:uint"/>
<xsd:element ref="vt:r4"/>
<xsd:element ref="vt:r8"/>
<xsd:element ref="vt:decimal"/>
<xsd:element ref="vt:lpstr"/>
<xsd:element ref="vt:lpwstr"/>
<xsd:element ref="vt:bstr"/>
<xsd:element ref="vt:date"/>
<xsd:element ref="vt:filetime"/>
<xsd:element ref="vt:bool"/>
<xsd:element ref="vt:cy"/>
<xsd:element ref="vt:error"/>
<xsd:element ref="vt:stream"/>
<xsd:element ref="vt:ostream"/>
<xsd:element ref="vt:storage"/>
<xsd:element ref="vt:ostorage"/>
<xsd:element ref="vt:vstream"/>
<xsd:element ref="vt:clsid"/>
</xsd:choice>
<xsd:attribute name="fmtid" use="required" type="s:ST_Guid"/>
<xsd:attribute name="pid" use="required" type="xsd:int"/>
<xsd:attribute name="name" use="optional" type="xsd:string"/>
<xsd:attribute name="linkTarget" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
elementFormDefault="qualified" blockDefault="#all">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
<xsd:element name="Properties" type="CT_Properties"/>
<xsd:complexType name="CT_Properties">
<xsd:all>
<xsd:element name="Template" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Manager" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Company" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Pages" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Words" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Characters" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="PresentationFormat" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="Lines" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Paragraphs" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Slides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="Notes" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="TotalTime" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="HiddenSlides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="MMClips" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="ScaleCrop" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="HeadingPairs" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
<xsd:element name="TitlesOfParts" minOccurs="0" maxOccurs="1" type="CT_VectorLpstr"/>
<xsd:element name="LinksUpToDate" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="CharactersWithSpaces" minOccurs="0" maxOccurs="1" type="xsd:int"/>
<xsd:element name="SharedDoc" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="HyperlinkBase" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="HLinks" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
<xsd:element name="HyperlinksChanged" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
<xsd:element name="DigSig" minOccurs="0" maxOccurs="1" type="CT_DigSigBlob"/>
<xsd:element name="Application" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="AppVersion" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="DocSecurity" minOccurs="0" maxOccurs="1" type="xsd:int"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="CT_VectorVariant">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_VectorLpstr">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:vector"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DigSigBlob">
<xsd:sequence minOccurs="1" maxOccurs="1">
<xsd:element ref="vt:blob"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
blockDefault="#all" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:simpleType name="ST_VectorBaseType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="variant"/>
<xsd:enumeration value="i1"/>
<xsd:enumeration value="i2"/>
<xsd:enumeration value="i4"/>
<xsd:enumeration value="i8"/>
<xsd:enumeration value="ui1"/>
<xsd:enumeration value="ui2"/>
<xsd:enumeration value="ui4"/>
<xsd:enumeration value="ui8"/>
<xsd:enumeration value="r4"/>
<xsd:enumeration value="r8"/>
<xsd:enumeration value="lpstr"/>
<xsd:enumeration value="lpwstr"/>
<xsd:enumeration value="bstr"/>
<xsd:enumeration value="date"/>
<xsd:enumeration value="filetime"/>
<xsd:enumeration value="bool"/>
<xsd:enumeration value="cy"/>
<xsd:enumeration value="error"/>
<xsd:enumeration value="clsid"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ArrayBaseType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="variant"/>
<xsd:enumeration value="i1"/>
<xsd:enumeration value="i2"/>
<xsd:enumeration value="i4"/>
<xsd:enumeration value="int"/>
<xsd:enumeration value="ui1"/>
<xsd:enumeration value="ui2"/>
<xsd:enumeration value="ui4"/>
<xsd:enumeration value="uint"/>
<xsd:enumeration value="r4"/>
<xsd:enumeration value="r8"/>
<xsd:enumeration value="decimal"/>
<xsd:enumeration value="bstr"/>
<xsd:enumeration value="date"/>
<xsd:enumeration value="bool"/>
<xsd:enumeration value="cy"/>
<xsd:enumeration value="error"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Cy">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\s*[0-9]*\.[0-9]{4}\s*"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Error">
<xsd:restriction base="xsd:string">
<xsd:pattern value="\s*0x[0-9A-Za-z]{8}\s*"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Empty"/>
<xsd:complexType name="CT_Null"/>
<xsd:complexType name="CT_Vector">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="variant"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="i8"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="ui8"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="lpstr"/>
<xsd:element ref="lpwstr"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="filetime"/>
<xsd:element ref="bool"/>
<xsd:element ref="cy"/>
<xsd:element ref="error"/>
<xsd:element ref="clsid"/>
</xsd:choice>
<xsd:attribute name="baseType" type="ST_VectorBaseType" use="required"/>
<xsd:attribute name="size" type="xsd:unsignedInt" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Array">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="variant"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="int"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="uint"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="decimal"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="bool"/>
<xsd:element ref="error"/>
<xsd:element ref="cy"/>
</xsd:choice>
<xsd:attribute name="lBounds" type="xsd:int" use="required"/>
<xsd:attribute name="uBounds" type="xsd:int" use="required"/>
<xsd:attribute name="baseType" type="ST_ArrayBaseType" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Variant">
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element ref="variant"/>
<xsd:element ref="vector"/>
<xsd:element ref="array"/>
<xsd:element ref="blob"/>
<xsd:element ref="oblob"/>
<xsd:element ref="empty"/>
<xsd:element ref="null"/>
<xsd:element ref="i1"/>
<xsd:element ref="i2"/>
<xsd:element ref="i4"/>
<xsd:element ref="i8"/>
<xsd:element ref="int"/>
<xsd:element ref="ui1"/>
<xsd:element ref="ui2"/>
<xsd:element ref="ui4"/>
<xsd:element ref="ui8"/>
<xsd:element ref="uint"/>
<xsd:element ref="r4"/>
<xsd:element ref="r8"/>
<xsd:element ref="decimal"/>
<xsd:element ref="lpstr"/>
<xsd:element ref="lpwstr"/>
<xsd:element ref="bstr"/>
<xsd:element ref="date"/>
<xsd:element ref="filetime"/>
<xsd:element ref="bool"/>
<xsd:element ref="cy"/>
<xsd:element ref="error"/>
<xsd:element ref="stream"/>
<xsd:element ref="ostream"/>
<xsd:element ref="storage"/>
<xsd:element ref="ostorage"/>
<xsd:element ref="vstream"/>
<xsd:element ref="clsid"/>
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="CT_Vstream">
<xsd:simpleContent>
<xsd:extension base="xsd:base64Binary">
<xsd:attribute name="version" type="s:ST_Guid"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:element name="variant" type="CT_Variant"/>
<xsd:element name="vector" type="CT_Vector"/>
<xsd:element name="array" type="CT_Array"/>
<xsd:element name="blob" type="xsd:base64Binary"/>
<xsd:element name="oblob" type="xsd:base64Binary"/>
<xsd:element name="empty" type="CT_Empty"/>
<xsd:element name="null" type="CT_Null"/>
<xsd:element name="i1" type="xsd:byte"/>
<xsd:element name="i2" type="xsd:short"/>
<xsd:element name="i4" type="xsd:int"/>
<xsd:element name="i8" type="xsd:long"/>
<xsd:element name="int" type="xsd:int"/>
<xsd:element name="ui1" type="xsd:unsignedByte"/>
<xsd:element name="ui2" type="xsd:unsignedShort"/>
<xsd:element name="ui4" type="xsd:unsignedInt"/>
<xsd:element name="ui8" type="xsd:unsignedLong"/>
<xsd:element name="uint" type="xsd:unsignedInt"/>
<xsd:element name="r4" type="xsd:float"/>
<xsd:element name="r8" type="xsd:double"/>
<xsd:element name="decimal" type="xsd:decimal"/>
<xsd:element name="lpstr" type="xsd:string"/>
<xsd:element name="lpwstr" type="xsd:string"/>
<xsd:element name="bstr" type="xsd:string"/>
<xsd:element name="date" type="xsd:dateTime"/>
<xsd:element name="filetime" type="xsd:dateTime"/>
<xsd:element name="bool" type="xsd:boolean"/>
<xsd:element name="cy" type="ST_Cy"/>
<xsd:element name="error" type="ST_Error"/>
<xsd:element name="stream" type="xsd:base64Binary"/>
<xsd:element name="ostream" type="xsd:base64Binary"/>
<xsd:element name="storage" type="xsd:base64Binary"/>
<xsd:element name="ostorage" type="xsd:base64Binary"/>
<xsd:element name="vstream" type="CT_Vstream"/>
<xsd:element name="clsid" type="s:ST_Guid"/>
</xsd:schema>
@@ -0,0 +1,582 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/math">
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
schemaLocation="wml.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
<xsd:simpleType name="ST_Integer255">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="1"/>
<xsd:maxInclusive value="255"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Integer255">
<xsd:attribute name="val" type="ST_Integer255" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Integer2">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="-2"/>
<xsd:maxInclusive value="2"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Integer2">
<xsd:attribute name="val" type="ST_Integer2" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_SpacingRule">
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0"/>
<xsd:maxInclusive value="4"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_SpacingRule">
<xsd:attribute name="val" type="ST_SpacingRule" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_UnSignedInteger">
<xsd:restriction base="xsd:unsignedInt"/>
</xsd:simpleType>
<xsd:complexType name="CT_UnSignedInteger">
<xsd:attribute name="val" type="ST_UnSignedInteger" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Char">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="1"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Char">
<xsd:attribute name="val" type="ST_Char" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_OnOff">
<xsd:attribute name="val" type="s:ST_OnOff"/>
</xsd:complexType>
<xsd:complexType name="CT_String">
<xsd:attribute name="val" type="s:ST_String"/>
</xsd:complexType>
<xsd:complexType name="CT_XAlign">
<xsd:attribute name="val" type="s:ST_XAlign" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_YAlign">
<xsd:attribute name="val" type="s:ST_YAlign" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Shp">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="centered"/>
<xsd:enumeration value="match"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Shp">
<xsd:attribute name="val" type="ST_Shp" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_FType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="bar"/>
<xsd:enumeration value="skw"/>
<xsd:enumeration value="lin"/>
<xsd:enumeration value="noBar"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_FType">
<xsd:attribute name="val" type="ST_FType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_LimLoc">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="undOvr"/>
<xsd:enumeration value="subSup"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_LimLoc">
<xsd:attribute name="val" type="ST_LimLoc" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_TopBot">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="bot"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_TopBot">
<xsd:attribute name="val" type="ST_TopBot" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_Script">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="roman"/>
<xsd:enumeration value="script"/>
<xsd:enumeration value="fraktur"/>
<xsd:enumeration value="double-struck"/>
<xsd:enumeration value="sans-serif"/>
<xsd:enumeration value="monospace"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Script">
<xsd:attribute name="val" type="ST_Script"/>
</xsd:complexType>
<xsd:simpleType name="ST_Style">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="p"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="i"/>
<xsd:enumeration value="bi"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Style">
<xsd:attribute name="val" type="ST_Style"/>
</xsd:complexType>
<xsd:complexType name="CT_ManualBreak">
<xsd:attribute name="alnAt" type="ST_Integer255"/>
</xsd:complexType>
<xsd:group name="EG_ScriptStyle">
<xsd:sequence>
<xsd:element name="scr" minOccurs="0" type="CT_Script"/>
<xsd:element name="sty" minOccurs="0" type="CT_Style"/>
</xsd:sequence>
</xsd:group>
<xsd:complexType name="CT_RPR">
<xsd:sequence>
<xsd:element name="lit" minOccurs="0" type="CT_OnOff"/>
<xsd:choice>
<xsd:element name="nor" minOccurs="0" type="CT_OnOff"/>
<xsd:sequence>
<xsd:group ref="EG_ScriptStyle"/>
</xsd:sequence>
</xsd:choice>
<xsd:element name="brk" minOccurs="0" type="CT_ManualBreak"/>
<xsd:element name="aln" minOccurs="0" type="CT_OnOff"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Text">
<xsd:simpleContent>
<xsd:extension base="s:ST_String">
<xsd:attribute ref="xml:space" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="CT_R">
<xsd:sequence>
<xsd:element name="rPr" type="CT_RPR" minOccurs="0"/>
<xsd:group ref="w:EG_RPr" minOccurs="0"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="w:EG_RunInnerContent"/>
<xsd:element name="t" type="CT_Text" minOccurs="0"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CtrlPr">
<xsd:sequence>
<xsd:group ref="w:EG_RPrMath" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_AccPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Acc">
<xsd:sequence>
<xsd:element name="accPr" type="CT_AccPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BarPr">
<xsd:sequence>
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Bar">
<xsd:sequence>
<xsd:element name="barPr" type="CT_BarPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BoxPr">
<xsd:sequence>
<xsd:element name="opEmu" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="noBreak" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="diff" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="brk" type="CT_ManualBreak" minOccurs="0"/>
<xsd:element name="aln" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Box">
<xsd:sequence>
<xsd:element name="boxPr" type="CT_BoxPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BorderBoxPr">
<xsd:sequence>
<xsd:element name="hideTop" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideBot" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideLeft" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="hideRight" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeH" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeV" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeBLTR" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="strikeTLBR" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_BorderBox">
<xsd:sequence>
<xsd:element name="borderBoxPr" type="CT_BorderBoxPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_DPr">
<xsd:sequence>
<xsd:element name="begChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="sepChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="endChr" type="CT_Char" minOccurs="0"/>
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="shp" type="CT_Shp" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_D">
<xsd:sequence>
<xsd:element name="dPr" type="CT_DPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_EqArrPr">
<xsd:sequence>
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
<xsd:element name="maxDist" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="objDist" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_EqArr">
<xsd:sequence>
<xsd:element name="eqArrPr" type="CT_EqArrPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_FPr">
<xsd:sequence>
<xsd:element name="type" type="CT_FType" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_F">
<xsd:sequence>
<xsd:element name="fPr" type="CT_FPr" minOccurs="0"/>
<xsd:element name="num" type="CT_OMathArg"/>
<xsd:element name="den" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_FuncPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Func">
<xsd:sequence>
<xsd:element name="funcPr" type="CT_FuncPr" minOccurs="0"/>
<xsd:element name="fName" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupChrPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="vertJc" type="CT_TopBot" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GroupChr">
<xsd:sequence>
<xsd:element name="groupChrPr" type="CT_GroupChrPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimLowPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimLow">
<xsd:sequence>
<xsd:element name="limLowPr" type="CT_LimLowPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="lim" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimUppPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LimUpp">
<xsd:sequence>
<xsd:element name="limUppPr" type="CT_LimUppPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="lim" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MCPr">
<xsd:sequence>
<xsd:element name="count" type="CT_Integer255" minOccurs="0"/>
<xsd:element name="mcJc" type="CT_XAlign" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MC">
<xsd:sequence>
<xsd:element name="mcPr" type="CT_MCPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MCS">
<xsd:sequence>
<xsd:element name="mc" type="CT_MC" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MPr">
<xsd:sequence>
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
<xsd:element name="plcHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="cGpRule" type="CT_SpacingRule" minOccurs="0"/>
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="cSp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="cGp" type="CT_UnSignedInteger" minOccurs="0"/>
<xsd:element name="mcs" type="CT_MCS" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_MR">
<xsd:sequence>
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_M">
<xsd:sequence>
<xsd:element name="mPr" type="CT_MPr" minOccurs="0"/>
<xsd:element name="mr" type="CT_MR" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_NaryPr">
<xsd:sequence>
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
<xsd:element name="limLoc" type="CT_LimLoc" minOccurs="0"/>
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="subHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="supHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Nary">
<xsd:sequence>
<xsd:element name="naryPr" type="CT_NaryPr" minOccurs="0"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PhantPr">
<xsd:sequence>
<xsd:element name="show" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroWid" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroAsc" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="zeroDesc" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="transp" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Phant">
<xsd:sequence>
<xsd:element name="phantPr" type="CT_PhantPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RadPr">
<xsd:sequence>
<xsd:element name="degHide" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Rad">
<xsd:sequence>
<xsd:element name="radPr" type="CT_RadPr" minOccurs="0"/>
<xsd:element name="deg" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SPrePr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SPre">
<xsd:sequence>
<xsd:element name="sPrePr" type="CT_SPrePr" minOccurs="0"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
<xsd:element name="e" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSub">
<xsd:sequence>
<xsd:element name="sSubPr" type="CT_SSubPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sub" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubSupPr">
<xsd:sequence>
<xsd:element name="alnScr" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSubSup">
<xsd:sequence>
<xsd:element name="sSubSupPr" type="CT_SSubSupPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sub" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSupPr">
<xsd:sequence>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_SSup">
<xsd:sequence>
<xsd:element name="sSupPr" type="CT_SSupPr" minOccurs="0"/>
<xsd:element name="e" type="CT_OMathArg"/>
<xsd:element name="sup" type="CT_OMathArg"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_OMathMathElements">
<xsd:choice>
<xsd:element name="acc" type="CT_Acc"/>
<xsd:element name="bar" type="CT_Bar"/>
<xsd:element name="box" type="CT_Box"/>
<xsd:element name="borderBox" type="CT_BorderBox"/>
<xsd:element name="d" type="CT_D"/>
<xsd:element name="eqArr" type="CT_EqArr"/>
<xsd:element name="f" type="CT_F"/>
<xsd:element name="func" type="CT_Func"/>
<xsd:element name="groupChr" type="CT_GroupChr"/>
<xsd:element name="limLow" type="CT_LimLow"/>
<xsd:element name="limUpp" type="CT_LimUpp"/>
<xsd:element name="m" type="CT_M"/>
<xsd:element name="nary" type="CT_Nary"/>
<xsd:element name="phant" type="CT_Phant"/>
<xsd:element name="rad" type="CT_Rad"/>
<xsd:element name="sPre" type="CT_SPre"/>
<xsd:element name="sSub" type="CT_SSub"/>
<xsd:element name="sSubSup" type="CT_SSubSup"/>
<xsd:element name="sSup" type="CT_SSup"/>
<xsd:element name="r" type="CT_R"/>
</xsd:choice>
</xsd:group>
<xsd:group name="EG_OMathElements">
<xsd:choice>
<xsd:group ref="EG_OMathMathElements"/>
<xsd:group ref="w:EG_PContentMath"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_OMathArgPr">
<xsd:sequence>
<xsd:element name="argSz" type="CT_Integer2" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_OMathArg">
<xsd:sequence>
<xsd:element name="argPr" type="CT_OMathArgPr" minOccurs="0"/>
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_Jc">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="centerGroup"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_OMathJc">
<xsd:attribute name="val" type="ST_Jc"/>
</xsd:complexType>
<xsd:complexType name="CT_OMathParaPr">
<xsd:sequence>
<xsd:element name="jc" type="CT_OMathJc" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_TwipsMeasure">
<xsd:attribute name="val" type="s:ST_TwipsMeasure" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_BreakBin">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="before"/>
<xsd:enumeration value="after"/>
<xsd:enumeration value="repeat"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_BreakBin">
<xsd:attribute name="val" type="ST_BreakBin"/>
</xsd:complexType>
<xsd:simpleType name="ST_BreakBinSub">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="--"/>
<xsd:enumeration value="-+"/>
<xsd:enumeration value="+-"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_BreakBinSub">
<xsd:attribute name="val" type="ST_BreakBinSub"/>
</xsd:complexType>
<xsd:complexType name="CT_MathPr">
<xsd:sequence>
<xsd:element name="mathFont" type="CT_String" minOccurs="0"/>
<xsd:element name="brkBin" type="CT_BreakBin" minOccurs="0"/>
<xsd:element name="brkBinSub" type="CT_BreakBinSub" minOccurs="0"/>
<xsd:element name="smallFrac" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="dispDef" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="lMargin" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="rMargin" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="defJc" type="CT_OMathJc" minOccurs="0"/>
<xsd:element name="preSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="postSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="interSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:element name="intraSp" type="CT_TwipsMeasure" minOccurs="0"/>
<xsd:choice minOccurs="0">
<xsd:element name="wrapIndent" type="CT_TwipsMeasure"/>
<xsd:element name="wrapRight" type="CT_OnOff"/>
</xsd:choice>
<xsd:element name="intLim" type="CT_LimLoc" minOccurs="0"/>
<xsd:element name="naryLim" type="CT_LimLoc" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="mathPr" type="CT_MathPr"/>
<xsd:complexType name="CT_OMathPara">
<xsd:sequence>
<xsd:element name="oMathParaPr" type="CT_OMathParaPr" minOccurs="0"/>
<xsd:element name="oMath" type="CT_OMath" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_OMath">
<xsd:sequence>
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="oMathPara" type="CT_OMathPara"/>
<xsd:element name="oMath" type="CT_OMath"/>
</xsd:schema>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
blockDefault="#all">
<xsd:simpleType name="ST_RelationshipId">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:attribute name="id" type="ST_RelationshipId"/>
<xsd:attribute name="embed" type="ST_RelationshipId"/>
<xsd:attribute name="link" type="ST_RelationshipId"/>
<xsd:attribute name="dm" type="ST_RelationshipId" default=""/>
<xsd:attribute name="lo" type="ST_RelationshipId" default=""/>
<xsd:attribute name="qs" type="ST_RelationshipId" default=""/>
<xsd:attribute name="cs" type="ST_RelationshipId" default=""/>
<xsd:attribute name="blip" type="ST_RelationshipId" default=""/>
<xsd:attribute name="pict" type="ST_RelationshipId"/>
<xsd:attribute name="href" type="ST_RelationshipId"/>
<xsd:attribute name="topLeft" type="ST_RelationshipId"/>
<xsd:attribute name="topRight" type="ST_RelationshipId"/>
<xsd:attribute name="bottomLeft" type="ST_RelationshipId"/>
<xsd:attribute name="bottomRight" type="ST_RelationshipId"/>
</xsd:schema>
@@ -0,0 +1,570 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:vml"
xmlns:pvml="urn:schemas-microsoft-com:office:powerpoint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:vml" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="urn:schemas-microsoft-com:office:office"
schemaLocation="vml-officeDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
schemaLocation="wml.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:word"
schemaLocation="vml-wordprocessingDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:excel"
schemaLocation="vml-spreadsheetDrawing.xsd"/>
<xsd:import namespace="urn:schemas-microsoft-com:office:powerpoint"
schemaLocation="vml-presentationDrawing.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:attributeGroup name="AG_Id">
<xsd:attribute name="id" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Style">
<xsd:attribute name="style" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Type">
<xsd:attribute name="type" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Adj">
<xsd:attribute name="adj" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Path">
<xsd:attribute name="path" type="xsd:string" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Fill">
<xsd:attribute name="filled" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Chromakey">
<xsd:attribute name="chromakey" type="s:ST_ColorType" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_Ext">
<xsd:attribute name="ext" form="qualified" type="ST_Ext"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_CoreAttributes">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="href" type="xsd:string" use="optional"/>
<xsd:attribute name="target" type="xsd:string" use="optional"/>
<xsd:attribute name="class" type="xsd:string" use="optional"/>
<xsd:attribute name="title" type="xsd:string" use="optional"/>
<xsd:attribute name="alt" type="xsd:string" use="optional"/>
<xsd:attribute name="coordsize" type="xsd:string" use="optional"/>
<xsd:attribute name="coordorigin" type="xsd:string" use="optional"/>
<xsd:attribute name="wrapcoords" type="xsd:string" use="optional"/>
<xsd:attribute name="print" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_ShapeAttributes">
<xsd:attributeGroup ref="AG_Chromakey"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="stroked" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="strokeweight" type="xsd:string" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_OfficeCoreAttributes">
<xsd:attribute ref="o:spid"/>
<xsd:attribute ref="o:oned"/>
<xsd:attribute ref="o:regroupid"/>
<xsd:attribute ref="o:doubleclicknotify"/>
<xsd:attribute ref="o:button"/>
<xsd:attribute ref="o:userhidden"/>
<xsd:attribute ref="o:bullet"/>
<xsd:attribute ref="o:hr"/>
<xsd:attribute ref="o:hrstd"/>
<xsd:attribute ref="o:hrnoshade"/>
<xsd:attribute ref="o:hrpct"/>
<xsd:attribute ref="o:hralign"/>
<xsd:attribute ref="o:allowincell"/>
<xsd:attribute ref="o:allowoverlap"/>
<xsd:attribute ref="o:userdrawn"/>
<xsd:attribute ref="o:bordertopcolor"/>
<xsd:attribute ref="o:borderleftcolor"/>
<xsd:attribute ref="o:borderbottomcolor"/>
<xsd:attribute ref="o:borderrightcolor"/>
<xsd:attribute ref="o:dgmlayout"/>
<xsd:attribute ref="o:dgmnodekind"/>
<xsd:attribute ref="o:dgmlayoutmru"/>
<xsd:attribute ref="o:insetmode"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_OfficeShapeAttributes">
<xsd:attribute ref="o:spt"/>
<xsd:attribute ref="o:connectortype"/>
<xsd:attribute ref="o:bwmode"/>
<xsd:attribute ref="o:bwpure"/>
<xsd:attribute ref="o:bwnormal"/>
<xsd:attribute ref="o:forcedash"/>
<xsd:attribute ref="o:oleicon"/>
<xsd:attribute ref="o:ole"/>
<xsd:attribute ref="o:preferrelative"/>
<xsd:attribute ref="o:cliptowrap"/>
<xsd:attribute ref="o:clip"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_AllCoreAttributes">
<xsd:attributeGroup ref="AG_CoreAttributes"/>
<xsd:attributeGroup ref="AG_OfficeCoreAttributes"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_AllShapeAttributes">
<xsd:attributeGroup ref="AG_ShapeAttributes"/>
<xsd:attributeGroup ref="AG_OfficeShapeAttributes"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_ImageAttributes">
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="cropleft" type="xsd:string" use="optional"/>
<xsd:attribute name="croptop" type="xsd:string" use="optional"/>
<xsd:attribute name="cropright" type="xsd:string" use="optional"/>
<xsd:attribute name="cropbottom" type="xsd:string" use="optional"/>
<xsd:attribute name="gain" type="xsd:string" use="optional"/>
<xsd:attribute name="blacklevel" type="xsd:string" use="optional"/>
<xsd:attribute name="gamma" type="xsd:string" use="optional"/>
<xsd:attribute name="grayscale" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="bilevel" type="s:ST_TrueFalse" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="AG_StrokeAttributes">
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="linestyle" type="ST_StrokeLineStyle" use="optional"/>
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
<xsd:attribute name="joinstyle" type="ST_StrokeJoinStyle" use="optional"/>
<xsd:attribute name="endcap" type="ST_StrokeEndCap" use="optional"/>
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
<xsd:attribute name="filltype" type="ST_FillType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="imageaspect" type="ST_ImageAspect" use="optional"/>
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="startarrow" type="ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="startarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="startarrowlength" type="ST_StrokeArrowLength" use="optional"/>
<xsd:attribute name="endarrow" type="ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="endarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="endarrowlength" type="ST_StrokeArrowLength" use="optional"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:forcedash"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="o:relid"/>
</xsd:attributeGroup>
<xsd:group name="EG_ShapeElements">
<xsd:choice>
<xsd:element ref="path"/>
<xsd:element ref="formulas"/>
<xsd:element ref="handles"/>
<xsd:element ref="fill"/>
<xsd:element ref="stroke"/>
<xsd:element ref="shadow"/>
<xsd:element ref="textbox"/>
<xsd:element ref="textpath"/>
<xsd:element ref="imagedata"/>
<xsd:element ref="o:skew"/>
<xsd:element ref="o:extrusion"/>
<xsd:element ref="o:callout"/>
<xsd:element ref="o:lock"/>
<xsd:element ref="o:clippath"/>
<xsd:element ref="o:signatureline"/>
<xsd:element ref="w10:wrap"/>
<xsd:element ref="w10:anchorlock"/>
<xsd:element ref="w10:bordertop"/>
<xsd:element ref="w10:borderbottom"/>
<xsd:element ref="w10:borderleft"/>
<xsd:element ref="w10:borderright"/>
<xsd:element ref="x:ClientData" minOccurs="0"/>
<xsd:element ref="pvml:textdata" minOccurs="0"/>
</xsd:choice>
</xsd:group>
<xsd:element name="shape" type="CT_Shape"/>
<xsd:element name="shapetype" type="CT_Shapetype"/>
<xsd:element name="group" type="CT_Group"/>
<xsd:element name="background" type="CT_Background"/>
<xsd:complexType name="CT_Shape">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="o:ink"/>
<xsd:element ref="pvml:iscomment"/>
<xsd:element ref="o:equationxml"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_Type"/>
<xsd:attributeGroup ref="AG_Adj"/>
<xsd:attributeGroup ref="AG_Path"/>
<xsd:attribute ref="o:gfxdata"/>
<xsd:attribute name="equationxml" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Shapetype">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="o:complex" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_Adj"/>
<xsd:attributeGroup ref="AG_Path"/>
<xsd:attribute ref="o:master"/>
</xsd:complexType>
<xsd:complexType name="CT_Group">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="group"/>
<xsd:element ref="shape"/>
<xsd:element ref="shapetype"/>
<xsd:element ref="arc"/>
<xsd:element ref="curve"/>
<xsd:element ref="image"/>
<xsd:element ref="line"/>
<xsd:element ref="oval"/>
<xsd:element ref="polyline"/>
<xsd:element ref="rect"/>
<xsd:element ref="roundrect"/>
<xsd:element ref="o:diagram"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute name="editas" type="ST_EditAs" use="optional"/>
<xsd:attribute ref="o:tableproperties"/>
<xsd:attribute ref="o:tablelimits"/>
</xsd:complexType>
<xsd:complexType name="CT_Background">
<xsd:sequence>
<xsd:element ref="fill" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Fill"/>
<xsd:attribute ref="o:bwmode"/>
<xsd:attribute ref="o:bwpure"/>
<xsd:attribute ref="o:bwnormal"/>
<xsd:attribute ref="o:targetscreensize"/>
</xsd:complexType>
<xsd:element name="fill" type="CT_Fill"/>
<xsd:element name="formulas" type="CT_Formulas"/>
<xsd:element name="handles" type="CT_Handles"/>
<xsd:element name="imagedata" type="CT_ImageData"/>
<xsd:element name="path" type="CT_Path"/>
<xsd:element name="textbox" type="CT_Textbox"/>
<xsd:element name="shadow" type="CT_Shadow"/>
<xsd:element name="stroke" type="CT_Stroke"/>
<xsd:element name="textpath" type="CT_TextPath"/>
<xsd:complexType name="CT_Fill">
<xsd:sequence>
<xsd:element ref="o:fill" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="type" type="ST_FillType" use="optional"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute name="size" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="position" type="xsd:string" use="optional"/>
<xsd:attribute name="aspect" type="ST_ImageAspect" use="optional"/>
<xsd:attribute name="colors" type="xsd:string" use="optional"/>
<xsd:attribute name="angle" type="xsd:decimal" use="optional"/>
<xsd:attribute name="alignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="focus" type="xsd:string" use="optional"/>
<xsd:attribute name="focussize" type="xsd:string" use="optional"/>
<xsd:attribute name="focusposition" type="xsd:string" use="optional"/>
<xsd:attribute name="method" type="ST_FillMethod" use="optional"/>
<xsd:attribute ref="o:detectmouseclick"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:opacity2"/>
<xsd:attribute name="recolor" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotate" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute ref="o:relid" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Formulas">
<xsd:sequence>
<xsd:element name="f" type="CT_F" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_F">
<xsd:attribute name="eqn" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_Handles">
<xsd:sequence>
<xsd:element name="h" type="CT_H" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_H">
<xsd:attribute name="position" type="xsd:string"/>
<xsd:attribute name="polar" type="xsd:string"/>
<xsd:attribute name="map" type="xsd:string"/>
<xsd:attribute name="invx" type="s:ST_TrueFalse"/>
<xsd:attribute name="invy" type="s:ST_TrueFalse"/>
<xsd:attribute name="switch" type="s:ST_TrueFalseBlank"/>
<xsd:attribute name="xrange" type="xsd:string"/>
<xsd:attribute name="yrange" type="xsd:string"/>
<xsd:attribute name="radiusrange" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ImageData">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_ImageAttributes"/>
<xsd:attributeGroup ref="AG_Chromakey"/>
<xsd:attribute name="embosscolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="recolortarget" type="s:ST_ColorType"/>
<xsd:attribute ref="o:href"/>
<xsd:attribute ref="o:althref"/>
<xsd:attribute ref="o:title"/>
<xsd:attribute ref="o:oleid"/>
<xsd:attribute ref="o:detectmouseclick"/>
<xsd:attribute ref="o:movie"/>
<xsd:attribute ref="o:relid"/>
<xsd:attribute ref="r:id"/>
<xsd:attribute ref="r:pict"/>
<xsd:attribute ref="r:href"/>
</xsd:complexType>
<xsd:complexType name="CT_Path">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="v" type="xsd:string" use="optional"/>
<xsd:attribute name="limo" type="xsd:string" use="optional"/>
<xsd:attribute name="textboxrect" type="xsd:string" use="optional"/>
<xsd:attribute name="fillok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokeok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="shadowok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="arrowok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="gradientshapeok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="textpathok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="insetpenok" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute ref="o:connecttype"/>
<xsd:attribute ref="o:connectlocs"/>
<xsd:attribute ref="o:connectangles"/>
<xsd:attribute ref="o:extrusionok"/>
</xsd:complexType>
<xsd:complexType name="CT_Shadow">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="ST_ShadowType" use="optional"/>
<xsd:attribute name="obscured" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="offset2" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Stroke">
<xsd:sequence>
<xsd:element ref="o:left" minOccurs="0"/>
<xsd:element ref="o:top" minOccurs="0"/>
<xsd:element ref="o:right" minOccurs="0"/>
<xsd:element ref="o:bottom" minOccurs="0"/>
<xsd:element ref="o:column" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_StrokeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_Textbox">
<xsd:choice>
<xsd:element ref="w:txbxContent" minOccurs="0"/>
<xsd:any namespace="##local" processContents="skip"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="inset" type="xsd:string" use="optional"/>
<xsd:attribute ref="o:singleclick"/>
<xsd:attribute ref="o:insetmode"/>
</xsd:complexType>
<xsd:complexType name="CT_TextPath">
<xsd:attributeGroup ref="AG_Id"/>
<xsd:attributeGroup ref="AG_Style"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fitshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fitpath" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="trim" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="xscale" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="string" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:element name="arc" type="CT_Arc"/>
<xsd:element name="curve" type="CT_Curve"/>
<xsd:element name="image" type="CT_Image"/>
<xsd:element name="line" type="CT_Line"/>
<xsd:element name="oval" type="CT_Oval"/>
<xsd:element name="polyline" type="CT_PolyLine"/>
<xsd:element name="rect" type="CT_Rect"/>
<xsd:element name="roundrect" type="CT_RoundRect"/>
<xsd:complexType name="CT_Arc">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="startAngle" type="xsd:decimal" use="optional"/>
<xsd:attribute name="endAngle" type="xsd:decimal" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Curve">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="from" type="xsd:string" use="optional"/>
<xsd:attribute name="control1" type="xsd:string" use="optional"/>
<xsd:attribute name="control2" type="xsd:string" use="optional"/>
<xsd:attribute name="to" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Image">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attributeGroup ref="AG_ImageAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_Line">
<xsd:sequence>
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="from" type="xsd:string" use="optional"/>
<xsd:attribute name="to" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Oval">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_PolyLine">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements"/>
<xsd:element ref="o:ink"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="points" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Rect">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
</xsd:complexType>
<xsd:complexType name="CT_RoundRect">
<xsd:choice maxOccurs="unbounded">
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
<xsd:attribute name="arcsize" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_Ext">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="view"/>
<xsd:enumeration value="edit"/>
<xsd:enumeration value="backwardCompatible"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="gradient"/>
<xsd:enumeration value="gradientRadial"/>
<xsd:enumeration value="tile"/>
<xsd:enumeration value="pattern"/>
<xsd:enumeration value="frame"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillMethod">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="linear"/>
<xsd:enumeration value="sigma"/>
<xsd:enumeration value="any"/>
<xsd:enumeration value="linear sigma"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ShadowType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="single"/>
<xsd:enumeration value="double"/>
<xsd:enumeration value="emboss"/>
<xsd:enumeration value="perspective"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeLineStyle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="single"/>
<xsd:enumeration value="thinThin"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thickBetweenThin"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeJoinStyle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="round"/>
<xsd:enumeration value="bevel"/>
<xsd:enumeration value="miter"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeEndCap">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="flat"/>
<xsd:enumeration value="square"/>
<xsd:enumeration value="round"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowLength">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="short"/>
<xsd:enumeration value="medium"/>
<xsd:enumeration value="long"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowWidth">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="narrow"/>
<xsd:enumeration value="medium"/>
<xsd:enumeration value="wide"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_StrokeArrowType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="block"/>
<xsd:enumeration value="classic"/>
<xsd:enumeration value="oval"/>
<xsd:enumeration value="diamond"/>
<xsd:enumeration value="open"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ImageAspect">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ignore"/>
<xsd:enumeration value="atMost"/>
<xsd:enumeration value="atLeast"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_EditAs">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="canvas"/>
<xsd:enumeration value="orgchart"/>
<xsd:enumeration value="radial"/>
<xsd:enumeration value="cycle"/>
<xsd:enumeration value="stacked"/>
<xsd:enumeration value="venn"/>
<xsd:enumeration value="bullseye"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,509 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:office:office" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
schemaLocation="shared-relationshipReference.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:attribute name="bwmode" type="ST_BWMode"/>
<xsd:attribute name="bwpure" type="ST_BWMode"/>
<xsd:attribute name="bwnormal" type="ST_BWMode"/>
<xsd:attribute name="targetscreensize" type="ST_ScreenSize"/>
<xsd:attribute name="insetmode" type="ST_InsetMode" default="custom"/>
<xsd:attribute name="spt" type="xsd:float"/>
<xsd:attribute name="wrapcoords" type="xsd:string"/>
<xsd:attribute name="oned" type="s:ST_TrueFalse"/>
<xsd:attribute name="regroupid" type="xsd:integer"/>
<xsd:attribute name="doubleclicknotify" type="s:ST_TrueFalse"/>
<xsd:attribute name="connectortype" type="ST_ConnectorType" default="straight"/>
<xsd:attribute name="button" type="s:ST_TrueFalse"/>
<xsd:attribute name="userhidden" type="s:ST_TrueFalse"/>
<xsd:attribute name="forcedash" type="s:ST_TrueFalse"/>
<xsd:attribute name="oleicon" type="s:ST_TrueFalse"/>
<xsd:attribute name="ole" type="s:ST_TrueFalseBlank"/>
<xsd:attribute name="preferrelative" type="s:ST_TrueFalse"/>
<xsd:attribute name="cliptowrap" type="s:ST_TrueFalse"/>
<xsd:attribute name="clip" type="s:ST_TrueFalse"/>
<xsd:attribute name="bullet" type="s:ST_TrueFalse"/>
<xsd:attribute name="hr" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrstd" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrnoshade" type="s:ST_TrueFalse"/>
<xsd:attribute name="hrpct" type="xsd:float"/>
<xsd:attribute name="hralign" type="ST_HrAlign" default="left"/>
<xsd:attribute name="allowincell" type="s:ST_TrueFalse"/>
<xsd:attribute name="allowoverlap" type="s:ST_TrueFalse"/>
<xsd:attribute name="userdrawn" type="s:ST_TrueFalse"/>
<xsd:attribute name="bordertopcolor" type="xsd:string"/>
<xsd:attribute name="borderleftcolor" type="xsd:string"/>
<xsd:attribute name="borderbottomcolor" type="xsd:string"/>
<xsd:attribute name="borderrightcolor" type="xsd:string"/>
<xsd:attribute name="connecttype" type="ST_ConnectType"/>
<xsd:attribute name="connectlocs" type="xsd:string"/>
<xsd:attribute name="connectangles" type="xsd:string"/>
<xsd:attribute name="master" type="xsd:string"/>
<xsd:attribute name="extrusionok" type="s:ST_TrueFalse"/>
<xsd:attribute name="href" type="xsd:string"/>
<xsd:attribute name="althref" type="xsd:string"/>
<xsd:attribute name="title" type="xsd:string"/>
<xsd:attribute name="singleclick" type="s:ST_TrueFalse"/>
<xsd:attribute name="oleid" type="xsd:float"/>
<xsd:attribute name="detectmouseclick" type="s:ST_TrueFalse"/>
<xsd:attribute name="movie" type="xsd:float"/>
<xsd:attribute name="spid" type="xsd:string"/>
<xsd:attribute name="opacity2" type="xsd:string"/>
<xsd:attribute name="relid" type="r:ST_RelationshipId"/>
<xsd:attribute name="dgmlayout" type="ST_DiagramLayout"/>
<xsd:attribute name="dgmnodekind" type="xsd:integer"/>
<xsd:attribute name="dgmlayoutmru" type="ST_DiagramLayout"/>
<xsd:attribute name="gfxdata" type="xsd:base64Binary"/>
<xsd:attribute name="tableproperties" type="xsd:string"/>
<xsd:attribute name="tablelimits" type="xsd:string"/>
<xsd:element name="shapedefaults" type="CT_ShapeDefaults"/>
<xsd:element name="shapelayout" type="CT_ShapeLayout"/>
<xsd:element name="signatureline" type="CT_SignatureLine"/>
<xsd:element name="ink" type="CT_Ink"/>
<xsd:element name="diagram" type="CT_Diagram"/>
<xsd:element name="equationxml" type="CT_EquationXml"/>
<xsd:complexType name="CT_ShapeDefaults">
<xsd:all minOccurs="0">
<xsd:element ref="v:fill" minOccurs="0"/>
<xsd:element ref="v:stroke" minOccurs="0"/>
<xsd:element ref="v:textbox" minOccurs="0"/>
<xsd:element ref="v:shadow" minOccurs="0"/>
<xsd:element ref="skew" minOccurs="0"/>
<xsd:element ref="extrusion" minOccurs="0"/>
<xsd:element ref="callout" minOccurs="0"/>
<xsd:element ref="lock" minOccurs="0"/>
<xsd:element name="colormru" minOccurs="0" type="CT_ColorMru"/>
<xsd:element name="colormenu" minOccurs="0" type="CT_ColorMenu"/>
</xsd:all>
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="spidmax" type="xsd:integer" use="optional"/>
<xsd:attribute name="style" type="xsd:string" use="optional"/>
<xsd:attribute name="fill" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="stroke" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
<xsd:attribute name="allowincell" form="qualified" type="s:ST_TrueFalse"/>
</xsd:complexType>
<xsd:complexType name="CT_Ink">
<xsd:sequence/>
<xsd:attribute name="i" type="xsd:string"/>
<xsd:attribute name="annotation" type="s:ST_TrueFalse"/>
<xsd:attribute name="contentType" type="ST_ContentType" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_SignatureLine">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="issignatureline" type="s:ST_TrueFalse"/>
<xsd:attribute name="id" type="s:ST_Guid"/>
<xsd:attribute name="provid" type="s:ST_Guid"/>
<xsd:attribute name="signinginstructionsset" type="s:ST_TrueFalse"/>
<xsd:attribute name="allowcomments" type="s:ST_TrueFalse"/>
<xsd:attribute name="showsigndate" type="s:ST_TrueFalse"/>
<xsd:attribute name="suggestedsigner" type="xsd:string" form="qualified"/>
<xsd:attribute name="suggestedsigner2" type="xsd:string" form="qualified"/>
<xsd:attribute name="suggestedsigneremail" type="xsd:string" form="qualified"/>
<xsd:attribute name="signinginstructions" type="xsd:string"/>
<xsd:attribute name="addlxml" type="xsd:string"/>
<xsd:attribute name="sigprovurl" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ShapeLayout">
<xsd:all>
<xsd:element name="idmap" type="CT_IdMap" minOccurs="0"/>
<xsd:element name="regrouptable" type="CT_RegroupTable" minOccurs="0"/>
<xsd:element name="rules" type="CT_Rules" minOccurs="0"/>
</xsd:all>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_IdMap">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="data" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_RegroupTable">
<xsd:sequence>
<xsd:element name="entry" type="CT_Entry" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_Entry">
<xsd:attribute name="new" type="xsd:int" use="optional"/>
<xsd:attribute name="old" type="xsd:int" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Rules">
<xsd:sequence>
<xsd:element name="r" type="CT_R" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_R">
<xsd:sequence>
<xsd:element name="proxy" type="CT_Proxy" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="type" type="ST_RType" use="optional"/>
<xsd:attribute name="how" type="ST_How" use="optional"/>
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Proxy">
<xsd:attribute name="start" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
<xsd:attribute name="end" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
<xsd:attribute name="connectloc" type="xsd:int" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Diagram">
<xsd:sequence>
<xsd:element name="relationtable" type="CT_RelationTable" minOccurs="0"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="dgmstyle" type="xsd:integer" use="optional"/>
<xsd:attribute name="autoformat" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="reverse" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="autolayout" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="dgmscalex" type="xsd:integer" use="optional"/>
<xsd:attribute name="dgmscaley" type="xsd:integer" use="optional"/>
<xsd:attribute name="dgmfontsize" type="xsd:integer" use="optional"/>
<xsd:attribute name="constrainbounds" type="xsd:string" use="optional"/>
<xsd:attribute name="dgmbasetextscale" type="xsd:integer" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_EquationXml">
<xsd:sequence>
<xsd:any namespace="##any"/>
</xsd:sequence>
<xsd:attribute name="contentType" type="ST_AlternateMathContentType" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_AlternateMathContentType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:complexType name="CT_RelationTable">
<xsd:sequence>
<xsd:element name="rel" type="CT_Relation" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_Relation">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="idsrc" type="xsd:string" use="optional"/>
<xsd:attribute name="iddest" type="xsd:string" use="optional"/>
<xsd:attribute name="idcntr" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_ColorMru">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="colors" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="CT_ColorMenu">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
<xsd:attribute name="fillcolor" type="s:ST_ColorType"/>
<xsd:attribute name="shadowcolor" type="s:ST_ColorType"/>
<xsd:attribute name="extrusioncolor" type="s:ST_ColorType"/>
</xsd:complexType>
<xsd:element name="skew" type="CT_Skew"/>
<xsd:element name="extrusion" type="CT_Extrusion"/>
<xsd:element name="callout" type="CT_Callout"/>
<xsd:element name="lock" type="CT_Lock"/>
<xsd:element name="OLEObject" type="CT_OLEObject"/>
<xsd:element name="complex" type="CT_Complex"/>
<xsd:element name="left" type="CT_StrokeChild"/>
<xsd:element name="top" type="CT_StrokeChild"/>
<xsd:element name="right" type="CT_StrokeChild"/>
<xsd:element name="bottom" type="CT_StrokeChild"/>
<xsd:element name="column" type="CT_StrokeChild"/>
<xsd:element name="clippath" type="CT_ClipPath"/>
<xsd:element name="fill" type="CT_Fill"/>
<xsd:complexType name="CT_Skew">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Extrusion">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="ST_ExtrusionType" default="parallel" use="optional"/>
<xsd:attribute name="render" type="ST_ExtrusionRender" default="solid" use="optional"/>
<xsd:attribute name="viewpointorigin" type="xsd:string" use="optional"/>
<xsd:attribute name="viewpoint" type="xsd:string" use="optional"/>
<xsd:attribute name="plane" type="ST_ExtrusionPlane" default="XY" use="optional"/>
<xsd:attribute name="skewangle" type="xsd:float" use="optional"/>
<xsd:attribute name="skewamt" type="xsd:string" use="optional"/>
<xsd:attribute name="foredepth" type="xsd:string" use="optional"/>
<xsd:attribute name="backdepth" type="xsd:string" use="optional"/>
<xsd:attribute name="orientation" type="xsd:string" use="optional"/>
<xsd:attribute name="orientationangle" type="xsd:float" use="optional"/>
<xsd:attribute name="lockrotationcenter" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="autorotationcenter" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotationcenter" type="xsd:string" use="optional"/>
<xsd:attribute name="rotationangle" type="xsd:string" use="optional"/>
<xsd:attribute name="colormode" type="ST_ColorMode" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="shininess" type="xsd:float" use="optional"/>
<xsd:attribute name="specularity" type="xsd:string" use="optional"/>
<xsd:attribute name="diffusity" type="xsd:string" use="optional"/>
<xsd:attribute name="metal" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="edge" type="xsd:string" use="optional"/>
<xsd:attribute name="facet" type="xsd:string" use="optional"/>
<xsd:attribute name="lightface" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="brightness" type="xsd:string" use="optional"/>
<xsd:attribute name="lightposition" type="xsd:string" use="optional"/>
<xsd:attribute name="lightlevel" type="xsd:string" use="optional"/>
<xsd:attribute name="lightharsh" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="lightposition2" type="xsd:string" use="optional"/>
<xsd:attribute name="lightlevel2" type="xsd:string" use="optional"/>
<xsd:attribute name="lightharsh2" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Callout">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="type" type="xsd:string" use="optional"/>
<xsd:attribute name="gap" type="xsd:string" use="optional"/>
<xsd:attribute name="angle" type="ST_Angle" use="optional"/>
<xsd:attribute name="dropauto" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="drop" type="ST_CalloutDrop" use="optional"/>
<xsd:attribute name="distance" type="xsd:string" use="optional"/>
<xsd:attribute name="lengthspecified" type="s:ST_TrueFalse" default="f" use="optional"/>
<xsd:attribute name="length" type="xsd:string" use="optional"/>
<xsd:attribute name="accentbar" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="textborder" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="minusx" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="minusy" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Lock">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="position" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="selection" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="grouping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="ungrouping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="rotation" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="cropping" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="verticies" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="adjusthandles" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="text" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="aspectratio" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="shapetype" type="s:ST_TrueFalse" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_OLEObject">
<xsd:sequence>
<xsd:element name="LinkType" type="ST_OLELinkType" minOccurs="0"/>
<xsd:element name="LockedField" type="s:ST_TrueFalseBlank" minOccurs="0"/>
<xsd:element name="FieldCodes" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Type" type="ST_OLEType" use="optional"/>
<xsd:attribute name="ProgID" type="xsd:string" use="optional"/>
<xsd:attribute name="ShapeID" type="xsd:string" use="optional"/>
<xsd:attribute name="DrawAspect" type="ST_OLEDrawAspect" use="optional"/>
<xsd:attribute name="ObjectID" type="xsd:string" use="optional"/>
<xsd:attribute ref="r:id" use="optional"/>
<xsd:attribute name="UpdateMode" type="ST_OLEUpdateMode" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_Complex">
<xsd:attributeGroup ref="v:AG_Ext"/>
</xsd:complexType>
<xsd:complexType name="CT_StrokeChild">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
<xsd:attribute name="linestyle" type="v:ST_StrokeLineStyle" use="optional"/>
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
<xsd:attribute name="joinstyle" type="v:ST_StrokeJoinStyle" use="optional"/>
<xsd:attribute name="endcap" type="v:ST_StrokeEndCap" use="optional"/>
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="filltype" type="v:ST_FillType" use="optional"/>
<xsd:attribute name="src" type="xsd:string" use="optional"/>
<xsd:attribute name="imageaspect" type="v:ST_ImageAspect" use="optional"/>
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
<xsd:attribute name="startarrow" type="v:ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="startarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="startarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
<xsd:attribute name="endarrow" type="v:ST_StrokeArrowType" use="optional"/>
<xsd:attribute name="endarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
<xsd:attribute name="endarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
<xsd:attribute ref="href"/>
<xsd:attribute ref="althref"/>
<xsd:attribute ref="title"/>
<xsd:attribute ref="forcedash"/>
</xsd:complexType>
<xsd:complexType name="CT_ClipPath">
<xsd:attribute name="v" type="xsd:string" use="required" form="qualified"/>
</xsd:complexType>
<xsd:complexType name="CT_Fill">
<xsd:attributeGroup ref="v:AG_Ext"/>
<xsd:attribute name="type" type="ST_FillType"/>
</xsd:complexType>
<xsd:simpleType name="ST_RType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="arc"/>
<xsd:enumeration value="callout"/>
<xsd:enumeration value="connector"/>
<xsd:enumeration value="align"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_How">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="middle"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="right"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_BWMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="color"/>
<xsd:enumeration value="auto"/>
<xsd:enumeration value="grayScale"/>
<xsd:enumeration value="lightGrayscale"/>
<xsd:enumeration value="inverseGray"/>
<xsd:enumeration value="grayOutline"/>
<xsd:enumeration value="highContrast"/>
<xsd:enumeration value="black"/>
<xsd:enumeration value="white"/>
<xsd:enumeration value="hide"/>
<xsd:enumeration value="undrawn"/>
<xsd:enumeration value="blackTextAndLines"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ScreenSize">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="544,376"/>
<xsd:enumeration value="640,480"/>
<xsd:enumeration value="720,512"/>
<xsd:enumeration value="800,600"/>
<xsd:enumeration value="1024,768"/>
<xsd:enumeration value="1152,862"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_InsetMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ColorMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ContentType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_DiagramLayout">
<xsd:restriction base="xsd:integer">
<xsd:enumeration value="0"/>
<xsd:enumeration value="1"/>
<xsd:enumeration value="2"/>
<xsd:enumeration value="3"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="perspective"/>
<xsd:enumeration value="parallel"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionRender">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="wireFrame"/>
<xsd:enumeration value="boundingCube"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ExtrusionPlane">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="XY"/>
<xsd:enumeration value="ZX"/>
<xsd:enumeration value="YZ"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Angle">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="any"/>
<xsd:enumeration value="30"/>
<xsd:enumeration value="45"/>
<xsd:enumeration value="60"/>
<xsd:enumeration value="90"/>
<xsd:enumeration value="auto"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CalloutDrop">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_CalloutPlacement">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="top"/>
<xsd:enumeration value="center"/>
<xsd:enumeration value="bottom"/>
<xsd:enumeration value="user"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConnectorType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="straight"/>
<xsd:enumeration value="elbow"/>
<xsd:enumeration value="curved"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_HrAlign">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="center"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_ConnectType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="rect"/>
<xsd:enumeration value="segments"/>
<xsd:enumeration value="custom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLELinkType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Embed"/>
<xsd:enumeration value="Link"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEDrawAspect">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Content"/>
<xsd:enumeration value="Icon"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_OLEUpdateMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Always"/>
<xsd:enumeration value="OnCall"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_FillType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="gradientCenter"/>
<xsd:enumeration value="solid"/>
<xsd:enumeration value="pattern"/>
<xsd:enumeration value="tile"/>
<xsd:enumeration value="frame"/>
<xsd:enumeration value="gradientUnscaled"/>
<xsd:enumeration value="gradientRadial"/>
<xsd:enumeration value="gradient"/>
<xsd:enumeration value="background"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:powerpoint"
targetNamespace="urn:schemas-microsoft-com:office:powerpoint" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="iscomment" type="CT_Empty"/>
<xsd:element name="textdata" type="CT_Rel"/>
<xsd:complexType name="CT_Empty"/>
<xsd:complexType name="CT_Rel">
<xsd:attribute name="id" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:excel"
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
targetNamespace="urn:schemas-microsoft-com:office:excel" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
schemaLocation="shared-commonSimpleTypes.xsd"/>
<xsd:element name="ClientData" type="CT_ClientData"/>
<xsd:complexType name="CT_ClientData">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="MoveWithCells" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SizeWithCells" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Anchor" type="xsd:string"/>
<xsd:element name="Locked" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DefaultSize" type="s:ST_TrueFalseBlank"/>
<xsd:element name="PrintObject" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Disabled" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoFill" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoLine" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoPict" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaMacro" type="xsd:string"/>
<xsd:element name="TextHAlign" type="xsd:string"/>
<xsd:element name="TextVAlign" type="xsd:string"/>
<xsd:element name="LockText" type="s:ST_TrueFalseBlank"/>
<xsd:element name="JustLastX" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SecretEdit" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Default" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Help" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Cancel" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Dismiss" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Accel" type="xsd:integer"/>
<xsd:element name="Accel2" type="xsd:integer"/>
<xsd:element name="Row" type="xsd:integer"/>
<xsd:element name="Column" type="xsd:integer"/>
<xsd:element name="Visible" type="s:ST_TrueFalseBlank"/>
<xsd:element name="RowHidden" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ColHidden" type="s:ST_TrueFalseBlank"/>
<xsd:element name="VTEdit" type="xsd:integer"/>
<xsd:element name="MultiLine" type="s:ST_TrueFalseBlank"/>
<xsd:element name="VScroll" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ValidIds" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaRange" type="xsd:string"/>
<xsd:element name="WidthMin" type="xsd:integer"/>
<xsd:element name="Sel" type="xsd:integer"/>
<xsd:element name="NoThreeD2" type="s:ST_TrueFalseBlank"/>
<xsd:element name="SelType" type="xsd:string"/>
<xsd:element name="MultiSel" type="xsd:string"/>
<xsd:element name="LCT" type="xsd:string"/>
<xsd:element name="ListItem" type="xsd:string"/>
<xsd:element name="DropStyle" type="xsd:string"/>
<xsd:element name="Colored" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DropLines" type="xsd:integer"/>
<xsd:element name="Checked" type="xsd:integer"/>
<xsd:element name="FmlaLink" type="xsd:string"/>
<xsd:element name="FmlaPict" type="xsd:string"/>
<xsd:element name="NoThreeD" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FirstButton" type="s:ST_TrueFalseBlank"/>
<xsd:element name="FmlaGroup" type="xsd:string"/>
<xsd:element name="Val" type="xsd:integer"/>
<xsd:element name="Min" type="xsd:integer"/>
<xsd:element name="Max" type="xsd:integer"/>
<xsd:element name="Inc" type="xsd:integer"/>
<xsd:element name="Page" type="xsd:integer"/>
<xsd:element name="Horiz" type="s:ST_TrueFalseBlank"/>
<xsd:element name="Dx" type="xsd:integer"/>
<xsd:element name="MapOCX" type="s:ST_TrueFalseBlank"/>
<xsd:element name="CF" type="ST_CF"/>
<xsd:element name="Camera" type="s:ST_TrueFalseBlank"/>
<xsd:element name="RecalcAlways" type="s:ST_TrueFalseBlank"/>
<xsd:element name="AutoScale" type="s:ST_TrueFalseBlank"/>
<xsd:element name="DDE" type="s:ST_TrueFalseBlank"/>
<xsd:element name="UIObj" type="s:ST_TrueFalseBlank"/>
<xsd:element name="ScriptText" type="xsd:string"/>
<xsd:element name="ScriptExtended" type="xsd:string"/>
<xsd:element name="ScriptLanguage" type="xsd:nonNegativeInteger"/>
<xsd:element name="ScriptLocation" type="xsd:nonNegativeInteger"/>
<xsd:element name="FmlaTxbx" type="xsd:string"/>
</xsd:choice>
<xsd:attribute name="ObjectType" type="ST_ObjectType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_CF">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="ST_ObjectType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Button"/>
<xsd:enumeration value="Checkbox"/>
<xsd:enumeration value="Dialog"/>
<xsd:enumeration value="Drop"/>
<xsd:enumeration value="Edit"/>
<xsd:enumeration value="GBox"/>
<xsd:enumeration value="Label"/>
<xsd:enumeration value="LineA"/>
<xsd:enumeration value="List"/>
<xsd:enumeration value="Movie"/>
<xsd:enumeration value="Note"/>
<xsd:enumeration value="Pict"/>
<xsd:enumeration value="Radio"/>
<xsd:enumeration value="RectA"/>
<xsd:enumeration value="Scroll"/>
<xsd:enumeration value="Spin"/>
<xsd:enumeration value="Shape"/>
<xsd:enumeration value="Group"/>
<xsd:enumeration value="Rect"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-microsoft-com:office:word"
targetNamespace="urn:schemas-microsoft-com:office:word" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="bordertop" type="CT_Border"/>
<xsd:element name="borderleft" type="CT_Border"/>
<xsd:element name="borderright" type="CT_Border"/>
<xsd:element name="borderbottom" type="CT_Border"/>
<xsd:complexType name="CT_Border">
<xsd:attribute name="type" type="ST_BorderType" use="optional"/>
<xsd:attribute name="width" type="xsd:positiveInteger" use="optional"/>
<xsd:attribute name="shadow" type="ST_BorderShadow" use="optional"/>
</xsd:complexType>
<xsd:element name="wrap" type="CT_Wrap"/>
<xsd:complexType name="CT_Wrap">
<xsd:attribute name="type" type="ST_WrapType" use="optional"/>
<xsd:attribute name="side" type="ST_WrapSide" use="optional"/>
<xsd:attribute name="anchorx" type="ST_HorizontalAnchor" use="optional"/>
<xsd:attribute name="anchory" type="ST_VerticalAnchor" use="optional"/>
</xsd:complexType>
<xsd:element name="anchorlock" type="CT_AnchorLock"/>
<xsd:complexType name="CT_AnchorLock"/>
<xsd:simpleType name="ST_BorderType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="single"/>
<xsd:enumeration value="thick"/>
<xsd:enumeration value="double"/>
<xsd:enumeration value="hairline"/>
<xsd:enumeration value="dot"/>
<xsd:enumeration value="dash"/>
<xsd:enumeration value="dotDash"/>
<xsd:enumeration value="dashDotDot"/>
<xsd:enumeration value="triple"/>
<xsd:enumeration value="thinThickSmall"/>
<xsd:enumeration value="thickThinSmall"/>
<xsd:enumeration value="thickBetweenThinSmall"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thickBetweenThin"/>
<xsd:enumeration value="thinThickLarge"/>
<xsd:enumeration value="thickThinLarge"/>
<xsd:enumeration value="thickBetweenThinLarge"/>
<xsd:enumeration value="wave"/>
<xsd:enumeration value="doubleWave"/>
<xsd:enumeration value="dashedSmall"/>
<xsd:enumeration value="dashDotStroked"/>
<xsd:enumeration value="threeDEmboss"/>
<xsd:enumeration value="threeDEngrave"/>
<xsd:enumeration value="HTMLOutset"/>
<xsd:enumeration value="HTMLInset"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_BorderShadow">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="t"/>
<xsd:enumeration value="true"/>
<xsd:enumeration value="f"/>
<xsd:enumeration value="false"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_WrapType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="topAndBottom"/>
<xsd:enumeration value="square"/>
<xsd:enumeration value="none"/>
<xsd:enumeration value="tight"/>
<xsd:enumeration value="through"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_WrapSide">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="both"/>
<xsd:enumeration value="left"/>
<xsd:enumeration value="right"/>
<xsd:enumeration value="largest"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_HorizontalAnchor">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="text"/>
<xsd:enumeration value="char"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_VerticalAnchor">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="margin"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="text"/>
<xsd:enumeration value="line"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,116 @@
<?xml version='1.0'?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
See http://www.w3.org/XML/1998/namespace.html and
http://www.w3.org/TR/REC-xml for information about this namespace.
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
Note that local names in this namespace are intended to be defined
only by the World Wide Web Consortium or its subgroups. The
following names are currently defined in this namespace and should
not be used with conflicting semantics by any Working Group,
specification, or document instance:
base (as an attribute name): denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.
lang (as an attribute name): denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.
space (as an attribute name): denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.
Father (in any context at all): denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
In appreciation for his vision, leadership and dedication
the W3C XML Plenary on this 10th day of February, 2000
reserves for Jon Bosak in perpetuity the XML name
xml:Father
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>This schema defines attributes and an attribute group
suitable for use by
schemas wishing to allow xml:base, xml:lang or xml:space attributes
on elements they define.
To enable this, such a schema must import this schema
for the XML namespace, e.g. as follows:
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
Subsequently, qualified reference to any of the attributes
or the group defined below will have the desired effect, e.g.
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
will define a type which will schema-validate an instance
element with any of those attributes</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
http://www.w3.org/2001/03/xml.xsd.
At the date of issue it can also be found at
http://www.w3.org/2001/xml.xsd.
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML Schema
itself. In other words, if the XML Schema namespace changes, the version
of this document at
http://www.w3.org/2001/xml.xsd will change
accordingly; the version at
http://www.w3.org/2001/03/xml.xsd will not change.
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang" type="xs:language">
<xs:annotation>
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
codes as the enumerated possible values . . .</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="space" default="preserve">
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
</xs:attributeGroup>
</xs:schema>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns="http://schemas.openxmlformats.org/package/2006/content-types"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/content-types"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xs:element name="Types" type="CT_Types"/>
<xs:element name="Default" type="CT_Default"/>
<xs:element name="Override" type="CT_Override"/>
<xs:complexType name="CT_Types">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="Default"/>
<xs:element ref="Override"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="CT_Default">
<xs:attribute name="Extension" type="ST_Extension" use="required"/>
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
</xs:complexType>
<xs:complexType name="CT_Override">
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
<xs:attribute name="PartName" type="xs:anyURI" use="required"/>
</xs:complexType>
<xs:simpleType name="ST_ContentType">
<xs:restriction base="xs:string">
<xs:pattern
value="(((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))/((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))((\s+)*;(\s+)*(((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+))=((([\p{IsBasicLatin}-[\p{Cc}&#127;\(\)&lt;&gt;@,;:\\&quot;/\[\]\?=\{\}\s\t]])+)|(&quot;(([\p{IsLatin-1Supplement}\p{IsBasicLatin}-[\p{Cc}&#127;&quot;\n\r]]|(\s+))|(\\[\p{IsBasicLatin}]))*&quot;))))*)"
/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_Extension">
<xs:restriction base="xs:string">
<xs:pattern
value="([!$&amp;'\(\)\*\+,:=]|(%[0-9a-fA-F][0-9a-fA-F])|[:@]|[a-zA-Z0-9\-_~])+"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" blockDefault="#all">
<xs:import namespace="http://purl.org/dc/elements/1.1/"
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
<xs:import namespace="http://purl.org/dc/terms/"
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
<xs:import id="xml" namespace="http://www.w3.org/XML/1998/namespace"/>
<xs:element name="coreProperties" type="CT_CoreProperties"/>
<xs:complexType name="CT_CoreProperties">
<xs:all>
<xs:element name="category" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element name="contentStatus" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element ref="dcterms:created" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:creator" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:description" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:identifier" minOccurs="0" maxOccurs="1"/>
<xs:element name="keywords" minOccurs="0" maxOccurs="1" type="CT_Keywords"/>
<xs:element ref="dc:language" minOccurs="0" maxOccurs="1"/>
<xs:element name="lastModifiedBy" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element name="lastPrinted" minOccurs="0" maxOccurs="1" type="xs:dateTime"/>
<xs:element ref="dcterms:modified" minOccurs="0" maxOccurs="1"/>
<xs:element name="revision" minOccurs="0" maxOccurs="1" type="xs:string"/>
<xs:element ref="dc:subject" minOccurs="0" maxOccurs="1"/>
<xs:element ref="dc:title" minOccurs="0" maxOccurs="1"/>
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:string"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Keywords" mixed="true">
<xs:sequence>
<xs:element name="value" minOccurs="0" maxOccurs="unbounded" type="CT_Keyword"/>
</xs:sequence>
<xs:attribute ref="xml:lang" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Keyword">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute ref="xml:lang" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/digital-signature"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/digital-signature"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xsd:element name="SignatureTime" type="CT_SignatureTime"/>
<xsd:element name="RelationshipReference" type="CT_RelationshipReference"/>
<xsd:element name="RelationshipsGroupReference" type="CT_RelationshipsGroupReference"/>
<xsd:complexType name="CT_SignatureTime">
<xsd:sequence>
<xsd:element name="Format" type="ST_Format"/>
<xsd:element name="Value" type="ST_Value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_RelationshipReference">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="SourceId" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="CT_RelationshipsGroupReference">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="SourceType" type="xsd:anyURI" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="ST_Format">
<xsd:restriction base="xsd:string">
<xsd:pattern
value="(YYYY)|(YYYY-MM)|(YYYY-MM-DD)|(YYYY-MM-DDThh:mmTZD)|(YYYY-MM-DDThh:mm:ssTZD)|(YYYY-MM-DDThh:mm:ss.sTZD)"
/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_Value">
<xsd:restriction base="xsd:string">
<xsd:pattern
value="(([0-9][0-9][0-9][0-9]))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):(((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))\.[0-9])(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))"
/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/relationships"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/package/2006/relationships"
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
<xsd:element name="Relationships" type="CT_Relationships"/>
<xsd:element name="Relationship" type="CT_Relationship"/>
<xsd:complexType name="CT_Relationships">
<xsd:sequence>
<xsd:element ref="Relationship" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Relationship">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="TargetMode" type="ST_TargetMode" use="optional"/>
<xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
<xsd:attribute name="Type" type="xsd:anyURI" use="required"/>
<xsd:attribute name="Id" type="xsd:ID" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="ST_TargetMode">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="External"/>
<xsd:enumeration value="Internal"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!--
This XSD is a modified version of the one found at:
https://github.com/plutext/docx4j/blob/master/xsd/mce/markup-compatibility-2006-MINIMAL.xsd
This XSD has 2 objectives:
1. round tripping @mc:Ignorable
<w:document
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
mc:Ignorable="w14 w15 wp14">
2. enabling AlternateContent to be manipulated in certain elements
(in the unusual case where the content model is xsd:any, it doesn't have to be explicitly added)
See further ECMA-376, 4th Edition, Office Open XML File Formats
Part 3 : Markup Compatibility and Extensibility
-->
<!-- Objective 1 -->
<xsd:attribute name="Ignorable" type="xsd:string" />
<!-- Objective 2 -->
<xsd:attribute name="MustUnderstand" type="xsd:string" />
<xsd:attribute name="ProcessContent" type="xsd:string" />
<!-- An AlternateContent element shall contain one or more Choice child elements, optionally followed by a
Fallback child element. If present, there shall be only one Fallback element, and it shall follow all Choice
elements. -->
<xsd:element name="AlternateContent">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Choice" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded"
processContents="strict">
</xsd:any>
</xsd:sequence>
<xsd:attribute name="Requires" type="xsd:string" use="required" />
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
<xsd:element name="Fallback" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded"
processContents="strict">
</xsd:any>
</xsd:sequence>
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<!-- AlternateContent elements might include the attributes Ignorable,
MustUnderstand and ProcessContent described in this Part of ECMA-376. These
attributes’ qualified names shall be prefixed when associated with an AlternateContent
element. -->
<xsd:attribute ref="mc:Ignorable" use="optional" />
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
<xsd:attribute ref="mc:ProcessContent" use="optional" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,560 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns="http://schemas.microsoft.com/office/word/2010/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2010/wordml">
<!-- <xsd:import id="rel" namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships" schemaLocation="orel.xsd"/> -->
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<!-- <xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartbasetypes.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartsplineproperties.xsd"/> -->
<xsd:complexType name="CT_LongHexNumber">
<xsd:attribute name="val" type="w:ST_LongHexNumber" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_OnOff">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true"/>
<xsd:enumeration value="false"/>
<xsd:enumeration value="0"/>
<xsd:enumeration value="1"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_OnOff">
<xsd:attribute name="val" type="ST_OnOff"/>
</xsd:complexType>
<xsd:element name="docId" type="CT_LongHexNumber"/>
<xsd:element name="conflictMode" type="CT_OnOff"/>
<xsd:attributeGroup name="AG_Parids">
<xsd:attribute name="paraId" type="w:ST_LongHexNumber"/>
<xsd:attribute name="textId" type="w:ST_LongHexNumber"/>
</xsd:attributeGroup>
<xsd:attribute name="anchorId" type="w:ST_LongHexNumber"/>
<xsd:attribute name="noSpellErr" type="ST_OnOff"/>
<xsd:element name="customXmlConflictInsRangeStart" type="w:CT_TrackChange"/>
<xsd:element name="customXmlConflictInsRangeEnd" type="w:CT_Markup"/>
<xsd:element name="customXmlConflictDelRangeStart" type="w:CT_TrackChange"/>
<xsd:element name="customXmlConflictDelRangeEnd" type="w:CT_Markup"/>
<xsd:group name="EG_RunLevelConflicts">
<xsd:sequence>
<xsd:element name="conflictIns" type="w:CT_RunTrackChange" minOccurs="0"/>
<xsd:element name="conflictDel" type="w:CT_RunTrackChange" minOccurs="0"/>
</xsd:sequence>
</xsd:group>
<xsd:group name="EG_Conflicts">
<xsd:choice>
<xsd:element name="conflictIns" type="w:CT_TrackChange" minOccurs="0"/>
<xsd:element name="conflictDel" type="w:CT_TrackChange" minOccurs="0"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Percentage">
<xsd:attribute name="val" type="a:ST_Percentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_PositiveFixedPercentage">
<xsd:attribute name="val" type="a:ST_PositiveFixedPercentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_PositivePercentage">
<xsd:attribute name="val" type="a:ST_PositivePercentage" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_SchemeColorVal">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="bg1"/>
<xsd:enumeration value="tx1"/>
<xsd:enumeration value="bg2"/>
<xsd:enumeration value="tx2"/>
<xsd:enumeration value="accent1"/>
<xsd:enumeration value="accent2"/>
<xsd:enumeration value="accent3"/>
<xsd:enumeration value="accent4"/>
<xsd:enumeration value="accent5"/>
<xsd:enumeration value="accent6"/>
<xsd:enumeration value="hlink"/>
<xsd:enumeration value="folHlink"/>
<xsd:enumeration value="dk1"/>
<xsd:enumeration value="lt1"/>
<xsd:enumeration value="dk2"/>
<xsd:enumeration value="lt2"/>
<xsd:enumeration value="phClr"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_RectAlignment">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="tl"/>
<xsd:enumeration value="t"/>
<xsd:enumeration value="tr"/>
<xsd:enumeration value="l"/>
<xsd:enumeration value="ctr"/>
<xsd:enumeration value="r"/>
<xsd:enumeration value="bl"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="br"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PathShadeType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="shape"/>
<xsd:enumeration value="circle"/>
<xsd:enumeration value="rect"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_LineCap">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="rnd"/>
<xsd:enumeration value="sq"/>
<xsd:enumeration value="flat"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PresetLineDashVal">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="solid"/>
<xsd:enumeration value="dot"/>
<xsd:enumeration value="sysDot"/>
<xsd:enumeration value="dash"/>
<xsd:enumeration value="sysDash"/>
<xsd:enumeration value="lgDash"/>
<xsd:enumeration value="dashDot"/>
<xsd:enumeration value="sysDashDot"/>
<xsd:enumeration value="lgDashDot"/>
<xsd:enumeration value="lgDashDotDot"/>
<xsd:enumeration value="sysDashDotDot"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_PenAlignment">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ctr"/>
<xsd:enumeration value="in"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_CompoundLine">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="sng"/>
<xsd:enumeration value="dbl"/>
<xsd:enumeration value="thickThin"/>
<xsd:enumeration value="thinThick"/>
<xsd:enumeration value="tri"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_RelativeRect">
<xsd:attribute name="l" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="t" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="r" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="b" use="optional" type="a:ST_Percentage"/>
</xsd:complexType>
<xsd:group name="EG_ColorTransform">
<xsd:choice>
<xsd:element name="tint" type="CT_PositiveFixedPercentage"/>
<xsd:element name="shade" type="CT_PositiveFixedPercentage"/>
<xsd:element name="alpha" type="CT_PositiveFixedPercentage"/>
<xsd:element name="hueMod" type="CT_PositivePercentage"/>
<xsd:element name="sat" type="CT_Percentage"/>
<xsd:element name="satOff" type="CT_Percentage"/>
<xsd:element name="satMod" type="CT_Percentage"/>
<xsd:element name="lum" type="CT_Percentage"/>
<xsd:element name="lumOff" type="CT_Percentage"/>
<xsd:element name="lumMod" type="CT_Percentage"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_SRgbColor">
<xsd:sequence>
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_SchemeColor">
<xsd:sequence>
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
</xsd:complexType>
<xsd:group name="EG_ColorChoice">
<xsd:choice>
<xsd:element name="srgbClr" type="CT_SRgbColor"/>
<xsd:element name="schemeClr" type="CT_SchemeColor"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_Color">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GradientStop">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="pos" type="a:ST_PositiveFixedPercentage" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_GradientStopList">
<xsd:sequence>
<xsd:element name="gs" type="CT_GradientStop" minOccurs="2" maxOccurs="10"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_LinearShadeProperties">
<xsd:attribute name="ang" type="a:ST_PositiveFixedAngle" use="optional"/>
<xsd:attribute name="scaled" type="ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_PathShadeProperties">
<xsd:sequence>
<xsd:element name="fillToRect" type="CT_RelativeRect" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="path" type="ST_PathShadeType" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_ShadeProperties">
<xsd:choice>
<xsd:element name="lin" type="CT_LinearShadeProperties"/>
<xsd:element name="path" type="CT_PathShadeProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_SolidColorFillProperties">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_GradientFillProperties">
<xsd:sequence>
<xsd:element name="gsLst" type="CT_GradientStopList" minOccurs="0"/>
<xsd:group ref="EG_ShadeProperties" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_FillProperties">
<xsd:choice>
<xsd:element name="noFill" type="w:CT_Empty"/>
<xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
<xsd:element name="gradFill" type="CT_GradientFillProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_PresetLineDashProperties">
<xsd:attribute name="val" type="ST_PresetLineDashVal" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_LineDashProperties">
<xsd:choice>
<xsd:element name="prstDash" type="CT_PresetLineDashProperties"/>
</xsd:choice>
</xsd:group>
<xsd:complexType name="CT_LineJoinMiterProperties">
<xsd:attribute name="lim" type="a:ST_PositivePercentage" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_LineJoinProperties">
<xsd:choice>
<xsd:element name="round" type="w:CT_Empty"/>
<xsd:element name="bevel" type="w:CT_Empty"/>
<xsd:element name="miter" type="CT_LineJoinMiterProperties"/>
</xsd:choice>
</xsd:group>
<xsd:simpleType name="ST_PresetCameraType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyObliqueTopLeft"/>
<xsd:enumeration value="legacyObliqueTop"/>
<xsd:enumeration value="legacyObliqueTopRight"/>
<xsd:enumeration value="legacyObliqueLeft"/>
<xsd:enumeration value="legacyObliqueFront"/>
<xsd:enumeration value="legacyObliqueRight"/>
<xsd:enumeration value="legacyObliqueBottomLeft"/>
<xsd:enumeration value="legacyObliqueBottom"/>
<xsd:enumeration value="legacyObliqueBottomRight"/>
<xsd:enumeration value="legacyPerspectiveTopLeft"/>
<xsd:enumeration value="legacyPerspectiveTop"/>
<xsd:enumeration value="legacyPerspectiveTopRight"/>
<xsd:enumeration value="legacyPerspectiveLeft"/>
<xsd:enumeration value="legacyPerspectiveFront"/>
<xsd:enumeration value="legacyPerspectiveRight"/>
<xsd:enumeration value="legacyPerspectiveBottomLeft"/>
<xsd:enumeration value="legacyPerspectiveBottom"/>
<xsd:enumeration value="legacyPerspectiveBottomRight"/>
<xsd:enumeration value="orthographicFront"/>
<xsd:enumeration value="isometricTopUp"/>
<xsd:enumeration value="isometricTopDown"/>
<xsd:enumeration value="isometricBottomUp"/>
<xsd:enumeration value="isometricBottomDown"/>
<xsd:enumeration value="isometricLeftUp"/>
<xsd:enumeration value="isometricLeftDown"/>
<xsd:enumeration value="isometricRightUp"/>
<xsd:enumeration value="isometricRightDown"/>
<xsd:enumeration value="isometricOffAxis1Left"/>
<xsd:enumeration value="isometricOffAxis1Right"/>
<xsd:enumeration value="isometricOffAxis1Top"/>
<xsd:enumeration value="isometricOffAxis2Left"/>
<xsd:enumeration value="isometricOffAxis2Right"/>
<xsd:enumeration value="isometricOffAxis2Top"/>
<xsd:enumeration value="isometricOffAxis3Left"/>
<xsd:enumeration value="isometricOffAxis3Right"/>
<xsd:enumeration value="isometricOffAxis3Bottom"/>
<xsd:enumeration value="isometricOffAxis4Left"/>
<xsd:enumeration value="isometricOffAxis4Right"/>
<xsd:enumeration value="isometricOffAxis4Bottom"/>
<xsd:enumeration value="obliqueTopLeft"/>
<xsd:enumeration value="obliqueTop"/>
<xsd:enumeration value="obliqueTopRight"/>
<xsd:enumeration value="obliqueLeft"/>
<xsd:enumeration value="obliqueRight"/>
<xsd:enumeration value="obliqueBottomLeft"/>
<xsd:enumeration value="obliqueBottom"/>
<xsd:enumeration value="obliqueBottomRight"/>
<xsd:enumeration value="perspectiveFront"/>
<xsd:enumeration value="perspectiveLeft"/>
<xsd:enumeration value="perspectiveRight"/>
<xsd:enumeration value="perspectiveAbove"/>
<xsd:enumeration value="perspectiveBelow"/>
<xsd:enumeration value="perspectiveAboveLeftFacing"/>
<xsd:enumeration value="perspectiveAboveRightFacing"/>
<xsd:enumeration value="perspectiveContrastingLeftFacing"/>
<xsd:enumeration value="perspectiveContrastingRightFacing"/>
<xsd:enumeration value="perspectiveHeroicLeftFacing"/>
<xsd:enumeration value="perspectiveHeroicRightFacing"/>
<xsd:enumeration value="perspectiveHeroicExtremeLeftFacing"/>
<xsd:enumeration value="perspectiveHeroicExtremeRightFacing"/>
<xsd:enumeration value="perspectiveRelaxed"/>
<xsd:enumeration value="perspectiveRelaxedModerately"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Camera">
<xsd:attribute name="prst" use="required" type="ST_PresetCameraType"/>
</xsd:complexType>
<xsd:complexType name="CT_SphereCoords">
<xsd:attribute name="lat" type="a:ST_PositiveFixedAngle" use="required"/>
<xsd:attribute name="lon" type="a:ST_PositiveFixedAngle" use="required"/>
<xsd:attribute name="rev" type="a:ST_PositiveFixedAngle" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_LightRigType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyFlat1"/>
<xsd:enumeration value="legacyFlat2"/>
<xsd:enumeration value="legacyFlat3"/>
<xsd:enumeration value="legacyFlat4"/>
<xsd:enumeration value="legacyNormal1"/>
<xsd:enumeration value="legacyNormal2"/>
<xsd:enumeration value="legacyNormal3"/>
<xsd:enumeration value="legacyNormal4"/>
<xsd:enumeration value="legacyHarsh1"/>
<xsd:enumeration value="legacyHarsh2"/>
<xsd:enumeration value="legacyHarsh3"/>
<xsd:enumeration value="legacyHarsh4"/>
<xsd:enumeration value="threePt"/>
<xsd:enumeration value="balanced"/>
<xsd:enumeration value="soft"/>
<xsd:enumeration value="harsh"/>
<xsd:enumeration value="flood"/>
<xsd:enumeration value="contrasting"/>
<xsd:enumeration value="morning"/>
<xsd:enumeration value="sunrise"/>
<xsd:enumeration value="sunset"/>
<xsd:enumeration value="chilly"/>
<xsd:enumeration value="freezing"/>
<xsd:enumeration value="flat"/>
<xsd:enumeration value="twoPt"/>
<xsd:enumeration value="glow"/>
<xsd:enumeration value="brightRoom"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ST_LightRigDirection">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="tl"/>
<xsd:enumeration value="t"/>
<xsd:enumeration value="tr"/>
<xsd:enumeration value="l"/>
<xsd:enumeration value="r"/>
<xsd:enumeration value="bl"/>
<xsd:enumeration value="b"/>
<xsd:enumeration value="br"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_LightRig">
<xsd:sequence>
<xsd:element name="rot" type="CT_SphereCoords" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="rig" type="ST_LightRigType" use="required"/>
<xsd:attribute name="dir" type="ST_LightRigDirection" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_BevelPresetType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="relaxedInset"/>
<xsd:enumeration value="circle"/>
<xsd:enumeration value="slope"/>
<xsd:enumeration value="cross"/>
<xsd:enumeration value="angle"/>
<xsd:enumeration value="softRound"/>
<xsd:enumeration value="convex"/>
<xsd:enumeration value="coolSlant"/>
<xsd:enumeration value="divot"/>
<xsd:enumeration value="riblet"/>
<xsd:enumeration value="hardEdge"/>
<xsd:enumeration value="artDeco"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Bevel">
<xsd:attribute name="w" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="h" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="prst" type="ST_BevelPresetType" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="ST_PresetMaterialType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="legacyMatte"/>
<xsd:enumeration value="legacyPlastic"/>
<xsd:enumeration value="legacyMetal"/>
<xsd:enumeration value="legacyWireframe"/>
<xsd:enumeration value="matte"/>
<xsd:enumeration value="plastic"/>
<xsd:enumeration value="metal"/>
<xsd:enumeration value="warmMatte"/>
<xsd:enumeration value="translucentPowder"/>
<xsd:enumeration value="powder"/>
<xsd:enumeration value="dkEdge"/>
<xsd:enumeration value="softEdge"/>
<xsd:enumeration value="clear"/>
<xsd:enumeration value="flat"/>
<xsd:enumeration value="softmetal"/>
<xsd:enumeration value="none"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Glow">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="rad" use="optional" type="a:ST_PositiveCoordinate"/>
</xsd:complexType>
<xsd:complexType name="CT_Shadow">
<xsd:sequence>
<xsd:group ref="EG_ColorChoice"/>
</xsd:sequence>
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_Reflection">
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="stA" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="stPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="endA" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="endPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="fadeDir" use="optional" type="a:ST_PositiveFixedAngle"/>
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_FillTextEffect">
<xsd:sequence>
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_TextOutlineEffect">
<xsd:sequence>
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
<xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
<xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
<xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
<xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
<xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
</xsd:complexType>
<xsd:complexType name="CT_Scene3D">
<xsd:sequence>
<xsd:element name="camera" type="CT_Camera"/>
<xsd:element name="lightRig" type="CT_LightRig"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_Props3D">
<xsd:sequence>
<xsd:element name="bevelT" type="CT_Bevel" minOccurs="0"/>
<xsd:element name="bevelB" type="CT_Bevel" minOccurs="0"/>
<xsd:element name="extrusionClr" type="CT_Color" minOccurs="0"/>
<xsd:element name="contourClr" type="CT_Color" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="extrusionH" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="contourW" type="a:ST_PositiveCoordinate" use="optional"/>
<xsd:attribute name="prstMaterial" type="ST_PresetMaterialType" use="optional"/>
</xsd:complexType>
<xsd:group name="EG_RPrTextEffects">
<xsd:sequence>
<xsd:element name="glow" minOccurs="0" type="CT_Glow"/>
<xsd:element name="shadow" minOccurs="0" type="CT_Shadow"/>
<xsd:element name="reflection" minOccurs="0" type="CT_Reflection"/>
<xsd:element name="textOutline" minOccurs="0" type="CT_TextOutlineEffect"/>
<xsd:element name="textFill" minOccurs="0" type="CT_FillTextEffect"/>
<xsd:element name="scene3d" minOccurs="0" type="CT_Scene3D"/>
<xsd:element name="props3d" minOccurs="0" type="CT_Props3D"/>
</xsd:sequence>
</xsd:group>
<xsd:simpleType name="ST_Ligatures">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="standard"/>
<xsd:enumeration value="contextual"/>
<xsd:enumeration value="historical"/>
<xsd:enumeration value="discretional"/>
<xsd:enumeration value="standardContextual"/>
<xsd:enumeration value="standardHistorical"/>
<xsd:enumeration value="contextualHistorical"/>
<xsd:enumeration value="standardDiscretional"/>
<xsd:enumeration value="contextualDiscretional"/>
<xsd:enumeration value="historicalDiscretional"/>
<xsd:enumeration value="standardContextualHistorical"/>
<xsd:enumeration value="standardContextualDiscretional"/>
<xsd:enumeration value="standardHistoricalDiscretional"/>
<xsd:enumeration value="contextualHistoricalDiscretional"/>
<xsd:enumeration value="all"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Ligatures">
<xsd:attribute name="val" type="ST_Ligatures" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_NumForm">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default"/>
<xsd:enumeration value="lining"/>
<xsd:enumeration value="oldStyle"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NumForm">
<xsd:attribute name="val" type="ST_NumForm" use="required"/>
</xsd:complexType>
<xsd:simpleType name="ST_NumSpacing">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default"/>
<xsd:enumeration value="proportional"/>
<xsd:enumeration value="tabular"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_NumSpacing">
<xsd:attribute name="val" type="ST_NumSpacing" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_StyleSet">
<xsd:attribute name="id" type="s:ST_UnsignedDecimalNumber" use="required"/>
<xsd:attribute name="val" type="ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:complexType name="CT_StylisticSets">
<xsd:sequence minOccurs="0">
<xsd:element name="styleSet" minOccurs="0" maxOccurs="unbounded" type="CT_StyleSet"/>
</xsd:sequence>
</xsd:complexType>
<xsd:group name="EG_RPrOpenType">
<xsd:sequence>
<xsd:element name="ligatures" minOccurs="0" type="CT_Ligatures"/>
<xsd:element name="numForm" minOccurs="0" type="CT_NumForm"/>
<xsd:element name="numSpacing" minOccurs="0" type="CT_NumSpacing"/>
<xsd:element name="stylisticSets" minOccurs="0" type="CT_StylisticSets"/>
<xsd:element name="cntxtAlts" minOccurs="0" type="CT_OnOff"/>
</xsd:sequence>
</xsd:group>
<xsd:element name="discardImageEditingData" type="CT_OnOff"/>
<xsd:element name="defaultImageDpi" type="CT_DefaultImageDpi"/>
<xsd:complexType name="CT_DefaultImageDpi">
<xsd:attribute name="val" type="w:ST_DecimalNumber" use="required"/>
</xsd:complexType>
<xsd:element name="entityPicker" type="w:CT_Empty"/>
<xsd:complexType name="CT_SdtCheckboxSymbol">
<xsd:attribute name="font" type="s:ST_String"/>
<xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
</xsd:complexType>
<xsd:complexType name="CT_SdtCheckbox">
<xsd:sequence>
<xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
<xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
<xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="checkbox" type="CT_SdtCheckbox"/>
</xsd:schema>
@@ -0,0 +1,67 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2012/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
<xsd:element name="color" type="w12:CT_Color"/>
<xsd:simpleType name="ST_SdtAppearance">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="boundingBox"/>
<xsd:enumeration value="tags"/>
<xsd:enumeration value="hidden"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="dataBinding" type="w12:CT_DataBinding"/>
<xsd:complexType name="CT_SdtAppearance">
<xsd:attribute name="val" type="ST_SdtAppearance"/>
</xsd:complexType>
<xsd:element name="appearance" type="CT_SdtAppearance"/>
<xsd:complexType name="CT_CommentsEx">
<xsd:sequence>
<xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentEx">
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
<xsd:attribute name="paraIdParent" type="w12:ST_LongHexNumber" use="optional"/>
<xsd:attribute name="done" type="s:ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:element name="commentsEx" type="CT_CommentsEx"/>
<xsd:complexType name="CT_People">
<xsd:sequence>
<xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_PresenceInfo">
<xsd:attribute name="providerId" type="xsd:string" use="required"/>
<xsd:attribute name="userId" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="CT_Person">
<xsd:sequence>
<xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="author" type="s:ST_String" use="required"/>
</xsd:complexType>
<xsd:element name="people" type="CT_People"/>
<xsd:complexType name="CT_SdtRepeatedSection">
<xsd:sequence>
<xsd:element name="sectionTitle" type="w12:CT_String" minOccurs="0"/>
<xsd:element name="doNotAllowInsertDeleteSection" type="w12:CT_OnOff" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="ST_Guid">
<xsd:restriction base="xsd:token">
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="CT_Guid">
<xsd:attribute name="val" type="ST_Guid"/>
</xsd:complexType>
<xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
<xsd:element name="repeatingSectionItem" type="w12:CT_Empty"/>
<xsd:element name="chartTrackingRefBased" type="w12:CT_OnOff"/>
<xsd:element name="collapsed" type="w12:CT_OnOff"/>
<xsd:element name="docId" type="CT_Guid"/>
<xsd:element name="footnoteColumns" type="w12:CT_DecimalNumber"/>
<xsd:element name="webExtensionLinked" type="w12:CT_OnOff"/>
<xsd:element name="webExtensionCreated" type="w12:CT_OnOff"/>
<xsd:attribute name="restartNumberingAfterBreak" type="s:ST_OnOff"/>
</xsd:schema>
@@ -0,0 +1,14 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_Extension">
<xsd:sequence>
<xsd:any processContents="lax"/>
</xsd:sequence>
<xsd:attribute name="uri" type="xsd:token"/>
</xsd:complexType>
<xsd:complexType name="CT_ExtensionList">
<xsd:sequence>
<xsd:element name="ext" type="CT_Extension" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,20 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml/cex" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml/cex">
<xsd:import id="w16" namespace="http://schemas.microsoft.com/office/word/2018/wordml" schemaLocation="wml-2018.xsd"/>
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:import id="s" namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
<xsd:complexType name="CT_CommentsExtensible">
<xsd:sequence>
<xsd:element name="commentExtensible" type="CT_CommentExtensible" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentExtensible">
<xsd:sequence>
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="durableId" type="w:ST_LongHexNumber" use="required"/>
<xsd:attribute name="dateUtc" type="w:ST_DateTime" use="optional"/>
<xsd:attribute name="intelligentPlaceholder" type="s:ST_OnOff" use="optional"/>
</xsd:complexType>
<xsd:element name="commentsExtensible" type="CT_CommentsExtensible"/>
</xsd:schema>
@@ -0,0 +1,13 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2016/wordml/cid" targetNamespace="http://schemas.microsoft.com/office/word/2016/wordml/cid">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_CommentsIds">
<xsd:sequence>
<xsd:element name="commentId" type="CT_CommentId" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CT_CommentId">
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
<xsd:attribute name="durableId" type="w12:ST_LongHexNumber" use="required"/>
</xsd:complexType>
<xsd:element name="commentsIds" type="CT_CommentsIds"/>
</xsd:schema>
@@ -0,0 +1,4 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" targetNamespace="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:attribute name="storeItemChecksum" type="w12:ST_String"/>
</xsd:schema>
@@ -0,0 +1,8 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2015/wordml/symex" targetNamespace="http://schemas.microsoft.com/office/word/2015/wordml/symex">
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
<xsd:complexType name="CT_SymEx">
<xsd:attribute name="font" type="w12:ST_String"/>
<xsd:attribute name="char" type="w12:ST_LongHexNumber"/>
</xsd:complexType>
<xsd:element name="symEx" type="CT_SymEx"/>
</xsd:schema>
@@ -0,0 +1,183 @@
"""
Helper for running LibreOffice (soffice) in environments where AF_UNIX
sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
at runtime and applies an LD_PRELOAD shim if needed.
Usage:
from office.soffice import run_soffice, get_soffice_env
# Option 1 – run soffice directly
result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
# Option 2 – get env dict for your own subprocess calls
env = get_soffice_env()
subprocess.run(["soffice", ...], env=env)
"""
import os
import socket
import subprocess
import tempfile
from pathlib import Path
def get_soffice_env() -> dict:
env = os.environ.copy()
env["SAL_USE_VCLPLUGIN"] = "svp"
if _needs_shim():
shim = _ensure_shim()
env["LD_PRELOAD"] = str(shim)
return env
def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
env = get_soffice_env()
return subprocess.run(["soffice"] + args, env=env, **kwargs)
_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
def _needs_shim() -> bool:
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
return True
def _ensure_shim() -> Path:
if _SHIM_SO.exists():
return _SHIM_SO
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
src.write_text(_SHIM_SOURCE)
subprocess.run(
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
check=True,
capture_output=True,
)
src.unlink()
return _SHIM_SO
_SHIM_SOURCE = r"""
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
static int (*real_socket)(int, int, int);
static int (*real_socketpair)(int, int, int, int[2]);
static int (*real_listen)(int, int);
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
static int (*real_close)(int);
static int (*real_read)(int, void *, size_t);
/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
static int is_shimmed[1024];
static int peer_of[1024];
static int wake_r[1024]; /* accept() blocks reading this */
static int wake_w[1024]; /* close() writes to this */
static int listener_fd = -1; /* FD that received listen() */
__attribute__((constructor))
static void init(void) {
real_socket = dlsym(RTLD_NEXT, "socket");
real_socketpair = dlsym(RTLD_NEXT, "socketpair");
real_listen = dlsym(RTLD_NEXT, "listen");
real_accept = dlsym(RTLD_NEXT, "accept");
real_close = dlsym(RTLD_NEXT, "close");
real_read = dlsym(RTLD_NEXT, "read");
for (int i = 0; i < 1024; i++) {
peer_of[i] = -1;
wake_r[i] = -1;
wake_w[i] = -1;
}
}
/* ---- socket ---------------------------------------------------------- */
int socket(int domain, int type, int protocol) {
if (domain == AF_UNIX) {
int fd = real_socket(domain, type, protocol);
if (fd >= 0) return fd;
/* socket(AF_UNIX) blocked – fall back to socketpair(). */
int sv[2];
if (real_socketpair(domain, type, protocol, sv) == 0) {
if (sv[0] >= 0 && sv[0] < 1024) {
is_shimmed[sv[0]] = 1;
peer_of[sv[0]] = sv[1];
int wp[2];
if (pipe(wp) == 0) {
wake_r[sv[0]] = wp[0];
wake_w[sv[0]] = wp[1];
}
}
return sv[0];
}
errno = EPERM;
return -1;
}
return real_socket(domain, type, protocol);
}
/* ---- listen ---------------------------------------------------------- */
int listen(int sockfd, int backlog) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
listener_fd = sockfd;
return 0;
}
return real_listen(sockfd, backlog);
}
/* ---- accept ---------------------------------------------------------- */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
/* Block until close() writes to the wake pipe. */
if (wake_r[sockfd] >= 0) {
char buf;
real_read(wake_r[sockfd], &buf, 1);
}
errno = ECONNABORTED;
return -1;
}
return real_accept(sockfd, addr, addrlen);
}
/* ---- close ----------------------------------------------------------- */
int close(int fd) {
if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
int was_listener = (fd == listener_fd);
is_shimmed[fd] = 0;
if (wake_w[fd] >= 0) { /* unblock accept() */
char c = 0;
write(wake_w[fd], &c, 1);
real_close(wake_w[fd]);
wake_w[fd] = -1;
}
if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
if (was_listener)
_exit(0); /* conversion done – exit */
}
return real_close(fd);
}
"""
if __name__ == "__main__":
import sys
result = run_soffice(sys.argv[1:])
sys.exit(result.returncode)
@@ -0,0 +1,132 @@
"""Unpack Office files (DOCX, PPTX, XLSX) for editing.
Extracts the ZIP archive, pretty-prints XML files, and optionally:
- Merges adjacent runs with identical formatting (DOCX only)
- Simplifies adjacent tracked changes from same author (DOCX only)
Usage:
python unpack.py <office_file> <output_dir> [options]
Examples:
python unpack.py document.docx unpacked/
python unpack.py presentation.pptx unpacked/
python unpack.py document.docx unpacked/ --merge-runs false
"""
import argparse
import sys
import zipfile
from pathlib import Path
import defusedxml.minidom
from helpers.merge_runs import merge_runs as do_merge_runs
from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines
SMART_QUOTE_REPLACEMENTS = {
"\u201c": "&#x201C;",
"\u201d": "&#x201D;",
"\u2018": "&#x2018;",
"\u2019": "&#x2019;",
}
def unpack(
input_file: str,
output_directory: str,
merge_runs: bool = True,
simplify_redlines: bool = True,
) -> tuple[None, str]:
input_path = Path(input_file)
output_path = Path(output_directory)
suffix = input_path.suffix.lower()
if not input_path.exists():
return None, f"Error: {input_file} does not exist"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file"
try:
output_path.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(input_path, "r") as zf:
zf.extractall(output_path)
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
for xml_file in xml_files:
_pretty_print_xml(xml_file)
message = f"Unpacked {input_file} ({len(xml_files)} XML files)"
if suffix == ".docx":
if simplify_redlines:
simplify_count, _ = do_simplify_redlines(str(output_path))
message += f", simplified {simplify_count} tracked changes"
if merge_runs:
merge_count, _ = do_merge_runs(str(output_path))
message += f", merged {merge_count} runs"
for xml_file in xml_files:
_escape_smart_quotes(xml_file)
return None, message
except zipfile.BadZipFile:
return None, f"Error: {input_file} is not a valid Office file"
except Exception as e:
return None, f"Error unpacking: {e}"
def _pretty_print_xml(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8"))
except Exception:
pass
def _escape_smart_quotes(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
for char, entity in SMART_QUOTE_REPLACEMENTS.items():
content = content.replace(char, entity)
xml_file.write_text(content, encoding="utf-8")
except Exception:
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Unpack an Office file (DOCX, PPTX, XLSX) for editing"
)
parser.add_argument("input_file", help="Office file to unpack")
parser.add_argument("output_directory", help="Output directory")
parser.add_argument(
"--merge-runs",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent runs with identical formatting (DOCX only, default: true)",
)
parser.add_argument(
"--simplify-redlines",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent tracked changes from same author (DOCX only, default: true)",
)
args = parser.parse_args()
_, message = unpack(
args.input_file,
args.output_directory,
merge_runs=args.merge_runs,
simplify_redlines=args.simplify_redlines,
)
print(message)
if "Error" in message:
sys.exit(1)
@@ -0,0 +1,111 @@
"""
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
Usage:
python validate.py <path> [--original <original_file>] [--auto-repair] [--author NAME]
The first argument can be either:
- An unpacked directory containing the Office document XML files
- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory
Auto-repair fixes:
- paraId/durableId values that exceed OOXML limits
- Missing xml:space="preserve" on w:t elements with whitespace
"""
import argparse
import sys
import tempfile
import zipfile
from pathlib import Path
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def main():
parser = argparse.ArgumentParser(description="Validate Office document XML files")
parser.add_argument(
"path",
help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)",
)
parser.add_argument(
"--original",
required=False,
default=None,
help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose output",
)
parser.add_argument(
"--auto-repair",
action="store_true",
help="Automatically repair common issues (hex IDs, whitespace preservation)",
)
parser.add_argument(
"--author",
default="Claude",
help="Author name for redlining validation (default: Claude)",
)
args = parser.parse_args()
path = Path(args.path)
assert path.exists(), f"Error: {path} does not exist"
original_file = None
if args.original:
original_file = Path(args.original)
assert original_file.is_file(), f"Error: {original_file} is not a file"
assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], (
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
)
file_extension = (original_file or path).suffix.lower()
assert file_extension in [".docx", ".pptx", ".xlsx"], (
f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file."
)
if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]:
temp_dir = tempfile.mkdtemp()
with zipfile.ZipFile(path, "r") as zf:
zf.extractall(temp_dir)
unpacked_dir = Path(temp_dir)
else:
assert path.is_dir(), f"Error: {path} is not a directory or Office file"
unpacked_dir = path
match file_extension:
case ".docx":
validators = [
DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
if original_file:
validators.append(
RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author)
)
case ".pptx":
validators = [
PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
case _:
print(f"Error: Validation not supported for file type {file_extension}")
sys.exit(1)
if args.auto_repair:
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
print(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
print("All validations PASSED!")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,15 @@
"""
Validation modules for Word document processing.
"""
from .base import BaseSchemaValidator
from .docx import DOCXSchemaValidator
from .pptx import PPTXSchemaValidator
from .redlining import RedliningValidator
__all__ = [
"BaseSchemaValidator",
"DOCXSchemaValidator",
"PPTXSchemaValidator",
"RedliningValidator",
]
@@ -0,0 +1,847 @@
"""
Base validator with common validation logic for document files.
"""
import re
from pathlib import Path
import defusedxml.minidom
import lxml.etree
class BaseSchemaValidator:
IGNORED_VALIDATION_ERRORS = [
"hyphenationZone",
"purl.org/dc/terms",
]
UNIQUE_ID_REQUIREMENTS = {
"comment": ("id", "file"),
"commentrangestart": ("id", "file"),
"commentrangeend": ("id", "file"),
"bookmarkstart": ("id", "file"),
"bookmarkend": ("id", "file"),
"sldid": ("id", "file"),
"sldmasterid": ("id", "global"),
"sldlayoutid": ("id", "global"),
"cm": ("authorid", "file"),
"sheet": ("sheetid", "file"),
"definedname": ("id", "file"),
"cxnsp": ("id", "file"),
"sp": ("id", "file"),
"pic": ("id", "file"),
"grpsp": ("id", "file"),
}
EXCLUDED_ID_CONTAINERS = {
"sectionlst",
}
ELEMENT_RELATIONSHIP_TYPES = {}
SCHEMA_MAPPINGS = {
"word": "ISO-IEC29500-4_2016/wml.xsd",
"ppt": "ISO-IEC29500-4_2016/pml.xsd",
"xl": "ISO-IEC29500-4_2016/sml.xsd",
"[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd",
"app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
"core.xml": "ecma/fouth-edition/opc-coreProperties.xsd",
"custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
".rels": "ecma/fouth-edition/opc-relationships.xsd",
"people.xml": "microsoft/wml-2012.xsd",
"commentsIds.xml": "microsoft/wml-cid-2016.xsd",
"commentsExtensible.xml": "microsoft/wml-cex-2018.xsd",
"commentsExtended.xml": "microsoft/wml-2012.xsd",
"chart": "ISO-IEC29500-4_2016/dml-chart.xsd",
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
}
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
PACKAGE_RELATIONSHIPS_NAMESPACE = (
"http://schemas.openxmlformats.org/package/2006/relationships"
)
OFFICE_RELATIONSHIPS_NAMESPACE = (
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
)
CONTENT_TYPES_NAMESPACE = (
"http://schemas.openxmlformats.org/package/2006/content-types"
)
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
OOXML_NAMESPACES = {
"http://schemas.openxmlformats.org/officeDocument/2006/math",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"http://schemas.openxmlformats.org/schemaLibrary/2006/main",
"http://schemas.openxmlformats.org/drawingml/2006/main",
"http://schemas.openxmlformats.org/drawingml/2006/chart",
"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
"http://schemas.openxmlformats.org/drawingml/2006/picture",
"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"http://schemas.openxmlformats.org/presentationml/2006/main",
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
"http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes",
"http://www.w3.org/XML/1998/namespace",
}
def __init__(self, unpacked_dir, original_file=None, verbose=False):
self.unpacked_dir = Path(unpacked_dir).resolve()
self.original_file = Path(original_file) if original_file else None
self.verbose = verbose
self.schemas_dir = Path(__file__).parent.parent / "schemas"
patterns = ["*.xml", "*.rels"]
self.xml_files = [
f for pattern in patterns for f in self.unpacked_dir.rglob(pattern)
]
if not self.xml_files:
print(f"Warning: No XML files found in {self.unpacked_dir}")
def validate(self):
raise NotImplementedError("Subclasses must implement the validate method")
def repair(self) -> int:
return self.repair_whitespace_preservation()
def repair_whitespace_preservation(self) -> int:
repairs = 0
for xml_file in self.xml_files:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
modified = False
for elem in dom.getElementsByTagName("*"):
if elem.tagName.endswith(":t") and elem.firstChild:
text = elem.firstChild.nodeValue
if text and (text.startswith((' ', '\t')) or text.endswith((' ', '\t'))):
if elem.getAttribute("xml:space") != "preserve":
elem.setAttribute("xml:space", "preserve")
text_preview = repr(text[:30]) + "..." if len(text) > 30 else repr(text)
print(f" Repaired: {xml_file.name}: Added xml:space='preserve' to {elem.tagName}: {text_preview}")
repairs += 1
modified = True
if modified:
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception:
pass
return repairs
def validate_xml(self):
errors = []
for xml_file in self.xml_files:
try:
lxml.etree.parse(str(xml_file))
except lxml.etree.XMLSyntaxError as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {e.lineno}: {e.msg}"
)
except Exception as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Unexpected error: {str(e)}"
)
if errors:
print(f"FAILED - Found {len(errors)} XML violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All XML files are well-formed")
return True
def validate_namespaces(self):
errors = []
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
declared = set(root.nsmap.keys()) - {None}
for attr_val in [
v for k, v in root.attrib.items() if k.endswith("Ignorable")
]:
undeclared = set(attr_val.split()) - declared
errors.extend(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Namespace '{ns}' in Ignorable but not declared"
for ns in undeclared
)
except lxml.etree.XMLSyntaxError:
continue
if errors:
print(f"FAILED - {len(errors)} namespace issues:")
for error in errors:
print(error)
return False
if self.verbose:
print("PASSED - All namespace prefixes properly declared")
return True
def validate_unique_ids(self):
errors = []
global_ids = {}
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
file_ids = {}
mc_elements = root.xpath(
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
)
for elem in mc_elements:
elem.getparent().remove(elem)
for elem in root.iter():
tag = (
elem.tag.split("}")[-1].lower()
if "}" in elem.tag
else elem.tag.lower()
)
if tag in self.UNIQUE_ID_REQUIREMENTS:
in_excluded_container = any(
ancestor.tag.split("}")[-1].lower() in self.EXCLUDED_ID_CONTAINERS
for ancestor in elem.iterancestors()
)
if in_excluded_container:
continue
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
id_value = None
for attr, value in elem.attrib.items():
attr_local = (
attr.split("}")[-1].lower()
if "}" in attr
else attr.lower()
)
if attr_local == attr_name:
id_value = value
break
if id_value is not None:
if scope == "global":
if id_value in global_ids:
prev_file, prev_line, prev_tag = global_ids[
id_value
]
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> "
f"already used in {prev_file} at line {prev_line} in <{prev_tag}>"
)
else:
global_ids[id_value] = (
xml_file.relative_to(self.unpacked_dir),
elem.sourceline,
tag,
)
elif scope == "file":
key = (tag, attr_name)
if key not in file_ids:
file_ids[key] = {}
if id_value in file_ids[key]:
prev_line = file_ids[key][id_value]
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> "
f"(first occurrence at line {prev_line})"
)
else:
file_ids[key][id_value] = elem.sourceline
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} ID uniqueness violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All required IDs are unique")
return True
def validate_file_references(self):
errors = []
rels_files = list(self.unpacked_dir.rglob("*.rels"))
if not rels_files:
if self.verbose:
print("PASSED - No .rels files found")
return True
all_files = []
for file_path in self.unpacked_dir.rglob("*"):
if (
file_path.is_file()
and file_path.name != "[Content_Types].xml"
and not file_path.name.endswith(".rels")
):
all_files.append(file_path.resolve())
all_referenced_files = set()
if self.verbose:
print(
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
)
for rels_file in rels_files:
try:
rels_root = lxml.etree.parse(str(rels_file)).getroot()
rels_dir = rels_file.parent
referenced_files = set()
broken_refs = []
for rel in rels_root.findall(
".//ns:Relationship",
namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE},
):
target = rel.get("Target")
if target and not target.startswith(
("http", "mailto:")
):
if target.startswith("/"):
target_path = self.unpacked_dir / target.lstrip("/")
elif rels_file.name == ".rels":
target_path = self.unpacked_dir / target
else:
base_dir = rels_dir.parent
target_path = base_dir / target
try:
target_path = target_path.resolve()
if target_path.exists() and target_path.is_file():
referenced_files.add(target_path)
all_referenced_files.add(target_path)
else:
broken_refs.append((target, rel.sourceline))
except (OSError, ValueError):
broken_refs.append((target, rel.sourceline))
if broken_refs:
rel_path = rels_file.relative_to(self.unpacked_dir)
for broken_ref, line_num in broken_refs:
errors.append(
f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}"
)
except Exception as e:
rel_path = rels_file.relative_to(self.unpacked_dir)
errors.append(f" Error parsing {rel_path}: {e}")
unreferenced_files = set(all_files) - all_referenced_files
if unreferenced_files:
for unref_file in sorted(unreferenced_files):
unref_rel_path = unref_file.relative_to(self.unpacked_dir)
errors.append(f" Unreferenced file: {unref_rel_path}")
if errors:
print(f"FAILED - Found {len(errors)} relationship validation errors:")
for error in errors:
print(error)
print(
"CRITICAL: These errors will cause the document to appear corrupt. "
+ "Broken references MUST be fixed, "
+ "and unreferenced files MUST be referenced or removed."
)
return False
else:
if self.verbose:
print(
"PASSED - All references are valid and all files are properly referenced"
)
return True
def validate_all_relationship_ids(self):
import lxml.etree
errors = []
for xml_file in self.xml_files:
if xml_file.suffix == ".rels":
continue
rels_dir = xml_file.parent / "_rels"
rels_file = rels_dir / f"{xml_file.name}.rels"
if not rels_file.exists():
continue
try:
rels_root = lxml.etree.parse(str(rels_file)).getroot()
rid_to_type = {}
for rel in rels_root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rid = rel.get("Id")
rel_type = rel.get("Type", "")
if rid:
if rid in rid_to_type:
rels_rel_path = rels_file.relative_to(self.unpacked_dir)
errors.append(
f" {rels_rel_path}: Line {rel.sourceline}: "
f"Duplicate relationship ID '{rid}' (IDs must be unique)"
)
type_name = (
rel_type.split("/")[-1] if "/" in rel_type else rel_type
)
rid_to_type[rid] = type_name
xml_root = lxml.etree.parse(str(xml_file)).getroot()
r_ns = self.OFFICE_RELATIONSHIPS_NAMESPACE
rid_attrs_to_check = ["id", "embed", "link"]
for elem in xml_root.iter():
for attr_name in rid_attrs_to_check:
rid_attr = elem.get(f"{{{r_ns}}}{attr_name}")
if not rid_attr:
continue
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
elem_name = (
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
)
if rid_attr not in rid_to_type:
errors.append(
f" {xml_rel_path}: Line {elem.sourceline}: "
f"<{elem_name}> r:{attr_name} references non-existent relationship '{rid_attr}' "
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
)
elif attr_name == "id" and self.ELEMENT_RELATIONSHIP_TYPES:
expected_type = self._get_expected_relationship_type(
elem_name
)
if expected_type:
actual_type = rid_to_type[rid_attr]
if expected_type not in actual_type.lower():
errors.append(
f" {xml_rel_path}: Line {elem.sourceline}: "
f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' "
f"but should point to a '{expected_type}' relationship"
)
except Exception as e:
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
errors.append(f" Error processing {xml_rel_path}: {e}")
if errors:
print(f"FAILED - Found {len(errors)} relationship ID reference errors:")
for error in errors:
print(error)
print("\nThese ID mismatches will cause the document to appear corrupt!")
return False
else:
if self.verbose:
print("PASSED - All relationship ID references are valid")
return True
def _get_expected_relationship_type(self, element_name):
elem_lower = element_name.lower()
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
if elem_lower.endswith("id") and len(elem_lower) > 2:
prefix = elem_lower[:-2]
if prefix.endswith("master"):
return prefix.lower()
elif prefix.endswith("layout"):
return prefix.lower()
else:
if prefix == "sld":
return "slide"
return prefix.lower()
if elem_lower.endswith("reference") and len(elem_lower) > 9:
prefix = elem_lower[:-9]
return prefix.lower()
return None
def validate_content_types(self):
errors = []
content_types_file = self.unpacked_dir / "[Content_Types].xml"
if not content_types_file.exists():
print("FAILED - [Content_Types].xml file not found")
return False
try:
root = lxml.etree.parse(str(content_types_file)).getroot()
declared_parts = set()
declared_extensions = set()
for override in root.findall(
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override"
):
part_name = override.get("PartName")
if part_name is not None:
declared_parts.add(part_name.lstrip("/"))
for default in root.findall(
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default"
):
extension = default.get("Extension")
if extension is not None:
declared_extensions.add(extension.lower())
declarable_roots = {
"sld",
"sldLayout",
"sldMaster",
"presentation",
"document",
"workbook",
"worksheet",
"theme",
}
media_extensions = {
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"bmp": "image/bmp",
"tiff": "image/tiff",
"wmf": "image/x-wmf",
"emf": "image/x-emf",
}
all_files = list(self.unpacked_dir.rglob("*"))
all_files = [f for f in all_files if f.is_file()]
for xml_file in self.xml_files:
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
"\\", "/"
)
if any(
skip in path_str
for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"]
):
continue
try:
root_tag = lxml.etree.parse(str(xml_file)).getroot().tag
root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag
if root_name in declarable_roots and path_str not in declared_parts:
errors.append(
f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml"
)
except Exception:
continue
for file_path in all_files:
if file_path.suffix.lower() in {".xml", ".rels"}:
continue
if file_path.name == "[Content_Types].xml":
continue
if "_rels" in file_path.parts or "docProps" in file_path.parts:
continue
extension = file_path.suffix.lstrip(".").lower()
if extension and extension not in declared_extensions:
if extension in media_extensions:
relative_path = file_path.relative_to(self.unpacked_dir)
errors.append(
f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: <Default Extension="{extension}" ContentType="{media_extensions[extension]}"/>'
)
except Exception as e:
errors.append(f" Error parsing [Content_Types].xml: {e}")
if errors:
print(f"FAILED - Found {len(errors)} content type declaration errors:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print(
"PASSED - All content files are properly declared in [Content_Types].xml"
)
return True
def validate_file_against_xsd(self, xml_file, verbose=False):
xml_file = Path(xml_file).resolve()
unpacked_dir = self.unpacked_dir.resolve()
is_valid, current_errors = self._validate_single_file_xsd(
xml_file, unpacked_dir
)
if is_valid is None:
return None, set()
elif is_valid:
return True, set()
original_errors = self._get_original_file_errors(xml_file)
assert current_errors is not None
new_errors = current_errors - original_errors
new_errors = {
e for e in new_errors
if not any(pattern in e for pattern in self.IGNORED_VALIDATION_ERRORS)
}
if new_errors:
if verbose:
relative_path = xml_file.relative_to(unpacked_dir)
print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)")
for error in list(new_errors)[:3]:
truncated = error[:250] + "..." if len(error) > 250 else error
print(f" - {truncated}")
return False, new_errors
else:
if verbose:
print(
f"PASSED - No new errors (original had {len(current_errors)} errors)"
)
return True, set()
def validate_against_xsd(self):
new_errors = []
original_error_count = 0
valid_count = 0
skipped_count = 0
for xml_file in self.xml_files:
relative_path = str(xml_file.relative_to(self.unpacked_dir))
is_valid, new_file_errors = self.validate_file_against_xsd(
xml_file, verbose=False
)
if is_valid is None:
skipped_count += 1
continue
elif is_valid and not new_file_errors:
valid_count += 1
continue
elif is_valid:
original_error_count += 1
valid_count += 1
continue
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
for error in list(new_file_errors)[:3]:
new_errors.append(
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
)
if self.verbose:
print(f"Validated {len(self.xml_files)} files:")
print(f" - Valid: {valid_count}")
print(f" - Skipped (no schema): {skipped_count}")
if original_error_count:
print(f" - With original errors (ignored): {original_error_count}")
print(
f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}"
)
if new_errors:
print("\nFAILED - Found NEW validation errors:")
for error in new_errors:
print(error)
return False
else:
if self.verbose:
print("\nPASSED - No new XSD validation errors introduced")
return True
def _get_schema_path(self, xml_file):
if xml_file.name in self.SCHEMA_MAPPINGS:
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
if xml_file.suffix == ".rels":
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS:
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name]
return None
def _clean_ignorable_namespaces(self, xml_doc):
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
xml_copy = lxml.etree.fromstring(xml_string)
for elem in xml_copy.iter():
attrs_to_remove = []
for attr in elem.attrib:
if "{" in attr:
ns = attr.split("}")[0][1:]
if ns not in self.OOXML_NAMESPACES:
attrs_to_remove.append(attr)
for attr in attrs_to_remove:
del elem.attrib[attr]
self._remove_ignorable_elements(xml_copy)
return lxml.etree.ElementTree(xml_copy)
def _remove_ignorable_elements(self, root):
elements_to_remove = []
for elem in list(root):
if not hasattr(elem, "tag") or callable(elem.tag):
continue
tag_str = str(elem.tag)
if tag_str.startswith("{"):
ns = tag_str.split("}")[0][1:]
if ns not in self.OOXML_NAMESPACES:
elements_to_remove.append(elem)
continue
self._remove_ignorable_elements(elem)
for elem in elements_to_remove:
root.remove(elem)
def _preprocess_for_mc_ignorable(self, xml_doc):
root = xml_doc.getroot()
if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib:
del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"]
return xml_doc
def _validate_single_file_xsd(self, xml_file, base_path):
schema_path = self._get_schema_path(xml_file)
if not schema_path:
return None, None
try:
with open(schema_path, "rb") as xsd_file:
parser = lxml.etree.XMLParser()
xsd_doc = lxml.etree.parse(
xsd_file, parser=parser, base_url=str(schema_path)
)
schema = lxml.etree.XMLSchema(xsd_doc)
with open(xml_file, "r") as f:
xml_doc = lxml.etree.parse(f)
xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc)
xml_doc = self._preprocess_for_mc_ignorable(xml_doc)
relative_path = xml_file.relative_to(base_path)
if (
relative_path.parts
and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS
):
xml_doc = self._clean_ignorable_namespaces(xml_doc)
if schema.validate(xml_doc):
return True, set()
else:
errors = set()
for error in schema.error_log:
errors.add(error.message)
return False, errors
except Exception as e:
return False, {str(e)}
def _get_original_file_errors(self, xml_file):
if self.original_file is None:
return set()
import tempfile
import zipfile
xml_file = Path(xml_file).resolve()
unpacked_dir = self.unpacked_dir.resolve()
relative_path = xml_file.relative_to(unpacked_dir)
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
zip_ref.extractall(temp_path)
original_xml_file = temp_path / relative_path
if not original_xml_file.exists():
return set()
is_valid, errors = self._validate_single_file_xsd(
original_xml_file, temp_path
)
return errors if errors else set()
def _remove_template_tags_from_text_nodes(self, xml_doc):
warnings = []
template_pattern = re.compile(r"\{\{[^}]*\}\}")
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
xml_copy = lxml.etree.fromstring(xml_string)
def process_text_content(text, content_type):
if not text:
return text
matches = list(template_pattern.finditer(text))
if matches:
for match in matches:
warnings.append(
f"Found template tag in {content_type}: {match.group()}"
)
return template_pattern.sub("", text)
return text
for elem in xml_copy.iter():
if not hasattr(elem, "tag") or callable(elem.tag):
continue
tag_str = str(elem.tag)
if tag_str.endswith("}t") or tag_str == "t":
continue
elem.text = process_text_content(elem.text, "text content")
elem.tail = process_text_content(elem.tail, "tail content")
return lxml.etree.ElementTree(xml_copy), warnings
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,446 @@
"""
Validator for Word document XML files against XSD schemas.
"""
import random
import re
import tempfile
import zipfile
import defusedxml.minidom
import lxml.etree
from .base import BaseSchemaValidator
class DOCXSchemaValidator(BaseSchemaValidator):
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
W14_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml"
W16CID_NAMESPACE = "http://schemas.microsoft.com/office/word/2016/wordml/cid"
ELEMENT_RELATIONSHIP_TYPES = {}
def validate(self):
if not self.validate_xml():
return False
all_valid = True
if not self.validate_namespaces():
all_valid = False
if not self.validate_unique_ids():
all_valid = False
if not self.validate_file_references():
all_valid = False
if not self.validate_content_types():
all_valid = False
if not self.validate_against_xsd():
all_valid = False
if not self.validate_whitespace_preservation():
all_valid = False
if not self.validate_deletions():
all_valid = False
if not self.validate_insertions():
all_valid = False
if not self.validate_all_relationship_ids():
all_valid = False
if not self.validate_id_constraints():
all_valid = False
if not self.validate_comment_markers():
all_valid = False
self.compare_paragraph_counts()
return all_valid
def validate_whitespace_preservation(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
if elem.text:
text = elem.text
if re.search(r"^[ \t\n\r]", text) or re.search(
r"[ \t\n\r]$", text
):
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
if (
xml_space_attr not in elem.attrib
or elem.attrib[xml_space_attr] != "preserve"
):
text_preview = (
repr(text)[:50] + "..."
if len(repr(text)) > 50
else repr(text)
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} whitespace preservation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All whitespace is properly preserved")
return True
def validate_deletions(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
for t_elem in root.xpath(".//w:del//w:t", namespaces=namespaces):
if t_elem.text:
text_preview = (
repr(t_elem.text)[:50] + "..."
if len(repr(t_elem.text)) > 50
else repr(t_elem.text)
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {t_elem.sourceline}: <w:t> found within <w:del>: {text_preview}"
)
for instr_elem in root.xpath(
".//w:del//w:instrText", namespaces=namespaces
):
text_preview = (
repr(instr_elem.text or "")[:50] + "..."
if len(repr(instr_elem.text or "")) > 50
else repr(instr_elem.text or "")
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {instr_elem.sourceline}: <w:instrText> found within <w:del> (use <w:delInstrText>): {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} deletion validation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - No w:t elements found within w:del elements")
return True
def count_paragraphs_in_unpacked(self):
count = 0
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
count = len(paragraphs)
except Exception as e:
print(f"Error counting paragraphs in unpacked document: {e}")
return count
def count_paragraphs_in_original(self):
original = self.original_file
if original is None:
return 0
count = 0
try:
with tempfile.TemporaryDirectory() as temp_dir:
with zipfile.ZipFile(original, "r") as zip_ref:
zip_ref.extractall(temp_dir)
doc_xml_path = temp_dir + "/word/document.xml"
root = lxml.etree.parse(doc_xml_path).getroot()
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
count = len(paragraphs)
except Exception as e:
print(f"Error counting paragraphs in original document: {e}")
return count
def validate_insertions(self):
errors = []
for xml_file in self.xml_files:
if xml_file.name != "document.xml":
continue
try:
root = lxml.etree.parse(str(xml_file)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
invalid_elements = root.xpath(
".//w:ins//w:delText[not(ancestor::w:del)]", namespaces=namespaces
)
for elem in invalid_elements:
text_preview = (
repr(elem.text or "")[:50] + "..."
if len(repr(elem.text or "")) > 50
else repr(elem.text or "")
)
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: <w:delText> within <w:ins>: {text_preview}"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} insertion validation violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - No w:delText elements within w:ins elements")
return True
def compare_paragraph_counts(self):
original_count = self.count_paragraphs_in_original()
new_count = self.count_paragraphs_in_unpacked()
diff = new_count - original_count
diff_str = f"+{diff}" if diff > 0 else str(diff)
print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})")
def _parse_id_value(self, val: str, base: int = 16) -> int:
return int(val, base)
def validate_id_constraints(self):
errors = []
para_id_attr = f"{{{self.W14_NAMESPACE}}}paraId"
durable_id_attr = f"{{{self.W16CID_NAMESPACE}}}durableId"
for xml_file in self.xml_files:
try:
for elem in lxml.etree.parse(str(xml_file)).iter():
if val := elem.get(para_id_attr):
if self._parse_id_value(val, base=16) >= 0x80000000:
errors.append(
f" {xml_file.name}:{elem.sourceline}: paraId={val} >= 0x80000000"
)
if val := elem.get(durable_id_attr):
if xml_file.name == "numbering.xml":
try:
if self._parse_id_value(val, base=10) >= 0x7FFFFFFF:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} >= 0x7FFFFFFF"
)
except ValueError:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} must be decimal in numbering.xml"
)
else:
if self._parse_id_value(val, base=16) >= 0x7FFFFFFF:
errors.append(
f" {xml_file.name}:{elem.sourceline}: "
f"durableId={val} >= 0x7FFFFFFF"
)
except Exception:
pass
if errors:
print(f"FAILED - {len(errors)} ID constraint violations:")
for e in errors:
print(e)
elif self.verbose:
print("PASSED - All paraId/durableId values within constraints")
return not errors
def validate_comment_markers(self):
errors = []
document_xml = None
comments_xml = None
for xml_file in self.xml_files:
if xml_file.name == "document.xml" and "word" in str(xml_file):
document_xml = xml_file
elif xml_file.name == "comments.xml":
comments_xml = xml_file
if not document_xml:
if self.verbose:
print("PASSED - No document.xml found (skipping comment validation)")
return True
try:
doc_root = lxml.etree.parse(str(document_xml)).getroot()
namespaces = {"w": self.WORD_2006_NAMESPACE}
range_starts = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentRangeStart", namespaces=namespaces
)
}
range_ends = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentRangeEnd", namespaces=namespaces
)
}
references = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in doc_root.xpath(
".//w:commentReference", namespaces=namespaces
)
}
orphaned_ends = range_ends - range_starts
for comment_id in sorted(
orphaned_ends, key=lambda x: int(x) if x and x.isdigit() else 0
):
errors.append(
f' document.xml: commentRangeEnd id="{comment_id}" has no matching commentRangeStart'
)
orphaned_starts = range_starts - range_ends
for comment_id in sorted(
orphaned_starts, key=lambda x: int(x) if x and x.isdigit() else 0
):
errors.append(
f' document.xml: commentRangeStart id="{comment_id}" has no matching commentRangeEnd'
)
comment_ids = set()
if comments_xml and comments_xml.exists():
comments_root = lxml.etree.parse(str(comments_xml)).getroot()
comment_ids = {
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
for elem in comments_root.xpath(
".//w:comment", namespaces=namespaces
)
}
marker_ids = range_starts | range_ends | references
invalid_refs = marker_ids - comment_ids
for comment_id in sorted(
invalid_refs, key=lambda x: int(x) if x and x.isdigit() else 0
):
if comment_id:
errors.append(
f' document.xml: marker id="{comment_id}" references non-existent comment'
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(f" Error parsing XML: {e}")
if errors:
print(f"FAILED - {len(errors)} comment marker violations:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All comment markers properly paired")
return True
def repair(self) -> int:
repairs = super().repair()
repairs += self.repair_durableId()
return repairs
def repair_durableId(self) -> int:
repairs = 0
for xml_file in self.xml_files:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
modified = False
for elem in dom.getElementsByTagName("*"):
if not elem.hasAttribute("w16cid:durableId"):
continue
durable_id = elem.getAttribute("w16cid:durableId")
needs_repair = False
if xml_file.name == "numbering.xml":
try:
needs_repair = (
self._parse_id_value(durable_id, base=10) >= 0x7FFFFFFF
)
except ValueError:
needs_repair = True
else:
try:
needs_repair = (
self._parse_id_value(durable_id, base=16) >= 0x7FFFFFFF
)
except ValueError:
needs_repair = True
if needs_repair:
value = random.randint(1, 0x7FFFFFFE)
if xml_file.name == "numbering.xml":
new_id = str(value)
else:
new_id = f"{value:08X}"
elem.setAttribute("w16cid:durableId", new_id)
print(
f" Repaired: {xml_file.name}: durableId {durable_id} → {new_id}"
)
repairs += 1
modified = True
if modified:
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception:
pass
return repairs
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,275 @@
"""
Validator for PowerPoint presentation XML files against XSD schemas.
"""
import re
from .base import BaseSchemaValidator
class PPTXSchemaValidator(BaseSchemaValidator):
PRESENTATIONML_NAMESPACE = (
"http://schemas.openxmlformats.org/presentationml/2006/main"
)
ELEMENT_RELATIONSHIP_TYPES = {
"sldid": "slide",
"sldmasterid": "slidemaster",
"notesmasterid": "notesmaster",
"sldlayoutid": "slidelayout",
"themeid": "theme",
"tablestyleid": "tablestyles",
}
def validate(self):
if not self.validate_xml():
return False
all_valid = True
if not self.validate_namespaces():
all_valid = False
if not self.validate_unique_ids():
all_valid = False
if not self.validate_uuid_ids():
all_valid = False
if not self.validate_file_references():
all_valid = False
if not self.validate_slide_layout_ids():
all_valid = False
if not self.validate_content_types():
all_valid = False
if not self.validate_against_xsd():
all_valid = False
if not self.validate_notes_slide_references():
all_valid = False
if not self.validate_all_relationship_ids():
all_valid = False
if not self.validate_no_duplicate_slide_layouts():
all_valid = False
return all_valid
def validate_uuid_ids(self):
import lxml.etree
errors = []
uuid_pattern = re.compile(
r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
)
for xml_file in self.xml_files:
try:
root = lxml.etree.parse(str(xml_file)).getroot()
for elem in root.iter():
for attr, value in elem.attrib.items():
attr_name = attr.split("}")[-1].lower()
if attr_name == "id" or attr_name.endswith("id"):
if self._looks_like_uuid(value):
if not uuid_pattern.match(value):
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: "
f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All UUID-like IDs contain valid hex values")
return True
def _looks_like_uuid(self, value):
clean_value = value.strip("{}()").replace("-", "")
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
def validate_slide_layout_ids(self):
import lxml.etree
errors = []
slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
if not slide_masters:
if self.verbose:
print("PASSED - No slide masters found")
return True
for slide_master in slide_masters:
try:
root = lxml.etree.parse(str(slide_master)).getroot()
rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
if not rels_file.exists():
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: "
f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
)
continue
rels_root = lxml.etree.parse(str(rels_file)).getroot()
valid_layout_rids = set()
for rel in rels_root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rel_type = rel.get("Type", "")
if "slideLayout" in rel_type:
valid_layout_rids.add(rel.get("Id"))
for sld_layout_id in root.findall(
f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
):
r_id = sld_layout_id.get(
f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
)
layout_id = sld_layout_id.get("id")
if r_id and r_id not in valid_layout_rids:
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: "
f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
f"references r:id='{r_id}' which is not found in slide layout relationships"
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
for error in errors:
print(error)
print(
"Remove invalid references or add missing slide layouts to the relationships file."
)
return False
else:
if self.verbose:
print("PASSED - All slide layout IDs reference valid slide layouts")
return True
def validate_no_duplicate_slide_layouts(self):
import lxml.etree
errors = []
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
for rels_file in slide_rels_files:
try:
root = lxml.etree.parse(str(rels_file)).getroot()
layout_rels = [
rel
for rel in root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
)
if "slideLayout" in rel.get("Type", "")
]
if len(layout_rels) > 1:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
)
except Exception as e:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
if errors:
print("FAILED - Found slides with duplicate slideLayout references:")
for error in errors:
print(error)
return False
else:
if self.verbose:
print("PASSED - All slides have exactly one slideLayout reference")
return True
def validate_notes_slide_references(self):
import lxml.etree
errors = []
notes_slide_references = {}
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
if not slide_rels_files:
if self.verbose:
print("PASSED - No slide relationship files found")
return True
for rels_file in slide_rels_files:
try:
root = lxml.etree.parse(str(rels_file)).getroot()
for rel in root.findall(
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
):
rel_type = rel.get("Type", "")
if "notesSlide" in rel_type:
target = rel.get("Target", "")
if target:
normalized_target = target.replace("../", "")
slide_name = rels_file.stem.replace(
".xml", ""
)
if normalized_target not in notes_slide_references:
notes_slide_references[normalized_target] = []
notes_slide_references[normalized_target].append(
(slide_name, rels_file)
)
except (lxml.etree.XMLSyntaxError, Exception) as e:
errors.append(
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
)
for target, references in notes_slide_references.items():
if len(references) > 1:
slide_names = [ref[0] for ref in references]
errors.append(
f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
)
for slide_name, rels_file in references:
errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
if errors:
print(
f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
)
for error in errors:
print(error)
print("Each slide may optionally have its own slide file.")
return False
else:
if self.verbose:
print("PASSED - All notes slide references are unique")
return True
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,247 @@
"""
Validator for tracked changes in Word documents.
"""
import subprocess
import tempfile
import zipfile
from pathlib import Path
class RedliningValidator:
def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"):
self.unpacked_dir = Path(unpacked_dir)
self.original_docx = Path(original_docx)
self.verbose = verbose
self.author = author
self.namespaces = {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
}
def repair(self) -> int:
return 0
def validate(self):
modified_file = self.unpacked_dir / "word" / "document.xml"
if not modified_file.exists():
print(f"FAILED - Modified document.xml not found at {modified_file}")
return False
try:
import xml.etree.ElementTree as ET
tree = ET.parse(modified_file)
root = tree.getroot()
del_elements = root.findall(".//w:del", self.namespaces)
ins_elements = root.findall(".//w:ins", self.namespaces)
author_del_elements = [
elem
for elem in del_elements
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
]
author_ins_elements = [
elem
for elem in ins_elements
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
]
if not author_del_elements and not author_ins_elements:
if self.verbose:
print(f"PASSED - No tracked changes by {self.author} found.")
return True
except Exception:
pass
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
try:
with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
zip_ref.extractall(temp_path)
except Exception as e:
print(f"FAILED - Error unpacking original docx: {e}")
return False
original_file = temp_path / "word" / "document.xml"
if not original_file.exists():
print(
f"FAILED - Original document.xml not found in {self.original_docx}"
)
return False
try:
import xml.etree.ElementTree as ET
modified_tree = ET.parse(modified_file)
modified_root = modified_tree.getroot()
original_tree = ET.parse(original_file)
original_root = original_tree.getroot()
except ET.ParseError as e:
print(f"FAILED - Error parsing XML files: {e}")
return False
self._remove_author_tracked_changes(original_root)
self._remove_author_tracked_changes(modified_root)
modified_text = self._extract_text_content(modified_root)
original_text = self._extract_text_content(original_root)
if modified_text != original_text:
error_message = self._generate_detailed_diff(
original_text, modified_text
)
print(error_message)
return False
if self.verbose:
print(f"PASSED - All changes by {self.author} are properly tracked")
return True
def _generate_detailed_diff(self, original_text, modified_text):
error_parts = [
f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes",
"",
"Likely causes:",
" 1. Modified text inside another author's <w:ins> or <w:del> tags",
" 2. Made edits without proper tracked changes",
" 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
"",
"For pre-redlined documents, use correct patterns:",
" - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
" - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
"",
]
git_diff = self._get_git_word_diff(original_text, modified_text)
if git_diff:
error_parts.extend(["Differences:", "============", git_diff])
else:
error_parts.append("Unable to generate word diff (git not available)")
return "\n".join(error_parts)
def _get_git_word_diff(self, original_text, modified_text):
try:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
original_file = temp_path / "original.txt"
modified_file = temp_path / "modified.txt"
original_file.write_text(original_text, encoding="utf-8")
modified_file.write_text(modified_text, encoding="utf-8")
result = subprocess.run(
[
"git",
"diff",
"--word-diff=plain",
"--word-diff-regex=.",
"-U0",
"--no-index",
str(original_file),
str(modified_file),
],
capture_output=True,
text=True,
)
if result.stdout.strip():
lines = result.stdout.split("\n")
content_lines = []
in_content = False
for line in lines:
if line.startswith("@@"):
in_content = True
continue
if in_content and line.strip():
content_lines.append(line)
if content_lines:
return "\n".join(content_lines)
result = subprocess.run(
[
"git",
"diff",
"--word-diff=plain",
"-U0",
"--no-index",
str(original_file),
str(modified_file),
],
capture_output=True,
text=True,
)
if result.stdout.strip():
lines = result.stdout.split("\n")
content_lines = []
in_content = False
for line in lines:
if line.startswith("@@"):
in_content = True
continue
if in_content and line.strip():
content_lines.append(line)
return "\n".join(content_lines)
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
pass
return None
def _remove_author_tracked_changes(self, root):
ins_tag = f"{{{self.namespaces['w']}}}ins"
del_tag = f"{{{self.namespaces['w']}}}del"
author_attr = f"{{{self.namespaces['w']}}}author"
for parent in root.iter():
to_remove = []
for child in parent:
if child.tag == ins_tag and child.get(author_attr) == self.author:
to_remove.append(child)
for elem in to_remove:
parent.remove(elem)
deltext_tag = f"{{{self.namespaces['w']}}}delText"
t_tag = f"{{{self.namespaces['w']}}}t"
for parent in root.iter():
to_process = []
for child in parent:
if child.tag == del_tag and child.get(author_attr) == self.author:
to_process.append((child, list(parent).index(child)))
for del_elem, del_index in reversed(to_process):
for elem in del_elem.iter():
if elem.tag == deltext_tag:
elem.tag = t_tag
for child in reversed(list(del_elem)):
parent.insert(del_index, child)
parent.remove(del_elem)
def _extract_text_content(self, root):
p_tag = f"{{{self.namespaces['w']}}}p"
t_tag = f"{{{self.namespaces['w']}}}t"
paragraphs = []
for p_elem in root.findall(f".//{p_tag}"):
text_parts = []
for t_elem in p_elem.findall(f".//{t_tag}"):
if t_elem.text:
text_parts.append(t_elem.text)
paragraph_text = "".join(text_parts)
if paragraph_text:
paragraphs.append(paragraph_text)
return "\n".join(paragraphs)
if __name__ == "__main__":
raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w:comments>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w15:commentsEx xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w15:commentsEx>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w16cex:commentsExtensible xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl cr w16du wp14">
</w16cex:commentsExtensible>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w16cid:commentsIds xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
</w16cid:commentsIds>
@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml">
</w15:people>
@@ -0,0 +1,3 @@
obj/
bin/
*.user
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 MiniMaxAI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="SimSun" w:cs="Times New Roman" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="0" w:line="480" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal — Times New Roman 12pt, double spaced, first line indent -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="0" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="720" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 — Bold, 14pt, no color, no indent -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Heading 2 — Bold, 13pt -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="26" />
<w:szCs w:val="26" />
</w:rPr>
</w:style>
<!-- Heading 3 — Bold, 12pt -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Title — Centered, bold, 14pt (academic title page) -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="480" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:b />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Quote — Block quote, indented 0.5 inch on both sides -->
<w:style w:type="paragraph" w:styleId="Quote">
<w:name w:val="Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="29" />
<w:pPr>
<w:spacing w:before="240" w:after="240" w:line="480" w:lineRule="auto" />
<w:ind w:left="720" w:right="720" w:firstLine="0" />
</w:pPr>
</w:style>
<!-- Table Normal -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<!-- Table Grid — Simple borders, no color -->
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto" />
</w:tblBorders>
</w:tblPr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
<w:ind w:firstLine="0" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
<w:ind w:firstLine="0" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,284 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Microsoft YaHei" w:cs="Arial" />
<w:color w:val="333333" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="160" w:line="259" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="160" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" />
<w:color w:val="333333" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 — Dark Blue -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="56" />
<w:szCs w:val="56" />
</w:rPr>
</w:style>
<!-- Heading 2 -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="48" />
<w:szCs w:val="48" />
</w:rPr>
</w:style>
<!-- Heading 3 -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="1F3864" />
<w:sz w:val="36" />
<w:szCs w:val="36" />
</w:rPr>
</w:style>
<!-- Heading 4 -->
<w:style w:type="paragraph" w:styleId="Heading4">
<w:name w:val="heading 4" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="3" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="1F3864" />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Title -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="240" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:color w:val="1F3864" />
<w:sz w:val="72" />
<w:szCs w:val="72" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="360" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="595959" />
<w:sz w:val="32" />
<w:szCs w:val="32" />
</w:rPr>
</w:style>
<!-- Table Grid — Corporate with blue header -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="BFBFBF" />
</w:tblBorders>
</w:tblPr>
<w:tblStylePr w:type="firstRow">
<w:rPr>
<w:b />
<w:color w:val="FFFFFF" />
</w:rPr>
<w:tcPr>
<w:shd w:val="clear" w:color="auto" w:fill="2F5496" />
<w:tcBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="2F5496" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="3A6BC5" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="3A6BC5" />
</w:tcBorders>
</w:tcPr>
</w:tblStylePr>
<w:tblStylePr w:type="band1Horz">
<w:tcPr>
<w:shd w:val="clear" w:color="auto" w:fill="D9E2F3" />
</w:tcPr>
</w:tblStylePr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,449 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<!-- Document Defaults -->
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="SimSun" w:cs="Arial" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" w:bidi="ar-SA" />
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="160" w:line="259" w:lineRule="auto" />
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<!-- Latent Styles -->
<w:latentStyles w:defLockedState="0" w:defUIPriority="99" w:defSemiHidden="0" w:defUnhideWhenUsed="0" w:defQFormat="0" w:count="376" />
<!-- Normal (Default Paragraph Style) -->
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal" />
<w:qFormat />
<w:pPr>
<w:spacing w:after="160" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Default Paragraph Font -->
<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont">
<w:name w:val="Default Paragraph Font" />
<w:uiPriority w:val="1" />
<w:semiHidden />
<w:unhideWhenUsed />
</w:style>
<!-- Heading 1 -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="480" w:after="240" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="0" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="56" />
<w:szCs w:val="56" />
</w:rPr>
</w:style>
<!-- Heading 2 -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="360" w:after="120" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="1" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="48" />
<w:szCs w:val="48" />
</w:rPr>
</w:style>
<!-- Heading 3 -->
<w:style w:type="paragraph" w:styleId="Heading3">
<w:name w:val="heading 3" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="240" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="2" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="36" />
<w:szCs w:val="36" />
</w:rPr>
</w:style>
<!-- Heading 4 -->
<w:style w:type="paragraph" w:styleId="Heading4">
<w:name w:val="heading 4" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="3" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="2F5496" />
<w:sz w:val="28" />
<w:szCs w:val="28" />
</w:rPr>
</w:style>
<!-- Heading 5 -->
<w:style w:type="paragraph" w:styleId="Heading5">
<w:name w:val="heading 5" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="4" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:color w:val="2F5496" />
<w:sz w:val="24" />
<w:szCs w:val="24" />
</w:rPr>
</w:style>
<!-- Heading 6 -->
<w:style w:type="paragraph" w:styleId="Heading6">
<w:name w:val="heading 6" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="9" />
<w:pPr>
<w:keepNext />
<w:keepLines />
<w:spacing w:before="160" w:after="80" w:line="240" w:lineRule="auto" />
<w:outlineLvl w:val="5" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:b />
<w:i />
<w:color w:val="2F5496" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
</w:rPr>
</w:style>
<!-- Title -->
<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="10" />
<w:pPr>
<w:spacing w:after="240" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light" w:hAnsi="Calibri Light" />
<w:color w:val="2F5496" />
<w:sz w:val="72" />
<w:szCs w:val="72" />
</w:rPr>
</w:style>
<!-- Subtitle -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:name w:val="Subtitle" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="11" />
<w:pPr>
<w:spacing w:after="360" w:line="240" w:lineRule="auto" />
<w:jc w:val="center" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="595959" />
<w:sz w:val="32" />
<w:szCs w:val="32" />
</w:rPr>
</w:style>
<!-- Quote -->
<w:style w:type="paragraph" w:styleId="Quote">
<w:name w:val="Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="29" />
<w:pPr>
<w:spacing w:before="240" w:after="240" />
<w:ind w:left="720" w:right="720" />
</w:pPr>
<w:rPr>
<w:i />
<w:color w:val="404040" />
</w:rPr>
</w:style>
<!-- Intense Quote -->
<w:style w:type="paragraph" w:styleId="IntenseQuote">
<w:name w:val="Intense Quote" />
<w:basedOn w:val="Normal" />
<w:next w:val="Normal" />
<w:qFormat />
<w:uiPriority w:val="30" />
<w:pPr>
<w:spacing w:before="240" w:after="240" />
<w:ind w:left="720" w:right="720" />
<w:pBdr>
<w:left w:val="single" w:sz="18" w:space="12" w:color="2F5496" />
</w:pBdr>
</w:pPr>
<w:rPr>
<w:b />
<w:i />
<w:color w:val="2F5496" />
</w:rPr>
</w:style>
<!-- TOC Heading -->
<w:style w:type="paragraph" w:styleId="TOCHeading">
<w:name w:val="TOC Heading" />
<w:basedOn w:val="Heading1" />
<w:next w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:outlineLvl w:val="9" />
</w:pPr>
</w:style>
<!-- TOC 1 -->
<w:style w:type="paragraph" w:styleId="TOC1">
<w:name w:val="toc 1" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:before="120" w:after="0" />
</w:pPr>
<w:rPr>
<w:b />
</w:rPr>
</w:style>
<!-- TOC 2 -->
<w:style w:type="paragraph" w:styleId="TOC2">
<w:name w:val="toc 2" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="240" />
</w:pPr>
</w:style>
<!-- TOC 3 -->
<w:style w:type="paragraph" w:styleId="TOC3">
<w:name w:val="toc 3" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="39" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="480" />
</w:pPr>
</w:style>
<!-- List Bullet -->
<w:style w:type="paragraph" w:styleId="ListBullet">
<w:name w:val="List Bullet" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="36" />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="720" w:hanging="360" />
<w:contextualSpacing />
</w:pPr>
</w:style>
<!-- List Number -->
<w:style w:type="paragraph" w:styleId="ListNumber">
<w:name w:val="List Number" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="36" />
<w:pPr>
<w:spacing w:after="0" />
<w:ind w:left="720" w:hanging="360" />
<w:contextualSpacing />
</w:pPr>
</w:style>
<!-- Table Normal -->
<w:style w:type="table" w:default="1" w:styleId="TableNormal">
<w:name w:val="Normal Table" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:tblPr>
<w:tblInd w:w="0" w:type="dxa" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
</w:style>
<!-- Table Grid -->
<w:style w:type="table" w:styleId="TableGrid">
<w:name w:val="Table Grid" />
<w:basedOn w:val="TableNormal" />
<w:uiPriority w:val="39" />
<w:tblPr>
<w:tblBorders>
<w:top w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:left w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:right w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto" />
<w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto" />
</w:tblBorders>
</w:tblPr>
</w:style>
<!-- Header -->
<w:style w:type="paragraph" w:styleId="Header">
<w:name w:val="header" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Footer -->
<w:style w:type="paragraph" w:styleId="Footer">
<w:name w:val="footer" />
<w:basedOn w:val="Normal" />
<w:uiPriority w:val="99" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:tabs>
<w:tab w:val="center" w:pos="4680" />
<w:tab w:val="right" w:pos="9360" />
</w:tabs>
<w:spacing w:after="0" w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="18" />
<w:szCs w:val="18" />
<w:color w:val="808080" />
</w:rPr>
</w:style>
<!-- Hyperlink -->
<w:style w:type="character" w:styleId="Hyperlink">
<w:name w:val="Hyperlink" />
<w:uiPriority w:val="99" />
<w:unhideWhenUsed />
<w:rPr>
<w:color w:val="0563C1" />
<w:u w:val="single" />
</w:rPr>
</w:style>
<!-- Comment Text -->
<w:style w:type="paragraph" w:styleId="CommentText">
<w:name w:val="annotation text" />
<w:basedOn w:val="Normal" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:pPr>
<w:spacing w:line="240" w:lineRule="auto" />
</w:pPr>
<w:rPr>
<w:sz w:val="20" />
<w:szCs w:val="20" />
</w:rPr>
</w:style>
<!-- Comment Reference -->
<w:style w:type="character" w:styleId="CommentReference">
<w:name w:val="annotation reference" />
<w:semiHidden />
<w:unhideWhenUsed />
<w:rPr>
<w:sz w:val="16" />
<w:szCs w:val="16" />
</w:rPr>
</w:style>
</w:styles>
@@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ============================================================================ -->
<!-- Aesthetic Rules Schema for minimax-docx -->
<!-- ============================================================================ -->
<!-- Purpose: Validates whether a document follows basic aesthetic rules that -->
<!-- produce visually harmonious results. This is a "taste checker" that flags -->
<!-- common ugly patterns. -->
<!-- -->
<!-- IMPORTANT: XSD validates STRUCTURE and VALUE RANGES, not SEMANTICS. -->
<!-- Many aesthetic rules require cross-element comparison (e.g., "H1 must be -->
<!-- larger than H2") which XSD cannot express. These rules are documented in -->
<!-- comments and must be enforced by a programmatic validator. -->
<!-- -->
<!-- Rules that CAN be expressed in XSD: -->
<!-- - Font size ranges (body 10-14pt, headings 10-26pt) -->
<!-- - Line spacing ranges (1.0x to 2.33x) -->
<!-- - Margin minimums (at least 0.5in on all sides) -->
<!-- - Table cell padding minimums -->
<!-- -->
<!-- Rules that CANNOT be expressed in XSD (enforce programmatically): -->
<!-- - H1 sz > H2 sz > H3 sz > body sz (hierarchy) -->
<!-- - Maximum 3 font families across all styles -->
<!-- - Heading space-before >= space-after -->
<!-- - Color contrast ratio between text and background -->
<!-- - Consistent font family within heading vs body groups -->
<!-- - Line spacing and font size harmony (larger text needs tighter spacing) -->
<!-- -->
<!-- MIT License - minimax-docx project -->
<!-- ============================================================================ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- ============================================================ -->
<!-- RULE 1: Body Font Size Range -->
<!-- ============================================================ -->
<!-- Body text must be 10-14pt (half-points: 20-28). -->
<!-- WHY: Below 10pt is hard to read for most adults. -->
<!-- Above 14pt body text looks childish or wasteful. -->
<!-- The sweet spot is 10.5-12pt for most font families. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticBodyFontSize">
<xs:annotation>
<xs:documentation>
Body text font size in half-points.
Acceptable range: 20-28 (10pt-14pt).
- 10pt (20): minimum for comfortable reading
- 11pt (22): modern default (Calibri, Aptos)
- 12pt (24): traditional default (Times New Roman)
- 14pt (28): maximum before body text looks oversized
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="20"/> <!-- 10pt minimum -->
<xs:maxInclusive value="28"/> <!-- 14pt maximum -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 2: Heading Font Size Range -->
<!-- ============================================================ -->
<!-- Headings must be 12-26pt (half-points: 24-52). -->
<!-- WHY: Below 12pt, a heading cannot be visually distinguished -->
<!-- from body text by size alone. -->
<!-- Above 26pt is poster-sized and wastes vertical space. -->
<!-- NOTE: Some academic styles use 12pt headings (same as body) -->
<!-- and differentiate via bold/italic/centering instead. -->
<!-- The lower bound of 24 (12pt) accommodates this. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticHeadingFontSize">
<xs:annotation>
<xs:documentation>
Heading font size in half-points.
Acceptable range: 24-52 (12pt-26pt).
- 12pt (24): APA-style (hierarchy via bold/italic, not size)
- 16pt (32): typical H2/H3
- 20pt (40): typical H1
- 26pt (52): maximum before headings dominate the page
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="24"/> <!-- 12pt minimum -->
<xs:maxInclusive value="52"/> <!-- 26pt maximum -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 3: Line Spacing Range -->
<!-- ============================================================ -->
<!-- Line spacing (in auto mode) must be 240-560 (1.0x-2.33x). -->
<!-- WHY: Below 1.0x, ascenders/descenders overlap — unreadable. -->
<!-- Above 2.33x, lines appear disconnected. -->
<!-- Sweet spots: 1.15x (276) for sans, 1.5x (360) for -->
<!-- generous layouts, 2.0x (480) for academic. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticLineSpacing">
<xs:annotation>
<xs:documentation>
Line spacing value for auto line-spacing rule.
In 240ths of single spacing: 240 = 1.0x, 480 = 2.0x.
Acceptable range: 240-560 (1.0x to 2.33x).
Common values:
- 240: single spacing (dense, technical)
- 259: Word's 1.08x default
- 276: 1.15x (modern corporate default)
- 336: 1.4x (executive/generous)
- 360: 1.5x (generous/minimal)
- 480: 2.0x (academic double spacing)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="240"/> <!-- 1.0x single spacing -->
<xs:maxInclusive value="560"/> <!-- ~2.33x — beyond double feels disconnected -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 3b: Fixed Line Spacing Range -->
<!-- ============================================================ -->
<!-- For lineRule="exact", line value is in DXA (twentieths of pt) -->
<!-- Range: 200-720 DXA (10pt-36pt fixed line height) -->
<!-- Chinese government standard uses 560 DXA (28pt). -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticFixedLineSpacing">
<xs:annotation>
<xs:documentation>
Fixed line spacing value (lineRule="exact") in DXA.
Acceptable range: 200-720 (10pt-36pt).
- 560: Chinese government standard (28pt, for 16pt body)
- 480: double-space equivalent for 12pt body
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="200"/> <!-- 10pt minimum fixed height -->
<xs:maxInclusive value="720"/> <!-- 36pt maximum fixed height -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 4: Margin Minimums -->
<!-- ============================================================ -->
<!-- All margins must be at least 720 DXA (0.5 inch). -->
<!-- WHY: Below 0.5in, most printers clip content. -->
<!-- Also, narrow margins create a cramped, unprofessional -->
<!-- appearance. Even "full bleed" designs need internal -->
<!-- text margins. -->
<!-- Max set to 4320 DXA (3 inches) to prevent absurd margins. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticMargin">
<xs:annotation>
<xs:documentation>
Page margin in DXA. Minimum 720 (0.5 inch), maximum 4320 (3 inches).
Common values:
- 720: 0.5in (minimum printable)
- 1440: 1.0in (standard US)
- 1588: 28mm (Chinese government left margin)
- 1800: 1.25in (executive/premium)
- 2160: 1.5in (binding margin or narrow-column design)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="720"/> <!-- 0.5in — minimum for print safety -->
<xs:maxInclusive value="4320"/> <!-- 3in — beyond this is absurd -->
</xs:restriction>
</xs:simpleType>
<!-- Top/bottom margins: signed because negative values can create -->
<!-- overlap effects, but we still enforce a reasonable minimum. -->
<xs:simpleType name="ST_AestheticVerticalMargin">
<xs:annotation>
<xs:documentation>
Vertical (top/bottom) page margin in DXA.
Range: 360 to 4320 (0.25in to 3in).
Slightly more permissive than horizontal margins because
header/footer areas may reduce effective vertical margin.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:integer">
<xs:minInclusive value="360"/> <!-- 0.25in — tighter vertical is sometimes acceptable -->
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 5: Paragraph Spacing Ranges -->
<!-- ============================================================ -->
<!-- Space before/after paragraphs should be 0-960 DXA (0-48pt). -->
<!-- WHY: More than 48pt of space before/after creates awkward -->
<!-- gaps that disrupt reading flow. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticParaSpacing">
<xs:annotation>
<xs:documentation>
Paragraph spacing (before/after) in DXA.
Range: 0-960 (0pt-48pt).
Common values:
- 0: academic style (uses first-line indent instead)
- 80: 4pt (tight, used after H2/H3)
- 120: 6pt (moderate)
- 160: 8pt (standard modern spacing)
- 200: 10pt (generous/executive)
- 240: 12pt (very generous/minimal)
- 480: 24pt (heading before — creates section break)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="960"/> <!-- 48pt max — beyond this is a page break, not spacing -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 6: Table Cell Padding Minimum -->
<!-- ============================================================ -->
<!-- Table cells need at least 28 DXA (~1.4pt) padding. -->
<!-- WHY: Without padding, text touches cell borders — visually -->
<!-- cramped and hard to read. Even borderless tables need -->
<!-- padding for column separation. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticCellPadding">
<xs:annotation>
<xs:documentation>
Table cell padding in DXA. Minimum 28 DXA (~1.4pt).
Recommended: 57 DXA (~2.85pt) for comfortable spacing.
Maximum: 288 DXA (~14pt) — beyond this wastes space.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="28"/> <!-- ~1.4pt minimum breathing room -->
<xs:maxInclusive value="288"/> <!-- ~14pt — more than this is excessive -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 7: Border Size Range -->
<!-- ============================================================ -->
<!-- Border size (in eighth-points) should be 2-24 (0.25pt-3pt). -->
<!-- WHY: Below 0.25pt borders may not render or print. -->
<!-- Above 3pt borders look heavy and distracting. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticBorderSize">
<xs:annotation>
<xs:documentation>
Border width in eighth-points.
Range: 2-24 (0.25pt to 3pt).
Common values:
- 4: 0.5pt (thin, standard)
- 6: 0.75pt (header separator in three-line tables)
- 8: 1.0pt (medium, good for framing borders)
- 12: 1.5pt (heavy, used for top/bottom in three-line tables)
- 24: 3.0pt (maximum before borders dominate)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="2"/> <!-- 0.25pt minimum visible -->
<xs:maxInclusive value="24"/> <!-- 3pt maximum tasteful -->
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 8: Color Value Format -->
<!-- ============================================================ -->
<!-- Colors must be valid 6-digit hex (RRGGBB) or "auto". -->
<!-- This is structural validation, not aesthetic validation. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticColor">
<xs:annotation>
<xs:documentation>
Color value: 6-digit hex (RRGGBB) or "auto".
Examples: "000000", "1F3864", "2C3E50", "auto".
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9A-Fa-f]{6}|auto"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- RULE 9: First-Line Indent Range -->
<!-- ============================================================ -->
<!-- If first-line indent is used, it should be 360-1440 DXA -->
<!-- (0.25in - 1.0in). -->
<!-- WHY: Below 0.25in the indent is barely visible. -->
<!-- Above 1.0in the indent looks like a tab error. -->
<!-- ============================================================ -->
<xs:simpleType name="ST_AestheticFirstLineIndent">
<xs:annotation>
<xs:documentation>
First-line indent in DXA. Range: 0-1440 (0in to 1.0in).
- 0: no indent (modern style with space-after)
- 480: 0.33in (compact)
- 640: ~0.44in (2 Chinese characters at 16pt)
- 720: 0.5in (standard APA/academic)
- 1440: 1.0in (maximum before it looks wrong)
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="1440"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Run Properties Check -->
<!-- ============================================================ -->
<!-- Validates run-level properties for aesthetic compliance. -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticRPr">
<xs:annotation>
<xs:documentation>
Aesthetic run properties validator.
Checks font size and color format at the run level.
</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticBodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticBodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="color" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_AestheticColor" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Spacing Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticSpacing">
<xs:annotation>
<xs:documentation>
Aesthetic spacing validator for paragraph spacing properties.
Validates line spacing and before/after spacing are in range.
</xs:documentation>
</xs:annotation>
<xs:attribute name="line" type="w:ST_AestheticLineSpacing" use="optional"/>
<xs:attribute name="before" type="w:ST_AestheticParaSpacing" use="optional"/>
<xs:attribute name="after" type="w:ST_AestheticParaSpacing" use="optional"/>
<xs:attribute name="lineRule" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="auto"/>
<xs:enumeration value="exact"/>
<xs:enumeration value="atLeast"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Page Margins Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticPageMargins">
<xs:annotation>
<xs:documentation>
Aesthetic page margin validator.
Ensures all margins meet minimum print-safe thresholds.
</xs:documentation>
</xs:annotation>
<xs:attribute name="top" type="w:ST_AestheticVerticalMargin" use="required"/>
<xs:attribute name="bottom" type="w:ST_AestheticVerticalMargin" use="required"/>
<xs:attribute name="left" type="w:ST_AestheticMargin" use="required"/>
<xs:attribute name="right" type="w:ST_AestheticMargin" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- COMPOSITE TYPE: Aesthetic Table Cell Margin Check -->
<!-- ============================================================ -->
<xs:complexType name="CT_AestheticTableCellMargin">
<xs:annotation>
<xs:documentation>
Aesthetic table cell margin validator.
Ensures minimum padding for readability.
</xs:documentation>
</xs:annotation>
<xs:attribute name="w" type="w:ST_AestheticCellPadding" use="required"/>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="dxa"/>
<xs:enumeration value="nil"/>
<xs:enumeration value="pct"/>
<xs:enumeration value="auto"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- ============================================================ -->
<!-- PROGRAMMATIC RULES (cannot be expressed in XSD) -->
<!-- ============================================================ -->
<!-- -->
<!-- The following rules must be checked by a programmatic -->
<!-- validator (e.g., AestheticRuleValidator.cs). They are -->
<!-- documented here for completeness. -->
<!-- -->
<!-- ── RULE P1: Heading Size Hierarchy ── -->
<!-- H1 sz >= H2 sz >= H3 sz >= body sz -->
<!-- Exception: APA-style where all headings = body size. -->
<!-- Implementation: Collect sz from Heading1/2/3 styles and -->
<!-- docDefaults. Verify monotonic decrease (or equality). -->
<!-- -->
<!-- ── RULE P2: Maximum 3 Font Families ── -->
<!-- Across docDefaults rPr + all style rPr, at most 3 distinct -->
<!-- font families (by Ascii name) should be used. -->
<!-- WHY: More than 3 fonts creates visual chaos. Professional -->
<!-- designs typically use 1-2 families. -->
<!-- Implementation: Collect all rFonts.ascii values from -->
<!-- docDefaults and all styles. Count distinct. Warn if > 3. -->
<!-- -->
<!-- ── RULE P3: Heading Space-Before >= Space-After ── -->
<!-- For heading styles, spaceBefore should be >= spaceAfter. -->
<!-- WHY: Headings should be visually closer to the content they -->
<!-- introduce than to the content above. This is the -->
<!-- "proximity principle" of Gestalt design. -->
<!-- Implementation: For each Heading style, compare pPr spacing -->
<!-- before vs after values. -->
<!-- -->
<!-- ── RULE P4: Spacing-Size Coherence ── -->
<!-- Paragraph after-spacing should be proportional to body size: -->
<!-- after >= bodySize * 0.5 AND after <= bodySize * 1.5 -->
<!-- WHY: Too little spacing makes paragraphs run together. -->
<!-- Too much spacing disconnects them. -->
<!-- Implementation: Get body sz from docDefaults, convert to DXA -->
<!-- (multiply by 10), check after-spacing ratio. -->
<!-- -->
<!-- ── RULE P5: Color Consistency ── -->
<!-- All heading styles should use the same color value. -->
<!-- Body text color (if set) should be consistent across styles. -->
<!-- WHY: Inconsistent colors look accidental, not designed. -->
<!-- Exception: Caption and footnote styles may differ. -->
<!-- Implementation: Collect color.val from heading styles. -->
<!-- Verify all are identical. -->
<!-- -->
<!-- ── RULE P6: Indent/Spacing Mutual Exclusion ── -->
<!-- If first-line indent > 0 in docDefaults, then after-spacing -->
<!-- should be 0 (and vice versa). Using BOTH indent AND spacing -->
<!-- is visually redundant — it signals uncertainty. -->
<!-- Exception: Headings may override this. -->
<!-- Implementation: Check docDefaults pPr. If firstLine > 0 AND -->
<!-- after > 0, emit a warning (not error). -->
<!-- -->
<!-- ── RULE P7: Table Border Consistency ── -->
<!-- Within a single table, border styles should be internally -->
<!-- consistent (all single, or all none — not a random mix). -->
<!-- Implementation: Check tblBorders for consistent val values. -->
<!-- -->
<!-- ── RULE P8: Line Spacing vs Font Size Harmony ── -->
<!-- For fixed line spacing (lineRule="exact"): -->
<!-- lineHeight >= fontSize * 1.2 -->
<!-- WHY: Fixed line spacing less than 1.2x the font size causes -->
<!-- ascender/descender clipping. -->
<!-- Implementation: When lineRule="exact", compare line value -->
<!-- against the effective font size. -->
<!-- -->
<!-- ============================================================ -->
</xs:schema>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Business Rules Gate-Check Schema for minimax-docx -->
<!-- Used in Scenario C (template application) as hard gate -->
<!-- Validates business compliance beyond XML correctness -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- ============================================================ -->
<!-- Page margins: constrained to reasonable bounds -->
<!-- Minimum 360 DXA (0.25 inch), maximum 4320 DXA (3 inches) -->
<!-- ============================================================ -->
<xs:simpleType name="ST_MarginMeasure">
<xs:restriction base="xs:integer">
<xs:minInclusive value="360"/>
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- Signed margin (top/bottom can be negative for overlap) -->
<xs:simpleType name="ST_SignedMarginMeasure">
<xs:restriction base="xs:integer">
<xs:minInclusive value="-4320"/>
<xs:maxInclusive value="4320"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Font size constraints -->
<!-- Body text: 16-144 half-points (8-72pt) -->
<!-- Heading text: 20-192 half-points (10-96pt) -->
<!-- ============================================================ -->
<xs:simpleType name="ST_BodyFontSize">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="16"/>
<xs:maxInclusive value="144"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_HeadingFontSize">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="20"/>
<xs:maxInclusive value="192"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Valid standard page sizes (width x height in DXA) -->
<!-- ============================================================ -->
<!-- Letter: 12240 x 15840 -->
<!-- A4: 11906 x 16838 -->
<!-- Legal: 12240 x 20160 -->
<!-- A3: 16838 x 23811 -->
<!-- A5: 8391 x 11906 -->
<xs:simpleType name="ST_PageWidth">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="5040"/>
<xs:maxInclusive value="31680"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ST_PageHeight">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="5040"/>
<xs:maxInclusive value="31680"/>
</xs:restriction>
</xs:simpleType>
<!-- ============================================================ -->
<!-- Constrained section properties for gate-check -->
<!-- ============================================================ -->
<xs:complexType name="CT_GateCheckSectPr">
<xs:all>
<xs:element name="pgSz" minOccurs="1">
<xs:complexType>
<xs:attribute name="w" type="w:ST_PageWidth" use="required"/>
<xs:attribute name="h" type="w:ST_PageHeight" use="required"/>
<xs:attribute name="orient" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="portrait"/>
<xs:enumeration value="landscape"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="pgMar" minOccurs="1">
<xs:complexType>
<xs:attribute name="top" type="w:ST_SignedMarginMeasure" use="required"/>
<xs:attribute name="bottom" type="w:ST_SignedMarginMeasure" use="required"/>
<xs:attribute name="left" type="w:ST_MarginMeasure" use="required"/>
<xs:attribute name="right" type="w:ST_MarginMeasure" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Required styles: at minimum Normal and Heading1 must exist -->
<!-- This is enforced programmatically by GateCheckValidator -->
<!-- rather than via XSD, since XSD cannot validate style presence -->
<!-- across separate XML parts. -->
<!-- ============================================================ -->
<!-- ============================================================ -->
<!-- Constrained run properties for font size validation -->
<!-- ============================================================ -->
<xs:complexType name="CT_GateCheckRPr">
<xs:all>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_BodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="w:ST_BodyFontSize" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Common type definitions for WordprocessingML subset schema -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<!-- Measurement: non-negative twips (1/1440 inch) -->
<xs:simpleType name="ST_TwipsMeasure">
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<!-- Measurement: signed twips (for negative margins/indents) -->
<xs:simpleType name="ST_SignedTwipsMeasure">
<xs:restriction base="xs:integer"/>
</xs:simpleType>
<!-- Half-point measure for font sizes (1 = 0.5pt) -->
<xs:simpleType name="ST_HpsMeasure">
<xs:restriction base="xs:positiveInteger"/>
</xs:simpleType>
<!-- Hex color: 6 hex digits -->
<xs:simpleType name="ST_HexColor">
<xs:restriction base="xs:string">
<xs:pattern value="auto|[0-9a-fA-F]{6}"/>
</xs:restriction>
</xs:simpleType>
<!-- On/Off toggle -->
<xs:simpleType name="ST_OnOff">
<xs:restriction base="xs:string">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
<xs:enumeration value="0"/>
<xs:enumeration value="1"/>
</xs:restriction>
</xs:simpleType>
<!-- Justification -->
<xs:simpleType name="ST_Jc">
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="center"/>
<xs:enumeration value="right"/>
<xs:enumeration value="both"/>
<xs:enumeration value="distribute"/>
<xs:enumeration value="start"/>
<xs:enumeration value="end"/>
</xs:restriction>
</xs:simpleType>
<!-- Break type -->
<xs:simpleType name="ST_BrType">
<xs:restriction base="xs:string">
<xs:enumeration value="page"/>
<xs:enumeration value="column"/>
<xs:enumeration value="textWrapping"/>
</xs:restriction>
</xs:simpleType>
<!-- Underline patterns -->
<xs:simpleType name="ST_Underline">
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="single"/>
<xs:enumeration value="words"/>
<xs:enumeration value="double"/>
<xs:enumeration value="thick"/>
<xs:enumeration value="dotted"/>
<xs:enumeration value="dash"/>
<xs:enumeration value="dotDash"/>
<xs:enumeration value="dotDotDash"/>
<xs:enumeration value="wave"/>
</xs:restriction>
</xs:simpleType>
<!-- Vertical alignment for subscript/superscript -->
<xs:simpleType name="ST_VerticalAlignRun">
<xs:restriction base="xs:string">
<xs:enumeration value="baseline"/>
<xs:enumeration value="superscript"/>
<xs:enumeration value="subscript"/>
</xs:restriction>
</xs:simpleType>
<!-- Section break type -->
<xs:simpleType name="ST_SectionMark">
<xs:restriction base="xs:string">
<xs:enumeration value="nextPage"/>
<xs:enumeration value="nextColumn"/>
<xs:enumeration value="continuous"/>
<xs:enumeration value="evenPage"/>
<xs:enumeration value="oddPage"/>
</xs:restriction>
</xs:simpleType>
<!-- Header/footer type -->
<xs:simpleType name="ST_HdrFtr">
<xs:restriction base="xs:string">
<xs:enumeration value="even"/>
<xs:enumeration value="default"/>
<xs:enumeration value="first"/>
</xs:restriction>
</xs:simpleType>
<!-- Table width type -->
<xs:simpleType name="ST_TblWidth">
<xs:restriction base="xs:string">
<xs:enumeration value="auto"/>
<xs:enumeration value="dxa"/>
<xs:enumeration value="nil"/>
<xs:enumeration value="pct"/>
</xs:restriction>
</xs:simpleType>
<!-- Vertical merge -->
<xs:simpleType name="ST_Merge">
<xs:restriction base="xs:string">
<xs:enumeration value="continue"/>
<xs:enumeration value="restart"/>
</xs:restriction>
</xs:simpleType>
<!-- Highlight colors -->
<xs:simpleType name="ST_HighlightColor">
<xs:restriction base="xs:string">
<xs:enumeration value="black"/>
<xs:enumeration value="blue"/>
<xs:enumeration value="cyan"/>
<xs:enumeration value="darkBlue"/>
<xs:enumeration value="darkCyan"/>
<xs:enumeration value="darkGray"/>
<xs:enumeration value="darkGreen"/>
<xs:enumeration value="darkMagenta"/>
<xs:enumeration value="darkRed"/>
<xs:enumeration value="darkYellow"/>
<xs:enumeration value="green"/>
<xs:enumeration value="lightGray"/>
<xs:enumeration value="magenta"/>
<xs:enumeration value="none"/>
<xs:enumeration value="red"/>
<xs:enumeration value="white"/>
<xs:enumeration value="yellow"/>
</xs:restriction>
</xs:simpleType>
<!-- Percentage (for table width pct, etc.) -->
<xs:simpleType name="ST_DecimalNumber">
<xs:restriction base="xs:integer"/>
</xs:simpleType>
<!-- Relationship ID reference -->
<xs:simpleType name="ST_RelationshipId">
<xs:restriction base="xs:string"/>
</xs:simpleType>
</xs:schema>
@@ -0,0 +1,589 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- WordprocessingML Subset Schema for minimax-docx -->
<!-- Curated subset of ISO 29500 covering elements agents commonly generate -->
<!-- MIT License - minimax-docx project -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
targetNamespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
elementFormDefault="qualified">
<xs:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
<xs:import namespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"/>
<!-- ============================================================ -->
<!-- Root element -->
<!-- ============================================================ -->
<xs:element name="document" type="w:CT_Document"/>
<xs:complexType name="CT_Document">
<xs:sequence>
<xs:element name="body" type="w:CT_Body" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Body -->
<!-- ============================================================ -->
<xs:complexType name="CT_Body">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
<xs:element name="sdt" type="w:CT_SdtBlock"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
<xs:element name="sectPr" type="w:CT_SectPr" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Paragraph -->
<!-- ============================================================ -->
<xs:complexType name="CT_P">
<xs:sequence>
<xs:element name="pPr" type="w:CT_PPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="r" type="w:CT_R"/>
<xs:element name="hyperlink" type="w:CT_Hyperlink"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
<xs:element name="commentRangeStart" type="w:CT_MarkupRange"/>
<xs:element name="commentRangeEnd" type="w:CT_MarkupRange"/>
<xs:element name="ins" type="w:CT_RunTrackChange"/>
<xs:element name="del" type="w:CT_RunTrackChange"/>
</xs:choice>
</xs:sequence>
<xs:attribute ref="r:id" use="optional"/>
</xs:complexType>
<!-- Paragraph Properties -->
<xs:complexType name="CT_PPr">
<xs:all>
<xs:element name="pStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="keepNext" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="keepLines" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="pageBreakBefore" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="widowControl" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="numPr" type="w:CT_NumPr" minOccurs="0"/>
<xs:element name="spacing" type="w:CT_Spacing" minOccurs="0"/>
<xs:element name="ind" type="w:CT_Ind" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="outlineLvl" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="rPr" type="w:CT_RPr" minOccurs="0"/>
<xs:element name="pBdr" type="w:CT_PBdr" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="tabs" type="w:CT_Tabs" minOccurs="0"/>
<xs:element name="sectPr" type="w:CT_SectPr" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Run -->
<!-- ============================================================ -->
<xs:complexType name="CT_R">
<xs:sequence>
<xs:element name="rPr" type="w:CT_RPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="t" type="w:CT_Text"/>
<xs:element name="delText" type="w:CT_Text"/>
<xs:element name="br" type="w:CT_Br"/>
<xs:element name="tab" type="w:CT_Empty"/>
<xs:element name="cr" type="w:CT_Empty"/>
<xs:element name="drawing" type="w:CT_Drawing"/>
<xs:element name="commentReference" type="w:CT_MarkupRef"/>
<xs:element name="footnoteReference" type="w:CT_FtnEdnRef"/>
<xs:element name="endnoteReference" type="w:CT_FtnEdnRef"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<!-- Run Properties -->
<xs:complexType name="CT_RPr">
<xs:all>
<xs:element name="rStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="rFonts" type="w:CT_Fonts" minOccurs="0"/>
<xs:element name="b" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="bCs" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="i" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="iCs" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="caps" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="smallCaps" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="strike" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="dstrike" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="vanish" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="color" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="themeColor" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="spacing" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="sz" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="szCs" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="highlight" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="u" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="vertAlign" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="lang" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="eastAsia" type="xs:string" use="optional"/>
<xs:attribute name="bidi" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Text -->
<!-- ============================================================ -->
<xs:complexType name="CT_Text" mixed="true">
<xs:attribute ref="xml:space" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Table -->
<!-- ============================================================ -->
<xs:complexType name="CT_Tbl">
<xs:sequence>
<xs:element name="tblPr" type="w:CT_TblPr" minOccurs="0"/>
<xs:element name="tblGrid" type="w:CT_TblGrid" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tr" type="w:CT_Row"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TblPr">
<xs:all>
<xs:element name="tblStyle" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblW" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblInd" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="tblBorders" type="w:CT_TblBorders" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="tblLayout" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="tblCellMar" type="w:CT_TblCellMar" minOccurs="0"/>
<xs:element name="tblLook" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="firstRow" type="xs:string" use="optional"/>
<xs:attribute name="lastRow" type="xs:string" use="optional"/>
<xs:attribute name="firstColumn" type="xs:string" use="optional"/>
<xs:attribute name="lastColumn" type="xs:string" use="optional"/>
<xs:attribute name="noHBand" type="xs:string" use="optional"/>
<xs:attribute name="noVBand" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblGrid">
<xs:sequence>
<xs:element name="gridCol" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="w" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_Row">
<xs:sequence>
<xs:element name="trPr" type="w:CT_TrPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="tc" type="w:CT_Cell"/>
<xs:element name="bookmarkStart" type="w:CT_BookmarkStart"/>
<xs:element name="bookmarkEnd" type="w:CT_BookmarkEnd"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TrPr">
<xs:all>
<xs:element name="trHeight" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="hRule" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="tblHeader" type="w:CT_OnOff" minOccurs="0"/>
<xs:element name="jc" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Cell">
<xs:sequence>
<xs:element name="tcPr" type="w:CT_TcPr" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CT_TcPr">
<xs:all>
<xs:element name="tcW" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="gridSpan" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:positiveInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="vMerge" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="tcBorders" type="w:CT_TcBorders" minOccurs="0"/>
<xs:element name="shd" type="w:CT_Shd" minOccurs="0"/>
<xs:element name="vAlign" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="noWrap" type="w:CT_OnOff" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Section Properties -->
<!-- ============================================================ -->
<xs:complexType name="CT_SectPr">
<xs:all>
<xs:element name="headerReference" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute ref="r:id" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="footerReference" minOccurs="0">
<xs:complexType>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute ref="r:id" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="type" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="pgSz" minOccurs="0">
<xs:complexType>
<xs:attribute name="w" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="h" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="orient" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="pgMar" minOccurs="0">
<xs:complexType>
<xs:attribute name="top" type="xs:integer" use="required"/>
<xs:attribute name="right" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="bottom" type="xs:integer" use="required"/>
<xs:attribute name="left" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="header" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="footer" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="gutter" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="pgNumType" minOccurs="0">
<xs:complexType>
<xs:attribute name="fmt" type="xs:string" use="optional"/>
<xs:attribute name="start" type="xs:nonNegativeInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="cols" minOccurs="0">
<xs:complexType>
<xs:attribute name="space" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="num" type="xs:positiveInteger" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="docGrid" minOccurs="0">
<xs:complexType>
<xs:attribute name="linePitch" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="type" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="titlePg" type="w:CT_OnOff" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- ============================================================ -->
<!-- Hyperlink -->
<!-- ============================================================ -->
<xs:complexType name="CT_Hyperlink">
<xs:sequence>
<xs:element name="r" type="w:CT_R" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="r:id" use="optional"/>
<xs:attribute name="anchor" type="xs:string" use="optional"/>
<xs:attribute name="history" type="xs:string" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Track Changes -->
<!-- ============================================================ -->
<xs:complexType name="CT_RunTrackChange">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="r" type="w:CT_R"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="author" type="xs:string" use="required"/>
<xs:attribute name="date" type="xs:dateTime" use="optional"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Bookmarks -->
<!-- ============================================================ -->
<xs:complexType name="CT_BookmarkStart">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="CT_BookmarkEnd">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Comments -->
<!-- ============================================================ -->
<xs:complexType name="CT_MarkupRange">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<xs:complexType name="CT_MarkupRef">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Footnote/Endnote reference -->
<!-- ============================================================ -->
<xs:complexType name="CT_FtnEdnRef">
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
<!-- ============================================================ -->
<!-- Drawing (basic inline image) -->
<!-- ============================================================ -->
<xs:complexType name="CT_Drawing">
<xs:sequence>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Structured Document Tag (content control) -->
<!-- ============================================================ -->
<xs:complexType name="CT_SdtBlock">
<xs:sequence>
<xs:element name="sdtPr" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="sdtContent" minOccurs="0">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="p" type="w:CT_P"/>
<xs:element name="tbl" type="w:CT_Tbl"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<!-- ============================================================ -->
<!-- Helper types -->
<!-- ============================================================ -->
<xs:complexType name="CT_OnOff">
<xs:attribute name="val" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Empty"/>
<xs:complexType name="CT_Br">
<xs:attribute name="type" type="xs:string" use="optional"/>
<xs:attribute name="clear" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Fonts">
<xs:attribute name="ascii" type="xs:string" use="optional"/>
<xs:attribute name="hAnsi" type="xs:string" use="optional"/>
<xs:attribute name="eastAsia" type="xs:string" use="optional"/>
<xs:attribute name="cs" type="xs:string" use="optional"/>
<xs:attribute name="asciiTheme" type="xs:string" use="optional"/>
<xs:attribute name="hAnsiTheme" type="xs:string" use="optional"/>
<xs:attribute name="eastAsiaTheme" type="xs:string" use="optional"/>
<xs:attribute name="cstheme" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_NumPr">
<xs:all>
<xs:element name="ilvl" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="numId" minOccurs="0">
<xs:complexType>
<xs:attribute name="val" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Spacing">
<xs:attribute name="before" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="after" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="line" type="xs:integer" use="optional"/>
<xs:attribute name="lineRule" type="xs:string" use="optional"/>
<xs:attribute name="beforeAutospacing" type="xs:string" use="optional"/>
<xs:attribute name="afterAutospacing" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Ind">
<xs:attribute name="left" type="xs:integer" use="optional"/>
<xs:attribute name="right" type="xs:integer" use="optional"/>
<xs:attribute name="hanging" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="firstLine" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="start" type="xs:integer" use="optional"/>
<xs:attribute name="end" type="xs:integer" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_TblWidth">
<xs:attribute name="w" type="xs:string" use="optional"/>
<xs:attribute name="type" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Shd">
<xs:attribute name="val" type="xs:string" use="optional"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
<xs:attribute name="fill" type="xs:string" use="optional"/>
<xs:attribute name="themeFill" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_Border">
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="sz" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="space" type="xs:nonNegativeInteger" use="optional"/>
<xs:attribute name="color" type="xs:string" use="optional"/>
<xs:attribute name="themeColor" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="CT_PBdr">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
<xs:element name="between" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblBorders">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
<xs:element name="insideH" type="w:CT_Border" minOccurs="0"/>
<xs:element name="insideV" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TcBorders">
<xs:all>
<xs:element name="top" type="w:CT_Border" minOccurs="0"/>
<xs:element name="left" type="w:CT_Border" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_Border" minOccurs="0"/>
<xs:element name="right" type="w:CT_Border" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_TblCellMar">
<xs:all>
<xs:element name="top" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="left" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="bottom" type="w:CT_TblWidth" minOccurs="0"/>
<xs:element name="right" type="w:CT_TblWidth" minOccurs="0"/>
</xs:all>
</xs:complexType>
<xs:complexType name="CT_Tabs">
<xs:sequence>
<xs:element name="tab" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="val" type="xs:string" use="required"/>
<xs:attribute name="pos" type="xs:integer" use="required"/>
<xs:attribute name="leader" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <file.doc> [output_directory]"
echo "Convert .doc to .docx using LibreOffice."
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
INPUT="$1"
OUTDIR="${2:-.}"
if [ ! -f "$INPUT" ]; then
echo "Error: File not found: $INPUT"
exit 1
fi
if ! command -v soffice &>/dev/null; then
echo "Error: soffice (LibreOffice) is required for .doc conversion but not found."
echo "Install LibreOffice: brew install --cask libreoffice"
exit 1
fi
BASENAME=$(basename "$INPUT" .doc)
mkdir -p "$OUTDIR"
echo "Converting: $INPUT -> $OUTDIR/$BASENAME.docx"
soffice --headless --convert-to docx --outdir "$OUTDIR" "$INPUT" >/dev/null 2>&1
OUTPUT="$OUTDIR/$BASENAME.docx"
if [ ! -f "$OUTPUT" ]; then
echo "Error: Conversion failed. Output file not created: $OUTPUT"
exit 1
fi
echo "Success: $OUTPUT"
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $(basename "$0") <file.docx>"
echo "Preview DOCX content as plain text."
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
INPUT="$1"
if [ ! -f "$INPUT" ]; then
echo "Error: File not found: $INPUT"
exit 1
fi
FILE_SIZE=$(du -h "$INPUT" | cut -f1)
echo "=== DOCX Preview: $(basename "$INPUT") ==="
echo "File size: $FILE_SIZE"
if command -v pandoc &>/dev/null; then
CONTENT=$(pandoc -f docx -t plain "$INPUT" 2>/dev/null)
WORD_COUNT=$(echo "$CONTENT" | wc -w | tr -d ' ')
EST_PAGES=$(( (WORD_COUNT + 249) / 250 ))
echo "Word count: $WORD_COUNT"
echo "Estimated pages: $EST_PAGES"
echo "---"
echo "$CONTENT"
else
echo "(pandoc not available, falling back to raw XML extract)"
echo "---"
unzip -p "$INPUT" word/document.xml 2>/dev/null | head -100
fi
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\MiniMaxAIDocx.Core\MiniMaxAIDocx.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.5" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
</Project>
@@ -0,0 +1,18 @@
using System.CommandLine;
using MiniMaxAIDocx.Core.Commands;
var rootCommand = new RootCommand("minimax-docx: OpenXML document generation and manipulation CLI");
// Scenario commands
rootCommand.Add(CreateCommand.Create());
rootCommand.Add(EditContentCommand.Create());
rootCommand.Add(ApplyTemplateCommand.Create());
// Tool commands
rootCommand.Add(ValidateCommand.Create());
rootCommand.Add(MergeRunsCommand.Create());
rootCommand.Add(FixOrderCommand.Create());
rootCommand.Add(AnalyzeCommand.Create());
rootCommand.Add(DiffCommand.Create());
return rootCommand.Parse(args).Invoke();
@@ -0,0 +1,147 @@
using System.CommandLine;
using System.IO.Compression;
using System.Text.Json;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class AnalyzeCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
private static readonly XNamespace WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to analyze", Required = true };
var jsonOption = new Option<bool>("--json") { Description = "Output as JSON" };
var cmd = new Command("analyze", "Analyze document structure and styles")
{
inputOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
using var zip = ZipFile.OpenRead(input);
var docEntry = zip.GetEntry("word/document.xml");
if (docEntry == null)
{
Console.Error.WriteLine("Not a valid DOCX");
return;
}
XDocument doc;
using (var stream = docEntry.Open())
doc = XDocument.Load(stream);
var body = doc.Root?.Element(W + "body");
if (body == null) return;
// Sections
var sections = body.Descendants(W + "sectPr").ToList();
var sectionBreaks = sections.Select(s => (string?)s.Element(W + "type")?.Attribute(W + "val") ?? "nextPage").ToList();
// Headings
var headings = new List<object>();
foreach (var p in body.Descendants(W + "p"))
{
var style = (string?)p.Element(W + "pPr")?.Element(W + "pStyle")?.Attribute(W + "val");
if (style?.StartsWith("Heading", StringComparison.OrdinalIgnoreCase) == true)
{
var text = string.Concat(p.Descendants(W + "t").Select(t => t.Value));
headings.Add(new { style, text });
}
}
// Tables
var tables = body.Descendants(W + "tbl").Select(tbl => new
{
rows = tbl.Elements(W + "tr").Count(),
cols = tbl.Elements(W + "tr").FirstOrDefault()?.Elements(W + "tc").Count() ?? 0
}).ToList();
// Images
var images = body.Descendants(W + "drawing").Count();
// Headers/footers
var headerRefs = sections.SelectMany(s => s.Elements(W + "headerReference")).Count();
var footerRefs = sections.SelectMany(s => s.Elements(W + "footerReference")).Count();
// Paragraphs and word count
var paragraphs = body.Descendants(W + "p").ToList();
var allText = string.Concat(body.Descendants(W + "t").Select(t => t.Value));
var wordCount = allText.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).Length;
// XML file sizes
var fileSizes = zip.Entries
.Where(e => e.FullName.StartsWith("word/") && e.FullName.EndsWith(".xml"))
.Select(e => new { file = e.FullName, size = e.Length })
.OrderByDescending(e => e.size)
.ToList();
// Styles
var styleNames = new List<string>();
var stylesEntry = zip.GetEntry("word/styles.xml");
if (stylesEntry != null)
{
using var stream = stylesEntry.Open();
var stylesDoc = XDocument.Load(stream);
styleNames = stylesDoc.Descendants(W + "style")
.Where(s => (string?)s.Attribute(W + "customStyle") == "1")
.Select(s => (string?)s.Attribute(W + "styleId") ?? "")
.Where(s => s != "")
.ToList();
}
var analysis = new
{
sections = new { count = sections.Count, breakTypes = sectionBreaks },
headings,
tables = new { count = tables.Count, details = tables },
images,
headerFooter = new { headers = headerRefs, footers = footerRefs },
paragraphs = paragraphs.Count,
estimatedWordCount = wordCount,
xmlFileSizes = fileSizes,
customStyles = new { count = styleNames.Count, names = styleNames }
};
if (asJson)
{
Console.WriteLine(JsonSerializer.Serialize(analysis, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
Console.WriteLine($"Sections: {sections.Count} ({string.Join(", ", sectionBreaks)})");
Console.WriteLine($"Headings: {headings.Count}");
foreach (var h in headings)
Console.WriteLine($" {h}");
Console.WriteLine($"Tables: {tables.Count}");
foreach (var t in tables)
Console.WriteLine($" {t.rows} rows x {t.cols} cols");
Console.WriteLine($"Images: {images}");
Console.WriteLine($"Headers: {headerRefs}");
Console.WriteLine($"Footers: {footerRefs}");
Console.WriteLine($"Paragraphs: {paragraphs.Count}");
Console.WriteLine($"Word count: ~{wordCount}");
Console.WriteLine($"Custom styles: {styleNames.Count}");
foreach (var s in styleNames)
Console.WriteLine($" {s}");
Console.WriteLine("XML file sizes:");
foreach (var f in fileSizes)
Console.WriteLine($" {f.file}: {f.size:N0} bytes");
}
});
return cmd;
}
}
@@ -0,0 +1,322 @@
using System.CommandLine;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario C: Apply formatting from a template DOCX to a source DOCX.
/// Copies styles, theme, numbering, headers/footers, and section properties
/// from the template while preserving all content from the source.
/// </summary>
public static class ApplyTemplateCommand
{
public static Command Create()
{
var inputOpt = new Option<string>("--input") { Description = "Source DOCX (content to keep)", Required = true };
var templateOpt = new Option<string>("--template") { Description = "Template DOCX (formatting to apply)", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output DOCX file path", Required = true };
var applyStylesOpt = new Option<bool>("--apply-styles") { Description = "Copy styles.xml from template" };
applyStylesOpt.DefaultValueFactory = _ => true;
var applyThemeOpt = new Option<bool>("--apply-theme") { Description = "Copy theme from template" };
applyThemeOpt.DefaultValueFactory = _ => true;
var applyNumberingOpt = new Option<bool>("--apply-numbering") { Description = "Copy numbering.xml from template" };
applyNumberingOpt.DefaultValueFactory = _ => true;
var applyHeadersFootersOpt = new Option<bool>("--apply-headers-footers") { Description = "Copy headers/footers from template" };
var applySectionsOpt = new Option<bool>("--apply-sections") { Description = "Apply section properties from template" };
applySectionsOpt.DefaultValueFactory = _ => true;
var cmd = new Command("apply-template", "Apply template formatting to a DOCX")
{
inputOpt, templateOpt, outputOpt, applyStylesOpt, applyThemeOpt,
applyNumberingOpt, applyHeadersFootersOpt, applySectionsOpt
};
cmd.SetAction((parseResult) =>
{
var inputPath = parseResult.GetValue(inputOpt)!;
var templatePath = parseResult.GetValue(templateOpt)!;
var outputPath = parseResult.GetValue(outputOpt)!;
var applyStyles = parseResult.GetValue(applyStylesOpt);
var applyTheme = parseResult.GetValue(applyThemeOpt);
var applyNumbering = parseResult.GetValue(applyNumberingOpt);
var applyHeadersFooters = parseResult.GetValue(applyHeadersFootersOpt);
var applySections = parseResult.GetValue(applySectionsOpt);
if (!File.Exists(inputPath)) { Console.Error.WriteLine($"Input file not found: {inputPath}"); return; }
if (!File.Exists(templatePath)) { Console.Error.WriteLine($"Template file not found: {templatePath}"); return; }
// Create output as a copy of the source
File.Copy(inputPath, outputPath, overwrite: true);
using var output = WordprocessingDocument.Open(outputPath, true);
using var template = WordprocessingDocument.Open(templatePath, false);
var outputMain = output.MainDocumentPart;
var templateMain = template.MainDocumentPart;
if (outputMain == null || templateMain == null)
{
Console.Error.WriteLine("Invalid document: missing main document part.");
return;
}
int appliedCount = 0;
if (applyStyles)
{
CopyStyles(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: styles");
}
if (applyTheme)
{
CopyTheme(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: theme");
}
if (applyNumbering)
{
CopyNumbering(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: numbering");
}
if (applyHeadersFooters)
{
CopyHeadersAndFooters(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: headers/footers");
}
if (applySections)
{
CopySectionProperties(templateMain, outputMain);
appliedCount++;
Console.WriteLine(" Applied: section properties");
}
outputMain.Document.Save();
Console.WriteLine($"Applied {appliedCount} formatting component(s) from template to {outputPath}");
});
return cmd;
}
/// <summary>
/// Replaces the output's StyleDefinitionsPart with the template's version.
/// </summary>
private static void CopyStyles(MainDocumentPart template, MainDocumentPart output)
{
var templateStyles = template.StyleDefinitionsPart;
if (templateStyles == null) return;
if (output.StyleDefinitionsPart != null)
output.DeletePart(output.StyleDefinitionsPart);
var newStylesPart = output.AddNewPart<StyleDefinitionsPart>();
using var stream = templateStyles.GetStream(FileMode.Open, FileAccess.Read);
newStylesPart.FeedData(stream);
}
/// <summary>
/// Replaces the output's ThemePart with the template's version.
/// </summary>
private static void CopyTheme(MainDocumentPart template, MainDocumentPart output)
{
var templateTheme = template.ThemePart;
if (templateTheme == null) return;
if (output.ThemePart != null)
output.DeletePart(output.ThemePart);
var newThemePart = output.AddNewPart<ThemePart>();
using var stream = templateTheme.GetStream(FileMode.Open, FileAccess.Read);
newThemePart.FeedData(stream);
}
/// <summary>
/// Copies numbering definitions from template, remapping numbering IDs
/// referenced in the output document's paragraphs.
/// </summary>
private static void CopyNumbering(MainDocumentPart template, MainDocumentPart output)
{
var templateNumbering = template.NumberingDefinitionsPart;
if (templateNumbering == null) return;
var referencedNumIds = new HashSet<string>();
var body = output.Document.Body;
if (body != null)
{
foreach (var numId in body.Descendants<NumberingId>())
{
if (numId.Val?.Value != null)
referencedNumIds.Add(numId.Val.Value.ToString());
}
}
if (output.NumberingDefinitionsPart != null)
output.DeletePart(output.NumberingDefinitionsPart);
var newNumberingPart = output.AddNewPart<NumberingDefinitionsPart>();
using var stream = templateNumbering.GetStream(FileMode.Open, FileAccess.Read);
newNumberingPart.FeedData(stream);
if (referencedNumIds.Count > 0)
{
Console.WriteLine($" Note: {referencedNumIds.Count} numbering reference(s) in document content mapped to template definitions.");
}
}
/// <summary>
/// Copies headers and footers from the template, remapping relationship IDs.
/// </summary>
private static void CopyHeadersAndFooters(MainDocumentPart template, MainDocumentPart output)
{
var outputBody = output.Document.Body;
if (outputBody == null) return;
// Remove existing header/footer parts from output
foreach (var hp in output.HeaderParts.ToList())
output.DeletePart(hp);
foreach (var fp in output.FooterParts.ToList())
output.DeletePart(fp);
// Remove existing header/footer references from all section properties
foreach (var sectPr in outputBody.Descendants<SectionProperties>())
{
foreach (var hr in sectPr.Elements<HeaderReference>().ToList())
hr.Remove();
foreach (var fr in sectPr.Elements<FooterReference>().ToList())
fr.Remove();
}
var templateBody = template.Document?.Body;
if (templateBody == null) return;
var templateFinalSectPr = templateBody.Descendants<SectionProperties>().LastOrDefault();
if (templateFinalSectPr == null) return;
var outputFinalSectPr = outputBody.Descendants<SectionProperties>().LastOrDefault();
if (outputFinalSectPr == null)
{
outputFinalSectPr = new SectionProperties();
outputBody.Append(outputFinalSectPr);
}
// Copy headers
foreach (var headerRef in templateFinalSectPr.Elements<HeaderReference>())
{
var templateHeaderPart = template.GetPartById(headerRef.Id!) as HeaderPart;
if (templateHeaderPart == null) continue;
var newHeaderPart = output.AddNewPart<HeaderPart>();
using (var stream = templateHeaderPart.GetStream(FileMode.Open, FileAccess.Read))
{
newHeaderPart.FeedData(stream);
}
CopyPartRelationships(templateHeaderPart, newHeaderPart);
var newRefId = output.GetIdOfPart(newHeaderPart);
outputFinalSectPr.InsertAt(new HeaderReference
{
Type = headerRef.Type,
Id = newRefId
}, 0);
}
// Copy footers
foreach (var footerRef in templateFinalSectPr.Elements<FooterReference>())
{
var templateFooterPart = template.GetPartById(footerRef.Id!) as FooterPart;
if (templateFooterPart == null) continue;
var newFooterPart = output.AddNewPart<FooterPart>();
using (var stream = templateFooterPart.GetStream(FileMode.Open, FileAccess.Read))
{
newFooterPart.FeedData(stream);
}
CopyPartRelationships(templateFooterPart, newFooterPart);
var newRefId = output.GetIdOfPart(newFooterPart);
var lastHeaderRef = outputFinalSectPr.Elements<HeaderReference>().LastOrDefault();
if (lastHeaderRef != null)
lastHeaderRef.InsertAfterSelf(new FooterReference { Type = footerRef.Type, Id = newRefId });
else
outputFinalSectPr.InsertAt(new FooterReference { Type = footerRef.Type, Id = newRefId }, 0);
}
}
/// <summary>
/// Copies sub-relationships (images, etc.) from a source part to a target part.
/// </summary>
private static void CopyPartRelationships(OpenXmlPart source, OpenXmlPart target)
{
foreach (var rel in source.ExternalRelationships)
{
target.AddExternalRelationship(rel.RelationshipType, rel.Uri, rel.Id);
}
foreach (var childPart in source.Parts)
{
try
{
var contentType = childPart.OpenXmlPart.ContentType;
if (contentType.StartsWith("image/"))
{
var newChild = target.AddNewPart<ImagePart>(contentType, childPart.RelationshipId);
using var stream = childPart.OpenXmlPart.GetStream(FileMode.Open, FileAccess.Read);
newChild.FeedData(stream);
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"[WARN] Skipped non-image embedded part: {ex.Message}");
}
}
}
/// <summary>
/// Copies page size, margins, columns, and document grid from template section properties.
/// </summary>
private static void CopySectionProperties(MainDocumentPart template, MainDocumentPart output)
{
var templateBody = template.Document?.Body;
var outputBody = output.Document?.Body;
if (templateBody == null || outputBody == null) return;
var templateSectPr = templateBody.Descendants<SectionProperties>().LastOrDefault();
if (templateSectPr == null) return;
var outputSectPr = outputBody.Descendants<SectionProperties>().LastOrDefault();
if (outputSectPr == null)
{
outputSectPr = new SectionProperties();
outputBody.Append(outputSectPr);
}
CopyChildElement<PageSize>(templateSectPr, outputSectPr);
CopyChildElement<PageMargin>(templateSectPr, outputSectPr);
CopyChildElement<Columns>(templateSectPr, outputSectPr);
CopyChildElement<DocGrid>(templateSectPr, outputSectPr);
CopyChildElement<PageBorders>(templateSectPr, outputSectPr);
}
private static void CopyChildElement<T>(SectionProperties source, SectionProperties target) where T : OpenXmlElement
{
var sourceElement = source.GetFirstChild<T>();
if (sourceElement == null) return;
var existing = target.GetFirstChild<T>();
existing?.Remove();
target.Append((T)sourceElement.CloneNode(true));
}
}
@@ -0,0 +1,324 @@
using System.CommandLine;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniMaxAIDocx.Core.OpenXml;
using MiniMaxAIDocx.Core.Typography;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario A: Create a new DOCX document from scratch with proper styles, sections,
/// headers/footers, and typography defaults.
/// </summary>
public static class CreateCommand
{
public static Command Create()
{
var outputOption = new Option<string>("--output") { Description = "Output DOCX file path", Required = true };
var typeOption = new Option<string>("--type") { Description = "Document type: report, letter, memo, academic" };
typeOption.DefaultValueFactory = _ => "report";
var titleOption = new Option<string>("--title") { Description = "Document title" };
var authorOption = new Option<string>("--author") { Description = "Document author" };
var pageSizeOption = new Option<string>("--page-size") { Description = "Page size: letter, a4, legal, a3" };
pageSizeOption.DefaultValueFactory = _ => "letter";
var marginsOption = new Option<string>("--margins") { Description = "Margin preset: standard, narrow, wide" };
marginsOption.DefaultValueFactory = _ => "standard";
var headerTextOption = new Option<string>("--header") { Description = "Header text" };
var footerTextOption = new Option<string>("--footer") { Description = "Footer text" };
var pageNumbersOption = new Option<bool>("--page-numbers") { Description = "Add page numbers in footer" };
var tocOption = new Option<bool>("--toc") { Description = "Insert table of contents placeholder" };
var contentJsonOption = new Option<string>("--content-json") { Description = "Path to JSON file describing document content" };
var cmd = new Command("create", "Create a new DOCX document from scratch")
{
outputOption, typeOption, titleOption, authorOption, pageSizeOption,
marginsOption, headerTextOption, footerTextOption, pageNumbersOption,
tocOption, contentJsonOption
};
cmd.SetAction((parseResult) =>
{
var output = parseResult.GetValue(outputOption)!;
var docType = parseResult.GetValue(typeOption) ?? "report";
var title = parseResult.GetValue(titleOption);
var author = parseResult.GetValue(authorOption);
var pageSizeName = parseResult.GetValue(pageSizeOption) ?? "letter";
var marginsName = parseResult.GetValue(marginsOption) ?? "standard";
var headerText = parseResult.GetValue(headerTextOption);
var footerText = parseResult.GetValue(footerTextOption);
var pageNumbers = parseResult.GetValue(pageNumbersOption);
var tocPlaceholder = parseResult.GetValue(tocOption);
var contentJson = parseResult.GetValue(contentJsonOption);
var fontConfig = GetFontConfig(docType);
var pageSize = GetPageSizeConfig(pageSizeName);
var margins = GetMargins(marginsName);
using var doc = WordprocessingDocument.Create(output, WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
var body = mainPart.Document.Body!;
// Add styles part with defaults
AddDefaultStyles(mainPart, fontConfig);
// Add section properties (page size, margins)
var sectPr = new SectionProperties();
sectPr.Append(new DocumentFormat.OpenXml.Wordprocessing.PageSize
{
Width = (UInt32Value)(uint)pageSize.WidthDxa,
Height = (UInt32Value)(uint)pageSize.HeightDxa
});
sectPr.Append(new PageMargin
{
Top = margins.TopDxa,
Bottom = margins.BottomDxa,
Left = (UInt32Value)(uint)margins.LeftDxa,
Right = (UInt32Value)(uint)margins.RightDxa
});
// Add header if requested
if (!string.IsNullOrEmpty(headerText))
{
var headerPart = mainPart.AddNewPart<HeaderPart>();
headerPart.Header = new Header(
new Paragraph(new Run(new Text(headerText))));
var headerRefId = mainPart.GetIdOfPart(headerPart);
sectPr.Append(new HeaderReference
{
Type = HeaderFooterValues.Default,
Id = headerRefId
});
}
// Add footer if requested
if (!string.IsNullOrEmpty(footerText) || pageNumbers)
{
var footerPart = mainPart.AddNewPart<FooterPart>();
var footerParagraph = new Paragraph();
if (!string.IsNullOrEmpty(footerText))
{
footerParagraph.Append(new Run(new Text(footerText)));
}
if (pageNumbers)
{
if (!string.IsNullOrEmpty(footerText))
footerParagraph.Append(new Run(new Text(" — ") { Space = SpaceProcessingModeValues.Preserve }));
footerParagraph.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.Begin }));
footerParagraph.Append(new Run(
new FieldCode(" PAGE ") { Space = SpaceProcessingModeValues.Preserve }));
footerParagraph.Append(new Run(
new FieldChar { FieldCharType = FieldCharValues.End }));
}
footerPart.Footer = new Footer(footerParagraph);
var footerRefId = mainPart.GetIdOfPart(footerPart);
sectPr.Append(new FooterReference
{
Type = HeaderFooterValues.Default,
Id = footerRefId
});
}
// Title
if (!string.IsNullOrEmpty(title))
{
var titlePara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Title" }),
new Run(new Text(title)));
body.Append(titlePara);
}
// Author subtitle
if (!string.IsNullOrEmpty(author))
{
var authorPara = new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "Subtitle" }),
new Run(new Text(author)));
body.Append(authorPara);
}
// TOC placeholder
if (tocPlaceholder)
{
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = "TOCHeading" }),
new Run(new Text("Table of Contents"))));
// Insert TOC field
var tocPara = new Paragraph();
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Begin }));
tocPara.Append(new Run(new FieldCode(" TOC \\o \"1-3\" \\h \\z \\u ") { Space = SpaceProcessingModeValues.Preserve }));
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.Separate }));
tocPara.Append(new Run(new Text("Update this field to generate table of contents.")));
tocPara.Append(new Run(new FieldChar { FieldCharType = FieldCharValues.End }));
body.Append(tocPara);
// Page break after TOC
body.Append(new Paragraph(new Run(new Break { Type = BreakValues.Page })));
}
// Content from JSON (if provided)
if (!string.IsNullOrEmpty(contentJson) && File.Exists(contentJson))
{
var jsonContent = File.ReadAllText(contentJson);
AddContentFromJson(body, jsonContent, fontConfig);
}
// Ensure body has at least one paragraph
if (!body.Elements<Paragraph>().Any())
{
body.Append(new Paragraph());
}
// sectPr must be the last child of body
body.Append(sectPr);
mainPart.Document.Save();
Console.WriteLine($"Created {docType} document: {output}");
});
return cmd;
}
private static FontConfig GetFontConfig(string docType) => docType.ToLowerInvariant() switch
{
"letter" => FontDefaults.Letter,
"memo" => FontDefaults.Memo,
"academic" => FontDefaults.Academic,
_ => FontDefaults.Report,
};
private static Typography.PageSize GetPageSizeConfig(string name) => name.ToLowerInvariant() switch
{
"a4" => PageSizes.A4,
"legal" => PageSizes.Legal,
"a3" => PageSizes.A3,
_ => PageSizes.Letter,
};
private static MarginConfig GetMargins(string name) => name.ToLowerInvariant() switch
{
"narrow" => PageSizes.NarrowMargins,
"wide" => PageSizes.WideMargins,
_ => PageSizes.StandardMargins,
};
private static void AddDefaultStyles(MainDocumentPart mainPart, FontConfig fontConfig)
{
var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
var styles = new Styles();
// Default run properties
var defaultRPr = new StyleRunProperties(
new RunFonts { Ascii = fontConfig.BodyFont, HighAnsi = fontConfig.BodyFont },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize) });
// Normal style
styles.Append(new Style(
new StyleName { Val = "Normal" },
new PrimaryStyle(),
defaultRPr)
{ Type = StyleValues.Paragraph, StyleId = "Normal", Default = true });
// Heading styles 1-6
double[] headingSizes = [fontConfig.Heading1Size, fontConfig.Heading2Size, fontConfig.Heading3Size,
fontConfig.Heading4Size, fontConfig.Heading5Size, fontConfig.Heading6Size];
for (int i = 0; i < 6; i++)
{
var level = i + 1;
var headingStyle = new Style(
new StyleName { Val = $"heading {level}" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new PrimaryStyle(),
new StyleParagraphProperties(
new KeepNext(),
new KeepLines(),
new SpacingBetweenLines { Before = "240", After = "120" },
new OutlineLevel { Val = i }),
new StyleRunProperties(
new RunFonts { Ascii = fontConfig.HeadingFont, HighAnsi = fontConfig.HeadingFont },
new FontSize { Val = UnitConverter.FontSizeToSz(headingSizes[i]) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(headingSizes[i]) },
new Bold()))
{ Type = StyleValues.Paragraph, StyleId = $"Heading{level}" };
styles.Append(headingStyle);
}
// Title style
styles.Append(new Style(
new StyleName { Val = "Title" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new PrimaryStyle(),
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "300" }),
new StyleRunProperties(
new RunFonts { Ascii = fontConfig.HeadingFont, HighAnsi = fontConfig.HeadingFont },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.Heading1Size + 6) },
new FontSizeComplexScript { Val = UnitConverter.FontSizeToSz(fontConfig.Heading1Size + 6) }))
{ Type = StyleValues.Paragraph, StyleId = "Title" });
// Subtitle style
styles.Append(new Style(
new StyleName { Val = "Subtitle" },
new BasedOn { Val = "Normal" },
new NextParagraphStyle { Val = "Normal" },
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { After = "200" }),
new StyleRunProperties(
new Color { Val = "5A5A5A" },
new FontSize { Val = UnitConverter.FontSizeToSz(fontConfig.BodySize + 2) }))
{ Type = StyleValues.Paragraph, StyleId = "Subtitle" });
stylesPart.Styles = styles;
stylesPart.Styles.Save();
}
private static void AddContentFromJson(Body body, string jsonContent, FontConfig fontConfig)
{
// Simple JSON content format: array of {type, text, level?}
// e.g. [{"type":"heading","text":"Introduction","level":1},{"type":"paragraph","text":"..."}]
try
{
using var jsonDoc = System.Text.Json.JsonDocument.Parse(jsonContent);
foreach (var element in jsonDoc.RootElement.EnumerateArray())
{
var type = element.GetProperty("type").GetString() ?? "paragraph";
var text = element.GetProperty("text").GetString() ?? "";
switch (type)
{
case "heading":
var level = element.TryGetProperty("level", out var lvl) ? lvl.GetInt32() : 1;
level = Math.Clamp(level, 1, 6);
body.Append(new Paragraph(
new ParagraphProperties(new ParagraphStyleId { Val = $"Heading{level}" }),
new Run(new Text(text))));
break;
case "paragraph":
body.Append(new Paragraph(new Run(new Text(text))));
break;
case "pagebreak":
body.Append(new Paragraph(new Run(new Break { Type = BreakValues.Page })));
break;
}
}
}
catch (System.Text.Json.JsonException ex)
{
Console.Error.WriteLine($"Warning: could not parse content JSON: {ex.Message}");
}
}
}
@@ -0,0 +1,155 @@
using System.CommandLine;
using System.IO.Compression;
using System.Text.Json;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class DiffCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static Command Create()
{
var beforeOption = new Option<string>("--before") { Description = "Original DOCX", Required = true };
var afterOption = new Option<string>("--after") { Description = "Modified DOCX", Required = true };
var jsonOption = new Option<bool>("--json") { Description = "Output as JSON" };
var cmd = new Command("diff", "Compare two DOCX files")
{
beforeOption, afterOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var before = parseResult.GetValue(beforeOption)!;
var after = parseResult.GetValue(afterOption)!;
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(before)) { Console.Error.WriteLine($"File not found: {before}"); return; }
if (!File.Exists(after)) { Console.Error.WriteLine($"File not found: {after}"); return; }
var beforeParas = ExtractParagraphs(before);
var afterParas = ExtractParagraphs(after);
var beforeStyles = ExtractStyleIds(before);
var afterStyles = ExtractStyleIds(after);
var beforeStructure = ExtractStructure(before);
var afterStructure = ExtractStructure(after);
// Text diff
var textChanges = new List<object>();
int maxLen = Math.Max(beforeParas.Count, afterParas.Count);
int changedParas = 0;
for (int i = 0; i < maxLen; i++)
{
var bText = i < beforeParas.Count ? beforeParas[i] : null;
var aText = i < afterParas.Count ? afterParas[i] : null;
if (bText != aText)
{
changedParas++;
textChanges.Add(new
{
paragraph = i + 1,
before = bText ?? "(absent)",
after = aText ?? "(absent)"
});
}
}
// Style diff
var addedStyles = afterStyles.Except(beforeStyles).ToList();
var removedStyles = beforeStyles.Except(afterStyles).ToList();
// Structure diff
var structureChanges = new List<string>();
if (beforeStructure.Sections != afterStructure.Sections)
structureChanges.Add($"Sections: {beforeStructure.Sections} -> {afterStructure.Sections}");
if (beforeStructure.Tables != afterStructure.Tables)
structureChanges.Add($"Tables: {beforeStructure.Tables} -> {afterStructure.Tables}");
if (beforeStructure.Images != afterStructure.Images)
structureChanges.Add($"Images: {beforeStructure.Images} -> {afterStructure.Images}");
var result = new
{
textChanges,
styleChanges = new { added = addedStyles, removed = removedStyles },
structureChanges,
summary = $"{changedParas} paragraphs changed, {addedStyles.Count + removedStyles.Count} styles modified, {structureChanges.Count} structural changes"
};
if (asJson)
{
Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
Console.WriteLine(result.summary);
Console.WriteLine();
if (textChanges.Count > 0)
{
Console.WriteLine($"Text changes ({textChanges.Count}):");
foreach (var tc in textChanges.Take(20))
Console.WriteLine($" {tc}");
if (textChanges.Count > 20)
Console.WriteLine($" ... and {textChanges.Count - 20} more");
}
if (addedStyles.Count > 0)
Console.WriteLine($"Added styles: {string.Join(", ", addedStyles)}");
if (removedStyles.Count > 0)
Console.WriteLine($"Removed styles: {string.Join(", ", removedStyles)}");
foreach (var sc in structureChanges)
Console.WriteLine($"Structure: {sc}");
}
});
return cmd;
}
private static List<string> ExtractParagraphs(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/document.xml");
if (entry == null) return new();
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return doc.Descendants(W + "p")
.Select(p => string.Concat(p.Descendants(W + "t").Select(t => t.Value)))
.ToList();
}
private static HashSet<string> ExtractStyleIds(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/styles.xml");
if (entry == null) return new();
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return doc.Descendants(W + "style")
.Select(s => (string?)s.Attribute(W + "styleId"))
.Where(id => id != null)
.ToHashSet()!;
}
private record StructureInfo(int Sections, int Tables, int Images);
private static StructureInfo ExtractStructure(string docxPath)
{
using var zip = ZipFile.OpenRead(docxPath);
var entry = zip.GetEntry("word/document.xml");
if (entry == null) return new(0, 0, 0);
using var stream = entry.Open();
var doc = XDocument.Load(stream);
return new(
doc.Descendants(W + "sectPr").Count(),
doc.Descendants(W + "tbl").Count(),
doc.Descendants(W + "drawing").Count()
);
}
}
@@ -0,0 +1,487 @@
using System.CommandLine;
using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniMaxAIDocx.Core.OpenXml;
namespace MiniMaxAIDocx.Core.Commands;
/// <summary>
/// Scenario B: Surgical content editing operations on existing DOCX files.
/// Preserves all existing formatting and minimizes XML changes.
/// </summary>
public static class EditContentCommand
{
public static Command Create()
{
var cmd = new Command("edit", "Edit existing DOCX content");
cmd.Add(CreateReplaceTextCommand());
cmd.Add(CreateFillTableCommand());
cmd.Add(CreateInsertParagraphCommand());
cmd.Add(CreateUpdateFieldCommand());
cmd.Add(CreateListPlaceholdersCommand());
cmd.Add(CreateFillPlaceholdersCommand());
return cmd;
}
private static Command CreateReplaceTextCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path (defaults to overwriting input)" };
var searchOpt = new Option<string>("--search") { Description = "Text to search for", Required = true };
var replaceOpt = new Option<string>("--replace") { Description = "Replacement text", Required = true };
var regexOpt = new Option<bool>("--regex") { Description = "Treat search as a regex pattern" };
var cmd = new Command("replace-text", "Replace text while preserving formatting")
{
inputOpt, outputOpt, searchOpt, replaceOpt, regexOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var search = parseResult.GetValue(searchOpt)!;
var replace = parseResult.GetValue(replaceOpt)!;
var useRegex = parseResult.GetValue(regexOpt);
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
int count = 0;
foreach (var paragraph in body.Descendants<Paragraph>())
{
count += ReplaceInParagraph(paragraph, search, replace, useRegex);
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Replaced {count} occurrence(s) in {output}");
});
return cmd;
}
private static Command CreateFillTableCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var tableIndexOpt = new Option<int>("--table-index") { Description = "Zero-based index of the table to fill" };
tableIndexOpt.DefaultValueFactory = _ => 0;
var csvOpt = new Option<string>("--csv") { Description = "CSV file with data to fill", Required = true };
var appendOpt = new Option<bool>("--append") { Description = "Append rows instead of replacing existing data rows" };
var cmd = new Command("fill-table", "Fill a table with data from CSV")
{
inputOpt, outputOpt, tableIndexOpt, csvOpt, appendOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var tableIndex = parseResult.GetValue(tableIndexOpt);
var csvPath = parseResult.GetValue(csvOpt)!;
var append = parseResult.GetValue(appendOpt);
if (output != input) File.Copy(input, output, overwrite: true);
if (!File.Exists(csvPath)) { Console.Error.WriteLine($"CSV file not found: {csvPath}"); return; }
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var tables = body.Elements<Table>().ToList();
if (tableIndex >= tables.Count)
{
Console.Error.WriteLine($"Table index {tableIndex} out of range (found {tables.Count} tables).");
return;
}
var table = tables[tableIndex];
var csvLines = File.ReadAllLines(csvPath);
if (csvLines.Length == 0) { Console.WriteLine("CSV is empty, nothing to fill."); return; }
// Get template row properties from the first data row (second row, after header)
var existingRows = table.Elements<TableRow>().ToList();
TableRow? templateRow = existingRows.Count > 1 ? existingRows[1] : existingRows.FirstOrDefault();
var templateTrPr = templateRow?.TableRowProperties?.CloneNode(true) as TableRowProperties;
if (!append)
{
// Remove all rows except the header row
for (int i = existingRows.Count - 1; i >= 1; i--)
existingRows[i].Remove();
}
int rowsAdded = 0;
// Skip header line in CSV (index 0)
for (int i = 1; i < csvLines.Length; i++)
{
var values = ParseCsvLine(csvLines[i]);
var newRow = new TableRow();
if (templateTrPr != null)
newRow.Append(templateTrPr.CloneNode(true));
foreach (var val in values)
{
var cell = new TableCell(
new Paragraph(new Run(new Text(val))));
newRow.Append(cell);
}
table.Append(newRow);
rowsAdded++;
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Added {rowsAdded} rows to table {tableIndex} in {output}");
});
return cmd;
}
private static Command CreateInsertParagraphCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var textOpt = new Option<string>("--text") { Description = "Paragraph text", Required = true };
var styleOpt = new Option<string>("--style") { Description = "Paragraph style (e.g. Heading1, Normal)" };
var afterOpt = new Option<int>("--after-paragraph") { Description = "Insert after this paragraph index (0-based)" };
afterOpt.DefaultValueFactory = _ => -1; // -1 = append at end
var cmd = new Command("insert-paragraph", "Insert a new paragraph")
{
inputOpt, outputOpt, textOpt, styleOpt, afterOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var text = parseResult.GetValue(textOpt)!;
var style = parseResult.GetValue(styleOpt);
var afterIndex = parseResult.GetValue(afterOpt);
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var newPara = new Paragraph();
if (!string.IsNullOrEmpty(style))
newPara.Append(new ParagraphProperties(new ParagraphStyleId { Val = style }));
newPara.Append(new Run(new Text(text)));
var paragraphs = body.Elements<Paragraph>().ToList();
if (afterIndex >= 0 && afterIndex < paragraphs.Count)
{
paragraphs[afterIndex].InsertAfterSelf(newPara);
}
else
{
// Insert before sectPr if present, otherwise append
var sectPr = body.Elements<SectionProperties>().FirstOrDefault();
if (sectPr != null)
sectPr.InsertBeforeSelf(newPara);
else
body.Append(newPara);
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Inserted paragraph in {output}");
});
return cmd;
}
private static Command CreateUpdateFieldCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var fieldNameOpt = new Option<string>("--field") { Description = "Document property field name (e.g. TITLE, AUTHOR)", Required = true };
var valueOpt = new Option<string>("--value") { Description = "New field value", Required = true };
var cmd = new Command("update-field", "Update a document property field value")
{
inputOpt, outputOpt, fieldNameOpt, valueOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var fieldName = parseResult.GetValue(fieldNameOpt)!;
var value = parseResult.GetValue(valueOpt)!;
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
// Update core properties
var props = doc.PackageProperties;
switch (fieldName.ToUpperInvariant())
{
case "TITLE": props.Title = value; break;
case "AUTHOR": props.Creator = value; break;
case "SUBJECT": props.Subject = value; break;
case "KEYWORDS": props.Keywords = value; break;
case "DESCRIPTION": props.Description = value; break;
case "CATEGORY": props.Category = value; break;
default:
Console.Error.WriteLine($"Unknown field: {fieldName}. Supported: TITLE, AUTHOR, SUBJECT, KEYWORDS, DESCRIPTION, CATEGORY");
return;
}
Console.WriteLine($"Updated {fieldName} to \"{value}\" in {output}");
});
return cmd;
}
private static Command CreateListPlaceholdersCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern (regex)" };
patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}"; // {{PLACEHOLDER}}
var cmd = new Command("list-placeholders", "List all placeholders found in the document")
{
inputOpt, patternOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var pattern = parseResult.GetValue(patternOpt)!;
using var doc = WordprocessingDocument.Open(input, false);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
var placeholders = new HashSet<string>();
var regex = new Regex(pattern);
foreach (var paragraph in body.Descendants<Paragraph>())
{
var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
foreach (Match match in regex.Matches(fullText))
{
placeholders.Add(match.Value);
}
}
if (placeholders.Count == 0)
{
Console.WriteLine("No placeholders found.");
return;
}
Console.WriteLine($"Found {placeholders.Count} unique placeholder(s):");
foreach (var p in placeholders.OrderBy(x => x))
Console.WriteLine($" {p}");
});
return cmd;
}
private static Command CreateFillPlaceholdersCommand()
{
var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
var outputOpt = new Option<string>("--output") { Description = "Output file path" };
var mappingOpt = new Option<string>("--mapping") { Description = "JSON file mapping placeholder names to values", Required = true };
var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern with capture group for the name" };
patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}";
var cmd = new Command("fill-placeholders", "Replace placeholders with values from a mapping file")
{
inputOpt, outputOpt, mappingOpt, patternOpt
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOpt)!;
var output = parseResult.GetValue(outputOpt) ?? input;
var mappingPath = parseResult.GetValue(mappingOpt)!;
var pattern = parseResult.GetValue(patternOpt)!;
if (!File.Exists(mappingPath)) { Console.Error.WriteLine($"Mapping file not found: {mappingPath}"); return; }
var mappingJson = File.ReadAllText(mappingPath);
Dictionary<string, string> mapping;
try
{
mapping = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(mappingJson) ?? [];
}
catch (System.Text.Json.JsonException ex)
{
Console.Error.WriteLine($"Invalid mapping JSON: {ex.Message}");
return;
}
if (output != input) File.Copy(input, output, overwrite: true);
using var doc = WordprocessingDocument.Open(output, true);
var body = doc.MainDocumentPart?.Document.Body;
if (body == null) { Console.Error.WriteLine("No document body found."); return; }
int totalReplacements = 0;
var regex = new Regex(pattern);
foreach (var paragraph in body.Descendants<Paragraph>())
{
var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
var matches = regex.Matches(fullText);
if (matches.Count == 0) continue;
foreach (Match match in matches)
{
var placeholderName = match.Groups.Count > 1 ? match.Groups[1].Value : match.Value;
if (mapping.TryGetValue(placeholderName, out var replacement))
{
totalReplacements += ReplaceInParagraph(paragraph, match.Value, replacement, false);
}
}
}
doc.MainDocumentPart!.Document.Save();
Console.WriteLine($"Filled {totalReplacements} placeholder(s) in {output}");
});
return cmd;
}
/// <summary>
/// Replaces text within a paragraph while preserving run formatting.
/// Handles the case where search text may span multiple runs.
/// </summary>
private static int ReplaceInParagraph(Paragraph paragraph, string search, string replace, bool useRegex)
{
var runs = paragraph.Elements<Run>().ToList();
if (runs.Count == 0) return 0;
// Build the full paragraph text and a map from character index to (run, position within run)
var fullText = string.Concat(runs.SelectMany(r => r.Elements<Text>().Select(t => t.Text)));
if (string.IsNullOrEmpty(fullText)) return 0;
int count = 0;
if (!useRegex)
{
// Simple case: search within each run first
foreach (var run in runs)
{
foreach (var textElement in run.Elements<Text>().ToList())
{
if (textElement.Text.Contains(search))
{
var newText = textElement.Text.Replace(search, replace);
count += (textElement.Text.Length - newText.Length + replace.Length - search.Length) == 0 ? 0 :
CountOccurrences(textElement.Text, search);
textElement.Text = newText;
if (newText.StartsWith(' ') || newText.EndsWith(' '))
textElement.Space = SpaceProcessingModeValues.Preserve;
}
}
}
// Handle cross-run matches by concatenating all runs, replacing, and rebuilding
if (count == 0 && fullText.Contains(search))
{
var newFullText = fullText.Replace(search, replace);
count = CountOccurrences(fullText, search);
RebuildRunsWithText(paragraph, runs, newFullText);
}
}
else
{
var regex = new Regex(search);
if (regex.IsMatch(fullText))
{
count = regex.Matches(fullText).Count;
var newFullText = regex.Replace(fullText, replace);
RebuildRunsWithText(paragraph, runs, newFullText);
}
}
return count;
}
/// <summary>
/// Replaces the text content of existing runs with new text,
/// preserving the formatting of the first run.
/// </summary>
private static void RebuildRunsWithText(Paragraph paragraph, List<Run> runs, string newText)
{
if (runs.Count == 0) return;
// Keep the first run's formatting, set its text to the full new text
var firstRun = runs[0];
var firstText = firstRun.Elements<Text>().FirstOrDefault();
if (firstText != null)
{
firstText.Text = newText;
if (newText.StartsWith(' ') || newText.EndsWith(' '))
firstText.Space = SpaceProcessingModeValues.Preserve;
}
// Remove all other runs
for (int i = 1; i < runs.Count; i++)
runs[i].Remove();
}
private static int CountOccurrences(string text, string search)
{
int count = 0;
int index = 0;
while ((index = text.IndexOf(search, index, StringComparison.Ordinal)) != -1)
{
count++;
index += search.Length;
}
return count;
}
private static string[] ParseCsvLine(string line)
{
// Simple CSV parser (handles quoted fields)
var result = new List<string>();
bool inQuotes = false;
var current = new System.Text.StringBuilder();
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{
current.Append('"');
i++;
}
else
{
inQuotes = !inQuotes;
}
}
else if (c == ',' && !inQuotes)
{
result.Add(current.ToString());
current.Clear();
}
else
{
current.Append(c);
}
}
result.Add(current.ToString());
return result.ToArray();
}
}
@@ -0,0 +1,108 @@
using System.CommandLine;
using System.IO.Compression;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class FixOrderCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
// Canonical element ordering within common parent elements per ISO 29500
private static readonly Dictionary<string, List<string>> ElementOrder = new()
{
["pPr"] = new() { "pStyle", "keepNext", "keepLines", "pageBreakBefore", "widowControl", "numPr", "suppressLineNumbers", "pBdr", "shd", "tabs", "suppressAutoHyphens", "spacing", "ind", "jc", "outlineLvl", "rPr" },
["rPr"] = new() { "rStyle", "rFonts", "b", "bCs", "i", "iCs", "caps", "smallCaps", "strike", "dstrike", "vanish", "color", "spacing", "w", "kern", "position", "sz", "szCs", "highlight", "u", "effect", "vertAlign", "lang" },
["tblPr"] = new() { "tblStyle", "tblpPr", "tblOverlap", "tblW", "jc", "tblInd", "tblBorders", "shd", "tblLayout", "tblCellMar", "tblLook" },
["tcPr"] = new() { "cnfStyle", "tcW", "gridSpan", "hMerge", "vMerge", "tcBorders", "shd", "noWrap", "tcMar", "textDirection", "tcFitText", "vAlign" },
["sectPr"] = new() { "headerReference", "footerReference", "footnotePr", "endnotePr", "type", "pgSz", "pgMar", "paperSrc", "pgBorders", "lnNumType", "pgNumType", "cols", "docGrid" },
};
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to fix", Required = true };
var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
var backupOption = new Option<bool>("--backup") { Description = "Create .bak before modifying", DefaultValueFactory = (_) => true };
var cmd = new Command("fix-order", "Fix OpenXML element ordering per ISO 29500")
{
inputOption, outputOption, backupOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var output = parseResult.GetValue(outputOption) ?? input;
var backup = parseResult.GetValue(backupOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
if (backup && output == input)
File.Copy(input, input + ".bak", true);
var tempPath = Path.GetTempFileName();
File.Copy(input, tempPath, true);
using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
var entry = zip.GetEntry("word/document.xml");
if (entry == null)
{
Console.Error.WriteLine("Not a valid DOCX");
return;
}
XDocument doc;
using (var stream = entry.Open())
doc = XDocument.Load(stream);
int reorderedCount = 0;
foreach (var (parentName, order) in ElementOrder)
{
foreach (var parent in doc.Descendants(W + parentName))
{
var children = parent.Elements().ToList();
var sorted = children.OrderBy(e =>
{
var idx = order.IndexOf(e.Name.LocalName);
return idx >= 0 ? idx : order.Count;
}).ToList();
bool changed = false;
for (int i = 0; i < children.Count; i++)
{
if (children[i] != sorted[i])
{
changed = true;
break;
}
}
if (changed)
{
parent.ReplaceNodes(sorted);
reorderedCount++;
}
}
}
entry.Delete();
var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
using (var stream = newEntry.Open())
doc.Save(stream);
zip.Dispose();
File.Copy(tempPath, output, true);
File.Delete(tempPath);
Console.WriteLine($"Reordered {reorderedCount} element group(s)");
Console.WriteLine($"Written to: {output}");
});
return cmd;
}
}
@@ -0,0 +1,122 @@
using System.CommandLine;
using System.IO.Compression;
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.Commands;
public static class MergeRunsCommand
{
private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to optimize", Required = true };
var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
var dryRunOption = new Option<bool>("--dry-run") { Description = "Report without modifying" };
var cmd = new Command("merge-runs", "Merge adjacent runs with identical formatting")
{
inputOption, outputOption, dryRunOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var output = parseResult.GetValue(outputOption) ?? input;
var dryRun = parseResult.GetValue(dryRunOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
var tempPath = Path.GetTempFileName();
File.Copy(input, tempPath, true);
using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
var entry = zip.GetEntry("word/document.xml");
if (entry == null)
{
Console.Error.WriteLine("Not a valid DOCX: missing word/document.xml");
return;
}
XDocument doc;
using (var stream = entry.Open())
doc = XDocument.Load(stream);
int originalCount = 0;
int mergedCount = 0;
foreach (var p in doc.Descendants(W + "p"))
{
var runs = p.Elements(W + "r").ToList();
originalCount += runs.Count;
for (int i = runs.Count - 1; i > 0; i--)
{
var current = runs[i];
var previous = runs[i - 1];
var curProps = current.Element(W + "rPr")?.ToString() ?? "";
var prevProps = previous.Element(W + "rPr")?.ToString() ?? "";
if (curProps == prevProps)
{
// Only merge if both contain only text elements
var curChildren = current.Elements().Where(e => e.Name != W + "rPr").ToList();
var prevChildren = previous.Elements().Where(e => e.Name != W + "rPr").ToList();
if (curChildren.All(e => e.Name == W + "t") && prevChildren.All(e => e.Name == W + "t"))
{
var prevText = previous.Elements(W + "t").LastOrDefault();
var curText = current.Elements(W + "t").FirstOrDefault();
if (prevText != null && curText != null)
{
prevText.Value += curText.Value;
prevText.SetAttributeValue(XNamespace.Xml + "space", "preserve");
foreach (var extra in current.Elements(W + "t").Skip(1))
{
previous.Add(new XElement(extra));
}
current.Remove();
runs.RemoveAt(i);
}
}
}
}
mergedCount += runs.Count;
}
if (dryRun)
{
Console.WriteLine($"Original runs: {originalCount}");
Console.WriteLine($"After merge: {mergedCount}");
Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
File.Delete(tempPath);
return;
}
entry.Delete();
var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
using (var stream = newEntry.Open())
doc.Save(stream);
zip.Dispose();
File.Copy(tempPath, output, true);
File.Delete(tempPath);
Console.WriteLine($"Original runs: {originalCount}");
Console.WriteLine($"After merge: {mergedCount}");
Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
Console.WriteLine($"Written to: {output}");
});
return cmd;
}
}
@@ -0,0 +1,107 @@
using System.CommandLine;
using System.Text.Json;
using MiniMaxAIDocx.Core.Validation;
namespace MiniMaxAIDocx.Core.Commands;
public static class ValidateCommand
{
public static Command Create()
{
var inputOption = new Option<string>("--input") { Description = "DOCX file to validate", Required = true };
var xsdOption = new Option<string>("--xsd") { Description = "XSD schema path for XML validation" };
var businessOption = new Option<bool>("--business") { Description = "Run business rule validation" };
var gateCheckOption = new Option<string>("--gate-check") { Description = "Template DOCX for gate-check validation" };
var jsonOption = new Option<bool>("--json") { Description = "Output results as JSON" };
var cmd = new Command("validate", "Validate DOCX structure and content")
{
inputOption, xsdOption, businessOption, gateCheckOption, jsonOption
};
cmd.SetAction((parseResult) =>
{
var input = parseResult.GetValue(inputOption)!;
var xsd = parseResult.GetValue(xsdOption);
var business = parseResult.GetValue(businessOption);
var gateCheck = parseResult.GetValue(gateCheckOption);
var asJson = parseResult.GetValue(jsonOption);
if (!File.Exists(input))
{
Console.Error.WriteLine($"File not found: {input}");
return;
}
var combinedResult = new ValidationResult();
GateCheckResult? gateResult = null;
if (xsd != null)
{
var xsdValidator = new XsdValidator();
combinedResult.Merge(xsdValidator.Validate(input, xsd));
}
if (business)
{
var bizValidator = new BusinessRuleValidator();
combinedResult.Merge(bizValidator.Validate(input));
}
if (gateCheck != null)
{
var gateValidator = new GateCheckValidator();
gateResult = gateValidator.Validate(input, gateCheck);
}
if (asJson)
{
var output = new
{
isValid = combinedResult.IsValid && (gateResult?.Passed ?? true),
errors = combinedResult.Errors,
warnings = combinedResult.Warnings,
gateCheck = gateResult == null ? null : new
{
passed = gateResult.Passed,
violations = gateResult.Violations
}
};
Console.WriteLine(JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true }));
}
else
{
if (combinedResult.Errors.Count > 0)
{
Console.WriteLine($"ERRORS ({combinedResult.Errors.Count}):");
foreach (var e in combinedResult.Errors)
Console.WriteLine($" [{e.Severity}] {e.Message}" + (e.LineNumber > 0 ? $" (line {e.LineNumber}:{e.LinePosition})" : ""));
}
if (combinedResult.Warnings.Count > 0)
{
Console.WriteLine($"WARNINGS ({combinedResult.Warnings.Count}):");
foreach (var w in combinedResult.Warnings)
Console.WriteLine($" [{w.Severity}] {w.Message}");
}
if (gateResult != null)
{
Console.WriteLine(gateResult.Passed ? "GATE CHECK: PASSED" : "GATE CHECK: FAILED");
foreach (var v in gateResult.Violations)
Console.WriteLine($" - {v}");
}
if (combinedResult.IsValid && (gateResult?.Passed ?? true))
Console.WriteLine("Validation: PASSED");
else
Console.WriteLine("Validation: FAILED");
}
if (!combinedResult.IsValid || gateResult is { Passed: false })
Environment.ExitCode = 1;
});
return cmd;
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
<PackageReference Include="System.CommandLine" Version="2.0.5" />
</ItemGroup>
</Project>
@@ -0,0 +1,169 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Manages the 4-file comment system (comments.xml, commentsExtended.xml,
/// commentsIds.xml, commentsExtensible.xml) plus document.xml markers.
/// </summary>
public static class CommentSynchronizer
{
/// <summary>
/// Adds a comment to the document, updating all required parts.
/// </summary>
public static int AddComment(WordprocessingDocument doc, string text, string author, string rangeBookmark)
{
var mainPart = doc.MainDocumentPart
?? throw new InvalidOperationException("Document has no main part.");
int commentId = GetNextCommentId(doc);
// Ensure comments part exists
var commentsPart = mainPart.WordprocessingCommentsPart
?? mainPart.AddNewPart<WordprocessingCommentsPart>();
if (commentsPart.Comments == null)
commentsPart.Comments = new Comments();
// Create the comment
var comment = new Comment
{
Id = commentId.ToString(),
Author = author,
Date = DateTime.UtcNow,
Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
};
comment.Append(new Paragraph(new Run(new Text(text))));
commentsPart.Comments.Append(comment);
// Add range markers in document body
var body = mainPart.Document.Body;
if (body != null)
{
// Find bookmark or append at end
var rangeStart = new CommentRangeStart { Id = commentId.ToString() };
var rangeEnd = new CommentRangeEnd { Id = commentId.ToString() };
var reference = new Run(new CommentReference { Id = commentId.ToString() });
body.Append(rangeStart);
body.Append(rangeEnd);
body.Append(new Paragraph(reference));
}
return commentId;
}
/// <summary>
/// Adds a reply to an existing comment.
/// </summary>
public static int AddReply(WordprocessingDocument doc, int parentCommentId, string text, string author)
{
var mainPart = doc.MainDocumentPart
?? throw new InvalidOperationException("Document has no main part.");
var commentsPart = mainPart.WordprocessingCommentsPart
?? throw new InvalidOperationException("Document has no comments part.");
int replyId = GetNextCommentId(doc);
var reply = new Comment
{
Id = replyId.ToString(),
Author = author,
Date = DateTime.UtcNow,
Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
};
reply.Append(new Paragraph(new Run(new Text(text))));
commentsPart.Comments?.Append(reply);
// Link reply to parent via commentsExtended.xml
LinkReplyToParent(doc, replyId, parentCommentId);
return replyId;
}
/// <summary>
/// Marks a comment as resolved/done by setting done="1" in commentsExtended.xml.
/// Uses raw XML manipulation since these extended parts lack typed SDK support.
/// </summary>
public static void ResolveComment(WordprocessingDocument doc, int commentId)
{
var mainPart = doc.MainDocumentPart;
if (mainPart == null) return;
// commentsExtended.xml is an untyped part — manipulate via raw XML
const string ceUri = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
foreach (var part in mainPart.Parts)
{
if (part.OpenXmlPart.ContentType.Contains("commentsExtensible"))
{
using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
var xdoc = System.Xml.Linq.XDocument.Load(stream);
var ns = System.Xml.Linq.XNamespace.Get(ceUri);
var commentEl = xdoc.Descendants(ns + "comment")
.FirstOrDefault(e => e.Attribute(ns + "paraId")?.Value != null);
// Set done flag if element found for this comment
if (commentEl != null)
{
commentEl.SetAttributeValue("done", "1");
stream.SetLength(0);
xdoc.Save(stream);
}
return;
}
}
}
/// <summary>
/// Links a reply comment to its parent via commentsExtended.xml (w15:commentEx).
/// Uses raw XML since the extended comment parts lack typed SDK support.
/// </summary>
private static void LinkReplyToParent(WordprocessingDocument doc, int replyId, int parentCommentId)
{
var mainPart = doc.MainDocumentPart;
if (mainPart == null) return;
const string w15Uri = "http://schemas.microsoft.com/office/word/2012/wordml";
var w15 = System.Xml.Linq.XNamespace.Get(w15Uri);
// Find or create commentsExtended part
foreach (var part in mainPart.Parts)
{
if (part.OpenXmlPart.ContentType.Contains("commentsExtended"))
{
using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
var xdoc = System.Xml.Linq.XDocument.Load(stream);
var root = xdoc.Root;
if (root == null) return;
root.Add(new System.Xml.Linq.XElement(w15 + "commentEx",
new System.Xml.Linq.XAttribute(w15 + "paraId", replyId.ToString("X8")),
new System.Xml.Linq.XAttribute(w15 + "paraIdParent", parentCommentId.ToString("X8")),
new System.Xml.Linq.XAttribute(w15 + "done", "0")));
stream.SetLength(0);
xdoc.Save(stream);
return;
}
}
}
/// <summary>
/// Finds the maximum existing comment ID and returns the next one.
/// </summary>
public static int GetNextCommentId(WordprocessingDocument doc)
{
var commentsPart = doc.MainDocumentPart?.WordprocessingCommentsPart;
if (commentsPart?.Comments == null) return 1;
int maxId = 0;
foreach (var comment in commentsPart.Comments.Elements<Comment>())
{
if (comment.Id?.Value != null && int.TryParse(comment.Id.Value, out int id) && id > maxId)
maxId = id;
}
return maxId + 1;
}
}
@@ -0,0 +1,80 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// Defines canonical child element ordering for key OpenXML parent elements
/// and provides reordering utilities.
/// </summary>
public static class ElementOrder
{
private static readonly Dictionary<string, string[]> OrderMap = new()
{
["w:body"] = ["w:p", "w:tbl", "w:sdt", "w:sectPr"],
["w:p"] = ["w:pPr", "w:hyperlink", "w:r", "w:ins", "w:del", "w:bookmarkStart", "w:bookmarkEnd", "w:commentRangeStart", "w:commentRangeEnd", "w:fldSimple"],
["w:pPr"] = ["w:pStyle", "w:keepNext", "w:keepLines", "w:pageBreakBefore", "w:widowControl", "w:numPr", "w:pBdr", "w:shd", "w:tabs", "w:suppressAutoHyphens", "w:spacing", "w:ind", "w:jc", "w:rPr", "w:sectPr", "w:pPrChange"],
["w:r"] = ["w:rPr", "w:t", "w:br", "w:tab", "w:cr", "w:sym", "w:drawing", "w:delText", "w:fldChar", "w:instrText", "w:lastRenderedPageBreak", "w:noBreakHyphen", "w:softHyphen"],
["w:rPr"] = ["w:rStyle", "w:rFonts", "w:b", "w:bCs", "w:i", "w:iCs", "w:caps", "w:smallCaps", "w:strike", "w:dstrike", "w:vanish", "w:color", "w:sz", "w:szCs", "w:u", "w:shd", "w:highlight", "w:lang", "w:rPrChange"],
["w:tbl"] = ["w:tblPr", "w:tblGrid", "w:tr"],
["w:tblPr"] = ["w:tblStyle", "w:tblpPr", "w:tblOverlap", "w:tblW", "w:jc", "w:tblCellSpacing", "w:tblInd", "w:tblBorders", "w:shd", "w:tblLayout", "w:tblCellMar", "w:tblLook", "w:tblPrChange"],
["w:tr"] = ["w:trPr", "w:tc"],
["w:trPr"] = ["w:cnfStyle", "w:divId", "w:gridBefore", "w:gridAfter", "w:wBefore", "w:wAfter", "w:cantSplit", "w:trHeight", "w:tblHeader", "w:tblCellSpacing", "w:jc", "w:hidden", "w:ins", "w:del", "w:trPrChange"],
["w:tc"] = ["w:tcPr", "w:p", "w:tbl"],
["w:tcPr"] = ["w:cnfStyle", "w:tcW", "w:gridSpan", "w:hMerge", "w:vMerge", "w:tcBorders", "w:shd", "w:noWrap", "w:tcMar", "w:textDirection", "w:tcFitText", "w:vAlign", "w:hideMark", "w:headers", "w:cellIns", "w:cellDel", "w:cellMerge", "w:tcPrChange"],
["w:sectPr"] = ["w:headerReference", "w:footerReference", "w:type", "w:pgSz", "w:pgMar", "w:paperSrc", "w:pgBorders", "w:lnNumType", "w:pgNumType", "w:cols", "w:formProt", "w:vAlign", "w:noEndnote", "w:titlePg", "w:textDirection", "w:bidi", "w:rtlGutter", "w:docGrid"],
["w:hdr"] = ["w:p", "w:tbl", "w:sdt"],
["w:ftr"] = ["w:p", "w:tbl", "w:sdt"],
};
/// <summary>
/// Returns the canonical child ordering for a given parent element name (e.g. "w:p").
/// Returns null if no ordering is defined.
/// </summary>
public static string[]? GetChildOrder(string parentElement)
{
return OrderMap.TryGetValue(parentElement, out var order) ? order : null;
}
/// <summary>
/// Reorders children of the given XElement according to the canonical ordering rules.
/// Children not listed in the ordering are placed at the end in their original order.
/// </summary>
public static void ReorderChildren(XElement parent)
{
var qualifiedName = GetQualifiedName(parent);
var order = GetChildOrder(qualifiedName);
if (order == null) return;
var children = parent.Elements().ToList();
if (children.Count <= 1) return;
var orderIndex = new Dictionary<string, int>();
for (int i = 0; i < order.Length; i++)
orderIndex[order[i]] = i;
int unknownBase = order.Length;
int unknownCounter = 0;
var sorted = children
.Select(c => (Element: c, QName: GetQualifiedName(c)))
.OrderBy(x => orderIndex.TryGetValue(x.QName, out var idx) ? idx : unknownBase + unknownCounter++)
.Select(x => x.Element)
.ToList();
parent.RemoveNodes();
foreach (var child in sorted)
parent.Add(child);
}
private static string GetQualifiedName(XElement element)
{
var ns = element.Name.Namespace;
var local = element.Name.LocalName;
if (ns == Ns.W) return $"w:{local}";
if (ns == Ns.R) return $"r:{local}";
if (ns == Ns.MC) return $"mc:{local}";
return local;
}
}
@@ -0,0 +1,42 @@
using System.Xml.Linq;
namespace MiniMaxAIDocx.Core.OpenXml;
/// <summary>
/// All OpenXML namespace URIs and common content/relationship type constants.
/// </summary>
public static class Ns
{
public static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
public static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
public static readonly XNamespace WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
public static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main";
public static readonly XNamespace MC = "http://schemas.openxmlformats.org/markup-compatibility/2006";
public static readonly XNamespace PIC = "http://schemas.openxmlformats.org/drawingml/2006/picture";
public static readonly XNamespace W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
public static readonly XNamespace W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
public static readonly XNamespace W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
public static readonly XNamespace W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
public static readonly XNamespace WPC = "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas";
public static readonly XNamespace WPS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
// Content types
public const string MainDocumentContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
public const string StylesContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
public const string HeaderContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
public const string FooterContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
public const string CommentsContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
// Relationship types
public const string DocumentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
public const string StylesRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
public const string HeaderRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
public const string FooterRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
public const string CommentsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
public const string ImageRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
public const string HyperlinkRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
public const string NumberingRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
public const string FontTableRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
public const string ThemeRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
public const string SettingsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
}
Loaded 100 of 735 files, more files were not shown because too many files have changed in this diff. Show more