Quelle est la manière la plus simple d'analyser un fichier INI en Java?

104

J'écris un remplacement instantané pour une application héritée en Java. L'une des conditions requises est que les fichiers ini utilisés par l'ancienne application doivent être lus tels quels dans la nouvelle application Java. Le format de ces fichiers ini est le style Windows commun, avec des sections d'en-tête et des paires clé = valeur, en utilisant # comme caractère pour les commentaires.

J'ai essayé d'utiliser la classe Properties de Java, mais bien sûr, cela ne fonctionnera pas s'il y a des conflits de noms entre différents en-têtes.

La question est donc de savoir quelle serait la manière la plus simple de lire ce fichier INI et d'accéder aux clés?

Mario Ortegón
la source

Réponses:

121

La bibliothèque que j'ai utilisée est ini4j . Il est léger et analyse les fichiers ini avec facilité. De plus, il n'utilise aucune dépendance ésotérique avec 10000 autres fichiers jar, car l'un des objectifs de conception était d'utiliser uniquement l'API Java standard

Voici un exemple d'utilisation de la bibliothèque:

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
la source
2
ne fonctionne pas, l'erreur indique "IniFile ne peut pas être résolu en un type"
Caballero
@Caballero oui il semble que le IniFilecours a été retiré, essayezIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html restera probablement à jour même s'ils modifient à nouveau le nom de la classe.
Lokathor
Est-ce que ce truc fonctionne même plus? Téléchargé la source 0.5.4 et il n'a même pas été construit, et ce n'était pas une dépendance manquante ... ne valait pas le temps de s'en préoccuper davantage. Ini4j contient également beaucoup d'autres conneries dont nous n'avons pas besoin, l'édition du registre Windoze ... allez. #LinuxMasterRace ... Mais je suppose que si cela fonctionne pour vous, assommez-vous.
Utilisateur du
Pour le fichier INI que j'ai écrit, j'ai dû utiliser la Winiclasse, comme illustré dans le didacticiel "One Minute"; le prefs.node("something").get("val", null)n'a pas fonctionné comme je m'y attendais.
Agi Hammerthief
65

Comme mentionné , ini4j peut être utilisé pour y parvenir. Laissez-moi vous montrer un autre exemple.

Si nous avons un fichier INI comme celui-ci:

[header]
key = value

Les éléments suivants doivent s'afficher valuesur STDOUT:

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

Consultez les didacticiels pour plus d'exemples.

tshepang
la source
2
Soigné! J'ai toujours juste utilisé BufferedReader et un peu de code d'analyse de chaîne copier / coller pour ne pas avoir à ajouter une autre dépendance à mes applications (cela peut exploser lorsque vous commencez à ajouter des API tierces pour les tâches les plus simples. ). Mais je ne peux pas ignorer ce genre de simplicité.
Gimby
30

Aussi simple que 80 lignes:

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 ));
   }
}
Aérospatial
la source
+1 Simplement pour l'utilisation de regex Pattern / Matcher. Fonctionne comme un charme
kalelien
Ce n'est pas une solution parfaite mais un bon point de départ, par exemple, l'absence de getSection () et getString () ne retourne defaultValue que si la section entière est manquante.
Jack Miller
Quelle est la différence de performances entre un tel regx et une implémentation de chaîne?
Ewoks le
Les performances lors de la lecture d'un petit fichier de configuration ne sont pas un problème. ouvrir et fermer un fichier est beaucoup plus coûteux, je crois.
Aerospace
Oui, c'est à peu près aussi simple que cela devrait être pour des cas d'utilisation simples. Je ne sais pas pourquoi les gens veulent le compliquer. Si vous êtes préoccupé par les performances (ou d'autres problèmes comme le rapport d'erreurs), oui, vous voudrez probablement utiliser autre chose (probablement un autre format entièrement).
Utilisateur du
16

Voici un exemple simple mais puissant, utilisant la classe apache HierarchicalINIConfiguration :

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");
}

