Jaki jest najłatwiejszy sposób przeanalizowania pliku INI w Javie?

104

Piszę zastępstwo dla starszej aplikacji w Javie. Jednym z wymagań jest to, że pliki ini używane przez starszą aplikację muszą być wczytywane w nowej aplikacji Java. Format tych plików ini to typowy styl systemu Windows, z sekcjami nagłówka i parami klucz = wartość, przy użyciu znaku # jako znaku komentarza.

Próbowałem użyć klasy Właściwości z języka Java, ale oczywiście to nie zadziała, jeśli występują konflikty nazw między różnymi nagłówkami.

Pytanie brzmi, jaki byłby najłatwiejszy sposób odczytania tego pliku INI i uzyskania dostępu do kluczy?

Mario Ortegón
źródło

Odpowiedzi:

121

Biblioteka, której użyłem, to ini4j . Jest lekki i z łatwością analizuje pliki ini. Nie używa też żadnych ezoterycznych zależności od 10000 innych plików jar, ponieważ jednym z celów projektowych było użycie tylko standardowego interfejsu API języka Java

Oto przykład wykorzystania biblioteki:

Ini ini = new Ini(new File(filename));
java.util.prefs.Preferences prefs = new IniPreferences(ini);
System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));
Mario Ortegón
źródło
2
nie działa, błąd mówi „Nie można przekształcić pliku IniFile w typ”
Caballero
@Caballero tak, wygląda na to, że IniFilezajęcia zostały wyjęte, spróbujIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html prawdopodobnie pozostanie na bieżąco, nawet jeśli ponownie zmienią nazwę klasy.
Lokathor,
Czy to już w ogóle działa? Pobrałem źródło 0.5.4 i nawet się nie skompilowało, i nie była to brakująca zależność ... nie warta czasu, żeby się tym bardziej przejmować. Również ini4j ma wiele innych bzdur, których nie potrzebujemy, edycja rejestru Windoze ... daj spokój. #LinuxMasterRace ... Ale myślę, że jeśli to działa dla ciebie, znokautuj się.
Użytkownik
W przypadku pliku INI, który napisałem, musiałem użyć Winiklasy, jak pokazano w samouczku „Jedna minuta”; prefs.node("something").get("val", null)nie działa tak, jak oczekiwano.
Agi Hammerthief
65

Jak wspomniano , można to osiągnąć za pomocą ini4j . Pokażę inny przykład.

Jeśli mamy taki plik INI:

[header]
key = value

W valueSTDOUT powinien zostać wyświetlony następujący komunikat :

Ini ini = new Ini(new File("/path/to/file"));
System.out.println(ini.get("header", "key"));

Sprawdź tutoriale więcej przykładów.

tshepang
źródło
2
Schludny! Zawsze używałem BufferedReader i trochę kopiuj / wklej kodu parsującego ciąg, aby nie musieć dodawać kolejnej zależności do moich aplikacji (która może wyskoczyć z proporcji, gdy zaczniesz dodawać zewnętrzne API nawet dla najprostszych zadań ). Ale nie mogę zignorować tego rodzaju prostoty.
Gimby,
30

Tak proste, jak 80 linii:

