domenica 15 gennaio 2012

[VB.NET] Gestire File INI con Classi

Descrizione :
Una mia tecnica essenziale per gestire file INI e simili con alcune Classi.

+ Articolo :
Accanto ai ben conosciuti .cfg, .conf, ecc., il file INI è un formato di file testuale utilizzato da numerosi programmi per la memorizzazione delle opzioni di funzionamento dei programmi stessi.
Nonostante in .NET la gestione di file INI sia considerata decisamente "Legacy", può capitare sempre di imbattersi in Applicazioni che ne fanno uso.

Personalmente vedo che esistono già molti How-To e suggerimenti online che spiegano come gestire in VB e C# un tipico file INI, ma spesso fanno uso di diverse dichiarazioni API esplicite.
Il mio scopo è invece quello di gestire un classico file INI semplicemente con alcune Classi essenzialmente .NET.

Anzitutto è necessario stabilire nomi e convenzioni precise e, prendendo a prestito un po' di "nomenclatura XML", definire come dovrebbe essere un file INI "ben formato".
Dato che siamo in VB.NET, applico senza dubbio lo "Standard Microsoft", e senza cercare troppo in giro mi affido alla definizione presente su Wikipedia :
http://it.wikipedia.org/wiki/File_INI

- Un file INI risulta suddiviso in Sezioni.
- Una Sezione inizia con il nome, racchiuso tra parentesi quadre :
[NomeSezione]
- Una Sezione può contenere un Commento e diversi Parametri.
- Un Parametro viene definito come coppia Chiave-Valore :
Chiave = Valore
- Un Commento deve iniziare con ";" :
; Commento Sezione
- Due Sezioni successive sono separate da una riga vuota.

--> Il file di test "INITest.ini" usato in questo articolo è lo stesso della pagina Wikipedia e ha inizialmente questo contenuto :

[Sezione1]
; Un commento su questa sezione.
Parametro1 = Questo è un valore assegnato, come quello seguente.
Parametro2 = 1

[Sezione2]
; Un altro commento.
Parametro1 = Altro esempio...
Parametro2 = ...e l'ultimo.

--> La Classe Parametro :
Public Class Parametro

    Private m_chiave As String
    Private m_valore As String

    Public Property Chiave() As String
        Get
            Return m_chiave
        End Get
        Set(ByVal value As String)
            m_chiave = value
        End Set
    End Property

    Public Property Valore() As String
        Get
            Return m_valore
        End Get
        Set(ByVal value As String)
            m_valore = value
        End Set
    End Property

    Public Sub New(ByVal nomeChiave As String, ByVal valoreChiave As String)

        m_chiave = nomeChiave
        m_valore = valoreChiave

    End Sub

    Public Overrides Function ToString() As String

        Return m_chiave & " = " & m_valore

    End Function

End Class

--> La Classe Sezione :
Public Class Sezione
    Inherits List(Of Parametro)

    Private m_nome As String
    Private m_commento As String

    Public Property Nome() As String
        Get
            Return m_nome
        End Get
        Set(ByVal value As String)
            m_nome = value
        End Set
    End Property

    Public Property Commento() As String
        Get
            Return m_commento
        End Get
        Set(ByVal value As String)
            m_commento = value
        End Set
    End Property

    Public Sub New(ByVal nome As String, Optional ByVal commento As String = "")

        m_nome = nome
        If commento <> "" Then m_commento = commento

    End Sub

    Public Function GetParametroByNome(ByVal nomeParametro As String) As Parametro

        For Each P As Parametro In Me
            If P.Chiave = nomeParametro Then Return P
        Next
        Return Nothing

    End Function

End Class

--> La Classe FileIni :
Public Class FileIni
    Inherits List(Of Sezione)

    Private m_nomeFileCompleto As String

    Public Sub New(ByVal nomeFileCompleto As String)

        m_nomeFileCompleto = nomeFileCompleto

    End Sub

    Public Sub Carica()

        Me.Clear()
        Dim linee As String() = IO.File.ReadAllLines(m_nomeFileCompleto)
        For Each l As String In linee
            If l <> "" Then
                Select Case l.First
                    Case ";" 'Commento di Sezione
                        Me.Last.Commento = l.Replace("; ", "")

                    Case "[" 'Sezione
                        Me.Add(New Sezione(l.Replace("[", "").Replace("]", "")))

                    Case Else 'Parametro ?
                        If l.Contains("=") Then Me.Last.Add(New Parametro(l.Split("=")(0).Trim, l.Split("=")(1).Trim))
                End Select
            End If
        Next

    End Sub

    Public Sub Salva()

        If Me.Count = 0 Then Exit Sub
        Dim linee As New List(Of String)

        For Each S As Sezione In Me
            linee.Add("[" & S.Nome & "]")
            If S.Commento <> "" Then linee.Add("; " & S.Commento)
            For Each P As Parametro In S
                linee.Add(P.ToString)
            Next
            linee.Add("")
        Next

        IO.File.WriteAllLines(m_nomeFileCompleto, linee)

    End Sub

    Public Function GetSezioneByNome(ByVal nomeSezione As String) As Sezione

        For Each S As Sezione In Me
            If S.Nome = nomeSezione Then Return S
        Next
        Return Nothing

    End Function

