Feedback

C# - Erweiterte Ini-Datei-Klasse

Veröffentlicht von am 12/23/2009
(2 Bewertungen)
Ich habe immer Thomas Westrupp's INI-Klasse (http://dotnet-snippets.de/dns/ini-datei-klasse-SID660.aspx) verwendet und im laufe der Projekte sind immer mehr Funktionen hinzu gekommen, die ich der Welt nicht enthalten möchte.

Über Verbesserrungs- und Funktionswünsche freue ich mich immer!

mfG

Original Beschreibung:

Mit der Ini-Datei-Klasse kann man rel. einfach Dateien im Ini-Format erstellen/bearbeiten, da ja doch noch in vielen Fällen von anderen Programmen Daten in dieser Form gespeichert werden.

Eine Konfiguration kann entweder komplett neu mit dem Standard-Konstruktor erstellt oder von einer vorhandenen Datei eingelesen werden.

Mit getValue kann man Werte auslesen, mit setValue setzen. getCaption gibt alle Einträge eines Bereiches zurück, mit Save kann man die Änderungen speichern und getAllCaptions gibt alle Bereichsnamen zurück.

Unterstützt Groß-/Kleinschreibung, XML-Export ist möglich

Folgende Namespaces werden benötigt:
System, System.Collections.Generic, System.Text, System.IO, System.Xml, System.Text.RegularExpressions
/// <summary>
/// Klasse, um Dateien im Ini-Format zu verwalten.
/// </summary>
public class INIFile
{
	/// <summary>
	/// Inhalt der Datei
	/// </summary>
	private List<string> lines = new List<string>();
	
	/// <summary>
	/// Anzahl der Zeilen in der INIDatei
	/// </summary>
	public int LineCount
	{
		get { return lines.Count; }
	}
	
	/// <summary>
	/// Verzeichnis der Datei
	/// </summary>
	/// <returns></returns>
	public string FileDirectory
	{
		get { return Path.GetDirectoryName(FileName); }
	}
	
	/// <summary>
	/// Voller Name der Datei bzw Titel der INI
	/// </summary>
	/// <returns></returns>
	public string FileName
	{
		get { return FileName; }
	}
	
	/// <summary>
	/// Voller Pfad und Name der Datei bzw Titel der INI
	/// </summary>
	private string fileName = "";
	
	/// <summary>
	/// Gibt an, welche Zeichen als Kommentarbeginn
	/// gewertet werden sollen. Dabei wird das erste
	/// Zeichen defaultm��ig f�r neue Kommentare
	/// verwendet.
	/// </summary>
	private string CommentCharacters = "#;";
	
	/// <summary>
	/// Regul�rer Ausdruck f�r einen Kommentar in einer Zeile
	/// </summary>
	private string regCommentStr = "";
	
	/// <summary>
	/// Regul�rer Ausdruck f�r einen Eintrag
	/// </summary>
	private Regex regEntry = null;
	
	/// <summary>
	/// Regul�rer Ausdruck f�r einen Bereichskopf
	/// </summary>
	private Regex regCaption = null;
	
	/// <summary>
	/// �nderrungen an einem Eintrag vornemen. Achtung CaseSensitive!
	/// </summary>
	public string this[string Caption, string Entry]
	{
		get
		{
			return getValue(Caption, Entry, true);
		}
		set
		{
			setValue(Caption, Entry, value.ToString(), true, false);
		}
	}
	
	/// <summary>
	/// Leerer Standard-Konstruktor
	/// </summary>
	public INIFile()
	{
		regCommentStr = @"(\s*[" + CommentCharacters + "](?<comment>.*))?";
		regEntry      = new Regex(@"^[ \t]*(?<entry>([^=])+)=(?<value>([^=" + CommentCharacters + "])+)" + regCommentStr + "$");
		regCaption    = new Regex(@"^[ \t]*(\[(?<caption>([^\]])+)\]){1}" + regCommentStr + "$");
	}
	