La configuration Commons a un certain nombre de dépendances d'exécution . Au minimum, commons-lang et commons-logging sont requis. Selon ce que vous en faites, vous pouvez avoir besoin de bibliothèques supplémentaires (voir le lien précédent pour plus de détails).

utilisateur50217
la source
1
Ce serait ma bonne réponse. Très simple à utiliser et polyvalent.
marcolopes
les configurations communes et non les collections.
jantox
13

Ou avec l'API Java standard, vous pouvez utiliser java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Peter
la source
12
Le problème est que, avec les fichiers ini, la structure a des en-têtes. La classe Property ne sait pas comment gérer les en-têtes et il pourrait y avoir des conflits de noms
Mario Ortegón
2
De plus, la Propertiesclasse n'obtient pas correctement les valeurs qui contiennent \
rds
3
+1 pour la solution simple, mais ne convient qu'aux fichiers de configuration simples, comme Mario Ortegon et rds l'ont remarqué.
Benj
1
Le fichier INI contient [section], le fichier de propriétés contient les affectations.
Aerospace
1
format de fichier: 1 / un simple orienté ligne ou 2 / un format XML simple ou 3 / un simple orienté ligne, utilisant ISO 8859-1 (avec échappements Unicode + utilisation native2asciipour d'autres encodages)
n611x007
10

En 18 lignes, étendant le java.util.Propertiespour analyser en plusieurs sections:

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
la source
2

Une autre option est qu'Apache Commons Config a également une classe pour le chargement à partir de fichiers INI . Il a quelques dépendances d'exécution , mais pour les fichiers INI, il ne devrait nécessiter que les collections Commons, la langue et la journalisation.

J'ai utilisé Commons Config sur des projets avec leurs propriétés et leurs configurations XML. Il est très facile à utiliser et prend en charge certaines fonctionnalités assez puissantes.

John Meagher
la source
2

Personnellement, je préfère Confucious .

C'est bien, car il ne nécessite aucune dépendance externe, il est minuscule - seulement 16K, et charge automatiquement votre fichier ini lors de l'initialisation. Par exemple

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
marque
la source
3 ans plus tard, Mark et l'OP sont probablement morts de vieillesse ... mais c'est une très bonne trouvaille.
Utilisateur du
6
J'utilise une canne pour me déplacer, mais toujours vivant et kickin '
Mario Ortegón
@ MarioOrtegón: Super d'entendre ça!
ישו אוהב אותך
0

La solution de hoat4 est très élégante et simple. Cela fonctionne pour tous les fichiers ini sains . Cependant, j'en ai vu beaucoup qui ont des caractères d'espace non échappés dans la clé .
Pour résoudre ce problème, j'ai téléchargé et modifié une copie de java.util.Properties. Bien que ce soit un peu peu orthodoxe et à court terme, les mods réels n'étaient que quelques lignes et assez simples. Je présenterai une proposition à la communauté JDK pour inclure les changements.

En ajoutant une variable de classe interne:

private boolean _spaceCharOn = false;

Je contrôle le traitement lié à la recherche du point de séparation clé / valeur. J'ai remplacé le code de recherche de caractères d'espace par une petite méthode privée qui renvoie un booléen en fonction de l'état de la variable ci-dessus.

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

Cette méthode est utilisée à deux endroits dans la méthode privée load0(...).
Il existe également une méthode publique pour l'activer, mais il serait préférable d'utiliser la version d'origine de Propertiessi le séparateur d'espace n'est pas un problème pour votre application.

S'il y a un intérêt, je serais prêt à publier le code dans mon IniFile.javadossier. Cela fonctionne avec les deux versions de Properties.

Bradley Willcott
la source
0

En utilisant la réponse de @Aerospace, j'ai réalisé qu'il était légitime que les fichiers INI aient des sections sans valeurs-clés. Dans ce cas, l'ajout à la carte de niveau supérieur doit avoir lieu avant que des valeurs-clés ne soient trouvées, ex (mise à jour minimale pour 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
la source
-1

C'est aussi simple que ça .....

//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
la source
1
Cela ne gère pas les sections INI
Igor Melnichenko