Added support for quoted keys with index arrays

This commit is contained in:
moandji.ezana 2015-01-15 11:44:14 +02:00
parent 62b2718a17
commit df235e5a29
6 changed files with 132 additions and 68 deletions

View file

@ -1,33 +1,102 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.ArrayList;
import java.util.List;
class Keys {
static String[] split(String key) {
List<String> splitKey = new ArrayList<String>();
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<Key> splitKey = new ArrayList<Key>();
StringBuilder current = new StringBuilder();
char[] chars = key.toCharArray();
boolean quoted = false;
boolean indexable = true;
boolean inIndex = false;
int index = -1;
for (char c : chars) {
if (c == '"') {
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.append(c);
current.insert(0, c);
} else {
splitKey.add(current.toString());
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
indexable = true;
index = -1;
current = new StringBuilder();
}
}
splitKey.add(current.toString());
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
return splitKey.toArray(new String[0]);
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() {}
}

View file

@ -75,9 +75,9 @@ class Results {
stack.pop();
}
String[] tableParts = Keys.split(tableName);
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");

View file

@ -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")

View file

@ -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;
@ -250,52 +248,28 @@ public class Toml {
return values.get(key);
}
String[] split = Keys.split(key);
Object current = new HashMap<String, Object>(values);
for (int i = 0; i < split.length; i++) {
String keyWithDot = join(Arrays.copyOfRange(split, i, split.length));
if (current instanceof Map && ((Map<String, Object>) current).containsKey(keyWithDot)) {
return ((Map<String, Object>) 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<String, Object>) current).containsKey(k.path)) {
return ((Map<String, Object>) current).get(k.path);
}
current = ((Map<String, Object>) current).get(splitKey);
current = ((Map<String, Object>) 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<String, Object> values) {
this.values = values != null ? values : Collections.<String, Object>emptyMap();

View file

@ -186,12 +186,7 @@ class TomlParser {
}
private String getTableName(String line) {
List<Object> resultValue = parse(parser().Table(), line);
if (resultValue == null) {
return null;
}
return (String) resultValue.get(0);
return Keys.getTableName(line);
}
private boolean isKeyValid(String key) {

View file

@ -51,13 +51,36 @@ public class QuotedKeysTest {
}
@Test
public void should_convert() throws Exception {
public void should_convert_quoted_keys_to_map_but_not_to_object_fields() throws Exception {
Quoted quoted = new Toml().parse("\"ʎǝʞ\" = \"value\" \n[map] \n \"ʎǝʞ\" = \"value\"").to(Quoted.class);
assertNull(quoted.ʎǝʞ);
assertEquals("value", quoted.map.get("\"ʎǝʞ\""));
}
@Test
public void should_support_table_array_index_with_quoted_key() throws Exception {
Toml toml = new Toml().parse("[[dog.\" type\"]] \n name = \"type0\" \n [[dog.\" type\"]] \n name = \"type1\"");
assertEquals("type0", toml.getString("dog.\" type\"[0].name"));
assertEquals("type1", toml.getString("dog.\" type\"[1].name"));
}
@Test
public void should_support_quoted_key_containing_square_brackets() throws Exception {
Toml toml = new Toml().parse("[dog.\" type[abc]\"] \n name = \"type0\" \n [dog.\" type[1]\"] \n \"name[]\" = \"type1\"");
assertEquals("type0", toml.getString("dog.\" type[abc]\".name"));
assertEquals("type1", toml.getString("dog.\" type[1]\".\"name[]\""));
}
@Test
public void should_support_quoted_key_containing_escaped_quote() throws Exception {
Toml toml = new Toml().parse("[dog.\"ty\\\"pe\"] \n \"na\\\"me\" = \"type0\"");
assertEquals("type0", toml.getString("dog.\"ty\\\"pe\".\"na\\\"me\""));
}
private static class Quoted {
String ʎǝʞ;