Add CssInlineStyleParser
This commit is contained in:
parent
23b95e70b9
commit
f1a38fca0f
@ -78,6 +78,7 @@ ext {
|
||||
|
||||
deps['test'] = [
|
||||
'junit' : 'junit:junit:4.12',
|
||||
'robolectric': 'org.robolectric:robolectric:3.8'
|
||||
'robolectric': 'org.robolectric:robolectric:3.8',
|
||||
'ix-java' : 'com.github.akarnokd:ixjava:1.0.0'
|
||||
]
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ dependencies {
|
||||
api it['commonmark-strikethrough']
|
||||
api it['commonmark-table']
|
||||
}
|
||||
|
||||
deps['test'].with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
@ -0,0 +1,171 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public abstract class CssInlineStyleParser {
|
||||
|
||||
@NonNull
|
||||
public abstract Iterable<CssProperty> parse(@NonNull String inlineStyle);
|
||||
|
||||
@NonNull
|
||||
public static CssInlineStyleParser create() {
|
||||
return new Impl();
|
||||
}
|
||||
|
||||
static class Impl extends CssInlineStyleParser {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Iterable<CssProperty> parse(@NonNull String inlineStyle) {
|
||||
return new CssIterable(inlineStyle);
|
||||
}
|
||||
|
||||
private static class CssIterable implements Iterable<CssProperty> {
|
||||
|
||||
private final String input;
|
||||
|
||||
CssIterable(@NonNull String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Iterator<CssProperty> iterator() {
|
||||
return new CssIterator();
|
||||
}
|
||||
|
||||
private class CssIterator implements Iterator<CssProperty> {
|
||||
|
||||
private final CssProperty cssProperty = new CssProperty();
|
||||
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
|
||||
private final int length = input.length();
|
||||
|
||||
private int index;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
||||
prepareNext();
|
||||
|
||||
return hasNextPrepared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CssProperty next() {
|
||||
if (!hasNextPrepared()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return cssProperty;
|
||||
}
|
||||
|
||||
private void prepareNext() {
|
||||
|
||||
// clear first
|
||||
cssProperty.set("", "");
|
||||
|
||||
builder.setLength(0);
|
||||
|
||||
String key = null;
|
||||
String value = null;
|
||||
|
||||
char c;
|
||||
|
||||
boolean keyHasWhiteSpace = false;
|
||||
|
||||
for (int i = index; i < length; i++) {
|
||||
|
||||
c = input.charAt(i);
|
||||
|
||||
// if we are building KEY, then when we encounter WS (white-space) we finish
|
||||
// KEY and wait for the ':', if we do not find it and we find EOF or ';'
|
||||
// we start creating KEY again after the ';'
|
||||
|
||||
if (key == null) {
|
||||
|
||||
if (':' == c) {
|
||||
|
||||
// we have no key yet, but we might have started creating it already
|
||||
if (builder.length() > 0) {
|
||||
key = builder.toString().trim();
|
||||
}
|
||||
|
||||
builder.setLength(0);
|
||||
|
||||
} else {
|
||||
// if by any chance we have here the ';' -> reset key and try to match next
|
||||
if (';' == c) {
|
||||
builder.setLength(0);
|
||||
} else {
|
||||
|
||||
// key cannot have WS gaps (but leading and trailing are OK)
|
||||
if (Character.isWhitespace(c)) {
|
||||
if (builder.length() > 0) {
|
||||
keyHasWhiteSpace = true;
|
||||
}
|
||||
} else {
|
||||
// if not a WS and we have found WS before, start a-new
|
||||
// else append
|
||||
if (keyHasWhiteSpace) {
|
||||
// start new filling
|
||||
builder.setLength(0);
|
||||
builder.append(c);
|
||||
// clear this flag
|
||||
keyHasWhiteSpace = false;
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value == null) {
|
||||
|
||||
if (Character.isWhitespace(c)) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(c);
|
||||
}
|
||||
} else if (';' == c) {
|
||||
|
||||
value = builder.toString().trim();
|
||||
builder.setLength(0);
|
||||
|
||||
// check if we have valid values -> if yes -> return it
|
||||
if (hasValues(key, value)) {
|
||||
index = i + 1;
|
||||
cssProperty.set(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// here we must additionally check for EOF (we might be tracking value here)
|
||||
if (key != null
|
||||
&& builder.length() > 0) {
|
||||
value = builder.toString().trim();
|
||||
cssProperty.set(key, value);
|
||||
index = length;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasNextPrepared() {
|
||||
return hasValues(cssProperty.key(), cssProperty.value());
|
||||
}
|
||||
|
||||
private boolean hasValues(@Nullable String key, @Nullable String value) {
|
||||
return !TextUtils.isEmpty(key)
|
||||
&& !TextUtils.isEmpty(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class CssProperty {
|
||||
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
CssProperty() {
|
||||
}
|
||||
|
||||
void set(@NonNull String key, @NonNull String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public CssProperty mutate() {
|
||||
final CssProperty cssProperty = new CssProperty();
|
||||
cssProperty.set(this.key, this.value);
|
||||
return cssProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CssProperty{" +
|
||||
"key='" + key + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
package ru.noties.markwon.renderer.html2;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ix.Ix;
|
||||
import ix.IxFunction;
|
||||
import ru.noties.markwon.test.TestUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static ru.noties.markwon.test.TestUtils.with;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class CssInlineStyleParserTest {
|
||||
|
||||
private CssInlineStyleParser.Impl impl;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
impl = new CssInlineStyleParser.Impl();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_single_pair() {
|
||||
|
||||
final String input = "key: value;";
|
||||
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_two_pairs() {
|
||||
|
||||
final String input = "key1: value1; key2: value2;";
|
||||
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key2", cssProperty.key());
|
||||
assertEquals("value2", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void one_pair_eof() {
|
||||
|
||||
final String input = "key: value";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void one_pair_eof_whitespaces() {
|
||||
|
||||
final String input = "key: value \n\n\t";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key", cssProperty.key());
|
||||
assertEquals("value", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void white_spaces() {
|
||||
|
||||
final String input = "\n\n\n\t \t key1 \n\n\n\t : \n\n\n\n \t value1 \n\n\n\n ; \n key2\n : \n value2 \n ; ";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key2", cssProperty.key());
|
||||
assertEquals("value2", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys() {
|
||||
|
||||
final String input = "key1 key2 key3 key4";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys_and_value() {
|
||||
|
||||
final String input = "key1 key2 key3 key4: value4";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(1, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key4", cssProperty.key());
|
||||
assertEquals("value4", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void list_of_keys_separated_by_semi_colon() {
|
||||
|
||||
final String input = "key1;key2;key3;key4;";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void key_value_with_invalid_between() {
|
||||
|
||||
final String input = "key1: value1; key2 key3: value3;";
|
||||
final List<CssProperty> list = listProperties(input);
|
||||
|
||||
assertEquals(2, list.size());
|
||||
|
||||
with(list.get(0), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key1", cssProperty.key());
|
||||
assertEquals("value1", cssProperty.value());
|
||||
}
|
||||
});
|
||||
|
||||
with(list.get(1), new TestUtils.Action<CssProperty>() {
|
||||
@Override
|
||||
public void apply(@NonNull CssProperty cssProperty) {
|
||||
assertEquals("key3", cssProperty.key());
|
||||
assertEquals("value3", cssProperty.value());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void css_functions() {
|
||||
|
||||
final Map<String, String> map = new HashMap<String, String>() {{
|
||||
put("attr", "\" (\" attr(href) \")\"");
|
||||
put("calc", "calc(100% - 100px)");
|
||||
put("cubic-bezier", "cubic-bezier(0.1, 0.7, 1.0, 0.1)");
|
||||
put("hsl", "hsl(120,100%,50%)");
|
||||
put("hsla", "hsla(120,100%,50%,0.3)");
|
||||
put("linear-gradient", "linear-gradient(red, yellow, blue)");
|
||||
put("radial-gradient", "radial-gradient(red, green, blue)");
|
||||
put("repeating-linear-gradient", "repeating-linear-gradient(red, yellow 10%, green 20%)");
|
||||
put("repeating-radial-gradient", "repeating-radial-gradient(red, yellow 10%, green 15%)");
|
||||
put("rgb", "rgb(255,0,0)");
|
||||
put("rgba", "rgba(255,0,0,0.3)");
|
||||
put("var", "var(--some-variable)");
|
||||
put("url", "url(\"url.gif\")");
|
||||
}};
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry: map.entrySet()) {
|
||||
builder.append(entry.getKey())
|
||||
.append(':')
|
||||
.append(entry.getValue())
|
||||
.append(';');
|
||||
}
|
||||
|
||||
for (CssProperty cssProperty: impl.parse(builder.toString())) {
|
||||
final String value = map.remove(cssProperty.key());
|
||||
assertNotNull(cssProperty.key(), value);
|
||||
assertEquals(cssProperty.key(), value, cssProperty.value());
|
||||
}
|
||||
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<CssProperty> listProperties(@NonNull String input) {
|
||||
return Ix.from(impl.parse(input))
|
||||
.map(new IxFunction<CssProperty, CssProperty>() {
|
||||
@Override
|
||||
public CssProperty apply(CssProperty cssProperty) {
|
||||
return cssProperty.mutate();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
17
markwon/src/test/java/ru/noties/markwon/test/TestUtils.java
Normal file
17
markwon/src/test/java/ru/noties/markwon/test/TestUtils.java
Normal file
@ -0,0 +1,17 @@
|
||||
package ru.noties.markwon.test;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public abstract class TestUtils {
|
||||
|
||||
public interface Action<T> {
|
||||
void apply(@NonNull T t);
|
||||
}
|
||||
|
||||
public static <T> void with(@NonNull T t, @NonNull Action<T> action) {
|
||||
action.apply(t);
|
||||
}
|
||||
|
||||
private TestUtils() {
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user