Odczytywanie / zapisywanie pliku INI


263

Czy w środowisku .NET jest jakaś klasa, która może odczytywać / zapisywać standardowe pliki .ini:

[Section]
<keyname>=<value>
...

Delphi ma TIniFilekomponent i chcę wiedzieć, czy jest coś podobnego do C #?


RemObjects ma bibliotekę Delphi Prism o nazwie ShineOn, która dostarcza podobną klasę plików INI. Ale musisz mieć Delphi Prism, aby skompilować go dla .NET ze źródła, ponieważ nie jest jeszcze dostępny skompilowany zestaw. code.remobjects.com/p/shineon
Lex Li

1
Mam ten sam problem i stworzyłem własną bibliotekę do analizowania plików ini: github.com/rickyah/ini-parser Mam nadzieję, że to pomaga
Ricardo Amores

5
Podobnie jak Ricky postanowiłem stworzyć własne rozwiązanie tego problemu. Jest dostępny na: github.com/MarioZ/MadMilkman.Ini
Mario Z

Odpowiedzi:


185

Twórcy frameworka .NET chcą, abyś używał plików konfiguracyjnych opartych na XML, a nie plików INI. Więc nie, nie ma wbudowanego mechanizmu ich odczytu.

Dostępne są jednak rozwiązania innych firm.


24
Chociaż prawdą jest, że pliki konfiguracyjne XML są właściwą drogą, nadal nie jest to odpowiedź na pytanie lub jest to VLQ tylko dla łącza.
Danny Beckett

6
@aloneguid Twierdziłbym, że duży zestaw dostępnych funkcji faktycznie przyczynił się do tego, że pliki konfiguracyjne .NET stały się dziwnymi behemotami z dużą ilością magii. Stały się „kodem w pliku konfiguracyjnym”, co prowadzi do dużej złożoności, dziwnych zachowań i utrudnia zarządzanie konfiguracją. (Patrzę na ciebie, „dostawców” bazy danych i parametry połączenia.) Pliki INI są więc również ogólnie lepsze do edycji nie ręcznej.
jpmc26

1
lubię starą metodę (P / Inovke) i możesz używać Unicode ze starą metodą, taką jak: File.WriteAllBytes (ścieżka, nowy bajt [] {0xFF, 0xFE});
sailfish009

2
Dobry pakiet, ale może być lepszy. Nie można całkowicie przeanalizować wartości zawierającej „=” lub „\ n”
Ahmad Behjati,

211

Przedmowa

Najpierw przeczytaj ten post na blogu MSDN na temat ograniczeń plików INI . Jeśli odpowiada Twoim potrzebom, czytaj dalej.

Jest to zwięzłe wdrożenie, które napisałem przy użyciu oryginalnego P / Invoke systemu Windows, więc jest obsługiwane przez wszystkie wersje systemu Windows z zainstalowanym programem .NET (tj. Windows 98 - Windows 10). Niniejszym udostępniam go w domenie publicznej - możesz swobodnie korzystać z niego w celach komercyjnych bez podawania źródła.

Mała klasa

Dodaj nową klasę IniFile.csdo swojego projektu:

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

// Change this to match your program's normal namespace
namespace MyProg
{
    class IniFile   // revision 11
    {
        string Path;
        string EXE = Assembly.GetExecutingAssembly().GetName().Name;

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern long WritePrivateProfileString(string Section, string Key, string Value, string FilePath);

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern int GetPrivateProfileString(string Section, string Key, string Default, StringBuilder RetVal, int Size, string FilePath);

        public IniFile(string IniPath = null)
        {
            Path = new FileInfo(IniPath ?? EXE + ".ini").FullName;
        }

        public string Read(string Key, string Section = null)
        {
            var RetVal = new StringBuilder(255);
            GetPrivateProfileString(Section ?? EXE, Key, "", RetVal, 255, Path);
            return RetVal.ToString();
        }

        public void Write(string Key, string Value, string Section = null)
        {
            WritePrivateProfileString(Section ?? EXE, Key, Value, Path);
        }