End Class

Come è facile vedere, Sezione è una List() di Parametri, e FileIni è List() di Sezioni.
La logica del tutto rimane semplice e senza nemmeno una chiamata esplicita API.
Per semplicità ho volutamente evitato di gestire Commenti di Parametro o fuori dalle Sezioni, così come altre eccezioni o formattazioni non-Standard.

--> Alcuni Esempi di utilizzo ... :
        'Carica
        Dim INI As New FileIni(Application.StartupPath & "\INITest.ini")
        INI.Carica()

        'Trova e Modifica
        INI.GetSezioneByNome("Sezione2").GetParametroByNome("Parametro2").Valore = DateTime.Now

        'Oppure (indici)
        INI(0)(1).Valore = DateTime.Now 'Valore su Parametro a indice 0 della Sezione a indice 1

        'Aggiungi Nuova Sezione
        INI.Add(New Sezione("Sezione" & INI.Count + 1, "Commento Sezione" & INI.Count + 1))
        With INI.Last
            .Add(New Parametro("Chiave" & .Count + 1, "Valore" & .Count + 1))
            .Add(New Parametro("Chiave" & .Count + 1, "Valore" & .Count + 1))
        End With
     
        'Salvataggio
        INI.Salva()

        MessageBox.Show("OK")

... E il gioco è fatto.

+ Fine Articolo.


Un Click su "Mi Piace" è il modo migliore per ringraziare l'autore di questo articolo.




10 commenti:

Anonimo ha detto...

Marco! I files ini sono obsoleti ormai da decine d'anni!!!!

MarcoGG ha detto...

Anche il Cobol è obsoleto da decine di anni, e ci sono istituti bancari che hanno gestionali che ci girano. E allora ? C'è una pletora di programmi e utility che ancora fanno uso di files di configurazione INI. Personalmente non ho mai fatto e non faccio mai uso di files .ini, e in .NET è praticamente "deprecato". Ciò non toglie che il presente Articolo possa essere d'aiuto a chi voglia ancora usare un semplice file di testo "The Ini Way"...

Unknown ha detto...

bellissimo e funzionantissimo !
grazie di cuore

Daniele

MarcoGG ha detto...

Prego ! :-)

Unknown ha detto...

Io non ho capito come fare a recuperare un valore...

Anonimo ha detto...

Non funziona quando trova commenti. In Carica si genera una eccezione in questa riga
Me.Last.Commento = l.Replace("; ", "")

Anonimo ha detto...

Anonimo, devi sostituire Me.Last con Me.Item(Me.Count - 1)

Unknown ha detto...

Salve.
Due errori:
1) la chiave "Logo" per lui è uguale a "Logo stampa" (che è nella riga successiva). Di conseguenza si ferma e la prima e non considera "Logo stampa". Se modifico la chiave in "Logo_stampa", la trova :(
2) Ho provato solo con la chiave e non con la sezione. Se non trova la chiave restituisce Nothing e nella procedura chiamante genera un errore.
Fai le prove facilmente usando un file INI con
[Configurazione]
Logo = Logo.gif
Logo Stampa = Logo_stampa.png

e chiamando la classe con
x = INI.GetSezioneByNome("Configurazione").GetParametroByNome("inesistente").Valore

MarcoGG ha detto...

Ciao, rispondo a te come ad altri che mi hanno fatto notare come questa tecnica non copra tutti i possibili casi riscontrabili in un file .INI. Come ho scritto esplicitamente si tratta di una "tecnica essenziale", che quindi può essere successivamente implementata ed affinata. Personalmente non trovo sia una buona idea creare nomi di entità ( esattamente come accade con i database ) che contengono spazi. "Logo Stampa = " non è mai una buona idea, che si usi un INI, un XML, o altro. Chiamerei molto meglio LogoStampa, come se fosse il nome di una Tabella DB.

Anonimo ha detto...

Grazie Marco. Utilissima e ottimo codice! I File .INI sono ancora usati moltissimo (2021) in quanto nelle aziende, quello che funziona, difficilmente si butta via!

Posta un commento

 
Design by Free WordPress Themes Modificato da MarcoGG