Qual é a maneira mais fácil de analisar um arquivo INI em Java?

104

Estou escrevendo um substituto imediato para um aplicativo legado em Java. Um dos requisitos é que os arquivos ini que o aplicativo antigo usava sejam lidos no estado em que se encontram no novo aplicativo Java. O formato desses arquivos ini é o estilo comum do Windows, com seções de cabeçalho e pares chave = valor, usando # como caractere para comentários.

Tentei usar a classe Properties do Java, mas é claro que não funcionará se houver conflito de nomes entre cabeçalhos diferentes.

Então a questão é: qual seria a maneira mais fácil de ler esse arquivo INI e acessar as chaves?

Mario Ortegón
fonte

Respostas:

121

A biblioteca que usei é a ini4j . É leve e analisa os arquivos ini com facilidade. Além disso, ele não usa dependências esotéricas para 10.000 outros arquivos jar, já que um dos objetivos do projeto era usar apenas a API Java padrão

Este é um exemplo de como a biblioteca é usada:

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
fonte
2
não funciona, o erro diz "IniFile não pode ser resolvido para um tipo"
Caballero
@Caballero sim, parece que a IniFileaula foi retirada, tenteIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html provavelmente ficará atualizado mesmo que altere o nome da classe novamente.
Lokathor
Essa coisa ainda está funcionando? Baixei o código-fonte 0.5.4 e nem compilava, e não era uma dependência perdida .. não valia a pena se preocupar mais com isso. Além disso, o ini4j tem um monte de outras porcarias que não precisamos, edição de registro do Windows ... vamos lá #LinuxMasterRace ... Mas acho que se funcionar para você, saia do ar.
Usuário de
Para o arquivo INI que escrevi, tive que usar a Winiclasse, conforme ilustrado no tutorial "One Minute"; o prefs.node("something").get("val", null)não funcionou da maneira que eu esperava.
Agi Hammerthief
65

Como mencionado , ini4j pode ser usado para conseguir isso. Deixe-me mostrar outro exemplo.

Se tivermos um arquivo INI como este:

[header]
key = value

O seguinte deve ser exibido valuepara STDOUT:

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

Verifique os tutoriais para mais exemplos.

tshepang
fonte
2
Arrumado! Sempre usei BufferedReader e um pouco de copiar / colar código de análise de String para não ter que adicionar outra dependência aos meus aplicativos (que pode explodir quando você começa a adicionar APIs de terceiros até mesmo para as tarefas mais simples ) Mas não posso ignorar esse tipo de simplicidade.
Gimby
30

Tão simples quanto 80 linhas:

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
fonte
+1 Simplesmente para uso de padrão regex / Matcher. Funciona como um encanto
Kalelien
Não é uma solução perfeita, mas um bom ponto de partida, por exemplo, getSection () e getString () ausentes só retornam defaultValue se a seção inteira estiver ausente.
Jack Miller
Qual é a diferença de desempenho entre um regx e trabalhar com implementação de string?
Ewoks
O desempenho ao ler um pequeno arquivo de configuração não é uma preocupação. abrir e fechar um arquivo consome muito mais, eu acredito.
Aeroespacial
Sim, isso é tão simples quanto deveria ser para casos de uso simples. Não sei por que as pessoas querem complicar. Se você está preocupado com o desempenho (ou outras preocupações como relatório de erros), sim, você provavelmente deseja usar outra coisa (provavelmente outro formato totalmente).
Usuário de
16

Aqui está um exemplo simples, mas poderoso, usando a classe do 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");
}

A configuração do Commons possui várias dependências de tempo de execução . No mínimo, commons-lang e commons-logging são necessários. Dependendo do que você está fazendo com ele, você pode precisar de bibliotecas adicionais (consulte o link anterior para obter detalhes).

user50217
fonte
1
Esta seria minha resposta correta. Muito simples de usar e versátil.
marcolopes
configurações comuns, não coleções.
jantox de
13

Ou com a API Java padrão, você pode usar java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Peter
fonte
12
O problema é que, com arquivos ini, a estrutura possui cabeçalhos. A classe Property não sabe como lidar com os cabeçalhos e pode haver conflitos de nomes
Mario Ortegón
2
Além disso, a Propertiesclasse não obtém adequadamente os valores que contêm \
rds
3
+1 para a solução simples, mas se encaixa apenas em arquivos de configuração simples, como Mario Ortegon e rds notaram.
Benj
1
O arquivo INI contém [seção], o arquivo de propriedades contém atribuições.
Aeroespacial de
1
formato de arquivo: 1 / orientado por linha simples ou 2 / formato XML simples ou 3 / orientado por linha simples, usando ISO 8859-1 (com escapes Unicode + uso native2asciipara outras codificações)
n611x007
10

Em 18 linhas, estendendo o java.util.Propertiespara analisar em várias seções:

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

Outra opção é que o Apache Commons Config também possui uma classe para carregar de arquivos INI . Ele tem algumas dependências de tempo de execução , mas para arquivos INI, deve requerer apenas coleções Commons, lang e registro.

Usei Commons Config em projetos com suas propriedades e configurações XML. É muito fácil de usar e oferece suporte a alguns recursos bastante poderosos.

John Meagher
fonte
2

Eu pessoalmente prefiro Confucious .

É bom, pois não requer nenhuma dependência externa, é minúsculo - apenas 16K e carrega automaticamente seu arquivo ini na inicialização. Por exemplo

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
Marca
fonte
3 anos depois, Mark e o OP provavelmente morreram de velhice ... mas este é um achado realmente bom.
Usuário de
6
Eu uso uma bengala para me locomover, mas ainda vivo e chutando
Mario Ortegón
@ MarioOrtegón: Que bom ouvir isso!
ישו אוהב אותך
0

A solução da hoat4 é muito elegante e simples. Ele funciona para todos os arquivos ini sãos . No entanto, tenho visto muitos que possuem caracteres de espaço sem escape na chave .
Para resolver isso, baixei e modifiquei uma cópia do java.util.Properties. Embora isso seja um pouco heterodoxo e de curto prazo, os mods reais eram apenas algumas linhas e bastante simples. Apresentarei uma proposta à comunidade JDK para incluir as mudanças.

Adicionando uma variável de classe interna:

private boolean _spaceCharOn = false;

Eu controlo o processamento relacionado à digitalização do ponto de separação de chave / valor. Substituí o código de pesquisa de caracteres de espaço por um pequeno método privado que retorna um booleano dependendo do estado da variável acima.

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

Este método é usado em dois locais dentro do método privado load0(...).
Também existe um método público para ativá-lo, mas seria melhor usar a versão original do Propertiesse o separador de espaço não for um problema para seu aplicativo.

Se houver interesse, gostaria de postar o código em meu IniFile.javaarquivo. Funciona com qualquer uma das versões do Properties.

Bradley Willcott
fonte
0

Usando a resposta de @Aerospace, percebi que é legítimo que os arquivos INI tenham seções sem nenhum valor-chave. Nesse caso, a adição ao mapa de nível superior deve acontecer antes que qualquer valor-chave seja encontrado, por exemplo, (minimamente atualizado 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
fonte
-2

É tão simples quanto isso ...

//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
fonte
1
Isso não lida com seções INI
Igor Melnichenko