        public void DeleteKey(string Key, string Section = null)
        {
            Write(Key, null, Section ?? EXE);
        }

        public void DeleteSection(string Section = null)
        {
            Write(null, null, Section ?? EXE);
        }

        public bool KeyExists(string Key, string Section = null)
        {
            return Read(Key, Section).Length > 0;
        }
    }
}

Jak tego użyć

Otwórz plik INI na jeden z 3 następujących sposobów:

// Creates or loads an INI file in the same directory as your executable
// named EXE.ini (where EXE is the name of your executable)
var MyIni = new IniFile();

// Or specify a specific name in the current dir
var MyIni = new IniFile("Settings.ini");

// Or specify a specific name in a specific dir
var MyIni = new IniFile(@"C:\Settings.ini");

Możesz napisać kilka takich wartości:

MyIni.Write("DefaultVolume", "100");
MyIni.Write("HomePage", "http://www.google.com");

Aby utworzyć taki plik:

[MyProg]
DefaultVolume=100
HomePage=http://www.google.com

Aby odczytać wartości z pliku INI:

var DefaultVolume = IniFile.Read("DefaultVolume");
var HomePage = IniFile.Read("HomePage");

Opcjonalnie możesz ustawić [Section]:

MyIni.Write("DefaultVolume", "100", "Audio");
MyIni.Write("HomePage", "http://www.google.com", "Web");

Aby utworzyć taki plik:

[Audio]
DefaultVolume=100

[Web]
HomePage=http://www.google.com

Możesz także sprawdzić, czy istnieje taki klucz:

if(!MyIni.KeyExists("DefaultVolume", "Audio"))
{
    MyIni.Write("DefaultVolume", "100", "Audio");
}

Możesz usunąć klucz w ten sposób:

MyIni.DeleteKey("DefaultVolume", "Audio");

Możesz również usunąć całą sekcję (w tym wszystkie klucze) w następujący sposób:

MyIni.DeleteSection("Web");

Skomentuj wszelkie ulepszenia!


4
Trochę się spóźniam, ale brakuje mi GetSections()metody.
stil

2
Być może bardziej tradycyjnym ustawieniem domyślnym byłyby pliki .ini dla aplikacji (nie dla zestawu) Path.GetFullPath(IniPath ?? Path.ChangeExtension(Application.ExecutablePath, ".ini")).
Eugene Ryabtsev

3
Naprawdę świetnie ! Umieścić to na github?
Emrys Myrooin

2
@ Danny Beckett, ładnie zrobione. Jest to prawie dokładnie to samo, co używane przez ostatnie lata .Net. Ulepszony ze starego kodu lata temu.
Damian

10
Teraz stary i, o ile szanuję Raymonda Chena, wiele ograniczeń w tym artykule dotyczyło ograniczeń konkretnej biblioteki INI w systemie Windows, a nie samego formatu INI. Inne, takie jak uprawnienia granularne, można łatwo ominąć za pomocą wielu plików. Urzędnik , zmodernizowana biblioteka INI byłyby najbardziej mile widziane, nawet dzisiaj.
Joel Coehoorn

68

Ten artykuł na temat CodeProject „ Klasa obsługi plików INI przy użyciu C # ” powinna pomóc.

Autor stworzył klasę C # „Ini”, która udostępnia dwie funkcje z KERNEL32.dll. Te funkcje to: WritePrivateProfileStringi GetPrivateProfileString. Potrzebne będą dwie przestrzenie nazw: System.Runtime.InteropServicesi System.Text.

Kroki korzystania z klasy Ini

W definicji przestrzeni nazw projektu dodaj

using INI;

Utwórz taki plik INIF

INIFile ini = new INIFile("C:\\test.ini");

Służy IniWriteValuedo zapisywania nowej wartości do określonego klucza w sekcji lub IniReadValuedo odczytu wartości z klucza w określonej sekcji.

Uwaga: jeśli zaczynasz od zera, możesz przeczytać ten artykuł MSDN : Porady: dodawanie plików konfiguracji aplikacji do projektów w języku C # . To lepszy sposób na skonfigurowanie aplikacji.


