diff --git a/README.md b/README.md index 170addb..14c4644 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ A Toml object can be mapped to a custom class with the `Toml#to(Class)` metho Any keys not found in both the TOML and the class are ignored. -Key groups can be mapped to other custom classes or to `Map`. +Key groups can be mapped to other custom classes. ```` name = "Mwanji Ezana" @@ -92,8 +92,8 @@ When defaults are present, a shallow merge is performed. ## TODO -* Support all special characters -* Convert Toml to custom class +* Fail on invalid definitions +* Support negative numbers ## License diff --git a/src/main/java/com/moandjiezana/toml/TomlParser.java b/src/main/java/com/moandjiezana/toml/TomlParser.java index 92bb6fe..68ec2a2 100644 --- a/src/main/java/com/moandjiezana/toml/TomlParser.java +++ b/src/main/java/com/moandjiezana/toml/TomlParser.java @@ -4,7 +4,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.parboiled.BaseParser; import org.parboiled.Rule; @@ -16,6 +18,7 @@ class TomlParser extends BaseParser { static class Results { public Map values = new HashMap(); + public Set keyGroups = new HashSet(); public StringBuilder errors = new StringBuilder(); } @@ -24,7 +27,25 @@ class TomlParser extends BaseParser { } Rule KeyGroup() { - return Sequence(KeyGroupDelimiter(), KeyGroupName(), addKeyGroup((String) pop()), KeyGroupDelimiter(), Spacing()); + return Sequence(Sequence(KeyGroupDelimiter(), KeyGroupName(), addKeyGroup((String) pop()), KeyGroupDelimiter(), Spacing()), checkKeyGroup(match())); + } + + Rule IllegalCharacters() { + return ZeroOrMore(TestNot(NewLine()), ANY); + } + + boolean checkKeyGroup(String definition) { + String afterBracket = definition.substring(definition.indexOf(']') + 1); + for (char character : afterBracket.toCharArray()) { + if (character == '#') { + return true; + } + + if (!Character.isWhitespace(character)) { + results().errors.append("Invalid key group definition: ").append(definition).append(". You may have forgotten a #"); + } + } + return true; } Rule Key() { @@ -60,7 +81,7 @@ class TomlParser extends BaseParser { } Rule StringValue() { - return Sequence(push(new StringBuilder()), '"', OneOrMore(TestNot('"'), FirstOf(SpecialCharacter(), AnyCharacter())), pushString(((StringBuilder) pop()).toString()), '"'); + return Sequence(push(new StringBuilder()), '"', OneOrMore(TestNot('"'), FirstOf(UnicodeCharacter(), SpecialCharacter(), AnyCharacter())), pushString(((StringBuilder) pop()).toString()), '"'); } Rule Year() { @@ -83,6 +104,10 @@ class TomlParser extends BaseParser { return CharRange('a', 'z'); } + Rule UnicodeCharacter() { + return Sequence(Sequence('\\', 'u', OneOrMore(FirstOf(CharRange('0', '9'), CharRange('A', 'F')))), pushCharacter(match())); + } + Rule SpecialCharacter() { return Sequence(Sequence('\\', ANY), pushCharacter(match())); } @@ -106,6 +131,11 @@ class TomlParser extends BaseParser { return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f"))); } + @SuppressNode + Rule NewLine() { + return AnyOf("\r\n"); + } + @SuppressNode Rule ArrayDelimiter() { return Sequence(Spacing(), ',', Spacing()); @@ -126,13 +156,20 @@ class TomlParser extends BaseParser { } Map newKeyGroup = (Map) getContext().getValueStack().peek(); + + if (!results().keyGroups.add(name)) { + results().errors.append("Could not create key group ").append(name).append(": key group already exists!\n"); + + return true; + } + for (String splitKey : split) { if (!newKeyGroup.containsKey(splitKey)) { newKeyGroup.put(splitKey, new HashMap()); } Object currentValue = newKeyGroup.get(splitKey); if (!(currentValue instanceof Map)) { - results().errors.append("Could not create key group ").append(name).append(": key already exists!\n"); + results().errors.append("Could not create key group ").append(name).append(": key already has a value!\n"); return true; } @@ -140,6 +177,7 @@ class TomlParser extends BaseParser { } push(newKeyGroup); + return true; } @@ -213,6 +251,14 @@ class TomlParser extends BaseParser { sb.append('\r'); } else if (sc.equals("\\\\")) { sb.append('\\'); + } else if (sc.equals("\\/")) { + sb.append('/'); + } else if (sc.equals("\\b")) { + sb.append('\b'); + } else if (sc.equals("\\f")) { + sb.append('\f'); + } else if (sc.startsWith("\\u")) { + sb.append(Character.toChars(Integer.parseInt(sc.substring(2), 16))); } else if (sc.startsWith("\\")) { results().errors.append(sc + " is a reserved special character and cannot be used!\n"); } else { diff --git a/src/test/java/com/moandjiezana/toml/TomlTest.java b/src/test/java/com/moandjiezana/toml/TomlTest.java index c5dc577..fe7c7a4 100644 --- a/src/test/java/com/moandjiezana/toml/TomlTest.java +++ b/src/test/java/com/moandjiezana/toml/TomlTest.java @@ -10,6 +10,7 @@ import java.io.File; import java.util.Calendar; import java.util.TimeZone; +import org.junit.Ignore; import org.junit.Test; public class TomlTest { @@ -99,6 +100,14 @@ public class TomlTest { assertEquals("value", toml.getKeyGroup("group.sub").getString("key")); } + @Test + public void should_get_value_from_key_group_with_sub_key_group() throws Exception { + Toml toml = new Toml().parse("[a.b]\nc=1\n[a]\nd=2"); + + assertEquals(2, toml.getLong("a.d").intValue()); + assertEquals(1, toml.getKeyGroup("a.b").getLong("c").intValue()); + } + @Test public void should_return_null_if_no_value_for_key() throws Exception { Toml toml = new Toml().parse(""); @@ -173,7 +182,14 @@ public class TomlTest { public void should_support_special_characters_in_strings() { Toml toml = new Toml().parse(new File(getClass().getResource("should_support_special_characters_in_strings.toml").getFile())); - assertEquals("\" \t \n \r \\", toml.getString("key")); + assertEquals("\" \t \n \r \\ / \b \f", toml.getString("key")); + } + + @Test + public void should_support_unicode_characters_in_strings() throws Exception { + Toml toml = new Toml().parse("key=\"\\u00B1\""); + + assertEquals("±", toml.getString("key")); } @Test(expected = IllegalStateException.class) @@ -200,11 +216,21 @@ public class TomlTest { @Test(expected = IllegalStateException.class) public void should_fail_when_key_is_overwritten_by_key_group() { - new Toml().parse("[fruit]\ntype=\"apple\"\n[fruit.type]\napple=\"yes\""); + new Toml().parse("[a]\nb=1\n[a.b]\nc=2"); } @Test(expected = IllegalStateException.class) public void should_fail_when_key_is_overwritten_by_another_key() { new Toml().parse("[fruit]\ntype=\"apple\"\ntype=\"orange\""); } + + @Test(expected = IllegalStateException.class) + public void should_fail_when_key_group_defined_twice() throws Exception { + new Toml().parse("[a]\nb=1\n[a]\nc=2"); + } + + @Ignore @Test(expected = IllegalStateException.class) + public void should_fail_when_illegal_characters_after_key_group() throws Exception { + new Toml().parse("[error] if you didn't catch this, your parser is broken"); + } } diff --git a/src/test/resources/com/moandjiezana/toml/hard_example.toml b/src/test/resources/com/moandjiezana/toml/hard_example.toml index af988e2..051a340 100644 --- a/src/test/resources/com/moandjiezana/toml/hard_example.toml +++ b/src/test/resources/com/moandjiezana/toml/hard_example.toml @@ -19,3 +19,15 @@ test_string = "You'll hate me after this - #" # " Annoying, isn't it? "]", # ] Oh yes I did ] + +# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test + +[error] if you didn't catch this, your parser is broken +#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this +#array = [ +# "This might most likely happen in multiline arrays", +# Like here, +# "or here, +# and here" +# ] End of array comment, forgot the # +#number = 3.14 pi <--again forgot the # \ No newline at end of file diff --git a/src/test/resources/com/moandjiezana/toml/should_support_special_characters_in_strings.toml b/src/test/resources/com/moandjiezana/toml/should_support_special_characters_in_strings.toml index 8c35dc9..bd40d5e 100644 --- a/src/test/resources/com/moandjiezana/toml/should_support_special_characters_in_strings.toml +++ b/src/test/resources/com/moandjiezana/toml/should_support_special_characters_in_strings.toml @@ -1 +1 @@ -key = "\" \t \n \r \\" \ No newline at end of file +key = "\" \t \n \r \\ \/ \b \f" \ No newline at end of file