From 1c87a9e85a6cc5b9651df5f1724e3c83aa4aeb97 Mon Sep 17 00:00:00 2001 From: "moandji.ezana" Date: Thu, 12 Feb 2015 22:38:17 +0200 Subject: [PATCH] Improved bare key validation and quoted key support --- .../com/moandjiezana/toml/Identifier.java | 194 ++++++++++++------ .../java/com/moandjiezana/toml/Results.java | 10 +- .../com/moandjiezana/toml/TomlParser.java | 2 +- .../com/moandjiezana/toml/BareKeysTest.java | 30 +++ 4 files changed, 169 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/Identifier.java b/src/main/java/com/moandjiezana/toml/Identifier.java index abaad22..ddc2478 100644 --- a/src/main/java/com/moandjiezana/toml/Identifier.java +++ b/src/main/java/com/moandjiezana/toml/Identifier.java @@ -41,6 +41,18 @@ class Identifier { return name; } + String getBareName() { + if (isKey()) { + return name; + } + + if (isTable()) { + return name.substring(1, name.length() - 1); + } + + return name.substring(2, name.length() - 2); + } + boolean isKey() { return type == Type.KEY; } @@ -71,7 +83,7 @@ class Identifier { } } - return sb.toString(); + return StringConverter.STRING_PARSER.replaceUnicodeCharacters(sb.toString()); } private static boolean isValidKey(String name, Context context) { @@ -102,42 +114,72 @@ class Identifier { } private static boolean isValidTable(String name, Context context) { + boolean valid = true; + if (!name.endsWith("]")) { + valid = false; + } + + String trimmed = name.substring(1, name.length() - 1).trim(); + if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) { + valid = false; + } + + if (!valid) { context.errors.invalidTable(name, context.line.get()); return false; } - char[] chars = name.toCharArray(); + char[] chars = trimmed.toCharArray(); boolean quoted = false; - boolean terminated = false; - int endIndex = -1; - boolean preKey = true; - boolean valid = true; + boolean dotAllowed = false; + boolean quoteAllowed = true; + boolean charAllowed = true; - for (int i = 1; i < name.length() - 1; i++) { - char c = name.charAt(i); - if (c == '"' && chars[i - 1] != '\\') { - if (!quoted && i > 1 && chars [i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) { - valid = false; - break; - } - quoted = !quoted; - } else if (!quoted && c == '.') { - preKey = true; - } else if (!quoted && Character.isWhitespace(c)) { - if (preKey && i > 1 && chars[i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) { - valid = false; - break; - } - if (!preKey && chars.length > i + 1 && chars[i + 1] != '.' && chars[i + 1] != ']' && !Character.isWhitespace(chars[i + 1])) { - valid = false; - break; - } - } else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1)) { - valid = false; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + + if (!valid) { break; - } else if (!quoted) { - preKey = false; + } + + if (c == '"') { + if (!quoteAllowed) { + valid = false; + } else if (quoted && chars[i - 1] != '\\') { + charAllowed = false; + dotAllowed = true; + quoteAllowed = false; + } else if (!quoted) { + quoted = true; + quoteAllowed = true; + } + } else if (quoted) { + continue; + } else if (c == '.') { + if (dotAllowed) { + charAllowed = true; + dotAllowed = false; + quoteAllowed = true; + } else { + context.errors.emptyImplicitTable(name, context.line.get()); + return false; + } + } else if (Character.isWhitespace(c)) { + char prev = chars[i - 1]; + if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') { + charAllowed = false; + dotAllowed = true; + quoteAllowed = true; + } + } else { + if (charAllowed && ALLOWED_CHARS_KEYS.indexOf(c) > -1) { + charAllowed = true; + dotAllowed = true; + quoteAllowed = false; + } else { + valid = false; + } } } @@ -151,47 +193,81 @@ class Identifier { } private static boolean isValidTableArray(String line, Context context) { - if (!line.endsWith("]]") || line.substring(2, line.length() - 2).trim().isEmpty()) { - context.errors.invalidTableArray(line, context.line.get()); - - return false; - } - - char[] chars = line.toCharArray(); - boolean quoted = false; - boolean preKey = true; boolean valid = true; - for (int i = 2; i < line.length() - 2; i++) { + if (!line.endsWith("]]")) { + valid = false; + } + + String trimmed = line.substring(2, line.length() - 2).trim(); + if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) { + valid = false; + } + + if (!valid) { + context.errors.invalidTableArray(line, context.line.get()); + return false; + } + + + char[] chars = trimmed.toCharArray(); + boolean quoted = false; + boolean dotAllowed = false; + boolean quoteAllowed = true; + boolean charAllowed = true; + + for (int i = 0; i < chars.length; i++) { char c = chars[i]; - if (c == '"' && chars[i - 1] != '\\') { - if (!quoted && i > 1 && chars [i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) { - valid = false; - } - quoted = !quoted; - } else if (!quoted && c == '.') { - preKey = true; - } else if (!quoted && Character.isWhitespace(c)) { - if (preKey && i > 2 && chars[i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) { - valid = false; - } - if (!preKey && chars.length > i + 1 && chars[i + 1] != '.' && chars[i + 1] != ']' && !Character.isWhitespace(chars[i + 1])) { - valid = false; - break; - } - } else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1)) { - valid = false; - } else if (!valid) { + + if (!valid) { break; + } + + if (c == '"') { + if (!quoteAllowed) { + valid = false; + } else if (quoted && chars[i - 1] != '\\') { + charAllowed = false; + dotAllowed = true; + quoteAllowed = false; + } else if (!quoted) { + quoted = true; + quoteAllowed = true; + } + } else if (quoted) { + continue; + } else if (c == '.') { + if (dotAllowed) { + charAllowed = true; + dotAllowed = false; + quoteAllowed = true; + } else { + context.errors.emptyImplicitTable(line, context.line.get()); + return false; + } + } else if (Character.isWhitespace(c)) { + char prev = chars[i - 1]; + if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') { + charAllowed = false; + dotAllowed = true; + quoteAllowed = true; + } } else { - preKey = false; + if (charAllowed && ALLOWED_CHARS_KEYS.indexOf(c) > -1) { + charAllowed = true; + dotAllowed = true; + quoteAllowed = false; + } else { + valid = false; + } } } if (!valid) { context.errors.invalidTableArray(line, context.line.get()); + return false; } - return valid; + return true; } } diff --git a/src/main/java/com/moandjiezana/toml/Results.java b/src/main/java/com/moandjiezana/toml/Results.java index cdf3d20..0a85431 100644 --- a/src/main/java/com/moandjiezana/toml/Results.java +++ b/src/main/java/com/moandjiezana/toml/Results.java @@ -162,7 +162,8 @@ class Results { } } - void startTableArray(String tableName) { + void startTableArray(Identifier identifier) { + String tableName = identifier.getBareName(); while (stack.size() > 1) { stack.pop(); } @@ -201,15 +202,11 @@ class Results { } void startTables(Identifier id) { - String tableName = id.getName().substring(1, id.getName().length() - 1); + String tableName = id.getBareName(); if (!tables.add(tableName)) { errors.duplicateTable(tableName, -1); } - if (tableName.endsWith(".")) { - errors.emptyImplicitTable(tableName, -1); - } - while (stack.size() > 1) { stack.pop(); } @@ -219,7 +216,6 @@ class Results { String tablePart = tableParts[i].name; Container currentContainer = stack.peek(); if (tablePart.isEmpty()) { - errors.emptyImplicitTable(tableName, -1); } else if (currentContainer.get(tablePart) instanceof Container) { Container nextTable = (Container) currentContainer.get(tablePart); stack.push(nextTable); diff --git a/src/main/java/com/moandjiezana/toml/TomlParser.java b/src/main/java/com/moandjiezana/toml/TomlParser.java index 790e96c..9d7eb4e 100644 --- a/src/main/java/com/moandjiezana/toml/TomlParser.java +++ b/src/main/java/com/moandjiezana/toml/TomlParser.java @@ -34,7 +34,7 @@ class TomlParser { } else if (id.isTable()) { results.startTables(id); } else if (id.isTableArray()) { - results.startTableArray(Keys.getTableArrayName(id.getName())); + results.startTableArray(id); } } } else if (c == '\n') { diff --git a/src/test/java/com/moandjiezana/toml/BareKeysTest.java b/src/test/java/com/moandjiezana/toml/BareKeysTest.java index 6e21b6e..54ccb3c 100644 --- a/src/test/java/com/moandjiezana/toml/BareKeysTest.java +++ b/src/test/java/com/moandjiezana/toml/BareKeysTest.java @@ -45,6 +45,11 @@ public class BareKeysTest { public void should_fail_when_characters_outside_accept_range_are_used_in_table_name() throws Exception { new Toml().parse("[~]"); } + + @Test(expected = IllegalStateException.class) + public void should_fail_when_characters_outside_accept_range_are_used_in_table_array_name() throws Exception { + new Toml().parse("[[~]]"); + } @Test(expected = IllegalStateException.class) public void should_fail_when_dots_in_key_name() throws Exception { @@ -66,6 +71,16 @@ public class BareKeysTest { new Toml().parse("[valid key]"); } + @Test(expected = IllegalStateException.class) + public void should_fail_on_sharp_sign_in_table_array_name() throws Exception { + new Toml().parse("[[group#]]\nkey=1"); + } + + @Test(expected = IllegalStateException.class) + public void should_fail_on_spaces_in_table_array_name() throws Exception { + new Toml().parse("[[valid key]]"); + } + @Test(expected = IllegalStateException.class) public void should_fail_on_question_marks_in_key_name() throws Exception { new Toml().parse("key?=true"); @@ -90,4 +105,19 @@ public class BareKeysTest { public void should_fail_on_nested_table_name_starting_with_empty_table_name() { new Toml().parse("[.b]"); } + + @Test(expected = IllegalStateException.class) + public void should_fail_on_nested_table_array_name_ending_with_empty_table_name() { + new Toml().parse("[[a.]]"); + } + + @Test(expected = IllegalStateException.class) + public void should_fail_on_nested_table_array_name_containing_empty_table_name() { + new Toml().parse("[[a..b]]"); + } + + @Test(expected = IllegalStateException.class) + public void should_fail_on_nested_table_array_name_starting_with_empty_table_name() { + new Toml().parse("[[.b]]"); + } }