Skip to content

Commit e487c94

Browse files
authored
Upload bim_slimmer.csx
1 parent d178714 commit e487c94

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed

Intermediate/bim_slimmer.csx

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
//
2+
// Title: BIM Slimmer - strip metadata bloat to reduce file size
3+
//
4+
// Author: Alexis Olson using GPT-5 Thinking and Claude Opus 4.1
5+
//
6+
// Description:
7+
// Opens a Tabular model .bim file, removes UI/engine bloat while preserving model semantics,
8+
// and saves a sibling .slim JSON. Preserves structural containers (model, tables, columns,
9+
// measures, partitions, relationships, etc.), removes empty/unused values, and supports
10+
// optional switches for display/query group metadata. Shows a summary with items removed and
11+
// size savings.
12+
//
13+
// How to use:
14+
// - Use in Tabular Editor (2 or 3) Advanced Scripting.
15+
// - (Optional) Customize the configuration options at the top of the script.
16+
// - When prompted, select a <ModelName>.bim file.
17+
// - The script writes <ModelName>.slim and displays a summary.
18+
19+
using System;
20+
using System.Collections.Generic;
21+
using System.IO;
22+
using System.Linq;
23+
using System.Windows.Forms;
24+
using Newtonsoft.Json;
25+
using Newtonsoft.Json.Linq;
26+
27+
// ==================== CONFIGURATION ====================
28+
// Use defaults specified or customize as needed
29+
30+
// Core metadata removal (recommended: ON)
31+
bool REMOVE_Annotations = true; // annotations, changedProperties, extendedProperties
32+
bool REMOVE_Lineage = true; // lineageTag, sourceLineageTag
33+
bool REMOVE_LanguageData = true; // cultures, translations, synonyms, linguisticMetadata
34+
35+
// Value-based cleanup (recommended: ON)
36+
bool REMOVE_DefaultValues = true; // dataCategory:Uncategorized, summarizeBy:none
37+
bool REMOVE_RedundantNames = true; // sourceColumn==name, displayName==name
38+
bool REMOVE_EmptyContainers = true; // empty {} and [] (preserves structural containers)
39+
40+
// Presentation properties (optional)
41+
bool REMOVE_SummarizeBy = true; // summarizeBy (all values, not just none)
42+
bool REMOVE_DisplayProps = true; // isHidden, displayFolder
43+
bool REMOVE_QueryGroups = false; // queryGroup, queryGroups, folder
44+
bool REMOVE_FormatString = true; // formatString literal only (NEVER formatStringDefinition)
45+
46+
// Additional metadata (recommended: ON)
47+
bool REMOVE_ExtraMetadata = true; // sourceProviderType, isNameInferred, isDataTypeInferred
48+
49+
// ==================== OUTPUT FORMAT =====================
50+
// Human-friendly indented JSON (false) or compacted (true)
51+
bool MINIFY_OUTPUT = true;
52+
53+
// ==================== MAIN EXECUTION ====================
54+
try
55+
{
56+
// Select file
57+
string inputPath;
58+
using (var dialog = new OpenFileDialog {
59+
Title = "Select BIM file to slim",
60+
Filter = "Tabular Model (*.bim)|*.bim|All files (*.*)|*.*",
61+
RestoreDirectory = true
62+
}) {
63+
if (dialog.ShowDialog() != DialogResult.OK) return;
64+
inputPath = dialog.FileName;
65+
}
66+
67+
// Generate output path
68+
var outputPath = Path.ChangeExtension(inputPath, ".slim");
69+
var originalSize = new FileInfo(inputPath).Length;
70+
71+
// Parse JSON
72+
var root = JToken.Parse(File.ReadAllText(inputPath));
73+
74+
// Build removal rules
75+
var dropKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
76+
if (REMOVE_Annotations) {
77+
dropKeys.UnionWith(new[] { "annotations", "changedProperties", "extendedProperties" });
78+
}
79+
if (REMOVE_Lineage) {
80+
dropKeys.UnionWith(new[] { "lineageTag", "sourceLineageTag" });
81+
}
82+
if (REMOVE_LanguageData) {
83+
dropKeys.UnionWith(new[] { "cultures", "translations", "synonyms", "linguisticMetadata" });
84+
}
85+
if (REMOVE_SummarizeBy) {
86+
dropKeys.Add("summarizeBy");
87+
}
88+
if (REMOVE_DisplayProps) {
89+
dropKeys.UnionWith(new[] { "isHidden", "displayFolder" });
90+
}
91+
if (REMOVE_QueryGroups) {
92+
dropKeys.UnionWith(new[] { "queryGroup", "queryGroups", "folder" });
93+
}
94+
if (REMOVE_ExtraMetadata) {
95+
dropKeys.UnionWith(new[] { "sourceProviderType", "isNameInferred", "isDataTypeInferred" });
96+
}
97+
98+
// Structural containers - never remove even if empty (preserves model schema)
99+
var preserve = new HashSet<string>(new[] {
100+
"model", "tables", "columns", "measures", "relationships", "partitions",
101+
"roles", "hierarchies", "levels", "dataSources", "perspectives", "expressions"
102+
}, StringComparer.OrdinalIgnoreCase);
103+
104+
// Track removals
105+
var stats = new Dictionary<string, int>();
106+
Action<string> Track = delegate(string key) {
107+
stats[key] = stats.ContainsKey(key) ? stats[key] + 1 : 1;
108+
};
109+
110+
// Helpers
111+
Func<string, string, bool> Eq = delegate(string a, string b) {
112+
return string.Equals(
113+
a != null ? a.Trim() : null,
114+
b != null ? b.Trim() : null,
115+
StringComparison.OrdinalIgnoreCase
116+
);
117+
};
118+
119+
Func<JToken, bool> IsEmpty = delegate(JToken t) {
120+
return t == null || t.Type == JTokenType.Null ||
121+
(t is JContainer && !((JContainer)t).HasValues) ||
122+
(t.Type == JTokenType.String && string.IsNullOrWhiteSpace((string)t));
123+
};
124+
125+
// Recursive cleaner
126+
Action<JToken> Clean = null;
127+
Clean = delegate(JToken token) {
128+
if (token == null) return;
129+
130+
if (token.Type == JTokenType.Object) {
131+
var obj = (JObject)token;
132+
133+
// Recurse first (depth-first)
134+
foreach (var prop in obj.Properties().ToList()) Clean(prop.Value);
135+
136+
var toRemove = new List<JProperty>();
137+
138+
foreach (var prop in obj.Properties()) {
139+
// Name-based removals
140+
if (dropKeys.Contains(prop.Name)) {
141+
toRemove.Add(prop);
142+
Track(prop.Name);
143+
continue;
144+
}
145+
146+
// formatString special handling (protect formatStringDefinition)
147+
if (REMOVE_FormatString && Eq(prop.Name, "formatString")) {
148+
toRemove.Add(prop);
149+
Track("formatString");
150+
continue;
151+
}
152+
153+
// Empty container removal (with structural preservation)
154+
if (REMOVE_EmptyContainers && IsEmpty(prop.Value) && !preserve.Contains(prop.Name)) {
155+
toRemove.Add(prop);
156+
Track("empty");
157+
continue;
158+
}
159+
}
160+
161+
// Value-based removals (checked after structure scan)
162+
if (REMOVE_DefaultValues) {
163+
var dc = obj.Property("dataCategory", StringComparison.OrdinalIgnoreCase);
164+
if (dc != null && dc.Value is JValue && Eq((string)dc.Value, "Uncategorized")) {
165+
toRemove.Add(dc);
166+
Track("dataCategory=default");
167+
}
168+
169+
var sb = obj.Property("summarizeBy", StringComparison.OrdinalIgnoreCase);
170+
if (sb != null && sb.Value is JValue && Eq((string)sb.Value, "none")) {
171+
toRemove.Add(sb);
172+
Track("summarizeBy=none");
173+
}
174+
}
175+
176+
if (REMOVE_RedundantNames) {
177+
var name = obj.Property("name", StringComparison.OrdinalIgnoreCase);
178+
if (name != null && name.Value is JValue) {
179+
var nameStr = (string)name.Value;
180+
var nameBracketed = nameStr != null ? string.Format("[{0}]", nameStr) : null;
181+
182+
var src = obj.Property("sourceColumn", StringComparison.OrdinalIgnoreCase);
183+
if (
184+
src != null &&
185+
src.Value is JValue &&
186+
(
187+
Eq((string)src.Value, nameStr) ||
188+
Eq((string)src.Value, nameBracketed)
189+
)
190+
) {
191+
toRemove.Add(src);
192+
Track("sourceColumn=name");
193+
}
194+
195+
var disp = obj.Property("displayName", StringComparison.OrdinalIgnoreCase);
196+
if (
197+
disp != null &&
198+
disp.Value is JValue &&
199+
(
200+
Eq((string)disp.Value, nameStr) ||
201+
Eq((string)disp.Value, nameBracketed)
202+
)
203+
) {
204+
toRemove.Add(disp);
205+
Track("displayName=name");
206+
}
207+
}
208+
}
209+
210+
// Apply all removals
211+
foreach (var prop in toRemove.Distinct()) prop.Remove();
212+
}
213+
else if (token.Type == JTokenType.Array) {
214+
var arr = (JArray)token;
215+
foreach (var item in arr.ToList()) {
216+
Clean(item);
217+
if (REMOVE_EmptyContainers && IsEmpty(item)) {
218+
item.Remove();
219+
Track("empty");
220+
}
221+
}
222+
}
223+
};
224+
225+
// Execute cleaning
226+
Clean(root);
227+
228+
// Save result
229+
var formatting = MINIFY_OUTPUT ? Formatting.None : Formatting.Indented;
230+
File.WriteAllText(outputPath, root.ToString(formatting));
231+
232+
// Report results
233+
var newSize = new FileInfo(outputPath).Length;
234+
var reduction = (1.0 - (double)newSize / originalSize) * 100;
235+
var summary =
236+
"BIM Slimmer Results\n" +
237+
"==================\n" +
238+
string.Format("Input: {0} ({1:N1} KB)\n", Path.GetFileName(inputPath), originalSize / 1024.0) +
239+
string.Format("Output: {0} ({1:N1} KB)\n", Path.GetFileName(outputPath), newSize / 1024.0) +
240+
string.Format("Saved: {0:F1}%\n\n", reduction) +
241+
string.Format("Removed: {0:N0} items\n", stats.Values.Sum()) +
242+
string.Join(
243+
"\n",
244+
stats.OrderBy(k => k.Key)
245+
.Select(k => string.Format(" • {0}: {1:N0}", k.Key, k.Value))
246+
.ToArray()
247+
);
248+
249+
Info(summary);
250+
}
251+
catch (Exception ex)
252+
{
253+
Error(string.Format("Processing failed: {0}", ex.Message));
254+
}

0 commit comments

Comments
 (0)