From df235e5a292d96e985b0f77b497402ee52a19f8d Mon Sep 17 00:00:00 2001 From: "moandji.ezana" Date: Thu, 15 Jan 2015 11:44:14 +0200 Subject: [PATCH] Added support for quoted keys with index arrays --- src/main/java/com/moandjiezana/toml/Keys.java | 87 +++++++++++++++++-- .../java/com/moandjiezana/toml/Results.java | 4 +- .../moandjiezana/toml/StringConverter.java | 33 +++---- src/main/java/com/moandjiezana/toml/Toml.java | 44 ++-------- .../com/moandjiezana/toml/TomlParser.java | 7 +- .../com/moandjiezana/toml/QuotedKeysTest.java | 25 +++++- 6 files changed, 132 insertions(+), 68 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/Keys.java b/src/main/java/com/moandjiezana/toml/Keys.java index 7478b8f..bffb319 100644 --- a/src/main/java/com/moandjiezana/toml/Keys.java +++ b/src/main/java/com/moandjiezana/toml/Keys.java @@ -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 splitKey = new ArrayList(); + 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 (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() {} - } diff --git a/src/main/java/com/moandjiezana/toml/Results.java b/src/main/java/com/moandjiezana/toml/Results.java index 73ad666..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 = 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"); 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 a95b443..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; @@ -250,52 +248,28 @@ public class Toml { return values.get(key); } - String[] split = Keys.split(key); Object current = new HashMap(values); - for (int i = 0; i < split.length; i++) { - - 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 resultValue = parse(parser().Table(), line); - if (resultValue == null) { - return null; - } - - return (String) resultValue.get(0); + return Keys.getTableName(line); } private boolean isKeyValid(String key) { diff --git a/src/test/java/com/moandjiezana/toml/QuotedKeysTest.java b/src/test/java/com/moandjiezana/toml/QuotedKeysTest.java index 4be4d80..0e8402d 100644 --- a/src/test/java/com/moandjiezana/toml/QuotedKeysTest.java +++ b/src/test/java/com/moandjiezana/toml/QuotedKeysTest.java @@ -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 ʎǝʞ;