Unire più file ODT in un unico file ODT

Da Gambas-it.org - Wikipedia.

Il file di formato "ODT" è un file compresso costituito vari file al suo interno, dei quali quello contenente i dati precipui testuali del file "ODT" è 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:4.0.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" 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])
  
  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.

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