@report-designer/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/aggregate-engine/aggregate-runtime.d.ts +14 -0
- package/dist/aggregate-engine/aggregate-runtime.d.ts.map +1 -0
- package/dist/aggregate-engine/aggregate-runtime.js +98 -0
- package/dist/aggregate-engine/aggregate-runtime.js.map +1 -0
- package/dist/aggregate-engine/aggregate-types.d.ts +14 -0
- package/dist/aggregate-engine/aggregate-types.d.ts.map +1 -0
- package/dist/aggregate-engine/aggregate-types.js +2 -0
- package/dist/aggregate-engine/aggregate-types.js.map +1 -0
- package/dist/aggregate-engine/index.d.ts +3 -0
- package/dist/aggregate-engine/index.d.ts.map +1 -0
- package/dist/aggregate-engine/index.js +3 -0
- package/dist/aggregate-engine/index.js.map +1 -0
- package/dist/band-planner/band-plan.d.ts +46 -0
- package/dist/band-planner/band-plan.d.ts.map +1 -0
- package/dist/band-planner/band-plan.js +2 -0
- package/dist/band-planner/band-plan.js.map +1 -0
- package/dist/band-planner/build-band-plan.d.ts +4 -0
- package/dist/band-planner/build-band-plan.d.ts.map +1 -0
- package/dist/band-planner/build-band-plan.js +84 -0
- package/dist/band-planner/build-band-plan.js.map +1 -0
- package/dist/band-planner/execute-band-plan.d.ts +8 -0
- package/dist/band-planner/execute-band-plan.d.ts.map +1 -0
- package/dist/band-planner/execute-band-plan.js +280 -0
- package/dist/band-planner/execute-band-plan.js.map +1 -0
- package/dist/band-planner/index.d.ts +4 -0
- package/dist/band-planner/index.d.ts.map +1 -0
- package/dist/band-planner/index.js +4 -0
- package/dist/band-planner/index.js.map +1 -0
- package/dist/chart/chart-capabilities.d.ts +40 -0
- package/dist/chart/chart-capabilities.d.ts.map +1 -0
- package/dist/chart/chart-capabilities.js +62 -0
- package/dist/chart/chart-capabilities.js.map +1 -0
- package/dist/chart/index.d.ts +2 -0
- package/dist/chart/index.d.ts.map +1 -0
- package/dist/chart/index.js +2 -0
- package/dist/chart/index.js.map +1 -0
- package/dist/command-engine/index.d.ts +49 -0
- package/dist/command-engine/index.d.ts.map +1 -0
- package/dist/command-engine/index.js +537 -0
- package/dist/command-engine/index.js.map +1 -0
- package/dist/conditional-format/index.d.ts +17 -0
- package/dist/conditional-format/index.d.ts.map +1 -0
- package/dist/conditional-format/index.js +171 -0
- package/dist/conditional-format/index.js.map +1 -0
- package/dist/data-dictionary/index.d.ts +3 -0
- package/dist/data-dictionary/index.d.ts.map +1 -0
- package/dist/data-dictionary/index.js +3 -0
- package/dist/data-dictionary/index.js.map +1 -0
- package/dist/data-dictionary/json-dictionary.d.ts +6 -0
- package/dist/data-dictionary/json-dictionary.d.ts.map +1 -0
- package/dist/data-dictionary/json-dictionary.js +195 -0
- package/dist/data-dictionary/json-dictionary.js.map +1 -0
- package/dist/data-dictionary/json-path.d.ts +9 -0
- package/dist/data-dictionary/json-path.d.ts.map +1 -0
- package/dist/data-dictionary/json-path.js +34 -0
- package/dist/data-dictionary/json-path.js.map +1 -0
- package/dist/event-engine/event-component-handles.d.ts +84 -0
- package/dist/event-engine/event-component-handles.d.ts.map +1 -0
- package/dist/event-engine/event-component-handles.js +276 -0
- package/dist/event-engine/event-component-handles.js.map +1 -0
- package/dist/event-engine/event-context.d.ts +23 -0
- package/dist/event-engine/event-context.d.ts.map +1 -0
- package/dist/event-engine/event-context.js +184 -0
- package/dist/event-engine/event-context.js.map +1 -0
- package/dist/event-engine/event-editor-contract.d.ts +39 -0
- package/dist/event-engine/event-editor-contract.d.ts.map +1 -0
- package/dist/event-engine/event-editor-contract.js +442 -0
- package/dist/event-engine/event-editor-contract.js.map +1 -0
- package/dist/event-engine/event-editor-data-contract.d.ts +10 -0
- package/dist/event-engine/event-editor-data-contract.d.ts.map +1 -0
- package/dist/event-engine/event-editor-data-contract.js +85 -0
- package/dist/event-engine/event-editor-data-contract.js.map +1 -0
- package/dist/event-engine/event-log.d.ts +3 -0
- package/dist/event-engine/event-log.d.ts.map +1 -0
- package/dist/event-engine/event-log.js +37 -0
- package/dist/event-engine/event-log.js.map +1 -0
- package/dist/event-engine/event-runner.d.ts +7 -0
- package/dist/event-engine/event-runner.d.ts.map +1 -0
- package/dist/event-engine/event-runner.js +265 -0
- package/dist/event-engine/event-runner.js.map +1 -0
- package/dist/event-engine/event-template.d.ts +14 -0
- package/dist/event-engine/event-template.d.ts.map +1 -0
- package/dist/event-engine/event-template.js +183 -0
- package/dist/event-engine/event-template.js.map +1 -0
- package/dist/event-engine/index.d.ts +9 -0
- package/dist/event-engine/index.d.ts.map +1 -0
- package/dist/event-engine/index.js +9 -0
- package/dist/event-engine/index.js.map +1 -0
- package/dist/event-engine/types.d.ts +129 -0
- package/dist/event-engine/types.d.ts.map +1 -0
- package/dist/event-engine/types.js +2 -0
- package/dist/event-engine/types.js.map +1 -0
- package/dist/expression-engine/ast.d.ts +35 -0
- package/dist/expression-engine/ast.d.ts.map +1 -0
- package/dist/expression-engine/ast.js +9 -0
- package/dist/expression-engine/ast.js.map +1 -0
- package/dist/expression-engine/chinese-money.d.ts +2 -0
- package/dist/expression-engine/chinese-money.d.ts.map +1 -0
- package/dist/expression-engine/chinese-money.js +90 -0
- package/dist/expression-engine/chinese-money.js.map +1 -0
- package/dist/expression-engine/evaluator.d.ts +26 -0
- package/dist/expression-engine/evaluator.d.ts.map +1 -0
- package/dist/expression-engine/evaluator.js +406 -0
- package/dist/expression-engine/evaluator.js.map +1 -0
- package/dist/expression-engine/index.d.ts +7 -0
- package/dist/expression-engine/index.d.ts.map +1 -0
- package/dist/expression-engine/index.js +5 -0
- package/dist/expression-engine/index.js.map +1 -0
- package/dist/expression-engine/lexer.d.ts +33 -0
- package/dist/expression-engine/lexer.d.ts.map +1 -0
- package/dist/expression-engine/lexer.js +169 -0
- package/dist/expression-engine/lexer.js.map +1 -0
- package/dist/expression-engine/parser.d.ts +8 -0
- package/dist/expression-engine/parser.d.ts.map +1 -0
- package/dist/expression-engine/parser.js +223 -0
- package/dist/expression-engine/parser.js.map +1 -0
- package/dist/expression-engine/report-functions.d.ts +3 -0
- package/dist/expression-engine/report-functions.d.ts.map +1 -0
- package/dist/expression-engine/report-functions.js +41 -0
- package/dist/expression-engine/report-functions.js.map +1 -0
- package/dist/fonts/index.d.ts +14 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/fonts/index.js +73 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/layout-engine/index.d.ts +3 -0
- package/dist/layout-engine/index.d.ts.map +1 -0
- package/dist/layout-engine/index.js +3 -0
- package/dist/layout-engine/index.js.map +1 -0
- package/dist/layout-engine/layout-band.d.ts +40 -0
- package/dist/layout-engine/layout-band.d.ts.map +1 -0
- package/dist/layout-engine/layout-band.js +1243 -0
- package/dist/layout-engine/layout-band.js.map +1 -0
- package/dist/layout-engine/measure.d.ts +10 -0
- package/dist/layout-engine/measure.d.ts.map +1 -0
- package/dist/layout-engine/measure.js +37 -0
- package/dist/layout-engine/measure.js.map +1 -0
- package/dist/pagination/index.d.ts +38 -0
- package/dist/pagination/index.d.ts.map +1 -0
- package/dist/pagination/index.js +114 -0
- package/dist/pagination/index.js.map +1 -0
- package/dist/pagination/page-number-pass.d.ts +3 -0
- package/dist/pagination/page-number-pass.d.ts.map +1 -0
- package/dist/pagination/page-number-pass.js +18 -0
- package/dist/pagination/page-number-pass.js.map +1 -0
- package/dist/pagination/paginate.d.ts +24 -0
- package/dist/pagination/paginate.d.ts.map +1 -0
- package/dist/pagination/paginate.js +904 -0
- package/dist/pagination/paginate.js.map +1 -0
- package/dist/render-document/index.d.ts +2 -0
- package/dist/render-document/index.d.ts.map +1 -0
- package/dist/render-document/index.js +2 -0
- package/dist/render-document/index.js.map +1 -0
- package/dist/render-document/types.d.ts +159 -0
- package/dist/render-document/types.d.ts.map +1 -0
- package/dist/render-document/types.js +2 -0
- package/dist/render-document/types.js.map +1 -0
- package/dist/render-engine/index.d.ts +65 -0
- package/dist/render-engine/index.d.ts.map +1 -0
- package/dist/render-engine/index.js +188 -0
- package/dist/render-engine/index.js.map +1 -0
- package/dist/rich-text/index.d.ts +2 -0
- package/dist/rich-text/index.d.ts.map +1 -0
- package/dist/rich-text/index.js +84 -0
- package/dist/rich-text/index.js.map +1 -0
- package/dist/table/table-structure.d.ts +10 -0
- package/dist/table/table-structure.d.ts.map +1 -0
- package/dist/table/table-structure.js +196 -0
- package/dist/table/table-structure.js.map +1 -0
- package/dist/template-model/index.d.ts +5 -0
- package/dist/template-model/index.d.ts.map +1 -0
- package/dist/template-model/index.js +5 -0
- package/dist/template-model/index.js.map +1 -0
- package/dist/template-model/normalize-template.d.ts +3 -0
- package/dist/template-model/normalize-template.d.ts.map +1 -0
- package/dist/template-model/normalize-template.js +167 -0
- package/dist/template-model/normalize-template.js.map +1 -0
- package/dist/template-model/schema.d.ts +10 -0
- package/dist/template-model/schema.d.ts.map +1 -0
- package/dist/template-model/schema.js +134 -0
- package/dist/template-model/schema.js.map +1 -0
- package/dist/template-model/template.d.ts +5 -0
- package/dist/template-model/template.d.ts.map +1 -0
- package/dist/template-model/template.js +194 -0
- package/dist/template-model/template.js.map +1 -0
- package/dist/template-model/types.d.ts +641 -0
- package/dist/template-model/types.d.ts.map +1 -0
- package/dist/template-model/types.js +36 -0
- package/dist/template-model/types.js.map +1 -0
- package/dist/text-format/index.d.ts +5 -0
- package/dist/text-format/index.d.ts.map +1 -0
- package/dist/text-format/index.js +311 -0
- package/dist/text-format/index.js.map +1 -0
- package/dist/text-style/index.d.ts +3 -0
- package/dist/text-style/index.d.ts.map +1 -0
- package/dist/text-style/index.js +2 -0
- package/dist/text-style/index.js.map +1 -0
- package/dist/text-style/resolve-text-style.d.ts +58 -0
- package/dist/text-style/resolve-text-style.d.ts.map +1 -0
- package/dist/text-style/resolve-text-style.js +155 -0
- package/dist/text-style/resolve-text-style.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
import { evalExpression } from '../expression-engine/evaluator';
|
|
2
|
+
import { AggregateRuntime } from '../aggregate-engine';
|
|
3
|
+
import { createEventContext, runEventScript } from '../event-engine';
|
|
4
|
+
import { applyConditionalFormatsToStyle } from '../conditional-format';
|
|
5
|
+
import { isRepeatOnEveryPageBandType } from '../template-model/types';
|
|
6
|
+
import { getChartCapabilities } from '../chart';
|
|
7
|
+
import { formatValue } from '../text-format';
|
|
8
|
+
import { resolveComponentStyle, resolveTextStyle } from '../text-style';
|
|
9
|
+
import { measureTextBox } from './measure';
|
|
10
|
+
export function createLayoutEventState() {
|
|
11
|
+
return {
|
|
12
|
+
componentExecutions: new WeakMap(),
|
|
13
|
+
componentAfterPrint: new WeakSet(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function layoutBand(band, options) {
|
|
17
|
+
const behavior = band.behavior ?? {
|
|
18
|
+
enabled: true,
|
|
19
|
+
printOn: 'allPages',
|
|
20
|
+
printIfEmpty: true,
|
|
21
|
+
printOnAllPages: isRepeatOnEveryPageBandType(band.type),
|
|
22
|
+
keepTogether: false,
|
|
23
|
+
canBreak: band.type === 'data',
|
|
24
|
+
printAtBottom: band.type === 'pageFooter',
|
|
25
|
+
autoGrow: true,
|
|
26
|
+
autoShrink: false,
|
|
27
|
+
};
|
|
28
|
+
const hierarchyIndentComponentId = findHierarchyIndentComponentId(band);
|
|
29
|
+
const hierarchyIndent = typeof options.context.row?.HierarchyIndent === 'string'
|
|
30
|
+
? options.context.row.HierarchyIndent
|
|
31
|
+
: '';
|
|
32
|
+
const components = band.components
|
|
33
|
+
.map((component) => {
|
|
34
|
+
const rendered = layoutComponentWithEvents(component, band, options);
|
|
35
|
+
return applyHierarchyIndent(rendered, component, hierarchyIndentComponentId, hierarchyIndent);
|
|
36
|
+
})
|
|
37
|
+
.filter((component) => Boolean(component));
|
|
38
|
+
const contentHeight = components.reduce((height, component) => Math.max(height, component.y - options.y + component.height), 0);
|
|
39
|
+
const fixedHeight = Math.max(0, band.height);
|
|
40
|
+
const resolvedHeight = behavior.autoGrow === false
|
|
41
|
+
? fixedHeight
|
|
42
|
+
: Math.max(fixedHeight, contentHeight);
|
|
43
|
+
const finalHeight = behavior.autoShrink && contentHeight < resolvedHeight ? contentHeight : resolvedHeight;
|
|
44
|
+
return {
|
|
45
|
+
id: `${band.id}-${options.x}-${options.y}`,
|
|
46
|
+
bandId: band.id,
|
|
47
|
+
bandType: band.type,
|
|
48
|
+
x: options.x,
|
|
49
|
+
y: options.y,
|
|
50
|
+
width: options.width,
|
|
51
|
+
height: finalHeight,
|
|
52
|
+
backgroundColor: resolveDataBandRowBackground(band, options.context.rowIndex, Boolean(options.context.row)),
|
|
53
|
+
components,
|
|
54
|
+
overflow: components.some((component) => component.overflow) || (behavior.autoGrow === false && contentHeight > fixedHeight),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function findHierarchyIndentComponentId(band) {
|
|
58
|
+
if (band.type !== 'hierarchicalData') {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return band.components
|
|
62
|
+
.filter((component) => component.type === 'text')
|
|
63
|
+
.sort((a, b) => a.x - b.x || a.y - b.y)[0]?.id;
|
|
64
|
+
}
|
|
65
|
+
function applyHierarchyIndent(rendered, source, indentComponentId, indent) {
|
|
66
|
+
if (!rendered || !indent || source.id !== indentComponentId || !isRenderText(rendered)) {
|
|
67
|
+
return rendered;
|
|
68
|
+
}
|
|
69
|
+
const sourceText = source.type === 'text' ? source.text : '';
|
|
70
|
+
if (sourceText.includes('HierarchyIndent')) {
|
|
71
|
+
return rendered;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...rendered,
|
|
75
|
+
content: `${indent}${rendered.content ?? ''}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function isRenderText(component) {
|
|
79
|
+
return component.type === 'text' && 'content' in component;
|
|
80
|
+
}
|
|
81
|
+
function resolveDataBandRowBackground(band, rowIndex, hasRow) {
|
|
82
|
+
if (!hasRow || (band.type !== 'data' && band.type !== 'hierarchicalData')) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return rowIndex % 2 === 0
|
|
86
|
+
? band.dataBand?.oddRowBackgroundColor
|
|
87
|
+
: band.dataBand?.evenRowBackgroundColor;
|
|
88
|
+
}
|
|
89
|
+
function layoutComponentWithEvents(component, band, options) {
|
|
90
|
+
if (!isComponentRenderable(component, options)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (!options.eventRuntime) {
|
|
94
|
+
return layoutComponent(component, band, options);
|
|
95
|
+
}
|
|
96
|
+
const cachedExecution = options.eventState?.componentExecutions.get(component);
|
|
97
|
+
const execution = cachedExecution ?? { canceled: false, hidden: false, hasValue: false };
|
|
98
|
+
if (!cachedExecution) {
|
|
99
|
+
runComponentEvent(component, band, options, 'getValue', execution);
|
|
100
|
+
runComponentEvent(component, band, options, 'beforePrint', execution);
|
|
101
|
+
options.eventState?.componentExecutions.set(component, execution);
|
|
102
|
+
}
|
|
103
|
+
if (execution.hidden || execution.canceled) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const box = layoutComponent(component, band, options, execution);
|
|
107
|
+
if (options.eventMode !== 'measure' && !options.eventState?.componentAfterPrint.has(component)) {
|
|
108
|
+
runComponentEvent(component, band, options, 'afterPrint', execution);
|
|
109
|
+
options.eventState?.componentAfterPrint.add(component);
|
|
110
|
+
}
|
|
111
|
+
return box;
|
|
112
|
+
}
|
|
113
|
+
function runComponentEvent(component, band, options, eventName, execution) {
|
|
114
|
+
const eventRuntime = options.eventRuntime;
|
|
115
|
+
const event = component.events?.[eventName];
|
|
116
|
+
if (!eventRuntime || !event)
|
|
117
|
+
return;
|
|
118
|
+
const target = { ownerType: 'component', ownerId: component.id, eventName };
|
|
119
|
+
const ctx = createEventContext({
|
|
120
|
+
mode: eventRuntime.mode,
|
|
121
|
+
report: eventRuntime.report,
|
|
122
|
+
page: eventRuntime.page,
|
|
123
|
+
band,
|
|
124
|
+
component,
|
|
125
|
+
row: options.context.row,
|
|
126
|
+
rowIndex: options.context.rowIndex,
|
|
127
|
+
dataSourceId: options.context.dataSourceId,
|
|
128
|
+
data: eventRuntime.data,
|
|
129
|
+
parameters: options.context.parameters ?? eventRuntime.parameters,
|
|
130
|
+
variables: eventRuntime.variables,
|
|
131
|
+
state: eventRuntime.state,
|
|
132
|
+
log: eventRuntime.log,
|
|
133
|
+
target,
|
|
134
|
+
runtime: eventRuntime.runtime,
|
|
135
|
+
execution,
|
|
136
|
+
});
|
|
137
|
+
runEventScript({
|
|
138
|
+
event,
|
|
139
|
+
ctx,
|
|
140
|
+
target,
|
|
141
|
+
eventLogs: eventRuntime.log,
|
|
142
|
+
runtimeState: eventRuntime.runtime,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function isComponentRenderable(component, options) {
|
|
146
|
+
if (!resolveOptionalBoolean(component.enabledExpression, options))
|
|
147
|
+
return false;
|
|
148
|
+
if (!resolveOptionalBoolean(component.visible, options))
|
|
149
|
+
return false;
|
|
150
|
+
if (!resolveOptionalBoolean(component.printableExpression, options))
|
|
151
|
+
return false;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
function resolveOptionalBoolean(value, options) {
|
|
155
|
+
if (!value || value.trim() === '')
|
|
156
|
+
return true;
|
|
157
|
+
return resolveTemplateBoolean(value, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {});
|
|
158
|
+
}
|
|
159
|
+
function layoutComponent(component, band, options, execution) {
|
|
160
|
+
if (component.type === 'text') {
|
|
161
|
+
const textComponent = component;
|
|
162
|
+
const effective = resolveTextComponentStyle(textComponent, options.styles ?? []);
|
|
163
|
+
const style = applyTextConditionalFormats(effective, component, options);
|
|
164
|
+
if (style.enabled === false) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const text = resolveText(effective, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}, execution);
|
|
168
|
+
const measured = measureTextBox({ ...effective, ...style, backgroundColor: style.background }, text);
|
|
169
|
+
return {
|
|
170
|
+
id: component.id,
|
|
171
|
+
type: 'text',
|
|
172
|
+
x: options.x + component.x,
|
|
173
|
+
y: options.y + component.y,
|
|
174
|
+
width: component.width,
|
|
175
|
+
height: measured.height,
|
|
176
|
+
content: text,
|
|
177
|
+
overflow: measured.overflow,
|
|
178
|
+
style: {
|
|
179
|
+
font: style.font ?? effective.font,
|
|
180
|
+
border: style.border ?? effective.border,
|
|
181
|
+
backgroundColor: style.background ?? effective.backgroundColor,
|
|
182
|
+
textAlign: style.textAlign ?? effective.textAlign,
|
|
183
|
+
verticalAlign: style.verticalAlign ?? effective.verticalAlign,
|
|
184
|
+
padding: style.padding ?? effective.padding,
|
|
185
|
+
format: style.format ?? effective.format,
|
|
186
|
+
canGrow: style.canGrow ?? effective.canGrow,
|
|
187
|
+
canShrink: style.canShrink ?? effective.canShrink,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (component.type === 'image') {
|
|
192
|
+
const imageComponent = component;
|
|
193
|
+
return {
|
|
194
|
+
id: component.id,
|
|
195
|
+
type: 'image',
|
|
196
|
+
x: options.x + component.x,
|
|
197
|
+
y: options.y + component.y,
|
|
198
|
+
width: component.width,
|
|
199
|
+
height: component.height,
|
|
200
|
+
src: resolveTemplateValue(imageComponent.src, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}),
|
|
201
|
+
fitMode: imageComponent.fitMode,
|
|
202
|
+
style: buildBaseRenderStyle(component),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (component.type === 'richtext') {
|
|
206
|
+
const richTextComponent = component;
|
|
207
|
+
return {
|
|
208
|
+
id: component.id,
|
|
209
|
+
type: 'richtext',
|
|
210
|
+
x: options.x + component.x,
|
|
211
|
+
y: options.y + component.y,
|
|
212
|
+
width: component.width,
|
|
213
|
+
height: component.height,
|
|
214
|
+
html: resolveRichText(richTextComponent, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}),
|
|
215
|
+
style: buildBaseRenderStyle(component),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (component.type === 'chart') {
|
|
219
|
+
return layoutChart(component, options);
|
|
220
|
+
}
|
|
221
|
+
if (component.type === 'barcode') {
|
|
222
|
+
const barcodeComponent = component;
|
|
223
|
+
return {
|
|
224
|
+
id: component.id,
|
|
225
|
+
type: 'barcode',
|
|
226
|
+
x: options.x + component.x,
|
|
227
|
+
y: options.y + component.y,
|
|
228
|
+
width: component.width,
|
|
229
|
+
height: component.height,
|
|
230
|
+
value: resolveTemplateValue(barcodeComponent.value, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}),
|
|
231
|
+
format: barcodeComponent.format,
|
|
232
|
+
showText: barcodeComponent.showText,
|
|
233
|
+
foregroundColor: barcodeComponent.foregroundColor,
|
|
234
|
+
font: barcodeComponent.font,
|
|
235
|
+
style: buildBaseRenderStyle(component),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (component.type === 'qrcode') {
|
|
239
|
+
const qrcodeComponent = component;
|
|
240
|
+
return {
|
|
241
|
+
id: component.id,
|
|
242
|
+
type: 'qrcode',
|
|
243
|
+
x: options.x + component.x,
|
|
244
|
+
y: options.y + component.y,
|
|
245
|
+
width: component.width,
|
|
246
|
+
height: component.height,
|
|
247
|
+
value: resolveTemplateValue(qrcodeComponent.value, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}),
|
|
248
|
+
format: qrcodeComponent.format,
|
|
249
|
+
foregroundColor: qrcodeComponent.foregroundColor,
|
|
250
|
+
style: buildBaseRenderStyle(component),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (component.type === 'checkbox') {
|
|
254
|
+
const checkboxComponent = component;
|
|
255
|
+
return {
|
|
256
|
+
id: component.id,
|
|
257
|
+
type: 'checkbox',
|
|
258
|
+
x: options.x + component.x,
|
|
259
|
+
y: options.y + component.y,
|
|
260
|
+
width: component.width,
|
|
261
|
+
height: component.height,
|
|
262
|
+
checked: typeof checkboxComponent.checked === 'boolean'
|
|
263
|
+
? checkboxComponent.checked
|
|
264
|
+
: resolveTemplateBoolean(checkboxComponent.checked, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {}),
|
|
265
|
+
label: checkboxComponent.label
|
|
266
|
+
? resolveTemplateValue(checkboxComponent.label, options.context, options.rowsByBand ?? {}, options.pageRowsByBand ?? {})
|
|
267
|
+
: undefined,
|
|
268
|
+
foregroundColor: checkboxComponent.foregroundColor,
|
|
269
|
+
font: checkboxComponent.font,
|
|
270
|
+
style: buildBaseRenderStyle(component),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (component.type === 'table') {
|
|
274
|
+
return layoutTable(component, options);
|
|
275
|
+
}
|
|
276
|
+
if (component.type === 'line') {
|
|
277
|
+
const lineComponent = component;
|
|
278
|
+
return {
|
|
279
|
+
id: component.id,
|
|
280
|
+
type: 'line',
|
|
281
|
+
x: options.x + component.x,
|
|
282
|
+
y: options.y + component.y,
|
|
283
|
+
width: component.width,
|
|
284
|
+
height: component.height,
|
|
285
|
+
startX: lineComponent.startX,
|
|
286
|
+
startY: lineComponent.startY,
|
|
287
|
+
endX: lineComponent.endX,
|
|
288
|
+
endY: lineComponent.endY,
|
|
289
|
+
lineColor: lineComponent.lineColor,
|
|
290
|
+
lineWidth: lineComponent.lineWidth,
|
|
291
|
+
lineStyle: lineComponent.lineStyle,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (component.type === 'shape') {
|
|
295
|
+
const shapeComponent = component;
|
|
296
|
+
return {
|
|
297
|
+
id: component.id,
|
|
298
|
+
type: 'shape',
|
|
299
|
+
x: options.x + component.x,
|
|
300
|
+
y: options.y + component.y,
|
|
301
|
+
width: component.width,
|
|
302
|
+
height: component.height,
|
|
303
|
+
shapeType: shapeComponent.shapeType,
|
|
304
|
+
fillColor: shapeComponent.fillColor,
|
|
305
|
+
borderColor: shapeComponent.borderColor,
|
|
306
|
+
borderWidth: shapeComponent.borderWidth,
|
|
307
|
+
borderStyle: shapeComponent.borderStyle,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (component.type === 'pagenumber') {
|
|
311
|
+
const pageNumberComponent = component;
|
|
312
|
+
return {
|
|
313
|
+
id: component.id,
|
|
314
|
+
type: 'text',
|
|
315
|
+
x: options.x + component.x,
|
|
316
|
+
y: options.y + component.y,
|
|
317
|
+
width: component.width,
|
|
318
|
+
height: component.height,
|
|
319
|
+
content: pageNumberContent(pageNumberComponent.format),
|
|
320
|
+
style: {
|
|
321
|
+
...buildBaseRenderStyle(component),
|
|
322
|
+
font: pageNumberComponent.font,
|
|
323
|
+
textAlign: pageNumberComponent.textAlign,
|
|
324
|
+
verticalAlign: pageNumberComponent.verticalAlign ?? 'middle',
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (component.type === 'datetime') {
|
|
329
|
+
const dateTimeComponent = component;
|
|
330
|
+
return {
|
|
331
|
+
id: component.id,
|
|
332
|
+
type: 'text',
|
|
333
|
+
x: options.x + component.x,
|
|
334
|
+
y: options.y + component.y,
|
|
335
|
+
width: component.width,
|
|
336
|
+
height: component.height,
|
|
337
|
+
content: formatDateTime(new Date(), dateTimeComponent.format),
|
|
338
|
+
style: {
|
|
339
|
+
...buildBaseRenderStyle(component),
|
|
340
|
+
font: dateTimeComponent.font,
|
|
341
|
+
textAlign: dateTimeComponent.textAlign,
|
|
342
|
+
verticalAlign: dateTimeComponent.verticalAlign ?? 'middle',
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (component.type === 'panel') {
|
|
347
|
+
const panelComponent = component;
|
|
348
|
+
const panelX = options.x + component.x;
|
|
349
|
+
const panelY = options.y + component.y;
|
|
350
|
+
const contentX = panelX + (panelComponent.padding?.left ?? 0);
|
|
351
|
+
const contentY = panelY + (panelComponent.padding?.top ?? 0);
|
|
352
|
+
const children = panelComponent.components.map((child) => layoutComponentWithEvents(child, band, {
|
|
353
|
+
...options,
|
|
354
|
+
x: contentX,
|
|
355
|
+
y: contentY,
|
|
356
|
+
})).filter((child) => Boolean(child));
|
|
357
|
+
const overflow = hasContainerOverflow(children, panelX, panelY, component.width, component.height);
|
|
358
|
+
return {
|
|
359
|
+
id: component.id,
|
|
360
|
+
type: 'panel',
|
|
361
|
+
x: panelX,
|
|
362
|
+
y: panelY,
|
|
363
|
+
width: component.width,
|
|
364
|
+
height: component.height,
|
|
365
|
+
children,
|
|
366
|
+
overflow,
|
|
367
|
+
style: {
|
|
368
|
+
...buildBaseRenderStyle(component),
|
|
369
|
+
backgroundColor: panelComponent.backgroundColor ?? component.backgroundColor,
|
|
370
|
+
border: panelComponent.border,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (component.type === 'subreport') {
|
|
375
|
+
const subreportComponent = component;
|
|
376
|
+
const subreportX = options.x + component.x;
|
|
377
|
+
const subreportY = options.y + component.y;
|
|
378
|
+
const rendered = options.renderSubreport?.(subreportComponent, subreportX, subreportY, options.context) ?? {
|
|
379
|
+
missing: true,
|
|
380
|
+
children: [createSubreportPlaceholder(subreportComponent, subreportX, subreportY)],
|
|
381
|
+
};
|
|
382
|
+
const height = Math.max(component.height, rendered.height ?? component.height);
|
|
383
|
+
return {
|
|
384
|
+
id: component.id,
|
|
385
|
+
type: 'subreport',
|
|
386
|
+
x: subreportX,
|
|
387
|
+
y: subreportY,
|
|
388
|
+
width: component.width,
|
|
389
|
+
height,
|
|
390
|
+
templateUrl: subreportComponent.templateUrl,
|
|
391
|
+
missing: rendered.missing,
|
|
392
|
+
children: rendered.children,
|
|
393
|
+
overflow: hasContainerOverflow(rendered.children, subreportX, subreportY, component.width, height),
|
|
394
|
+
style: buildBaseRenderStyle(component),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
id: component.id,
|
|
399
|
+
type: component.type,
|
|
400
|
+
x: options.x + component.x,
|
|
401
|
+
y: options.y + component.y,
|
|
402
|
+
width: component.width,
|
|
403
|
+
height: component.height,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function layoutTable(component, options) {
|
|
407
|
+
const effective = resolveTableComponentStyle(component, options.styles ?? []);
|
|
408
|
+
const tableRows = normalizeRenderableTableRows(effective);
|
|
409
|
+
const columns = renderColumnsFromRows(effective.width, tableRows);
|
|
410
|
+
const rows = buildSimpleTableRows(effective, tableRows, {
|
|
411
|
+
rowsByBand: options.rowsByBand ?? {},
|
|
412
|
+
pageRowsByBand: options.pageRowsByBand ?? {},
|
|
413
|
+
context: options.context,
|
|
414
|
+
});
|
|
415
|
+
const tableHeight = Math.max(effective.height, tableRows.reduce((sum, row) => sum + (row.height ?? 8), 0));
|
|
416
|
+
return {
|
|
417
|
+
id: effective.id,
|
|
418
|
+
type: 'table',
|
|
419
|
+
x: options.x + effective.x,
|
|
420
|
+
y: options.y + effective.y,
|
|
421
|
+
width: effective.width,
|
|
422
|
+
height: tableHeight,
|
|
423
|
+
columns,
|
|
424
|
+
rows,
|
|
425
|
+
showBorder: effective.showBorder ?? false,
|
|
426
|
+
style: buildTableRenderStyle(effective),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function resolveTableComponentStyle(component, styles) {
|
|
430
|
+
const resolved = resolveComponentStyle(component, styles);
|
|
431
|
+
return {
|
|
432
|
+
...component,
|
|
433
|
+
font: resolved.font,
|
|
434
|
+
border: resolved.border,
|
|
435
|
+
backgroundColor: resolved.backgroundColor,
|
|
436
|
+
padding: resolved.padding,
|
|
437
|
+
textAlign: resolved.textAlign,
|
|
438
|
+
verticalAlign: resolved.verticalAlign,
|
|
439
|
+
format: resolved.format,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function buildBaseRenderStyle(component) {
|
|
443
|
+
const border = component.border;
|
|
444
|
+
const style = {
|
|
445
|
+
backgroundColor: component.backgroundColor,
|
|
446
|
+
border,
|
|
447
|
+
padding: component.padding,
|
|
448
|
+
};
|
|
449
|
+
return Object.values(style).some(value => value !== undefined) ? style : undefined;
|
|
450
|
+
}
|
|
451
|
+
function buildTableRenderStyle(component) {
|
|
452
|
+
const style = {
|
|
453
|
+
backgroundColor: component.backgroundColor,
|
|
454
|
+
};
|
|
455
|
+
return Object.values(style).some(value => value !== undefined) ? style : undefined;
|
|
456
|
+
}
|
|
457
|
+
function normalizeRenderableTableRows(component) {
|
|
458
|
+
const sourceRows = component.rows?.length
|
|
459
|
+
? component.rows
|
|
460
|
+
: legacyRenderableTableRows(component);
|
|
461
|
+
const columnCount = Math.max(1, sourceRows.reduce((count, row) => Math.max(count, renderableColumnCount(row)), 0), component.columnCount ?? 0);
|
|
462
|
+
return sourceRows.map((row, rowIndex) => ({
|
|
463
|
+
...row,
|
|
464
|
+
id: row.id || `row_${rowIndex + 1}`,
|
|
465
|
+
height: Math.max(0.1, row.height ?? component.rowHeight ?? 8),
|
|
466
|
+
cells: normalizeRowCells(row, rowIndex, columnCount),
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
function renderableColumnCount(row) {
|
|
470
|
+
return row.cells.reduce((count, cell, sourceIndex) => {
|
|
471
|
+
const columnIndex = Number.isInteger(cell.column) && cell.column != null && cell.column >= 0
|
|
472
|
+
? cell.column
|
|
473
|
+
: sourceIndex;
|
|
474
|
+
return Math.max(count, columnIndex + Math.max(1, cell.colSpan ?? 1));
|
|
475
|
+
}, row.cells.length);
|
|
476
|
+
}
|
|
477
|
+
function normalizeRowCells(row, rowIndex, columnCount) {
|
|
478
|
+
const positioned = new Map();
|
|
479
|
+
row.cells.forEach((cell, sourceIndex) => {
|
|
480
|
+
const columnIndex = Number.isInteger(cell.column) && cell.column != null && cell.column >= 0
|
|
481
|
+
? Math.min(cell.column, columnCount - 1)
|
|
482
|
+
: sourceIndex;
|
|
483
|
+
positioned.set(columnIndex, cell);
|
|
484
|
+
});
|
|
485
|
+
return Array.from({ length: columnCount }, (_, columnIndex) => ({
|
|
486
|
+
id: positioned.get(columnIndex)?.id ?? `cell_${rowIndex + 1}_${columnIndex + 1}`,
|
|
487
|
+
...positioned.get(columnIndex),
|
|
488
|
+
}));
|
|
489
|
+
}
|
|
490
|
+
function legacyRenderableTableRows(component) {
|
|
491
|
+
const columnCount = Math.max(1, component.columnCount ?? component.columns?.length ?? 3);
|
|
492
|
+
const rowCount = Math.max(1, component.rowCount ?? 1);
|
|
493
|
+
return Array.from({ length: rowCount }, (_, rowIndex) => {
|
|
494
|
+
return {
|
|
495
|
+
id: `row_${rowIndex + 1}`,
|
|
496
|
+
height: component.rowHeight ?? 8,
|
|
497
|
+
cells: Array.from({ length: columnCount }, (_, columnIndex) => {
|
|
498
|
+
const cell = component.cells?.find(item => (item.row ?? 0) === rowIndex && (item.column ?? 0) === columnIndex);
|
|
499
|
+
const column = component.columns?.[columnIndex];
|
|
500
|
+
return {
|
|
501
|
+
id: `cell_${rowIndex + 1}_${columnIndex + 1}`,
|
|
502
|
+
...cell,
|
|
503
|
+
text: cell?.text ?? (column?.field ? `{${column.field}}` : undefined),
|
|
504
|
+
width: cell?.width ?? column?.width,
|
|
505
|
+
};
|
|
506
|
+
}),
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function renderColumnsFromRows(tableWidth, rows) {
|
|
511
|
+
const firstRow = rows[0] ?? { cells: [] };
|
|
512
|
+
const widths = resolveRenderRowWidths(firstRow, tableWidth);
|
|
513
|
+
return widths.map((width, index) => ({
|
|
514
|
+
id: `col_${index + 1}`,
|
|
515
|
+
header: `Column ${index + 1}`,
|
|
516
|
+
field: '',
|
|
517
|
+
width,
|
|
518
|
+
cellType: 'text',
|
|
519
|
+
}));
|
|
520
|
+
}
|
|
521
|
+
function resolveRenderRowWidths(row, rowWidth) {
|
|
522
|
+
const fixed = row.cells.reduce((sum, cell) => sum + (cell.width ?? 0), 0);
|
|
523
|
+
const autoCount = row.cells.filter(cell => cell.width == null).length;
|
|
524
|
+
const autoWidth = autoCount > 0 ? Math.max(0, rowWidth - fixed) / autoCount : 0;
|
|
525
|
+
return row.cells.map(cell => Math.round((cell.width ?? autoWidth) * 10) / 10);
|
|
526
|
+
}
|
|
527
|
+
function buildSimpleTableRows(component, rows, options) {
|
|
528
|
+
const covered = new Set();
|
|
529
|
+
return rows.map((row, rowIndex) => {
|
|
530
|
+
const renderedRow = [];
|
|
531
|
+
row.cells.forEach((cell, columnIndex) => {
|
|
532
|
+
if (covered.has(`${rowIndex}-${columnIndex}`))
|
|
533
|
+
return;
|
|
534
|
+
const rowSpan = Math.max(1, Math.min(cell.rowSpan ?? 1, rows.length - rowIndex));
|
|
535
|
+
const colSpan = Math.max(1, Math.min(cell.colSpan ?? 1, row.cells.length - columnIndex));
|
|
536
|
+
markCoveredCells(covered, rowIndex, columnIndex, rowSpan, colSpan);
|
|
537
|
+
renderedRow.push({
|
|
538
|
+
row: rowIndex,
|
|
539
|
+
column: columnIndex,
|
|
540
|
+
content: tableCellContent({
|
|
541
|
+
cell,
|
|
542
|
+
column: { id: `col_${columnIndex + 1}`, header: '', field: '', width: 0, cellType: 'text' },
|
|
543
|
+
context: options.context,
|
|
544
|
+
rowsByBand: options.rowsByBand,
|
|
545
|
+
pageRowsByBand: options.pageRowsByBand,
|
|
546
|
+
}),
|
|
547
|
+
rowSpan,
|
|
548
|
+
colSpan,
|
|
549
|
+
height: row.height ?? 8,
|
|
550
|
+
style: resolvedRenderTableCellStyle(component, row, cell, rowIndex, columnIndex),
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
return renderedRow;
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function resolvedRenderTableCellStyle(component, row, cell, rowIndex, columnIndex) {
|
|
557
|
+
const rowStyle = row.style ?? {};
|
|
558
|
+
const cellStyle = cell.style ?? {};
|
|
559
|
+
const border = cell.border
|
|
560
|
+
?? cellStyle.border
|
|
561
|
+
?? row.border
|
|
562
|
+
?? rowStyle.border
|
|
563
|
+
?? component.border;
|
|
564
|
+
const style = {
|
|
565
|
+
backgroundColor: cell.backgroundColor ?? cellStyle.backgroundColor ?? row.backgroundColor ?? rowStyle.backgroundColor ?? component.backgroundColor,
|
|
566
|
+
font: mergeTableFonts(component.font, rowStyle.font, row.font, cellStyle.font, cell.font),
|
|
567
|
+
border: collapsedRenderBorder(border, rowIndex, columnIndex),
|
|
568
|
+
padding: cell.padding ?? cellStyle.padding ?? row.padding ?? rowStyle.padding ?? component.padding,
|
|
569
|
+
textAlign: cell.textAlign ?? cellStyle.textAlign ?? row.textAlign ?? rowStyle.textAlign ?? component.textAlign,
|
|
570
|
+
verticalAlign: cell.verticalAlign ?? cellStyle.verticalAlign ?? row.verticalAlign ?? rowStyle.verticalAlign ?? component.verticalAlign,
|
|
571
|
+
format: cell.format ?? cellStyle.format ?? row.format ?? rowStyle.format ?? component.format,
|
|
572
|
+
};
|
|
573
|
+
return Object.values(style).some(value => value !== undefined) ? style : undefined;
|
|
574
|
+
}
|
|
575
|
+
function mergeTableFonts(...fonts) {
|
|
576
|
+
const merged = fonts.reduce((next, font) => {
|
|
577
|
+
if (!font)
|
|
578
|
+
return next;
|
|
579
|
+
Object.entries(font).forEach(([key, value]) => {
|
|
580
|
+
if (value !== undefined) {
|
|
581
|
+
next[key] = value;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
return next;
|
|
585
|
+
}, {});
|
|
586
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
587
|
+
}
|
|
588
|
+
function collapsedRenderBorder(border, rowIndex, columnIndex) {
|
|
589
|
+
if (!border || border.style === 'none' || !border.width)
|
|
590
|
+
return undefined;
|
|
591
|
+
return {
|
|
592
|
+
...border,
|
|
593
|
+
sides: {
|
|
594
|
+
top: rowIndex === 0 && Boolean(border.sides.top),
|
|
595
|
+
left: columnIndex === 0 && Boolean(border.sides.left),
|
|
596
|
+
right: Boolean(border.sides.right),
|
|
597
|
+
bottom: Boolean(border.sides.bottom),
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function layoutChart(component, options) {
|
|
602
|
+
const chartX = options.x + component.x;
|
|
603
|
+
const chartY = options.y + component.y;
|
|
604
|
+
const rows = resolveChartRows(component, options);
|
|
605
|
+
const sortedRows = applyChartSort(rows, component.binding?.sort ?? [], options.context);
|
|
606
|
+
const aggregate = resolveChartAggregate(component);
|
|
607
|
+
const data = buildChartData(component, sortedRows, aggregate, options);
|
|
608
|
+
return {
|
|
609
|
+
id: component.id,
|
|
610
|
+
type: 'chart',
|
|
611
|
+
x: chartX,
|
|
612
|
+
y: chartY,
|
|
613
|
+
width: component.width,
|
|
614
|
+
height: component.height,
|
|
615
|
+
chartType: component.chartType,
|
|
616
|
+
data,
|
|
617
|
+
rawData: sortedRows,
|
|
618
|
+
binding: component.binding ?? { dimensions: [], measures: [], sort: [] },
|
|
619
|
+
title: component.title?.text,
|
|
620
|
+
subtitle: component.title?.subtitle,
|
|
621
|
+
showLegend: component.legend?.visible ?? true,
|
|
622
|
+
legendPosition: component.legend?.position ?? 'bottom',
|
|
623
|
+
showAxes: component.axes?.x?.visible ?? component.axes?.y?.visible ?? true,
|
|
624
|
+
showGrid: component.axes?.x?.gridVisible ?? component.axes?.y?.gridVisible ?? true,
|
|
625
|
+
showLabels: component.labels?.visible ?? false,
|
|
626
|
+
labelType: component.labels?.content === 'custom' ? undefined : component.labels?.content,
|
|
627
|
+
axisTitleX: component.axes?.x?.title,
|
|
628
|
+
axisTitleY: component.axes?.y?.title,
|
|
629
|
+
axisLabelRotation: component.axes?.x?.labelRotate,
|
|
630
|
+
titleConfig: component.title,
|
|
631
|
+
legendConfig: component.legend,
|
|
632
|
+
axesConfig: component.axes,
|
|
633
|
+
labelsConfig: component.labels,
|
|
634
|
+
theme: component.theme,
|
|
635
|
+
plotOptions: component.plotOptions,
|
|
636
|
+
aggregate,
|
|
637
|
+
emptyMessage: component.emptyMessage ?? 'No data',
|
|
638
|
+
style: buildBaseRenderStyle(component),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* 聚合模式:binding.aggregate 已删除,改由 measure.aggregation 推导。
|
|
643
|
+
* 取第一个度量(或 dualAxis 的 left 度量)的 aggregation,缺省为 'none'。
|
|
644
|
+
*/
|
|
645
|
+
function resolveChartAggregate(component) {
|
|
646
|
+
const measures = component.binding?.measures ?? [];
|
|
647
|
+
const caps = getChartCapabilities(component.chartType);
|
|
648
|
+
const index = caps.measures === 'dualAxis' ? measures.findIndex(m => m.axis !== 'right') : 0;
|
|
649
|
+
const measure = measures[index < 0 ? 0 : index];
|
|
650
|
+
return measure?.aggregation ?? 'none';
|
|
651
|
+
}
|
|
652
|
+
function resolveChartRows(component, options) {
|
|
653
|
+
const binding = component.binding;
|
|
654
|
+
const dataSourceId = binding?.dataSourceId;
|
|
655
|
+
if (binding?.arrayPath && options.context.row) {
|
|
656
|
+
const nested = asRecordArray(valueAtPath(options.context.row, binding.arrayPath));
|
|
657
|
+
if (nested.length > 0)
|
|
658
|
+
return nested;
|
|
659
|
+
}
|
|
660
|
+
if (!dataSourceId) {
|
|
661
|
+
return options.context.row ? [options.context.row] : [];
|
|
662
|
+
}
|
|
663
|
+
if (dataSourceId === options.context.dataSourceId && options.context.row) {
|
|
664
|
+
return [options.context.row];
|
|
665
|
+
}
|
|
666
|
+
return options.context.rowsByBand?.[dataSourceId] ?? options.rowsByBand?.[dataSourceId] ?? [];
|
|
667
|
+
}
|
|
668
|
+
function applyChartSort(rows, sort, context) {
|
|
669
|
+
if (sort.length === 0)
|
|
670
|
+
return rows;
|
|
671
|
+
return [...rows].sort((left, right) => {
|
|
672
|
+
for (const rule of sort) {
|
|
673
|
+
const leftValue = resolveChartExpressionValue(rule.field, left, context);
|
|
674
|
+
const rightValue = resolveChartExpressionValue(rule.field, right, context);
|
|
675
|
+
const order = compareChartValues(leftValue, rightValue);
|
|
676
|
+
if (order !== 0) {
|
|
677
|
+
return rule.direction === 'desc' ? -order : order;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return 0;
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function buildChartData(component, rows, aggregate, options) {
|
|
684
|
+
const caps = getChartCapabilities(component.chartType);
|
|
685
|
+
const binding = component.binding ?? {};
|
|
686
|
+
const dimensions = binding.dimensions ?? [];
|
|
687
|
+
const measures = binding.measures ?? [];
|
|
688
|
+
const supportsSeriesField = caps.series === 'fieldOrMeasureNames';
|
|
689
|
+
const seriesField = supportsSeriesField ? binding.seriesField : undefined;
|
|
690
|
+
const measureFields = measures.map(measure => measure.field).filter(Boolean);
|
|
691
|
+
const isScatter = component.chartType === 'scatter';
|
|
692
|
+
const isHeatmap = component.chartType === 'heatmap';
|
|
693
|
+
const isSankey = component.chartType === 'sankey';
|
|
694
|
+
const isHierarchical = caps.dimensions === 'hierarchical';
|
|
695
|
+
const points = rows.flatMap((row) => {
|
|
696
|
+
const measureValues = resolveMeasureValues(row, measureFields);
|
|
697
|
+
const measureKey = measures[0]?.field;
|
|
698
|
+
if (isScatter) {
|
|
699
|
+
const x = resolveNumber(row[dimensions[0]?.field ?? '']);
|
|
700
|
+
const y = resolveNumber(row[measures[0]?.field ?? '']);
|
|
701
|
+
return {
|
|
702
|
+
category: x == null ? '' : String(x),
|
|
703
|
+
value: y,
|
|
704
|
+
measureValues,
|
|
705
|
+
measureKey,
|
|
706
|
+
label: x == null ? undefined : String(x),
|
|
707
|
+
x, y,
|
|
708
|
+
raw: row,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
if (isHeatmap) {
|
|
712
|
+
const category = readField(row, dimensions[0]?.field);
|
|
713
|
+
const series = readField(row, dimensions[1]?.field);
|
|
714
|
+
const value = resolveNumber(row[measures[0]?.field ?? '']);
|
|
715
|
+
return {
|
|
716
|
+
category: category == null ? '' : String(category),
|
|
717
|
+
value,
|
|
718
|
+
measureValues,
|
|
719
|
+
measureKey,
|
|
720
|
+
series: series == null ? undefined : String(series),
|
|
721
|
+
label: category == null ? undefined : String(category),
|
|
722
|
+
x: null,
|
|
723
|
+
y: value,
|
|
724
|
+
raw: row,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
if (isSankey) {
|
|
728
|
+
const source = readField(row, dimensions[0]?.field);
|
|
729
|
+
const target = readField(row, dimensions[1]?.field);
|
|
730
|
+
const value = resolveNumber(row[measures[0]?.field ?? '']);
|
|
731
|
+
return {
|
|
732
|
+
category: source == null ? '' : String(source),
|
|
733
|
+
value,
|
|
734
|
+
measureValues,
|
|
735
|
+
measureKey,
|
|
736
|
+
source: source == null ? undefined : String(source),
|
|
737
|
+
target: target == null ? undefined : String(target),
|
|
738
|
+
label: source == null ? undefined : String(source),
|
|
739
|
+
x: null,
|
|
740
|
+
y: value,
|
|
741
|
+
raw: row,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
if (isHierarchical) {
|
|
745
|
+
const path = dimensions.map(d => readField(row, d.field)).filter(v => v != null).map(String);
|
|
746
|
+
const value = resolveNumber(row[measures[0]?.field ?? '']);
|
|
747
|
+
return {
|
|
748
|
+
category: path[0] ?? '',
|
|
749
|
+
value,
|
|
750
|
+
measureValues,
|
|
751
|
+
measureKey,
|
|
752
|
+
path: path.length > 0 ? path : undefined,
|
|
753
|
+
label: path[path.length - 1],
|
|
754
|
+
x: null,
|
|
755
|
+
y: value,
|
|
756
|
+
raw: row,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
// 笛卡尔分类图(single 维度):按 measure 展开。
|
|
760
|
+
const category = readField(row, dimensions[0]?.field);
|
|
761
|
+
const boundSeries = readField(row, seriesField);
|
|
762
|
+
const series = boundSeries == null ? undefined : String(boundSeries);
|
|
763
|
+
const basePoint = {
|
|
764
|
+
category: category == null ? '' : String(category),
|
|
765
|
+
value: null,
|
|
766
|
+
measureValues,
|
|
767
|
+
measureKey,
|
|
768
|
+
series,
|
|
769
|
+
label: category == null ? undefined : String(category),
|
|
770
|
+
x: null,
|
|
771
|
+
y: null,
|
|
772
|
+
raw: row,
|
|
773
|
+
};
|
|
774
|
+
if (caps.measures === 'multi' || caps.measures === 'dualAxis') {
|
|
775
|
+
if (seriesField && measures[0]) {
|
|
776
|
+
const value = resolveNumber(row[measures[0].field]);
|
|
777
|
+
return {
|
|
778
|
+
...basePoint,
|
|
779
|
+
value,
|
|
780
|
+
y: value,
|
|
781
|
+
measureKey: measures[0].field,
|
|
782
|
+
axis: caps.measures === 'dualAxis' ? (measures[0].axis ?? 'left') : undefined,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
// 未绑定系列字段时,每个度量展开为一个 point(即一个系列)。
|
|
786
|
+
return measures.map(measure => {
|
|
787
|
+
const value = resolveNumber(row[measure.field]);
|
|
788
|
+
return {
|
|
789
|
+
...basePoint,
|
|
790
|
+
value,
|
|
791
|
+
y: value,
|
|
792
|
+
measureKey: measure.field,
|
|
793
|
+
series: measure.alias || measure.field,
|
|
794
|
+
axis: caps.measures === 'dualAxis' ? (measure.axis ?? 'left') : undefined,
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
// 单度量
|
|
799
|
+
const value = resolveNumber(row[measures[0]?.field ?? '']);
|
|
800
|
+
return {
|
|
801
|
+
...basePoint,
|
|
802
|
+
value,
|
|
803
|
+
y: value,
|
|
804
|
+
};
|
|
805
|
+
});
|
|
806
|
+
if (aggregate === 'none')
|
|
807
|
+
return points;
|
|
808
|
+
return aggregateChartPoints(points, isScatter, aggregate);
|
|
809
|
+
}
|
|
810
|
+
function readField(row, field) {
|
|
811
|
+
if (!field)
|
|
812
|
+
return undefined;
|
|
813
|
+
return row[field];
|
|
814
|
+
}
|
|
815
|
+
function aggregateChartPoints(points, isScatter, aggregate) {
|
|
816
|
+
const grouped = new Map();
|
|
817
|
+
for (const point of points) {
|
|
818
|
+
const key = isScatter
|
|
819
|
+
? `${point.x ?? point.category}::${point.series ?? ''}`
|
|
820
|
+
: `${point.category}::${point.series ?? ''}`;
|
|
821
|
+
const entry = grouped.get(key);
|
|
822
|
+
if (!entry) {
|
|
823
|
+
grouped.set(key, {
|
|
824
|
+
point,
|
|
825
|
+
values: point.value == null ? [] : [point.value],
|
|
826
|
+
measureValues: collectMeasureValues(point),
|
|
827
|
+
count: 1,
|
|
828
|
+
});
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
entry.count += 1;
|
|
832
|
+
if (point.value != null)
|
|
833
|
+
entry.values.push(point.value);
|
|
834
|
+
mergeMeasureValues(entry.measureValues, point);
|
|
835
|
+
}
|
|
836
|
+
return Array.from(grouped.values()).map(({ point, values, measureValues, count }) => {
|
|
837
|
+
const value = aggregateValues(values, count, aggregate);
|
|
838
|
+
return {
|
|
839
|
+
...point,
|
|
840
|
+
value,
|
|
841
|
+
measureValues: aggregateMeasureValues(measureValues, count, aggregate),
|
|
842
|
+
y: point.x == null ? value : (point.y ?? value),
|
|
843
|
+
x: point.x ?? null,
|
|
844
|
+
};
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
function resolveMeasureValues(row, fields) {
|
|
848
|
+
if (fields.length === 0)
|
|
849
|
+
return undefined;
|
|
850
|
+
return Object.fromEntries(fields.map(field => [field, resolveNumber(row[field])]));
|
|
851
|
+
}
|
|
852
|
+
function collectMeasureValues(point) {
|
|
853
|
+
const values = {};
|
|
854
|
+
mergeMeasureValues(values, point);
|
|
855
|
+
return values;
|
|
856
|
+
}
|
|
857
|
+
function mergeMeasureValues(target, point) {
|
|
858
|
+
for (const [field, value] of Object.entries(point.measureValues ?? {})) {
|
|
859
|
+
if (value == null)
|
|
860
|
+
continue;
|
|
861
|
+
target[field] = [...(target[field] ?? []), value];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function aggregateMeasureValues(values, count, aggregate) {
|
|
865
|
+
const entries = Object.entries(values);
|
|
866
|
+
if (entries.length === 0)
|
|
867
|
+
return undefined;
|
|
868
|
+
return Object.fromEntries(entries.map(([field, fieldValues]) => [field, aggregateValues(fieldValues, count, aggregate)]));
|
|
869
|
+
}
|
|
870
|
+
function aggregateValues(values, count, aggregate) {
|
|
871
|
+
if (aggregate === 'count')
|
|
872
|
+
return count;
|
|
873
|
+
if (values.length === 0)
|
|
874
|
+
return null;
|
|
875
|
+
if (aggregate === 'min')
|
|
876
|
+
return Math.min(...values);
|
|
877
|
+
if (aggregate === 'max')
|
|
878
|
+
return Math.max(...values);
|
|
879
|
+
if (aggregate === 'avg')
|
|
880
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
881
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
882
|
+
}
|
|
883
|
+
function resolveChartExpressionValue(expression, row, context) {
|
|
884
|
+
if (!expression)
|
|
885
|
+
return undefined;
|
|
886
|
+
try {
|
|
887
|
+
return evalExpression(expression, (source, field) => resolveFieldForChart(row, context, source, field), context.rowIndex, expressionVariables(context, { row }), new AggregateRuntime({ rowsByBand: context.rowsByBand ?? {}, pageRowsByBand: {}, defaultDataSourceId: context.dataSourceId }), context.expressionFunctions);
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
return expression;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function resolveFieldForChart(row, context, source, field) {
|
|
894
|
+
if (['Parameter', 'Parameters', 'Params'].includes(source)) {
|
|
895
|
+
return context.parameters?.[field];
|
|
896
|
+
}
|
|
897
|
+
if (!source && context.parameters && field in context.parameters) {
|
|
898
|
+
return context.parameters[field];
|
|
899
|
+
}
|
|
900
|
+
if (!source && context.expressionVariables && field in context.expressionVariables) {
|
|
901
|
+
return context.expressionVariables[field];
|
|
902
|
+
}
|
|
903
|
+
const scoped = row[source];
|
|
904
|
+
if (scoped && typeof scoped === 'object' && !Array.isArray(scoped)) {
|
|
905
|
+
return scoped[field];
|
|
906
|
+
}
|
|
907
|
+
return row[field] ?? row[`${source}.${field}`] ?? context.groupValues[field];
|
|
908
|
+
}
|
|
909
|
+
function compareChartValues(left, right) {
|
|
910
|
+
if (left == null && right == null)
|
|
911
|
+
return 0;
|
|
912
|
+
if (left == null)
|
|
913
|
+
return -1;
|
|
914
|
+
if (right == null)
|
|
915
|
+
return 1;
|
|
916
|
+
const leftNumber = Number(left);
|
|
917
|
+
const rightNumber = Number(right);
|
|
918
|
+
if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
|
|
919
|
+
return leftNumber - rightNumber;
|
|
920
|
+
}
|
|
921
|
+
return String(left).localeCompare(String(right));
|
|
922
|
+
}
|
|
923
|
+
function resolveNumber(value) {
|
|
924
|
+
if (value == null || value === '')
|
|
925
|
+
return null;
|
|
926
|
+
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
927
|
+
return Number.isFinite(numberValue) ? numberValue : null;
|
|
928
|
+
}
|
|
929
|
+
function valueAtPath(row, path) {
|
|
930
|
+
return path.split('.').filter(Boolean).reduce((value, segment) => {
|
|
931
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
932
|
+
return value[segment];
|
|
933
|
+
}
|
|
934
|
+
return undefined;
|
|
935
|
+
}, row);
|
|
936
|
+
}
|
|
937
|
+
function asRecordArray(value) {
|
|
938
|
+
return Array.isArray(value)
|
|
939
|
+
? value.filter((item) => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
|
|
940
|
+
: [];
|
|
941
|
+
}
|
|
942
|
+
function createSubreportPlaceholder(component, x, y) {
|
|
943
|
+
return {
|
|
944
|
+
id: `${component.id}-missing-placeholder`,
|
|
945
|
+
type: 'text',
|
|
946
|
+
x,
|
|
947
|
+
y,
|
|
948
|
+
width: component.width,
|
|
949
|
+
height: component.height,
|
|
950
|
+
content: `Missing subreport: ${component.templateUrl}`,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function hasContainerOverflow(children, x, y, width, height) {
|
|
954
|
+
return children.some(child => (Boolean(child.overflow)
|
|
955
|
+
|| child.x < x
|
|
956
|
+
|| child.y < y
|
|
957
|
+
|| child.x + child.width > x + width
|
|
958
|
+
|| child.y + child.height > y + height));
|
|
959
|
+
}
|
|
960
|
+
function markCoveredCells(covered, row, column, rowSpan, colSpan) {
|
|
961
|
+
for (let coveredRow = row; coveredRow < row + rowSpan; coveredRow += 1) {
|
|
962
|
+
for (let coveredColumn = column; coveredColumn < column + colSpan; coveredColumn += 1) {
|
|
963
|
+
if (coveredRow === row && coveredColumn === column)
|
|
964
|
+
continue;
|
|
965
|
+
covered.add(`${coveredRow}-${coveredColumn}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
function tableCellContent(options) {
|
|
970
|
+
if (options.cell?.text) {
|
|
971
|
+
const value = resolveTableCellText(options.cell.text, options.context, options.rowsByBand, options.pageRowsByBand);
|
|
972
|
+
return formatValue(value, options.cell.format);
|
|
973
|
+
}
|
|
974
|
+
if (!options.column.field) {
|
|
975
|
+
return '';
|
|
976
|
+
}
|
|
977
|
+
const value = options.context.row?.[options.column.field] ?? options.context.row?.[`${options.context.dataSourceId}.${options.column.field}`];
|
|
978
|
+
return formatValue(value, options.cell?.format);
|
|
979
|
+
}
|
|
980
|
+
function resolveTableCellText(text, context, rowsByBand, pageRowsByBand) {
|
|
981
|
+
if (text.includes('{PageNumber}') || text.includes('{TotalPages}')) {
|
|
982
|
+
return text;
|
|
983
|
+
}
|
|
984
|
+
if (!text.includes('{') && !text.includes('(') && !text.includes('=')) {
|
|
985
|
+
return text;
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
return evalExpression(text, (source, field) => resolveField(context, source, field), context.rowIndex, expressionVariables(context), new AggregateRuntime({ rowsByBand: context.rowsByBand ?? rowsByBand, pageRowsByBand, defaultDataSourceId: context.dataSourceId }), context.expressionFunctions);
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
return resolveTemplateValue(text, context, rowsByBand, pageRowsByBand);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function resolveTemplateBoolean(value, context, rowsByBand, pageRowsByBand) {
|
|
995
|
+
const resolved = resolveTemplateValue(value, context, rowsByBand, pageRowsByBand).trim().toLowerCase();
|
|
996
|
+
if (['true', '1', 'yes', 'y'].includes(resolved))
|
|
997
|
+
return true;
|
|
998
|
+
if (['false', '0', 'no', 'n', ''].includes(resolved))
|
|
999
|
+
return false;
|
|
1000
|
+
return Boolean(resolved);
|
|
1001
|
+
}
|
|
1002
|
+
function pageNumberContent(format) {
|
|
1003
|
+
switch (format) {
|
|
1004
|
+
case '1':
|
|
1005
|
+
return '{PageNumber}';
|
|
1006
|
+
case 'Page 1':
|
|
1007
|
+
return 'Page {PageNumber}';
|
|
1008
|
+
case 'Page 1 of N':
|
|
1009
|
+
return 'Page {PageNumber} of {TotalPages}';
|
|
1010
|
+
case '1/N':
|
|
1011
|
+
default:
|
|
1012
|
+
return '{PageNumber}/{TotalPages}';
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function formatDateTime(date, pattern) {
|
|
1016
|
+
const parts = {
|
|
1017
|
+
yyyy: String(date.getFullYear()).padStart(4, '0'),
|
|
1018
|
+
MM: String(date.getMonth() + 1).padStart(2, '0'),
|
|
1019
|
+
dd: String(date.getDate()).padStart(2, '0'),
|
|
1020
|
+
HH: String(date.getHours()).padStart(2, '0'),
|
|
1021
|
+
mm: String(date.getMinutes()).padStart(2, '0'),
|
|
1022
|
+
ss: String(date.getSeconds()).padStart(2, '0'),
|
|
1023
|
+
};
|
|
1024
|
+
return pattern.replace(/yyyy|MM|dd|HH|mm|ss/g, token => parts[token] ?? token);
|
|
1025
|
+
}
|
|
1026
|
+
function resolveText(component, context, rowsByBand, pageRowsByBand, execution) {
|
|
1027
|
+
if (execution?.hasValue) {
|
|
1028
|
+
return formatValue(execution.value, component.format);
|
|
1029
|
+
}
|
|
1030
|
+
if (component.text.includes('{PageNumber}') || component.text.includes('{TotalPages}')) {
|
|
1031
|
+
return component.text;
|
|
1032
|
+
}
|
|
1033
|
+
if (!component.text.includes('{') && !component.text.includes('(') && !component.text.includes('=')) {
|
|
1034
|
+
return component.text;
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const value = evalExpression(component.text, (source, field) => resolveField(context, source, field), context.rowIndex, expressionVariables(context), new AggregateRuntime({ rowsByBand: context.rowsByBand ?? rowsByBand, pageRowsByBand, defaultDataSourceId: context.dataSourceId }), context.expressionFunctions);
|
|
1038
|
+
return formatValue(value, component.format);
|
|
1039
|
+
}
|
|
1040
|
+
catch {
|
|
1041
|
+
return resolveTemplateValue(component.text, context, rowsByBand, pageRowsByBand);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function resolveRichText(component, context, rowsByBand, pageRowsByBand) {
|
|
1045
|
+
return resolveTemplateValue(component.html, context, rowsByBand, pageRowsByBand);
|
|
1046
|
+
}
|
|
1047
|
+
function resolveTemplateValue(value, context, rowsByBand, pageRowsByBand) {
|
|
1048
|
+
if (!value.includes('{') && !value.includes('(') && !value.includes('=')) {
|
|
1049
|
+
return value;
|
|
1050
|
+
}
|
|
1051
|
+
const runtime = new AggregateRuntime({ rowsByBand: context.rowsByBand ?? rowsByBand, pageRowsByBand, defaultDataSourceId: context.dataSourceId });
|
|
1052
|
+
const evaluateTextExpression = (expression) => {
|
|
1053
|
+
const result = evalExpression(expression, (source, field) => resolveField(context, source, field), context.rowIndex, expressionVariables(context), runtime, context.expressionFunctions);
|
|
1054
|
+
return result == null ? '' : String(result);
|
|
1055
|
+
};
|
|
1056
|
+
const placeholderPattern = /\{([^{}]+)\}/g;
|
|
1057
|
+
const isSinglePlaceholder = value.trim().match(/^\{([^{}]+)\}$/);
|
|
1058
|
+
const withFunctions = replaceEmbeddedFunctionCalls(value, (expression) => {
|
|
1059
|
+
try {
|
|
1060
|
+
return evaluateTextExpression(expression);
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
return expression;
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
if (withFunctions !== value) {
|
|
1067
|
+
return resolveTemplateValue(withFunctions, context, rowsByBand, pageRowsByBand);
|
|
1068
|
+
}
|
|
1069
|
+
if (!isSinglePlaceholder && placeholderPattern.test(value)) {
|
|
1070
|
+
return value.replace(/\{([^{}]+)\}/g, (match, expressionBody) => {
|
|
1071
|
+
try {
|
|
1072
|
+
return evaluateTextExpression(`{${expressionBody}}`);
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
return match;
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
return evaluateTextExpression(value);
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
return value;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
function replaceEmbeddedFunctionCalls(value, replace) {
|
|
1087
|
+
let output = '';
|
|
1088
|
+
let index = 0;
|
|
1089
|
+
while (index < value.length) {
|
|
1090
|
+
const start = findNextFunctionStart(value, index);
|
|
1091
|
+
if (start < 0) {
|
|
1092
|
+
output += value.slice(index);
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1095
|
+
const end = findFunctionCallEnd(value, start);
|
|
1096
|
+
if (end < 0) {
|
|
1097
|
+
output += value.slice(index);
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
output += value.slice(index, start);
|
|
1101
|
+
output += replace(value.slice(start, end));
|
|
1102
|
+
index = end;
|
|
1103
|
+
}
|
|
1104
|
+
return output;
|
|
1105
|
+
}
|
|
1106
|
+
function findNextFunctionStart(value, from) {
|
|
1107
|
+
for (let index = from; index < value.length; index += 1) {
|
|
1108
|
+
if (!isIdentifierStart(value[index])) {
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
const previous = index > 0 ? value[index - 1] : '';
|
|
1112
|
+
if (isIdentifierPart(previous)) {
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
let cursor = index + 1;
|
|
1116
|
+
while (cursor < value.length && isIdentifierPart(value[cursor])) {
|
|
1117
|
+
cursor += 1;
|
|
1118
|
+
}
|
|
1119
|
+
if (value[cursor] === '(') {
|
|
1120
|
+
return index;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return -1;
|
|
1124
|
+
}
|
|
1125
|
+
function findFunctionCallEnd(value, start) {
|
|
1126
|
+
const open = value.indexOf('(', start);
|
|
1127
|
+
if (open < 0)
|
|
1128
|
+
return -1;
|
|
1129
|
+
let depth = 0;
|
|
1130
|
+
let inString = false;
|
|
1131
|
+
let inFieldRef = false;
|
|
1132
|
+
for (let index = open; index < value.length; index += 1) {
|
|
1133
|
+
const char = value[index];
|
|
1134
|
+
const previous = index > 0 ? value[index - 1] : '';
|
|
1135
|
+
if (inString) {
|
|
1136
|
+
if (char === '"' && previous !== '\\') {
|
|
1137
|
+
inString = false;
|
|
1138
|
+
}
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
if (inFieldRef) {
|
|
1142
|
+
if (char === '}') {
|
|
1143
|
+
inFieldRef = false;
|
|
1144
|
+
}
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (char === '"') {
|
|
1148
|
+
inString = true;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (char === '{') {
|
|
1152
|
+
inFieldRef = true;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (char === '(') {
|
|
1156
|
+
depth += 1;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (char === ')') {
|
|
1160
|
+
depth -= 1;
|
|
1161
|
+
if (depth === 0) {
|
|
1162
|
+
return index + 1;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return -1;
|
|
1167
|
+
}
|
|
1168
|
+
function isIdentifierStart(char) {
|
|
1169
|
+
return Boolean(char && /[A-Za-z_]/.test(char));
|
|
1170
|
+
}
|
|
1171
|
+
function isIdentifierPart(char) {
|
|
1172
|
+
return Boolean(char && /[A-Za-z0-9_.]/.test(char));
|
|
1173
|
+
}
|
|
1174
|
+
function resolveTextComponentStyle(component, styles) {
|
|
1175
|
+
const resolved = resolveTextStyle(component, styles);
|
|
1176
|
+
return {
|
|
1177
|
+
...component,
|
|
1178
|
+
font: resolved.font,
|
|
1179
|
+
border: resolved.border,
|
|
1180
|
+
backgroundColor: resolved.backgroundColor,
|
|
1181
|
+
textAlign: resolved.textAlign,
|
|
1182
|
+
verticalAlign: resolved.verticalAlign,
|
|
1183
|
+
padding: resolved.padding,
|
|
1184
|
+
format: resolved.format,
|
|
1185
|
+
canGrow: resolved.canGrow,
|
|
1186
|
+
canShrink: resolved.canShrink,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function applyTextConditionalFormats(component, sourceComponent, options) {
|
|
1190
|
+
const baseStyle = {
|
|
1191
|
+
font: component.font,
|
|
1192
|
+
background: component.backgroundColor,
|
|
1193
|
+
border: component.border,
|
|
1194
|
+
padding: component.padding,
|
|
1195
|
+
textAlign: component.textAlign,
|
|
1196
|
+
verticalAlign: component.verticalAlign,
|
|
1197
|
+
format: component.format,
|
|
1198
|
+
canGrow: component.canGrow,
|
|
1199
|
+
canShrink: component.canShrink,
|
|
1200
|
+
enabled: true,
|
|
1201
|
+
};
|
|
1202
|
+
return applyConditionalFormatsToStyle(baseStyle, options.conditionalFormats ?? [], sourceComponent, {
|
|
1203
|
+
resolveField: (source, field) => resolveField(options.context, source, field),
|
|
1204
|
+
rowIndex: options.context.rowIndex,
|
|
1205
|
+
variables: expressionVariables(options.context),
|
|
1206
|
+
functions: options.context.expressionFunctions,
|
|
1207
|
+
reportRuntime: new AggregateRuntime({
|
|
1208
|
+
rowsByBand: options.context.rowsByBand ?? options.rowsByBand ?? {},
|
|
1209
|
+
pageRowsByBand: options.pageRowsByBand ?? {},
|
|
1210
|
+
defaultDataSourceId: options.context.dataSourceId,
|
|
1211
|
+
}),
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
function expressionVariables(context, extra = {}) {
|
|
1215
|
+
return {
|
|
1216
|
+
row: context.row,
|
|
1217
|
+
groupValues: context.groupValues,
|
|
1218
|
+
parameters: context.parameters,
|
|
1219
|
+
...extra,
|
|
1220
|
+
...(context.expressionVariables ?? {}),
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function resolveField(context, source, field) {
|
|
1224
|
+
if (['Parameter', 'Parameters', 'Params'].includes(source)) {
|
|
1225
|
+
return context.parameters?.[field];
|
|
1226
|
+
}
|
|
1227
|
+
if (!source && context.parameters && field in context.parameters) {
|
|
1228
|
+
return context.parameters[field];
|
|
1229
|
+
}
|
|
1230
|
+
if (!source && context.expressionVariables && field in context.expressionVariables) {
|
|
1231
|
+
return context.expressionVariables[field];
|
|
1232
|
+
}
|
|
1233
|
+
if (source === 'Group' || source === 'Groups') {
|
|
1234
|
+
return context.groupValues[field];
|
|
1235
|
+
}
|
|
1236
|
+
const row = context.row ?? {};
|
|
1237
|
+
const scoped = row[source];
|
|
1238
|
+
if (scoped && typeof scoped === 'object' && !Array.isArray(scoped)) {
|
|
1239
|
+
return scoped[field];
|
|
1240
|
+
}
|
|
1241
|
+
return row[field] ?? row[`${source}.${field}`] ?? context.groupValues[field];
|
|
1242
|
+
}
|
|
1243
|
+
//# sourceMappingURL=layout-band.js.map
|