dimcode-darwin-x64 0.1.2-beta.1 → 0.1.2

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 (148) hide show
  1. package/bin/dimcode +0 -0
  2. package/package.json +1 -1
  3. package/bin/runtime/sandbox/dim-sandbox-runner +0 -0
  4. package/bin/runtime/sandbox/manifest.json +0 -15
  5. package/bin/skills-assets/deep-investigate/SKILL.md +0 -101
  6. package/bin/skills-assets/deep-investigate/references/prompts.md +0 -75
  7. package/bin/skills-assets/deep-investigate/references/templates.md +0 -73
  8. package/bin/skills-assets/deep-investigate/references/thinking-tools.md +0 -36
  9. package/bin/skills-assets/docs-sprint/SKILL.md +0 -73
  10. package/bin/skills-assets/docs-sprint/agents/openai.yaml +0 -4
  11. package/bin/skills-assets/docs-sprint/references/contract-discipline.md +0 -30
  12. package/bin/skills-assets/docs-sprint/references/delivery-plan.md +0 -162
  13. package/bin/skills-assets/docs-sprint/references/documentation-system.md +0 -109
  14. package/bin/skills-assets/docs-sprint/references/ui-layout.md +0 -73
  15. package/bin/skills-assets/docs-sprint/references/worktree-guide.md +0 -45
  16. package/bin/skills-assets/docx/SKILL.md +0 -273
  17. package/bin/skills-assets/docx/assets/styles/academic_styles.xml +0 -250
  18. package/bin/skills-assets/docx/assets/styles/corporate_styles.xml +0 -284
  19. package/bin/skills-assets/docx/assets/styles/default_styles.xml +0 -449
  20. package/bin/skills-assets/docx/assets/xsd/aesthetic-rules.xsd +0 -470
  21. package/bin/skills-assets/docx/assets/xsd/business-rules.xsd +0 -130
  22. package/bin/skills-assets/docx/assets/xsd/common-types.xsd +0 -159
  23. package/bin/skills-assets/docx/assets/xsd/wml-subset.xsd +0 -589
  24. package/bin/skills-assets/docx/references/cjk_typography.md +0 -357
  25. package/bin/skills-assets/docx/references/cjk_university_template_guide.md +0 -184
  26. package/bin/skills-assets/docx/references/comments_guide.md +0 -191
  27. package/bin/skills-assets/docx/references/design_good_bad_examples.md +0 -829
  28. package/bin/skills-assets/docx/references/design_principles.md +0 -819
  29. package/bin/skills-assets/docx/references/openxml_element_order.md +0 -308
  30. package/bin/skills-assets/docx/references/openxml_encyclopedia_part1.md +0 -4061
  31. package/bin/skills-assets/docx/references/openxml_encyclopedia_part2.md +0 -2820
  32. package/bin/skills-assets/docx/references/openxml_encyclopedia_part3.md +0 -3381
  33. package/bin/skills-assets/docx/references/openxml_namespaces.md +0 -82
  34. package/bin/skills-assets/docx/references/openxml_units.md +0 -72
  35. package/bin/skills-assets/docx/references/scenario_a_create.md +0 -284
  36. package/bin/skills-assets/docx/references/scenario_b_edit_content.md +0 -295
  37. package/bin/skills-assets/docx/references/scenario_c_apply_template.md +0 -456
  38. package/bin/skills-assets/docx/references/track_changes_guide.md +0 -200
  39. package/bin/skills-assets/docx/references/troubleshooting.md +0 -506
  40. package/bin/skills-assets/docx/references/typography_guide.md +0 -294
  41. package/bin/skills-assets/docx/references/xsd_validation_guide.md +0 -158
  42. package/bin/skills-assets/docx/scripts/doc_to_docx.sh +0 -40
  43. package/bin/skills-assets/docx/scripts/docx_preview.sh +0 -37
  44. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Docx.Cli.csproj +0 -19
  45. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Program.cs +0 -18
  46. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/AnalyzeCommand.cs +0 -147
  47. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ApplyTemplateCommand.cs +0 -322
  48. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/CreateCommand.cs +0 -324
  49. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/DiffCommand.cs +0 -155
  50. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/EditContentCommand.cs +0 -487
  51. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/FixOrderCommand.cs +0 -108
  52. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/MergeRunsCommand.cs +0 -122
  53. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ValidateCommand.cs +0 -107
  54. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Docx.Core.csproj +0 -15
  55. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/CommentSynchronizer.cs +0 -169
  56. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/ElementOrder.cs +0 -80
  57. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/NamespaceConstants.cs +0 -42
  58. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/RunMerger.cs +0 -81
  59. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/StyleAnalyzer.cs +0 -81
  60. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/TrackChangesHelper.cs +0 -99
  61. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/UnitConverter.cs +0 -23
  62. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples.cs +0 -1832
  63. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch1.cs +0 -910
  64. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch2.cs +0 -999
  65. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch3.cs +0 -1048
  66. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch4.cs +0 -1038
  67. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/CharacterFormattingSamples.cs +0 -1020
  68. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/DocumentCreationSamples.cs +0 -1121
  69. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FieldAndTocSamples.cs +0 -624
  70. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FootnoteAndCommentSamples.cs +0 -675
  71. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/HeaderFooterSamples.cs +0 -838
  72. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ImageSamples.cs +0 -917
  73. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ListAndNumberingSamples.cs +0 -826
  74. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ParagraphFormattingSamples.cs +0 -1199
  75. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/StyleSystemSamples.cs +0 -1487
  76. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TableSamples.cs +0 -1163
  77. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TrackChangesSamples.cs +0 -595
  78. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/CjkHelper.cs +0 -39
  79. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/FontDefaults.cs +0 -24
  80. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/PageSizes.cs +0 -20
  81. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/BusinessRuleValidator.cs +0 -224
  82. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/GateCheckValidator.cs +0 -148
  83. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/ValidationResult.cs +0 -23
  84. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/XsdValidator.cs +0 -69
  85. package/bin/skills-assets/docx/scripts/dotnet/Docx.slnx +0 -4
  86. package/bin/skills-assets/docx/scripts/env_check.sh +0 -196
  87. package/bin/skills-assets/docx/scripts/setup.ps1 +0 -274
  88. package/bin/skills-assets/docx/scripts/setup.sh +0 -504
  89. package/bin/skills-assets/pdf/README.md +0 -222
  90. package/bin/skills-assets/pdf/SKILL.md +0 -191
  91. package/bin/skills-assets/pdf/design/design.md +0 -381
  92. package/bin/skills-assets/pdf/scripts/cover.py +0 -1579
  93. package/bin/skills-assets/pdf/scripts/fill_inspect.py +0 -200
  94. package/bin/skills-assets/pdf/scripts/fill_write.py +0 -242
  95. package/bin/skills-assets/pdf/scripts/make.sh +0 -491
  96. package/bin/skills-assets/pdf/scripts/merge.py +0 -112
  97. package/bin/skills-assets/pdf/scripts/palette.py +0 -521
  98. package/bin/skills-assets/pdf/scripts/reformat_parse.py +0 -374
  99. package/bin/skills-assets/pdf/scripts/render_body.py +0 -1052
  100. package/bin/skills-assets/pdf/scripts/render_cover.js +0 -111
  101. package/bin/skills-assets/pptx-generator/SKILL.md +0 -248
  102. package/bin/skills-assets/pptx-generator/references/design-system.md +0 -392
  103. package/bin/skills-assets/pptx-generator/references/editing.md +0 -162
  104. package/bin/skills-assets/pptx-generator/references/pitfalls.md +0 -112
  105. package/bin/skills-assets/pptx-generator/references/pptxgenjs.md +0 -420
  106. package/bin/skills-assets/pptx-generator/references/slide-types.md +0 -413
  107. package/bin/skills-assets/skill-creator/SKILL.md +0 -368
  108. package/bin/skills-assets/skill-creator/agents/openai.yaml +0 -5
  109. package/bin/skills-assets/skill-creator/assets/skill-creator-small.svg +0 -3
  110. package/bin/skills-assets/skill-creator/assets/skill-creator.png +0 -0
  111. package/bin/skills-assets/skill-creator/license.txt +0 -202
  112. package/bin/skills-assets/skill-creator/references/openai_yaml.md +0 -49
  113. package/bin/skills-assets/skill-creator/scripts/generate_openai_yaml.py +0 -226
  114. package/bin/skills-assets/skill-creator/scripts/init_skill.py +0 -397
  115. package/bin/skills-assets/skill-creator/scripts/quick_validate.py +0 -101
  116. package/bin/skills-assets/skill-installer/LICENSE.txt +0 -202
  117. package/bin/skills-assets/skill-installer/SKILL.md +0 -58
  118. package/bin/skills-assets/skill-installer/agents/openai.yaml +0 -5
  119. package/bin/skills-assets/skill-installer/assets/skill-installer-small.svg +0 -3
  120. package/bin/skills-assets/skill-installer/assets/skill-installer.png +0 -0
  121. package/bin/skills-assets/skill-installer/scripts/github_utils.py +0 -21
  122. package/bin/skills-assets/skill-installer/scripts/install-skill-from-github.py +0 -308
  123. package/bin/skills-assets/skill-installer/scripts/list-skills.py +0 -107
  124. package/bin/skills-assets/xlsx/SKILL.md +0 -137
  125. package/bin/skills-assets/xlsx/references/create.md +0 -691
  126. package/bin/skills-assets/xlsx/references/edit.md +0 -684
  127. package/bin/skills-assets/xlsx/references/fix.md +0 -37
  128. package/bin/skills-assets/xlsx/references/format.md +0 -768
  129. package/bin/skills-assets/xlsx/references/ooxml-cheatsheet.md +0 -231
  130. package/bin/skills-assets/xlsx/references/read-analyze.md +0 -97
  131. package/bin/skills-assets/xlsx/references/validate.md +0 -772
  132. package/bin/skills-assets/xlsx/scripts/formula_check.py +0 -422
  133. package/bin/skills-assets/xlsx/scripts/libreoffice_recalc.py +0 -248
  134. package/bin/skills-assets/xlsx/scripts/shared_strings_builder.py +0 -163
  135. package/bin/skills-assets/xlsx/scripts/style_audit.py +0 -575
  136. package/bin/skills-assets/xlsx/scripts/xlsx_add_column.py +0 -395
  137. package/bin/skills-assets/xlsx/scripts/xlsx_insert_row.py +0 -274
  138. package/bin/skills-assets/xlsx/scripts/xlsx_pack.py +0 -87
  139. package/bin/skills-assets/xlsx/scripts/xlsx_reader.py +0 -362
  140. package/bin/skills-assets/xlsx/scripts/xlsx_shift_rows.py +0 -396
  141. package/bin/skills-assets/xlsx/scripts/xlsx_unpack.py +0 -130
  142. package/bin/skills-assets/xlsx/templates/minimal_xlsx/[Content_Types].xml +0 -9
  143. package/bin/skills-assets/xlsx/templates/minimal_xlsx/_rels/.rels +0 -6
  144. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +0 -19
  145. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +0 -33
  146. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/styles.xml +0 -160
  147. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/workbook.xml +0 -30
  148. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +0 -70
