@nguyenphp/antigravity-marketing 1.0.18 → 1.0.19
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.
- package/README.md +186 -78
- package/package.json +4 -3
- package/templates/.agent/skills/marketing-report-expert/SKILL.md +70 -0
- package/templates/.agent/skills/minimax-docx/LICENSE +21 -0
- package/templates/.agent/skills/minimax-docx/SKILL.md +274 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
- package/templates/.agent/skills/minimax-docx/references/cjk_typography.md +357 -0
- package/templates/.agent/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
- package/templates/.agent/skills/minimax-docx/references/comments_guide.md +191 -0
- package/templates/.agent/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
- package/templates/.agent/skills/minimax-docx/references/design_principles.md +819 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_element_order.md +308 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_namespaces.md +82 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_units.md +72 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_a_create.md +284 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
- package/templates/.agent/skills/minimax-docx/references/track_changes_guide.md +200 -0
- package/templates/.agent/skills/minimax-docx/references/troubleshooting.md +506 -0
- package/templates/.agent/skills/minimax-docx/references/typography_guide.md +294 -0
- package/templates/.agent/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
- package/templates/.agent/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
- package/templates/.agent/skills/minimax-docx/scripts/docx_preview.sh +37 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
- package/templates/.agent/skills/minimax-docx/scripts/env_check.sh +196 -0
- package/templates/.agent/skills/minimax-docx/scripts/setup.ps1 +274 -0
- package/templates/.agent/skills/minimax-docx/scripts/setup.sh +504 -0
- package/templates/.agent/skills/minimax-multimodal-toolkit/SKILL.md +359 -0
- package/templates/.agent/skills/minimax-pdf/README.md +222 -0
- package/templates/.agent/skills/minimax-pdf/SKILL.md +201 -0
- package/templates/.agent/skills/minimax-pdf/design/design.md +381 -0
- package/templates/.agent/skills/minimax-pdf/scripts/cover.py +1579 -0
- package/templates/.agent/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
- package/templates/.agent/skills/minimax-pdf/scripts/fill_write.py +242 -0
- package/templates/.agent/skills/minimax-pdf/scripts/make.sh +491 -0
- package/templates/.agent/skills/minimax-pdf/scripts/merge.py +112 -0
- package/templates/.agent/skills/minimax-pdf/scripts/palette.py +559 -0
- package/templates/.agent/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
- package/templates/.agent/skills/minimax-pdf/scripts/render_body.py +1055 -0
- package/templates/.agent/skills/minimax-pdf/scripts/render_cover.cjs +111 -0
- package/templates/.agent/skills/minimax-xlsx/SKILL.md +138 -0
- package/templates/.agent/skills/minimax-xlsx/references/create.md +691 -0
- package/templates/.agent/skills/minimax-xlsx/references/edit.md +684 -0
- package/templates/.agent/skills/minimax-xlsx/references/fix.md +37 -0
- package/templates/.agent/skills/minimax-xlsx/references/format.md +768 -0
- package/templates/.agent/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
- package/templates/.agent/skills/minimax-xlsx/references/read-analyze.md +97 -0
- package/templates/.agent/skills/minimax-xlsx/references/validate.md +772 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/formula_check.py +422 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/style_audit.py +575 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
- package/templates/.agent/skills/pptx-generator/SKILL.md +249 -0
- package/templates/.agent/skills/pptx-generator/references/design-system.md +392 -0
- package/templates/.agent/skills/pptx-generator/references/editing.md +162 -0
- package/templates/.agent/skills/pptx-generator/references/pitfalls.md +112 -0
- package/templates/.agent/skills/pptx-generator/references/pptxgenjs.md +420 -0
- package/templates/.agent/skills/pptx-generator/references/slide-types.md +413 -0
- package/templates/.agent/skills/tutorial-video-expert/SKILL.md +88 -0
- package/templates/.agent/skills/ui-ux-pro-max/SKILL.md +170 -585
- package/templates/.agent/skills/vision-analysis/SKILL.md +174 -0
- package/templates/.agent/workflows/analyze.md +3 -0
- package/templates/.agent/workflows/brand-report.md +44 -0
- package/templates/.agent/workflows/report.md +49 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
xlsx_shift_rows.py — Shift all row references in an unpacked xlsx working directory
|
|
5
|
+
after inserting or deleting rows.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Insert 2 rows at row 5 (rows 5+ shift down by 2)
|
|
9
|
+
python3 xlsx_shift_rows.py <work_dir> insert 5 2
|
|
10
|
+
|
|
11
|
+
# Delete 1 row at row 8 (rows 9+ shift up by 1)
|
|
12
|
+
python3 xlsx_shift_rows.py <work_dir> delete 8 1
|
|
13
|
+
|
|
14
|
+
What it updates in every XML file under <work_dir>:
|
|
15
|
+
- <row r="N"> attributes in worksheet sheetData
|
|
16
|
+
- <c r="XN"> cell address attributes in worksheet sheetData
|
|
17
|
+
- <f> formula text: absolute row references (e.g. B7, $B$7, $B7) in all sheets
|
|
18
|
+
- <mergeCell ref="A5:C7"> ranges
|
|
19
|
+
- <conditionalFormatting sqref="..."> ranges
|
|
20
|
+
- <dataValidations sqref="..."> ranges
|
|
21
|
+
- <dimension ref="A1:D20"> extent marker
|
|
22
|
+
- Table <table ref="A1:D20"> in xl/tables/*.xml
|
|
23
|
+
- Chart series <numRef><f> and <strRef><f> range references in xl/charts/*.xml
|
|
24
|
+
- PivotCache source <worksheetSource ref="..."> in xl/pivotCaches/*.xml
|
|
25
|
+
|
|
26
|
+
IMPORTANT: Run this script on the UNPACKED directory before repacking.
|
|
27
|
+
After running, repack with xlsx_pack.py and re-validate with formula_check.py.
|
|
28
|
+
|
|
29
|
+
Limitations:
|
|
30
|
+
- Named ranges in workbook.xml <definedNames> are NOT updated automatically.
|
|
31
|
+
Review them manually after running this script.
|
|
32
|
+
- Structured table references (Table[@Column]) are NOT updated.
|
|
33
|
+
- External workbook links in xl/externalLinks/ are NOT updated.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import sys
|
|
37
|
+
import os
|
|
38
|
+
import re
|
|
39
|
+
import xml.etree.ElementTree as ET
|
|
40
|
+
import xml.dom.minidom
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def col_letter(n: int) -> str:
|
|
44
|
+
"""Convert 1-based column number to Excel column letter(s)."""
|
|
45
|
+
r = ""
|
|
46
|
+
while n > 0:
|
|
47
|
+
n, rem = divmod(n - 1, 26)
|
|
48
|
+
r = chr(65 + rem) + r
|
|
49
|
+
return r
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def col_number(s: str) -> int:
|
|
53
|
+
"""Convert Excel column letter(s) to 1-based column number."""
|
|
54
|
+
n = 0
|
|
55
|
+
for c in s.upper():
|
|
56
|
+
n = n * 26 + (ord(c) - 64)
|
|
57
|
+
return n
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Core shifting logic for formula strings
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def _shift_refs(text: str, at: int, delta: int) -> str:
|
|
65
|
+
"""Shift cell references in a non-quoted formula fragment."""
|
|
66
|
+
def replacer(m: re.Match) -> str:
|
|
67
|
+
dollar_col = m.group(1) # "$" or ""
|
|
68
|
+
col_part = m.group(2) # e.g. "B" or "AB"
|
|
69
|
+
dollar_row = m.group(3) # "$" or ""
|
|
70
|
+
row_str = m.group(4) # e.g. "7"
|
|
71
|
+
row = int(row_str)
|
|
72
|
+
if row >= at:
|
|
73
|
+
row = max(1, row + delta)
|
|
74
|
+
return f"{dollar_col}{col_part}{dollar_row}{row}"
|
|
75
|
+
|
|
76
|
+
pattern = r'(\$?)([A-Z]+)(\$?)(\d+)'
|
|
77
|
+
return re.sub(pattern, replacer, text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def shift_formula(formula: str, at: int, delta: int) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Shift absolute and mixed row references >= `at` by `delta` in a formula string.
|
|
83
|
+
|
|
84
|
+
Handles:
|
|
85
|
+
B7 (relative col, absolute row — shifts if row >= at)
|
|
86
|
+
$B$7 (absolute col, absolute row — shifts)
|
|
87
|
+
$B7 (absolute col, relative row — shifts)
|
|
88
|
+
B$7 (relative col, absolute — shifts)
|
|
89
|
+
BUT NOT: B:B (whole-column reference — left as-is)
|
|
90
|
+
|
|
91
|
+
Skips content inside single-quoted sheet name prefixes to avoid
|
|
92
|
+
corrupting names like 'Budget FY2025' (where FY2025 is NOT a cell ref).
|
|
93
|
+
|
|
94
|
+
Does NOT handle:
|
|
95
|
+
- Named ranges
|
|
96
|
+
- Structured references (Table[@Col])
|
|
97
|
+
- R1C1 notation
|
|
98
|
+
"""
|
|
99
|
+
# Split on quoted sheet names: 'Sheet Name' portions are odd-indexed
|
|
100
|
+
segments = re.split(r"('[^']*(?:''[^']*)*')", formula)
|
|
101
|
+
result = []
|
|
102
|
+
for i, seg in enumerate(segments):
|
|
103
|
+
if i % 2 == 1:
|
|
104
|
+
result.append(seg)
|
|
105
|
+
else:
|
|
106
|
+
result.append(_shift_refs(seg, at, delta))
|
|
107
|
+
return "".join(result)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def shift_sqref(sqref: str, at: int, delta: int) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Shift row references in a sqref string (space-separated cell/range addresses).
|
|
113
|
+
E.g. "A5:D20 B30" → shift rows >= 5 by delta.
|
|
114
|
+
"""
|
|
115
|
+
parts = sqref.split()
|
|
116
|
+
result = []
|
|
117
|
+
for part in parts:
|
|
118
|
+
if ':' in part:
|
|
119
|
+
left, right = part.split(':', 1)
|
|
120
|
+
left = shift_formula(left, at, delta)
|
|
121
|
+
right = shift_formula(right, at, delta)
|
|
122
|
+
result.append(f"{left}:{right}")
|
|
123
|
+
else:
|
|
124
|
+
result.append(shift_formula(part, at, delta))
|
|
125
|
+
return " ".join(result)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def shift_chart_range(text: str, at: int, delta: int) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Shift row references inside a chart range formula like:
|
|
131
|
+
Sheet1!$B$5:$B$20
|
|
132
|
+
'Q1 Data'!$A$3:$A$15
|
|
133
|
+
"""
|
|
134
|
+
# Split on the "!" to preserve sheet name
|
|
135
|
+
if '!' not in text:
|
|
136
|
+
return text
|
|
137
|
+
bang = text.index('!')
|
|
138
|
+
sheet_part = text[:bang + 1]
|
|
139
|
+
range_part = text[bang + 1:]
|
|
140
|
+
return sheet_part + shift_formula(range_part, at, delta)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# XML file processors
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
NS_MAIN = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
148
|
+
NS_DRAWING = "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
|
149
|
+
|
|
150
|
+
# Namespace map used by ElementTree for tag lookup
|
|
151
|
+
NSMAP = {"ss": NS_MAIN}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _tag(local: str) -> str:
|
|
155
|
+
return f"{{{NS_MAIN}}}{local}"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def process_worksheet(path: str, at: int, delta: int) -> int:
|
|
159
|
+
"""Update row/cell references in a worksheet XML. Returns change count."""
|
|
160
|
+
tree = ET.parse(path)
|
|
161
|
+
root = tree.getroot()
|
|
162
|
+
changes = 0
|
|
163
|
+
|
|
164
|
+
# 1. <dimension ref="A1:D20">
|
|
165
|
+
for dim in root.iter(_tag("dimension")):
|
|
166
|
+
old = dim.get("ref", "")
|
|
167
|
+
new = shift_sqref(old, at, delta)
|
|
168
|
+
if new != old:
|
|
169
|
+
dim.set("ref", new)
|
|
170
|
+
changes += 1
|
|
171
|
+
|
|
172
|
+
# 2. <row r="N"> and <c r="XN"> inside sheetData
|
|
173
|
+
sheet_data = root.find(_tag("sheetData"))
|
|
174
|
+
if sheet_data is not None:
|
|
175
|
+
rows_to_reorder = []
|
|
176
|
+
for row_el in list(sheet_data):
|
|
177
|
+
r_str = row_el.get("r")
|
|
178
|
+
if r_str is None:
|
|
179
|
+
continue
|
|
180
|
+
r = int(r_str)
|
|
181
|
+
if r >= at:
|
|
182
|
+
new_r = max(1, r + delta)
|
|
183
|
+
row_el.set("r", str(new_r))
|
|
184
|
+
changes += 1
|
|
185
|
+
# Update each cell's r attribute
|
|
186
|
+
for cell_el in row_el:
|
|
187
|
+
cell_ref = cell_el.get("r", "")
|
|
188
|
+
if cell_ref:
|
|
189
|
+
new_ref = shift_formula(cell_ref, at, delta)
|
|
190
|
+
if new_ref != cell_ref:
|
|
191
|
+
cell_el.set("r", new_ref)
|
|
192
|
+
changes += 1
|
|
193
|
+
|
|
194
|
+
# Also update formulas in every row (formulas can reference any row)
|
|
195
|
+
for cell_el in row_el:
|
|
196
|
+
f_el = cell_el.find(_tag("f"))
|
|
197
|
+
if f_el is not None and f_el.text:
|
|
198
|
+
new_f = shift_formula(f_el.text, at, delta)
|
|
199
|
+
if new_f != f_el.text:
|
|
200
|
+
f_el.text = new_f
|
|
201
|
+
changes += 1
|
|
202
|
+
|
|
203
|
+
# 3. <mergeCell ref="A5:C7">
|
|
204
|
+
for mc in root.iter(_tag("mergeCell")):
|
|
205
|
+
old = mc.get("ref", "")
|
|
206
|
+
new = shift_sqref(old, at, delta)
|
|
207
|
+
if new != old:
|
|
208
|
+
mc.set("ref", new)
|
|
209
|
+
changes += 1
|
|
210
|
+
|
|
211
|
+
# 4. <conditionalFormatting sqref="...">
|
|
212
|
+
for cf in root.iter(_tag("conditionalFormatting")):
|
|
213
|
+
old = cf.get("sqref", "")
|
|
214
|
+
new = shift_sqref(old, at, delta)
|
|
215
|
+
if new != old:
|
|
216
|
+
cf.set("sqref", new)
|
|
217
|
+
changes += 1
|
|
218
|
+
|
|
219
|
+
# 5. <dataValidation sqref="...">
|
|
220
|
+
for dv in root.iter(_tag("dataValidation")):
|
|
221
|
+
old = dv.get("sqref", "")
|
|
222
|
+
new = shift_sqref(old, at, delta)
|
|
223
|
+
if new != old:
|
|
224
|
+
dv.set("sqref", new)
|
|
225
|
+
changes += 1
|
|
226
|
+
|
|
227
|
+
if changes > 0:
|
|
228
|
+
_write_tree(tree, path)
|
|
229
|
+
return changes
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def process_chart(path: str, at: int, delta: int) -> int:
|
|
233
|
+
"""Update data range references in a chart XML."""
|
|
234
|
+
# Charts use DrawingML namespace; we look for <f> elements with range strings
|
|
235
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
236
|
+
content = fh.read()
|
|
237
|
+
|
|
238
|
+
# Pattern matches content of <f>Sheet1!$A$1:$A$10</f> style elements
|
|
239
|
+
def replace_f(m: re.Match) -> str:
|
|
240
|
+
tag_open = m.group(1)
|
|
241
|
+
inner = m.group(2)
|
|
242
|
+
tag_close = m.group(3)
|
|
243
|
+
new_inner = shift_chart_range(inner, at, delta)
|
|
244
|
+
return f"{tag_open}{new_inner}{tag_close}"
|
|
245
|
+
|
|
246
|
+
new_content = re.sub(r'(<(?:[^:>]+:)?f>)([^<]+)(</(?:[^:>]+:)?f>)',
|
|
247
|
+
replace_f, content)
|
|
248
|
+
changes = content != new_content
|
|
249
|
+
if changes:
|
|
250
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
251
|
+
fh.write(new_content)
|
|
252
|
+
return 1 if changes else 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def process_table(path: str, at: int, delta: int) -> int:
|
|
256
|
+
"""Update the ref attribute on the <table> root element."""
|
|
257
|
+
tree = ET.parse(path)
|
|
258
|
+
root = tree.getroot()
|
|
259
|
+
# The root element IS the table
|
|
260
|
+
old = root.get("ref", "")
|
|
261
|
+
if not old:
|
|
262
|
+
return 0
|
|
263
|
+
new = shift_sqref(old, at, delta)
|
|
264
|
+
if new == old:
|
|
265
|
+
return 0
|
|
266
|
+
root.set("ref", new)
|
|
267
|
+
_write_tree(tree, path)
|
|
268
|
+
return 1
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def process_pivot_cache(path: str, at: int, delta: int) -> int:
|
|
272
|
+
"""Update worksheetSource ref in a pivot cache definition."""
|
|
273
|
+
tree = ET.parse(path)
|
|
274
|
+
root = tree.getroot()
|
|
275
|
+
changes = 0
|
|
276
|
+
# Look for <worksheetSource ref="A1:D100" ...>
|
|
277
|
+
for ws in root.iter():
|
|
278
|
+
if ws.tag.endswith("}worksheetSource") or ws.tag == "worksheetSource":
|
|
279
|
+
old = ws.get("ref", "")
|
|
280
|
+
if old:
|
|
281
|
+
new = shift_sqref(old, at, delta)
|
|
282
|
+
if new != old:
|
|
283
|
+
ws.set("ref", new)
|
|
284
|
+
changes += 1
|
|
285
|
+
if changes:
|
|
286
|
+
_write_tree(tree, path)
|
|
287
|
+
return changes
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _write_tree(tree: ET.ElementTree, path: str) -> None:
|
|
291
|
+
"""Write ElementTree back to file with pretty-printing."""
|
|
292
|
+
tree.write(path, encoding="unicode", xml_declaration=False)
|
|
293
|
+
# Re-pretty-print for readability
|
|
294
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
295
|
+
raw = fh.read()
|
|
296
|
+
try:
|
|
297
|
+
dom = xml.dom.minidom.parseString(raw.encode("utf-8"))
|
|
298
|
+
pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
|
|
299
|
+
lines = [line for line in pretty.splitlines() if line.strip()]
|
|
300
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
301
|
+
fh.write("\n".join(lines) + "\n")
|
|
302
|
+
except Exception:
|
|
303
|
+
pass # If pretty-print fails, leave the file as-is
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ---------------------------------------------------------------------------
|
|
307
|
+
# Main driver
|
|
308
|
+
# ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
def main() -> None:
|
|
311
|
+
if len(sys.argv) < 5:
|
|
312
|
+
print(__doc__)
|
|
313
|
+
sys.exit(1)
|
|
314
|
+
|
|
315
|
+
work_dir = sys.argv[1]
|
|
316
|
+
operation = sys.argv[2].lower()
|
|
317
|
+
at = int(sys.argv[3])
|
|
318
|
+
count = int(sys.argv[4])
|
|
319
|
+
|
|
320
|
+
if operation not in ("insert", "delete"):
|
|
321
|
+
print(f"ERROR: operation must be 'insert' or 'delete', got '{operation}'")
|
|
322
|
+
sys.exit(1)
|
|
323
|
+
|
|
324
|
+
if operation == "insert":
|
|
325
|
+
delta = count
|
|
326
|
+
else:
|
|
327
|
+
delta = -count
|
|
328
|
+
|
|
329
|
+
if not os.path.isdir(work_dir):
|
|
330
|
+
print(f"ERROR: Directory not found: {work_dir}")
|
|
331
|
+
sys.exit(1)
|
|
332
|
+
|
|
333
|
+
print(f"Operation : {operation} {count} row(s) at row {at} (delta={delta:+d})")
|
|
334
|
+
print(f"Work dir : {work_dir}")
|
|
335
|
+
print()
|
|
336
|
+
|
|
337
|
+
total_changes = 0
|
|
338
|
+
|
|
339
|
+
# Process all worksheets
|
|
340
|
+
ws_dir = os.path.join(work_dir, "xl", "worksheets")
|
|
341
|
+
if os.path.isdir(ws_dir):
|
|
342
|
+
for fname in sorted(os.listdir(ws_dir)):
|
|
343
|
+
if fname.endswith(".xml"):
|
|
344
|
+
fpath = os.path.join(ws_dir, fname)
|
|
345
|
+
n = process_worksheet(fpath, at, delta)
|
|
346
|
+
if n:
|
|
347
|
+
print(f" Updated {n:3d} references in xl/worksheets/{fname}")
|
|
348
|
+
total_changes += n
|
|
349
|
+
|
|
350
|
+
# Process all charts
|
|
351
|
+
charts_dir = os.path.join(work_dir, "xl", "charts")
|
|
352
|
+
if os.path.isdir(charts_dir):
|
|
353
|
+
for fname in sorted(os.listdir(charts_dir)):
|
|
354
|
+
if fname.endswith(".xml"):
|
|
355
|
+
fpath = os.path.join(charts_dir, fname)
|
|
356
|
+
n = process_chart(fpath, at, delta)
|
|
357
|
+
if n:
|
|
358
|
+
print(f" Updated chart ranges in xl/charts/{fname}")
|
|
359
|
+
total_changes += n
|
|
360
|
+
|
|
361
|
+
# Process all tables
|
|
362
|
+
tables_dir = os.path.join(work_dir, "xl", "tables")
|
|
363
|
+
if os.path.isdir(tables_dir):
|
|
364
|
+
for fname in sorted(os.listdir(tables_dir)):
|
|
365
|
+
if fname.endswith(".xml"):
|
|
366
|
+
fpath = os.path.join(tables_dir, fname)
|
|
367
|
+
n = process_table(fpath, at, delta)
|
|
368
|
+
if n:
|
|
369
|
+
print(f" Updated table ref in xl/tables/{fname}")
|
|
370
|
+
total_changes += n
|
|
371
|
+
|
|
372
|
+
# Process pivot cache definitions
|
|
373
|
+
cache_dir = os.path.join(work_dir, "xl", "pivotCaches")
|
|
374
|
+
if os.path.isdir(cache_dir):
|
|
375
|
+
for fname in sorted(os.listdir(cache_dir)):
|
|
376
|
+
if "Definition" in fname and fname.endswith(".xml"):
|
|
377
|
+
fpath = os.path.join(cache_dir, fname)
|
|
378
|
+
n = process_pivot_cache(fpath, at, delta)
|
|
379
|
+
if n:
|
|
380
|
+
print(f" Updated pivot source range in xl/pivotCaches/{fname}")
|
|
381
|
+
total_changes += n
|
|
382
|
+
|
|
383
|
+
print()
|
|
384
|
+
print(f"Total changes: {total_changes}")
|
|
385
|
+
print()
|
|
386
|
+
print("IMPORTANT: Review named ranges in xl/workbook.xml <definedNames> manually.")
|
|
387
|
+
print(" Structured table references (Table[@Col]) are NOT updated.")
|
|
388
|
+
print()
|
|
389
|
+
print("Next steps:")
|
|
390
|
+
print(" 1. Review the changes above")
|
|
391
|
+
print(f" 2. python3 xlsx_pack.py {work_dir} output.xlsx")
|
|
392
|
+
print(" 3. python3 formula_check.py output.xlsx")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
if __name__ == "__main__":
|
|
396
|
+
main()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
xlsx_unpack.py — Unpack an xlsx file into a working directory for XML editing.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 xlsx_unpack.py <input.xlsx> <output_dir>
|
|
8
|
+
|
|
9
|
+
What it does:
|
|
10
|
+
1. Unzips the xlsx (which is a ZIP archive)
|
|
11
|
+
2. Pretty-prints all XML and .rels files for readability
|
|
12
|
+
3. Prints a summary of key files to edit
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
import zipfile
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import xml.dom.minidom
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def pretty_print_xml(content: bytes) -> str:
|
|
23
|
+
"""Pretty-print XML bytes. Returns original content on parse failure."""
|
|
24
|
+
try:
|
|
25
|
+
dom = xml.dom.minidom.parseString(content)
|
|
26
|
+
pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
|
|
27
|
+
# Remove the extra blank lines toprettyxml adds
|
|
28
|
+
lines = [line for line in pretty.splitlines() if line.strip()]
|
|
29
|
+
return "\n".join(lines) + "\n"
|
|
30
|
+
except Exception:
|
|
31
|
+
return content.decode("utf-8", errors="replace")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def unpack(xlsx_path: str, output_dir: str) -> None:
|
|
35
|
+
if not os.path.isfile(xlsx_path):
|
|
36
|
+
print(f"ERROR: File not found: {xlsx_path}", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
if not xlsx_path.lower().endswith((".xlsx", ".xlsm")):
|
|
40
|
+
print(f"WARNING: '{xlsx_path}' does not have an .xlsx/.xlsm extension", file=sys.stderr)
|
|
41
|
+
|
|
42
|
+
if os.path.exists(output_dir):
|
|
43
|
+
shutil.rmtree(output_dir)
|
|
44
|
+
os.makedirs(output_dir)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with zipfile.ZipFile(xlsx_path, "r") as z:
|
|
48
|
+
# Validate member paths to prevent zip-slip (path traversal) attacks
|
|
49
|
+
for member in z.namelist():
|
|
50
|
+
member_path = os.path.realpath(os.path.join(output_dir, member))
|
|
51
|
+
if not member_path.startswith(os.path.realpath(output_dir) + os.sep) and member_path != os.path.realpath(output_dir):
|
|
52
|
+
print(f"ERROR: Zip entry '{member}' would escape target directory (path traversal blocked)", file=sys.stderr)
|
|
53
|
+
shutil.rmtree(output_dir, ignore_errors=True)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
z.extractall(output_dir)
|
|
56
|
+
except zipfile.BadZipFile:
|
|
57
|
+
shutil.rmtree(output_dir, ignore_errors=True)
|
|
58
|
+
print(f"ERROR: '{xlsx_path}' is not a valid ZIP/xlsx file", file=sys.stderr)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
# Pretty-print XML and .rels files
|
|
62
|
+
xml_count = 0
|
|
63
|
+
for dirpath, _, filenames in os.walk(output_dir):
|
|
64
|
+
for fname in filenames:
|
|
65
|
+
if fname.endswith(".xml") or fname.endswith(".rels"):
|
|
66
|
+
fpath = os.path.join(dirpath, fname)
|
|
67
|
+
with open(fpath, "rb") as f:
|
|
68
|
+
raw = f.read()
|
|
69
|
+
pretty = pretty_print_xml(raw)
|
|
70
|
+
with open(fpath, "w", encoding="utf-8") as f:
|
|
71
|
+
f.write(pretty)
|
|
72
|
+
xml_count += 1
|
|
73
|
+
|
|
74
|
+
print(f"Unpacked '{xlsx_path}' → '{output_dir}'")
|
|
75
|
+
print(f"Pretty-printed {xml_count} XML/rels files\n")
|
|
76
|
+
|
|
77
|
+
# Print key files grouped by category
|
|
78
|
+
categories = {
|
|
79
|
+
"Package root": ["[Content_Types].xml", "_rels/.rels"],
|
|
80
|
+
"Workbook": ["xl/workbook.xml", "xl/_rels/workbook.xml.rels"],
|
|
81
|
+
"Styles & Strings": ["xl/styles.xml", "xl/sharedStrings.xml"],
|
|
82
|
+
"Worksheets": [],
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
all_files = []
|
|
86
|
+
for dirpath, _, filenames in os.walk(output_dir):
|
|
87
|
+
for fname in filenames:
|
|
88
|
+
rel = os.path.relpath(os.path.join(dirpath, fname), output_dir)
|
|
89
|
+
all_files.append(rel)
|
|
90
|
+
|
|
91
|
+
# Collect worksheets
|
|
92
|
+
for rel in sorted(all_files):
|
|
93
|
+
if rel.startswith("xl/worksheets/") and rel.endswith(".xml"):
|
|
94
|
+
categories["Worksheets"].append(rel)
|
|
95
|
+
|
|
96
|
+
print("Key files to inspect/edit:")
|
|
97
|
+
for category, files in categories.items():
|
|
98
|
+
if not files:
|
|
99
|
+
continue
|
|
100
|
+
print(f"\n [{category}]")
|
|
101
|
+
for f in files:
|
|
102
|
+
full = os.path.join(output_dir, f)
|
|
103
|
+
if os.path.isfile(full):
|
|
104
|
+
size = os.path.getsize(full)
|
|
105
|
+
print(f" {f} ({size:,} bytes)")
|
|
106
|
+
else:
|
|
107
|
+
print(f" {f} (not found)")
|
|
108
|
+
|
|
109
|
+
# Warn about high-risk files present
|
|
110
|
+
risky = {
|
|
111
|
+
"xl/vbaProject.bin": "VBA macros — DO NOT modify",
|
|
112
|
+
"xl/pivotTables": "Pivot tables — update source ranges carefully if shifting rows",
|
|
113
|
+
"xl/charts": "Charts — update data ranges if shifting rows",
|
|
114
|
+
}
|
|
115
|
+
print("\n [High-risk content detected:]")
|
|
116
|
+
found_any = False
|
|
117
|
+
for path, warning in risky.items():
|
|
118
|
+
full = os.path.join(output_dir, path)
|
|
119
|
+
if os.path.exists(full):
|
|
120
|
+
print(f" ⚠️ {path} — {warning}")
|
|
121
|
+
found_any = True
|
|
122
|
+
if not found_any:
|
|
123
|
+
print(" ✓ None (safe to edit)")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
if len(sys.argv) != 3:
|
|
128
|
+
print("Usage: xlsx_unpack.py <input.xlsx> <output_dir>")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
unpack(sys.argv[1], sys.argv[2])
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
3
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
4
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
5
|
+
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
6
|
+
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
|
|
7
|
+
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
|
8
|
+
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
|
|
9
|
+
</Types>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
3
|
+
<Relationship Id="rId1"
|
|
4
|
+
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
|
5
|
+
Target="xl/workbook.xml"/>
|
|
6
|
+
</Relationships>
|
package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<!--
|
|
3
|
+
workbook.xml.rels — Maps each sheet r:id to its worksheet XML file.
|
|
4
|
+
|
|
5
|
+
When adding a new sheet:
|
|
6
|
+
- Add: <Relationship Id="rId2" Type="...worksheet" Target="worksheets/sheet2.xml"/>
|
|
7
|
+
- The Id must match the r:id in workbook.xml <sheet> element
|
|
8
|
+
-->
|
|
9
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
10
|
+
<Relationship Id="rId1"
|
|
11
|
+
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
|
|
12
|
+
Target="worksheets/sheet1.xml"/>
|
|
13
|
+
<Relationship Id="rId2"
|
|
14
|
+
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
|
|
15
|
+
Target="styles.xml"/>
|
|
16
|
+
<Relationship Id="rId3"
|
|
17
|
+
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
|
18
|
+
Target="sharedStrings.xml"/>
|
|
19
|
+
</Relationships>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<!--
|
|
3
|
+
sharedStrings.xml — The shared string table.
|
|
4
|
+
|
|
5
|
+
All text values in cells use this table. Instead of storing text directly
|
|
6
|
+
in the cell, each text string is stored here once, and cells reference it
|
|
7
|
+
by 0-based index:
|
|
8
|
+
|
|
9
|
+
<c r="A1" t="s"><v>0</v></c> → first string in this table
|
|
10
|
+
|
|
11
|
+
To add strings:
|
|
12
|
+
1. Append a new <si><t>Your Text</t></si> element
|
|
13
|
+
2. Increment both `count` and `uniqueCount` attributes
|
|
14
|
+
3. Use the new string's 0-based index in the cell's <v> element
|
|
15
|
+
|
|
16
|
+
Special characters in text:
|
|
17
|
+
- & → &
|
|
18
|
+
- < → <
|
|
19
|
+
- > → >
|
|
20
|
+
- Leading/trailing spaces → use xml:space="preserve": <t xml:space="preserve"> text </t>
|
|
21
|
+
|
|
22
|
+
count = total number of string references across the workbook
|
|
23
|
+
uniqueCount = number of unique strings in this table
|
|
24
|
+
(They may differ if some strings are used in multiple cells)
|
|
25
|
+
-->
|
|
26
|
+
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
27
|
+
count="0" uniqueCount="0">
|
|
28
|
+
<!-- Strings will be added here. Example:
|
|
29
|
+
<si><t>Revenue</t></si>
|
|
30
|
+
<si><t>Cost of Goods Sold</t></si>
|
|
31
|
+
<si><t>Gross Profit</t></si>
|
|
32
|
+
-->
|
|
33
|
+
</sst>
|