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.
10 commenti:
Marco! I files ini sono obsoleti ormai da decine d'anni!!!!
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"...
bellissimo e funzionantissimo !
grazie di cuore
Daniele
Prego ! :-)
Io non ho capito come fare a recuperare un valore...
Non funziona quando trova commenti. In Carica si genera una eccezione in questa riga
Me.Last.Commento = l.Replace("; ", "")
Anonimo, devi sostituire Me.Last con Me.Item(Me.Count - 1)
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
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.
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