// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.util; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.io.StreamUtils; /** * Parses Infinity Engine INI files. * * The format differs from the original Windows INI file format, as it uses double slashes * instead of semicolons to start comments. */ public class IniMap { private final List<IniMapSection> entries = new ArrayList<IniMapSection>(); public IniMap(String name) { this(ResourceFactory.getResourceEntry(name), false); } public IniMap(String name, boolean ignoreComments) { this(ResourceFactory.getResourceEntry(name), ignoreComments); } public IniMap(ResourceEntry entry) { this(entry, false); } public IniMap(ResourceEntry entry, boolean ignoreComments) { init(entry, ignoreComments); } /** Returns number of available INI sections. */ public int getSectionCount() { return entries.size(); } /** Returns the specified INI section. */ public IniMapSection getSection(int index) { if (index >= 0 && index < getSectionCount()) { return entries.get(index); } return null; } /** Returns the INI section with the specified section name. */ public IniMapSection getSection(String name) { if (name == null) { name = ""; } for (final IniMapSection section: entries) { if (section.getName().equalsIgnoreCase(name)) { return section; } } return null; } /** * Returns the section instance for an unnamed (or empty) section, if available. */ public IniMapSection getUnnamedSection() { if (entries.size() > 0 && entries.get(0).getName().isEmpty()) { return entries.get(0); } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (final IniMapSection s: entries) { sb.append(s.toString()).append('\n'); } return sb.toString(); } private void init(ResourceEntry entry, boolean ignoreComments) { // reading and storing unprocessed lines of text String[] lines = null; if (entry != null) { try { ByteBuffer bb = entry.getResourceBuffer(); lines = StreamUtils.readString(bb, bb.limit(), Misc.CHARSET_DEFAULT).split("\r?\n"); } catch (Exception e) { e.printStackTrace(); return; } } // parsing lines String curSection = null; int curSectionLine = 0; List<IniMapEntry> section = new ArrayList<IniMapEntry>(); for (int i = 0, count = lines.length; i < count; i++) { final String line = lines[i].trim(); if (Pattern.matches("^\\[.+\\]$", line)) { // new section found // storing content of previous section if (curSection != null || section.size() > 0) { entries.add(new IniMapSection(curSection, curSectionLine, section)); } curSection = line.substring(1, line.length() - 1); curSectionLine = i; section.clear(); } else { // potential section entry IniMapEntry e = parseEntry(line, i, ignoreComments); if (e != null) { section.add(e); } } } // adding last section if (curSection != null || section.size() > 0) { entries.add(new IniMapSection(curSection, curSectionLine, section)); } } private IniMapEntry parseEntry(String line, int lineNr, boolean ignoreComments) { IniMapEntry retVal = null; if (line != null && !line.isEmpty()) { String key = null, value = null; boolean isValue = false; int start = 0, pos = 0; for (; pos < line.length(); pos++) { char ch = line.charAt(pos); if (!isValue && ch == '=') { key = line.substring(start, pos).trim(); isValue = true; start = pos + 1; } else if (!ignoreComments && ch == '/' && pos+1 < line.length() && line.charAt(pos+1) == '/') { break; // skip comments } } // End of line: only "value" tokens are valid if (isValue) { value = line.substring(start, pos).trim(); } retVal = new IniMapEntry(key, value, lineNr); } return retVal; } }