1
Chcę przeczytać cały plik INI. Jak zrobić to samo zamiast czytać sekcję, klucz
venkat

to działało dla mnie, a potem przestało działać z innego punktu. Nie mam pojęcia, co się zmieniło pod maską
nawfal

1
Uważaj, korzystając z przestarzałych funkcji API Win32. Więcej informacji: stackoverflow.com/questions/11451641/…
Pedro77

Przez jakiś czas korzystałem z tego podejścia, ale ulepszenia zabezpieczeń, począwszy od Win7, prawie dla mnie to zabiły. Nadal możesz używać tego podejścia, ale będziesz przechowywać .ini w ProgramData i tam będzie zapisywać / zapisywać aplikację.
Jess,

Nie zapisuj plików ini konfiguracji aplikacji w ProgramData. Nie należą one ani do Rejestru, ani do ProgramData. Pliki konfiguracyjne powinny znajdować się w folderach LocalApplicationData.
deegee

47

Znalazłem tę prostą implementację:

http://bytes.com/topic/net/insights/797169-reading-parsing-ini-file-c

Działa dobrze na to, czego potrzebuję.

Oto jak z niego korzystasz:

public class TestParser
{
    public static void Main()
    {
        IniParser parser = new IniParser(@"C:\test.ini");

        String newMessage;

        newMessage = parser.GetSetting("appsettings", "msgpart1");
        newMessage += parser.GetSetting("appsettings", "msgpart2");
        newMessage += parser.GetSetting("punctuation", "ex");

        //Returns "Hello World!"
        Console.WriteLine(newMessage);
        Console.ReadLine();
    }
}

Oto kod:

using System;
using System.IO;
using System.Collections;

public class IniParser
{
    private Hashtable keyPairs = new Hashtable();
    private String iniFilePath;

    private struct SectionPair
    {
        public String Section;
        public String Key;
    }

    /// <summary>
    /// Opens the INI file at the given path and enumerates the values in the IniParser.
    /// </summary>
    /// <param name="iniPath">Full path to INI file.</param>
    public IniParser(String iniPath)
    {
        TextReader iniFile = null;
        String strLine = null;
        String currentRoot = null;
        String[] keyPair = null;

        iniFilePath = iniPath;

        if (File.Exists(iniPath))
        {
            try
            {
                iniFile = new StreamReader(iniPath);

                strLine = iniFile.ReadLine();

                while (strLine != null)
                {
                    strLine = strLine.Trim().ToUpper();

                    if (strLine != "")
                    {
                        if (strLine.StartsWith("[") && strLine.EndsWith("]"))
                        {
                            currentRoot = strLine.Substring(1, strLine.Length - 2);
                        }
                        else
                        {
                            keyPair = strLine.Split(new char[] { '=' }, 2);

                            SectionPair sectionPair;
                            String value = null;

                            if (currentRoot == null)
                                currentRoot = "ROOT";

                            sectionPair.Section = currentRoot;
                            sectionPair.Key = keyPair[0];

                            if (keyPair.Length > 1)
                                value = keyPair[1];

                            keyPairs.Add(sectionPair, value);
                        }
                    }

                    strLine = iniFile.ReadLine();
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (iniFile != null)
                    iniFile.Close();
            }
        }
        else
            throw new FileNotFoundException("Unable to locate " + iniPath);

    }

    /// <summary>
    /// Returns the value for the given section, key pair.
    /// </summary>
    /// <param name="sectionName">Section name.</param>
    /// <param name="settingName">Key name.</param>
    public String GetSetting(String sectionName, String settingName)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        return (String)keyPairs[sectionPair];
    }

    /// <summary>
    /// Enumerates all lines for given section.
    /// </summary>
    /// <param name="sectionName">Section to enum.</param>
    public String[] EnumSection(String sectionName)
    {
        ArrayList tmpArray = new ArrayList();

        foreach (SectionPair pair in keyPairs.Keys)
        {
            if (pair.Section == sectionName.ToUpper())
                tmpArray.Add(pair.Key);
        }

        return (String[])tmpArray.ToArray(typeof(String));
    }

