@nguyenphp/antigravity-marketing 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +130 -78
  2. package/package.json +4 -3
  3. package/templates/.agent/skills/marketing-report-expert/SKILL.md +70 -0
  4. package/templates/.agent/skills/minimax-docx/LICENSE +21 -0
  5. package/templates/.agent/skills/minimax-docx/SKILL.md +274 -0
  6. package/templates/.agent/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
  7. package/templates/.agent/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
  8. package/templates/.agent/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
  9. package/templates/.agent/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
  10. package/templates/.agent/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
  11. package/templates/.agent/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
  12. package/templates/.agent/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
  13. package/templates/.agent/skills/minimax-docx/references/cjk_typography.md +357 -0
  14. package/templates/.agent/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
  15. package/templates/.agent/skills/minimax-docx/references/comments_guide.md +191 -0
  16. package/templates/.agent/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
  17. package/templates/.agent/skills/minimax-docx/references/design_principles.md +819 -0
  18. package/templates/.agent/skills/minimax-docx/references/openxml_element_order.md +308 -0
  19. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
  20. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
  21. package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
  22. package/templates/.agent/skills/minimax-docx/references/openxml_namespaces.md +82 -0
  23. package/templates/.agent/skills/minimax-docx/references/openxml_units.md +72 -0
  24. package/templates/.agent/skills/minimax-docx/references/scenario_a_create.md +284 -0
  25. package/templates/.agent/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
  26. package/templates/.agent/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
  27. package/templates/.agent/skills/minimax-docx/references/track_changes_guide.md +200 -0
  28. package/templates/.agent/skills/minimax-docx/references/troubleshooting.md +506 -0
  29. package/templates/.agent/skills/minimax-docx/references/typography_guide.md +294 -0
  30. package/templates/.agent/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
  31. package/templates/.agent/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
  32. package/templates/.agent/skills/minimax-docx/scripts/docx_preview.sh +37 -0
  33. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
  34. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
  35. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
  36. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
  37. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
  38. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
  39. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
  40. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
  41. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
  42. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
  43. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
  44. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
  45. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
  46. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
  47. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
  48. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
  49. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
  50. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
  51. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
  52. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
  53. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
  54. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
  55. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
  56. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
  57. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
  58. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
  59. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
  60. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
  61. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
  62. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
  63. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
  64. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
  65. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
  66. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
  67. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
  68. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
  69. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
  70. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
  71. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
  72. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
  73. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
  74. package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
  75. package/templates/.agent/skills/minimax-docx/scripts/env_check.sh +196 -0
  76. package/templates/.agent/skills/minimax-docx/scripts/setup.ps1 +274 -0
  77. package/templates/.agent/skills/minimax-docx/scripts/setup.sh +504 -0
  78. package/templates/.agent/skills/minimax-multimodal-toolkit/SKILL.md +359 -0
  79. package/templates/.agent/skills/minimax-pdf/README.md +222 -0
  80. package/templates/.agent/skills/minimax-pdf/SKILL.md +201 -0
  81. package/templates/.agent/skills/minimax-pdf/design/design.md +381 -0
  82. package/templates/.agent/skills/minimax-pdf/scripts/cover.py +1579 -0
  83. package/templates/.agent/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
  84. package/templates/.agent/skills/minimax-pdf/scripts/fill_write.py +242 -0
  85. package/templates/.agent/skills/minimax-pdf/scripts/make.sh +491 -0
  86. package/templates/.agent/skills/minimax-pdf/scripts/merge.py +112 -0
  87. package/templates/.agent/skills/minimax-pdf/scripts/palette.py +559 -0
  88. package/templates/.agent/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
  89. package/templates/.agent/skills/minimax-pdf/scripts/render_body.py +1055 -0
  90. package/templates/.agent/skills/minimax-pdf/scripts/render_cover.cjs +111 -0
  91. package/templates/.agent/skills/minimax-xlsx/SKILL.md +138 -0
  92. package/templates/.agent/skills/minimax-xlsx/references/create.md +691 -0
  93. package/templates/.agent/skills/minimax-xlsx/references/edit.md +684 -0
  94. package/templates/.agent/skills/minimax-xlsx/references/fix.md +37 -0
  95. package/templates/.agent/skills/minimax-xlsx/references/format.md +768 -0
  96. package/templates/.agent/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
  97. package/templates/.agent/skills/minimax-xlsx/references/read-analyze.md +97 -0
  98. package/templates/.agent/skills/minimax-xlsx/references/validate.md +772 -0
  99. package/templates/.agent/skills/minimax-xlsx/scripts/formula_check.py +422 -0
  100. package/templates/.agent/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
  101. package/templates/.agent/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
  102. package/templates/.agent/skills/minimax-xlsx/scripts/style_audit.py +575 -0
  103. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
  104. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
  105. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
  106. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
  107. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
  108. package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
  109. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
  110. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
  111. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
  112. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
  113. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
  114. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
  115. package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
  116. package/templates/.agent/skills/pptx-generator/SKILL.md +249 -0
  117. package/templates/.agent/skills/pptx-generator/references/design-system.md +392 -0
  118. package/templates/.agent/skills/pptx-generator/references/editing.md +162 -0
  119. package/templates/.agent/skills/pptx-generator/references/pitfalls.md +112 -0
  120. package/templates/.agent/skills/pptx-generator/references/pptxgenjs.md +420 -0
  121. package/templates/.agent/skills/pptx-generator/references/slide-types.md +413 -0
  122. package/templates/.agent/skills/tutorial-video-expert/SKILL.md +88 -0
  123. package/templates/.agent/skills/ui-ux-pro-max/SKILL.md +170 -585
  124. package/templates/.agent/skills/vision-analysis/SKILL.md +174 -0
  125. package/templates/.agent/workflows/analyze.md +3 -0
  126. package/templates/.agent/workflows/brand-report.md +44 -0
  127. package/templates/.agent/workflows/report.md +49 -0
  128. package/templates/.agent/agents/backend-specialist.md +0 -263
  129. package/templates/.agent/agents/database-architect.md +0 -226
  130. package/templates/.agent/agents/debugger.md +0 -225
  131. package/templates/.agent/agents/devops-engineer.md +0 -242
  132. package/templates/.agent/agents/frontend-specialist.md +0 -527
  133. package/templates/.agent/agents/game-developer.md +0 -162
  134. package/templates/.agent/agents/mobile-developer.md +0 -377
  135. package/templates/.agent/agents/penetration-tester.md +0 -188
  136. package/templates/.agent/agents/security-auditor.md +0 -170
  137. package/templates/.agent/agents/test-engineer.md +0 -158
  138. package/templates/.agent/skills/api-patterns/SKILL.md +0 -81
  139. package/templates/.agent/skills/api-patterns/api-style.md +0 -42
  140. package/templates/.agent/skills/api-patterns/auth.md +0 -24
  141. package/templates/.agent/skills/api-patterns/documentation.md +0 -26
  142. package/templates/.agent/skills/api-patterns/graphql.md +0 -41
  143. package/templates/.agent/skills/api-patterns/rate-limiting.md +0 -31
  144. package/templates/.agent/skills/api-patterns/response.md +0 -37
  145. package/templates/.agent/skills/api-patterns/rest.md +0 -40
  146. package/templates/.agent/skills/api-patterns/scripts/api_validator.py +0 -211
  147. package/templates/.agent/skills/api-patterns/security-testing.md +0 -122
  148. package/templates/.agent/skills/api-patterns/trpc.md +0 -41
  149. package/templates/.agent/skills/api-patterns/versioning.md +0 -22
  150. package/templates/.agent/skills/app-builder/SKILL.md +0 -75
  151. package/templates/.agent/skills/app-builder/agent-coordination.md +0 -71
  152. package/templates/.agent/skills/app-builder/feature-building.md +0 -53
  153. package/templates/.agent/skills/app-builder/project-detection.md +0 -34
  154. package/templates/.agent/skills/app-builder/scaffolding.md +0 -118
  155. package/templates/.agent/skills/app-builder/tech-stack.md +0 -40
  156. package/templates/.agent/skills/app-builder/templates/SKILL.md +0 -39
  157. package/templates/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +0 -76
  158. package/templates/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +0 -92
  159. package/templates/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +0 -88
  160. package/templates/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +0 -88
  161. package/templates/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +0 -83
  162. package/templates/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +0 -90
  163. package/templates/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +0 -90
  164. package/templates/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +0 -82
  165. package/templates/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +0 -100
  166. package/templates/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +0 -106
  167. package/templates/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +0 -101
  168. package/templates/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +0 -83
  169. package/templates/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +0 -93
  170. package/templates/.agent/skills/architecture/SKILL.md +0 -55
  171. package/templates/.agent/skills/architecture/context-discovery.md +0 -43
  172. package/templates/.agent/skills/architecture/examples.md +0 -94
  173. package/templates/.agent/skills/architecture/pattern-selection.md +0 -68
  174. package/templates/.agent/skills/architecture/patterns-reference.md +0 -50
  175. package/templates/.agent/skills/architecture/trade-off-analysis.md +0 -77
  176. package/templates/.agent/skills/bash-linux/SKILL.md +0 -199
  177. package/templates/.agent/skills/behavioral-modes/SKILL.md +0 -242
  178. package/templates/.agent/skills/clean-code/SKILL.md +0 -201
  179. package/templates/.agent/skills/code-review-checklist/SKILL.md +0 -109
  180. package/templates/.agent/skills/database-design/SKILL.md +0 -52
  181. package/templates/.agent/skills/database-design/database-selection.md +0 -43
  182. package/templates/.agent/skills/database-design/indexing.md +0 -39
  183. package/templates/.agent/skills/database-design/migrations.md +0 -48
  184. package/templates/.agent/skills/database-design/optimization.md +0 -36
  185. package/templates/.agent/skills/database-design/orm-selection.md +0 -30
  186. package/templates/.agent/skills/database-design/schema-design.md +0 -56
  187. package/templates/.agent/skills/database-design/scripts/schema_validator.py +0 -172
  188. package/templates/.agent/skills/deployment-procedures/SKILL.md +0 -241
  189. package/templates/.agent/skills/docker-expert/SKILL.md +0 -409
  190. package/templates/.agent/skills/game-development/2d-games/SKILL.md +0 -119
  191. package/templates/.agent/skills/game-development/3d-games/SKILL.md +0 -135
  192. package/templates/.agent/skills/game-development/SKILL.md +0 -167
  193. package/templates/.agent/skills/game-development/game-art/SKILL.md +0 -185
  194. package/templates/.agent/skills/game-development/game-audio/SKILL.md +0 -190
  195. package/templates/.agent/skills/game-development/game-design/SKILL.md +0 -129
  196. package/templates/.agent/skills/game-development/mobile-games/SKILL.md +0 -108
  197. package/templates/.agent/skills/game-development/multiplayer/SKILL.md +0 -132
  198. package/templates/.agent/skills/game-development/pc-games/SKILL.md +0 -144
  199. package/templates/.agent/skills/game-development/vr-ar/SKILL.md +0 -123
  200. package/templates/.agent/skills/game-development/web-games/SKILL.md +0 -150
  201. package/templates/.agent/skills/lint-and-validate/SKILL.md +0 -45
  202. package/templates/.agent/skills/lint-and-validate/scripts/lint_runner.py +0 -172
  203. package/templates/.agent/skills/lint-and-validate/scripts/type_coverage.py +0 -173
  204. package/templates/.agent/skills/mcp-builder/SKILL.md +0 -176
  205. package/templates/.agent/skills/nestjs-expert/SKILL.md +0 -552
  206. package/templates/.agent/skills/nextjs-best-practices/SKILL.md +0 -203
  207. package/templates/.agent/skills/nodejs-best-practices/SKILL.md +0 -333
  208. package/templates/.agent/skills/parallel-agents/SKILL.md +0 -175
  209. package/templates/.agent/skills/performance-profiling/SKILL.md +0 -143
  210. package/templates/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +0 -76
  211. package/templates/.agent/skills/powershell-windows/SKILL.md +0 -167
  212. package/templates/.agent/skills/prisma-expert/SKILL.md +0 -355
  213. package/templates/.agent/skills/python-patterns/SKILL.md +0 -441
  214. package/templates/.agent/skills/react-patterns/SKILL.md +0 -198
  215. package/templates/.agent/skills/red-team-tactics/SKILL.md +0 -199
  216. package/templates/.agent/skills/server-management/SKILL.md +0 -161
  217. package/templates/.agent/skills/systematic-debugging/SKILL.md +0 -109
  218. package/templates/.agent/skills/tdd-workflow/SKILL.md +0 -149
  219. package/templates/.agent/skills/testing-patterns/SKILL.md +0 -178
  220. package/templates/.agent/skills/testing-patterns/scripts/test_runner.py +0 -219
  221. package/templates/.agent/skills/typescript-expert/SKILL.md +0 -429
  222. package/templates/.agent/skills/vue-expert/SKILL.md +0 -374
  223. package/templates/.agent/skills/vulnerability-scanner/SKILL.md +0 -276
  224. package/templates/.agent/skills/vulnerability-scanner/checklists.md +0 -121
  225. package/templates/.agent/skills/vulnerability-scanner/scripts/security_scan.py +0 -458
  226. package/templates/.agent/skills/webapp-testing/SKILL.md +0 -187
  227. package/templates/.agent/skills/webapp-testing/scripts/playwright_runner.py +0 -173
  228. package/templates/.agent/workflows/debug.md +0 -103
  229. package/templates/.agent/workflows/deploy.md +0 -176
  230. package/templates/.agent/workflows/enhance.md +0 -63
  231. package/templates/.agent/workflows/test.md +0 -144
