dimcode-darwin-arm64 0.2.19-beta.0 → 0.2.20-beta.0

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 (109) hide show
  1. package/bin/dimcode +0 -0
  2. package/bin/skills-assets/chrome-control/SKILL.md +96 -0
  3. package/bin/skills-assets/dim-workflow/SKILL.md +78 -30
  4. package/bin/skills-assets/officecli/SKILL.md +423 -0
  5. package/package.json +1 -1
  6. package/bin/skills-assets/docx/SKILL.md +0 -273
  7. package/bin/skills-assets/docx/assets/styles/academic_styles.xml +0 -250
  8. package/bin/skills-assets/docx/assets/styles/corporate_styles.xml +0 -284
  9. package/bin/skills-assets/docx/assets/styles/default_styles.xml +0 -449
  10. package/bin/skills-assets/docx/assets/xsd/aesthetic-rules.xsd +0 -470
  11. package/bin/skills-assets/docx/assets/xsd/business-rules.xsd +0 -130
  12. package/bin/skills-assets/docx/assets/xsd/common-types.xsd +0 -159
  13. package/bin/skills-assets/docx/assets/xsd/wml-subset.xsd +0 -589
  14. package/bin/skills-assets/docx/references/cjk_typography.md +0 -357
  15. package/bin/skills-assets/docx/references/cjk_university_template_guide.md +0 -184
  16. package/bin/skills-assets/docx/references/comments_guide.md +0 -191
  17. package/bin/skills-assets/docx/references/design_good_bad_examples.md +0 -829
  18. package/bin/skills-assets/docx/references/design_principles.md +0 -819
  19. package/bin/skills-assets/docx/references/openxml_element_order.md +0 -308
  20. package/bin/skills-assets/docx/references/openxml_encyclopedia_part1.md +0 -4061
  21. package/bin/skills-assets/docx/references/openxml_encyclopedia_part2.md +0 -2820
  22. package/bin/skills-assets/docx/references/openxml_encyclopedia_part3.md +0 -3381
  23. package/bin/skills-assets/docx/references/openxml_namespaces.md +0 -82
  24. package/bin/skills-assets/docx/references/openxml_units.md +0 -72
  25. package/bin/skills-assets/docx/references/scenario_a_create.md +0 -284
  26. package/bin/skills-assets/docx/references/scenario_b_edit_content.md +0 -295
  27. package/bin/skills-assets/docx/references/scenario_c_apply_template.md +0 -456
  28. package/bin/skills-assets/docx/references/track_changes_guide.md +0 -200
  29. package/bin/skills-assets/docx/references/troubleshooting.md +0 -506
  30. package/bin/skills-assets/docx/references/typography_guide.md +0 -294
  31. package/bin/skills-assets/docx/references/xsd_validation_guide.md +0 -158
  32. package/bin/skills-assets/docx/scripts/doc_to_docx.sh +0 -40
  33. package/bin/skills-assets/docx/scripts/docx_preview.sh +0 -37
  34. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Docx.Cli.csproj +0 -19
  35. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Program.cs +0 -18
  36. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/AnalyzeCommand.cs +0 -147
  37. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ApplyTemplateCommand.cs +0 -322
  38. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/CreateCommand.cs +0 -324
  39. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/DiffCommand.cs +0 -155
  40. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/EditContentCommand.cs +0 -487
  41. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/FixOrderCommand.cs +0 -108
  42. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/MergeRunsCommand.cs +0 -122
  43. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ValidateCommand.cs +0 -107
  44. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Docx.Core.csproj +0 -15
  45. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/CommentSynchronizer.cs +0 -169
  46. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/ElementOrder.cs +0 -80
  47. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/NamespaceConstants.cs +0 -42
  48. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/RunMerger.cs +0 -81
  49. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/StyleAnalyzer.cs +0 -81
  50. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/TrackChangesHelper.cs +0 -99
  51. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/UnitConverter.cs +0 -23
  52. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples.cs +0 -1832
  53. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch1.cs +0 -910
  54. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch2.cs +0 -999
  55. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch3.cs +0 -1048
  56. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch4.cs +0 -1038
  57. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/CharacterFormattingSamples.cs +0 -1020
  58. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/DocumentCreationSamples.cs +0 -1121
  59. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FieldAndTocSamples.cs +0 -624
  60. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FootnoteAndCommentSamples.cs +0 -675
  61. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/HeaderFooterSamples.cs +0 -838
  62. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ImageSamples.cs +0 -917
  63. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ListAndNumberingSamples.cs +0 -826
  64. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ParagraphFormattingSamples.cs +0 -1199
  65. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/StyleSystemSamples.cs +0 -1487
  66. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TableSamples.cs +0 -1163
  67. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TrackChangesSamples.cs +0 -595
  68. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/CjkHelper.cs +0 -39
  69. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/FontDefaults.cs +0 -24
  70. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/PageSizes.cs +0 -20
  71. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/BusinessRuleValidator.cs +0 -224
  72. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/GateCheckValidator.cs +0 -148
  73. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/ValidationResult.cs +0 -23
  74. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/XsdValidator.cs +0 -69
  75. package/bin/skills-assets/docx/scripts/dotnet/Docx.slnx +0 -4
  76. package/bin/skills-assets/docx/scripts/env_check.sh +0 -196
  77. package/bin/skills-assets/docx/scripts/setup.ps1 +0 -274
  78. package/bin/skills-assets/docx/scripts/setup.sh +0 -504
  79. package/bin/skills-assets/pptx-generator/SKILL.md +0 -248
  80. package/bin/skills-assets/pptx-generator/references/design-system.md +0 -392
  81. package/bin/skills-assets/pptx-generator/references/editing.md +0 -162
  82. package/bin/skills-assets/pptx-generator/references/pitfalls.md +0 -112
  83. package/bin/skills-assets/pptx-generator/references/pptxgenjs.md +0 -420
  84. package/bin/skills-assets/pptx-generator/references/slide-types.md +0 -413
  85. package/bin/skills-assets/xlsx/SKILL.md +0 -137
  86. package/bin/skills-assets/xlsx/references/create.md +0 -691
  87. package/bin/skills-assets/xlsx/references/edit.md +0 -684
  88. package/bin/skills-assets/xlsx/references/fix.md +0 -37
  89. package/bin/skills-assets/xlsx/references/format.md +0 -768
  90. package/bin/skills-assets/xlsx/references/ooxml-cheatsheet.md +0 -231
  91. package/bin/skills-assets/xlsx/references/read-analyze.md +0 -97
  92. package/bin/skills-assets/xlsx/references/validate.md +0 -772
  93. package/bin/skills-assets/xlsx/scripts/formula_check.py +0 -422
  94. package/bin/skills-assets/xlsx/scripts/libreoffice_recalc.py +0 -248
  95. package/bin/skills-assets/xlsx/scripts/shared_strings_builder.py +0 -163
  96. package/bin/skills-assets/xlsx/scripts/style_audit.py +0 -575
  97. package/bin/skills-assets/xlsx/scripts/xlsx_add_column.py +0 -395
  98. package/bin/skills-assets/xlsx/scripts/xlsx_insert_row.py +0 -274
  99. package/bin/skills-assets/xlsx/scripts/xlsx_pack.py +0 -87
  100. package/bin/skills-assets/xlsx/scripts/xlsx_reader.py +0 -362
  101. package/bin/skills-assets/xlsx/scripts/xlsx_shift_rows.py +0 -396
  102. package/bin/skills-assets/xlsx/scripts/xlsx_unpack.py +0 -130
  103. package/bin/skills-assets/xlsx/templates/minimal_xlsx/[Content_Types].xml +0 -9
  104. package/bin/skills-assets/xlsx/templates/minimal_xlsx/_rels/.rels +0 -6
  105. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +0 -19
  106. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +0 -33
  107. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/styles.xml +0 -160
  108. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/workbook.xml +0 -30
  109. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +0 -70