	/// <summary>
	/// Konstruktor, welcher sofort eine Datei einliest
	/// </summary>
	/// <param name="filename">Name der einzulesenden Datei</param>
	public INIFile(string filename) : this ()
	{
		if(!File.Exists(filename))
			throw new IOException("File " + filename + "  not found");
		
		Read(File.ReadAllText(filename));
	}
	
	/// <summary>
	/// Methode die einen string im INI-Format einlie�t
	/// </summary>
	/// <param name="iniFile">String im INI-Format</param>
	public void Read(string iniFile)
	{
		lines.AddRange(iniFile.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None));
	}
	
	/// <summary>
	/// Methode die einen string im INI-Format einlie�t
	/// </summary>
	/// <param name="iniFile">String im INI-Format</param>
	public void ReadFile(string iniFile)
	{
		Read(File.ReadAllText(iniFile));
	}
	
	/// <summary>
	/// Kopie der Datei speichern (Objekt bleibt unber�hrt)
	/// </summary>
	/// <param name="file"></param>
	/// <returns></returns>
	public bool SaveAs(string file)
	{
		if(fileName == "") return false;
		try
		{
			File.WriteAllLines(fileName, lines.ToArray());
		}
		catch(IOException ex)
		{
			throw new IOException("Fehler beim Schreiben der Datei " + fileName, ex);
		}
		catch(Exception ex)
		{
			throw new IOException("Fehler beim Schreiben der Datei " + fileName, ex);
		}
		return true;
	}
	
	/// <summary>
	/// Datei speichern
	/// </summary>
	/// <returns></returns>
	public bool Save()
	{
		return SaveAs(fileName);
	}
	
	/// <summary>
	/// Datei unter anderrem Namen speichern
	/// </summary>
	/// <param name="file"></param>
	/// <returns></returns>
	public bool Save(string file)
	{
		fileName = file;
		return SaveAs(fileName);
	}
	
	/// <summary>
	/// Sucht die Zeilennummer (nullbasiert)
	/// eines gew�nschten Eintrages
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>Nummer der Zeile, sonst -1</returns>
	public int GetCaptionLine(string Caption, bool CaseSensitive)
	{
		if(!CaseSensitive) Caption = Caption.ToLower();
		for(int i = 0; i < lines.Count; i++)
		{
			string line = lines[i].Trim();
			if(line == "") continue;
			if(!CaseSensitive) line = line.ToLower();
			// Erst den gew�nschten Abschnitt suchen
			if(line == "[" + Caption + "]")
				return i;
		}
		return -1;// Bereich nicht gefunden
	}
	
	/// <summary>
	/// Pr�ft ob ein Bereich existiert.
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>True wenn der Breich existiert, sonst false</returns>
	public bool CaptionExist(string Caption, bool CaseSensitive)
	{
		return GetCaptionLine(Caption, CaseSensitive) != -1;
	}
	
	/// <summary>
	/// Sucht die Zeilennummer (nullbasiert)
	/// eines gew�nschten Eintrages
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="Entry">Name des Eintrages</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>Nummer der Zeile, sonst -1</returns>
	public int GetEntryLine(string Caption, string Entry, bool CaseSensitive)
	{
		Caption = Caption.ToLower();
		if(!CaseSensitive) Entry = Entry.ToLower();
		int CaptionStart = GetCaptionLine(Caption, false);
		if(CaptionStart < 0) return -1;
		for(int i = CaptionStart + 1; i < lines.Count; i++)
		{
			string line = lines[i].Trim();
			if(line == "") continue;
			if(!CaseSensitive) line = line.ToLower();
			if(line.StartsWith("["))
				return -1;// Ende, wenn der n�chste Abschnitt beginnt
			if(Regex.IsMatch(line, @"^[ \t]*[" + CommentCharacters + "]"))
				continue; // Kommentar
			if(line.StartsWith(Entry+"="))
				return i;// Eintrag gefunden
		}
		return -1;// Eintrag nicht gefunden
	}
	
	/// <summary>
	/// Pr�ft ob ein Eintrag existiert.
	/// </summary>
	/// <param name="Caption">Name des Bereiches in dem gesucht werden soll</param>
	/// <param name="Entry">Name des Eintrag der gesucht werden soll</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>True wenn der Eintrag existiert, sonst false</returns>
	public bool EntryExist(string Caption, string Entry, bool CaseSensitive)
	{
		return GetCaptionLine(Caption, CaseSensitive) != -1;
	}
	
	/// <summary>
	/// Kommentiert einen Wert aus
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="Entry">Name des Eintrages</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>true = Eintrag gefunden und auskommentiert</returns>
	public bool commentValue(string Caption, string Entry, bool CaseSensitive)
	{
		int line = GetEntryLine(Caption, Entry, CaseSensitive);
		if(line < 0)
			return false;
		lines[line] = CommentCharacters[0] + lines[line];
		return true;
	}
	
	/// <summary>
	/// L�scht einen Wert
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="Entry">Name des Eintrages</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>true = Eintrag gefunden und gel�scht</returns>
	public bool deleteValue(string Caption, string Entry, bool CaseSensitive)
	{
		int line = GetEntryLine(Caption, Entry, CaseSensitive);
		if(line < 0)
			return false;
		lines.RemoveAt(line);
		return true;
	}
	
	/// <summary>
	/// Liest den Wert eines Eintrages aus
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="Entry">Name des Eintrages</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>Wert des Eintrags oder leer</returns>
	public string getValue(string Caption, string Entry, bool CaseSensitive)
	{
		int line = GetEntryLine(Caption, Entry, CaseSensitive);
		if(line < 0)
			return "";
		int pos = lines[line].IndexOf("=");
		if(pos < 0)
			return "";
		return lines[line].Substring(pos + 1).Trim();
		// Evtl. noch abschliessende Kommentarbereiche entfernen
	}
	
	/// <summary>
	/// Liest den Wert eines Eintrages aus
	/// Ignoriert Gross-/Kleinschreibung
	/// </summary>
	/// <param name="Caption">Name des Bereiches</param>
	/// <param name="Entry">Name des Eintrages</param>
	/// <returns>Wert des Eintrags oder leer</returns>
	public string getValue(string Caption, string Entry)
	{
		return getValue(Caption, Entry, false);
	}
	
	/// <summary>
	/// Setzt einen Wert in einem Bereich. Wenn der Wert
	/// (und der Bereich) noch nicht existiert, werden die
	/// entsprechenden Eintr�ge erstellt.
	/// </summary>
	/// <param name="Caption">Name des Bereichs</param>
	/// <param name="Entry">name des Eintrags</param>
	/// <param name="Value">Wert des Eintrags</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>true = Eintrag erfolgreich gesetzt</returns>
	public bool setValue(string Caption, string Entry, string Value, bool CaseSensitive, bool SearchComments)
	{
		Caption = Caption.ToLower();
		if(!CaseSensitive)
			Entry = Entry.ToLower();
		int lastCommentedFound = -1;
		int CaptionStart = GetCaptionLine(Caption, false);
		if(CaptionStart < 0)
		{
			lines.Add("[" + Caption + "]");
			lines.Add(Entry + "=" + Value);
			return true;
		}
		int EntryLine = GetEntryLine(Caption, Entry, CaseSensitive);
		for(int i = CaptionStart + 1; i < lines.Count; i++)
		{
			string line = lines[i].Trim();
			if(!CaseSensitive)
				line = line.ToLower();
			if(line == "")
				continue;
			// Ende, wenn der n�chste Abschnitt beginnt
			if(line.StartsWith("["))
			{
				lines.Insert(i, Entry + "=" + Value);
				return true;
			}
			// Suche aukommentierte, aber gesuchte Eintr�ge,
			// falls der Eintrag noch nicht existiert.
			if(SearchComments && EntryLine<0 && Regex.IsMatch(line, @"^[ \t]*[" + CommentCharacters + "]"))
			{
				string tmpLine = line.Substring(1).Trim();
				if(tmpLine.StartsWith(Entry))
				{
					// Werte vergleichen, wenn gleich,
					// nur Kommentarzeichen l�schen
					int pos = tmpLine.IndexOf("=");
					if(pos > 0)
					{
						if(Value == tmpLine.Substring(pos + 1).Trim())
						{
							lines[i] = tmpLine;
							return true;
						}
					}
					lastCommentedFound = i;
				}
				continue;// Kommentar
			}
			if(line.StartsWith(Entry))
			{
				lines[i] = Entry + "=" + Value;
				return true;
			}
		}
		if(lastCommentedFound > 0)
			lines.Insert(lastCommentedFound + 1, Entry + "=" + Value);
		else
			lines.Insert(CaptionStart + 1, Entry + "=" + Value);
		return true;
	}
	
	/// <summary>
	/// Setzt einen Wert in einem Bereich. Wenn der Wert
	/// (und der Bereich) noch nicht existiert, werden die
	/// entsprechenden Eintr�ge erstellt.
	/// </summary>
	/// <param name="Caption">Name des Bereichs</param>
	/// <param name="Entry">name des Eintrags</param>
	/// <param name="Value">Wert des Eintrags</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>true = Eintrag erfolgreich gesetzt</returns>
	public bool setValue(string Caption, string Entry, string Value)
	{
		return setValue(Caption, Entry, Value, false, false);
	}
	
	/// <summary>
	/// Liest alle Eintr�ge uns deren Werte eines Bereiches aus
	/// </summary>
	/// <param name="Caption">Name des Bereichs</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>Sortierte Liste mit Eintr�gen und Werten</returns>
	public SortedList<string, string> getCaption(string Caption, bool caseSensitive)
	{
		SortedList<string, string> result = new SortedList<string,string>();
		bool CaptionFound = false;
		for(int i = 0; i < lines.Count; i++)
		{
			string line = lines[i].Trim();
			if(line == "")
				continue;
			
			if(caseSensitive)
				Caption.ToLower();
			
			// Erst den gew�nschten Abschnitt suchen
			if(!CaptionFound)
			{
				if(caseSensitive)
					line.ToLower();
				
				if(line != "[" + Caption + "]")
				{
					continue;
				}
				else
				{
					CaptionFound = true;
					continue;
				}
			}
			
			if(line.StartsWith("[")) // Ende, wenn der n�chste Abschnitt beginnt
				break;
			if(Regex.IsMatch(line, @"^[ \t]*[" + CommentCharacters + "]")) // Kommentar
				continue;
			int pos = line.IndexOf("=");
			if(pos < 0)
				result.Add(line, "");
			else
				result.Add(line.Substring(0,pos).Trim(),line.Substring(pos + 1).Trim());
		}
		return result;
	}
	
	/// <summary>
	/// Liest alle Eintr�ge uns deren Werte eines Bereiches aus.
	/// Ignoriert Gross-/Kleinschreibung
	/// </summary>
	/// <param name="Caption">Name des Bereichs</param>
	/// <param name="CaseSensitive">true = Gross-/Kleinschreibung beachten</param>
	/// <returns>Sortierte Liste mit Eintr�gen und Werten</returns>
	public SortedList<string, string> getCaption(string Caption)
	{
		return getCaption(Caption, false);
	}
	
	/// <summary>
	/// Erstellt eine Liste aller enthaltenen Bereiche
	/// </summary>
	/// <returns>Liste mit gefundenen Bereichen</returns>
	public List<string> getAllCaptions()
	{
		List<string> result = new List<string>();
		for(int i = 0; i < lines.Count; i++)
		{
			string line = lines[i];
			Match mCaption = regCaption.Match(lines[i]);
			if(mCaption.Success)
				result.Add(mCaption.Groups["caption"].Value.Trim());
		}
		return result;
	}
	
	/// <summary>
	/// Exportiert s�mtliche Bereiche und deren Werte in ein XML-Dokument
	/// </summary>
	/// <returns>XML-Dokument</returns>
	public XmlDocument exportToXml()
	{
		XmlDocument doc = new XmlDocument();
		XmlElement root = null;
		
		root = doc.CreateElement(Path.GetFileNameWithoutExtension(this.fileName));
		
		doc.AppendChild(root);
		XmlElement Caption = null;
		
		for(int i = 0; i < lines.Count; i++)
		{
			Match mEntry = regEntry.Match(lines[i]);
			Match mCaption = regCaption.Match(lines[i]);
			if(mCaption.Success)
			{
				Caption = doc.CreateElement(mCaption.Groups["caption"].Value.Trim());
				root.AppendChild(Caption);
				continue;
			}
			
			if(mEntry.Success)
			{
				XmlElement xe = doc.CreateElement(mEntry.Groups["entry"].Value.Trim());
				xe.InnerXml = mEntry.Groups["value"].Value.Trim();
				if(Caption == null)
					root.AppendChild(xe);
				else
					Caption.AppendChild(xe);
			}
		}
		return doc;
	}
}