@@ -1,395 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- """
4
- xlsx_add_column.py — Add a new column to a worksheet in an unpacked xlsx.
5
-
6
- Usage examples:
7
- # Add a percentage column with formulas and number format
8
- python3 xlsx_add_column.py /tmp/work/ --col G \\
9
- --sheet "Budget FY2025" \\
10
- --header "% of Total" \\
11
- --formula '=F{row}/$F$10' --formula-rows 2:9 \\
12
- --total-row 10 --total-formula '=SUM(G2:G9)' \\
13
- --numfmt '0.0%'
14
-
15
- What it does:
16
- 1. Adds header cell (copies style from previous column's header)
17
- 2. Adds formula cells for the specified row range
18
- 3. Adds a total formula cell if specified
19
- 4. Creates a new cell style with the given numfmt if needed
20
- 5. Updates sharedStrings.xml for header text
21
- 6. Updates dimension ref and column definitions
22
-
23
- IMPORTANT: Run on an UNPACKED directory (from xlsx_unpack.py).
24
- After running, repack with xlsx_pack.py.
25
- """
26
-
27
- import argparse
28
- import copy
29
- import os
30
- import re
31
- import sys
32
- import xml.dom.minidom
33
- import xml.etree.ElementTree as ET
34
-
35
- NS_SS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
36
- NS_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
37
-
38
- ET.register_namespace('', NS_SS)
39
- ET.register_namespace('r', NS_REL)
40
- ET.register_namespace('xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing')
41
- ET.register_namespace('x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main')
42
- ET.register_namespace('xr2', 'http://schemas.microsoft.com/office/spreadsheetml/2015/revision2')
43
- ET.register_namespace('mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006')
44
-
45
-
46
- def _tag(local: str) -> str:
47
- return f"{{{NS_SS}}}{local}"
48
-
49
-
50
- def _write_tree(tree: ET.ElementTree, path: str) -> None:
51
- tree.write(path, encoding="unicode", xml_declaration=False)
52
- with open(path, "r", encoding="utf-8") as fh:
53
- raw = fh.read()
54
- try:
55
- dom = xml.dom.minidom.parseString(raw.encode("utf-8"))
56
- pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
57
- lines = [line for line in pretty.splitlines() if line.strip()]
58
- with open(path, "w", encoding="utf-8") as fh:
59
- fh.write("\n".join(lines) + "\n")
60
- except Exception:
61
- pass
62
-
63
-
64
- def col_number(s: str) -> int:
65
- n = 0
66
- for c in s.upper():
67
- n = n * 26 + (ord(c) - 64)
68
- return n
69
-
70
-
71
- def col_letter(n: int) -> str:
72
- r = ""
73
- while n > 0:
74
- n, rem = divmod(n - 1, 26)
75
- r = chr(65 + rem) + r
76
- return r
77
-
78
-
79
- def find_ws_path(work_dir: str, sheet_name: str | None) -> str:
80
- wb_tree = ET.parse(os.path.join(work_dir, "xl", "workbook.xml"))
81
- rid = None
82
- for sheet in wb_tree.getroot().iter(_tag("sheet")):
83
- if sheet_name is None or sheet.get("name") == sheet_name:
84
- rid = sheet.get(f"{{{NS_REL}}}id")
85
- break
86
-
87
- if rid is None:
88
- print(f"ERROR: Sheet not found: {sheet_name}")
89
- sys.exit(1)
90
-
91
- rels_tree = ET.parse(os.path.join(work_dir, "xl", "_rels", "workbook.xml.rels"))
92
- for rel in rels_tree.getroot():
93
- if rel.get("Id") == rid:
94
- return os.path.join(work_dir, "xl", rel.get("Target"))
95
-
96
- print(f"ERROR: Relationship not found: {rid}")
97
- sys.exit(1)
98
-
99
-
100
- def add_shared_string(work_dir: str, text: str) -> int:
101
- ss_path = os.path.join(work_dir, "xl", "sharedStrings.xml")
102
- tree = ET.parse(ss_path)
103
- root = tree.getroot()
104
-
105
- idx = 0
106
- for si in root.findall(_tag("si")):
107
- t_el = si.find(_tag("t"))
108
- if t_el is not None and t_el.text == text:
109
- return idx
110
- idx += 1
111
-
112
- si = ET.SubElement(root, _tag("si"))
113
- t = ET.SubElement(si, _tag("t"))
114
- t.set("{http://www.w3.org/XML/1998/namespace}space", "preserve")
115
- t.text = text
116
-
117
- root.set("count", str(int(root.get("count", "0")) + 1))
118
- root.set("uniqueCount", str(int(root.get("uniqueCount", "0")) + 1))
119
-
120
- _write_tree(tree, ss_path)
121
- return idx
122
-
123
-
124
- def get_cell_style(ws_tree: ET.ElementTree, col: str, row: int) -> int:
125
- ref = f"{col}{row}"
126
- for row_el in ws_tree.getroot().iter(_tag("row")):
127
- if row_el.get("r") == str(row):
128
- for c in row_el:
129
- if c.get("r") == ref:
130
- return int(c.get("s", "0"))
131
- return 0
132
-
133
-
134
- def ensure_numfmt_style(work_dir: str, ref_style_idx: int, numfmt_code: str) -> int:
135
- """Clone a cellXfs entry with the given numfmt. Returns new style index."""
136
- styles_path = os.path.join(work_dir, "xl", "styles.xml")
137
- tree = ET.parse(styles_path)
138
- root = tree.getroot()
139
-
140
- # Find or add numFmt
141
- numfmts = root.find(_tag("numFmts"))
142
- numfmt_id = None
143
- if numfmts is not None:
144
- for nf in numfmts:
145
- if nf.get("formatCode") == numfmt_code:
146
- numfmt_id = int(nf.get("numFmtId"))
147
- break
148
-
149
- if numfmt_id is None:
150
- max_id = 163
151
- if numfmts is not None:
152
- for nf in numfmts:
153
- max_id = max(max_id, int(nf.get("numFmtId", "0")))
154
- else:
155
- numfmts = ET.SubElement(root, _tag("numFmts"))
156
- numfmts.set("count", "0")
157
- root.remove(numfmts)
158
- root.insert(0, numfmts)
159
-
160
- numfmt_id = max_id + 1
161
- nf = ET.SubElement(numfmts, _tag("numFmt"))
162
- nf.set("numFmtId", str(numfmt_id))
163
- nf.set("formatCode", numfmt_code)
164
- numfmts.set("count", str(len(list(numfmts))))
165
-
166
- # Find or create cellXfs entry
167
- cellxfs = root.find(_tag("cellXfs"))
168
- xf_list = list(cellxfs)
169
- ref_xf = xf_list[min(ref_style_idx, len(xf_list) - 1)]
170
-
171
- for i, xf in enumerate(xf_list):
172
- if (xf.get("numFmtId") == str(numfmt_id) and
173
- xf.get("fontId") == ref_xf.get("fontId") and
174
- xf.get("fillId") == ref_xf.get("fillId") and
175
- xf.get("borderId") == ref_xf.get("borderId")):
176
- return i
177
-
178
- new_xf = copy.deepcopy(ref_xf)
179
- new_xf.set("numFmtId", str(numfmt_id))
180
- new_xf.set("applyNumberFormat", "true")
181
- cellxfs.append(new_xf)
182
- cellxfs.set("count", str(len(list(cellxfs))))
183
-
184
- _write_tree(tree, styles_path)
185
- return len(list(cellxfs)) - 1
186
-
187
-
188
- def _apply_border_to_row(work_dir: str, ws_path: str, ws_tree: ET.ElementTree,
189
- ws_root: ET.Element, row_map: dict, border_row: int,
190
- border_style: str, new_col: str) -> None:
191
- """Apply a top border to ALL cells in the specified row (A through new_col)."""
192
- styles_path = os.path.join(work_dir, "xl", "styles.xml")
193
- st_tree = ET.parse(styles_path)
194
- st_root = st_tree.getroot()
195
-
196
- # 1. Create a new border entry with the specified top style
197
- borders = st_root.find(_tag("borders"))
198
- new_border = ET.SubElement(borders, _tag("border"))
199
- for side in ("left", "right"):
200
- ET.SubElement(new_border, _tag(side))
201
- top_el = ET.SubElement(new_border, _tag("top"))
202
- top_el.set("style", border_style)
203
- ET.SubElement(new_border, _tag("bottom"))
204
- ET.SubElement(new_border, _tag("diagonal"))
205
- borders.set("count", str(len(list(borders))))
206
- new_border_id = len(list(borders)) - 1
207
-
208
- # 2. For each existing style used in the row, create a clone with the new borderId
209
- cellxfs = st_root.find(_tag("cellXfs"))
210
- style_remap = {} # old_style_idx -> new_style_idx
211
-
212
- if border_row not in row_map:
213
- return
214
-
215
- row_el = row_map[border_row]
216
- # Collect all cells in this row and their styles
217
- for c in row_el:
218
- old_s = int(c.get("s", "0"))
219
- if old_s not in style_remap:
220
- xf_list = list(cellxfs)
221
- ref_xf = xf_list[min(old_s, len(xf_list) - 1)]
222
- new_xf = copy.deepcopy(ref_xf)
223
- new_xf.set("borderId", str(new_border_id))
224
- new_xf.set("applyBorder", "true")
225
- cellxfs.append(new_xf)
226
- cellxfs.set("count", str(len(list(cellxfs))))
227
- style_remap[old_s] = len(list(cellxfs)) - 1
228
-
229
- # 3. Apply remapped styles to all cells in the row
230
- for c in row_el:
231
- old_s = int(c.get("s", "0"))
232
- if old_s in style_remap:
233
- c.set("s", str(style_remap[old_s]))
234
-
235
- _write_tree(st_tree, styles_path)
236
- last_col_num = col_number(new_col)
237
- print(f" Applied {border_style} top border to all cells in row {border_row} "
238
- f"(A-{new_col}, {len(style_remap)} style(s) cloned)")
239
-
240
-
241
- def main() -> None:
242
- parser = argparse.ArgumentParser(
243
- description="Add a column to a worksheet in an unpacked xlsx")
244
- parser.add_argument("work_dir", help="Unpacked xlsx working directory")
245
- parser.add_argument("--col", required=True, help="Column letter (e.g., G)")
246
- parser.add_argument("--sheet", default=None, help="Sheet name (default: first)")
247
- parser.add_argument("--header", default=None, help="Header text for row 1")
248
- parser.add_argument("--formula", default=None,
249
- help="Formula template with {row} placeholder")
250
- parser.add_argument("--formula-rows", default=None,
251
- help="Row range for formulas (e.g., 2:9)")
252
- parser.add_argument("--total-row", type=int, default=None,
253
- help="Row number for total formula")
254
- parser.add_argument("--total-formula", default=None,
255
- help="Formula for total row")
256
- parser.add_argument("--numfmt", default=None,
257
- help="Number format for data/total cells (e.g., 0.0%%)")
258
- parser.add_argument("--border-row", type=int, default=None,
259
- help="Row to apply a top border to ALL cells (e.g., 10)")
260
- parser.add_argument("--border-style", default="medium",
261
- help="Border style: thin, medium, thick (default: medium)")
262
- args = parser.parse_args()
263
-
264
- col = args.col.upper()
265
- prev_col = col_letter(col_number(col) - 1) if col_number(col) > 1 else "A"
266
-
267
- ws_path = find_ws_path(args.work_dir, args.sheet)
268
- ws_tree = ET.parse(ws_path)
269
- changes = 0
270
-
271
- print(f"Adding column {col} to {os.path.basename(ws_path)}")
272
-
273
- # Resolve styles from previous column
274
- header_style = get_cell_style(ws_tree, prev_col, 1) if args.header else 0
275
-
276
- data_style = None
277
- if args.formula_rows:
278
- start_row = int(args.formula_rows.split(":")[0])
279
- ref = get_cell_style(ws_tree, prev_col, start_row)
280
- data_style = (ensure_numfmt_style(args.work_dir, ref, args.numfmt)
281
- if args.numfmt else ref)
282
-
283
- total_style = None
284
- if args.total_row:
285
- ref = get_cell_style(ws_tree, prev_col, args.total_row)
286
- total_style = (ensure_numfmt_style(args.work_dir, ref, args.numfmt)
287
- if args.numfmt else ref)
288
-
289
- # Add header to sharedStrings
290
- header_idx = add_shared_string(args.work_dir, args.header) if args.header else None
291
-
292
- # Re-parse worksheet (sharedStrings write may have changed state)
293
- ws_tree = ET.parse(ws_path)
294
- root = ws_tree.getroot()
295
- sheet_data = root.find(_tag("sheetData"))
296
-
297
- row_map = {}
298
- for row_el in sheet_data:
299
- r = row_el.get("r")
300
- if r:
301
- row_map[int(r)] = row_el
302
-
303
- # Add header cell
304
- if args.header and 1 in row_map:
305
- cell = ET.SubElement(row_map[1], _tag("c"))
306
- cell.set("r", f"{col}1")
307
- cell.set("s", str(header_style))
308
- cell.set("t", "s")
309
- v = ET.SubElement(cell, _tag("v"))
310
- v.text = str(header_idx)
311
- changes += 1
312
- print(f" {col}1 = \"{args.header}\" (header, style={header_style})")
313
-
314
- # Add formula cells
315
- if args.formula and args.formula_rows:
316
- start, end = map(int, args.formula_rows.split(":"))
317
- for row_num in range(start, end + 1):
318
- if row_num not in row_map:
319
- row_el = ET.SubElement(sheet_data, _tag("row"))
320
- row_el.set("r", str(row_num))
321
- row_map[row_num] = row_el
322
-
323
- formula_text = args.formula.replace("{row}", str(row_num))
324
- formula_text = formula_text.lstrip("=")
325
- cell = ET.SubElement(row_map[row_num], _tag("c"))
326
- cell.set("r", f"{col}{row_num}")
327
- if data_style is not None:
328
- cell.set("s", str(data_style))
329
- f_el = ET.SubElement(cell, _tag("f"))
330
- f_el.text = formula_text
331
- changes += 1
332
-
333
- print(f" {col}{start}:{col}{end} = formulas (style={data_style})")
334
-
335
- # Add total formula
336
- if args.total_row and args.total_formula:
337
- if args.total_row not in row_map:
338
- row_el = ET.SubElement(sheet_data, _tag("row"))
339
- row_el.set("r", str(args.total_row))
340
- row_map[args.total_row] = row_el
341
-
342
- total_f = args.total_formula.lstrip("=")
343
- cell = ET.SubElement(row_map[args.total_row], _tag("c"))
344
- cell.set("r", f"{col}{args.total_row}")
345
- if total_style is not None:
346
- cell.set("s", str(total_style))
347
- f_el = ET.SubElement(cell, _tag("f"))
348
- f_el.text = total_f
349
- changes += 1
350
- print(f" {col}{args.total_row} = ={total_f} (style={total_style})")
351
-
352
- # Update dimension
353
- for dim in root.iter(_tag("dimension")):
354
- old_ref = dim.get("ref", "")
355
- if ":" in old_ref:
356
- start_ref, end_ref = old_ref.split(":")
357
- end_col_str = re.match(r"([A-Z]+)", end_ref).group(1)
358
- end_row_str = re.search(r"(\d+)", end_ref).group(1)
359
- if col_number(col) > col_number(end_col_str):
360
- new_ref = f"{start_ref}:{col}{end_row_str}"
361
- dim.set("ref", new_ref)
362
- print(f" Dimension: {old_ref} → {new_ref}")
363
-
364
- # Extend <cols> to cover new column
365
- cols_el = root.find(_tag("cols"))
366
- if cols_el is not None:
367
- new_col_num = col_number(col)
368
- covered = any(
369
- int(c.get("min", "0")) <= new_col_num <= int(c.get("max", "0"))
370
- for c in cols_el
371
- )
372
- if not covered:
373
- prev_num = col_number(prev_col)
374
- for c in cols_el:
375
- if int(c.get("min", "0")) <= prev_num <= int(c.get("max", "0")):
376
- new_col_def = copy.deepcopy(c)
377
- new_col_def.set("min", str(new_col_num))
378
- new_col_def.set("max", str(new_col_num))
379
- cols_el.append(new_col_def)
380
- print(f" Added <col> definition for column {col}")
381
- break
382
-
383
- # Apply border to entire row if requested
384
- if args.border_row:
385
- _apply_border_to_row(args.work_dir, ws_path, ws_tree, root,
386
- row_map, args.border_row, args.border_style,
387
- col)
388
-
389
- _write_tree(ws_tree, ws_path)
390
- print(f"\nDone. {changes} cells added.")
391
- print(f"\nNext: python3 xlsx_pack.py {args.work_dir} output.xlsx")
392
-
393
-
394
- if __name__ == "__main__":
395
- main()
@@ -1,274 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- """
4
- xlsx_insert_row.py — Insert a new data row into a worksheet in an unpacked xlsx.
5
-
6
- Usage examples:
7
- # Insert "Utilities" row at position 6, copying styles from row 5
8
- python3 xlsx_insert_row.py /tmp/work/ --at 6 \\
9
- --sheet "Budget FY2025" \\
10
- --text A=Utilities \\
11
- --values B=3000 C=3000 D=3500 E=3500 \\
12
- --formula 'F=SUM(B{row}:E{row})' \\
13
- --copy-style-from 5
14
-
15
- What it does:
16
- 1. Shifts all rows >= at down by 1 (calls xlsx_shift_rows.py)
17
- 2. Adds text values to sharedStrings.xml
18
- 3. Inserts new row with specified cells (text, numbers, formulas)
19
- 4. Copies cell styles from a reference row
20
- 5. Updates dimension ref
21
-
22
- The shift operation automatically expands SUM formulas that span the
23
- insertion point, so total-row formulas are updated without extra work.
24
-
25
- IMPORTANT: Run on an UNPACKED directory (from xlsx_unpack.py).
26
- After running, repack with xlsx_pack.py.
27
- """
28
-
29
- import argparse
30
- import os
31
- import re
32
- import subprocess
33
- import sys
34
- import xml.dom.minidom
35
- import xml.etree.ElementTree as ET
36
-
37
- NS_SS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
38
- NS_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
39
-
40
- ET.register_namespace('', NS_SS)
41
- ET.register_namespace('r', NS_REL)
42
- ET.register_namespace('xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing')
43
- ET.register_namespace('x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main')
44
- ET.register_namespace('xr2', 'http://schemas.microsoft.com/office/spreadsheetml/2015/revision2')
45
- ET.register_namespace('mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006')
46
-
47
-
48
- def _tag(local: str) -> str:
49
- return f"{{{NS_SS}}}{local}"
50
-
51
-
52
- def _write_tree(tree: ET.ElementTree, path: str) -> None:
53
- tree.write(path, encoding="unicode", xml_declaration=False)
54
- with open(path, "r", encoding="utf-8") as fh:
55
- raw = fh.read()
56
- try:
57
- dom = xml.dom.minidom.parseString(raw.encode("utf-8"))
58
- pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
59
- lines = [line for line in pretty.splitlines() if line.strip()]
60
- with open(path, "w", encoding="utf-8") as fh:
61
- fh.write("\n".join(lines) + "\n")
62
- except Exception:
63
- pass
64
-
65
-
66
- def col_number(s: str) -> int:
67
- n = 0
68
- for c in s.upper():
69
- n = n * 26 + (ord(c) - 64)
70
- return n
71
-
72
-
73
- def find_ws_path(work_dir: str, sheet_name: str | None) -> str:
74
- wb_tree = ET.parse(os.path.join(work_dir, "xl", "workbook.xml"))
75
- rid = None
76
- for sheet in wb_tree.getroot().iter(_tag("sheet")):
77
- if sheet_name is None or sheet.get("name") == sheet_name:
78
- rid = sheet.get(f"{{{NS_REL}}}id")
79
- break
80
-
81
- if rid is None:
82
- print(f"ERROR: Sheet not found: {sheet_name}")
83
- sys.exit(1)
84
-
85
- rels_tree = ET.parse(os.path.join(work_dir, "xl", "_rels", "workbook.xml.rels"))
86
- for rel in rels_tree.getroot():
87
- if rel.get("Id") == rid:
88
- return os.path.join(work_dir, "xl", rel.get("Target"))
89
-
90
- print(f"ERROR: Relationship not found: {rid}")
91
- sys.exit(1)
92
-
93
-
94
- def add_shared_string(work_dir: str, text: str) -> int:
95
- ss_path = os.path.join(work_dir, "xl", "sharedStrings.xml")
96
- tree = ET.parse(ss_path)
97
- root = tree.getroot()
98
-
99
- idx = 0
100
- for si in root.findall(_tag("si")):
101
- t_el = si.find(_tag("t"))
102
- if t_el is not None and t_el.text == text:
103
- return idx
104
- idx += 1
105
-
106
- si = ET.SubElement(root, _tag("si"))
107
- t = ET.SubElement(si, _tag("t"))
108
- t.set("{http://www.w3.org/XML/1998/namespace}space", "preserve")
109
- t.text = text
110
-
111
- root.set("count", str(int(root.get("count", "0")) + 1))
112
- root.set("uniqueCount", str(int(root.get("uniqueCount", "0")) + 1))
113
-
114
- _write_tree(tree, ss_path)
115
- return idx
116
-
117
-
118
- def get_row_styles(ws_tree: ET.ElementTree, row_num: int) -> dict[str, int]:
119
- """Get {col_letter: style_index} for all cells in a row."""
120
- styles = {}
121
- for row_el in ws_tree.getroot().iter(_tag("row")):
122
- if row_el.get("r") == str(row_num):
123
- for c in row_el:
124
- ref = c.get("r", "")
125
- col_str = re.match(r"([A-Z]+)", ref)
126
- if col_str:
127
- styles[col_str.group(1)] = int(c.get("s", "0"))
128
- break
129
- return styles
130
-
131
-
132
- def parse_kv(specs: list[str] | None) -> dict[str, str]:
133
- if not specs:
134
- return {}
135
- result = {}
136
- for spec in specs:
137
- col, _, val = spec.partition("=")
138
- result[col.upper()] = val
139
- return result
140
-
141
-
142
- def main() -> None:
143
- parser = argparse.ArgumentParser(
144
- description="Insert a new row into a worksheet in an unpacked xlsx")
145
- parser.add_argument("work_dir", help="Unpacked xlsx working directory")
146
- parser.add_argument("--at", type=int, required=True,
147
- help="Row number to insert at (existing rows shift down)")
148
- parser.add_argument("--sheet", default=None, help="Sheet name (default: first)")
149
- parser.add_argument("--text", nargs="+", default=None,
150
- help="Text cells: COL=VALUE (e.g., A=Utilities)")
151
- parser.add_argument("--values", nargs="+", default=None,
152
- help="Numeric cells: COL=VALUE (e.g., B=3000 C=3000)")
153
- parser.add_argument("--formula", nargs="+", default=None,
154
- help="Formula cells: COL=FORMULA with {row} (e.g., F=SUM(B{row}:E{row}))")
155
- parser.add_argument("--copy-style-from", type=int, default=None,
156
- help="Copy cell styles from this row number")
157
- args = parser.parse_args()
158
-
159
- at = args.at
160
- text_cells = parse_kv(args.text)
161
- num_cells = parse_kv(args.values)
162
- formula_cells = parse_kv(args.formula)
163
-
164
- # Step 1: Shift rows down using xlsx_shift_rows.py
165
- script_dir = os.path.dirname(os.path.abspath(__file__))
166
- shift_script = os.path.join(script_dir, "xlsx_shift_rows.py")
167
-
168
- print(f"Step 1: Shifting rows >= {at} down by 1...")
169
- result = subprocess.run(
170
- [sys.executable, shift_script, args.work_dir, "insert", str(at), "1"],
171
- capture_output=True, text=True,
172
- )
173
- if result.returncode != 0:
174
- print(f"ERROR: shift_rows failed:\n{result.stderr}")
175
- sys.exit(1)
176
- print(result.stdout)
177
-
178
- # Step 2: Resolve worksheet path and get reference styles
179
- ws_path = find_ws_path(args.work_dir, args.sheet)
180
- ws_tree = ET.parse(ws_path)
181
-
182
- ref_styles = {}
183
- if args.copy_style_from is not None:
184
- ref_styles = get_row_styles(ws_tree, args.copy_style_from)
185
- print(f"Step 2: Copied styles from row {args.copy_style_from}: {ref_styles}")
186
-
187
- # Step 3: Add text values to sharedStrings
188
- text_indices = {}
189
- for col, text in text_cells.items():
190
- text_indices[col] = add_shared_string(args.work_dir, text)
191
- print(f" Added shared string: \"{text}\" → index {text_indices[col]}")
192
-
193
- # Step 4: Re-parse worksheet and build new row
194
- ws_tree = ET.parse(ws_path)
195
- root = ws_tree.getroot()
196
- sheet_data = root.find(_tag("sheetData"))
197
-
198
- new_row = ET.Element(_tag("row"))
199
- new_row.set("r", str(at))
200
-
201
- all_cols = sorted(
202
- set(list(text_cells) + list(num_cells) + list(formula_cells)),
203
- key=col_number,
204
- )
205
-
206
- for col in all_cols:
207
- cell = ET.SubElement(new_row, _tag("c"))
208
- cell.set("r", f"{col}{at}")
209
-
210
- if col in ref_styles:
211
- cell.set("s", str(ref_styles[col]))
212
-
213
- if col in text_cells:
214
- cell.set("t", "s")
215
- v = ET.SubElement(cell, _tag("v"))
216
- v.text = str(text_indices[col])
217
- elif col in num_cells:
218
- # Omit t attribute for numbers — "n" is the default per OOXML spec
219
- v = ET.SubElement(cell, _tag("v"))
220
- v.text = str(num_cells[col])
221
- elif col in formula_cells:
222
- formula_text = formula_cells[col].replace("{row}", str(at)).lstrip("=")
223
- f_el = ET.SubElement(cell, _tag("f"))
224
- f_el.text = formula_text
225
- # Use formula style from reference if available; it may differ
226
- # from the data style (e.g., black font vs blue font).
227
- # Look for the formula column's style specifically.
228
- if col in ref_styles:
229
- cell.set("s", str(ref_styles[col]))
230
-
231
- # Insert new row at the correct position in sheetData (sorted by row number)
232
- insert_idx = 0
233
- for i, row_el in enumerate(list(sheet_data)):
234
- r = row_el.get("r")
235
- if r and int(r) > at:
236
- insert_idx = i
237
- break
238
- insert_idx = i + 1
239
-
240
- sheet_data.insert(insert_idx, new_row)
241
-
242
- print(f"\nStep 3: Inserted row {at} with {len(all_cols)} cells:")
243
- for col in all_cols:
244
- if col in text_cells:
245
- print(f" {col}{at} = \"{text_cells[col]}\" (text)")
246
- elif col in num_cells:
247
- print(f" {col}{at} = {num_cells[col]} (number)")
248
- elif col in formula_cells:
249
- ftext = formula_cells[col].replace("{row}", str(at))
250
- print(f" {col}{at} = {ftext} (formula)")
251
-
252
- # Step 5: Update dimension
253
- for dim in root.iter(_tag("dimension")):
254
- old_ref = dim.get("ref", "")
255
- if ":" in old_ref:
256
- start_ref, end_ref = old_ref.split(":")
257
- end_row = int(re.search(r"(\d+)", end_ref).group(1))
258
- end_col = re.match(r"([A-Z]+)", end_ref).group(1)
259
- # Dimension was already shifted by shift_rows, just verify
260
- max_col = max(col_number(end_col), max(col_number(c) for c in all_cols))
261
- max_col_letter = end_col if col_number(end_col) >= max_col else col
262
- new_ref = f"{start_ref}:{max_col_letter}{end_row}"
263
- if new_ref != old_ref:
264
- dim.set("ref", new_ref)
265
- print(f"\n Dimension: {old_ref} → {new_ref}")
266
-
267
- _write_tree(ws_tree, ws_path)
268
-
269
- print(f"\nDone. Row {at} inserted successfully.")
270
- print(f"\nNext: python3 xlsx_pack.py {args.work_dir} output.xlsx")
271
-
272
-
273
- if __name__ == "__main__":
274
- main()