@zentto/report-core 0.1.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.
@@ -0,0 +1,32 @@
1
+ import type { ReportLayout, Band, DataSet, RenderOptions } from '../types.js';
2
+ import type { BindingContext } from './data-binding.js';
3
+ /** Intermediate representation of a rendered band positioned on a page */
4
+ export interface RenderedBand {
5
+ band: Band;
6
+ y: number;
7
+ context: BindingContext;
8
+ /** For detail bands: the current row data */
9
+ rowData?: Record<string, unknown>;
10
+ rowIndex?: number;
11
+ /** For group bands: the group key and rows */
12
+ groupKey?: string;
13
+ groupRows?: Record<string, unknown>[];
14
+ }
15
+ export interface PageContent {
16
+ pageNumber: number;
17
+ bands: RenderedBand[];
18
+ }
19
+ /** Convert mm to px (96 DPI) */
20
+ export declare function mmToPx(mm: number): number;
21
+ /** Convert inches to px (96 DPI) */
22
+ export declare function inToPx(inches: number): number;
23
+ /** Convert pt to px */
24
+ export declare function ptToPx(pt: number): number;
25
+ /** Convert any unit to px */
26
+ export declare function toPx(value: number, unit: string): number;
27
+ /**
28
+ * Process a report layout + datasets into paginated content.
29
+ * This is the core algorithm that handles band placement and page breaks.
30
+ */
31
+ export declare function processLayout(layout: ReportLayout, dataSets: DataSet, options?: RenderOptions): PageContent[];
32
+ //# sourceMappingURL=band-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"band-engine.d.ts","sourceRoot":"","sources":["../../src/engine/band-engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EACZ,IAAI,EACJ,OAAO,EAIP,aAAa,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,EAAE,cAAc,CAAC;IACxB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,gCAAgC;AAChC,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,oCAAoC;AACpC,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,uBAAuB;AACvB,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,6BAA6B;AAC7B,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxD;AAqED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,WAAW,EAAE,CA8Nf"}
@@ -0,0 +1,255 @@
1
+ // @zentto/report-core — Band layout engine
2
+ // Processes bands + data into paginated content
3
+ /** Convert mm to px (96 DPI) */
4
+ export function mmToPx(mm) {
5
+ return mm * 96 / 25.4;
6
+ }
7
+ /** Convert inches to px (96 DPI) */
8
+ export function inToPx(inches) {
9
+ return inches * 96;
10
+ }
11
+ /** Convert pt to px */
12
+ export function ptToPx(pt) {
13
+ return pt * 96 / 72;
14
+ }
15
+ /** Convert any unit to px */
16
+ export function toPx(value, unit) {
17
+ switch (unit) {
18
+ case 'mm': return mmToPx(value);
19
+ case 'in': return inToPx(value);
20
+ case 'pt': return ptToPx(value);
21
+ case 'px': return value;
22
+ default: return value;
23
+ }
24
+ }
25
+ /** Get usable page height (total - margins) */
26
+ function getUsableHeight(layout) {
27
+ const unit = layout.pageSize.unit;
28
+ const total = toPx(layout.pageSize.height, unit);
29
+ const marginTop = toPx(layout.margins.top, unit);
30
+ const marginBottom = toPx(layout.margins.bottom, unit);
31
+ return total - marginTop - marginBottom;
32
+ }
33
+ /** Get usable page width */
34
+ function getUsableWidth(layout) {
35
+ const unit = layout.pageSize.unit;
36
+ const total = layout.orientation === 'landscape'
37
+ ? toPx(layout.pageSize.height, unit)
38
+ : toPx(layout.pageSize.width, unit);
39
+ const marginLeft = toPx(layout.margins.left, unit);
40
+ const marginRight = toPx(layout.margins.right, unit);
41
+ return total - marginLeft - marginRight;
42
+ }
43
+ /** Get bands by type */
44
+ function getBandsByType(layout, type) {
45
+ return layout.bands.filter(b => b.type === type && b.visible !== false);
46
+ }
47
+ /** Sort and group array data */
48
+ function prepareData(data, layout) {
49
+ let result = [...data];
50
+ // Apply sorting
51
+ if (layout.sorting && layout.sorting.length > 0) {
52
+ result.sort((a, b) => {
53
+ for (const sort of layout.sorting) {
54
+ const aVal = a[sort.field];
55
+ const bVal = b[sort.field];
56
+ if (aVal === bVal)
57
+ continue;
58
+ const cmp = aVal < bVal ? -1 : 1;
59
+ return sort.direction === 'desc' ? -cmp : cmp;
60
+ }
61
+ return 0;
62
+ });
63
+ }
64
+ return result;
65
+ }
66
+ /** Group rows by a field */
67
+ function groupByField(rows, field) {
68
+ const groups = new Map();
69
+ for (const row of rows) {
70
+ const key = String(row[field] ?? '');
71
+ const group = groups.get(key);
72
+ if (group) {
73
+ group.push(row);
74
+ }
75
+ else {
76
+ groups.set(key, [row]);
77
+ }
78
+ }
79
+ return groups;
80
+ }
81
+ /**
82
+ * Process a report layout + datasets into paginated content.
83
+ * This is the core algorithm that handles band placement and page breaks.
84
+ */
85
+ export function processLayout(layout, dataSets, options) {
86
+ const usableHeight = getUsableHeight(layout);
87
+ const unit = layout.pageSize.unit;
88
+ const reportHeaders = getBandsByType(layout, 'reportHeader');
89
+ const pageHeaders = getBandsByType(layout, 'pageHeader');
90
+ const columnHeaders = getBandsByType(layout, 'columnHeader');
91
+ const detailBands = getBandsByType(layout, 'detail');
92
+ const pageFooters = getBandsByType(layout, 'pageFooter');
93
+ const reportFooters = getBandsByType(layout, 'reportFooter');
94
+ const groupHeaders = getBandsByType(layout, 'groupHeader');
95
+ const groupFooters = getBandsByType(layout, 'groupFooter');
96
+ // Calculate fixed heights
97
+ const pageHeaderHeight = pageHeaders.reduce((s, b) => s + toPx(b.height, unit), 0);
98
+ const pageFooterHeight = pageFooters.reduce((s, b) => s + toPx(b.height, unit), 0);
99
+ const columnHeaderHeight = columnHeaders.reduce((s, b) => s + toPx(b.height, unit), 0);
100
+ const pages = [];
101
+ let currentPageBands = [];
102
+ let currentY = 0;
103
+ let pageNumber = 1;
104
+ const baseCtx = {
105
+ dataSets,
106
+ parameters: options?.parameters,
107
+ };
108
+ function availableHeight() {
109
+ return usableHeight - currentY - pageFooterHeight;
110
+ }
111
+ function startNewPage() {
112
+ // Add page footer to current page
113
+ if (currentPageBands.length > 0) {
114
+ for (const band of pageFooters) {
115
+ currentPageBands.push({
116
+ band,
117
+ y: usableHeight - pageFooterHeight,
118
+ context: { ...baseCtx, pageNumber },
119
+ });
120
+ }
121
+ pages.push({ pageNumber, bands: currentPageBands });
122
+ }
123
+ pageNumber++;
124
+ currentPageBands = [];
125
+ currentY = 0;
126
+ // Add page header
127
+ for (const band of pageHeaders) {
128
+ currentPageBands.push({
129
+ band,
130
+ y: currentY,
131
+ context: { ...baseCtx, pageNumber },
132
+ });
133
+ currentY += toPx(band.height, unit);
134
+ }
135
+ // Add column headers if they repeat
136
+ for (const band of columnHeaders) {
137
+ if (band.repeatOnEveryPage !== false) {
138
+ currentPageBands.push({
139
+ band,
140
+ y: currentY,
141
+ context: { ...baseCtx, pageNumber },
142
+ });
143
+ currentY += toPx(band.height, unit);
144
+ }
145
+ }
146
+ }
147
+ function addBand(band, ctx, rowData, rowIndex, groupKey, groupRows) {
148
+ const bandHeight = toPx(band.height, unit);
149
+ if (bandHeight > availableHeight()) {
150
+ startNewPage();
151
+ }
152
+ currentPageBands.push({
153
+ band,
154
+ y: currentY,
155
+ context: ctx,
156
+ rowData,
157
+ rowIndex,
158
+ groupKey,
159
+ groupRows,
160
+ });
161
+ currentY += bandHeight;
162
+ }
163
+ // ─── Start rendering ───────────────────────────────────────────
164
+ // Page header (first page)
165
+ for (const band of pageHeaders) {
166
+ currentPageBands.push({
167
+ band,
168
+ y: currentY,
169
+ context: { ...baseCtx, pageNumber },
170
+ });
171
+ currentY += toPx(band.height, unit);
172
+ }
173
+ // Report header (first page only)
174
+ for (const band of reportHeaders) {
175
+ addBand(band, { ...baseCtx, pageNumber });
176
+ }
177
+ // Column headers
178
+ for (const band of columnHeaders) {
179
+ addBand(band, { ...baseCtx, pageNumber });
180
+ }
181
+ // Detail bands — iterate over data
182
+ for (const detailBand of detailBands) {
183
+ const dsId = detailBand.dataSource;
184
+ if (!dsId)
185
+ continue;
186
+ const rawData = dataSets[dsId];
187
+ if (!Array.isArray(rawData))
188
+ continue;
189
+ const rows = prepareData(rawData, layout);
190
+ // Check if we have groups
191
+ if (layout.groups && layout.groups.length > 0) {
192
+ for (const groupDef of layout.groups) {
193
+ const grouped = groupByField(rows, groupDef.field);
194
+ for (const [key, groupRows] of grouped) {
195
+ // Group header
196
+ const ghBand = layout.bands.find(b => b.id === groupDef.headerBandId);
197
+ if (ghBand && ghBand.visible !== false) {
198
+ if (groupDef.pageBreakBefore && pages.length > 0) {
199
+ startNewPage();
200
+ }
201
+ addBand(ghBand, { ...baseCtx, pageNumber, groupRows, allRows: rows }, groupRows[0], undefined, key, groupRows);
202
+ }
203
+ // Detail rows within group
204
+ for (let i = 0; i < groupRows.length; i++) {
205
+ addBand(detailBand, {
206
+ ...baseCtx,
207
+ pageNumber,
208
+ currentRow: groupRows[i],
209
+ currentIndex: i,
210
+ groupRows,
211
+ allRows: rows,
212
+ }, groupRows[i], i);
213
+ }
214
+ // Group footer
215
+ if (groupDef.footerBandId) {
216
+ const gfBand = layout.bands.find(b => b.id === groupDef.footerBandId);
217
+ if (gfBand && gfBand.visible !== false) {
218
+ addBand(gfBand, { ...baseCtx, pageNumber, groupRows, allRows: rows }, undefined, undefined, key, groupRows);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ else {
225
+ // No groups — simple detail iteration
226
+ for (let i = 0; i < rows.length; i++) {
227
+ addBand(detailBand, {
228
+ ...baseCtx,
229
+ pageNumber,
230
+ currentRow: rows[i],
231
+ currentIndex: i,
232
+ allRows: rows,
233
+ }, rows[i], i);
234
+ }
235
+ }
236
+ }
237
+ // Report footer
238
+ for (const band of reportFooters) {
239
+ const allRows = Object.values(dataSets).find(Array.isArray);
240
+ addBand(band, { ...baseCtx, pageNumber, allRows: allRows || [] });
241
+ }
242
+ // Close last page
243
+ for (const band of pageFooters) {
244
+ currentPageBands.push({
245
+ band,
246
+ y: usableHeight - pageFooterHeight,
247
+ context: { ...baseCtx, pageNumber },
248
+ });
249
+ }
250
+ if (currentPageBands.length > 0) {
251
+ pages.push({ pageNumber, bands: currentPageBands });
252
+ }
253
+ return pages;
254
+ }
255
+ //# sourceMappingURL=band-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"band-engine.js","sourceRoot":"","sources":["../../src/engine/band-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { DataSet, ReportElement, AggregateFunction } from '../types.js';
2
+ /** Context passed during rendering — current row, aggregates, page info */
3
+ export interface BindingContext {
4
+ dataSets: DataSet;
5
+ currentRow?: Record<string, unknown>;
6
+ currentIndex?: number;
7
+ groupRows?: Record<string, unknown>[];
8
+ allRows?: Record<string, unknown>[];
9
+ pageNumber?: number;
10
+ totalPages?: number;
11
+ parameters?: Record<string, unknown>;
12
+ aggregates?: Map<string, number>;
13
+ }
14
+ /** Resolve a field value from the binding context */
15
+ export declare function resolveField(dataSourceId: string, fieldName: string, ctx: BindingContext): unknown;
16
+ /** Resolve template expressions like {{ds1.clientName}} in strings */
17
+ export declare function resolveTemplate(template: string, ctx: BindingContext): string;
18
+ /** Compute an aggregate over a set of rows */
19
+ export declare function computeAggregate(rows: Record<string, unknown>[], field: string, fn: AggregateFunction): number;
20
+ /** Format a value for display */
21
+ export declare function formatValue(value: unknown, format?: string): string;
22
+ /** Resolve the display value for any element */
23
+ export declare function resolveElementValue(element: ReportElement, ctx: BindingContext): string;
24
+ /** Evaluate simple arithmetic expressions like =qty*price */
25
+ export declare function evaluateSimpleExpression(expr: string, ctx: BindingContext): unknown;
26
+ //# sourceMappingURL=data-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-binding.d.ts","sourceRoot":"","sources":["../../src/engine/data-binding.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EAKb,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,qDAAqD;AACrD,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAoBT;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,MAAM,CAK7E;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,iBAAiB,GACpB,MAAM,CAqBR;AAED,iCAAiC;AACjC,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CA4CnE;AAoBD,gDAAgD;AAChD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,cAAc,GAClB,MAAM,CAoDR;AAED,6DAA6D;AAC7D,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,cAAc,GAClB,OAAO,CAqBT"}
@@ -0,0 +1,184 @@
1
+ // @zentto/report-core — Data binding engine
2
+ // Resolves field references and expressions against datasets
3
+ /** Resolve a field value from the binding context */
4
+ export function resolveField(dataSourceId, fieldName, ctx) {
5
+ // If we have a current row (inside detail/group iteration), use it
6
+ if (ctx.currentRow && fieldName in ctx.currentRow) {
7
+ return ctx.currentRow[fieldName];
8
+ }
9
+ const ds = ctx.dataSets[dataSourceId];
10
+ if (!ds)
11
+ return undefined;
12
+ // Object data source — direct field access
13
+ if (!Array.isArray(ds)) {
14
+ return ds[fieldName];
15
+ }
16
+ // Array data source with current index
17
+ if (ctx.currentIndex !== undefined && ds[ctx.currentIndex]) {
18
+ return ds[ctx.currentIndex][fieldName];
19
+ }
20
+ return undefined;
21
+ }
22
+ /** Resolve template expressions like {{ds1.clientName}} in strings */
23
+ export function resolveTemplate(template, ctx) {
24
+ return template.replace(/\{\{(\w+)\.(\w+)\}\}/g, (_match, dsId, field) => {
25
+ const value = resolveField(dsId, field, ctx);
26
+ return value != null ? String(value) : '';
27
+ });
28
+ }
29
+ /** Compute an aggregate over a set of rows */
30
+ export function computeAggregate(rows, field, fn) {
31
+ const values = rows
32
+ .map(r => r[field])
33
+ .filter((v) => typeof v === 'number');
34
+ if (values.length === 0)
35
+ return 0;
36
+ switch (fn) {
37
+ case 'sum':
38
+ return values.reduce((a, b) => a + b, 0);
39
+ case 'avg':
40
+ return values.reduce((a, b) => a + b, 0) / values.length;
41
+ case 'count':
42
+ return rows.length;
43
+ case 'min':
44
+ return Math.min(...values);
45
+ case 'max':
46
+ return Math.max(...values);
47
+ default:
48
+ return 0;
49
+ }
50
+ }
51
+ /** Format a value for display */
52
+ export function formatValue(value, format) {
53
+ if (value == null)
54
+ return '';
55
+ if (!format)
56
+ return String(value);
57
+ // Number formats
58
+ if (typeof value === 'number') {
59
+ if (format.startsWith('$')) {
60
+ const decimals = (format.match(/\.(\d+)/)?.[1] || '2').length;
61
+ const prefix = format.charAt(0);
62
+ return prefix + value.toLocaleString(undefined, {
63
+ minimumFractionDigits: decimals,
64
+ maximumFractionDigits: decimals,
65
+ });
66
+ }
67
+ if (format.includes('#') || format.includes('0')) {
68
+ const decimals = (format.split('.')[1] || '').length;
69
+ return value.toLocaleString(undefined, {
70
+ minimumFractionDigits: decimals,
71
+ maximumFractionDigits: decimals,
72
+ });
73
+ }
74
+ if (format === 'percent') {
75
+ return (value * 100).toFixed(1) + '%';
76
+ }
77
+ }
78
+ // Date formats
79
+ if (value instanceof Date || (typeof value === 'string' && !isNaN(Date.parse(value)))) {
80
+ const date = value instanceof Date ? value : new Date(value);
81
+ return formatDate(date, format);
82
+ }
83
+ // Boolean
84
+ if (typeof value === 'boolean') {
85
+ if (format === 'yesno')
86
+ return value ? 'Yes' : 'No';
87
+ if (format === 'sino')
88
+ return value ? 'Si' : 'No';
89
+ return String(value);
90
+ }
91
+ // Uppercase / lowercase
92
+ if (format === 'uppercase')
93
+ return String(value).toUpperCase();
94
+ if (format === 'lowercase')
95
+ return String(value).toLowerCase();
96
+ return String(value);
97
+ }
98
+ function formatDate(date, format) {
99
+ const pad = (n) => String(n).padStart(2, '0');
100
+ const yyyy = date.getFullYear();
101
+ const MM = pad(date.getMonth() + 1);
102
+ const dd = pad(date.getDate());
103
+ const HH = pad(date.getHours());
104
+ const mm = pad(date.getMinutes());
105
+ const ss = pad(date.getSeconds());
106
+ return format
107
+ .replace('yyyy', String(yyyy))
108
+ .replace('MM', MM)
109
+ .replace('dd', dd)
110
+ .replace('HH', HH)
111
+ .replace('mm', mm)
112
+ .replace('ss', ss);
113
+ }
114
+ /** Resolve the display value for any element */
115
+ export function resolveElementValue(element, ctx) {
116
+ switch (element.type) {
117
+ case 'text':
118
+ return resolveTemplate(element.content, ctx);
119
+ case 'field': {
120
+ const fe = element;
121
+ // Aggregate field (used in footers)
122
+ if (fe.aggregate) {
123
+ const rows = ctx.groupRows || ctx.allRows || [];
124
+ const val = computeAggregate(rows, fe.field, fe.aggregate);
125
+ return formatValue(val, fe.format);
126
+ }
127
+ // Expression field
128
+ if (fe.expression) {
129
+ const val = evaluateSimpleExpression(fe.expression, ctx);
130
+ return formatValue(val, fe.format);
131
+ }
132
+ // Regular field
133
+ const val = resolveField(fe.dataSource, fe.field, ctx);
134
+ return formatValue(val, fe.format);
135
+ }
136
+ case 'image': {
137
+ const ie = element;
138
+ return resolveTemplate(ie.src, ctx);
139
+ }
140
+ case 'barcode': {
141
+ const be = element;
142
+ return resolveTemplate(be.value, ctx);
143
+ }
144
+ case 'pageNumber':
145
+ if (element.format) {
146
+ return element.format
147
+ .replace('{page}', String(ctx.pageNumber || 1))
148
+ .replace('{pages}', String(ctx.totalPages || 1));
149
+ }
150
+ return String(ctx.pageNumber || 1);
151
+ case 'totalPages':
152
+ return String(ctx.totalPages || 1);
153
+ case 'currentDate': {
154
+ const now = new Date();
155
+ return element.format ? formatDate(now, element.format) : now.toLocaleDateString();
156
+ }
157
+ default:
158
+ return '';
159
+ }
160
+ }
161
+ /** Evaluate simple arithmetic expressions like =qty*price */
162
+ export function evaluateSimpleExpression(expr, ctx) {
163
+ let expression = expr.startsWith('=') ? expr.slice(1) : expr;
164
+ // Replace field references with actual values
165
+ expression = expression.replace(/(\w+)/g, (match) => {
166
+ if (ctx.currentRow && match in ctx.currentRow) {
167
+ const val = ctx.currentRow[match];
168
+ return typeof val === 'number' ? String(val) : `"${val}"`;
169
+ }
170
+ // Check if it's a number literal
171
+ if (!isNaN(Number(match)))
172
+ return match;
173
+ return `"${match}"`;
174
+ });
175
+ try {
176
+ // Safe evaluation using Function constructor (no access to global scope)
177
+ const fn = new Function(`"use strict"; return (${expression});`);
178
+ return fn();
179
+ }
180
+ catch {
181
+ return expr;
182
+ }
183
+ }
184
+ //# sourceMappingURL=data-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-binding.js","sourceRoot":"","sources":["../../src/engine/data-binding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { BindingContext } from './data-binding.js';
2
+ /**
3
+ * Evaluate an expression string with context.
4
+ *
5
+ * Supports:
6
+ * - Field references: {fieldName} or {dataSource.field}
7
+ * - Function calls: =SUM({qty}), =IF({status}=="paid","PAID","PENDING")
8
+ * - Arithmetic: =qty*price
9
+ * - Aggregate over group: =SUM_GROUP(detail, total)
10
+ */
11
+ export declare function evaluateExpression(expr: string, ctx: BindingContext): unknown;
12
+ //# sourceMappingURL=expression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../../src/engine/expression.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6DxD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,cAAc,GAClB,OAAO,CAoDT"}
@@ -0,0 +1,112 @@
1
+ // @zentto/report-core — Expression engine
2
+ // Evaluates formulas and expressions in report fields
3
+ import { computeAggregate } from './data-binding.js';
4
+ /** Built-in functions available in expressions */
5
+ const FUNCTIONS = {
6
+ // Aggregates
7
+ SUM: (...args) => args.reduce((a, b) => a + b, 0),
8
+ AVG: (...args) => {
9
+ const nums = args;
10
+ return nums.reduce((a, b) => a + b, 0) / nums.length;
11
+ },
12
+ COUNT: (...args) => args.length,
13
+ MIN: (...args) => Math.min(...args),
14
+ MAX: (...args) => Math.max(...args),
15
+ // Math
16
+ ROUND: (val, decimals) => Math.round(val * 10 ** decimals) / 10 ** decimals,
17
+ ABS: (val) => Math.abs(val),
18
+ CEIL: (val) => Math.ceil(val),
19
+ FLOOR: (val) => Math.floor(val),
20
+ // String
21
+ UPPER: (val) => String(val).toUpperCase(),
22
+ LOWER: (val) => String(val).toLowerCase(),
23
+ TRIM: (val) => String(val).trim(),
24
+ LEFT: (val, n) => String(val).slice(0, n),
25
+ RIGHT: (val, n) => String(val).slice(-n),
26
+ LEN: (val) => String(val).length,
27
+ CONCAT: (...args) => args.map(String).join(''),
28
+ // Logic
29
+ IF: (cond, trueVal, falseVal) => cond ? trueVal : falseVal,
30
+ IIF: (cond, trueVal, falseVal) => cond ? trueVal : falseVal,
31
+ // Date
32
+ NOW: () => new Date().toISOString(),
33
+ TODAY: () => new Date().toISOString().split('T')[0],
34
+ YEAR: (val) => new Date(val).getFullYear(),
35
+ MONTH: (val) => new Date(val).getMonth() + 1,
36
+ DAY: (val) => new Date(val).getDate(),
37
+ // Format
38
+ FORMAT: (val, fmt) => {
39
+ if (typeof val === 'number' && typeof fmt === 'string') {
40
+ if (fmt.startsWith('$')) {
41
+ return '$' + val.toFixed(2);
42
+ }
43
+ const decimals = (fmt.split('.')[1] || '').length;
44
+ return val.toFixed(decimals);
45
+ }
46
+ return String(val);
47
+ },
48
+ // Null handling
49
+ ISNULL: (val, fallback) => val ?? fallback,
50
+ COALESCE: (...args) => args.find(a => a != null) ?? null,
51
+ };
52
+ /**
53
+ * Evaluate an expression string with context.
54
+ *
55
+ * Supports:
56
+ * - Field references: {fieldName} or {dataSource.field}
57
+ * - Function calls: =SUM({qty}), =IF({status}=="paid","PAID","PENDING")
58
+ * - Arithmetic: =qty*price
59
+ * - Aggregate over group: =SUM_GROUP(detail, total)
60
+ */
61
+ export function evaluateExpression(expr, ctx) {
62
+ if (!expr)
63
+ return '';
64
+ // Non-expression strings are returned as-is
65
+ if (!expr.startsWith('='))
66
+ return expr;
67
+ const formula = expr.slice(1).trim();
68
+ // Check for aggregate function over dataset: SUM_GROUP(dsId, field)
69
+ const aggGroupMatch = formula.match(/^(SUM|AVG|COUNT|MIN|MAX)_GROUP\((\w+),\s*(\w+)\)$/);
70
+ if (aggGroupMatch) {
71
+ const [, fn, _dsId, field] = aggGroupMatch;
72
+ const rows = ctx.groupRows || ctx.allRows || [];
73
+ return computeAggregate(rows, field, fn.toLowerCase());
74
+ }
75
+ // Check for aggregate function over all: SUM_ALL(field)
76
+ const aggAllMatch = formula.match(/^(SUM|AVG|COUNT|MIN|MAX)_ALL\((\w+)\)$/);
77
+ if (aggAllMatch) {
78
+ const [, fn, field] = aggAllMatch;
79
+ const rows = ctx.allRows || [];
80
+ return computeAggregate(rows, field, fn.toLowerCase());
81
+ }
82
+ // Replace {fieldName} with actual values
83
+ let resolved = formula.replace(/\{(\w+)(?:\.(\w+))?\}/g, (_m, ref1, ref2) => {
84
+ if (ref2) {
85
+ // {dataSource.field}
86
+ const ds = ctx.dataSets[ref1];
87
+ if (ds && !Array.isArray(ds))
88
+ return JSON.stringify(ds[ref2] ?? null);
89
+ if (ctx.currentRow && ref1 in ctx.currentRow)
90
+ return JSON.stringify(ctx.currentRow[ref1]);
91
+ }
92
+ // {field} — look in current row
93
+ if (ctx.currentRow && ref1 in ctx.currentRow) {
94
+ const val = ctx.currentRow[ref1];
95
+ return typeof val === 'string' ? JSON.stringify(val) : String(val ?? 'null');
96
+ }
97
+ return 'null';
98
+ });
99
+ // Replace function calls with JS equivalents
100
+ for (const [name, fn] of Object.entries(FUNCTIONS)) {
101
+ const regex = new RegExp(`\\b${name}\\(`, 'g');
102
+ resolved = resolved.replace(regex, `__fn.${name}(`);
103
+ }
104
+ try {
105
+ const fn = new Function('__fn', `"use strict"; return (${resolved});`);
106
+ return fn(FUNCTIONS);
107
+ }
108
+ catch {
109
+ return `#ERR: ${expr}`;
110
+ }
111
+ }
112
+ //# sourceMappingURL=expression.js.map