7 Kommentare zum Snippet

Christopher Kk schrieb am 12/24/2009:
Find ich ganz hilfreich. Aber was ich mir noch geiler vorstellen könnte. Wenn deine Klasse sowas wie ein Dictionary wäre. Als keys sind die konfigurationspunkte eingetragen und die values sind dann eben die werte für den konfigurationspunkt.

Das man dann zum beispiel so hier drauf zugreifen könnte.

Console.Writeline(einIniFile["Laufwerk"]);
//Ausgabe: C:/
//oder
einIniFile["Hintergrundfarbe"] = "Blau";

Sowas wäre richtig geil.
Ansonsten 9 Punkte von mir.
M1C schrieb am 12/27/2009:
Gute Idee aber was währe bei folgenden Fall:

[Section1]
EntryBla=asd
EntryBup=bnm

[Section2]
EntryBla=NotAsd

Und jetzt der Zugriff:
einIniFile["EntryBla"]
Welches wird jetzt genommen?
Oder gibt es eine Dictory-Klasse mit 2 Keys? Ich hab keine gefunden =(

mfG
Christopher Kk schrieb am 12/27/2009:
Naja warum 2 keys. Dein Key ist "Section1", deine Values dahinter sind wiederum Dictionarys mit dem Key EntryBla und EntryBlub und hier sind die Values dann die richtigen Values sozusagen. also eigentlich muss man nur ein Dictionar<string,Dictionary<string,string>> machen wenn ich jetzt nicht komplett falsch denke.
M1C schrieb am 12/27/2009:
is drin =)
Stephan Knecht schrieb am 11/3/2010:
Hallo,
den größten Teil vom Code verstehe ich ja, aber was mich interessieren würde ist:

Wie sieht der Aufruf in einem C# Programm aus wenn ich mich auf diesen Teil der Klasse beziehen will um z.B. einen Wert zu setzen.

	/// <summary>
/// �nderrungen an einem Eintrag vornemen. Achtung CaseSensitive!
/// </summary>
public string this[string Caption, string Entry]
{
get
{
return getValue(Caption, Entry, true);
}
set
{
setValue(Caption, Entry, value.ToString(), true, false);
}
}


Gruß Stephan
M1C schrieb am 3/18/2011:
zB so:


INIFile = new INIFile();
INIFile["foo", "bar"] = "some text";

produziert das:

[foo]
bar=some text
dariusarnold schrieb am 4/15/2013:
Hey..
Ich bin mir nicht ganz sicher. Aber bei mir hat das ganze noch nicht funktioniert:

public bool SaveAs(string file)
{
if (fileName == "") return false;
try
{
File.WriteAllLines(fileName, lines.ToArray());
}
catch (IOException ex)
{
throw new IOException("Fehler beim Schreiben der Datei " + fileName, ex);
}
catch (Exception ex)
{
throw new IOException("Fehler beim Schreiben der Datei " + fileName, ex);
}
return true;
}


Welchen Sinn hat der file Parameter??
Schließlich wird fileName beim normalen Speichern als file übergeben..
Man muss demnach doch einfach file anstatt fileName in der Methode verwenden?! Bei mir jedenfalls klappt es jetzt :)

Diese Änderung wäre vielleicht noch Sinnvoll, obwohl die normale Save-Methode natürlich Funktioniert, da beim 'Nicht-Standard-Kunstruktor', fileName zugewiesen wird. Und ohne den geht die 'Nur'-Save()-Methode ja nicht.
 

Logge dich ein, um hier zu kommentieren!