@@ -1,487 +0,0 @@
1
- using System.CommandLine;
2
- using System.Text.RegularExpressions;
3
- using DocumentFormat.OpenXml;
4
- using DocumentFormat.OpenXml.Packaging;
5
- using DocumentFormat.OpenXml.Wordprocessing;
6
- using Docx.Core.OpenXml;
7
-
8
- namespace Docx.Core.Commands;
9
-
10
- /// <summary>
11
- /// Scenario B: Surgical content editing operations on existing DOCX files.
12
- /// Preserves all existing formatting and minimizes XML changes.
13
- /// </summary>
14
- public static class EditContentCommand
15
- {
16
- public static Command Create()
17
- {
18
- var cmd = new Command("edit", "Edit existing DOCX content");
19
-
20
- cmd.Add(CreateReplaceTextCommand());
21
- cmd.Add(CreateFillTableCommand());
22
- cmd.Add(CreateInsertParagraphCommand());
23
- cmd.Add(CreateUpdateFieldCommand());
24
- cmd.Add(CreateListPlaceholdersCommand());
25
- cmd.Add(CreateFillPlaceholdersCommand());
26
-
27
- return cmd;
28
- }
29
-
30
- private static Command CreateReplaceTextCommand()
31
- {
32
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
33
- var outputOpt = new Option<string>("--output") { Description = "Output file path (defaults to overwriting input)" };
34
- var searchOpt = new Option<string>("--search") { Description = "Text to search for", Required = true };
35
- var replaceOpt = new Option<string>("--replace") { Description = "Replacement text", Required = true };
36
- var regexOpt = new Option<bool>("--regex") { Description = "Treat search as a regex pattern" };
37
-
38
- var cmd = new Command("replace-text", "Replace text while preserving formatting")
39
- {
40
- inputOpt, outputOpt, searchOpt, replaceOpt, regexOpt
41
- };
42
-
43
- cmd.SetAction((parseResult) =>
44
- {
45
- var input = parseResult.GetValue(inputOpt)!;
46
- var output = parseResult.GetValue(outputOpt) ?? input;
47
- var search = parseResult.GetValue(searchOpt)!;
48
- var replace = parseResult.GetValue(replaceOpt)!;
49
- var useRegex = parseResult.GetValue(regexOpt);
50
-
51
- if (output != input) File.Copy(input, output, overwrite: true);
52
-
53
- using var doc = WordprocessingDocument.Open(output, true);
54
- var body = doc.MainDocumentPart?.Document.Body;
55
- if (body == null) { Console.Error.WriteLine("No document body found."); return; }
56
-
57
- int count = 0;
58
- foreach (var paragraph in body.Descendants<Paragraph>())
59
- {
60
- count += ReplaceInParagraph(paragraph, search, replace, useRegex);
61
- }
62
-
63
- doc.MainDocumentPart!.Document.Save();
64
- Console.WriteLine($"Replaced {count} occurrence(s) in {output}");
65
- });
66
-
67
- return cmd;
68
- }
69
-
70
- private static Command CreateFillTableCommand()
71
- {
72
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
73
- var outputOpt = new Option<string>("--output") { Description = "Output file path" };
74
- var tableIndexOpt = new Option<int>("--table-index") { Description = "Zero-based index of the table to fill" };
75
- tableIndexOpt.DefaultValueFactory = _ => 0;
76
- var csvOpt = new Option<string>("--csv") { Description = "CSV file with data to fill", Required = true };
77
- var appendOpt = new Option<bool>("--append") { Description = "Append rows instead of replacing existing data rows" };
78
-
79
- var cmd = new Command("fill-table", "Fill a table with data from CSV")
80
- {
81
- inputOpt, outputOpt, tableIndexOpt, csvOpt, appendOpt
82
- };
83
-
84
- cmd.SetAction((parseResult) =>
85
- {
86
- var input = parseResult.GetValue(inputOpt)!;
87
- var output = parseResult.GetValue(outputOpt) ?? input;
88
- var tableIndex = parseResult.GetValue(tableIndexOpt);
89
- var csvPath = parseResult.GetValue(csvOpt)!;
90
- var append = parseResult.GetValue(appendOpt);
91
-
92
- if (output != input) File.Copy(input, output, overwrite: true);
93
-
94
- if (!File.Exists(csvPath)) { Console.Error.WriteLine($"CSV file not found: {csvPath}"); return; }
95
-
96
- using var doc = WordprocessingDocument.Open(output, true);
97
- var body = doc.MainDocumentPart?.Document.Body;
98
- if (body == null) { Console.Error.WriteLine("No document body found."); return; }
99
-
100
- var tables = body.Elements<Table>().ToList();
101
- if (tableIndex >= tables.Count)
102
- {
103
- Console.Error.WriteLine($"Table index {tableIndex} out of range (found {tables.Count} tables).");
104
- return;
105
- }
106
-
107
- var table = tables[tableIndex];
108
- var csvLines = File.ReadAllLines(csvPath);
109
- if (csvLines.Length == 0) { Console.WriteLine("CSV is empty, nothing to fill."); return; }
110
-
111
- // Get template row properties from the first data row (second row, after header)
112
- var existingRows = table.Elements<TableRow>().ToList();
113
- TableRow? templateRow = existingRows.Count > 1 ? existingRows[1] : existingRows.FirstOrDefault();
114
- var templateTrPr = templateRow?.TableRowProperties?.CloneNode(true) as TableRowProperties;
115
-
116
- if (!append)
117
- {
118
- // Remove all rows except the header row
119
- for (int i = existingRows.Count - 1; i >= 1; i--)
120
- existingRows[i].Remove();
121
- }
122
-
123
- int rowsAdded = 0;
124
- // Skip header line in CSV (index 0)
125
- for (int i = 1; i < csvLines.Length; i++)
126
- {
127
- var values = ParseCsvLine(csvLines[i]);
128
- var newRow = new TableRow();
129
- if (templateTrPr != null)
130
- newRow.Append(templateTrPr.CloneNode(true));
131
-
132
- foreach (var val in values)
133
- {
134
- var cell = new TableCell(
135
- new Paragraph(new Run(new Text(val))));
136
- newRow.Append(cell);
137
- }
138
-
139
- table.Append(newRow);
140
- rowsAdded++;
141
- }
142
-
143
- doc.MainDocumentPart!.Document.Save();
144
- Console.WriteLine($"Added {rowsAdded} rows to table {tableIndex} in {output}");
145
- });
146
-
147
- return cmd;
148
- }
149
-
150
- private static Command CreateInsertParagraphCommand()
151
- {
152
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
153
- var outputOpt = new Option<string>("--output") { Description = "Output file path" };
154
- var textOpt = new Option<string>("--text") { Description = "Paragraph text", Required = true };
155
- var styleOpt = new Option<string>("--style") { Description = "Paragraph style (e.g. Heading1, Normal)" };
156
- var afterOpt = new Option<int>("--after-paragraph") { Description = "Insert after this paragraph index (0-based)" };
157
- afterOpt.DefaultValueFactory = _ => -1; // -1 = append at end
158
-
159
- var cmd = new Command("insert-paragraph", "Insert a new paragraph")
160
- {
161
- inputOpt, outputOpt, textOpt, styleOpt, afterOpt
162
- };
163
-
164
- cmd.SetAction((parseResult) =>
165
- {
166
- var input = parseResult.GetValue(inputOpt)!;
167
- var output = parseResult.GetValue(outputOpt) ?? input;
168
- var text = parseResult.GetValue(textOpt)!;
169
- var style = parseResult.GetValue(styleOpt);
170
- var afterIndex = parseResult.GetValue(afterOpt);
171
-
172
- if (output != input) File.Copy(input, output, overwrite: true);
173
-
174
- using var doc = WordprocessingDocument.Open(output, true);
175
- var body = doc.MainDocumentPart?.Document.Body;
176
- if (body == null) { Console.Error.WriteLine("No document body found."); return; }
177
-
178
- var newPara = new Paragraph();
179
- if (!string.IsNullOrEmpty(style))
180
- newPara.Append(new ParagraphProperties(new ParagraphStyleId { Val = style }));
181
- newPara.Append(new Run(new Text(text)));
182
-
183
- var paragraphs = body.Elements<Paragraph>().ToList();
184
- if (afterIndex >= 0 && afterIndex < paragraphs.Count)
185
- {
186
- paragraphs[afterIndex].InsertAfterSelf(newPara);
187
- }
188
- else
189
- {
190
- // Insert before sectPr if present, otherwise append
191
- var sectPr = body.Elements<SectionProperties>().FirstOrDefault();
192
- if (sectPr != null)
193
- sectPr.InsertBeforeSelf(newPara);
194
- else
195
- body.Append(newPara);
196
- }
197
-
198
- doc.MainDocumentPart!.Document.Save();
199
- Console.WriteLine($"Inserted paragraph in {output}");
200
- });
201
-
202
- return cmd;
203
- }
204
-
205
- private static Command CreateUpdateFieldCommand()
206
- {
207
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
208
- var outputOpt = new Option<string>("--output") { Description = "Output file path" };
209
- var fieldNameOpt = new Option<string>("--field") { Description = "Document property field name (e.g. TITLE, AUTHOR)", Required = true };
210
- var valueOpt = new Option<string>("--value") { Description = "New field value", Required = true };
211
-
212
- var cmd = new Command("update-field", "Update a document property field value")
213
- {
214
- inputOpt, outputOpt, fieldNameOpt, valueOpt
215
- };
216
-
217
- cmd.SetAction((parseResult) =>
218
- {
219
- var input = parseResult.GetValue(inputOpt)!;
220
- var output = parseResult.GetValue(outputOpt) ?? input;
221
- var fieldName = parseResult.GetValue(fieldNameOpt)!;
222
- var value = parseResult.GetValue(valueOpt)!;
223
-
224
- if (output != input) File.Copy(input, output, overwrite: true);
225
-
226
- using var doc = WordprocessingDocument.Open(output, true);
227
-
228
- // Update core properties
229
- var props = doc.PackageProperties;
230
- switch (fieldName.ToUpperInvariant())
231
- {
232
- case "TITLE": props.Title = value; break;
233
- case "AUTHOR": props.Creator = value; break;
234
- case "SUBJECT": props.Subject = value; break;
235
- case "KEYWORDS": props.Keywords = value; break;
236
- case "DESCRIPTION": props.Description = value; break;
237
- case "CATEGORY": props.Category = value; break;
238
- default:
239
- Console.Error.WriteLine($"Unknown field: {fieldName}. Supported: TITLE, AUTHOR, SUBJECT, KEYWORDS, DESCRIPTION, CATEGORY");
240
- return;
241
- }
242
-
243
- Console.WriteLine($"Updated {fieldName} to \"{value}\" in {output}");
244
- });
245
-
246
- return cmd;
247
- }
248
-
249
- private static Command CreateListPlaceholdersCommand()
250
- {
251
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
252
- var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern (regex)" };
253
- patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}"; // {{PLACEHOLDER}}
254
-
255
- var cmd = new Command("list-placeholders", "List all placeholders found in the document")
256
- {
257
- inputOpt, patternOpt
258
- };
259
-
260
- cmd.SetAction((parseResult) =>
261
- {
262
- var input = parseResult.GetValue(inputOpt)!;
263
- var pattern = parseResult.GetValue(patternOpt)!;
264
-
265
- using var doc = WordprocessingDocument.Open(input, false);
266
- var body = doc.MainDocumentPart?.Document.Body;
267
- if (body == null) { Console.Error.WriteLine("No document body found."); return; }
268
-
269
- var placeholders = new HashSet<string>();
270
- var regex = new Regex(pattern);
271
-
272
- foreach (var paragraph in body.Descendants<Paragraph>())
273
- {
274
- var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
275
- foreach (Match match in regex.Matches(fullText))
276
- {
277
- placeholders.Add(match.Value);
278
- }
279
- }
280
-
281
- if (placeholders.Count == 0)
282
- {
283
- Console.WriteLine("No placeholders found.");
284
- return;
285
- }
286
-
287
- Console.WriteLine($"Found {placeholders.Count} unique placeholder(s):");
288
- foreach (var p in placeholders.OrderBy(x => x))
289
- Console.WriteLine($" {p}");
290
- });
291
-
292
- return cmd;
293
- }
294
-
295
- private static Command CreateFillPlaceholdersCommand()
296
- {
297
- var inputOpt = new Option<string>("--input") { Description = "Input DOCX file", Required = true };
298
- var outputOpt = new Option<string>("--output") { Description = "Output file path" };
299
- var mappingOpt = new Option<string>("--mapping") { Description = "JSON file mapping placeholder names to values", Required = true };
300
- var patternOpt = new Option<string>("--pattern") { Description = "Placeholder pattern with capture group for the name" };
301
- patternOpt.DefaultValueFactory = _ => @"\{\{(\w+)\}\}";
302
-
303
- var cmd = new Command("fill-placeholders", "Replace placeholders with values from a mapping file")
304
- {
305
- inputOpt, outputOpt, mappingOpt, patternOpt
306
- };
307
-
308
- cmd.SetAction((parseResult) =>
309
- {
310
- var input = parseResult.GetValue(inputOpt)!;
311
- var output = parseResult.GetValue(outputOpt) ?? input;
312
- var mappingPath = parseResult.GetValue(mappingOpt)!;
313
- var pattern = parseResult.GetValue(patternOpt)!;
314
-
315
- if (!File.Exists(mappingPath)) { Console.Error.WriteLine($"Mapping file not found: {mappingPath}"); return; }
316
-
317
- var mappingJson = File.ReadAllText(mappingPath);
318
- Dictionary<string, string> mapping;
319
- try
320
- {
321
- mapping = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(mappingJson) ?? [];
322
- }
323
- catch (System.Text.Json.JsonException ex)
324
- {
325
- Console.Error.WriteLine($"Invalid mapping JSON: {ex.Message}");
326
- return;
327
- }
328
-
329
- if (output != input) File.Copy(input, output, overwrite: true);
330
-
331
- using var doc = WordprocessingDocument.Open(output, true);
332
- var body = doc.MainDocumentPart?.Document.Body;
333
- if (body == null) { Console.Error.WriteLine("No document body found."); return; }
334
-
335
- int totalReplacements = 0;
336
- var regex = new Regex(pattern);
337
-
338
- foreach (var paragraph in body.Descendants<Paragraph>())
339
- {
340
- var fullText = string.Concat(paragraph.Descendants<Text>().Select(t => t.Text));
341
- var matches = regex.Matches(fullText);
342
- if (matches.Count == 0) continue;
343
-
344
- foreach (Match match in matches)
345
- {
346
- var placeholderName = match.Groups.Count > 1 ? match.Groups[1].Value : match.Value;
347
- if (mapping.TryGetValue(placeholderName, out var replacement))
348
- {
349
- totalReplacements += ReplaceInParagraph(paragraph, match.Value, replacement, false);
350
- }
351
- }
352
- }
353
-
354
- doc.MainDocumentPart!.Document.Save();
355
- Console.WriteLine($"Filled {totalReplacements} placeholder(s) in {output}");
356
- });
357
-
358
- return cmd;
359
- }
360
-
361
- /// <summary>
362
- /// Replaces text within a paragraph while preserving run formatting.
363
- /// Handles the case where search text may span multiple runs.
364
- /// </summary>
365
- private static int ReplaceInParagraph(Paragraph paragraph, string search, string replace, bool useRegex)
366
- {
367
- var runs = paragraph.Elements<Run>().ToList();
368
- if (runs.Count == 0) return 0;
369
-
370
- // Build the full paragraph text and a map from character index to (run, position within run)
371
- var fullText = string.Concat(runs.SelectMany(r => r.Elements<Text>().Select(t => t.Text)));
372
- if (string.IsNullOrEmpty(fullText)) return 0;
373
-
374
- int count = 0;
375
-
376
- if (!useRegex)
377
- {
378
- // Simple case: search within each run first
379
- foreach (var run in runs)
380
- {
381
- foreach (var textElement in run.Elements<Text>().ToList())
382
- {
383
- if (textElement.Text.Contains(search))
384
- {
385
- var newText = textElement.Text.Replace(search, replace);
386
- count += (textElement.Text.Length - newText.Length + replace.Length - search.Length) == 0 ? 0 :
387
- CountOccurrences(textElement.Text, search);
388
- textElement.Text = newText;
389
- if (newText.StartsWith(' ') || newText.EndsWith(' '))
390
- textElement.Space = SpaceProcessingModeValues.Preserve;
391
- }
392
- }
393
- }
394
-
395
- // Handle cross-run matches by concatenating all runs, replacing, and rebuilding
396
- if (count == 0 && fullText.Contains(search))
397
- {
398
- var newFullText = fullText.Replace(search, replace);
399
- count = CountOccurrences(fullText, search);
400
- RebuildRunsWithText(paragraph, runs, newFullText);
401
- }
402
- }
403
- else
404
- {
405
- var regex = new Regex(search);
406
- if (regex.IsMatch(fullText))
407
- {
408
- count = regex.Matches(fullText).Count;
409
- var newFullText = regex.Replace(fullText, replace);
410
- RebuildRunsWithText(paragraph, runs, newFullText);
411
- }
412
- }
413
-
414
- return count;
415
- }
416
-
417
- /// <summary>
418
- /// Replaces the text content of existing runs with new text,
419
- /// preserving the formatting of the first run.
420
- /// </summary>
421
- private static void RebuildRunsWithText(Paragraph paragraph, List<Run> runs, string newText)
422
- {
423
- if (runs.Count == 0) return;
424
-
425
- // Keep the first run's formatting, set its text to the full new text
426
- var firstRun = runs[0];
427
- var firstText = firstRun.Elements<Text>().FirstOrDefault();
428
- if (firstText != null)
429
- {
430
- firstText.Text = newText;
431
- if (newText.StartsWith(' ') || newText.EndsWith(' '))
432
- firstText.Space = SpaceProcessingModeValues.Preserve;
433
- }
434
-
435
- // Remove all other runs
436
- for (int i = 1; i < runs.Count; i++)
437
- runs[i].Remove();
438
- }
439
-
440
- private static int CountOccurrences(string text, string search)
441
- {
442
- int count = 0;
443
- int index = 0;
444
- while ((index = text.IndexOf(search, index, StringComparison.Ordinal)) != -1)
445
- {
446
- count++;
447
- index += search.Length;
448
- }
449
- return count;
450
- }
451
-
452
- private static string[] ParseCsvLine(string line)
453
- {
454
- // Simple CSV parser (handles quoted fields)
455
- var result = new List<string>();
456
- bool inQuotes = false;
457
- var current = new System.Text.StringBuilder();
458
-
459
- for (int i = 0; i < line.Length; i++)
460
- {
461
- char c = line[i];
462
- if (c == '"')
463
- {
464
- if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
465
- {
466
- current.Append('"');
467
- i++;
468
- }
469
- else
470
- {
471
- inQuotes = !inQuotes;
472
- }
473
- }
474
- else if (c == ',' && !inQuotes)
475
- {
476
- result.Add(current.ToString());
477
- current.Clear();
478
- }
479
- else
480
- {
481
- current.Append(c);
482
- }
483
- }
484
- result.Add(current.ToString());
485
- return result.ToArray();
486
- }
487
- }
@@ -1,108 +0,0 @@
1
- using System.CommandLine;
2
- using System.IO.Compression;
3
- using System.Xml.Linq;
4
-
5
- namespace Docx.Core.Commands;
6
-
7
- public static class FixOrderCommand
8
- {
9
- private static readonly XNamespace W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
10
-
11
- // Canonical element ordering within common parent elements per ISO 29500
12
- private static readonly Dictionary<string, List<string>> ElementOrder = new()
13
- {
14
- ["pPr"] = new() { "pStyle", "keepNext", "keepLines", "pageBreakBefore", "widowControl", "numPr", "suppressLineNumbers", "pBdr", "shd", "tabs", "suppressAutoHyphens", "spacing", "ind", "jc", "outlineLvl", "rPr" },
15
- ["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" },
16
- ["tblPr"] = new() { "tblStyle", "tblpPr", "tblOverlap", "tblW", "jc", "tblInd", "tblBorders", "shd", "tblLayout", "tblCellMar", "tblLook" },
17
- ["tcPr"] = new() { "cnfStyle", "tcW", "gridSpan", "hMerge", "vMerge", "tcBorders", "shd", "noWrap", "tcMar", "textDirection", "tcFitText", "vAlign" },
18
- ["sectPr"] = new() { "headerReference", "footerReference", "footnotePr", "endnotePr", "type", "pgSz", "pgMar", "paperSrc", "pgBorders", "lnNumType", "pgNumType", "cols", "docGrid" },
19
- };
20
-
21
- public static Command Create()
22
- {
23
- var inputOption = new Option<string>("--input") { Description = "DOCX file to fix", Required = true };
24
- var outputOption = new Option<string>("--output") { Description = "Output path (default: overwrite input)" };
25
- var backupOption = new Option<bool>("--backup") { Description = "Create .bak before modifying", DefaultValueFactory = (_) => true };
26
-
27
- var cmd = new Command("fix-order", "Fix OpenXML element ordering per ISO 29500")
28
- {
29
- inputOption, outputOption, backupOption
30
- };
31
-
32
- cmd.SetAction((parseResult) =>
33
- {
34
- var input = parseResult.GetValue(inputOption)!;
35
- var output = parseResult.GetValue(outputOption) ?? input;
36
- var backup = parseResult.GetValue(backupOption);
37
-
38
- if (!File.Exists(input))
39
- {
40
- Console.Error.WriteLine($"File not found: {input}");
41
- return;
42
- }
43
-
44
- if (backup && output == input)
45
- File.Copy(input, input + ".bak", true);
46
-
47
- var tempPath = Path.GetTempFileName();
48
- File.Copy(input, tempPath, true);
49
-
50
- using var zip = ZipFile.Open(tempPath, ZipArchiveMode.Update);
51
- var entry = zip.GetEntry("word/document.xml");
52
- if (entry == null)
53
- {
54
- Console.Error.WriteLine("Not a valid DOCX");
55
- return;
56
- }
57
-
58
- XDocument doc;
59
- using (var stream = entry.Open())
60
- doc = XDocument.Load(stream);
61
-
62
- int reorderedCount = 0;
63
-
64
- foreach (var (parentName, order) in ElementOrder)
65
- {
66
- foreach (var parent in doc.Descendants(W + parentName))
67
- {
68
- var children = parent.Elements().ToList();
69
- var sorted = children.OrderBy(e =>
70
- {
71
- var idx = order.IndexOf(e.Name.LocalName);
72
- return idx >= 0 ? idx : order.Count;
73
- }).ToList();
74
-
75
- bool changed = false;
76
- for (int i = 0; i < children.Count; i++)
77
- {
78
- if (children[i] != sorted[i])
79
- {
80
- changed = true;
81
- break;
82
- }
83
- }
84
-
85
- if (changed)
86
- {
87
- parent.ReplaceNodes(sorted);
88
- reorderedCount++;
89
- }
90
- }
91
- }
92
-
93
- entry.Delete();
94
- var newEntry = zip.CreateEntry("word/document.xml", CompressionLevel.Optimal);
95
- using (var stream = newEntry.Open())
96
- doc.Save(stream);
97
-
98
- zip.Dispose();
99
- File.Copy(tempPath, output, true);
100
- File.Delete(tempPath);
101
-
102
- Console.WriteLine($"Reordered {reorderedCount} element group(s)");
103
- Console.WriteLine($"Written to: {output}");
104
- });
105
-
106
- return cmd;
107
- }
108
- }