partial work on KeyPathConverter replacement

This commit is contained in:
marhali 2022-04-10 20:29:13 +02:00
parent 4b34fe357f
commit 9d5be9caa0
3 changed files with 265 additions and 26 deletions

View File

@ -13,6 +13,7 @@ import java.util.regex.Pattern;
* If nesting is enabled the delimiter within a section is escaped otherwise the delimiter between the key sections.
* @author marhali
*/
@Deprecated // Replacement moved to utils
public class KeyPathConverter {
private final boolean nestKeys;

View File

@ -0,0 +1,145 @@
package de.marhali.easyi18n.util;
import de.marhali.easyi18n.model.translation.KeyPath;
import de.marhali.easyi18n.settings.ProjectSettings;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* Stateful utility to transform absolute translation keys into their character literal representation and backwards.
* @author marhali
*/
public class KeyPathConverter {
private final ProjectSettings settings;
/**
* Constructs a new converter instance
* @param settings Delimiter configuration
*/
public KeyPathConverter(ProjectSettings settings) {
this.settings = settings;
}
/**
* Transform to character literal representation
* @param path Absolute key path
* @return Character literal
*/
public @NotNull String toString(@NotNull KeyPath path) {
StringBuilder builder = new StringBuilder();
for(int i = 0; i < path.size(); i++) {
if(i > 0) { // Delimiters
if(i == 1 && settings.getFolderStrategy().isNamespaceMode() && settings.getNamespaceDelimiter() != null) {
builder.append(quoteDelimiter(settings.getNamespaceDelimiter()));
} else {
builder.append(quoteDelimiter(settings.getSectionDelimiter()));
}
}
// Section content
builder.append(quoteSection(path.get(i)));
}
return builder.toString();
}
/**
* Splits provided character literal into key path sections.
* If namespace mode is activated and none was provided, the default namespace will be added.
* @return Layered key path sections
*/
public @NotNull KeyPath fromString(@NotNull String literalPath) {
KeyPath path = new KeyPath();
int i = 0;
for(String section : literalPath.split(getSplitRegex())) {
// Missing namespace
if(i == 0 && settings.getFolderStrategy().isNamespaceMode() && hasDefaultNamespace()) {
if(section.length() == literalPath.length() || !String.valueOf(literalPath.charAt(section.length())).equals(settings.getNamespaceDelimiter())) {
path.add(settings.getDefaultNamespace());
}
}
path.add(unquoteSection(section));
i++;
}
return path;
}
@Override
public String toString() {
return "KeyPathConverter{" +
"settings=" + settings +
'}';
}
/*
* INTERNAL METHODS
*/
private boolean hasDefaultNamespace() {
return settings.getDefaultNamespace() != null && !settings.getDefaultNamespace().isEmpty();
}
private String getSplitRegex() {
return settings.isNestedKeys()
? ("(?<!" + Pattern.quote("\\") + ")" + getSplitCharsRegex())
: Pattern.quote("\\") + getSplitCharsRegex();
}
private String getSplitCharsRegex() {
StringBuilder builder = new StringBuilder();
builder.append("(");
builder.append(Pattern.quote(settings.getSectionDelimiter()));
// Add optional namespace delimiter if present
if(hasDefaultNamespace()) {
builder.append("|");
builder.append(Pattern.quote(Objects.requireNonNull(settings.getNamespaceDelimiter())));
}
builder.append(")");
return builder.toString();
}
/**
* Securely escape found delimiters inside provided section according to the configured policy.
*/
private String quoteSection(String section) {
if(!settings.isNestedKeys()) {
return section;
}
if(hasDefaultNamespace()) {
section = section.replace(settings.getNamespaceDelimiter(), "\\" + settings.getNamespaceDelimiter());
}
section = section.replace(settings.getSectionDelimiter(), "\\" + settings.getSectionDelimiter());
return section;
}
private String unquoteSection(String section) {
if(hasDefaultNamespace()) {
section = section.replace("\\" + settings.getNamespaceDelimiter(), settings.getNamespaceDelimiter());
}
section = section.replace("\\" + settings.getSectionDelimiter(), settings.getSectionDelimiter());
return section;
}
/**
* Securely escape provided delimiter according to the configured policy.
*/
private String quoteDelimiter(String delimiter) {
return settings.isNestedKeys() ? delimiter : delimiter.replace(delimiter, "\\" + delimiter);
}
}

View File

@ -1,53 +1,146 @@
package de.marhali.easyi18n;
import de.marhali.easyi18n.model.KeyPath;
import de.marhali.easyi18n.model.KeyPathConverter;
import de.marhali.easyi18n.io.parser.ParserStrategyType;
import de.marhali.easyi18n.model.FolderStrategyType;
import de.marhali.easyi18n.model.translation.KeyPath;
import de.marhali.easyi18n.settings.ProjectSettings;
import de.marhali.easyi18n.util.KeyPathConverter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit tests for {@link KeyPathConverter}.
* @author marhali
*/
public class KeyPathConverterTest {
private final KeyPathConverter deepMapper = new KeyPathConverter(true);
private final KeyPathConverter flatMapper = new KeyPathConverter(false);
@Test
public void testNestedConcat() {
Assert.assertEquals("first\\\\.section.second.third",
deepMapper.concat(KeyPath.of("first.section", "second", "third")));
public void nonNestedSingle() {
KeyPathConverter converter = getConverter(FolderStrategyType.SINGLE, null, ".", null, false);
Assert.assertEquals("first.second.third",
deepMapper.concat(KeyPath.of("first", "second", "third")));
Assert.assertEquals("username", converter.toString(new KeyPath("username")));
Assert.assertEquals("username\\.nested.section", converter.toString(new KeyPath("username", "nested.section")));
Assert.assertEquals("username.normal.nested", converter.toString(new KeyPath("username.normal.nested")));
Assert.assertEquals(new KeyPath("username"), converter.fromString("username"));
Assert.assertEquals(new KeyPath("username", "nested.section"), converter.fromString("username\\.nested.section"));
Assert.assertEquals(new KeyPath("username.normal.nested"), converter.fromString("username.normal.nested"));
}
@Test
public void testNestedSplit() {
Assert.assertEquals(KeyPath.of("first.section", "second", "third"),
deepMapper.split("first\\\\.section.second.third"));
public void nonNestedNamespace() {
KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, ":", ".", "common", false);
Assert.assertEquals(KeyPath.of("first", "second", "third"),
deepMapper.split("first.second.third"));
Assert.assertEquals("username", converter.toString(new KeyPath("username")));
Assert.assertEquals("username.title\\:concat.leaf\\.node", converter.toString(new KeyPath("username.title", "concat.leaf", "node")));
Assert.assertEquals(new KeyPath("common", "username"), converter.fromString("username"));
// TODO: problem here
Assert.assertEquals(new KeyPath("username.title", "concat.leaf", "node"), converter.fromString("username.title\\:concat\\.leaf.node"));
}
@Test
public void testNonNestedConcat() {
Assert.assertEquals("flat.map\\\\.deeper",
flatMapper.concat(KeyPath.of("flat.map", "deeper")));
public void single() {
KeyPathConverter converter = getConverter(FolderStrategyType.SINGLE,null, ".", null, true);
Assert.assertEquals("flat.map.keys",
flatMapper.concat(KeyPath.of("flat.map.keys")));
Assert.assertEquals("username", converter.toString(new KeyPath("username")));
Assert.assertEquals("username.title", converter.toString(new KeyPath("username", "title")));
Assert.assertEquals("username.nested\\.section", converter.toString(new KeyPath("username", "nested.section")));
Assert.assertEquals("username.deep.nested", converter.toString(new KeyPath("username", "deep", "nested")));
Assert.assertEquals(new KeyPath("username"), converter.fromString("username"));
Assert.assertEquals(new KeyPath("username", "title"), converter.fromString("username.title"));
Assert.assertEquals(new KeyPath("username", "nested.section"), converter.fromString("username.nested\\.section"));
Assert.assertEquals(new KeyPath("username", "deep", "nested"), converter.fromString("username.deep.nested"));
}
@Test
public void testNonNestedSplit() {
Assert.assertEquals(KeyPath.of("flat.keys.with", "deep.section"),
flatMapper.split("flat.keys.with\\\\.deep.section"));
public void namespace() {
KeyPathConverter converter = getConverter(FolderStrategyType.MODULARIZED_NAMESPACE, ":", ".", "common", true);
Assert.assertEquals(KeyPath.of("flat.keys.only"),
flatMapper.split("flat.keys.only"));
Assert.assertEquals("common", converter.toString(new KeyPath("common")));
Assert.assertEquals("common:username", converter.toString(new KeyPath("common", "username")));
Assert.assertEquals("nested\\:common:username", converter.toString(new KeyPath("nested:common", "username")));
Assert.assertEquals("common:username.nested\\.section", converter.toString(new KeyPath("common", "username", "nested.section")));
Assert.assertEquals("common:username.deep.nested", converter.toString(new KeyPath("common", "username", "deep", "nested")));
Assert.assertEquals(new KeyPath("common", "key"), converter.fromString("key"));
Assert.assertEquals(new KeyPath("common", "common:username", "title"), converter.fromString("common\\:username.title"));
Assert.assertEquals(new KeyPath("user", "title"), converter.fromString("user:title"));
Assert.assertEquals(new KeyPath("user:complex", "deep.nested", "value"), converter.fromString("user\\:complex:deep\\.nested.value"));
}
}
private KeyPathConverter getConverter(FolderStrategyType strategy, String namespaceDelim,
String sectionDelim, String defaultNs, boolean nestKeys) {
return new KeyPathConverter(new ProjectSettings() {
@Override
public @Nullable String getLocalesDirectory() {
return null;
}
@Override
public @NotNull FolderStrategyType getFolderStrategy() {
return strategy;
}
@Override
public @NotNull ParserStrategyType getParserStrategy() {
return null;
}
@Override
public @NotNull String getFilePattern() {
return null;
}
@Override
public boolean isSorting() {
return false;
}
@Override
public @Nullable String getNamespaceDelimiter() {
return namespaceDelim;
}
@Override
public @NotNull String getSectionDelimiter() {
return sectionDelim;
}
@Override
public @Nullable String getContextDelimiter() {
return null;
}
@Override
public @Nullable String getPluralDelimiter() {
return null;
}
@Override
public @Nullable String getDefaultNamespace() {
return defaultNs;
}
@Override
public @NotNull String getPreviewLocale() {
return null;
}
@Override
public boolean isNestedKeys() {
return nestKeys;
}
@Override
public boolean isAssistance() {
return false;
}
});
}
}