    /// <summary>
    /// Adds or replaces a setting to the table to be saved.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    /// <param name="settingValue">Value of key.</param>
    public void AddSetting(String sectionName, String settingName, String settingValue)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        if (keyPairs.ContainsKey(sectionPair))
            keyPairs.Remove(sectionPair);

        keyPairs.Add(sectionPair, settingValue);
    }

    /// <summary>
    /// Adds or replaces a setting to the table to be saved with a null value.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    public void AddSetting(String sectionName, String settingName)
    {
        AddSetting(sectionName, settingName, null);
    }

    /// <summary>
    /// Remove a setting.
    /// </summary>
    /// <param name="sectionName">Section to add under.</param>
    /// <param name="settingName">Key name to add.</param>
    public void DeleteSetting(String sectionName, String settingName)
    {
        SectionPair sectionPair;
        sectionPair.Section = sectionName.ToUpper();
        sectionPair.Key = settingName.ToUpper();

        if (keyPairs.ContainsKey(sectionPair))
            keyPairs.Remove(sectionPair);
    }

    /// <summary>
    /// Save settings to new file.
    /// </summary>
    /// <param name="newFilePath">New file path.</param>
    public void SaveSettings(String newFilePath)
    {
        ArrayList sections = new ArrayList();
        String tmpValue = "";
        String strToSave = "";

        foreach (SectionPair sectionPair in keyPairs.Keys)
        {
            if (!sections.Contains(sectionPair.Section))
                sections.Add(sectionPair.Section);
        }

        foreach (String section in sections)
        {
            strToSave += ("[" + section + "]\r\n");

            foreach (SectionPair sectionPair in keyPairs.Keys)
            {
                if (sectionPair.Section == section)
                {
                    tmpValue = (String)keyPairs[sectionPair];

                    if (tmpValue != null)
                        tmpValue = "=" + tmpValue;

                    strToSave += (sectionPair.Key + tmpValue + "\r\n");
                }
            }

            strToSave += "\r\n";
        }

        try
        {
            TextWriter tw = new StreamWriter(newFilePath);
            tw.Write(strToSave);
            tw.Close();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// Save settings back to ini file.
    /// </summary>
    public void SaveSettings()
    {
        SaveSettings(iniFilePath);
    }
}

38
+1 do przesunięcia powyżej downvote. Na co tak naprawdę narzekasz? Powiedział, że ZNALEZIŁ. Czy zlekceważysz go za to, że nie znalazł takiego z typowymi akcesoriami i narzędziem do budowania ciągów?
Tormod

1
@Tormod: Chciałbym głosować za komentarzem. To forum techniczne, kiedy głosujemy na rozwiązania, a nie (oczywiście pozytywnie). Gdyby rozwiązanie opublikowane przez samego Knutha miało wady, należałoby - i należy - wskazać. Nie ma znaczenia, czy rozwiązanie zostało znalezione, czy napisane przez plakat.
23

7
Myślę, że rozszerzasz definicję „wady”. Jeśli rozwiązanie nie podkreśla twoich wrażliwości, po prostu nie głosuj pozytywnie. Właśnie zostawiłem notatkę z informacją, że już zlekceważyłem jego opinię, aby pozostali 7 facetów, którzy głosowali mój komentarz, nie zrobiliby tego sami.
Tormod

21

Kod w odpowiedzi joerage jest inspirujący.

Niestety zmienia obudowę znaków klawiszy i nie obsługuje komentarzy. Napisałem więc coś, co powinno być wystarczająco solidne, aby odczytać (tylko) bardzo brudne pliki INI i pozwala odzyskać klucze takimi, jakie są.

Korzysta z LINQ, zagnieżdżonego słownika ciągów bez rozróżniania wielkości liter, do przechowywania sekcji, kluczy i wartości oraz odczytywania pliku za jednym razem.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class IniReader
{
    Dictionary<string, Dictionary<string, string>> ini = new Dictionary<string, Dictionary<string, string>>(StringComparer.InvariantCultureIgnoreCase);

    public IniReader(string file)
    {
        var txt = File.ReadAllText(file);

        Dictionary<string, string> currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

        ini[""] = currentSection;

        foreach(var line in txt.Split(new[]{"\n"}, StringSplitOptions.RemoveEmptyEntries)
                               .Where(t => !string.IsNullOrWhiteSpace(t))
                               .Select(t => t.Trim()))
        {
            if (line.StartsWith(";"))
                continue;

            if (line.StartsWith("[") && line.EndsWith("]"))
            {
                currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
                ini[line.Substring(1, line.LastIndexOf("]") - 1)] = currentSection;
                continue;
            }

            var idx = line.IndexOf("=");
            if (idx == -1)
                currentSection[line] = "";
            else
                currentSection[line.Substring(0, idx)] = line.Substring(idx + 1);
        }
    }

    public string GetValue(string key)
    {
        return GetValue(key, "", "");
    }

    public string GetValue(string key, string section)
    {
        return GetValue(key, section, "");
    }

    public string GetValue(string key, string section, string @default)
    {
        if (!ini.ContainsKey(section))
            return @default;

        if (!ini[section].ContainsKey(key))
            return @default;

        return ini[section][key];
    }

    public string[] GetKeys(string section)
    {
        if (!ini.ContainsKey(section))
            return new string[0];

        return ini[section].Keys.ToArray();
    }

    public string[] GetSections()
    {
        return ini.Keys.Where(t => t != "").ToArray();
    }
}

4
i dziękuję, że tego catch (Exception ex) { throw ex; }nie
umieściłeś

1
Dobry! Konieczne są przynajmniej niektóre zmiany, aby działały lepiej. Wiersz 16: ini [""] = currentSection; Do: // ini [""] = currentSection; Należy to usunąć, ponieważ za każdym razem pierwszy element [0] będzie pustym segmentem z powodu tej inicjalizacji. Linia 36: currentSection [line.Substring (0, idx)] = line.Substring (idx + 1); Do: currentSection [line.Substring (0, idx) .Trim ()] = line.Substring (idx + 1) .Trim (); Klucz i wartości należy przycinać niezależnie, nie tylko na linii Przytnij. W plikach konfiguracyjnych podobnych do INI zwykle osoby, które dodają pary K-> V, mają tendencję do wyrównywania tych równości w sekcjach. Dziękuję Ci!
LXSoft

Byłem długo. Wielkie dzięki za sugestie. Wszystkie mają sens i zasługują na ten kod, aby mieć dobre odświeżenie.
Larry,

13

Chcę wprowadzić bibliotekę IniParser, którą utworzyłem całkowicie w języku c #, więc nie zawiera żadnych zależności w żadnym systemie operacyjnym, co czyni ją kompatybilną z Mono. Open Source z licencją MIT - więc można go używać w dowolnym kodzie.

Możesz sprawdzić źródło w GitHub , jest ono również dostępne jako pakiet NuGet

Jest wysoce konfigurowalny i bardzo prosty w użyciu .

Przepraszam za bezwstydną wtyczkę, ale mam nadzieję, że może pomóc każdemu, kto zmieni tę odpowiedź.


4

Jeśli potrzebujesz tylko dostępu do odczytu, a nie dostępu do zapisu i używasz Microsoft.Extensions.Confiuration(domyślnie jest dostarczany w pakiecie z programem ASP.NET Core, ale działa również ze zwykłymi programami), możesz użyć pakietu NuGet Microsoft.Extensions.Configuration.Inido importowania plików ini do ustawień konfiguracji.

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddIniFile("SomeConfig.ini", optional: false);
    Configuration = builder.Build();
}

