diff --git a/docs/MANUALE_UTENTE_NIS2_AGILE.docx b/docs/MANUALE_UTENTE_NIS2_AGILE.docx new file mode 100644 index 0000000..bb9bffa Binary files /dev/null and b/docs/MANUALE_UTENTE_NIS2_AGILE.docx differ diff --git a/scripts/genera-manuale-utente.py b/scripts/genera-manuale-utente.py new file mode 100644 index 0000000..6a3cc05 --- /dev/null +++ b/scripts/genera-manuale-utente.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Genera il Manuale d'uso di NIS2 Agile in .docx usando solo stdlib (zipfile+XML).""" +import zipfile, re, datetime +from xml.sax.saxutils import escape + +OUT = "/projects/nis2-agile/docs/MANUALE_UTENTE_NIS2_AGILE.docx" + +# ───────────────────────────────────────────────────────────────────────── +# CONTENUTO DEL MANUALE (DSL: lista di blocchi) +# ('title', titolo, sottotitolo, riga_meta) +# ('h1'|'h2'|'h3', testo) | ('p', testo) | ('note', testo) +# ('ul', [voci]) | ('ol', [voci]) +# ('table', [intestazioni], [[riga],...]) +# ('pb',) page break +# Nel testo: **grassetto** supportato. +# ───────────────────────────────────────────────────────────────────────── +DATE = "11 giugno 2026" +C = [] +def add(*b): C.append(b) + +add('title', "NIS2 Agile", "Manuale d'uso del prodotto", + "Versione piattaforma 1.14.0 · Aggiornato all'%s" % DATE) + +# ===== INDICE (statico, i numeri di pagina non sono calcolati) ===== +add('h1', "Indice dei contenuti") +for n in ["1. Introduzione","2. Primi passi (accesso, registrazione, onboarding)", + "3. Dashboard","4. Gap Analysis NIS2 (Art. 21)","5. Gap Analysis ACN (Determinazione 164179/2025)", + "6. Modello Organizzativo SGSI (ISO 27001/27017/27018)","7. Gestione dei Rischi", + "8. Gestione degli Incidenti (Art. 23)","9. Policy e procedure","10. Supply Chain (fornitori)", + "11. Formazione","12. Asset","13. Audit & Report","14. Whistleblowing (Art. 32)", + "15. Aggiornamenti normativi","16. Knowledge Base e Assistente AI","17. Segnalazioni (feedback)", + "18. Impostazioni","19. Amministrazione","20. Integrazioni e API","21. Glossario","22. Riferimenti normativi"]: + add('p', n) +add('pb') + +# ===== 1. INTRODUZIONE ===== +add('h1', "1. Introduzione") +add('h2', "1.1 Che cos'è NIS2 Agile") +add('p', "NIS2 Agile è una piattaforma SaaS multi-tenant che supporta le aziende nel percorso di conformità alla **Direttiva (UE) 2022/2555 (NIS2)** e al suo recepimento italiano (**D.Lgs. 138/2024**), comprese le **Determinazioni dell'Agenzia per la Cybersicurezza Nazionale (ACN)**. Integra l'intelligenza artificiale (Claude) per gap analysis, generazione di policy, classificazione degli incidenti e suggerimento dei rischi.") +add('p', "Dalla versione 1.14.0 la piattaforma include anche un modulo guidato per la costruzione del **Modello Organizzativo SGSI secondo ISO/IEC 27001:2022**, con estensioni cloud ISO/IEC 27017:2015 e 27018:2019.") +add('h2', "1.2 A chi è rivolto") +add('ul', ["**PMI** che devono adeguarsi agli obblighi NIS2 con risorse limitate;", + "**Grandi imprese / Enterprise** con processi di compliance strutturati;", + "**Consulenti e CISO** che gestiscono più organizzazioni clienti tramite uno studio (consulting firm)."]) +add('h2', "1.3 Quadro normativo di riferimento") +add('table', ["Fonte","Ruolo"], + [["Direttiva (UE) 2022/2555 (NIS2)","Principi e obblighi europei di cibersicurezza"], + ["D.Lgs. 138/2024","Recepimento italiano della NIS2 (obblighi operativi)"], + ["Determinazione ACN 164179/2025","Misure di sicurezza di base, classificazione e notifica incidenti (Allegati)"], + ["ISO/IEC 27001:2022","Requisiti del Sistema di Gestione della Sicurezza delle Informazioni (best practice)"], + ["ISO/IEC 27017:2015 / 27018:2019","Controlli per servizi cloud / protezione PII in cloud (best practice)"]]) +add('note', "Disclaimer: NIS2 Agile è uno strumento di supporto operativo e pre-audit. Non costituisce consulenza legale né parere professionale vincolante, e non rilascia certificazioni ISO. Le valutazioni proposte (perimetro, applicabilità, significatività) vanno confermate con il referente compliance/legale dell'organizzazione.") + +# ===== 2. PRIMI PASSI ===== +add('pb'); add('h1', "2. Primi passi") +add('h2', "2.1 Accesso e indirizzo") +add('p', "La piattaforma è raggiungibile all'indirizzo **https://nis2.agile.software/**. L'accesso avviene con email e password (autenticazione JWT). Se la tua organizzazione usa il Single Sign-On centralizzato (AgileHub), le credenziali sono condivise tra i prodotti della suite.") +add('h2', "2.2 Registrazione") +add('ol', ["Apri la pagina di **Registrazione** e inserisci i dati richiesti (email, password conforme alla policy di sicurezza).", + "Al termine vieni indirizzato automaticamente alla procedura di **onboarding** aziendale.", + "Se possiedi un **invito/licenza** (token `inv_...`) fornito da un partner, inseriscilo per attivare l'organizzazione pre-configurata."]) +add('h2', "2.3 Recupero password") +add('p', "Dalla pagina di login usa **Password dimenticata** per ricevere un link di reset via email. Il cambio password effettuato da AgileHub si propaga automaticamente entro pochi minuti grazie al sync SSO.") +add('h2', "2.4 Onboarding dell'organizzazione") +add('p', "Il wizard di onboarding crea l'organizzazione e ne determina la classificazione NIS2. Si articola in quattro passi:") +add('ol', ["**Dati azienda** — inserimento manuale oppure precompilazione tramite **Partita IVA** (lookup automatico) o caricamento della **visura camerale** (estrazione AI dei dati).", + "**Verifica e completamento** — settore (ATECO → settore NIS2), numero dipendenti, fatturato, sede, contatti.", + "**Profilo utente** — nome, ruolo, telefono.", + "**Classificazione NIS2** — la piattaforma propone se il soggetto è **essenziale**, **importante** o **non applicabile** in base a settore e dimensione; è possibile aderire volontariamente."]) +add('note', "La classificazione (essenziale/importante) determina il perimetro applicabile in più moduli, in particolare la Gap Analysis ACN e gli obblighi sugli incidenti. Puoi rivederla in Impostazioni › Classificazione NIS2.") +add('h2', "2.5 Ruoli e permessi") +add('table', ["Ruolo","Descrizione"], + [["super_admin","Amministratore di piattaforma (accesso a tutte le organizzazioni)"], + ["org_admin","Amministratore dell'organizzazione"], + ["compliance_manager","Responsabile compliance: gestisce assessment, rischi, policy, SGSI"], + ["board_member","Organo direttivo: visione e approvazioni"], + ["auditor","Revisore: lettura e compilazione evidenze/controlli"], + ["employee","Dipendente: formazione, segnalazioni"], + ["consultant","Consulente di studio che opera su più organizzazioni clienti"]]) +add('h2', "2.6 Multi-organizzazione, lingua e aiuto") +add('ul', ["Chi appartiene a più organizzazioni (es. consulenti) può **cambiare contesto** dall'intestazione/menu.", + "L'interfaccia è disponibile in **Italiano** ed **Inglese** (selettore lingua).", + "Ogni pagina dispone di **Aiuto contestuale** (icona ?) con guida e riferimenti normativi certi."]) + +# ===== 3. DASHBOARD ===== +add('pb'); add('h1', "3. Dashboard") +add('p', "La Dashboard è la pagina iniziale dopo l'accesso e offre una visione d'insieme dello stato di conformità.") +add('ul', ["**Avanzamento implementazione misure Art. 21 NIS2** — indicatore complessivo (gauge);", + "**Scadenze imminenti** — adempimenti e revisioni in arrivo da più moduli;", + "**Attività recenti** — ultime azioni tracciate;", + "**Heatmap dei rischi** — distribuzione dei rischi su probabilità × impatto;", + "**Benchmark settoriale** (anonimizzato) — confronto con il proprio settore;", + "**Compliance Journey** — percorso guidato che indica i prossimi passi consigliati."]) + +# ===== 4. GAP ANALYSIS NIS2 ===== +add('pb'); add('h1', "4. Gap Analysis NIS2 (Art. 21)") +add('p', "Analisi di **primo livello** sulle 10 categorie di misure dell'Art. 21.2 NIS2, basata su un questionario di 80 domande (10 categorie), ciascuna collegata anche al controllo ISO 27001 corrispondente.") +add('h2', "4.1 Come si svolge") +add('ol', ["Crea un nuovo assessment: la piattaforma pre-popola tutte le domande.", + "Per ogni domanda indica lo stato: **Attuato**, **Parziale**, **Non attuato**, **Non applicabile**. Le risposte si salvano automaticamente.", + "Completa l'assessment per ottenere un **punteggio** per categoria e complessivo.", + "Avvia l'**analisi AI** per ricevere sintesi esecutiva, priorità, quick win e una roadmap di conformità."]) +add('note', "Le risposte della Gap Analysis NIS2 alimentano automaticamente il Modello SGSI (capitolo 6): il loro stato pre-popola lo Statement of Applicability ISO 27001.") + +# ===== 5. GAP ANALYSIS ACN ===== +add('pb'); add('h1', "5. Gap Analysis ACN (Determinazione 164179/2025)") +add('p', "Analisi di **secondo livello** sulle misure e i requisiti puntuali stabiliti dall'ACN, articolati secondo il **Framework Nazionale** (funzioni Governo GV, Identificazione ID, Protezione PR, Rilevamento DE, Risposta RS, Ripristino RC).") +add('table', ["Classificazione","Perimetro applicabile"], + [["Soggetto IMPORTANTE (Allegato 1)","37 misure / 87 requisiti"], + ["Soggetto ESSENZIALE (Allegato 2)","43 misure / 116 requisiti"]]) +add('ul', ["Il perimetro è filtrato automaticamente in base alla classificazione NIS2 dell'organizzazione;", + "scoring per funzione del Framework Nazionale e punteggio complessivo;", + "**piano d'azione** dei requisiti non conformi e **analisi AI** ancorata ai 203 requisiti ACN reali (nessun riferimento inventato);", + "**Catalogo requisiti** consultabile in sola lettura."]) + +# ===== 6. MODELLO SGSI ISO 27001 (NUOVO) ===== +add('pb'); add('h1', "6. Modello Organizzativo SGSI (ISO 27001/27017/27018)") +add('p', "Procedura guidata per costruire il **Sistema di Gestione della Sicurezza delle Informazioni (SGSI / ISMS)** secondo **ISO/IEC 27001:2022** (clausole 4–10) e il relativo **Statement of Applicability (SoA)** sui 93 controlli dell'Annex A. È uno strumento di supporto e pre-audit, non una certificazione.") +add('h2', "6.1 Il wizard in 6 passi") +add('table', ["Passo","Clausola ISO 27001","Contenuto"], + [["1. Contesto e Ambito","cl. 4","Scope del SGSI, contesto interno/esterno, parti interessate, confini, esclusioni motivate, attivazione estensioni cloud"], + ["2. Leadership","cl. 5","Ruoli e responsabilità con matrice RACI (Responsible, Accountable, Consulted, Informed)"], + ["3. Risk","cl. 6","Metodologia di risk assessment e obiettivi del SGSI (il registro rischi resta nel modulo Rischi)"], + ["4. Statement of Applicability","cl. 6.1.3","Applicabilità, stato e motivazione dei 93 controlli Annex A + estensioni cloud"], + ["5. Documented Information","cl. 7–8","Policy e procedure, con bozze generabili dall'AI"], + ["6. Monitoraggio e Miglioramento","cl. 9–10","Audit interni e non conformità (moduli Audit & Report e NCR/CAPA); checklist di completamento"]]) +add('h2', "6.2 SoA pre-popolato dalla Gap Analysis NIS2") +add('p', "Ogni domanda della Gap Analysis NIS2 è collegata a un controllo ISO 27001 (es. A.5.1). Per questo lo Statement of Applicability viene **pre-compilato** a partire dalle risposte NIS2 già date: i controlli con corrispondenza riportano il badge **\"da NIS2\"** e uno stato proposto (Attuato = 100%, Parziale = 50%, Non attuato = 0%). I controlli non coperti dal NIS2 (es. parte dei controlli fisici A.7) restano da compilare manualmente.") +add('note', "Il pulsante \"Ri-deriva dal NIS2\" aggiunge soltanto i controlli mancanti e non sovrascrive le modifiche manuali già inserite.") +add('h2', "6.3 Estensioni cloud (condizionali)") +add('ul', ["**ISO/IEC 27017:2015** — attivala se usi servizi cloud pubblici o sei un fornitore cloud: aggiunge 7 controlli cloud (CLD.*) al SoA;", + "**ISO/IEC 27018:2019** — attivala se tratti dati personali (PII) in cloud pubblico come responsabile: aggiunge i controlli privacy (PII.*), sinergici con il GDPR.", + "Le sezioni cloud/PII compaiono nel SoA **solo** se attivi i relativi flag nel passo 1."]) +add('h2', "6.4 Documenti e generazione AI") +add('p', "Nel passo 5 puoi generare bozze di policy e procedure (Policy SGSI, Dichiarazione di Applicabilità, controllo accessi, gestione incidenti, continuità, sicurezza fornitori). Le bozze sono prodotte dall'AI con ancoraggio alle fonti certe e richiedono **revisione umana** prima dell'approvazione.") +add('h2', "6.5 Esportazione") +add('p', "Il pulsante **Esporta SGSI** apre una versione stampabile del modello (ambito, metodologia, SoA con stato dei controlli), utile come evidenza in vista di un audit.") + +# ===== 7. RISCHI ===== +add('pb'); add('h1', "7. Gestione dei Rischi") +add('p', "Registro dei rischi con matrice **Probabilità × Impatto 5×5** e supporto AI, conforme all'Art. 21 NIS2 e alla metodologia ISO 27005.") +add('ul', ["Creazione del rischio con scala probabilità/impatto (dettagli ISO 27005);", + "**Piani di trattamento** (mitigare, accettare, trasferire, evitare) e stato di avanzamento;", + "**Suggerimento AI** dei rischi per settore e asset (dati anonimizzati);", + "Funzioni avanzate: registro quantitativo **FAIR** (calcolo ALE Monte Carlo) e dashboard **KRI**."]) + +# ===== 8. INCIDENTI ===== +add('pb'); add('h1', "8. Gestione degli Incidenti (Art. 23)") +add('p', "Gestione del ciclo di vita degli incidenti significativi e degli obblighi di notifica al CSIRT secondo l'Art. 23 e la Determinazione ACN 164179/2025.") +add('h2', "8.1 Tempistiche di notifica") +add('table', ["Adempimento","Termine"], + [["Pre-allarme (early warning)","entro 24 ore dalla conoscenza"], + ["Notifica completa","entro 72 ore dalla conoscenza"], + ["Relazione finale","entro 1 mese dalla notifica delle 72 ore"]]) +add('h2', "8.2 Funzioni") +add('ul', ["**Albero decisionale** per valutare la significatività (utenti ≥ 500, durata > 4h, cross-border, attacco cyber, danni > €100k);", + "**timeline** dell'incidente e metriche (triage, contenimento, eradicazione, ripristino);", + "invio di **early warning, notifica e relazione finale** al CSIRT;", + "**classificazione AI** (tipo IS-1..IS-4, severità) con citazione della fonte (Allegato 3 = importanti, Allegato 4 = essenziali);", + "**PIR** (Post-Incident Review) per l'apprendimento."]) + +# ===== 9. POLICY ===== +add('pb'); add('h1', "9. Policy e procedure") +add('ul', ["Creazione di policy da **template** o **generazione AI** (bozze marcate da revisionare);", + "flusso di **approvazione** e **versioning** con confronto delle versioni (diff);", + "**attestazione** di presa visione da parte del personale e relativo tracciamento."]) +add('note', "Le bozze generate dall'AI riportano un banner di avviso: vanno riviste e approvate da un responsabile prima della pubblicazione.") + +# ===== 10. SUPPLY CHAIN ===== +add('pb'); add('h1', "10. Supply Chain (fornitori)") +add('p', "Gestione della sicurezza della catena di approvvigionamento (Art. 21.2.d).") +add('ul', ["Anagrafica fornitori, categorie e **import** massivo;", + "**questionari** configurabili (categorie, template, domande) inviati ai fornitori;", + "**portale fornitore** dedicato con accesso separato via OTP/magic-link (autosalvataggio risposte);", + "**campagne** di valutazione e cruscotto del rischio fornitori (risk overview)."]) + +# ===== 11. FORMAZIONE ===== +add('pb'); add('h1', "11. Formazione") +add('p', "Gestione della formazione e dell'igiene informatica (Art. 21.2.g) e degli obblighi formativi per gli organi direttivi (Art. 20).") +add('ul', ["Catalogo **corsi** e **assegnazioni** al personale;", + "monitoraggio della **compliance formativa** e dei corsi obbligatori non completati."]) + +# ===== 12. ASSET ===== +add('pb'); add('h1', "12. Asset") +add('p', "Inventario degli asset informativi e delle dipendenze.") +add('ul', ["Anagrafica asset e **import** (CMDB/cloud);", + "**scoring di rilevanza NIS2** (0–100, criterio GV.OC-04) e classificazione dei sistemi rilevanti;", + "**mappa delle dipendenze** tra asset."]) + +# ===== 13. AUDIT & REPORT ===== +add('pb'); add('h1', "13. Audit & Report") +add('ul', ["Gestione dei **controlli di conformità** (framework NIS2 / ISO 27001) con stato ed evidenze;", + "caricamento **evidenze** e registro dei **log di audit**;", + "**mappature**: NIS2 ↔ ISO 27001 e NIST CSF 2.0; **registro dei sistemi rilevanti** (GV.OC-04);", + "**report esecutivo** stampabile ed **export CSV**;", + "**catena di integrità (hash chain SHA-256)**: ogni voce del log è concatenata alla precedente; verifica integrità ed **export certificato**."]) + +# ===== 14. WHISTLEBLOWING ===== +add('pb'); add('h1', "14. Whistleblowing (Art. 32)") +add('p', "Canale per le segnalazioni, anche anonime.") +add('ul', ["Invio segnalazione (anche **anonima**, con codice di tracciamento);", + "assegnazione, gestione e chiusura della segnalazione;", + "consultazione anonima dello stato tramite codice di tracciamento."]) + +# ===== 15. NORMATIVE ===== +add('pb'); add('h1', "15. Aggiornamenti normativi") +add('ul', ["Feed degli aggiornamenti **NIS2 / ACN / DORA**;", + "presa visione (**ACK**) tracciata per ciascun aggiornamento;", + "elenco aggiornamenti **in attesa** di presa visione e statistiche."]) + +# ===== 16. KB & AI ===== +add('pb'); add('h1', "16. Knowledge Base e Assistente AI") +add('h2', "16.1 Knowledge Base (RAG)") +add('p', "La Knowledge Base alimenta le risposte dell'AI tramite ricerca semantica (RAG). Prevede tre livelli di visibilità:") +add('table', ["Livello","Chi possiede","Chi vede"], + [["SYSTEM","Fornitore della piattaforma","Tutti gli utenti"], + ["FIRM","Studio di consulenza","Collaboratori dello studio + organizzazioni condivise"], + ["ORG","Singola organizzazione","Solo gli utenti di quella organizzazione"]]) +add('ul', ["Caricamento documenti (testo) con scelta dello scope;", + "ricerca semantica e cancellazione documenti;", + "le fonti normative certe (NIS2, D.Lgs. 138/2024, Determinazioni ACN) e i cataloghi **ISO 27001/27002/27017/27018** sono già indicizzati a livello SYSTEM."]) +add('h2', "16.2 Assistente AI") +add('p', "L'assistente risponde alle domande pescando dalla Knowledge Base e **cita sempre la fonte normativa pertinente**, senza inventare riferimenti. Le risposte sono di orientamento operativo e non sostituiscono il parere legale.") + +# ===== 17. FEEDBACK ===== +add('pb'); add('h1', "17. Segnalazioni (feedback)") +add('p', "Strumento integrato per segnalare bug o suggerimenti di UX. Le segnalazioni vengono classificate dall'AI e, una volta risolte, l'esito può essere comunicato ai membri dell'organizzazione. Ogni segnalazione cattura automaticamente la **versione del prodotto** e la **superficie** (web/mobile) per facilitare la diagnosi.") + +# ===== 18. IMPOSTAZIONI ===== +add('pb'); add('h1', "18. Impostazioni") +add('ul', ["**Organizzazione** — dati, classificazione NIS2;", + "**Profilo** — dati personali, cambio password, preferenze;", + "**Membri** — invito e gestione degli utenti e dei ruoli;", + "**Sessioni** — gestione dei dispositivi collegati (multi-device) e revoca;", + "**Branding** (white-label per studi) — personalizzazione per lo studio di consulenza."]) + +# ===== 19. AMMINISTRAZIONE ===== +add('pb'); add('h1', "19. Amministrazione") +add('p', "Area riservata al ruolo **super_admin**: gestione delle organizzazioni, degli utenti e statistiche di piattaforma.") + +# ===== 20. INTEGRAZIONI E API ===== +add('pb'); add('h1', "20. Integrazioni e API") +add('ul', ["**Services API** (autenticazione tramite API Key con scope) per integrazioni esterne, es. GRC/231: gap-analysis, misure, incidenti, formazione, scadenze e snapshot completo;", + "**Webhook** con firma HMAC-SHA256 e retry, per notifiche verso sistemi terzi;", + "**Licenze/inviti B2B** per provisioning di organizzazioni da parte di partner;", + "**SSO** centralizzato della suite AgileHub (sincronizzazione password)."]) + +# ===== 21. GLOSSARIO ===== +add('pb'); add('h1', "21. Glossario") +add('table', ["Termine","Significato"], + [["NIS2","Direttiva (UE) 2022/2555 sulla cibersicurezza"], + ["ACN","Agenzia per la Cybersicurezza Nazionale"], + ["SGSI / ISMS","Sistema di Gestione della Sicurezza delle Informazioni (ISO 27001)"], + ["SoA","Statement of Applicability: dichiarazione di applicabilità dei controlli"], + ["Annex A","Elenco dei 93 controlli di sicurezza di ISO/IEC 27001:2022"], + ["RACI","Matrice di responsabilità: Responsible, Accountable, Consulted, Informed"], + ["CSIRT","Computer Security Incident Response Team (autorità di notifica)"], + ["PIR","Post-Incident Review (revisione post-incidente)"], + ["RAG","Retrieval-Augmented Generation: AI con recupero da knowledge base"], + ["PII","Personally Identifiable Information (dati personali)"], + ["Soggetto essenziale / importante","Categorie NIS2 che determinano gli obblighi applicabili"]]) + +# ===== 22. RIFERIMENTI NORMATIVI ===== +add('pb'); add('h1', "22. Riferimenti normativi") +add('ul', ["Direttiva (UE) 2022/2555 (NIS2);", + "Direttiva (UE) 2022/2557 (CER);", + "D.Lgs. 4 settembre 2024, n. 138;", + "Determinazione ACN n. 164179 del 14/04/2025 (misure di base, classificazione e notifica incidenti, Allegati);", + "Determinazione ACN n. 333017/2025 (piattaforma ACN, designazione rappresentanti);", + "ISO/IEC 27001:2022, 27002:2022, 27017:2015, 27018:2019 (best practice, non vincolanti)."]) +add('note', "Questo manuale descrive le funzionalità della piattaforma NIS2 Agile alla versione 1.14.0. Le schermate e le voci di menu possono variare con gli aggiornamenti. Per l'aiuto contestuale usa l'icona ? presente in ogni pagina.") + +# ───────────────────────────────────────────────────────────────────────── +# EMITTER OOXML +# ───────────────────────────────────────────────────────────────────────── +def runs(text): + """Converte **bold** in run WordprocessingML.""" + out = [] + for i, part in enumerate(re.split(r'\*\*', text)): + if part == '': + continue + bold = (i % 2 == 1) + rpr = '' if bold else '' + out.append('%s%s' % (rpr, escape(part))) + return ''.join(out) or '' + +def para(text, style=None): + ppr = '' % style if style else '' + return '%s%s' % (ppr, runs(text)) + +def listitem(text, numid): + return ('' + '%s' % (numid, runs(text))) + +def cell(text, header=False): + shd = '' if header else '' + t = ('**%s**' % text) if header else text + # nelle intestazioni il testo bianco + if header: + r = '%s' % escape(text) + else: + r = runs(text) + return ('%s%s' % (shd, r)) + +def table(headers, rows): + borders = ('' + ''.join( + '' % s + for s in ['top','left','bottom','right','insideH','insideV']) + '') + tblpr = '%s' % borders + out = ['', tblpr] + out.append('' + ''.join(cell(h, True) for h in headers) + '') + for row in rows: + out.append('' + ''.join(cell(c) for c in row) + '') + out.append('') + out.append('') # spacing after table + return ''.join(out) + +def title_block(t, sub, meta): + return (para(t, 'Title') + para(sub, 'Subtitle') + para(meta, 'Meta')) + +body = [] +for b in C: + k = b[0] + if k == 'title': body.append(title_block(b[1], b[2], b[3])) + elif k == 'h1': body.append(para(b[1], 'Heading1')) + elif k == 'h2': body.append(para(b[1], 'Heading2')) + elif k == 'h3': body.append(para(b[1], 'Heading3')) + elif k == 'p': body.append(para(b[1])) + elif k == 'note': body.append(para(b[1], 'Note')) + elif k == 'ul': body.append(''.join(listitem(x, 1) for x in b[1])) + elif k == 'ol': body.append(''.join(listitem(x, 2) for x in b[1])) + elif k == 'table': body.append(table(b[1], b[2])) + elif k == 'pb': body.append('') + +SECT = ('' + '' + '') + +document = ('' + '' + '' + ''.join(body) + SECT + '') + +STYLES = ('' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '') + +NUMBERING = ('' + '' + '' + '' + '' + '' + '') + +CONTENT_TYPES = ('' + '' + '' + '' + '' + '' + '' + '' + '') + +RELS = ('' + '' + '' + '' + '') + +DOC_RELS = ('' + '' + '' + '' + '') + +CORE = ('' + '' + 'Manuale d\'uso NIS2 AgileNIS2 Agile' + 'NIS2 Agile' + '2026-06-11T00:00:00Z' + '2026-06-11T00:00:00Z' + '') + +with zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED) as z: + z.writestr('[Content_Types].xml', CONTENT_TYPES) + z.writestr('_rels/.rels', RELS) + z.writestr('word/document.xml', document) + z.writestr('word/styles.xml', STYLES) + z.writestr('word/numbering.xml', NUMBERING) + z.writestr('word/_rels/document.xml.rels', DOC_RELS) + z.writestr('docProps/core.xml', CORE) + +print("OK ->", OUT) +print("blocchi:", len(C))