¿Cuál es la forma más sencilla de analizar un archivo INI en Java?

104

Estoy escribiendo un reemplazo directo para una aplicación heredada en Java. Uno de los requisitos es que los archivos ini que usaba la aplicación anterior deben leerse tal cual en la nueva aplicación Java. El formato de estos archivos ini es el estilo común de Windows, con secciones de encabezado y pares clave = valor, utilizando # como carácter para comentar.

Intenté usar la clase Propiedades de Java, pero, por supuesto, eso no funcionará si hay conflictos de nombres entre diferentes encabezados.

Entonces, la pregunta es, ¿cuál sería la forma más fácil de leer este archivo INI y acceder a las claves?

Mario Ortegón
fuente

Respuestas:

121

La biblioteca que he usado es ini4j . Es ligero y analiza los archivos ini con facilidad. Además, no usa dependencias esotéricas para otros 10,000 archivos jar, ya que uno de los objetivos del diseño era usar solo la API estándar de Java

Este es un ejemplo de cómo se usa la biblioteca:

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
fuente
2
no funciona, el error dice "IniFile no se puede resolver a un tipo"
Caballero
@Caballero sí parece que la IniFileclase se sacó, pruebaIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html probablemente se mantendrá actualizado incluso si vuelven a cambiar el nombre de la clase.
Lokathor
¿Esta cosa ya funciona? Descargué la fuente 0.5.4 y ni siquiera se compiló, y no era una dependencia faltante ... no valía la pena preocuparse más por ella. Además, ini4j tiene muchas otras tonterías que no necesitamos, edición de registro de Windoze ... vamos. #LinuxMasterRace ... Pero supongo que si te funciona, déjate inconsciente.
Usuario
Para el archivo INI que escribí, tuve que usar la Winiclase, como se ilustra en el tutorial "Un minuto"; el prefs.node("something").get("val", null)no funcionó de la manera que esperaba.
Agi Hammerthief
65

Como se mencionó , ini4j se puede usar para lograr esto. Permítanme mostrarles otro ejemplo.

Si tenemos un archivo INI como este:

[header]
key = value

Lo siguiente debería mostrarse valueen STDOUT:

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

Consulte los tutoriales para ver más ejemplos.

tshepang
fuente
2
¡Ordenado! Siempre he estado usando BufferedReader y un poco de copiar / pegar código de análisis de cadenas para no tener que agregar otra dependencia a mis aplicaciones (que puede exagerar cuando empiezas a agregar API de terceros incluso para las tareas más simples ). Pero no puedo ignorar este tipo de simplicidad.
Gimby
30

Tan simple como 80 líneas:

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 ));
   }
}
Aeroespacial
fuente
+1 Simplemente para usar el patrón / comparador de expresiones regulares. Funciona como un encanto
kalelien
No es una solución perfecta, pero es un buen punto de partida, por ejemplo, falta getSection () y getString () solo devuelve defaultValue si falta la sección completa.
Jack Miller
¿Cuál es la diferencia de rendimiento entre un regx y trabajar con implementación de cadenas?
Ewoks
El rendimiento al leer un archivo de configuración pequeño no es un problema. Creo que abrir y cerrar un archivo consume mucho más.
Aeroespacial
Sí, esto es tan simple como debería ser para casos de uso simples. No estoy seguro de por qué la gente quiere complicarlo. Si le preocupa el rendimiento (u otras inquietudes como la notificación de errores), sí, probablemente quiera usar otra cosa (probablemente otro formato completamente diferente).
Usuario
16

Aquí hay un ejemplo simple pero poderoso, usando la clase 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");
}

Commons Configuration tiene varias dependencias de tiempo de ejecución . Como mínimo, se requieren commons-lang y commons-logging . Dependiendo de lo que esté haciendo con él, es posible que necesite bibliotecas adicionales (consulte el enlace anterior para obtener más detalles).

user50217
fuente
1
Esta sería mi respuesta correcta. Muy simple de usar y versátil.
marcolopes
configuraciones comunes, no colecciones.
jantox
13

O con la API estándar de Java, puede usar java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Pedro
fuente
12
El problema es que, con los archivos ini, la estructura tiene encabezados. La clase Propiedad no sabe cómo manejar los encabezados y podría haber enfrentamientos de nombres
Mario Ortegón
2
Además, la Propertiesclase no obtiene correctamente los valores que contienen \
rds
3
+1 para la solución simple, pero solo se ajusta a archivos de configuración simples, como lo notaron Mario Ortegon y rds.
Benj
1
El archivo INI contiene [sección], el archivo de propiedades contiene asignaciones.
Aeroespacial
1
formato de archivo: 1 / un formato simple orientado a líneas o 2 / un formato XML simple o 3 / un formato simple orientado a líneas, usando ISO 8859-1 (con escapes Unicode + uso native2asciipara otras codificaciones)
n611x007
10

En 18 líneas, extendiendo el java.util.Propertiespara analizar en múltiples secciones:

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
fuente
2

Otra opción es Apache Commons Config que también tiene una clase para cargar desde archivos INI . Tiene algunas dependencias de tiempo de ejecución , pero para los archivos INI solo debería requerir colecciones, lang y registro de Commons.

He usado Commons Config en proyectos con sus propiedades y configuraciones XML. Es muy fácil de usar y admite algunas funciones bastante potentes.

John Meagher
fuente
2

Personalmente prefiero Confucious .

Es bueno, ya que no requiere dependencias externas, es pequeño, solo 16K y carga automáticamente su archivo ini en la inicialización. P.ej

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
marca
fuente
3 años después, Mark y el OP probablemente hayan muerto de vejez ... pero este es un hallazgo realmente bueno.
Usuario
6
Uso un bastón para moverme, pero sigo vivo y coleando
Mario Ortegón
@ MarioOrtegón: ¡Qué bueno escuchar eso!
ישו אוהב אותך
0

La solución de hoat4 es muy elegante y sencilla. Funciona para todos los archivos ini cuerdos . Sin embargo, he visto muchos que tienen caracteres de espacio sin escape en la clave .
Para solucionar esto, he descargado y modificado una copia de java.util.Properties. Aunque esto es un poco poco ortodoxo y de corto plazo, las modificaciones reales eran solo unas pocas líneas y bastante simples. Presentaré una propuesta a la comunidad de JDK para incluir los cambios.

Añadiendo una variable de clase interna:

private boolean _spaceCharOn = false;

Yo controlo el procesamiento relacionado con la exploración del punto de separación clave / valor. Reemplacé el código de búsqueda de caracteres de espacio con un pequeño método privado que devuelve un booleano según el estado de la variable anterior.

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

Este método se utiliza en dos lugares dentro del método privado load0(...).
También hay un método público para activarlo, pero sería mejor usar la versión original de Propertiessi el separador de espacios no es un problema para su aplicación.

Si hay interés, estaría dispuesto a publicar el código en mi IniFile.javaarchivo. Funciona con cualquier versión de Properties.

Bradley Willcott
fuente
0

Usando la respuesta de @Aerospace, me di cuenta de que es legítimo que los archivos INI tengan secciones sin valores clave. En este caso, la adición al mapa de nivel superior debe ocurrir antes de que se encuentren valores-clave, por ejemplo (mínimamente actualizado para 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
fuente
-2

Es tan simple como esto .....

//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
fuente
1
Esto no maneja las secciones INI
Igor Melnichenko