Wystarczy dodać, że otrzymujesz klucze za pomocąConfiguration["keyname"]
kofifus

@ scott Mam problem z jakiegokolwiek powodu, że IIS nie rozpoznaje go, gdy aplikacja jest uruchomiona. jest wdrożony i tam, ale nie jest konsumowany. HTTP 500.30 powrócił, a dziennik aplikacji IIS mówi „plik konfiguracyjny nie został znaleziony i nie jest opcjonalny”.
one.beat.consumer

3

Zwykle podczas tworzenia aplikacji przy użyciu C # i środowiska .NET nie używa się plików INI. Bardziej powszechne jest przechowywanie ustawień w pliku konfiguracyjnym opartym na XML lub w rejestrze. Jeśli jednak oprogramowanie współdzieli ustawienia ze starszą aplikacją, może być łatwiej użyć pliku konfiguracyjnego, niż powielać informacje w innym miejscu.

.NET Framework nie obsługuje bezpośredniego korzystania z plików INI. Możesz jednak używać funkcji Windows API z Platform Invocation Services (P / Invoke) do zapisu i odczytu z plików. W tym linku tworzymy klasę, która reprezentuje pliki INI i używa funkcji Windows API do manipulowania nimi. Proszę przejść przez poniższy link.

Odczytywanie i zapisywanie plików INI


