diff --git a/README.md b/README.md
index 59afaac..f3a69a8 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,9 @@ name = "Mwanji Ezana"
[address]
street = "123 A Street"
city = "AnyVille"
+
+[contacts]
+ "email address" = me@example.com
```
```java
@@ -62,6 +65,7 @@ class Address {
class User {
String name;
Address address;
+ Map contacts;
}
```
@@ -70,10 +74,13 @@ User user = new Toml().parse(tomlFile).to(User.class);
assert user.name.equals("Mwanji Ezana");
assert user.address.street.equals("123 A Street");
+assert user.contacts.get("\"email address\"").equals("me@example.com");
```
Any keys not found in both the TOML and the class are ignored. Fields may be private.
+Quoted keys cannot be mapped directly to a Java object, but they can be used as keys within a `Map`.
+
TOML primitives can be mapped to a number of Java types:
TOML | Java
@@ -105,8 +112,11 @@ You can also navigate values within a table with a compound key of the form `tab
Non-existent keys return null.
+When retrieving quoted keys, the quotes must be used and the key must be spelled exactly the same way, including quotes and whitespace.
+
```toml
title = "TOML Example"
+"sub title" = "Now with quoted keys"
[database]
ports = [ 8001, 8001, 8002 ]
@@ -139,6 +149,7 @@ title = "TOML Example"
Toml toml = new Toml().parse(getTomlFile());
String title = toml.getString("title");
+String subTitle = toml.getString("\"sub title\"");
Boolean enabled = toml.getBoolean("database.enabled");
List ports = toml.getList("database.ports", Long.class);
String password = toml.getString("database.credentials.password");
diff --git a/pom.xml b/pom.xml
index b399907..c3e60bf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.moandjiezana.tomltoml4j
- 1.0.0-SNAPSHOT
+ 0.3.2-SNAPSHOTtoml4jA parser for TOMLhttp://moandjiezana.com/toml/toml4j
diff --git a/src/main/java/com/moandjiezana/toml/Keys.java b/src/main/java/com/moandjiezana/toml/Keys.java
new file mode 100644
index 0000000..bffb319
--- /dev/null
+++ b/src/main/java/com/moandjiezana/toml/Keys.java
@@ -0,0 +1,102 @@
+package com.moandjiezana.toml;
+
+import static com.moandjiezana.toml.ValueConverterUtils.isComment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Keys {
+
+ static class Key {
+ final String name;
+ final int index;
+ final String path;
+
+ Key(String name, int index, Key next) {
+ this.name = name;
+ this.index = index;
+ if (next != null) {
+ this.path = name + "." + next.path;
+ } else {
+ this.path = name;
+ }
+ }
+ }
+
+ static Keys.Key[] split(String key) {
+ List splitKey = new ArrayList();
+ StringBuilder current = new StringBuilder();
+ char[] chars = key.toCharArray();
+ boolean quoted = false;
+ boolean indexable = true;
+ boolean inIndex = false;
+ int index = -1;
+
+ for (int i = chars.length - 1; i > -1; i--) {
+ char c = chars[i];
+ if (c == ']' && indexable) {
+ inIndex = true;
+ continue;
+ }
+ indexable = false;
+ if (c == '[' && inIndex) {
+ inIndex = false;
+ index = Integer.parseInt(current.toString());
+ current = new StringBuilder();
+ continue;
+ }
+ if (c == '"' && (i == 0 || chars[i - 1] != '\\')) {
+ quoted = !quoted;
+ indexable = false;
+ }
+ if (c != '.' || quoted) {
+ current.insert(0, c);
+ } else {
+ splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
+ indexable = true;
+ index = -1;
+ current = new StringBuilder();
+ }
+ }
+
+ splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
+
+ return splitKey.toArray(new Key[0]);
+ }
+
+ /**
+ * @param line raw TOML iine to parse
+ * @return null if line is not a valid table identifier
+ */
+ static String getTableName(String line) {
+ StringBuilder sb = new StringBuilder();
+ char[] chars = line.toCharArray();
+ boolean quoted = false;
+ boolean terminated = false;
+
+ for (int i = 1; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == '"' && chars[i - 1] != '\\') {
+ quoted = !quoted;
+ } else if (!quoted && c == ']') {
+ terminated = true;
+ break;
+ } else if (!quoted && c == '[') {
+ break;
+ }
+
+ sb.append(c);
+ }
+
+ String tableName = sb.toString();
+
+ if (!terminated || !isComment(line.substring(tableName.length() + 2))) {
+ return null;
+ }
+
+ tableName = StringConverter.STRING_PARSER.replaceUnicodeCharacters(tableName);
+ return tableName;
+ }
+
+ private Keys() {}
+}
diff --git a/src/main/java/com/moandjiezana/toml/Results.java b/src/main/java/com/moandjiezana/toml/Results.java
index 84e5a53..1a3ae9b 100644
--- a/src/main/java/com/moandjiezana/toml/Results.java
+++ b/src/main/java/com/moandjiezana/toml/Results.java
@@ -75,9 +75,9 @@ class Results {
stack.pop();
}
- String[] tableParts = tableName.split("\\.");
+ Keys.Key[] tableParts = Keys.split(tableName);
for (int i = 0; i < tableParts.length; i++) {
- String tablePart = tableParts[i];
+ String tablePart = tableParts[i].name;
Container currentContainer = stack.peek();
if (tablePart.isEmpty()) {
errors.append("Empty implicit table: " + tableName + "!\n");
diff --git a/src/main/java/com/moandjiezana/toml/StringConverter.java b/src/main/java/com/moandjiezana/toml/StringConverter.java
index 05acfe7..2971e27 100644
--- a/src/main/java/com/moandjiezana/toml/StringConverter.java
+++ b/src/main/java/com/moandjiezana/toml/StringConverter.java
@@ -35,25 +35,16 @@ class StringConverter implements ValueConverter {
value = value.substring(1, stringTerminator);
value = replaceUnicodeCharacters(value);
-
- chars = value.toCharArray();
- for (int i = 0; i < chars.length - 1; i++) {
- char ch = chars[i];
- char next = chars[i + 1];
-
- if (ch == '\\' && next == '\\') {
- i++;
- } else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '/' || next == '\\')) {
- return INVALID;
- }
- }
-
value = replaceSpecialCharacters(value);
+
+ if (value == null) {
+ return INVALID;
+ }
return value;
}
- private String replaceUnicodeCharacters(String value) {
+ String replaceUnicodeCharacters(String value) {
Matcher unicodeMatcher = UNICODE_REGEX.matcher(value);
while (unicodeMatcher.find()) {
@@ -62,7 +53,19 @@ class StringConverter implements ValueConverter {
return value;
}
- private String replaceSpecialCharacters(String value) {
+ String replaceSpecialCharacters(String value) {
+ char[] chars = value.toCharArray();
+ for (int i = 0; i < chars.length - 1; i++) {
+ char ch = chars[i];
+ char next = chars[i + 1];
+
+ if (ch == '\\' && next == '\\') {
+ i++;
+ } else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '/' || next == '\\')) {
+ return null;
+ }
+ }
+
return value.replace("\\n", "\n")
.replace("\\\"", "\"")
.replace("\\t", "\t")
diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java
index 740e40f..f79b5b9 100644
--- a/src/main/java/com/moandjiezana/toml/Toml.java
+++ b/src/main/java/com/moandjiezana/toml/Toml.java
@@ -12,14 +12,12 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.gson.Gson;
@@ -33,13 +31,13 @@ import com.google.gson.JsonElement;
* {@link #getList(String, Class)}, {@link #getTable(String)} and {@link #getTables(String)} return empty values if there is no matching key.
*
*
Example usage:
- *
+ *
* Toml toml = new Toml().parse(getTomlFile());
* String name = toml.getString("name");
* Long port = toml.getLong("server.ip"); // compound key. Is equivalent to:
* Long port2 = toml.getTable("server").getLong("ip");
* MyConfig config = toml.to(MyConfig.class);
- *
+ *
*
*/
public class Toml {
@@ -67,7 +65,7 @@ public class Toml {
/**
* Populates the current Toml instance with values from file.
*
- * @param file
+ * @param file The File to be read
* @return this instance
* @throws IllegalStateException If file contains invalid TOML
*/
@@ -82,7 +80,7 @@ public class Toml {
/**
* Populates the current Toml instance with values from inputStream.
*
- * @param inputStream
+ * @param inputStream Closed after it has been read.
* @return this instance
* @throws IllegalStateException If file contains invalid TOML
*/
@@ -93,7 +91,7 @@ public class Toml {
/**
* Populates the current Toml instance with values from reader.
*
- * @param reader
+ * @param reader Closed after it has been read.
* @return this instance
* @throws IllegalStateException If file contains invalid TOML
*/
@@ -122,7 +120,7 @@ public class Toml {
/**
* Populates the current Toml instance with values from tomlString.
*
- * @param tomlString
+ * @param tomlString String to be read.
* @return this instance
* @throws IllegalStateException If tomlString is not valid TOML
*/
@@ -169,9 +167,8 @@ public class Toml {
}
/**
- * If no value is found for key, an empty Toml instance is returned.
- *
- * @param key
+ * @param key A table name, not including square brackets.
+ * @return A new Toml instance. Empty if no value is found for key.
*/
@SuppressWarnings("unchecked")
public Toml getTable(String key) {
@@ -179,8 +176,8 @@ public class Toml {
}
/**
- * If no value is found for key, an empty list is returned.
- * @param key
+ * @param key Name of array of tables, not including square brackets.
+ * @return An empty List if no value is found for key.
*/
@SuppressWarnings("unchecked")
public List getTables(String key) {
@@ -214,7 +211,9 @@ public class Toml {
*
TOML array to {@link Set}
*
*
- * @param targetClass
+ * @param targetClass Class to deserialize TOML to.
+ * @param type of targetClass.
+ * @return A new instance of targetClass.
*/
public T to(Class targetClass) {
return to(targetClass, DEFAULT_GSON);
@@ -245,55 +244,32 @@ public class Toml {
@SuppressWarnings("unchecked")
private Object get(String key) {
- String[] split = key.split("\\.");
+ if (values.containsKey(key)) {
+ return values.get(key);
+ }
+
Object current = new HashMap(values);
- for (int i = 0; i < split.length; i++) {
- if (i == 0 && values.containsKey(key)) {
- return values.get(key);
- }
-
- String keyWithDot = join(Arrays.copyOfRange(split, i, split.length));
- if (current instanceof Map && ((Map) current).containsKey(keyWithDot)) {
- return ((Map) current).get(keyWithDot);
- }
-
- String splitKey = split[i];
- Matcher matcher = ARRAY_INDEX_PATTERN.matcher(splitKey);
- int index = -1;
-
- if (matcher.find()) {
- splitKey = matcher.group(1);
- index = Integer.parseInt(matcher.group(2), 10);
+ Keys.Key[] keys = Keys.split(key);
+
+ for (Keys.Key k : keys) {
+ if (k.index == -1 && current instanceof Map && ((Map) current).containsKey(k.path)) {
+ return ((Map) current).get(k.path);
}
- current = ((Map) current).get(splitKey);
+ current = ((Map) current).get(k.name);
- if (index > -1 && current != null) {
- current = ((List>) current).get(index);
+ if (k.index > -1 && current != null) {
+ current = ((List>) current).get(k.index);
}
if (current == null) {
return defaults != null ? defaults.get(key) : null;
}
}
-
+
return current;
}
-
- private String join(String[] strings) {
- StringBuilder sb = new StringBuilder();
-
- for (String string : strings) {
- sb.append(string).append('.');
- }
-
- if (sb.length() > 0) {
- sb.deleteCharAt(sb.length() - 1);
- }
-
- return sb.toString();
- }
private Toml(Toml defaults, Map values) {
this.values = values != null ? values : Collections.emptyMap();
diff --git a/src/main/java/com/moandjiezana/toml/TomlParser.java b/src/main/java/com/moandjiezana/toml/TomlParser.java
index d514c86..8e3594b 100644
--- a/src/main/java/com/moandjiezana/toml/TomlParser.java
+++ b/src/main/java/com/moandjiezana/toml/TomlParser.java
@@ -186,12 +186,7 @@ class TomlParser {
}
private String getTableName(String line) {
- List