1/* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 * LE DOCUMENT CRÃA â une seule représentation, trois formes, quatre modes 3 * ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 * 5 * « L'unité est le SUJET, pas le fichier. » 6 * 7 * Un plan d'investissement se lit, se présente et se calcule. Ce sont trois 8 * FORMES du même sujet, pas trois documents. Ce fichier tient la chose dont 9 * les trois formes sont des rendus â et rien d'autre. 10 * 11 * ââ CE QUI EXISTAIT, ET POURQUOI ÃA NE SUFFISAIT PLUS âââââââââââââââââââââ 12 * 13 * Le document vivait en Markdown DANS le message (`src/crea-doc.js`), et 14 * l'export le relisait à chaque fois. Deux chemins parallèles vers le même 15 * livrable : `crea-doc.js` fabrique un `.doc` qui est du HTML avec un en-tête 16 * Word, pendant que la fiche `docx` de `server/crea-skills-place.js` dit en 17 * toutes lettres « jamais du HTML renommé ». Le Markdown dans un message ne 18 * peut pas non plus s'ouvrir depuis un modèle, ni se rendre en diaporama sans 19 * être réécrit. 20 * 21 * ââ LA RÃGLE QUI TIENT CE FICHIER PETIT âââââââââââââââââââââââââââââââââââ 22 * 23 * UN TYPE DE BLOC N'ENTRE QUE SI AU MOINS DEUX FORMES SAVENT LE RENDRE. 24 * 25 * Sans elle, ce modèle grossit à chaque demande et redevient Word â sept 26 * cents notions dont personne ne se sert, et un aller-retour qui perd la 27 * moitié de ce qu'il transporte à chaque passage. 28 * 29 * Elle mord tout de suite, et c'est le signe qu'elle sert : 30 * · `code` ne rentre pas â seule la forme texte le rend. Il appartient au 31 * Studio Code, et un document qui embarque du code exécutable n'est plus 32 * un document. 33 * · les notes d'orateur ne rentrent pas â seule la diapo les rend. Ce n'est 34 * pas un type de bloc, c'est un DÃTAIL DE RENDU de la forme diapo, dérivé 35 * des paragraphes de la section. 36 * 37 * Elle n'est pas une intention : `RENDUS` la déclare, et 38 * `tests/document-crea-smoke.mjs` la vérifie dans les deux sens sur la table 39 * exécutée â un type qu'aucune forme ne rend, comme une forme qui prétend 40 * rendre un type qui n'existe pas, font échouer la garde. 41 * 42 * ââ CE QUE CE FICHIER N'EST PAS âââââââââââââââââââââââââââââââââââââââââââ 43 * 44 * Ce n'est pas un éditeur, ni une bouche. Il ne compose aucun prompt, ne 45 * porte aucune identité, et ne sait pas dans quel studio il est. Il tient une 46 * structure et ses rendus â c'est tout son poste. 47 * ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 48 49/* ââ LES HUIT TYPES, ET CE QUE CHACUN PORTE âââââââââââââââââââââââââââââââ 50 * 51 * Huit, parce que huit ont passé la règle des deux formes. Ce n'est pas un 52 * chiffre choisi : c'est ce qui reste. */ 53const BLOCS_CREA = { 54 titre: { champs: ['texte'] }, 55 section: { champs: ['texte', 'niveau'] }, 56 paragraphe: { champs: ['texte'] }, 57 liste: { champs: ['items', 'ordonnee'] }, 58 tableau: { champs: ['entetes', 'lignes'] }, 59 image: { champs: ['source', 'alt'] }, 60 encadre: { champs: ['texte', 'intitule'] }, 61 kpi: { champs: ['libelle', 'valeur', 'cible', 'unite'] }, 62} 63 64/* ââ CE QUE CHAQUE FORME SAIT RENDRE ââââââââââââââââââââââââââââââââââââââ 65 * 66 * Cette table est la RÃGLE ELLE-MÃME, pas son commentaire. La garde la lit, 67 * compte les formes par type, et refuse un type qui n'en aurait qu'une. 68 * 69 * Un type absent d'une forme n'est pas une perte silencieuse : le rendu le 70 * laisse tomber sciemment, et c'est écrit ici. Une image n'a pas de place 71 * dans un tableur, un KPI n'a pas de place dans un paragraphe suivi. */ 72const RENDUS_CREA = { 73 texte: ['titre', 'section', 'paragraphe', 'liste', 'tableau', 'image', 'encadre', 'kpi'], 74 diapo: ['titre', 'section', 'paragraphe', 'liste', 'tableau', 'image', 'encadre', 'kpi'], 75 tableur: ['titre', 'section', 'tableau', 'kpi'], 76} 77 78/* ââ LECTURE DU MARKDOWN ââââââââââââââââââââââââââââââââââââââââââââââââââ 79 * 80 * Le mode 1 fait écrire Créa en Markdown, et c'est le contrat de sortie du 81 * Studio Documents. Cette porte-là doit donc rester ouverte : le document
82 * naît d'un Markdown, et sait y retourner sans se déformer. 83 * 84 * ON NE RÃ-IMPLÃMENTE PAS UN PARSEUR MARKDOWN COMPLET. On lit les sept 85 * formes que le contrat de sortie promet â titre, parties, paragraphes, 86 * listes, tableaux, images, encadrés â et rien de plus. Ce qui n'est pas 87 * reconnu devient un paragraphe : on ne perd pas de texte, on perd du 88 * balisage, et c'est le bon sens de la perte. */ 89function documentDepuisMarkdown(md) { 90 const lignes = String(md == null ? '' : md).replace(/\r\n?/g, '\n').split('\n') 91 const doc = { titre: '', blocs: [] } 92 let i = 0 93 94 const estTableau = (l, suivante) => 95 /^\s*\|.*\|\s*$/.test(l) && /^\s*\|[\s:|-]+\|\s*$/.test(suivante || '') 96 97 const cellules = (l) => l.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(c => c.trim()) 98 99 while (i < lignes.length) { 100 const l = lignes[i] 101 102 if (!l.trim()) { i++; continue } 103 104 // # titre â le premier seulement ; les suivants deviennent des sections, 105 // parce qu'un document n'a qu'un titre et que Word non plus n'en a qu'un. 106 const h = l.match(/^(#{1,6})\s+(.*)$/) 107 if (h) { 108 const niveau = h[1].length 109 const texte = h[2].trim() 110 if (niveau === 1 && !doc.titre) doc.titre = texte 111 else doc.blocs.push({ type: 'section', texte: texte, niveau: Math.max(2, niveau) }) 112 i++; continue 113 } 114 115 // | tableau | 116 if (estTableau(l, lignes[i + 1])) { 117 const entetes = cellules(l) 118 i += 2 119 const corps = [] 120 while (i < lignes.length && /^\s*\|.*\|\s*$/.test(lignes[i])) { corps.push(cellules(lignes[i])); i++ } 121 doc.blocs.push({ type: 'tableau', entetes: entetes, lignes: corps }) 122 continue 123 } 124 125 //  126 const img = l.match(/^\s*!\[([^\]]*)\]\(([^)]+)\)\s*$/) 127 if (img) { doc.blocs.push({ type: 'image', alt: img[1], source: img[2] }); i++; continue } 128 129 // > encadré â les lignes consécutives forment UN bloc. Un « **Intitulé â** » 130 // en tête devient l'intitulé : c'est la forme que le contrat de sortie du 131 // Studio Documents produit pour les hypothèses. 132 if (/^\s*>\s?/.test(l)) { 133 const parts = [] 134 while (i < lignes.length && /^\s*>\s?/.test(lignes[i])) { parts.push(lignes[i].replace(/^\s*>\s?/, '')); i++ } 135 let texte = parts.join(' ').trim() 136 let intitule = '' 137 const tete = texte.match(/^\*\*([^*]+)\*\*\s*[â:-]\s*(.*)$/) 138 if (tete) { intitule = tete[1].trim(); texte = tete[2].trim() } 139 doc.blocs.push({ type: 'encadre', intitule: intitule, texte: texte }) 140 continue 141 } 142 143 // - liste / 1. liste 144 if (/^\s*([-*+]|\d+\.)\s+/.test(l)) { 145 const ordonnee = /^\s*\d+\.\s+/.test(l) 146 const items = [] 147 while (i < lignes.length && /^\s*([-*+]|\d+\.)\s+/.test(lignes[i])) { 148 items.push(lignes[i].replace(/^\s*([-*+]|\d+\.)\s+/, '').trim()); i++ 149 } 150 doc.blocs.push({ type: 'liste', items: items, ordonnee: ordonnee }) 151 continue 152 } 153 154 // paragraphe â les lignes consécutives se recollent, comme en Markdown. 155 const parts = [] 156 while (i < lignes.length && lignes[i].trim() && 157 !/^(#{1,6})\s/.test(lignes[i]) && !/^\s*>/.test(lignes[i]) && 158 !/^\s*([-*+]|\d+\.)\s+/.test(lignes[i]) && !/^\s*\|/.test(lignes[i]) && 159 !/^\s*!\[/.test(lignes[i])) { parts.push(lignes[i].trim()); i++ } 160 if (parts.length) doc.blocs.push({ type: 'paragraphe', texte: parts.join(' ') }) 161 else i++ 162 } 163 164 return doc 165} 166 167/* ââ RETOUR AU MARKDOWN âââââââââââââââââââââââââââââââââââââââââââââââââââ 168 * 169 * L'ALLER-RETOUR EST CE QUI SE MESURE. Un modèle de document se juge sur ce 170 * qui survit à un tour complet, pas sur ce qu'il sait représenter. La garde 171 * rejoue `markdown â document â markdown â document` et exige que les DEUX 172 * documents soient identiques : c'est la seule preuve que rien ne se dégrade 173 * à chaque passage. */ 174function documentVersMarkdown(doc) { 175 const d = doc || {} 176 const out = [] 177 if (d.titre) out.push('# ' + d.titre) 178 ;
178(d.blocs || []).forEach(function (b) { 179 if (b.type === 'section') out.push('#'.repeat(Math.max(2, b.niveau || 2)) + ' ' + b.texte) 180 else if (b.type === 'paragraphe') out.push(b.texte) 181 else if (b.type === 'liste') out.push((b.items || []).map(function (it, n) { 182 return (b.ordonnee ? (n + 1) + '. ' : '- ') + it 183 }).join('\n')) 184 else if (b.type === 'tableau') { 185 const e = b.entetes || [] 186 out.push('| ' + e.join(' | ') + ' |\n|' + e.map(function () { return ' --- ' }).join('|') + '|\n' + 187 (b.lignes || []).map(function (r) { return '| ' + r.join(' | ') + ' |' }).join('\n')) 188 } 189 else if (b.type === 'image') out.push(' + ')') 190 else if (b.type === 'encadre') out.push('> ' + (b.intitule ? '**' + b.intitule + '** â ' : '') + b.texte) 191 else if (b.type === 'kpi') out.push('> **' + b.libelle + '** â ' + b.valeur + (b.unite ? ' ' + b.unite : '') + 192 (b.cible ? ' (cible ' + b.cible + ')' : '')) 193 }) 194 return out.join('\n\n') 195} 196 197/* ââ FORME TEXTE ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 198 * Le document tel qu'on le lit et qu'on l'imprime. */ 199function documentVersHtml(doc) { 200 const d = doc || {} 201 const e = function (s) { 202 return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { 203 return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c] 204 }) 205 } 206 /* ââ L'EMPHASE EN LIGNE ââââââââââââââââââââââââââââââââââââââââââââââââ 207 * MESURà à L'ÃCRAN, sur un vrai plan d'investissement : « Le point mort est 208 * atteint au **mois 19** » s'affichait avec ses quatre astérisques. Toutes 209 * les formes d'emphase d'un document â un chiffre qu'on souligne, un nom de 210 * mois, un terme défini â sortaient en clair. Seul l'encadré s'en tirait, 211 * parce que son intitulé est extrait par la STRUCTURE et non par le style. 212 * 213 * ÃCHAPPER D'ABORD, BALISER ENSUITE, ET DANS CET ORDRE : `e()` transforme le 214 * `<` de l'usager en `<` ; les balises posées après ne peuvent donc plus 215 * être confondues avec du texte, et rien de ce qu'on tape ne devient du 216 * balisage. L'inverse â baliser puis échapper â rendrait les `<strong>` en 217 * clair et laisserait passer le reste. 218 * 219 * TROIS FORMES, PAS PLUS : le gras, l'italique et le code. Ce ne sont pas 220 * trois choix de style â ce sont les trois que le Markdown des contrats de 221 * sortie produit. Une quatrième serait une syntaxe que rien n'écrit. */ 222 const enLigne = function (s) { 223 return e(s) 224 .replace(/`([^`]+)`/g, '<code>$1</code>') 225 /* PAS D'ESPACE CONTRE LE DÃLIMITEUR â c'est la règle du Markdown, et 226 ici elle protège un cas qui arrive tout le temps dans ces documents : 227 « 2 340 h * 42 ⬠» est une MULTIPLICATION. Sans cette contrainte, tout 228 ce qui sépare deux astérisques dans un calcul passait en italique, et 229 les astérisques disparaissaient â le lecteur ne voyait plus l'opération. 230 Mesuré sur « 2 * 3 = 6 et 4 * 5 = 20 ». */ 231 .replace(/\*\*([^\s*][^*\n]*?[^\s*]|[^\s*])\*\*/g, '<strong>$1</strong>') 232 .replace(/(^|[^*\w])\*([^\s*][^*\n]*?[^\s*]|[^\s*])\*(?!\*)/g, '$1<em>$2</em>') 233 } 234 const out = [] 235 if (d.titre) out.push('<h1>' + enLigne(d.titre) + '</h1>') 236 ;(d.blocs || []).forEach(function (b) { 237 if (b.type === 'section') out.push('<h' + Math.min(6, b.niveau || 2) + '>' + enLigne(b.texte) + '</h' + Math.min(6, b.niveau || 2) + '>') 238 else if (b.type === 'paragraphe') out.push('<p>' + enLigne(b.texte) + '</p>') 239 else if (b.type === 'liste') { 240 const t = b.ordonnee ? 'ol' : 'ul' 241 out.push('<' + t + '>' + (b.items || []).map(function (i) { return '<li>' + enLigne(i) + '</li>' }).join('') + '</' + t + '>') 242 } 243 else if (b.type === 'tableau') out.push('<table><thead><tr>' + 244 (b.entetes || []).map(function (h) { return '<th>' + e(h) + '</th>' }).join('') + '</tr></thead><tbody>' + 245 (b.lignes || []).map(function (r) { return '<tr>' + r.map(function (c) { return '<td>' + enLigne(c) + '</td>' }).join('') + '</tr>' }).join('') + 246 '</tbody></table>') 247 else if (b.type === 'image') out.push('<img src="' + e(b.source) + '" alt="' + e(b.alt) + '">') 248 else if (b.type === 'encadre') out.push('<blockquote>' + (b.intitule ? '<strong>' + enLigne(b.intitule) + '</strong> â ' : '') + enLigne(b.texte) + '</blockquote>') 249 else if (b.type === 'kpi') out.push('<p class="crea-kpi"><strong>' + e(b.libelle) + '</strong> ' + e(b.valeur) + 250 (b.unite ? ' ' + e(b.unite) : '') + (b.cible ? ' <span>cible ' + e(b.cible) + '</span>' : '') + '</p>') 251 }) 252 return out.join('\n') 253} 254 255/* ââ FORME DIAPO ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 256 * 257 * UNE SECTION FAIT UNE DIAPO. C'est la règle la plus simple qui donne un 258 * découpage juste : l'auteur a déjà décidé où se coupent les idées en posant 259 * ses parties, et redécouper autrement reviendrait à le contredire. 260 * 261 * Rend le contrat que `detectStudioArtifact` relit déjà â `{title, theme, 262 * slides:[â¦]}`. Les notes d'orateur sont DÃRIVÃES des paragraphes de la 263 * section : elles n'existent pas dans le modèle, parce qu'une seule forme les 264 * rendrait. */ 265function documentVersDiapos(doc) { 266 const d = doc || {} 267 const slides = [] 268 let n = 1 269 slides.push({ id: n++, layout: 'title', title: d.titre || 'Document', subtitle: '', notes: '' }) 270 271 let courante = null 272 const pousser = function () { if (courante) { slides.push(courante); courante = null } } 273 274 ;
274(d.blocs || []).forEach(function (b) { 275 if (b.type === 'section') { 276 pousser() 277 courante = { id: n++, layout: 'content', title: b.texte, bullets: [], notes: '' } 278 return 279 } 280 if (!courante) courante = { id: n++, layout: 'content', title: d.titre || '', bullets: [], notes: '' } 281 282 if (b.type === 'liste') (b.items || []).forEach(function (i) { courante.bullets.push(i) }) 283 else if (b.type === 'encadre') courante.bullets.push((b.intitule ? b.intitule + ' â ' : '') + b.texte) 284 else if (b.type === 'kpi') courante.bullets.push(b.libelle + ' : ' + b.valeur + (b.unite ? ' ' + b.unite : '')) 285 else if (b.type === 'tableau') (b.lignes || []).forEach(function (r) { courante.bullets.push(r.join(' · ')) }) 286 else if (b.type === 'image') courante.image = b.source 287 // LE PARAGRAPHE DESCEND AUX NOTES, PAS AUX PUCES. Un paragraphe projeté 288 // en puce donne la diapo qu'on lit à voix haute pendant que la salle la 289 // lit aussi â le défaut le plus commun d'un diaporama. 290 else if (b.type === 'paragraphe') courante.notes = (courante.notes ? courante.notes + ' ' : '') + b.texte 291 }) 292 pousser() 293 return { title: d.titre || 'Document', theme: 'dark', slides: slides } 294} 295 296/* ââ FORME TABLEUR ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 297 * 298 * UN TABLEAU FAIT UNE FEUILLE, nommée par la section qui le précède. Les KPI 299 * se rassemblent à part et ressortent dans le contrat `{kpis:[â¦]}` que 300 * `detectStudioArtifact` relit déjà . 301 * 302 * Le reste est laissé, sciemment : un paragraphe n'a pas de cellule, et le 303 * loger en A1 ferait un tableur qu'on ne peut ni calculer ni relire. */ 304function documentVersTableur(doc) { 305 const d = doc || {} 306 const feuilles = [] 307 const kpis = [] 308 let nom = '' 309 let rang = 1 310 311 ;(d.blocs || []).forEach(function (b) { 312 if (b.type === 'section') { nom = b.texte; return } 313 if (b.type === 'kpi') { 314 kpis.push({ label: b.libelle, value: b.valeur, target: b.cible || '', unit: b.unite || '', trend: 'flat' }) 315 return 316 } 317 if (b.type === 'tableau') { 318 feuilles.push({ nom: nom || ('Feuille ' + rang++), entetes: b.entetes || [], lignes: b.lignes || [] }) 319 } 320 }) 321 322 return { title: d.titre || 'Document', feuilles: feuilles, kpis: kpis } 323} 324 325/* ââ LA DÃRIVATION : UN DOCUMENT DEVIENT UN MODÃLE ââââââââââââââââââââââââ 326 * 327 * « Un document produit peut-il devenir un modèle ? » â c'était la question qui 328 * séparait un outil qui apprend de son usage d'un outil qu'il faut nourrir à la 329 * main. Voici la réponse mécanique. 330 * 331 * CE N'EST NI UNE FORME NI UN MODE. On ne rend pas le sujet autrement : on en 332 * RETIRE ce qui appartient à ce cas-là , et on garde ce qui servira au suivant. 333 * La structure et la prose survivent, les chiffres deviennent des blancs. 334 * 335 * Le contrat de sortie du Studio Documents décrit déjà ce qu'est un modèle : 336 * « les variables entre crochets â [NOM], [DATE], [SOCIÃTÃ] â laissées telles 337 * quelles ». Cette fonction produit exactement cette forme-là . 338 * 339 * ââ CE QU'ELLE REMPLACE, ET C'EST TOUT ââââââââââââââââââââââââââââââââââââ 340 * 341 * Ce qui se détecte SANS DEVINER : les montants, les pourcentages, les années 342 * et les dates. Un nombre est un nombre â on ne se trompe pas. 343 * 344 * ââ CE QU'ELLE NE DÃTECTE PAS, ET IL FAUT LE DIRE âââââââââââââââââââââââââ 345 * 346 * LES NOMS. Ni la société, ni la personne, ni le lieu, ni le client. Aucune 347 * règle mécanique ne distingue « Atelier Marin » d'un mot ordinaire, et 348 * prétendre le contraire serait pire que de ne rien faire : on croirait le 349 * modèle anonyme. 350 * 351 * Ãa compte parce qu'un modèle se PUBLIE â sur la place de marché, pour 352 * quelqu'un d'autre. Un modèle dérivé d'un vrai document peut encore porter le 353 * nom d'un client. Celui qui le publie doit le relire, et l'interface doit le 354 * lui dire avant. `tests/document-crea-smoke.mjs` tient cette limite : elle est 355 * vérifiée, pas seulement écrite ici. */ 356const VARIABLES_CREA = [ 357 // Un montant : le symbole ou le code décide, jamais le nombre seul. 358 { quoi: /\d[\d .,]*\s*(?:â¬|\$|£|EUR|USD|kâ¬|Kâ¬|Mâ¬)/g, par: '[MONTANT]' }, 359 // Un pourcentage. 360 { quoi: /\d+(?:[.,]\d+)?\s*%/g, par: '[POURCENTAGE]' }, 361 // Une date écrite en chiffres. 362 { quoi: /\b\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4}\b/g, par: '[DATE]' }, 363 // Une année seule, dans la fourchette où c'en est une. 364 { quoi: /\b(?:19|20)\d{2}\b/g, par: '[ANNÃE]' }, 365] 366 367function texteEnModele(t) { 368 let out = String(t == null ? '' : t) 369 VARIABLES_CREA.forEach(function (v) { out = out.replace(v.quoi, v.par) }) 370 return out 371} 372 373function documentVersModele(doc) { 374 const d = doc || {} 375 return { 376 titre: texteEnModele(d.titre), 377 blocs: (d.blocs || []).map(function (b) { 378 const n = Object.assign({}, b) 379 if (typeof n.texte === 'string') n.texte = texteEnModele(n.texte) 380 if (typeof n.intitule === 'string') n.intitule = texteEnModele(n.intitule) 381 if (Array.isArray(n.items)) n.items = n.items.map(texteEnModele) 382 if (Array.isArray(n.entetes)) n.entetes = n.entetes.map(texteEnModele) 383 if (Array.isArray(n.lignes)) n.lignes = n.lignes.map(function (r) { return r.map(texteEnModele) }) 384 if (typeof n.valeur === 'string') n.valeur = texteEnModele(n.valeur) 385 if (typeof n.cible === 'string') n.cible = texteEnModele(n.cible) 386 return n 387 }), 388 } 389} 390 391/* ââ LES PONTS VERS LES VRAIS FORMATS ââââââââââââââââââââââââââââââââââââââ 392 * 393 * CE QUI S'EST PASSÃ, ET IL FAUT L'ÃCRIRE : ce poste a été construit à la main 394 * â huit types de blocs, un modèle, trois rendus â pendant qu'un pont vers les 395 * VRAIES bibliothèques dormait à côté, sur le serveur. `server/crea-lm-docs.js` 396 * utilise `docx`, `exceljs`, `pdfkit` et `pptxgenjs` ;
396 elles sont installées, 397 * elles fabriquent de vrais fichiers Office, et ce modèle ne leur parlait pas. 398 * Mesuré : zéro référence dans les deux sens. 399 * 400 * C'est exactement ce que le dépôt appelle un canal parallèle, et il est né de 401 * n'avoir pas cherché ce qui existait avant d'écrire. 402 * 403 * CES TROIS FONCTIONS NE FABRIQUENT RIEN. Elles traduisent le document dans la 404 * forme que chaque fabricant attend, et s'arrêtent là . Le `.docx` est fait par 405 * `docx`, le `.xlsx` par `exceljs`, le `.pptx` par `pptxgenjs` â pas ici. Si un 406 * jour l'une d'elles se met à écrire du XML, c'est que le canal parallèle est 407 * revenu. 408 * 409 * CE QU'ELLES PERDENT, ET LE DIRE VAUT MIEUX QUE LE DÃCOUVRIR : le pont est 410 * plus pauvre que le modèle. `creerDocx` ne connaît que `{titre, contenu}` â 411 * donc un tableau y descend en texte, et un encadré perd son filet. Ce n'est 412 * pas une limite du document, c'est une limite du pont, et c'est le pont qu'il 413 * faudra enrichir â pas ce fichier. 414 */ 415 416/* Le texte d'un bloc, aplati pour un pont qui ne connaît que des lignes. */ 417function blocEnTexte(b) { 418 if (b.type === 'paragraphe') return b.texte || '' 419 if (b.type === 'liste') return (b.items || []).map(function (i) { return '⢠' + i }).join('\n') 420 if (b.type === 'encadre') return (b.intitule ? b.intitule + ' â ' : '') + (b.texte || '') 421 if (b.type === 'kpi') return b.libelle + ' : ' + b.valeur + (b.unite ? ' ' + b.unite : '') + 422 (b.cible ? ' (cible ' + b.cible + ')' : '') 423 if (b.type === 'tableau') { 424 /* UN TABLEAU DESCEND EN LIGNES DE TEXTE, et c'est une PERTE. Le pont Word 425 n'a pas de tableau ; le taire donnerait un document où des chiffres ont 426 disparu sans trace. La forme tableur, elle, le garde entier. */ 427 const l = [(b.entetes || []).join(' | ')] 428 for (const r of (b.lignes || [])) l.push(r.join(' | ')) 429 return l.join('\n') 430 } 431 return b.texte || '' 432} 433 434/* Vers `creerDocx` et `creerPdf` â tous deux attendent `{titre, sections}`, 435 * une section étant `{titre, contenu}`. Les blocs se regroupent sous leur 436 * section ; ce qui précède la première va dans une section sans titre. */ 437function documentVersSections(doc) { 438 const d = doc || {} 439 const sections = [] 440 let cour = { titre: '', contenu: [] } 441 for (const b of (d.blocs || [])) { 442 if (b.type === 'section') { 443 if (cour.titre || cour.contenu.length) sections.push(cour) 444 cour = { titre: b.texte || '', contenu: [] } 445 } else { 446 const t = blocEnTexte(b) 447 if (t) cour.contenu.push(t) 448 } 449 } 450 if (cour.titre || cour.contenu.length) sections.push(cour) 451 return { 452 titre: d.titre || 'Document', 453 sections: sections.map(function (s) { return { titre: s.titre, contenu: s.contenu.join('\n\n') } }), 454 } 455} 456 457/* Vers `creerXlsx`. Il attend `{nom, entetes, donnees}` ; `versTableur` produit 458 * déjà `{nom, entetes, lignes}` â on ne recalcule donc rien, on renomme le 459 * champ. Les indicateurs partent dans leur propre feuille : les mêler aux 460 * tableaux ferait une feuille qui ne se lit ni comme l'un ni comme l'autre. */ 461function documentVersXlsx(doc) { 462 const t = documentVersTableur(doc) 463 const feuilles = t.feuilles.map(function (f) { 464 return { nom: f.nom.slice(0, 31), entetes: f.entetes, donnees: f.lignes } 465 }) 466 if (t.kpis.length) { 467 feuilles.unshift({ 468 nom: 'Indicateurs', 469 entetes: ['Indicateur', 'Valeur', 'Unité', 'Cible'], 470 donnees: t.kpis.map(function (k) { return [k.label, k.value, k.unit || '', k.target || ''] }), 471 }) 472 } 473 return { titre: (doc || {}).titre || 'Classeur', feuilles: feuilles } 474} 475 476/* Vers `creerPptx`. Il attend `{titre, puces, notes}` ; `versDiapos` produit 477 * `{title, bullets, notes}` â on renomme, on ne recalcule pas. 478 * 479 * LE PONT A ÃTà ENRICHI POUR ÃA, ET C'EST LE BON SENS DE LA CORRECTION : il 480 * jetait les notes d'orateur, faute de champ. La tentation était de les faire 481 * descendre dans `contenu` â c'est-à -dire SUR la diapo. C'eût été défaire 482 * exactement ce que `versDiapos` protège : la diapo qu'on lit à voix haute 483 * pendant que la salle la lit aussi. `pptxgenjs` porte `addNotes` nativement ; 484 * c'est le pont qui manquait d'une ligne, pas le modèle qui devait plier. */ 485function documentVersPptx(doc) { 486 const d = documentVersDiapos(doc) 487 const out = [] 488 for (const s of (d.slides || [])) { 489 const puces = s.bullets || [] 490 const notes = s.notes || '' 491 /* LA DIAPO DE TITRE NE PASSE PAS : `creerPptx` fabrique la sienne, et la 492 laisser passer en ferait deux à la suite. On la reconnaît à ce qu'elle 493 est â le titre du document, sans puce ni note. */ 494 if (!puces.length && !notes) continue 495 out.push({ titre: s.title || '', puces: puces, notes: notes }) 496 } 497 return { titre: d.title || 'Présentation', diapositives: out } 498} 499 500/* Le poste s'expose au navigateur sans se supposer dedans : la garde 501 * l'exécute dans Node, où `window` n'existe pas. */ 502const DOCUMENT_CREA = { 503 BLOCS: BLOCS_CREA, 504 RENDUS: RENDUS_CREA, 505 depuisMarkdown: documentDepuisMarkdown, 506 versMarkdown: documentVersMarkdown, 507 versHtml: documentVersHtml, 508 versDiapos: documentVersDiapos, 509 versTableur: documentVersTableur, 510 versModele: documentVersModele, 511 /* LES PONTS. Ils traduisent, ils ne fabriquent pas : `docx`, `exceljs` et 512 `pptxgenjs` font les fichiers, sur le serveur. */ 513 versSections: documentVersSections, 514 versXlsx: documentVersXlsx, 515 versPptx: documentVersPptx, 516 VARIABLES: VARIABLES_CREA, 517} 518if (typeof window !== 'undefined') window.DOCUMENT_CREA = DOCUMENT_CREA
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.