package windows.prefs;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IniFile {

   private Pattern  _section  = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*" );
   private Pattern  _keyValue = Pattern.compile( "\\s*([^=]*)=(.*)" );
   private Map< String,
      Map< String,
         String >>  _entries  = new HashMap<>();

   public IniFile( String path ) throws IOException {
      load( path );
   }

   public void load( String path ) throws IOException {
      try( BufferedReader br = new BufferedReader( new FileReader( path ))) {
         String line;
         String section = null;
         while(( line = br.readLine()) != null ) {
            Matcher m = _section.matcher( line );
            if( m.matches()) {
               section = m.group( 1 ).trim();
            }
            else if( section != null ) {
               m = _keyValue.matcher( line );
               if( m.matches()) {
                  String key   = m.group( 1 ).trim();
                  String value = m.group( 2 ).trim();
                  Map< String, String > kv = _entries.get( section );
                  if( kv == null ) {
                     _entries.put( section, kv = new HashMap<>());   
                  }
                  kv.put( key, value );
               }
            }
         }
      }
   }

   public String getString( String section, String key, String defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return kv.get( key );
   }

   public int getInt( String section, String key, int defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Integer.parseInt( kv.get( key ));
   }

   public float getFloat( String section, String key, float defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Float.parseFloat( kv.get( key ));
   }

   public double getDouble( String section, String key, double defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Double.parseDouble( kv.get( key ));
   }
}
Lotnictwo
źródło
+1 po prostu za użycie wyrażenia regularnego Wzorzec / Dopasowanie. Działa jak urok
kalelien
Nie jest to idealne rozwiązanie, ale dobry punkt wyjścia, np. Brakujące getSection () i getString () zwracają defaultValue tylko wtedy, gdy brakuje całej sekcji.
Jack Miller,
jaka jest różnica wydajności między takim regx a pracą z implementacją string?
Ewoks
Wydajność podczas czytania małego pliku konfiguracyjnego nie jest problemem. Uważam, że otwieranie i zamykanie pliku jest znacznie bardziej pracochłonne.
Aerospace
Tak, to jest tak proste, jak powinno być w przypadku prostych przypadków użycia. Nie wiem, dlaczego ludzie chcą to komplikować. Jeśli martwisz się wydajnością (lub innymi obawami, takimi jak raportowanie błędów), tak, prawdopodobnie chcesz użyć czegoś innego (prawdopodobnie zupełnie innego formatu).
Użytkownik
16

Oto prosty, ale potężny przykład, wykorzystujący HierarchicalINIConfiguration klasy Apache :

HierarchicalINIConfiguration iniConfObj = new HierarchicalINIConfiguration(iniFile); 

// Get Section names in ini file     
Set setOfSections = iniConfObj.getSections();
Iterator sectionNames = setOfSections.iterator();

