@legalplace/prerenderx 3.8.18-staging.2 → 3.8.18

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.
@@ -1,23 +1,10 @@
1
- import type { OptionV3 } from "@legalplace/models-v3-types";
2
1
  import type { Types } from "@legalplace/referencesparser";
3
2
  import type ConditionsRunner from "@legalplace/conditions-runner";
4
3
  import type InputsType from "@legalplace/types/dist/inputs";
5
- import {
6
- isOptionDisplayed,
7
- buildOutputTableCellStyleAttr,
8
- buildOutputTableStyleAttr,
9
- getOutputTableCellStyle,
10
- replaceOutputTableMarkers,
11
- stripOutputTableMarkers,
12
- } from "@legalplace/referencesparser";
13
4
  import {
14
5
  parseOutputWithVariables,
15
6
  getRelatedVariablesValues,
16
7
  } from "./OutputParser";
17
- import {
18
- cleanupRenderedOutputHtml,
19
- expandOutputTableMarkup,
20
- } from "./tableOutputExpansion";
21
8
  import type NumAuto from "./NumAuto";
22
9
 
23
10
  class OptionRender {
@@ -53,225 +40,83 @@ class OptionRender {
53
40
  this.renderOption();
54
41
  }
55
42
 
56
- /**
57
- * Returns rendered HTML outputs for this option subtree.
58
- */
59
43
  getOutputs() {
60
44
  return this.outputs;
61
45
  }
62
46
 
63
- /**
64
- * Returns whether an option is displayed at the current occurrence.
65
- */
66
- private isDisplayed(optionId = this.id): boolean {
67
- return isOptionDisplayed(
68
- this.references,
69
- this.ovc,
70
- (type, entityId, entityIndex) => {
71
- if (type === "options") {
72
- const conditionObject = this.references.conditions.options[entityId];
73
- return conditionObject === undefined
74
- ? true
75
- : this.conditions.executeCondition(
76
- conditionObject,
77
- entityId,
78
- entityIndex,
79
- "options"
80
- );
81
- }
82
- if (type === "sections") {
83
- const conditionObject =
84
- this.references.conditions.sections.main[entityId];
85
- return conditionObject === undefined
86
- ? true
87
- : this.conditions.executeCondition(
88
- conditionObject,
89
- entityId,
90
- entityIndex,
91
- "sections"
92
- );
93
- }
94
- return true;
95
- },
96
- optionId,
97
- this.index
98
- );
99
- }
100
-
101
- /**
102
- * Renders a table cell option as a td element.
103
- */
104
- private renderOutputTableCell(cellId: number): string {
105
- const cellOption = this.references.options[cellId];
106
- if (cellOption?.meta.tableRole !== "cell") return "";
107
-
108
- const styleAttr = buildOutputTableCellStyleAttr(
109
- getOutputTableCellStyle(cellOption)
110
- );
111
-
112
- // Hidden cells are omitted entirely instead of rendering an empty td
113
- if (!this.isDisplayed(cellId)) {
114
- return "";
115
- }
116
-
117
- const outputReference = this.references.outputs[cellId];
118
- if (outputReference === undefined) {
119
- return `<td${styleAttr}></td>`;
120
- }
121
-
122
- const autonums: [string, string, number, string | null][] =
123
- outputReference.autonums.map((currentAn) => {
124
- const fn =
125
- currentAn[0] === "numauto-pre"
126
- ? this.NumAuto.getNumPre
127
- : this.NumAuto.getNum;
128
- return [
129
- currentAn[0],
130
- currentAn[1],
131
- fn(currentAn[1], currentAn[2]),
132
- currentAn[2],
133
- ];
134
- });
135
-
136
- const variables = getRelatedVariablesValues(
137
- cellId,
138
- this.index,
139
- outputReference.variables,
140
- this.ovc,
141
- this.references
142
- );
143
-
144
- const content = parseOutputWithVariables(
145
- outputReference.raw,
146
- this.index,
147
- variables,
148
- autonums
149
- );
150
-
151
- return `<td${styleAttr}>${content}</td>`;
152
- }
153
-
154
- /**
155
- * Renders a table row option as a tr element.
156
- */
157
- private renderOutputTableRow(rowId: number): string {
158
- const rowOption = this.references.options[rowId];
159
- if (rowOption?.meta.tableRole !== "row") return "";
160
- if (!this.isDisplayed(rowId)) return "";
161
-
162
- const cells = rowOption.options
163
- .map((cellId) => this.renderOutputTableCell(cellId))
164
- .join("");
165
-
166
- if (cells.length === 0) return "";
167
-
168
- return `<tr>${cells}</tr>`;
169
- }
170
-
171
- /**
172
- * Renders a table option as a table element from its child rows.
173
- */
174
- private renderOutputTable(option: OptionV3): string {
175
- const rows = option.options
176
- .map((rowId) => this.renderOutputTableRow(rowId))
177
- .filter((row) => row.length > 0)
178
- .join("");
179
-
180
- if (rows.length === 0) return "";
181
-
182
- const styleAttr = buildOutputTableStyleAttr(option.meta.tableStyle);
183
- return `<table${styleAttr}><tbody>${rows}</tbody></table>`;
184
- }
185
-
186
- /**
187
- * Renders the current option output when displayed.
188
- */
189
47
  private renderOption() {
190
48
  const option = this.references.options[this.id];
191
- const { tableRole } = option.meta;
192
-
193
- if (tableRole === "row" || tableRole === "cell") {
194
- return;
195
- }
196
-
197
- if (!this.isDisplayed()) return;
49
+ const conditionObject = this.references.conditions.options[this.id];
50
+ const condition =
51
+ conditionObject === undefined
52
+ ? true
53
+ : this.conditions.executeCondition(
54
+ conditionObject,
55
+ this.id,
56
+ this.index,
57
+ "options"
58
+ );
59
+
60
+ // Rendering documents sections
61
+ if (condition === true) {
62
+ if (
63
+ typeof option.meta.output === "string" &&
64
+ option.meta.output.trim().length > 0
65
+ ) {
66
+ // output Reference
67
+ const outputReference = this.references.outputs[this.id];
68
+
69
+ // Autonums
70
+ let autonums: [string, string, number, string | null][] = [];
71
+ if (condition !== false) {
72
+ autonums = outputReference.autonums.map((currentAn) => {
73
+ const fn =
74
+ currentAn[0] === "numauto-pre"
75
+ ? this.NumAuto.getNumPre
76
+ : this.NumAuto.getNum;
77
+ return [
78
+ currentAn[0],
79
+ currentAn[1],
80
+ fn(currentAn[1], currentAn[2]),
81
+ currentAn[2],
82
+ ];
83
+ });
84
+ }
198
85
 
199
- if (tableRole === "table") {
200
- const tableHtml = this.renderOutputTable(option);
201
- if (tableHtml.length > 0) {
202
- this.outputs.push(cleanupRenderedOutputHtml(tableHtml));
86
+ // variables
87
+ const variables = getRelatedVariablesValues(
88
+ this.id,
89
+ this.index,
90
+ outputReference.variables,
91
+ this.ovc,
92
+ this.references
93
+ );
94
+ let output = parseOutputWithVariables(
95
+ option.meta.output,
96
+ this.index,
97
+ variables,
98
+ autonums
99
+ );
100
+
101
+ // Removing spaces between tags
102
+ output = output.replace(/<\/([a-z]+)>([\n\s]+)</gi, "</$1><");
103
+
104
+ // Replacing \n with line-break
105
+ output = output.replace(/\n/g, "<br />");
106
+
107
+ // Cleaning \xA0
108
+ output = output.replace(/\xA0/g, " ");
109
+
110
+ // Pushing output
111
+ this.outputs.push(output);
203
112
  }
204
- return;
113
+ this.renderChildren();
205
114
  }
206
-
207
- if (
208
- typeof option.meta.output === "string" &&
209
- option.meta.output.trim().length > 0
210
- ) {
211
- const outputReference = this.references.outputs[this.id];
212
-
213
- const autonums: [string, string, number, string | null][] =
214
- outputReference.autonums.map((currentAn) => {
215
- const fn =
216
- currentAn[0] === "numauto-pre"
217
- ? this.NumAuto.getNumPre
218
- : this.NumAuto.getNum;
219
- return [
220
- currentAn[0],
221
- currentAn[1],
222
- fn(currentAn[1], currentAn[2]),
223
- currentAn[2],
224
- ];
225
- });
226
-
227
- const variables = getRelatedVariablesValues(
228
- this.id,
229
- this.index,
230
- outputReference.variables,
231
- this.ovc,
232
- this.references
233
- );
234
-
235
- let output = expandOutputTableMarkup({
236
- output: option.meta.output,
237
- references: this.references,
238
- ovc: this.ovc,
239
- conditions: this.conditions,
240
- contextOptionId: this.id,
241
- defaultOccurrenceIndex: this.index,
242
- });
243
-
244
- output = parseOutputWithVariables(
245
- output,
246
- this.index,
247
- variables,
248
- autonums
249
- );
250
-
251
- output = replaceOutputTableMarkers(output, (tableId) => {
252
- const childOption = this.references.options[tableId];
253
- if (!childOption || childOption.meta.tableRole !== "table") return "";
254
- if (!this.isDisplayed(tableId)) return "";
255
- return this.renderOutputTable(childOption);
256
- });
257
-
258
- output = stripOutputTableMarkers(output);
259
- output = cleanupRenderedOutputHtml(output);
260
-
261
- this.outputs.push(output);
262
- }
263
- this.renderChildren();
264
115
  }
265
116
 
266
- /**
267
- * Recursively renders child options.
268
- */
269
117
  private renderChildren() {
270
118
  const option = this.references.options[this.id];
271
119
  option.options.forEach((optionId) => {
272
- const childOption = this.references.options[optionId];
273
- if (childOption?.meta.tableRole === "table") return;
274
-
275
120
  this.outputs = [
276
121
  ...this.outputs,
277
122
  ...new OptionRender({
@@ -199,33 +199,20 @@ export const parseOutputWithVariables: parseOutputT = (
199
199
  };
200
200
 
201
201
  /**
202
- * Parses document name with variables.
203
- * When `index` is provided, each `[var:X]` is resolved using the value
204
- * at occurrence `index` instead of always using occurrence 0. This is
205
- * required for document multiples where each occurrence of the document
206
- * must have its own filename interpolated with the right variable values.
207
- *
208
- * @param text Document's name (or filenameTemplate)
209
- * @param ovc Inputs (parallel arrays)
210
- * @param references References parsed from the model
211
- * @param fallback Fallback used when an interpolated variable is empty
212
- * @param index Occurrence index (defaults to 0)
202
+ * Parses document name with variables
203
+ * @param text Document's name
213
204
  */
214
205
  type TParseDocumentNameWithVariables = (
215
206
  text: string,
216
207
  ovc: InputsType,
217
208
  references: Types.ReferencesType,
218
- fallback?: string,
219
- index?: number,
220
- contextOptionId?: number
209
+ fallback?: string
221
210
  ) => string;
222
211
  export const parseDocumentNameWithVariables: TParseDocumentNameWithVariables = (
223
212
  text,
224
213
  ovc,
225
214
  references,
226
- fallback,
227
- index = 0,
228
- contextOptionId
215
+ fallback
229
216
  ) => {
230
217
  /**
231
218
  * Variables
@@ -252,30 +239,12 @@ export const parseDocumentNameWithVariables: TParseDocumentNameWithVariables = (
252
239
  (c) => ovc !== undefined || references.variables[c] !== undefined
253
240
  );
254
241
 
242
+ // Getting values
255
243
  const variablesValues: Record<string, string> = {};
256
- if (typeof contextOptionId === "number") {
257
- const relatedValues = getRelatedVariablesValues(
258
- contextOptionId,
259
- index,
260
- variables,
261
- ovc,
262
- references
263
- );
264
- variables.forEach((variableId) => {
265
- variablesValues[variableId] = (
266
- relatedValues[variableId] ?? ""
267
- ).toString();
268
- });
269
- } else {
270
- variables.forEach((variableId) => {
271
- const variableValues = ovc.variables[variableId];
272
- const valueAtIndex =
273
- variableValues?.[index] !== undefined
274
- ? variableValues[index]
275
- : variableValues?.[0] || "";
276
- variablesValues[variableId] = (valueAtIndex ?? "").toString();
277
- });
278
- }
244
+ variables.forEach((variableId) => {
245
+ const value = ovc.variables[variableId]?.[0] || "";
246
+ variablesValues[variableId] = value.toString();
247
+ });
279
248
 
280
249
  // Inserting values
281
250
  let parsedText = `${text}`;
@@ -1,11 +1,8 @@
1
1
  import type { ModelV3 } from "@legalplace/models-v3-types";
2
+ import type { Types } from "@legalplace/referencesparser";
3
+ import { ReferencesParser } from "@legalplace/referencesparser";
2
4
  import ConditionsRunner from "@legalplace/conditions-runner";
3
5
  import OvcConverter from "@legalplace/ovc-converter";
4
- import {
5
- ReferencesParser,
6
- type OutputRegionRole,
7
- type Types,
8
- } from "@legalplace/referencesparser";
9
6
  import type OvcType from "@legalplace/types/dist/ovc";
10
7
  import type InputsType from "@legalplace/types/dist/inputs";
11
8
  import crypto from "crypto";
@@ -19,7 +16,6 @@ import type {
19
16
  DocumentIndex,
20
17
  DocumentOutputIndex,
21
18
  DocumentPdfFormIndex,
22
- DocumentLayout,
23
19
  } from "../types/types";
24
20
 
25
21
  class Prerender {
@@ -92,178 +88,67 @@ class Prerender {
92
88
  }
93
89
 
94
90
  const index = this.documentsIndex[documentNameHash];
95
- const occurrenceIndex = index.occurrenceIndex ?? 0;
96
91
 
97
92
  this.renderedDocuments[documentNameHash] = Prerender.isPdfForm(index)
98
- ? this.pdfFiller.fillDocument(index.slug, occurrenceIndex)
99
- : this.documentRender.renderDocument(index.slug, occurrenceIndex);
93
+ ? this.pdfFiller.fillDocument(index.slug)
94
+ : this.documentRender.renderDocument(index.slug);
100
95
 
101
96
  return this.renderedDocuments[documentNameHash];
102
97
  }
103
98
 
104
- /**
105
- * Returns rendered header/footer regions and page layout settings for DOCX.
106
- */
107
- public getDocumentLayout(documentNameHash: string): DocumentLayout {
108
- const index = this.documentsIndex[documentNameHash];
109
- const occurrenceIndex = index.occurrenceIndex ?? 0;
110
- const document = this.references.documents[index.slug];
111
- const layoutParams = document.params?.layout;
112
-
113
- const renderRegion = (role: OutputRegionRole): string | undefined => {
114
- const html = this.documentRender.renderRegion(
115
- index.slug,
116
- role,
117
- occurrenceIndex
118
- );
119
- return html.trim().length > 0 ? html : undefined;
120
- };
121
-
122
- const header = renderRegion("header");
123
- const footer = renderRegion("footer");
124
- const differentFirstPage = layoutParams?.differentFirstPage === true;
125
-
126
- return {
127
- header,
128
- footer,
129
- firstPageHeader: differentFirstPage
130
- ? renderRegion("firstPageHeader")
131
- : undefined,
132
- firstPageFooter: differentFirstPage
133
- ? renderRegion("firstPageFooter")
134
- : undefined,
135
- differentFirstPage,
136
- margins: layoutParams?.margins,
137
- headerDistance: layoutParams?.headerDistance,
138
- footerDistance: layoutParams?.footerDistance,
139
- };
140
- }
141
-
142
- /**
143
- * Resolves a document occurrence's display name. Interpolates `[var:X]`
144
- * in `document.name` at the linked occurrence. Optional `filenameTemplate`
145
- * overrides the title when set and non-empty after interpolation.
146
- */
147
- private resolveDocumentOccurrenceName(
148
- document: Types.ReferencesDocumentType,
149
- docMultiple: NonNullable<
150
- NonNullable<Types.ReferencesDocumentType["params"]>["multiple"]
151
- >,
152
- occurrenceIndex: number
153
- ): string {
154
- const baseName = parseDocumentNameWithVariables(
155
- document.name,
156
- this.ovc,
157
- this.references,
158
- document.fallbackName,
159
- occurrenceIndex,
160
- docMultiple.repeatOption
161
- );
162
-
163
- const template = docMultiple.filenameTemplate;
164
- if (typeof template !== "string" || template.trim().length === 0) {
165
- return baseName;
166
- }
167
-
168
- const resolved = parseDocumentNameWithVariables(
169
- template,
170
- this.ovc,
171
- this.references,
172
- undefined,
173
- occurrenceIndex,
174
- docMultiple.repeatOption
175
- );
176
-
177
- if (resolved.trim().length === 0) return baseName;
178
- return resolved;
179
- }
180
-
181
- /**
182
- * Returns the array of occurrences (true = render this occurrence) for a
183
- * given document slug. For non-multiple documents, returns `[true]`.
184
- * For multiple documents, returns the parallel array of the source option
185
- * (`params.multiple.repeatOption`), defaulting to `[true]` if absent.
186
- */
187
- private getDocumentOccurrences(slug: string): boolean[] {
188
- const document = this.references.documents[slug];
189
- const docMultiple = document.params?.multiple;
190
- if (docMultiple?.enabled !== true) return [true];
191
-
192
- const { repeatOption } = docMultiple;
193
- if (typeof repeatOption !== "number") return [true];
194
-
195
- const inputs = this.ovc.options[String(repeatOption)];
196
- if (!Array.isArray(inputs) || inputs.length === 0) return [true];
197
- return inputs;
198
- }
199
-
200
99
  private generateDocumentNamesHash() {
201
100
  Object.keys(this.references.documents).forEach((slug) => {
202
101
  const document = this.references.documents[slug];
203
- const documentParams = document.params;
204
- const docMultiple = documentParams?.multiple;
205
- const isMultipleDocument = docMultiple?.enabled === true;
206
-
207
- const occurrences = this.getDocumentOccurrences(slug);
208
-
209
- occurrences.forEach((occurrenceValue, occurrenceIndex) => {
210
- if (occurrenceValue !== true) return;
211
-
212
- const conditionObject = this.references.conditions.documents[slug];
213
- const condition =
214
- conditionObject === undefined
215
- ? true
216
- : this.conditions.executeCondition(
217
- conditionObject,
218
- 0,
219
- occurrenceIndex,
220
- "documents"
221
- );
222
-
223
- if (condition !== true) return;
224
-
225
- const seed = isMultipleDocument ? `${slug}:${occurrenceIndex}` : slug;
226
- const hash = crypto.createHash("sha1").update(seed).digest("hex");
227
-
228
- const name =
229
- isMultipleDocument && docMultiple
230
- ? this.resolveDocumentOccurrenceName(
231
- document,
232
- docMultiple,
233
- occurrenceIndex
234
- )
235
- : parseDocumentNameWithVariables(
236
- document.name,
237
- this.ovc,
238
- this.references,
239
- document.fallbackName
240
- );
241
-
102
+ const documentParams = this.references.documents[slug].params;
103
+ const conditionObject = this.references.conditions.documents[slug];
104
+ const condition =
105
+ conditionObject === undefined
106
+ ? true
107
+ : this.conditions.executeCondition(
108
+ conditionObject,
109
+ 0,
110
+ 0,
111
+ "documents"
112
+ );
113
+
114
+ // Rendering documents sections
115
+ if (condition === true) {
116
+ const hash = crypto.createHash("sha1").update(slug).digest("hex");
117
+
118
+ // Referencing in index
242
119
  if (
243
120
  typeof document.pdf === "object" &&
244
121
  typeof document.pdf.id === "string" &&
245
122
  typeof document.pdf.form === "object"
246
123
  ) {
247
124
  this.documentsIndex[hash] = {
248
- name,
125
+ name: parseDocumentNameWithVariables(
126
+ document.name,
127
+ this.ovc,
128
+ this.references,
129
+ document.fallbackName
130
+ ),
249
131
  slug,
250
132
  form: true,
251
133
  fdf: {
252
134
  id: "",
253
135
  fields: {},
254
136
  },
255
- ...(isMultipleDocument ? { occurrenceIndex } : {}),
256
137
  };
257
138
  } else {
258
139
  this.documentsIndex[hash] = {
259
- name,
140
+ name: parseDocumentNameWithVariables(
141
+ document.name,
142
+ this.ovc,
143
+ this.references,
144
+ document.fallbackName
145
+ ),
260
146
  slug,
261
147
  pdf: documentParams?.formats?.pdf !== false,
262
148
  docx: documentParams?.formats?.docx !== false,
263
- ...(isMultipleDocument ? { occurrenceIndex } : {}),
264
149
  };
265
150
  }
266
- });
151
+ }
267
152
  });
268
153
  }
269
154
 
@@ -279,15 +164,15 @@ class Prerender {
279
164
  private generateAllDocuments() {
280
165
  Object.keys(this.documentsIndex).forEach((documentNameHash) => {
281
166
  const index = this.documentsIndex[documentNameHash];
282
- const occurrenceIndex = index.occurrenceIndex ?? 0;
283
167
  if (Prerender.isPdfForm(index)) {
284
168
  this.renderedDocuments[documentNameHash] = this.pdfFiller.fillDocument(
285
- index.slug,
286
- occurrenceIndex
169
+ this.documentsIndex[documentNameHash].slug
287
170
  );
288
171
  } else {
289
172
  this.renderedDocuments[documentNameHash] =
290
- this.documentRender.renderDocument(index.slug, occurrenceIndex);
173
+ this.documentRender.renderDocument(
174
+ this.documentsIndex[documentNameHash].slug
175
+ );
291
176
  }
292
177
  });
293
178
  }