Riscrittura completa: istruzioni concrete (login, registrazione 3 step, onboarding 4 step con campi reali, navigazione), per ogni modulo "cosa trovi e come si usa" estratto dall'help contestuale del prodotto (help.js), FAQ, glossario. Rimosse le interruzioni di pagina che lasciavano pagine vuote. Generatore self-contained (estrae i contenuti da public/js/help.js via node). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
321 lines
27 KiB
Python
321 lines
27 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""Manuale d'uso COMPLETO di NIS2 Agile (.docx, solo stdlib). Contenuti moduli da help.js reale."""
|
||
import zipfile, re, json, html as _html, subprocess, os
|
||
from xml.sax.saxutils import escape
|
||
|
||
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # repo root
|
||
OUT = os.path.join(BASE, "docs", "MANUALE_UTENTE_NIS2_AGILE.docx")
|
||
HELP_JSON = "/tmp/help.json"
|
||
# Auto-estrae i contenuti dall'help reale del prodotto (public/js/help.js) via node.
|
||
_node = ('const fs=require("fs");let s=fs.readFileSync(process.argv[1],"utf8");'
|
||
'const a=s.indexOf("var _helpContent");const start=s.indexOf("{",a);'
|
||
'const end=s.indexOf("var _pageMap");let obj=s.slice(start,s.lastIndexOf("}",end)+1);'
|
||
'fs.writeFileSync(process.argv[2],JSON.stringify(eval("("+obj+")")));')
|
||
subprocess.run(["node","-e",_node, os.path.join(BASE,"public","js","help.js"), HELP_JSON], check=True)
|
||
HELP = json.load(open(HELP_JSON, encoding="utf-8"))
|
||
DATE = "11 giugno 2026"
|
||
|
||
C = []
|
||
def add(*b): C.append(b)
|
||
|
||
def html2mk(s):
|
||
"""Converte l'HTML dell'help nel markup interno (**bold**) + testo pulito."""
|
||
s = re.sub(r'</?strong>|</?b>', '**', s)
|
||
s = re.sub(r'<[^>]+>', '', s) # rimuovi altri tag (span/em/...)
|
||
s = _html.unescape(s) # < ≥ ...
|
||
s = s.replace(' ', ' ')
|
||
s = re.sub(r'\s+', ' ', s).strip()
|
||
# bilancia eventuali ** dispari
|
||
if s.count('**') % 2: s += '**'
|
||
return s
|
||
|
||
def module_chapter(num, title, topic_key, lead=None):
|
||
t = HELP.get(topic_key, {})
|
||
add('h1', f"{num}. {title}")
|
||
if lead: add('p', lead)
|
||
if t.get('intro'): add('p', html2mk(t['intro']))
|
||
for sec in t.get('sections', []):
|
||
add('h3', html2mk(sec.get('heading','')))
|
||
items = [html2mk(x) for x in sec.get('items', [])]
|
||
if items: add('ul', items)
|
||
refs = [html2mk(x) for x in t.get('references', [])]
|
||
if refs:
|
||
add('h3', "Riferimenti normativi")
|
||
add('ul', refs)
|
||
|
||
# ══════════════════ COPERTINA ══════════════════
|
||
add('title', "NIS2 Agile", "Manuale d'uso del prodotto",
|
||
f"Guida operativa completa · Versione piattaforma 1.14.0 · {DATE}")
|
||
add('p', "Questo manuale spiega passo dopo passo come accedere alla piattaforma, registrarsi, completare l'onboarding aziendale e utilizzare ogni modulo. Per ogni schermata trovi cosa contiene, a cosa serve e come operare.")
|
||
add('pb')
|
||
|
||
# ══════════════════ INDICE ══════════════════
|
||
add('h1', "Indice")
|
||
toc = ["1. Introduzione","2. Requisiti e accesso alla piattaforma","3. Registrazione di un nuovo utente",
|
||
"4. Onboarding: configurazione dell'azienda","5. Come muoversi nell'interfaccia","6. Dashboard",
|
||
"7. Gap Analysis NIS2 (Art. 21)","8. Gap Analysis ACN (Determinazione 164179/2025)",
|
||
"9. Modello Organizzativo SGSI (ISO 27001/27017/27018)","10. Gestione dei Rischi",
|
||
"11. Gestione degli Incidenti (Art. 23)","12. Policy e procedure","13. Supply Chain (fornitori)",
|
||
"14. Formazione","15. Asset","16. Audit & Report","17. Whistleblowing (Art. 32)",
|
||
"18. Aggiornamenti normativi","19. Knowledge Base","20. Compliance Journey","21. Segnalazioni (feedback)",
|
||
"22. Impostazioni","23. Gestione delle aziende (consulenti)","24. Domande frequenti (FAQ)",
|
||
"25. Glossario","26. Riferimenti normativi"]
|
||
for x in toc: add('p', x)
|
||
add('note', "I nomi delle voci e l'aspetto delle schermate possono variare leggermente con gli aggiornamenti. In ogni pagina è disponibile il pulsante di aiuto contestuale (icona ?) con la guida e i riferimenti normativi.")
|
||
|
||
# ══════════════════ 1. INTRODUZIONE ══════════════════
|
||
add('h1', "1. Introduzione")
|
||
add('p', "**NIS2 Agile** è una piattaforma web (SaaS) che accompagna le aziende nella conformità alla **Direttiva (UE) 2022/2555 (NIS2)**, al suo recepimento italiano (**D.Lgs. 138/2024**) e alle **Determinazioni dell'Agenzia per la Cybersicurezza Nazionale (ACN)**. Integra l'intelligenza artificiale per gap analysis, generazione di policy, classificazione degli incidenti e suggerimento dei rischi.")
|
||
add('p', "Dalla versione 1.14.0 include anche il **Modello Organizzativo SGSI** secondo **ISO/IEC 27001:2022**, con estensioni cloud ISO 27017/27018.")
|
||
add('h3', "A chi è rivolto")
|
||
add('ul', ["**PMI** che devono adeguarsi agli obblighi NIS2;",
|
||
"**Grandi imprese** con processi di compliance strutturati;",
|
||
"**Consulenti e CISO** che seguono più aziende clienti tramite uno studio."])
|
||
add('h3', "Come è organizzato il lavoro nella piattaforma")
|
||
add('p', "Il percorso tipico è: **(1)** accedi o registrati → **(2)** completa l'onboarding dell'azienda (che ne determina la classificazione NIS2) → **(3)** esegui la Gap Analysis NIS2 → **(4)** approfondisci con la Gap Analysis ACN e costruisci il Modello SGSI → **(5)** gestisci rischi, incidenti, policy, fornitori, formazione e audit nel tempo.")
|
||
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 vanno confermate con il referente compliance/legale dell'organizzazione.")
|
||
|
||
# ══════════════════ 2. ACCESSO ══════════════════
|
||
add('h1', "2. Requisiti e accesso alla piattaforma")
|
||
add('h3', "Requisiti")
|
||
add('ul', ["Un browser web aggiornato (Chrome, Edge, Firefox o Safari);",
|
||
"una connessione a Internet;",
|
||
"le credenziali di accesso (email e password) oppure un invito ricevuto dal proprio studio/partner."])
|
||
add('h3', "Indirizzo della piattaforma")
|
||
add('p', "Apri il browser e vai su **https://nis2.agile.software/**. Si apre la pagina di accesso.")
|
||
add('h3', "Come accedere (login)")
|
||
add('ol', ["Nel campo **Indirizzo Email** inserisci l'email con cui sei registrato (es. nome@azienda.it).",
|
||
"Nel campo **Password** inserisci la tua password. Puoi usare l'icona a forma di occhio per mostrarla/nasconderla.",
|
||
"Premi **Accedi**.",
|
||
"Se le credenziali sono corrette: se hai già un'azienda configurata vieni portato alla **Dashboard**, altrimenti alla procedura di **Onboarding**."])
|
||
add('p', "In caso di errore compare un messaggio in rosso sotto il titolo. Verifica email e password e riprova.")
|
||
add('h3', "Password dimenticata")
|
||
add('ol', ["Dalla pagina di accesso scegli **Password dimenticata**.",
|
||
"Inserisci l'email dell'account: riceverai un messaggio con un link per impostare una nuova password.",
|
||
"Apri il link, scegli la nuova password (rispettando i requisiti di sicurezza) e conferma."])
|
||
add('note', "Se la tua azienda usa il Single Sign-On della suite (AgileHub), la password è condivisa tra i prodotti: un cambio password si propaga automaticamente entro pochi minuti.")
|
||
add('h3', "Disconnessione e sessioni")
|
||
add('p', "Per uscire usa la voce **Esci/Logout** nel menu utente. Dalle Impostazioni puoi vedere i **dispositivi collegati** (sessioni attive) e revocarli singolarmente o tutti.")
|
||
|
||
# ══════════════════ 3. REGISTRAZIONE ══════════════════
|
||
add('h1', "3. Registrazione di un nuovo utente")
|
||
add('p', "Se non hai un account, dalla pagina di accesso scegli **Registrati**. La registrazione è una procedura guidata in tre passi, con un indicatore di avanzamento in alto.")
|
||
add('h3', "Passo 1 — Ruolo e (eventuale) codice invito")
|
||
add('p', "Scegli il **ruolo** con cui ti registri, selezionando una delle schede disponibili:")
|
||
add('table', ["Scheda ruolo","Quando sceglierlo"],
|
||
[["Responsabile compliance","Gestisci direttamente assessment, rischi, policy e SGSI dell'azienda"],
|
||
["Amministratore organizzazione","Amministri l'azienda, gli utenti e le impostazioni"],
|
||
["Consulente","Segui più aziende clienti per conto di uno studio"],
|
||
["Auditor","Esegui verifiche e compili evidenze/controlli (lettura prevalente)"],
|
||
["Membro del board","Organo direttivo: visione e approvazioni"]])
|
||
add('p', "Se hai ricevuto un **Codice Invito** da un partner o dal tuo studio (formato `inv_...`), inseriscilo nell'apposito campo: il sistema lo verifica e mostra l'azienda/piano associati. Premi **Avanti** per proseguire.")
|
||
add('h3', "Passo 2 — Dati personali e password")
|
||
add('ul', ["**Nome** e **Cognome** (es. Mario Rossi);",
|
||
"**Email** (sarà il tuo identificativo di accesso);",
|
||
"eventuale **Partita IVA** dell'azienda (verificata in tempo reale, dove richiesto);",
|
||
"**Password** (minimo 8 caratteri): un indicatore mostra la robustezza della password mentre la digiti;",
|
||
"**Conferma password**: ripeti la stessa password."])
|
||
add('h3', "Passo 3 — Conferma e avvio")
|
||
add('p', "Rivedi i dati, accetta le condizioni e completa la registrazione. Al termine vieni indirizzato automaticamente all'**Onboarding** per configurare l'azienda (se non sei entrato tramite un invito che la pre-configura).")
|
||
|
||
# ══════════════════ 4. ONBOARDING ══════════════════
|
||
add('h1', "4. Onboarding: configurazione dell'azienda")
|
||
add('p', "L'onboarding crea l'organizzazione e ne stabilisce la **classificazione NIS2** (essenziale, importante o non applicabile), che determina gli obblighi e il perimetro applicabile negli altri moduli. È una procedura guidata con uno **stepper** in alto che mostra i passi.")
|
||
add('h3', "Passo 1 — Inserisci la Partita IVA")
|
||
add('p', "Nella prima schermata (\"Inserisci la Partita IVA dell'azienda\") digiti la **Partita IVA** (es. 12345678901) e premi il pulsante di ricerca: la piattaforma tenta di **recuperare automaticamente** i dati aziendali. In alternativa puoi caricare la **visura camerale** (i dati vengono estratti dall'AI) oppure proseguire e inserire tutto manualmente. Premi **Continua**.")
|
||
add('h3', "Passo 2 — Verifica e completa i dati aziendali")
|
||
add('p', "Nella schermata \"Verifica e Completa i Dati Aziendali\" un banner indica la fonte dei dati (visura, lookup P.IVA o inserimento manuale). Controlla e completa i campi:")
|
||
add('ul', ["**Ragione sociale** (es. Acme S.r.l.);",
|
||
"**Partita IVA** e **Codice Fiscale**;",
|
||
"**Indirizzo**, **Città**, **Sito web**;",
|
||
"**Email aziendale** e **Telefono aziendale**;",
|
||
"**Settore** — scegli dall'elenco il settore NIS2 (es. Energia, Trasporti, Sanità, Infrastrutture digitali, ecc.): è determinante per la classificazione;",
|
||
"**Numero di dipendenti** e **fatturato** annuo, usati per stabilire la dimensione dell'impresa."])
|
||
add('h3', "Passo 3 — Il tuo profilo")
|
||
add('p', "Inserisci i dati del tuo profilo utente: **nome completo**, **ruolo** in azienda e **telefono** di contatto.")
|
||
add('h3', "Passo 4 — Classificazione NIS2")
|
||
add('p', "In base a settore e dimensione, la piattaforma **propone la classificazione**: soggetto **essenziale**, **importante** oppure **non applicabile**. Se non rientri obbligatoriamente ma vuoi adeguarti, puoi indicare l'**adesione volontaria**. Conferma per completare l'onboarding e accedere alla Dashboard.")
|
||
add('note', "Puoi rivedere o aggiornare la classificazione in qualsiasi momento da Impostazioni › Classificazione NIS2. La classificazione filtra automaticamente, ad esempio, i requisiti applicabili nella Gap Analysis ACN e gli obblighi sugli incidenti.")
|
||
|
||
# ══════════════════ 5. NAVIGAZIONE ══════════════════
|
||
add('h1', "5. Come muoversi nell'interfaccia")
|
||
add('p', "Dopo l'accesso l'interfaccia è composta da una **barra laterale** (sidebar) a sinistra con il menu, e dall'**area principale** a destra che mostra la pagina selezionata. In alto a destra di ogni pagina trovi il pulsante di **aiuto contestuale (?)** e le azioni della pagina.")
|
||
add('h3', "Le sezioni del menu")
|
||
add('table', ["Sezione","Voci"],
|
||
[["Principale","Dashboard, Compliance Journey, Gap Analysis (NIS2), Gap Analysis ACN, Modello SGSI (ISO 27001)"],
|
||
["Gestione","Rischi, Incidenti, Policy, Supply Chain, Segnalazioni (whistleblowing), Normative, AI Cross-Analysis, Knowledge Base"],
|
||
["Operativo","Formazione, Asset, Audit & Report"],
|
||
["Sistema","Guida all'uso, Impostazioni, Architettura, Simulazione Demo, Integrazioni Esterne"]])
|
||
add('h3', "Lingua e aiuto")
|
||
add('ul', ["L'interfaccia è disponibile in **Italiano** ed **Inglese** (selettore lingua).",
|
||
"Il pulsante **?** in ogni pagina apre la guida contestuale con spiegazioni e riferimenti normativi.",
|
||
"Chi appartiene a più aziende (consulenti) può **cambiare organizzazione** dal menu."])
|
||
|
||
# ══════════════════ 6..23 MODULI (da help.js) ══════════════════
|
||
module_chapter(6, "Dashboard", "dashboard")
|
||
module_chapter(7, "Gap Analysis NIS2 (Art. 21)", "assessment",
|
||
"Apri la voce **Gap Analysis** dal menu. È l'analisi di primo livello sulle 10 categorie di misure dell'Art. 21 NIS2.")
|
||
module_chapter(8, "Gap Analysis ACN (Determinazione 164179/2025)", "acn",
|
||
"Apri la voce **Gap Analysis ACN**. È l'analisi di secondo livello sui requisiti puntuali ACN.")
|
||
module_chapter(9, "Modello Organizzativo SGSI (ISO 27001/27017/27018)", "isms",
|
||
"Apri la voce **Modello SGSI (ISO 27001)**. Una procedura guidata in 6 passi costruisce il Sistema di Gestione della Sicurezza delle Informazioni; lo Statement of Applicability è pre-popolato dalle risposte della Gap Analysis NIS2.")
|
||
module_chapter(10, "Gestione dei Rischi", "risks", "Apri la voce **Rischi**.")
|
||
module_chapter(11, "Gestione degli Incidenti (Art. 23)", "incidents", "Apri la voce **Incidenti**.")
|
||
module_chapter(12, "Policy e procedure", "policies", "Apri la voce **Policy**.")
|
||
module_chapter(13, "Supply Chain (fornitori)", "supply-chain", "Apri la voce **Supply Chain**.")
|
||
module_chapter(14, "Formazione", "training", "Apri la voce **Formazione**.")
|
||
module_chapter(15, "Asset", "assets", "Apri la voce **Asset**.")
|
||
module_chapter(16, "Audit & Report", "reports", "Apri la voce **Audit & Report**.")
|
||
module_chapter(17, "Whistleblowing (Art. 32)", "whistleblowing", "Apri la voce **Segnalazioni**.")
|
||
module_chapter(18, "Aggiornamenti normativi", "normative", "Apri la voce **Normative**.")
|
||
module_chapter(19, "Knowledge Base", "kb", "Apri la voce **Knowledge Base**.")
|
||
module_chapter(20, "Compliance Journey", "workflow", "Apri la voce **Compliance Journey**: un percorso guidato che indica i prossimi passi consigliati.")
|
||
module_chapter(21, "Segnalazioni (feedback)", "feedback",
|
||
"Usa lo strumento di feedback per segnalare problemi o suggerimenti: ogni segnalazione cattura automaticamente versione e superficie del prodotto.")
|
||
module_chapter(22, "Impostazioni", "settings", "Apri la voce **Impostazioni**.")
|
||
module_chapter(23, "Gestione delle aziende (consulenti)", "companies",
|
||
"Disponibile per i consulenti: consente di gestire più aziende clienti da un unico accesso.")
|
||
|
||
# ══════════════════ 24. FAQ ══════════════════
|
||
add('h1', "24. Domande frequenti (FAQ)")
|
||
add('h3', "Non riesco ad accedere")
|
||
add('p', "Verifica email e password; usa **Password dimenticata** per reimpostarla. Se usi il SSO della suite, accedi con le credenziali centralizzate.")
|
||
add('h3', "Ho sbagliato la classificazione NIS2")
|
||
add('p', "Puoi correggerla in **Impostazioni › Classificazione NIS2**: il perimetro dei moduli (es. Gap Analysis ACN) si aggiorna di conseguenza.")
|
||
add('h3', "Lo Statement of Applicability del Modello SGSI è quasi vuoto")
|
||
add('p', "Viene pre-popolato dalle risposte della **Gap Analysis NIS2**: completa prima quell'assessment, poi usa **Ri-deriva dal NIS2** nel SoA. I controlli senza corrispondenza NIS2 (es. alcuni controlli fisici) vanno compilati manualmente.")
|
||
add('h3', "Le email non vengono inviate")
|
||
add('p', "Negli ambienti dimostrativi l'invio email può essere disattivato per sicurezza. In produzione le notifiche (CSIRT, formazione, inviti) vengono inviate regolarmente.")
|
||
add('h3', "Le bozze generate dall'AI sono definitive?")
|
||
add('p', "No: sono **bozze** da rivedere e approvare. Riportano sempre un avviso e citano le fonti normative.")
|
||
|
||
# ══════════════════ 25. GLOSSARIO ══════════════════
|
||
add('h1', "25. Glossario")
|
||
add('table', ["Termine","Significato"],
|
||
[["NIS2","Direttiva (UE) 2022/2555 sulla cibersicurezza"],
|
||
["ACN","Agenzia per la Cybersicurezza Nazionale"],
|
||
["Soggetto essenziale / importante","Categorie NIS2 che determinano gli obblighi applicabili"],
|
||
["Gap Analysis","Analisi degli scostamenti tra stato attuale e requisiti"],
|
||
["SGSI / ISMS","Sistema di Gestione della Sicurezza delle Informazioni (ISO 27001)"],
|
||
["SoA","Statement of Applicability: dichiarazione di applicabilità dei controlli"],
|
||
["Annex A","I 93 controlli di sicurezza di ISO/IEC 27001:2022"],
|
||
["RACI","Matrice di responsabilità: Responsible, Accountable, Consulted, Informed"],
|
||
["CSIRT","Squadra nazionale di risposta agli incidenti (autorità di notifica)"],
|
||
["NCR / CAPA","Non conformità / azioni correttive e preventive"],
|
||
["PIR","Post-Incident Review (revisione post-incidente)"],
|
||
["RAG","Recupero da knowledge base a supporto delle risposte AI"],
|
||
["PII","Dati personali (Personally Identifiable Information)"]])
|
||
|
||
# ══════════════════ 26. RIFERIMENTI ══════════════════
|
||
add('h1', "26. 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);",
|
||
"Determinazione ACN n. 333017/2025;",
|
||
"ISO/IEC 27001:2022, 27002:2022, 27017:2015, 27018:2019 (best practice, non vincolanti)."])
|
||
add('note', f"Manuale d'uso di NIS2 Agile — versione piattaforma 1.14.0, aggiornato all'{DATE}. Per assistenza usa il pulsante di aiuto (?) presente in ogni pagina o la voce \"Guida all'uso\" nel menu.")
|
||
|
||
# ════════════════════════════════ EMITTER OOXML ════════════════════════════════
|
||
def runs(text):
|
||
out = []
|
||
for i, part in enumerate(re.split(r'\*\*', text)):
|
||
if part == '': continue
|
||
rpr = '<w:rPr><w:b/></w:rPr>' if (i % 2 == 1) else ''
|
||
out.append('<w:r>%s<w:t xml:space="preserve">%s</w:t></w:r>' % (rpr, escape(part)))
|
||
return ''.join(out) or '<w:r><w:t/></w:r>'
|
||
|
||
def para(text, style=None):
|
||
ppr = '<w:pPr><w:pStyle w:val="%s"/></w:pPr>' % style if style else ''
|
||
return '<w:p>%s%s</w:p>' % (ppr, runs(text))
|
||
|
||
def listitem(text, numid):
|
||
return ('<w:p><w:pPr><w:pStyle w:val="ListParagraph"/>'
|
||
'<w:numPr><w:ilvl w:val="0"/><w:numId w:val="%d"/></w:numPr></w:pPr>%s</w:p>' % (numid, runs(text)))
|
||
|
||
def cell(text, header=False):
|
||
if header:
|
||
shd = '<w:shd w:val="clear" w:fill="1F3864"/>'
|
||
r = '<w:r><w:rPr><w:b/><w:color w:val="FFFFFF"/></w:rPr><w:t xml:space="preserve">%s</w:t></w:r>' % escape(text)
|
||
else:
|
||
shd = ''; r = runs(text)
|
||
return '<w:tc><w:tcPr><w:tcW w:w="0" w:type="auto"/>%s</w:tcPr><w:p>%s</w:p></w:tc>' % (shd, r)
|
||
|
||
def table(headers, rows):
|
||
borders = '<w:tblBorders>' + ''.join('<w:%s w:val="single" w:sz="4" w:space="0" w:color="BFBFBF"/>'%s
|
||
for s in ['top','left','bottom','right','insideH','insideV']) + '</w:tblBorders>'
|
||
out = ['<w:tbl><w:tblPr><w:tblW w:w="5000" w:type="pct"/>%s<w:tblLook w:firstRow="1"/></w:tblPr>' % borders]
|
||
out.append('<w:tr>' + ''.join(cell(h, True) for h in headers) + '</w:tr>')
|
||
for row in rows: out.append('<w:tr>' + ''.join(cell(c) for c in row) + '</w:tr>')
|
||
out.append('</w:tbl><w:p/>')
|
||
return ''.join(out)
|
||
|
||
body = []
|
||
for b in C:
|
||
k = b[0]
|
||
if k == 'title': body.append(para(b[1],'Title')+para(b[2],'Subtitle')+para(b[3],'Meta'))
|
||
elif k in ('h1','h2','h3'): body.append(para(b[1], {'h1':'Heading1','h2':'Heading2','h3':'Heading3'}[k]))
|
||
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('<w:p><w:r><w:br w:type="page"/></w:r></w:p>')
|
||
|
||
SECT = ('<w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0"/></w:sectPr>')
|
||
document = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>'
|
||
+ ''.join(body) + SECT + '</w:body></w:document>')
|
||
|
||
STYLES = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
|
||
'<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/><w:sz w:val="22"/></w:rPr></w:rPrDefault></w:docDefaults>'
|
||
'<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:pPr><w:spacing w:after="140" w:line="276" w:lineRule="auto"/></w:pPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Title"><w:name w:val="Title"/><w:pPr><w:spacing w:before="2600" w:after="120"/></w:pPr><w:rPr><w:b/><w:color w:val="1F3864"/><w:sz w:val="72"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/><w:pPr><w:spacing w:after="80"/></w:pPr><w:rPr><w:color w:val="2E74B5"/><w:sz w:val="34"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Meta"><w:name w:val="Meta"/><w:rPr><w:color w:val="808080"/><w:sz w:val="20"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:pPr><w:keepNext/><w:spacing w:before="360" w:after="120"/><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="3" w:color="2E74B5"/></w:pBdr><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:b/><w:color w:val="1F3864"/><w:sz w:val="32"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:pPr><w:keepNext/><w:spacing w:before="240" w:after="80"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:b/><w:color w:val="2E74B5"/><w:sz w:val="27"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:pPr><w:keepNext/><w:spacing w:before="200" w:after="60"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:b/><w:color w:val="404040"/><w:sz w:val="24"/></w:rPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="ListParagraph"><w:name w:val="List Paragraph"/><w:basedOn w:val="Normal"/><w:pPr><w:ind w:left="567"/><w:spacing w:after="60"/></w:pPr></w:style>'
|
||
'<w:style w:type="paragraph" w:styleId="Note"><w:name w:val="Note"/><w:basedOn w:val="Normal"/><w:pPr><w:pBdr><w:left w:val="single" w:sz="18" w:space="6" w:color="F59E0B"/></w:pBdr><w:ind w:left="227"/><w:spacing w:before="120" w:after="120"/><w:shd w:val="clear" w:fill="FFFBEB"/></w:pPr><w:rPr><w:i/><w:color w:val="7A5B00"/></w:rPr></w:style>'
|
||
'</w:styles>')
|
||
|
||
NUMBERING = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
|
||
'<w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="bullet"/><w:lvlText w:val="•"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="567" w:hanging="284"/></w:pPr><w:rPr><w:rFonts w:ascii="Symbol" w:hAnsi="Symbol" w:hint="default"/></w:rPr></w:lvl></w:abstractNum>'
|
||
'<w:abstractNum w:abstractNumId="1"><w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="567" w:hanging="284"/></w:pPr></w:lvl></w:abstractNum>'
|
||
'<w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num><w:num w:numId="2"><w:abstractNumId w:val="1"/></w:num></w:numbering>')
|
||
|
||
CT = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
||
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
||
'<Default Extension="xml" ContentType="application/xml"/>'
|
||
'<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
|
||
'<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>'
|
||
'<Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>'
|
||
'<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>')
|
||
RELS = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
||
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>'
|
||
'<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>')
|
||
DRELS = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
||
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'
|
||
'<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/></Relationships>')
|
||
CORE = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
'<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
|
||
'<dc:title>Manuale d\'uso NIS2 Agile</dc:title><dc:creator>NIS2 Agile</dc:creator><cp:lastModifiedBy>NIS2 Agile</cp:lastModifiedBy>'
|
||
'<dcterms:created xsi:type="dcterms:W3CDTF">2026-06-11T00:00:00Z</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">2026-06-11T00:00:00Z</dcterms:modified></cp:coreProperties>')
|
||
|
||
with zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED) as z:
|
||
z.writestr('[Content_Types].xml', CT)
|
||
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', DRELS)
|
||
z.writestr('docProps/core.xml', CORE)
|
||
print("OK ->", OUT, "| blocchi:", len(C))
|