@@ -0,0 +1,122 @@
1
+ using System.CommandLine;
2
+ using System.IO.Compression;
3
+ using System.Xml.Linq;
4
+
5
+ namespace MiniMaxAIDocx.Core.Commands;
6
+
7
+ public static class MergeRunsCommand
8
+ {
9
+ private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
10
+
11
+ public static Command Create()
12
+ {
13
+ var inputOption = new Option<string>("--input") { Description = "DOCX file to optimize", Required = true };
14
+ var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
15
+ var dryRunOption = new Option<bool>("--dry-run") { Description = "Report without modifying" };
16
+
17
+ var cmd = new Command("merge-runs", "Merge adjacent runs with identical formatting")
18
+ {
19
+ inputOption, outputOption, dryRunOption
20
+ };
21
+
22
+ cmd.SetAction((parseResult) =>
23
+ {
24
+ var input = parseResult.GetValue(inputOption)!;
25
+ var output = parseResult.GetValue(outputOption) ?? input;
26
+ var dryRun = parseResult.GetValue(dryRunOption);
27
+
28
+ if (!File.Exists(input))
29
+ {
30
+ Console.Error.WriteLine($"File not found: {input}");
31
+ return;
32
+ }
33
+
34
+ var tempPath = Path.GetTempFileName();
35
+ File.Copy(input, tempPath, true);
36
+
37
+ using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
38
+ var entry = zip.GetEntry("word/document.xml");
39
+ if (entry == null)
40
+ {
41
+ Console.Error.WriteLine("Not a valid DOCX: missing word/document.xml");
42
+ return;
43
+ }
44
+
45
+ XDocument doc;
46
+ using (var stream = entry.Open())
47
+ doc = XDocument.Load(stream);
48
+
49
+ int originalCount = 0;
50
+ int mergedCount = 0;
51
+
52
+ foreach (var p in doc.Descendants(W + "p"))
53
+ {
54
+ var runs = p.Elements(W + "r").ToList();
55
+ originalCount += runs.Count;
56
+
57
+ for (int i = runs.Count - 1; i > 0; i--)
58
+ {
59
+ var current = runs[i];
60
+ var previous = runs[i - 1];
61
+
62
+ var curProps = current.Element(W + "rPr")?.ToString() ?? "";
63
+ var prevProps = previous.Element(W + "rPr")?.ToString() ?? "";
64
+
65
+ if (curProps == prevProps)
66
+ {
67
+ // Only merge if both contain only text elements
68
+ var curChildren = current.Elements().Where(e => e.Name != W + "rPr").ToList();
69
+ var prevChildren = previous.Elements().Where(e => e.Name != W + "rPr").ToList();
70
+
71
+ if (curChildren.All(e => e.Name == W + "t") && prevChildren.All(e => e.Name == W + "t"))
72
+ {
73
+ var prevText = previous.Elements(W + "t").LastOrDefault();
74
+ var curText = current.Elements(W + "t").FirstOrDefault();
75
+
76
+ if (prevText != null && curText != null)
77
+ {
78
+ prevText.Value += curText.Value;
79
+ prevText.SetAttributeValue(XNamespace.Xml + "space", "preserve");
80
+
81
+ foreach (var extra in current.Elements(W + "t").Skip(1))
82
+ {
83
+ previous.Add(new XElement(extra));
84
+ }
85
+
86
+ current.Remove();
87
+ runs.RemoveAt(i);
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ mergedCount += runs.Count;
94
+ }
95
+
96
+ if (dryRun)
97
+ {
98
+ Console.WriteLine($"Original runs: {originalCount}");
99
+ Console.WriteLine($"After merge: {mergedCount}");
100
+ Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
101
+ File.Delete(tempPath);
102
+ return;
103
+ }
104
+
105
+ entry.Delete();
106
+ var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
107
+ using (var stream = newEntry.Open())
108
+ doc.Save(stream);
109
+
110
+ zip.Dispose();
111
+ File.Copy(tempPath, output, true);
112
+ File.Delete(tempPath);
113
+
114
+ Console.WriteLine($"Original runs: {originalCount}");
115
+ Console.WriteLine($"After merge: {mergedCount}");
116
+ Console.WriteLine($"Reduction: {(originalCount > 0 ? (originalCount - mergedCount) * 100.0 / originalCount : 0):F1}%");
117
+ Console.WriteLine($"Written to: {output}");
118
+ });
119
+
120
+ return cmd;
121
+ }
122
+ }
@@ -0,0 +1,107 @@
1
+ using System.CommandLine;
2
+ using System.Text.Json;
3
+ using MiniMaxAIDocx.Core.Validation;
4
+
5
+ namespace MiniMaxAIDocx.Core.Commands;
6
+
7
+ public static class ValidateCommand
8
+ {
9
+ public static Command Create()
10
+ {
11
+ var inputOption = new Option<string>("--input") { Description = "DOCX file to validate", Required = true };
12
+ var xsdOption = new Option<string>("--xsd") { Description = "XSD schema path for XML validation" };
13
+ var businessOption = new Option<bool>("--business") { Description = "Run business rule validation" };
14
+ var gateCheckOption = new Option<string>("--gate-check") { Description = "Template DOCX for gate-check validation" };
15
+ var jsonOption = new Option<bool>("--json") { Description = "Output results as JSON" };
16
+
17
+ var cmd = new Command("validate", "Validate DOCX structure and content")
18
+ {
19
+ inputOption, xsdOption, businessOption, gateCheckOption, jsonOption
20
+ };
21
+
22
+ cmd.SetAction((parseResult) =>
23
+ {
24
+ var input = parseResult.GetValue(inputOption)!;
25
+ var xsd = parseResult.GetValue(xsdOption);
26
+ var business = parseResult.GetValue(businessOption);
27
+ var gateCheck = parseResult.GetValue(gateCheckOption);
28
+ var asJson = parseResult.GetValue(jsonOption);
29
+
30
+ if (!File.Exists(input))
31
+ {
32
+ Console.Error.WriteLine($"File not found: {input}");
33
+ return;
34
+ }
35
+
36
+ var combinedResult = new ValidationResult();
37
+ GateCheckResult? gateResult = null;
38
+
39
+ if (xsd != null)
40
+ {
41
+ var xsdValidator = new XsdValidator();
42
+ combinedResult.Merge(xsdValidator.Validate(input, xsd));
43
+ }
44
+
45
+ if (business)
46
+ {
47
+ var bizValidator = new BusinessRuleValidator();
48
+ combinedResult.Merge(bizValidator.Validate(input));
49
+ }
50
+
51
+ if (gateCheck != null)
52
+ {
53
+ var gateValidator = new GateCheckValidator();
54
+ gateResult = gateValidator.Validate(input, gateCheck);
55
+ }
56
+
57
+ if (asJson)
58
+ {
59
+ var output = new
60
+ {
61
+ isValid = combinedResult.IsValid && (gateResult?.Passed ?? true),
62
+ errors = combinedResult.Errors,
63
+ warnings = combinedResult.Warnings,
64
+ gateCheck = gateResult == null ? null : new
65
+ {
66
+ passed = gateResult.Passed,
67
+ violations = gateResult.Violations
68
+ }
69
+ };
70
+ Console.WriteLine(JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true }));
71
+ }
72
+ else
73
+ {
74
+ if (combinedResult.Errors.Count > 0)
75
+ {
76
+ Console.WriteLine($"ERRORS ({combinedResult.Errors.Count}):");
77
+ foreach (var e in combinedResult.Errors)
78
+ Console.WriteLine($" [{e.Severity}] {e.Message}" + (e.LineNumber > 0 ? $" (line {e.LineNumber}:{e.LinePosition})" : ""));
79
+ }
80
+
81
+ if (combinedResult.Warnings.Count > 0)
82
+ {
83
+ Console.WriteLine($"WARNINGS ({combinedResult.Warnings.Count}):");
84
+ foreach (var w in combinedResult.Warnings)
85
+ Console.WriteLine($" [{w.Severity}] {w.Message}");
86
+ }
87
+
88
+ if (gateResult != null)
89
+ {
90
+ Console.WriteLine(gateResult.Passed ? "GATE CHECK: PASSED" : "GATE CHECK: FAILED");
91
+ foreach (var v in gateResult.Violations)
92
+ Console.WriteLine($" - {v}");
93
+ }
94
+
95
+ if (combinedResult.IsValid && (gateResult?.Passed ?? true))
96
+ Console.WriteLine("Validation: PASSED");
97
+ else
98
+ Console.WriteLine("Validation: FAILED");
99
+ }
100
+
101
+ if (!combinedResult.IsValid || gateResult is { Passed: false })
102
+ Environment.ExitCode = 1;
103
+ });
104
+
105
+ return cmd;
106
+ }
107
+ }
@@ -0,0 +1,15 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net8.0</TargetFramework>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <Nullable>enable</Nullable>
7
+ <NeutralLanguage>en</NeutralLanguage>
8
+ </PropertyGroup>
9
+
10
+ <ItemGroup>
11
+ <PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
12
+ <PackageReference Include="System.CommandLine" Version="2.0.5" />
13
+ </ItemGroup>
14
+
15
+ </Project>
@@ -0,0 +1,169 @@
1
+ using DocumentFormat.OpenXml;
2
+ using DocumentFormat.OpenXml.Packaging;
3
+ using DocumentFormat.OpenXml.Wordprocessing;
4
+
5
+ namespace MiniMaxAIDocx.Core.OpenXml;
6
+
7
+ /// <summary>
8
+ /// Manages the 4-file comment system (comments.xml, commentsExtended.xml,
9
+ /// commentsIds.xml, commentsExtensible.xml) plus document.xml markers.
10
+ /// </summary>
11
+ public static class CommentSynchronizer
12
+ {
13
+ /// <summary>
14
+ /// Adds a comment to the document, updating all required parts.
15
+ /// </summary>
16
+ public static int AddComment(WordprocessingDocument doc, string text, string author, string rangeBookmark)
17
+ {
18
+ var mainPart = doc.MainDocumentPart
19
+ ?? throw new InvalidOperationException("Document has no main part.");
20
+
21
+ int commentId = GetNextCommentId(doc);
22
+
23
+ // Ensure comments part exists
24
+ var commentsPart = mainPart.WordprocessingCommentsPart
25
+ ?? mainPart.AddNewPart<WordprocessingCommentsPart>();
26
+
27
+ if (commentsPart.Comments == null)
28
+ commentsPart.Comments = new Comments();
29
+
30
+ // Create the comment
31
+ var comment = new Comment
32
+ {
33
+ Id = commentId.ToString(),
34
+ Author = author,
35
+ Date = DateTime.UtcNow,
36
+ Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
37
+ };
38
+ comment.Append(new Paragraph(new Run(new Text(text))));
39
+ commentsPart.Comments.Append(comment);
40
+
41
+ // Add range markers in document body
42
+ var body = mainPart.Document.Body;
43
+ if (body != null)
44
+ {
45
+ // Find bookmark or append at end
46
+ var rangeStart = new CommentRangeStart { Id = commentId.ToString() };
47
+ var rangeEnd = new CommentRangeEnd { Id = commentId.ToString() };
48
+ var reference = new Run(new CommentReference { Id = commentId.ToString() });
49
+
50
+ body.Append(rangeStart);
51
+ body.Append(rangeEnd);
52
+ body.Append(new Paragraph(reference));
53
+ }
54
+
55
+ return commentId;
56
+ }
57
+
58
+ /// <summary>
59
+ /// Adds a reply to an existing comment.
60
+ /// </summary>
61
+ public static int AddReply(WordprocessingDocument doc, int parentCommentId, string text, string author)
62
+ {
63
+ var mainPart = doc.MainDocumentPart
64
+ ?? throw new InvalidOperationException("Document has no main part.");
65
+
66
+ var commentsPart = mainPart.WordprocessingCommentsPart
67
+ ?? throw new InvalidOperationException("Document has no comments part.");
68
+
69
+ int replyId = GetNextCommentId(doc);
70
+
71
+ var reply = new Comment
72
+ {
73
+ Id = replyId.ToString(),
74
+ Author = author,
75
+ Date = DateTime.UtcNow,
76
+ Initials = author.Length > 0 ? author[..1].ToUpperInvariant() : "A"
77
+ };
78
+ reply.Append(new Paragraph(new Run(new Text(text))));
79
+ commentsPart.Comments?.Append(reply);
80
+
81
+ // Link reply to parent via commentsExtended.xml
82
+ LinkReplyToParent(doc, replyId, parentCommentId);
83
+
84
+ return replyId;
85
+ }
86
+
87
+ /// <summary>
88
+ /// Marks a comment as resolved/done by setting done="1" in commentsExtended.xml.
89
+ /// Uses raw XML manipulation since these extended parts lack typed SDK support.
90
+ /// </summary>
91
+ public static void ResolveComment(WordprocessingDocument doc, int commentId)
92
+ {
93
+ var mainPart = doc.MainDocumentPart;
94
+ if (mainPart == null) return;
95
+
96
+ // commentsExtended.xml is an untyped part — manipulate via raw XML
97
+ const string ceUri = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
98
+ foreach (var part in mainPart.Parts)
99
+ {
100
+ if (part.OpenXmlPart.ContentType.Contains("commentsExtensible"))
101
+ {
102
+ using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
103
+ var xdoc = System.Xml.Linq.XDocument.Load(stream);
104
+ var ns = System.Xml.Linq.XNamespace.Get(ceUri);
105
+ var commentEl = xdoc.Descendants(ns + "comment")
106
+ .FirstOrDefault(e => e.Attribute(ns + "paraId")?.Value != null);
107
+ // Set done flag if element found for this comment
108
+ if (commentEl != null)
109
+ {
110
+ commentEl.SetAttributeValue("done", "1");
111
+ stream.SetLength(0);
112
+ xdoc.Save(stream);
113
+ }
114
+ return;
115
+ }
116
+ }
117
+ }
118
+
119
+ /// <summary>
120
+ /// Links a reply comment to its parent via commentsExtended.xml (w15:commentEx).
121
+ /// Uses raw XML since the extended comment parts lack typed SDK support.
122
+ /// </summary>
123
+ private static void LinkReplyToParent(WordprocessingDocument doc, int replyId, int parentCommentId)
124
+ {
125
+ var mainPart = doc.MainDocumentPart;
126
+ if (mainPart == null) return;
127
+
128
+ const string w15Uri = "http://schemas.microsoft.com/office/word/2012/wordml";
129
+ var w15 = System.Xml.Linq.XNamespace.Get(w15Uri);
130
+
131
+ // Find or create commentsExtended part
132
+ foreach (var part in mainPart.Parts)
133
+ {
134
+ if (part.OpenXmlPart.ContentType.Contains("commentsExtended"))
135
+ {
136
+ using var stream = part.OpenXmlPart.GetStream(FileMode.Open, FileAccess.ReadWrite);
137
+ var xdoc = System.Xml.Linq.XDocument.Load(stream);
138
+ var root = xdoc.Root;
139
+ if (root == null) return;
140
+
141
+ root.Add(new System.Xml.Linq.XElement(w15 + "commentEx",
142
+ new System.Xml.Linq.XAttribute(w15 + "paraId", replyId.ToString("X8")),
143
+ new System.Xml.Linq.XAttribute(w15 + "paraIdParent", parentCommentId.ToString("X8")),
144
+ new System.Xml.Linq.XAttribute(w15 + "done", "0")));
145
+
146
+ stream.SetLength(0);
147
+ xdoc.Save(stream);
148
+ return;
149
+ }
150
+ }
151
+ }
152
+
153
+ /// <summary>
154
+ /// Finds the maximum existing comment ID and returns the next one.
155
+ /// </summary>
156
+ public static int GetNextCommentId(WordprocessingDocument doc)
157
+ {
158
+ var commentsPart = doc.MainDocumentPart?.WordprocessingCommentsPart;
159
+ if (commentsPart?.Comments == null) return 1;
160
+
161
+ int maxId = 0;
162
+ foreach (var comment in commentsPart.Comments.Elements<Comment>())
163
+ {
164
+ if (comment.Id?.Value != null && int.TryParse(comment.Id.Value, out int id) && id > maxId)
165
+ maxId = id;
166
+ }
167
+ return maxId + 1;
168
+ }
169
+ }
@@ -0,0 +1,80 @@
1
+ using System.Xml.Linq;
2
+
3
+ namespace MiniMaxAIDocx.Core.OpenXml;
4
+
5
+ /// <summary>
6
+ /// Defines canonical child element ordering for key OpenXML parent elements
7
+ /// and provides reordering utilities.
8
+ /// </summary>
9
+ public static class ElementOrder
10
+ {
11
+ private static readonly Dictionary<string, string[]> OrderMap = new()
12
+ {
13
+ ["w:body"] = ["w:p", "w:tbl", "w:sdt", "w:sectPr"],
14
+ ["w:p"] = ["w:pPr", "w:hyperlink", "w:r", "w:ins", "w:del", "w:bookmarkStart", "w:bookmarkEnd", "w:commentRangeStart", "w:commentRangeEnd", "w:fldSimple"],
15
+ ["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"],
16
+ ["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"],
17
+ ["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"],
18
+ ["w:tbl"] = ["w:tblPr", "w:tblGrid", "w:tr"],
19
+ ["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"],
20
+ ["w:tr"] = ["w:trPr", "w:tc"],
21
+ ["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"],
22
+ ["w:tc"] = ["w:tcPr", "w:p", "w:tbl"],
23
+ ["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"],
24
+ ["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"],
25
+ ["w:hdr"] = ["w:p", "w:tbl", "w:sdt"],
26
+ ["w:ftr"] = ["w:p", "w:tbl", "w:sdt"],
27
+ };
28
+
29
+ /// <summary>
30
+ /// Returns the canonical child ordering for a given parent element name (e.g. "w:p").
31
+ /// Returns null if no ordering is defined.
32
+ /// </summary>
33
+ public static string[]? GetChildOrder(string parentElement)
34
+ {
35
+ return OrderMap.TryGetValue(parentElement, out var order) ? order : null;
36
+ }
37
+
38
+ /// <summary>
39
+ /// Reorders children of the given XElement according to the canonical ordering rules.
40
+ /// Children not listed in the ordering are placed at the end in their original order.
41
+ /// </summary>
42
+ public static void ReorderChildren(XElement parent)
43
+ {
44
+ var qualifiedName = GetQualifiedName(parent);
45
+ var order = GetChildOrder(qualifiedName);
46
+ if (order == null) return;
47
+
48
+ var children = parent.Elements().ToList();
49
+ if (children.Count <= 1) return;
50
+
51
+ var orderIndex = new Dictionary<string, int>();
52
+ for (int i = 0; i < order.Length; i++)
53
+ orderIndex[order[i]] = i;
54
+
55
+ int unknownBase = order.Length;
56
+ int unknownCounter = 0;
57
+
58
+ var sorted = children
59
+ .Select(c => (Element: c, QName: GetQualifiedName(c)))
60
+ .OrderBy(x => orderIndex.TryGetValue(x.QName, out var idx) ? idx : unknownBase + unknownCounter++)
61
+ .Select(x => x.Element)
62
+ .ToList();
63
+
64
+ parent.RemoveNodes();
65
+ foreach (var child in sorted)
66
+ parent.Add(child);
67
+ }
68
+
69
+ private static string GetQualifiedName(XElement element)
70
+ {
71
+ var ns = element.Name.Namespace;
72
+ var local = element.Name.LocalName;
73
+
74
+ if (ns == Ns.W) return $"w:{local}";
75
+ if (ns == Ns.R) return $"r:{local}";
76
+ if (ns == Ns.MC) return $"mc:{local}";
77
+
78
+ return local;
79
+ }
80
+ }
@@ -0,0 +1,42 @@
1
+ using System.Xml.Linq;
2
+
3
+ namespace MiniMaxAIDocx.Core.OpenXml;
4
+
5
+ /// <summary>
6
+ /// All OpenXML namespace URIs and common content/relationship type constants.
7
+ /// </summary>
8
+ public static class Ns
9
+ {
10
+ public static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
11
+ public static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
12
+ public static readonly XNamespace WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
13
+ public static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main";
14
+ public static readonly XNamespace MC = "http://schemas.openxmlformats.org/markup-compatibility/2006";
15
+ public static readonly XNamespace PIC = "http://schemas.openxmlformats.org/drawingml/2006/picture";
16
+ public static readonly XNamespace W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
17
+ public static readonly XNamespace W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
18
+ public static readonly XNamespace W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
19
+ public static readonly XNamespace W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
20
+ public static readonly XNamespace WPC = "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas";
21
+ public static readonly XNamespace WPS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
22
+
23
+ // Content types
24
+ public const string MainDocumentContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
25
+ public const string StylesContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
26
+ public const string HeaderContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";
27
+ public const string FooterContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";
28
+ public const string CommentsContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
29
+
30
+ // Relationship types
31
+ public const string DocumentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
32
+ public const string StylesRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
33
+ public const string HeaderRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
34
+ public const string FooterRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";
35
+ public const string CommentsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
36
+ public const string ImageRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
37
+ public const string HyperlinkRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
38
+ public const string NumberingRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
39
+ public const string FontTableRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
40
+ public const string ThemeRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
41
+ public const string SettingsRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
42
+ }
@@ -0,0 +1,81 @@
1
+ using System.Xml.Linq;
2
+
3
+ namespace MiniMaxAIDocx.Core.OpenXml;
4
+
5
+ /// <summary>
6
+ /// Result of a run merge operation.
7
+ /// </summary>
8
+ public record RunMergeResult(int OriginalRunCount, int MergedRunCount, int SizeReductionBytes);
9
+
10
+ /// <summary>
11
+ /// Merges adjacent w:r elements with identical w:rPr formatting to reduce document size.
12
+ /// </summary>
13
+ public static class RunMerger
14
+ {
15
+ /// <summary>
16
+ /// Merges adjacent runs with identical formatting in all paragraphs of the document body.
17
+ /// </summary>
18
+ public static RunMergeResult MergeRuns(XDocument document)
19
+ {
20
+ var body = document.Root?.Element(Ns.W + "body");
21
+ if (body == null) return new(0, 0, 0);
22
+
23
+ int originalCount = 0;
24
+ int removedCount = 0;
25
+
26
+ foreach (var paragraph in body.Descendants(Ns.W + "p"))
27
+ {
28
+ var runs = paragraph.Elements(Ns.W + "r").ToList();
29
+ originalCount += runs.Count;
30
+
31
+ for (int i = runs.Count - 1; i > 0; i--)
32
+ {
33
+ var current = runs[i];
34
+ var previous = runs[i - 1];
35
+
36
+ if (!AreRunPropertiesEqual(previous, current)) continue;
37
+
38
+ // Merge text content from current into previous
39
+ var prevText = GetOrCreateTextElement(previous);
40
+ var currText = current.Element(Ns.W + "t");
41
+ if (currText != null && prevText != null)
42
+ {
43
+ prevText.Value += currText.Value;
44
+ // Preserve xml:space="preserve" if either has it
45
+ if (currText.Attribute(XNamespace.Xml + "space")?.Value == "preserve" ||
46
+ prevText.Value.StartsWith(' ') || prevText.Value.EndsWith(' '))
47
+ {
48
+ prevText.SetAttributeValue(XNamespace.Xml + "space", "preserve");
49
+ }
50
+ }
51
+
52
+ current.Remove();
53
+ removedCount++;
54
+ }
55
+ }
56
+
57
+ return new(originalCount, originalCount - removedCount, 0);
58
+ }
59
+
60
+ private static bool AreRunPropertiesEqual(XElement run1, XElement run2)
61
+ {
62
+ var rPr1 = run1.Element(Ns.W + "rPr");
63
+ var rPr2 = run2.Element(Ns.W + "rPr");
64
+
65
+ if (rPr1 == null && rPr2 == null) return true;
66
+ if (rPr1 == null || rPr2 == null) return false;
67
+
68
+ return XNode.DeepEquals(rPr1, rPr2);
69
+ }
70
+
71
+ private static XElement? GetOrCreateTextElement(XElement run)
72
+ {
73
+ var t = run.Element(Ns.W + "t");
74
+ if (t == null)
75
+ {
76
+ t = new XElement(Ns.W + "t");
77
+ run.Add(t);
78
+ }
79
+ return t;
80
+ }
81
+ }