4
Trzymaj się z dala od rejestru! Dane konfiguracji aplikacji nie powinny być zapisywane w rejestrze.
deegee

3

Jeśli chcesz tylko prosty czytnik bez sekcji i innych bibliotek DLL, oto proste rozwiązanie:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tool
{
    public class Config
    {
        Dictionary <string, string> values;
        public Config (string path)
        {
            values = File.ReadLines(path)
            .Where(line => (!String.IsNullOrWhiteSpace(line) && !line.StartsWith("#")))
            .Select(line => line.Split(new char[] { '=' }, 2, 0))
            .ToDictionary(parts => parts[0].Trim(), parts => parts.Length>1?parts[1].Trim():null);
        }
        public string Value (string name, string value=null)
        {
            if (values!=null && values.ContainsKey(name))
            {
                return values[name];
            }
            return value;
        }
    }
}

Przykład użycia:

    file = new Tool.Config (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\config.ini");
    command = file.Value ("command");
    action = file.Value ("action");
    string value;
    //second parameter is default value if no key found with this name
    value = file.Value("debug","true");
    this.debug = (value.ToLower()=="true" || value== "1");
    value = file.Value("plain", "false");
    this.plain = (value.ToLower() == "true" || value == "1");

Tymczasem konfiguruj zawartość pliku (jak widzisz obsługuje # symbol komentarza do linii):

#command to run
command = php

#default script
action = index.php

#debug mode
#debug = true

#plain text mode
#plain = false

#icon = favico.ico

3

Wypróbuj tę metodę:

public static Dictionary<string, string> ParseIniDataWithSections(string[] iniData)
{
    var dict = new Dictionary<string, string>();
    var rows = iniData.Where(t => 
        !String.IsNullOrEmpty(t.Trim()) && !t.StartsWith(";") && (t.Contains('[') || t.Contains('=')));
    if (rows == null || rows.Count() == 0) return dict;
    string section = "";
    foreach (string row in rows)
    {
        string rw = row.TrimStart();
        if (rw.StartsWith("["))
            section = rw.TrimStart('[').TrimEnd(']');
        else
        {
            int index = rw.IndexOf('=');
            dict[section + "-" + rw.Substring(0, index).Trim()] = rw.Substring(index+1).Trim().Trim('"');
        }
    }
    return dict;
}

Tworzy słownik, w którym klucz to „-”. Możesz załadować w następujący sposób:

var dict = ParseIniDataWithSections(File.ReadAllLines(fileName));

3

PeanutButter.INI jest Nuget zapakowane klasa dla plików INI manipulacji. Obsługuje odczyt / zapis, w tym komentarze - komentarze są zapisywane podczas zapisu. Wydaje się być dość popularny, jest przetestowany i łatwy w użyciu. Jest również całkowicie darmowy i open source.

Oświadczenie: Jestem autorem PeanutButter.INI.


Czy możesz podać link do dokumentacji PeanutButter.INI?
Shroombaker


3

Jestem spóźniony, aby dołączyć do imprezy, ale miałem dzisiaj ten sam problem i napisałem następującą implementację:

using System.Text.RegularExpressions;

static bool match(this string str, string pat, out Match m) =>
    (m = Regex.Match(str, pat, RegexOptions.IgnoreCase)).Success;

static void Main()
{
    Dictionary<string, Dictionary<string, string>> ini = new Dictionary<string, Dictionary<string, string>>();
    string section = "";

    foreach (string line in File.ReadAllLines(.........)) // read from file
    {
        string ln = (line.Contains('#') ? line.Remove(line.IndexOf('#')) : line).Trim();

        if (ln.match(@"^[ \t]*\[(?<sec>[\w\-]+)\]", out Match m))
            section = m.Groups["sec"].ToString();
        else if (ln.match(@"^[ \t]*(?<prop>[\w\-]+)\=(?<val>.*)", out m))
        {
            if (!ini.ContainsKey(section))
                ini[section] = new Dictionary<string, string>();

            ini[section][m.Groups["prop"].ToString()] = m.Groups["val"].ToString();
        }
    }


    // access the ini file as follows:
    string content = ini["section"]["property"];
}

Należy zauważyć, że ta implementacja nie obsługuje sekcji ani właściwości, które nie zostały znalezione. Aby to osiągnąć, powinieneś rozszerzyć Dictionary<,>klasę o obsługę nieuzasadnionych kluczy.


Aby serializować wystąpienie Dictionary<string, Dictionary<string, string>>pliku .ini-file, używam następującego kodu:

string targetpath = .........;
Dictionary<string, Dictionary<string, string>> ini = ........;
StringBuilder sb = new StringBuilder();

foreach (string section in ini.Keys)
{
    sb.AppendLine($"[{section}]");

    foreach (string property in ini[section].Keys)
        sb.AppendLine($"{property}={ini[section][property]");
}

File.WriteAllText(targetpath, sb.ToString());

2

W CommonLibrary.NET dostępny jest Ini Parser

Ma to różne bardzo wygodne przeciążenia do uzyskiwania przekrojów / wartości i jest bardzo lekki.


1
W przypadku, gdy nie jest to oczywiste, patrząc na najwyższy poziom biblioteki (nie było to dla mnie oczywiste!), Klasa IniDcoument i inni są w ComLib.IO.
Tim Keating

2
Dla każdego, kto patrzy na tę trasę, CommonLibrary.NET nie wydaje się przestrzegać konwencji .INI. Używa dwukropka „:” jako separatora zamiast znaku równości i nie obsługuje komentarzy (rozpoczęcie linii znakiem średnika lub funta spowoduje błąd analizy).
jmmr

2

Oto moja własna wersja, wykorzystująca wyrażenia regularne. Ten kod zakłada, że ​​nazwa każdej sekcji jest unikalna - jeśli jednak nie jest to prawdą - sensowne jest zastąpienie słownika Listą. Ta funkcja obsługuje komentowanie plików .ini, zaczynając od „;” postać. Sekcja zaczyna się normalnie [sekcja], a pary klucz-wartość również mają normalnie „klucz = wartość”. Takie samo założenie jak dla sekcji - nazwa klucza jest unikalna.

/// <summary>
/// Loads .ini file into dictionary.
/// </summary>
public static Dictionary<String, Dictionary<String, String>> loadIni(String file)
{
    Dictionary<String, Dictionary<String, String>> d = new Dictionary<string, Dictionary<string, string>>();

    String ini = File.ReadAllText(file);

    // Remove comments, preserve linefeeds, if end-user needs to count line number.
    ini = Regex.Replace(ini, @"^\s*;.*$", "", RegexOptions.Multiline);

    // Pick up all lines from first section to another section
    foreach (Match m in Regex.Matches(ini, "(^|[\r\n])\\[([^\r\n]*)\\][\r\n]+(.*?)(\\[([^\r\n]*)\\][\r\n]+|$)", RegexOptions.Singleline))
    {
        String sectionName = m.Groups[2].Value;
        Dictionary<String, String> lines = new Dictionary<String, String>();

        // Pick up "key = value" kind of syntax.
        foreach (Match l in Regex.Matches(ini, @"^\s*(.*?)\s*=\s*(.*?)\s*$", RegexOptions.Multiline))
        {
            String key = l.Groups[1].Value;
            String value = l.Groups[2].Value;

            // Open up quotation if any.
            value = Regex.Replace(value, "^\"(.*)\"$", "$1");

            if (!lines.ContainsKey(key))
                lines[key] = value;
        }

        if (!d.ContainsKey(sectionName))
            d[sectionName] = lines;
    }

    return d;
}

Ta funkcja nie działa, dla mnie: Zapomina jedną sekcję na dwie części. Próbowałem przed i bez pustych linii przed [Sekcją].
iksess

czy możesz skopiować przykład swojego pliku .ini, który nie działa?
TarmoPikaro

-3

Oto moja klasa, działa jak urok:

public static class IniFileManager
{


    [DllImport("kernel32")]
    private static extern long WritePrivateProfileString(string section,
        string key, string val, string filePath);
    [DllImport("kernel32")]
    private static extern int GetPrivateProfileString(string section,
             string key, string def, StringBuilder retVal,
        int size, string filePath);
    [DllImport("kernel32.dll")]
    private static extern int GetPrivateProfileSection(string lpAppName,
             byte[] lpszReturnBuffer, int nSize, string lpFileName);


    /// <summary>
    /// Write Data to the INI File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// Section name
    /// <PARAM name="Key"></PARAM>
    /// Key Name
    /// <PARAM name="Value"></PARAM>
    /// Value Name
    public static void IniWriteValue(string sPath,string Section, string Key, string Value)
    {
        WritePrivateProfileString(Section, Key, Value, sPath);
    }

    /// <summary>
    /// Read Data Value From the Ini File
    /// </summary>
    /// <PARAM name="Section"></PARAM>
    /// <PARAM name="Key"></PARAM>
    /// <PARAM name="Path"></PARAM>
    /// <returns></returns>
    public static string IniReadValue(string sPath,string Section, string Key)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(Section, Key, "", temp,
                                        255, sPath);
        return temp.ToString();

    }

}

Użycie jest obviouse, ponieważ jest to klasa statyczna, wystarczy wywołać IniFileManager.IniWriteValue do odczytu sekcji lub IniFileManager.IniReadValue do odczytu sekcji.


To podejście zostało już wykazane i wyjaśnione w innej odpowiedzi . Co dodaje twoja odpowiedź, której nie obejmuje?
Palec

Uważaj, że to działa tylko wtedy, gdy plik .ini jest zapisany w UNICODE (16bit LE). Użyj Notepad ++, aby przekonwertować tekst na Unicode, ponieważ jeśli zapiszesz go w UTF-8, nie zadziała. Akceptowany jest również ANSI, ale nie można czytać liter akcentowanych
użytkownik2991288

-6

Powinieneś czytać i zapisywać dane z plików xml, ponieważ możesz zapisać cały obiekt do xml, a także możesz wypełnić obiekt z zapisanego xml. Lepiej jest łatwo manipulować obiektami.

Oto jak to zrobić: Zapisz dane obiektu do pliku XML: https://msdn.microsoft.com/en-us/library/ms172873.aspx Odczytaj dane obiektu z pliku XML: https://msdn.microsoft. com / en-us / library / ms172872.aspx


1
Zalecane są linki do zasobów zewnętrznych, ale dodaj kontekst wokół linku, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego. Zawsze podawaj najistotniejszą część ważnego linku, na wypadek gdyby strona docelowa była nieosiągalna lub została trwale wyłączona.
davejal

Uważam, że tytuły linków są bardzo jasne na temat odniesień / kontekstu. Jeśli uważasz, że to za mało, możesz go edytować.
Daniel

1
Nie odnosi się do rzeczywistego pytania.
Erik Knowles
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.