mirror of
https://github.com/plexusorg/toml4j.git
synced 2025-01-01 04:52:24 +00:00
Added support for all special and Unicode characters and checking for duplicate key groups.
This commit is contained in:
parent
450e7402ef
commit
2483dc48ae
5 changed files with 93 additions and 9 deletions
|
@ -61,7 +61,7 @@ A Toml object can be mapped to a custom class with the `Toml#to(Class<T>)` metho
|
||||||
|
|
||||||
Any keys not found in both the TOML and the class are ignored.
|
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<String, Object>`.
|
Key groups can be mapped to other custom classes.
|
||||||
|
|
||||||
````
|
````
|
||||||
name = "Mwanji Ezana"
|
name = "Mwanji Ezana"
|
||||||
|
@ -92,8 +92,8 @@ When defaults are present, a shallow merge is performed.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Support all special characters
|
* Fail on invalid definitions
|
||||||
* Convert Toml to custom class
|
* Support negative numbers
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.parboiled.BaseParser;
|
import org.parboiled.BaseParser;
|
||||||
import org.parboiled.Rule;
|
import org.parboiled.Rule;
|
||||||
|
@ -16,6 +18,7 @@ class TomlParser extends BaseParser<Object> {
|
||||||
|
|
||||||
static class Results {
|
static class Results {
|
||||||
public Map<String, Object> values = new HashMap<String, Object>();
|
public Map<String, Object> values = new HashMap<String, Object>();
|
||||||
|
public Set<String> keyGroups = new HashSet<String>();
|
||||||
public StringBuilder errors = new StringBuilder();
|
public StringBuilder errors = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +27,25 @@ class TomlParser extends BaseParser<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rule KeyGroup() {
|
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() {
|
Rule Key() {
|
||||||
|
@ -60,7 +81,7 @@ class TomlParser extends BaseParser<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rule StringValue() {
|
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() {
|
Rule Year() {
|
||||||
|
@ -83,6 +104,10 @@ class TomlParser extends BaseParser<Object> {
|
||||||
return CharRange('a', 'z');
|
return CharRange('a', 'z');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rule UnicodeCharacter() {
|
||||||
|
return Sequence(Sequence('\\', 'u', OneOrMore(FirstOf(CharRange('0', '9'), CharRange('A', 'F')))), pushCharacter(match()));
|
||||||
|
}
|
||||||
|
|
||||||
Rule SpecialCharacter() {
|
Rule SpecialCharacter() {
|
||||||
return Sequence(Sequence('\\', ANY), pushCharacter(match()));
|
return Sequence(Sequence('\\', ANY), pushCharacter(match()));
|
||||||
}
|
}
|
||||||
|
@ -106,6 +131,11 @@ class TomlParser extends BaseParser<Object> {
|
||||||
return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f")));
|
return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressNode
|
||||||
|
Rule NewLine() {
|
||||||
|
return AnyOf("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressNode
|
@SuppressNode
|
||||||
Rule ArrayDelimiter() {
|
Rule ArrayDelimiter() {
|
||||||
return Sequence(Spacing(), ',', Spacing());
|
return Sequence(Spacing(), ',', Spacing());
|
||||||
|
@ -126,13 +156,20 @@ class TomlParser extends BaseParser<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> newKeyGroup = (Map<String, Object>) getContext().getValueStack().peek();
|
Map<String, Object> newKeyGroup = (Map<String, Object>) 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) {
|
for (String splitKey : split) {
|
||||||
if (!newKeyGroup.containsKey(splitKey)) {
|
if (!newKeyGroup.containsKey(splitKey)) {
|
||||||
newKeyGroup.put(splitKey, new HashMap<String, Object>());
|
newKeyGroup.put(splitKey, new HashMap<String, Object>());
|
||||||
}
|
}
|
||||||
Object currentValue = newKeyGroup.get(splitKey);
|
Object currentValue = newKeyGroup.get(splitKey);
|
||||||
if (!(currentValue instanceof Map)) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +177,7 @@ class TomlParser extends BaseParser<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
push(newKeyGroup);
|
push(newKeyGroup);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +251,14 @@ class TomlParser extends BaseParser<Object> {
|
||||||
sb.append('\r');
|
sb.append('\r');
|
||||||
} else if (sc.equals("\\\\")) {
|
} else if (sc.equals("\\\\")) {
|
||||||
sb.append('\\');
|
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("\\")) {
|
} else if (sc.startsWith("\\")) {
|
||||||
results().errors.append(sc + " is a reserved special character and cannot be used!\n");
|
results().errors.append(sc + " is a reserved special character and cannot be used!\n");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.io.File;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TomlTest {
|
public class TomlTest {
|
||||||
|
@ -99,6 +100,14 @@ public class TomlTest {
|
||||||
assertEquals("value", toml.getKeyGroup("group.sub").getString("key"));
|
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
|
@Test
|
||||||
public void should_return_null_if_no_value_for_key() throws Exception {
|
public void should_return_null_if_no_value_for_key() throws Exception {
|
||||||
Toml toml = new Toml().parse("");
|
Toml toml = new Toml().parse("");
|
||||||
|
@ -173,7 +182,14 @@ public class TomlTest {
|
||||||
public void should_support_special_characters_in_strings() {
|
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()));
|
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)
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@ -200,11 +216,21 @@ public class TomlTest {
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void should_fail_when_key_is_overwritten_by_key_group() {
|
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)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void should_fail_when_key_is_overwritten_by_another_key() {
|
public void should_fail_when_key_is_overwritten_by_another_key() {
|
||||||
new Toml().parse("[fruit]\ntype=\"apple\"\ntype=\"orange\"");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,15 @@ test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
||||||
"]",
|
"]",
|
||||||
# ] Oh yes I did
|
# ] 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 #
|
|
@ -1 +1 @@
|
||||||
key = "\" \t \n \r \\"
|
key = "\" \t \n \r \\ \/ \b \f"
|
Loading…
Reference in a new issue