There is a open source project named [ini4j] for processing Windows .ini configuration files. However, I found it an overkill for my purposes. So here is my simple implementation of a .ini parser. It mimics the standard
There are only a few simple rules:
Example Code
The test file
Following ini4j, here is a test file:
java.util.Properties
class with enhancements to get and set properties by section name. There are only a few simple rules:
- Leading and trailing spaces are trimmed from section names, property names and property values.
- Section names are enclosed between
[
and]
. - Properties following a section header belong to that section
- Properties defined before the appearance of any section headers are considered global properties and should be set and get with no section names.
- You can use either equal sign (
=
) or colon (:
) to assign property values - Comments begin with either a semicolon (
;
), or a sharp sign (#
) and extend to the end of line. It doesn't have to be the first character. - A backslash (
\
) escapes the next character (e.g.,\#
is a literal#
,\\
is a literal\
). - If the last character of a line is backslash (
\
), the value is continued on the next line with new line character included.
Example Code
import java.util.*;
import java.io.*;
public class IniProperties {
private Properties globalProperties;
private Map<String,Properties> properties;
enum ParseState {
NORMAL,
ESCAPE,
ESC_CRNL,
COMMENT
}
public IniProperties() {
globalProperties = new Properties();
properties = new HashMap<String,Properties>();
}
/**
* Load ini as properties from input stream.
*/
public void load(InputStream in) throws IOException {
int bufSize = 4096;
byte[] buffer = new byte[bufSize];
int n = in.read(buffer, 0, bufSize);
ParseState state = ParseState.NORMAL;
boolean section_open = false;
String current_section = null;
String key = null, value = null;
StringBuilder sb = new StringBuilder();
while (n >= 0) {
for (int i = 0; i < n; i++) {
char c = (char) buffer[i];
if (state == ParseState.COMMENT) {
// comment, skip to end of line
if ((c == '\r') ||(c == '\n')) {
state = ParseState.NORMAL;
}
else {
continue;
}
}
if (state == ParseState.ESCAPE) {
sb.append(c);
if (c == '\r') {
// if the EOL is \r\n, \ escapes both chars
state = ParseState.ESC_CRNL;
}
else {
state = ParseState.NORMAL;
}
continue;
}
switch (c) {
case '[': // start section
sb = new StringBuilder();
section_open = true;
break;
case ']': // end section
if (section_open) {
current_section = sb.toString().trim();
sb = new StringBuilder();
properties.put(current_section, new Properties());
section_open = false;
}
else {
sb.append(c);
}
break;
case '\\': // escape char, take the next char as is
state = ParseState.ESCAPE;
break;
case '#':
case ';':
state = ParseState.COMMENT;
break;
case '=': // assignment operator
case ':':
if (key == null) {
key = sb.toString().trim();
sb = new StringBuilder();
}
else {
sb.append(c);
}
break;
case '\r':
case '\n':
if ((state == ParseState.ESC_CRNL) && (c == '\n')) {
sb.append(c);
state = ParseState.NORMAL;
}
else {
if (sb.length() > 0) {
value = sb.toString().trim();
sb = new StringBuilder();
if (key != null) {
if (current_section == null) {
this.setProperty(key, value);
}
else {
this.setProperty(current_section, key, value);
}
}
}
key = null;
value = null;
}
break;
default:
sb.append(c);
}
}
n = in.read(buffer, 0, bufSize);
}
}
/**
* Get global property by name.
*/
public String getProperty(String name) {
return globalProperties.getProperty(name);
}
/**
* Set global property.
*/
public void setProperty(String name, String value) {
globalProperties.setProperty(name, value);
}
/**
* Return iterator of global properties.
*/
@SuppressWarnings("unchecked")
public Iterator<String> properties() {
return new IteratorFromEnumeration<String>(
(Enumeration<String>)globalProperties.propertyNames());
}
/**
* Get property value for specified section and name. Returns null
* if section or property does not exist.
*/
public String getProperty(String section, String name) {
Properties p = properties.get(section);
return p == null ? null : p.getProperty(name);
}
/**
* Set property value for specified section and name. Creates section
* if not existing.
*/
public void setProperty(String section, String name, String value) {
Properties p = properties.get(section);
if (p == null) {
p = new Properties();
properties.put(section, p);
}
p.setProperty(name, value);
}
/**
* Return property iterator for specified section. Returns null if
* specified section does not exist.
*/
@SuppressWarnings("unchecked")
public Iterator<String> properties(String section) {
Properties p = properties.get(section);
if (p == null) {
return null;
}
return new IteratorFromEnumeration<String>(
(Enumeration<String>)p.propertyNames());
}
/**
* Return iterator of names of section.
*/
public Iterator<String> sections() {
return properties.keySet().iterator();
}
/**
* Dumps properties to output stream.
*/
public void dump(PrintStream out) throws IOException {
// Global properties
Iterator<String> props = this.properties();
while (props.hasNext()) {
String name = props.next();
out.printf("%s = %s\n", name, dumpEscape(getProperty(name)));
}
// sections
Iterator<String> sections = this.sections();
while (sections.hasNext()) {
String section = sections.next();
out.printf("\n[%s]\n", section);
props = this.properties(section);
while (props.hasNext()) {
String name = props.next();
out.printf("%s = %s\n", name, dumpEscape(getProperty(section, name)));
}
}
}
private static String dumpEscape(String s) {
return s.replaceAll("\\\\", "\\\\\\\\")
.replaceAll(";", "\\\\;")
.replaceAll("#", "\\\\#")
.replaceAll("(\r?\n|\r)", "\\\\$1");
}
// private class used to coerce Enumerator to Iterator.
private static class IteratorFromEnumeration<E> implements Iterator {
private Enumeration<E> e;
public IteratorFromEnumeration(Enumeration<E> e) {
this.e = e;
}
public boolean hasNext() {
return e.hasMoreElements();
}
public E next() {
return e.nextElement();
}
public void remove() {
throw new UnsupportedOperationException("Can't
change underlying enumeration");
}
}
public static void main(String[] args) throws IOException {
IniProperties props = new IniProperties();
InputStream in = new BufferedInputStream(new FileInputStream("test.ini"));
props.load(in);
in.close();
props.dump(System.out);
}
}
The test file
Following ini4j, here is a test file:
; Global properties
story = Snow White and the Seven Dwarfs
year = 1937
url = www.imdb.com/title/tt0029583/
; A backslash at the end of the line escapes the new line
; character, the property value continues to the next line.
; Since the ; character (after Dwarfs) starts a comment
; (either at the beginning or middle of the line, we have
; to escape it with a backslash.
plot = Snow White, pursued by a jealous queen, hides with the Dwarfs\; \
the queen feeds her a poison apple, but Prince Charming \
awakens her with a kiss. # this is a comment
# This is also a comment line
# The first : can also be used as assignment operator
Tagline: Walt Disney's New characters in his first full-length production!
file = C:\\local\\snowwhite.mpg
; Bashful
[bashful]
weight = 45.7
height = 98.8
age = 67
homePage = http://snowwhite.tale/~bashful
; Doc
[doc]
weight = 49.5
height = 87.7
age = 63
homePage = http://doc.dwarfs
No comments:
Post a Comment