while(sectionNames.hasNext()){

 String sectionName = sectionNames.next().toString();

 SubnodeConfiguration sObj = iniObj.getSection(sectionName);
 Iterator it1 =   sObj.getKeys();

    while (it1.hasNext()) {
    // Get element
    Object key = it1.next();
    System.out.print("Key " + key.toString() +  " Value " +  
                     sObj.getString(key.toString()) + "\n");
}

Commons Configuration ma wiele zależności w czasie wykonywania . Jako minimum wymagane są rejestracje typu common-lang i commons-logs . W zależności od tego, co z nim robisz, możesz potrzebować dodatkowych bibliotek (zobacz poprzednie łącze, aby uzyskać szczegółowe informacje).

user50217
źródło
1
To byłaby moja poprawna odpowiedź. Bardzo prosty w użyciu i wszechstronny.
marcolopes
wspólne konfiguracje, a nie kolekcje.
jantox
13

Lub ze standardowym interfejsem API Java możesz użyć java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Piotr
źródło
12
Problem polega na tym, że w przypadku plików ini struktura ma nagłówki. Klasa Property nie wie, jak radzić sobie z nagłówkami i mogą wystąpić konflikty nazw
Mario Ortegón
2
Ponadto Propertiesklasa nie pobiera poprawnie wartości zawierających \
rds,
3
+1 za proste rozwiązanie, ale pasuje tylko do prostych plików konfiguracyjnych, jak zauważyli Mario Ortegon i rds.
Benj,
1
Plik INI zawiera [sekcja], plik właściwości zawiera przypisania.
Aerospace
1
format pliku: 1 / prosty zorientowany liniowo lub 2 / prosty format XML lub 3 / prosty zorientowany liniowo, przy użyciu ISO 8859-1 (ze native2ascii
znakami
10

W 18 wierszach, rozszerzając java.util.Propertiesto parse na wiele sekcji:

public static Map<String, Properties> parseINI(Reader reader) throws IOException {
    Map<String, Properties> result = new HashMap();
    new Properties() {

        private Properties section;

        @Override
        public Object put(Object key, Object value) {
            String header = (((String) key) + " " + value).trim();
            if (header.startsWith("[") && header.endsWith("]"))
                return result.put(header.substring(1, header.length() - 1), 
                        section = new Properties());
            else
                return section.put(key, value);
        }

    }.load(reader);
    return result;
}
hoat4
źródło
2

Inną opcją jest to, że Apache Commons Config ma również klasę do ładowania z plików INI . Ma pewne zależności w czasie wykonywania , ale w przypadku plików INI powinien wymagać tylko kolekcji Commons, lang i rejestrowania.

Użyłem Commons Config w projektach z ich właściwościami i konfiguracjami XML. Jest bardzo łatwy w użyciu i obsługuje kilka całkiem potężnych funkcji.

John Meagher
źródło
2

Osobiście wolę Confucious .

Jest fajny, ponieważ nie wymaga żadnych zewnętrznych zależności, jest mały - tylko 16K i automatycznie ładuje twój plik ini podczas inicjalizacji. Na przykład

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
znak
źródło
3 lata później Mark i OP prawdopodobnie zmarli ze starości ... ale to naprawdę dobre znalezisko.
Użytkownik
6
Używam laski do poruszania się, ale wciąż żyję i
kopie
@ MarioOrtegón: Wspaniale to słyszeć!
ישו אוהב אותך
0

Rozwiązanie hoat4 jest bardzo eleganckie i proste. Działa dla wszystkich rozsądnych plików ini. Jednak widziałem wiele, które mają niezatarte znaki spacji w kluczu .
Aby rozwiązać ten problem, pobrałem i zmodyfikowałem kopię pliku java.util.Properties. Chociaż jest to trochę niekonwencjonalne i krótkoterminowe, rzeczywiste mody miały tylko kilka linijek i były dość proste. Przedstawię społeczności JDK propozycję wprowadzenia zmian.

Dodając wewnętrzną zmienną klasy:

private boolean _spaceCharOn = false;

Kontroluję przetwarzanie związane ze skanowaniem w poszukiwaniu punktu separacji klucz / wartość. Zastąpiłem kod wyszukiwania znaków spacji małą metodą prywatną, która zwraca wartość logiczną w zależności od stanu powyższej zmiennej.

private boolean isSpaceSeparator(char c) {
    if (_spaceCharOn) {
        return (c == ' ' || c == '\t' || c == '\f');
    } else {
        return (c == '\t' || c == '\f');
    }
}

Ta metoda jest używana w dwóch miejscach w ramach metody prywatnej load0(...).
Istnieje również publiczna metoda włączania go, ale lepiej byłoby użyć oryginalnej wersji, Propertiesjeśli separator spacji nie stanowi problemu dla twojej aplikacji.

Jeśli jest zainteresowanie, chętnie prześlę kod do mojego IniFile.javapliku. Działa z każdą wersją Properties.

Bradley Willcott
źródło
0

Korzystając z odpowiedzi @Aerospace, zdałem sobie sprawę, że pliki INI mogą mieć sekcje bez żadnych par klucz-wartość. W takim przypadku dodanie do mapy najwyższego poziomu powinno nastąpić przed znalezieniem jakichkolwiek par klucz-wartość, np. (Minimalna aktualizacja dla Java 8):

            Path location = ...;
            try (BufferedReader br = new BufferedReader(new FileReader(location.toFile()))) {
                String line;
                String section = null;
                while ((line = br.readLine()) != null) {
                    Matcher m = this.section.matcher(line);
                    if (m.matches()) {
                        section = m.group(1).trim();
                        entries.computeIfAbsent(section, k -> new HashMap<>());
                    } else if (section != null) {
                        m = keyValue.matcher(line);
                        if (m.matches()) {
                            String key = m.group(1).trim();
                            String value = m.group(2).trim();
                            entries.get(section).put(key, value);
                        }
                    }
                }
            } catch (IOException ex) {
                System.err.println("Failed to read and parse INI file '" + location + "', " + ex.getMessage());
                ex.printStackTrace(System.err);
            }
Denis Baranov
źródło
-1

To jest tak proste, jak to .....

//import java.io.FileInputStream;
//import java.io.FileInputStream;

Properties prop = new Properties();
//c:\\myapp\\config.ini is the location of the ini file
//ini file should look like host=localhost
prop.load(new FileInputStream("c:\\myapp\\config.ini"));
String host = prop.getProperty("host");
Desmond
źródło
1
To nie dotyczy sekcji INI
Igor Melnichenko