@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.
- package/dist/engine/band-engine.d.ts +32 -0
- package/dist/engine/band-engine.d.ts.map +1 -0
- package/dist/engine/band-engine.js +255 -0
- package/dist/engine/band-engine.js.map +1 -0
- package/dist/engine/data-binding.d.ts +26 -0
- package/dist/engine/data-binding.d.ts.map +1 -0
- package/dist/engine/data-binding.js +184 -0
- package/dist/engine/data-binding.js.map +1 -0
- package/dist/engine/expression.d.ts +12 -0
- package/dist/engine/expression.d.ts.map +1 -0
- package/dist/engine/expression.js +112 -0
- package/dist/engine/expression.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/render/html-renderer.d.ts +6 -0
- package/dist/render/html-renderer.d.ts.map +1 -0
- package/dist/render/html-renderer.js +228 -0
- package/dist/render/html-renderer.js.map +1 -0
- package/dist/schema/report-schema.d.ts +4154 -0
- package/dist/schema/report-schema.d.ts.map +1 -0
- package/dist/schema/report-schema.js +200 -0
- package/dist/schema/report-schema.js.map +1 -0
- package/dist/serialization/json.d.ts +815 -0
- package/dist/serialization/json.d.ts.map +1 -0
- package/dist/serialization/json.js +99 -0
- package/dist/serialization/json.js.map +1 -0
- package/dist/types.d.ts +208 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
|
@@ -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":"AAAA,2CAA2C;AAC3C,gDAAgD;AA+BhD,gCAAgC;AAChC,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACxB,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,OAAO,MAAM,GAAG,EAAE,CAAC;AACrB,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,IAAI,CAAC,KAAa,EAAE,IAAY;IAC9C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC;QACxB,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC;IACxB,CAAC;AACH,CAAC;AAED,+CAA+C;AAC/C,SAAS,eAAe,CAAC,MAAoB;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvD,OAAO,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;AAC1C,CAAC;AAED,4BAA4B;AAC5B,SAAS,cAAc,CAAC,MAAoB;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,KAAK,WAAW;QAC9C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;QACpC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACrD,OAAO,KAAK,GAAG,UAAU,GAAG,WAAW,CAAC;AAC1C,CAAC;AAED,wBAAwB;AACxB,SAAS,cAAc,CAAC,MAAoB,EAAE,IAAkB;IAC9D,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,gCAAgC;AAChC,SAAS,WAAW,CAClB,IAA+B,EAC/B,MAAoB;IAEpB,IAAI,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAEvB,gBAAgB;IAChB,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACnB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAQ,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC3B,IAAI,IAAI,KAAK,IAAI;oBAAE,SAAS;gBAC5B,MAAM,GAAG,GAAG,IAAK,GAAG,IAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACnC,OAAO,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAChD,CAAC;YACD,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4BAA4B;AAC5B,SAAS,YAAY,CACnB,IAA+B,EAC/B,KAAa;IAEb,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqC,CAAC;IAC5D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAoB,EACpB,QAAiB,EACjB,OAAuB;IAEvB,MAAM,YAAY,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IAElC,MAAM,aAAa,GAAG,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7D,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7D,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE3D,0BAA0B;IAC1B,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,MAAM,kBAAkB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvF,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,IAAI,gBAAgB,GAAmB,EAAE,CAAC;IAC1C,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,OAAO,GAAmB;QAC9B,QAAQ;QACR,UAAU,EAAE,OAAO,EAAE,UAAU;KAChC,CAAC;IAEF,SAAS,eAAe;QACtB,OAAO,YAAY,GAAG,QAAQ,GAAG,gBAAgB,CAAC;IACpD,CAAC;IAED,SAAS,YAAY;QACnB,kCAAkC;QAClC,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;gBAC/B,gBAAgB,CAAC,IAAI,CAAC;oBACpB,IAAI;oBACJ,CAAC,EAAE,YAAY,GAAG,gBAAgB;oBAClC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE;iBACpC,CAAC,CAAC;YACL,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,UAAU,EAAE,CAAC;QACb,gBAAgB,GAAG,EAAE,CAAC;QACtB,QAAQ,GAAG,CAAC,CAAC;QAEb,kBAAkB;QAClB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,gBAAgB,CAAC,IAAI,CAAC;gBACpB,IAAI;gBACJ,CAAC,EAAE,QAAQ;gBACX,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE;aACpC,CAAC,CAAC;YACH,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,oCAAoC;QACpC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,iBAAiB,KAAK,KAAK,EAAE,CAAC;gBACrC,gBAAgB,CAAC,IAAI,CAAC;oBACpB,IAAI;oBACJ,CAAC,EAAE,QAAQ;oBACX,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE;iBACpC,CAAC,CAAC;gBACH,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,OAAO,CACd,IAAU,EACV,GAAmB,EACnB,OAAiC,EACjC,QAAiB,EACjB,QAAiB,EACjB,SAAqC;QAErC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAE3C,IAAI,UAAU,GAAG,eAAe,EAAE,EAAE,CAAC;YACnC,YAAY,EAAE,CAAC;QACjB,CAAC;QAED,gBAAgB,CAAC,IAAI,CAAC;YACpB,IAAI;YACJ,CAAC,EAAE,QAAQ;YACX,OAAO,EAAE,GAAG;YACZ,OAAO;YACP,QAAQ;YACR,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;QACH,QAAQ,IAAI,UAAU,CAAC;IACzB,CAAC;IAED,kEAAkE;IAElE,2BAA2B;IAC3B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CAAC;YACpB,IAAI;YACJ,CAAC,EAAE,QAAQ;YACX,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE;SACpC,CAAC,CAAC;QACH,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,kCAAkC;IAClC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,iBAAiB;IACjB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,mCAAmC;IACnC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,SAAS;QAEtC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAE1C,0BAA0B;QAC1B,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACrC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAEnD,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,OAAO,EAAE,CAAC;oBACvC,eAAe;oBACf,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,YAAY,CAAC,CAAC;oBACtE,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;wBACvC,IAAI,QAAQ,CAAC,eAAe,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;4BACjD,YAAY,EAAE,CAAC;wBACjB,CAAC;wBACD,OAAO,CACL,MAAM,EACN,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EACpD,SAAS,CAAC,CAAC,CAAC,EACZ,SAAS,EACT,GAAG,EACH,SAAS,CACV,CAAC;oBACJ,CAAC;oBAED,2BAA2B;oBAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC1C,OAAO,CACL,UAAU,EACV;4BACE,GAAG,OAAO;4BACV,UAAU;4BACV,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;4BACxB,YAAY,EAAE,CAAC;4BACf,SAAS;4BACT,OAAO,EAAE,IAAI;yBACd,EACD,SAAS,CAAC,CAAC,CAAC,EACZ,CAAC,CACF,CAAC;oBACJ,CAAC;oBAED,eAAe;oBACf,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;wBAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,YAAY,CAAC,CAAC;wBACtE,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;4BACvC,OAAO,CACL,MAAM,EACN,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EACpD,SAAS,EACT,SAAS,EACT,GAAG,EACH,SAAS,CACV,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrC,OAAO,CACL,UAAU,EACV;oBACE,GAAG,OAAO;oBACV,UAAU;oBACV,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;oBACnB,YAAY,EAAE,CAAC;oBACf,OAAO,EAAE,IAAI;iBACd,EACD,IAAI,CAAC,CAAC,CAAC,EACP,CAAC,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAA0C,CAAC;QACrG,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,kBAAkB;IAClB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CAAC;YACpB,IAAI;YACJ,CAAC,EAAE,YAAY,GAAG,gBAAgB;YAClC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE;SACpC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -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
|