Unire più file ODT in un unico file ODT
Il file di formato "ODT" è un file compresso, costituito da vari file al suo interno, che possono essere visualizzati usando un normale programma per decomprimere i file compressi (come .zip, .tar, etc.).
Tra i file, contenuti dal file di formato "ODT", quello contenente i dati precipui testuali del file "ODT", e pertanto il più rilevante, è il file con nome "content.xml ".
Il predetto file "content.xml " è un file di formato XML, costituito da più elementi, contenenti vari tag che definiscono la struttura e le caratteristiche del contenuto del file Document writer di formato "ODT". Gli elementi più specifici per la definizione del contenuto testuale e non testuale di un file "ODT" sono:
<office:automatic-styles>
che contiene i tag relativi alla definizione delle caratteristiche della struttura della pagina, del testo e degli eventuali oggetti non testuali;
<office:body>
che contiene i tag relativi all'effettivo contenuto testuale ed eventualmente a quello non testuale.
Volendo unire due o più file di formato "ODT" in un unico file "ODT", dovremo dunque tenere nella dovuta considerazione questi due elementi. In particolare, se - ad esempio - avendo due file "ODT", chiamati rispettivamente "odt1.odt" e "odt2.odt", e vogliamo aggiunegere il contenuto del file "odt2.odt" al contenuto del file "odt1.odt", bisognerà inserire la parte testuale del file "content.xml", appartenente al file "odt2.odt", che va dal tag <office:automatic-styles> (compreso) sino al tag </office:body> (compreso), in mezzo ai tag </office:body></office:document-content> del file principale "odt1.odt" .
Al termine, quindi, nell'esempio sopra descritto avremo una situazione degli elementi principali, che qui interessano, del file "odt1.odt", così come di seguito riportata:
<?xml version="1.0" encoding="UTF-8"?> <office:document-content ...... ...... ...... <office:automatic-styles> ' Parte del file "odt1.odt" principale ...... ...... </office:automatic-styles> <office:body> ...... ...... </office:body> <office:automatic-styles> ' Parte aggiunta dal file secondario "odt2.odt" ...... ...... </office:automatic-styles> <office:body> ...... ...... </office:body> <----- La parte aggiuntiva di un eventuale terzo file "odt", da unire ai primi due, andrebbe posta ovviamente qui </office:document-content>
Un codice Gambas per aggiungere il contenuto di uno o più file "ODT" ad un file "ODT" principale
Per ottenere con Gambas l'unione di più file di formato "ODT" mediante l'aggiunta degli elementi essenziali, visti sopra, del file "content.xml" di ciascun file "ODT" secondario da unire al file "ODT" principale, va sostanzialmente seguita una procedura complessa strutturata nelle seguenti fasi:
1) estrarre in una qualsiasi cartella il file "content.xml", cambiandone contestualmente nome, di ciascun file secondario "ODT" da aggiungere al principale;
2) estrarre da ciascun file la parte sopra descritta dei tag qui essenziali;
3) inserire ogni parte fra i tag sopra indicati all'interno del file di frmato "XML", che originariamente aveva nome "content.xml", ma poi, come visto, opportunamente modificato;
4) creazione del nuovo file "content.xml" da quello di frmato "XML", di cui al precedente punto 3);
4) inserimento del nuovo file "content.xml" all'interno del file principale "odt1.odt", sostituendo l'originario file omologo (content.xml).
Nell'esempio di applicazione "a riga di comando" che segue, sono previsti due Moduli.
Il Modulo secondario, che chiameremo ad esempio "CambioODT.module", contiene il seguente codice:
Library "libzip:5.0" Public Struct zip_stat valid As Long name As Pointer index As Long size As Long comp_size As Long mtime As Long crc As Integer comp_method As Short encryption_method As Short flags As Integer End Struct Private Const ZIP_CREATE As Integer = 1 ' struct zip *zip_open(const char *, int, int *) ' Open zip archive. Private Extern zip_open(path As String, flags As Integer, errorp As Pointer) As Pointer ' zip_int64_t zip_get_num_entries(struct zip *, int) ' Get number of files in archive. Private Extern zip_get_num_entries(archive As Pointer, flags As Integer) As Long ' int zip_stat_index(struct zip *, int, int, struct zip_stat *) ' Get information about file by index. Private Extern zip_stat_index(archive As Pointer, index As Integer, flags As Integer, zst As Zip_stat) As Integer ' struct zip_file * zip_fopen_index(struct zip *, int, int) ' Open file in zip archive for reading by index. Private Extern zip_fopen_index(archive As Pointer, fileno As Integer, flags As Integer) As Pointer ' zip_int64_t zip_fread(struct zip_file *, void *, size_t) ' Read from file. Private Extern zip_fread(archive As Pointer, outbuf As Byte[], toread As Pointer) As Long ' struct zip_source *zip_source_file(struct zip *, const char *, zip_uint64_t, zip_int64_t) ' Create data source from a file. Private Extern zip_source_file(archive As Pointer, fname As String, start As Long, len As Long) As Pointer ' const char *zip_get_name(struct zip *, zip_uint64_t, int) ' Get name of file by index. Private Extern zip_get_name(zip As Pointer, index As Long, flags As Integer) As String ' int zip_replace(struct zip *, zip_uint64_t, struct zip_source *) ' Replace file in zip archive. Private Extern zip_replace(archive As Pointer, index As Long, source As Pointer) As Integer ' int zip_fclose(struct zip_file *) ' Close file in zip archive. Private Extern zip_fclose(zf As Pointer) As Integer ' int zip_close(struct zip *) ' Close zip archive. Private Extern zip_close(archive As Pointer) As Integer Public Procedure Estrazione(fileODT As String[], decomp As String) Dim percorso As String Dim z, zf As Pointer Dim i, lun, c As Integer Dim zs As New Zip_stat Dim l As Long Dim fl As File Dim buf As Byte[] For Each percorso In fileODT buf = New Byte[64] l = 0 z = zip_open(percorso, 0, 0) If z == 0 Then Error.Raise("Impossibile aprire un file '.odt' !") For i = 0 To zip_get_num_entries(z, 0) - 1 If zip_stat_index(z, i, 0, zs) = 0 Then zf = zip_fopen_index(z, i, 0) If zf == 0 Then Error.Raise("Errore alla funzione 'zip_fopen_index()' !") If File.Name(String@(zs.name)) == "content.xml" Then Inc c fl = Open decomp &/ "xml" & CStr(c) & ".xml" For Create While l < zs.size lun = zip_fread(zf, buf, 64) If lun < 0 Then Error.Raise("Impossibile leggere il file 'odt' !") buf.Write(fl, 0, lun) l += lun Wend Endif Endif Next fl.Close zip_fclose(zf) zip_close(z) Next End Public Procedure Sostituzione(odt As String[]) Dim zip, src As Pointer Dim err As Integer Dim l As Long = -1 zip = zip_open(odt[0], ZIP_CREATE, VarPtr(err)) If zip == 0 Then zip_close(zip) Error.Raise("Impossibile caricare il file 'odt' !") Endif src = zip_source_file(zip, "/tmp/content.xml", 0, 0) If src == 0 Then zip_close(zip) Error.Raise("Impossibile creare il sorgente dati del file 'odt' !") Endif Repeat Inc l Until zip_get_name(zip, l, 0) = "content.xml" zip_replace(zip, l, src) zip_close(zip) End
Il Modulo principale (Main.module) contiene invece il seuente codice:
Public Sub Main() Dim s As String Dim odt, xml As New String[] ' Vanno specificati il percorso ed il nome di tutti i file "odt" (in questo esempio 3 file) che si intende unire in un unico file "odt" (in questo esempio il file chiamato "odt1.odt"): odt = ["/tmp/odt1.odt", "/tmp/odt2.odt", "/tmp/odt3.odt"] ' Nel secondo argomento deve essere specificata la cartella dove saranno immagazzinati: ' i file "content.xml", con i loro nomi modificati, di ciascun file "odt": CambioODT.Estrazione(odt, "/tmp") s = File.Load("/tmp/xml1.xml") xml.Push(Scan(s, "*</office:document-content>*")[0]) s = File.Load("/tmp/xml2.xml") xml.Push(Scan(s, "*</office:font-face-decls>*</office:document-content>*")[1]) s = File.Load("/tmp/xml3.xml") xml.Push(Scan(s, "*</office:font-face-decls>*</office:document-content>*")[1]) File.Save("/tmp/content.xml", xml.Join(Null, Null) & "</office:document-content>") ' Sostituzione del file originario "content.xml", contenuto del file "/tmp/odt1.odt", con il nuovo file "content.xml": CambioODT.Sostituzione(odt) End
Con tale codice i contenuti testuali dei vari file ODT uniti, appariranno nell'unico file ODT l'uno dopo l'altro senza soluzione di continuità: i dati testuali di ciascun file saranno distinto dai dati del file successivo solo da un semplice salto di riga a capo.
Come si è già accennato sopra, nell'esempio precedente il file ODT, che riceve i dati degli altri due file ODT, è il primo (nell'esempio il file chiamato: "odt1.odt").
Qualora si preferisca distinguere più marcatamente i contenuti testuali dei vari file ODT unificati, potrà porsi tra le parti "XML" di ciascun file un'istruzione di salto di pagina, come la seguente:
"<office:automatic-styles><style:style style:name=\"P1\" " & "style:family=\"paragraph\" style:parent-style-name=\"Standard\">" & "<style:paragraph-properties fo:break-before=\"page\"/></style:style></office:automatic-styles>" & "<office:body><office:text text:use-soft-page-breaks=\"true\"><text:sequence-decls>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Illustration\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Table\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Text\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Drawing\"/>" & "</text:sequence-decls><text:p text:style-name=\"Standard\"/>" & "<text:p text:style-name=\"P1\"/></office:text></office:body>"
Pertanto, facendo riferimento all'esempio precedentemente proposto, il codice del Modulo principale "Main.module" sarà il seguente:
Private Const SALTO_PAGINA As String = "<office:automatic-styles><style:style style:name=\"P1\" " & "style:family=\"paragraph\" style:parent-style-name=\"Standard\">" & "<style:paragraph-properties fo:break-before=\"page\"/>" & "</style:style></office:automatic-styles>" & "<office:body><office:text text:use-soft-page-breaks=\"true\"><text:sequence-decls>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Illustration\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Table\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Text\"/>" & "<text:sequence-decl text:display-outline-level=\"0\" text:name=\"Drawing\"/>" & "</text:sequence-decls><text:p text:style-name=\"Standard\"/>" & "<text:p text:style-name=\"P1\"/></office:text></office:body>" Public Sub Main() Dim s As String Dim odt, xml As New String[] ' Vanno specificati il percorso ed il nome di tutti i file "odt" che si intende unire in un unico file "odt" (in questo esempio 3 file): odt = ["/tmp/odt1.odt", "/tmp/odt2.odt", "/tmp/odt3.odt"] ' Nel secondo argomento deve essere specificata la cartella dove saranno immagazzinati: ' i file "content.xml", con i loro nomi modificati, di ciascun file "odt": CambioODT.Estrazione(odt, "/tmp") s = File.Load("/tmp/xml1.xml") xml.Push(Scan(s, "*</office:document-content>*")[0]) ' Imposta il "Salto di Pagina" tra il testo del primo file "odt" ed il testo del secondo file "odt": xml.Push(SALTO_PAGINA) s = File.Load("/tmp/xml2.xml") xml.Push(Scan(s, "*</office:font-face-decls>*</office:document-content>*")[1]) ' Imposta il "Salto di Pagina" tra il testo del secondoo file "odt" ed il testo del terzo file "odt": xml.Push(SALTO_PAGINA) s = File.Load("/tmp/xml3.xml") xml.Push(Scan(s, "*</office:font-face-decls>*</office:document-content>*")[1]) File.Save("/tmp/content.xml", xml.Join(Null, Null) & "</office:document-content>") ' Sostituzione del file originario "content.xml", contenuto del file "/tmp/odt1.odt", con il nuovo file "content.xml": CambioODT.Sostituzione(odt) End